+import { initializeSite } from "@utils/app";
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 { initializeSite, isAuthPath } from "@utils/app";
+import { ErrorPageData } from "@utils/types";
import type { Request, Response } from "express";
import { StaticRouter, matchPath } from "inferno-router";
import { renderToString } from "inferno-server";
FailedRequestState,
wrapClient,
} from "../../shared/services/HttpService";
-import { ErrorPageData, initializeSite, isAuthPath } from "../../shared/utils";
import { createSsrHtml } from "../utils/create-ssr-html";
import { getErrorPageData } from "../utils/get-error-page-data";
import { setForwardedHeaders } from "../utils/set-forwarded-headers";
import { renderToString } from "inferno-server";
import serialize from "serialize-javascript";
import sharp from "sharp";
+import { favIconPngUrl, favIconUrl } from "../../shared/config";
import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces";
-import { favIconPngUrl, favIconUrl } from "../../shared/utils";
import { fetchIconPng } from "./fetch-icon-png";
const customHtmlHeader = process.env["LEMMY_UI_CUSTOM_HTML_HEADER"] || "";
+import { ErrorPageData } from "@utils/types";
import { GetSiteResponse } from "lemmy-js-client";
-import { ErrorPageData } from "../../shared/utils";
export function getErrorPageData(error: Error, site?: GetSiteResponse) {
const errorPageData: ErrorPageData = {};
-import { Component, createRef, linkEvent, RefObject } from "inferno";
+import { isAuthPath, setIsoData } from "@utils/app";
+import { Component, RefObject, createRef, linkEvent } from "inferno";
import { Provider } from "inferno-i18next-dess";
import { Route, Switch } from "inferno-router";
import { i18n } from "../../i18next";
import { IsoDataOptionalSite } from "../../interfaces";
import { routes } from "../../routes";
-import { isAuthPath, setIsoData } from "../../utils";
import AuthGuard from "../common/auth-guard";
import ErrorGuard from "../common/error-guard";
import { ErrorPage } from "./error-page";
+import { setIsoData } from "@utils/app";
import { Component } from "inferno";
import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router";
import { i18n } from "../../i18next";
import { IsoDataOptionalSite } from "../../interfaces";
-import { setIsoData } from "../../utils";
export class ErrorPage extends Component<any, any> {
private isoData: IsoDataOptionalSite = setIsoData(this.context);
import { Component } from "inferno";
import { NavLink } from "inferno-router";
import { GetSiteResponse } from "lemmy-js-client";
+import { docsUrl, joinLemmyUrl, repoUrl } from "../../config";
import { i18n } from "../../i18next";
-import { docsUrl, joinLemmyUrl, repoUrl } from "../../utils";
import { VERSION } from "../../version";
interface FooterProps {
+import { myAuth, showAvatars } from "@utils/app";
import { isBrowser } from "@utils/browser";
-import { poll } from "@utils/helpers";
+import { numToSI, poll } from "@utils/helpers";
import { amAdmin, canCreateCommunity } from "@utils/roles";
import { Component, createRef, linkEvent } from "inferno";
import { NavLink } from "inferno-router";
GetUnreadCountResponse,
GetUnreadRegistrationApplicationCountResponse,
} from "lemmy-js-client";
+import { donateLemmyUrl, updateUnreadCountsInterval } from "../../config";
import { i18n } from "../../i18next";
import { UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
-import {
- donateLemmyUrl,
- myAuth,
- numToSI,
- showAvatars,
- toast,
- updateUnreadCountsInterval,
-} from "../../utils";
+import { toast } from "../../toast";
import { Icon } from "../common/icon";
import { PictrsImage } from "../common/pictrs-image";
+import { myAuthRequired } from "@utils/app";
+import { capitalizeFirstLetter } from "@utils/helpers";
import { Component } from "inferno";
import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router";
import { i18n } from "../../i18next";
import { CommentNodeI } from "../../interfaces";
import { UserService } from "../../services";
-import { capitalizeFirstLetter, myAuthRequired } from "../../utils";
import { Icon } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
+import {
+ colorList,
+ getCommentParentId,
+ myAuth,
+ myAuthRequired,
+ newVote,
+ showScores,
+} from "@utils/app";
+import { futureDaysToUnixTime, numToSI } from "@utils/helpers";
import {
amCommunityCreator,
canAdmin,
TransferCommunity,
} from "lemmy-js-client";
import moment from "moment";
+import { commentTreeMaxDepth } from "../../config";
import { i18n } from "../../i18next";
import {
BanType,
PurgeType,
VoteType,
} from "../../interfaces";
+import { mdToHtml, mdToHtmlNoImages } from "../../markdown";
import { UserService } from "../../services";
-import {
- colorList,
- commentTreeMaxDepth,
- futureDaysToUnixTime,
- getCommentParentId,
- mdToHtml,
- mdToHtmlNoImages,
- myAuth,
- myAuthRequired,
- newVote,
- numToSI,
- setupTippy,
- showScores,
-} from "../../utils";
+import { setupTippy } from "../../tippy";
import { Icon, PurgeWarning, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time";
import { CommunityLink } from "../community/community-link";
+import { colorList } from "@utils/app";
import classNames from "classnames";
import { Component } from "inferno";
import {
TransferCommunity,
} from "lemmy-js-client";
import { CommentNodeI, CommentViewType } from "../../interfaces";
-import { colorList } from "../../utils";
import { CommentNode } from "./comment-node";
interface CommentNodesProps {
+import { myAuthRequired } from "@utils/app";
import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import {
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { CommentNodeI, CommentViewType } from "../../interfaces";
-import { myAuthRequired } from "../../utils";
import { Icon, Spinner } from "../common/icon";
import { PersonListing } from "../person/person-listing";
import { CommentNode } from "./comment-node";
+import { numToSI } from "@utils/helpers";
import { Link } from "inferno-router";
import {
CommunityAggregates,
SiteAggregates,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { numToSI } from "../../utils";
interface BadgesProps {
counts: CommunityAggregates | SiteAggregates;
+import { randomStr } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import { CommentSortType } from "lemmy-js-client";
+import { relTags, sortingHelpUrl } from "../../config";
import { i18n } from "../../i18next";
-import { randomStr, relTags, sortingHelpUrl } from "../../utils";
import { Icon } from "./icon";
interface CommentSortSelectProps {
import { Component } from "inferno";
-import { getEmojiMart } from "../../utils";
+import { getEmojiMart } from "../../markdown";
interface EmojiMartProps {
onEmojiClick?(val: any): any;
+import { setIsoData } from "@utils/app";
import { Component } from "inferno";
-import { setIsoData } from "../../utils";
import { ErrorPage } from "../app/error-page";
class ErrorGuard extends Component<any, any> {
import { Helmet } from "inferno-helmet";
import { httpExternalPath } from "../../env";
import { i18n } from "../../i18next";
-import { md } from "../../utils";
+import { md } from "../../markdown";
interface HtmlTagsProps {
title: string;
+import { randomStr } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import { i18n } from "../../i18next";
import { HttpService, UserService } from "../../services";
-import { randomStr, toast } from "../../utils";
+import { toast } from "../../toast";
import { Icon } from "./icon";
interface ImageUploadFormProps {
+import { selectableLanguages } from "@utils/app";
+import { randomStr } from "@utils/helpers";
import classNames from "classnames";
import { Component, linkEvent } from "inferno";
import { Language } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { UserService } from "../../services/UserService";
-import { randomStr, selectableLanguages } from "../../utils";
import { Icon } from "./icon";
interface LanguageSelectProps {
+import { randomStr } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import { ListingType } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { UserService } from "../../services";
-import { randomStr } from "../../utils";
interface ListingTypeSelectProps {
type_: ListingType;
import { isBrowser } from "@utils/browser";
+import { numToSI, randomStr } from "@utils/helpers";
import autosize from "autosize";
import classNames from "classnames";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import { Language } from "lemmy-js-client";
-import { i18n } from "../../i18next";
-import { HttpService, UserService } from "../../services";
import {
concurrentImageUpload,
- customEmojisLookup,
markdownFieldCharacterLimit,
markdownHelpUrl,
maxUploadImages,
- mdToHtml,
- numToSI,
- pictrsDeleteToast,
- randomStr,
relTags,
- setupTippy,
- setupTribute,
- toast,
-} from "../../utils";
+} from "../../config";
+import { i18n } from "../../i18next";
+import { customEmojisLookup, mdToHtml, setupTribute } from "../../markdown";
+import { HttpService, UserService } from "../../services";
+import { setupTippy } from "../../tippy";
+import { pictrsDeleteToast, toast } from "../../toast";
import { EmojiPicker } from "./emoji-picker";
import { Icon, Spinner } from "./icon";
import { LanguageSelect } from "./language-select";
+import { capitalizeFirstLetter } from "@utils/helpers";
import { Component } from "inferno";
import moment from "moment";
import { i18n } from "../../i18next";
-import { capitalizeFirstLetter } from "../../utils";
import { Icon } from "./icon";
interface MomentTimeProps {
+import { ThemeColor } from "@utils/types";
import classNames from "classnames";
-import { ThemeColor } from "../../utils";
interface ProgressBarProps {
className?: string;
+import { myAuthRequired } from "@utils/app";
import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import {
RegistrationApplicationView,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { mdToHtml, myAuthRequired } from "../../utils";
+import { mdToHtml } from "../../markdown";
import { PersonListing } from "../person/person-listing";
import { Spinner } from "./icon";
import { MarkdownTextArea } from "./markdown-textarea";
+import { Choice } from "@utils/types";
import classNames from "classnames";
import {
ChangeEvent,
RefObject,
} from "inferno";
import { i18n } from "../../i18next";
-import { Choice } from "../../utils";
import { Icon, Spinner } from "./icon";
interface SearchableSelectProps {
+import { randomStr } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import { SortType } from "lemmy-js-client";
+import { relTags, sortingHelpUrl } from "../../config";
import { i18n } from "../../i18next";
-import { randomStr, relTags, sortingHelpUrl } from "../../utils";
import { Icon } from "./icon";
interface SortSelectProps {
-import { getQueryParams, getQueryString } from "@utils/helpers";
+import {
+ editCommunity,
+ myAuth,
+ myAuthRequired,
+ setIsoData,
+ showLocal,
+} from "@utils/app";
+import {
+ getPageFromString,
+ getQueryParams,
+ getQueryString,
+ numToSI,
+} from "@utils/helpers";
import type { QueryParams } from "@utils/types";
+import { RouteDataResponse } from "@utils/types";
import { Component, linkEvent } from "inferno";
import {
CommunityResponse,
import { InitialFetchRequest } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
-import {
- RouteDataResponse,
- editCommunity,
- getPageFromString,
- myAuth,
- myAuthRequired,
- numToSI,
- setIsoData,
- showLocal,
-} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import { ListingTypeSelect } from "../common/listing-type-select";
+import { myAuthRequired } from "@utils/app";
+import { capitalizeFirstLetter, randomStr } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import {
CommunityView,
Language,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { capitalizeFirstLetter, myAuthRequired, randomStr } from "../../utils";
import { Icon, Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form";
import { LanguageSelect } from "../common/language-select";
+import { showAvatars } from "@utils/app";
+import { hostname } from "@utils/helpers";
import { Component } from "inferno";
import { Link } from "inferno-router";
import { Community } from "lemmy-js-client";
-import { hostname, relTags, showAvatars } from "../../utils";
+import { relTags } from "../../config";
import { PictrsImage } from "../common/pictrs-image";
interface CommunityLinkProps {
-import { getQueryParams, getQueryString } from "@utils/helpers";
+import {
+ commentsToFlatNodes,
+ communityRSSUrl,
+ editComment,
+ editPost,
+ editWith,
+ enableDownvotes,
+ enableNsfw,
+ getCommentParentId,
+ getDataTypeString,
+ myAuth,
+ postToCommentSortType,
+ setIsoData,
+ showLocal,
+ updateCommunityBlock,
+ updatePersonBlock,
+} from "@utils/app";
+import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
+import {
+ getPageFromString,
+ getQueryParams,
+ getQueryString,
+} from "@utils/helpers";
import type { QueryParams } from "@utils/types";
+import { RouteDataResponse } from "@utils/types";
import { Component, linkEvent } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route";
import {
SortType,
TransferCommunity,
} from "lemmy-js-client";
+import { fetchLimit, relTags } from "../../config";
import { i18n } from "../../i18next";
import {
CommentViewType,
import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
-import {
- RouteDataResponse,
- commentsToFlatNodes,
- communityRSSUrl,
- editComment,
- editPost,
- editWith,
- enableDownvotes,
- enableNsfw,
- fetchLimit,
- getCommentParentId,
- getDataTypeString,
- getPageFromString,
- myAuth,
- postToCommentSortType,
- relTags,
- restoreScrollPosition,
- saveScrollPosition,
- setIsoData,
- setupTippy,
- showLocal,
- toast,
- updateCommunityBlock,
- updatePersonBlock,
-} from "../../utils";
+import { setupTippy } from "../../tippy";
+import { toast } from "../../toast";
import { CommentNodes } from "../comment/comment-nodes";
import { BannerIconHeader } from "../common/banner-icon-header";
import { DataTypeSelect } from "../common/data-type-select";
+import { enableNsfw, setIsoData } from "@utils/app";
import { Component } from "inferno";
import {
CreateCommunity as CreateCommunityI,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { HttpService } from "../../services/HttpService";
-import { enableNsfw, setIsoData } from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { CommunityForm } from "./community-form";
+import { myAuthRequired } from "@utils/app";
+import { getUnixTime, hostname } from "@utils/helpers";
import { amAdmin, amMod, amTopMod } from "@utils/roles";
import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
RemoveCommunity,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
+import { mdToHtml } from "../../markdown";
import { UserService } from "../../services";
-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";
+import {
+ fetchThemeList,
+ myAuthRequired,
+ setIsoData,
+ showLocal,
+} from "@utils/app";
+import { capitalizeFirstLetter } from "@utils/helpers";
+import { RouteDataResponse } from "@utils/types";
import classNames from "classnames";
import { Component, linkEvent } from "inferno";
import {
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
+import { removeFromEmojiDataModel, updateEmojiDataModel } from "../../markdown";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
-import {
- RouteDataResponse,
- capitalizeFirstLetter,
- fetchThemeList,
- myAuthRequired,
- removeFromEmojiDataModel,
- setIsoData,
- showLocal,
- toast,
- updateEmojiDataModel,
-} from "../../utils";
+import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import Tabs from "../common/tabs";
+import { myAuthRequired, setIsoData } from "@utils/app";
import { Component, linkEvent } from "inferno";
import {
CreateCustomEmoji,
GetSiteResponse,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
+import { customEmojisLookup } from "../../markdown";
import { HttpService } from "../../services/HttpService";
-import {
- customEmojisLookup,
- myAuthRequired,
- pictrsDeleteToast,
- setIsoData,
- toast,
-} from "../../utils";
+import { pictrsDeleteToast, toast } from "../../toast";
import { EmojiMart } from "../common/emoji-mart";
import { HtmlTags } from "../common/html-tags";
import { Icon } from "../common/icon";
-import { getQueryParams, getQueryString } from "@utils/helpers";
+import {
+ commentsToFlatNodes,
+ editComment,
+ editPost,
+ editWith,
+ enableDownvotes,
+ enableNsfw,
+ getCommentParentId,
+ getDataTypeString,
+ myAuth,
+ postToCommentSortType,
+ setIsoData,
+ showLocal,
+ updatePersonBlock,
+} from "@utils/app";
+import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
+import {
+ getPageFromString,
+ getQueryParams,
+ getQueryString,
+ getRandomFromList,
+} from "@utils/helpers";
import { canCreateCommunity } from "@utils/roles";
import type { QueryParams } from "@utils/types";
+import { RouteDataResponse } from "@utils/types";
import { NoOptionI18nKeys } from "i18next";
import { Component, MouseEventHandler, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
SortType,
TransferCommunity,
} from "lemmy-js-client";
+import { fetchLimit, relTags, trendingFetchLimit } from "../../config";
import { i18n } from "../../i18next";
import {
CommentViewType,
DataType,
InitialFetchRequest,
} from "../../interfaces";
+import { mdToHtml } from "../../markdown";
import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
-import {
- RouteDataResponse,
- commentsToFlatNodes,
- editComment,
- editPost,
- editWith,
- enableDownvotes,
- enableNsfw,
- fetchLimit,
- getCommentParentId,
- getDataTypeString,
- getPageFromString,
- getRandomFromList,
- mdToHtml,
- myAuth,
- postToCommentSortType,
- relTags,
- restoreScrollPosition,
- saveScrollPosition,
- setIsoData,
- setupTippy,
- showLocal,
- toast,
- trendingFetchLimit,
- updatePersonBlock,
-} from "../../utils";
+import { setupTippy } from "../../tippy";
+import { toast } from "../../toast";
import { CommentNodes } from "../comment/comment-nodes";
import { DataTypeSelect } from "../common/data-type-select";
import { HtmlTags } from "../common/html-tags";
+import { setIsoData } from "@utils/app";
+import { RouteDataResponse } from "@utils/types";
import { Component } from "inferno";
import {
GetFederatedInstancesResponse,
GetSiteResponse,
Instance,
} from "lemmy-js-client";
+import { relTags } from "../../config";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
-import { RouteDataResponse, relTags, setIsoData } from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
+import { setIsoData } from "@utils/app";
import { Component } from "inferno";
import { GetSiteResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { mdToHtml, setIsoData } from "../../utils";
+import { mdToHtml } from "../../markdown";
import { HtmlTags } from "../common/html-tags";
interface LegalState {
+import { myAuth, setIsoData } from "@utils/app";
import { isBrowser } from "@utils/browser";
+import { validEmail } from "@utils/helpers";
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 { myAuth, setIsoData, toast, validEmail } from "../../utils";
+import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
+import { myAuthRequired } from "@utils/app";
+import { capitalizeFirstLetter } from "@utils/helpers";
import classNames from "classnames";
import { Component, FormEventHandler, linkEvent } from "inferno";
import { EditSite, LocalSiteRateLimit } from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { capitalizeFirstLetter, myAuthRequired } from "../../utils";
import { Spinner } from "../common/icon";
import Tabs from "../common/tabs";
+import { fetchThemeList, setIsoData } from "@utils/app";
import { Component, linkEvent } from "inferno";
import { Helmet } from "inferno-helmet";
import {
import { i18n } from "../../i18next";
import { UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
-import { fetchThemeList, setIsoData } from "../../utils";
import { Spinner } from "../common/icon";
import { SiteForm } from "./site-form";
+import { myAuth, setIsoData } from "@utils/app";
import { isBrowser } from "@utils/browser";
+import { validEmail } from "@utils/helpers";
import { Options, passwordStrength } from "check-password-strength";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
LoginResponse,
SiteView,
} from "lemmy-js-client";
+import { joinLemmyUrl } from "../../config";
import { i18n } from "../../i18next";
+import { mdToHtml } from "../../markdown";
import { UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
-import {
- joinLemmyUrl,
- mdToHtml,
- myAuth,
- setIsoData,
- toast,
- validEmail,
-} from "../../utils";
+import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
+import { myAuthRequired } from "@utils/app";
+import { capitalizeFirstLetter, validInstanceTLD } from "@utils/helpers";
import {
Component,
InfernoKeyboardEvent,
ListingType,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
-import {
- capitalizeFirstLetter,
- myAuthRequired,
- validInstanceTLD,
-} from "../../utils";
import { Icon, Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form";
import { LanguageSelect } from "../common/language-select";
import { Component, linkEvent } from "inferno";
import { PersonView, Site, SiteAggregates } from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { mdToHtml } from "../../utils";
+import { mdToHtml } from "../../markdown";
import { Badges } from "../common/badges";
import { BannerIconHeader } from "../common/banner-icon-header";
import { Icon } from "../common/icon";
+import { myAuthRequired } from "@utils/app";
+import { capitalizeFirstLetter } from "@utils/helpers";
import { Component, InfernoMouseEvent, linkEvent } from "inferno";
import { EditSite, Tagline } from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { capitalizeFirstLetter, myAuthRequired } from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
-import { debounce, getQueryParams, getQueryString } from "@utils/helpers";
+import {
+ fetchUsers,
+ getUpdatedSearchId,
+ myAuth,
+ personToChoice,
+ setIsoData,
+} from "@utils/app";
+import {
+ debounce,
+ getIdFromString,
+ getPageFromString,
+ getQueryParams,
+ getQueryString,
+} from "@utils/helpers";
import { amAdmin, amMod } from "@utils/roles";
import type { QueryParams } from "@utils/types";
+import { Choice, RouteDataResponse } from "@utils/types";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
Person,
} from "lemmy-js-client";
import moment from "moment";
+import { fetchLimit } from "../config";
import { i18n } from "../i18next";
import { InitialFetchRequest } from "../interfaces";
import { FirstLoadService } from "../services/FirstLoadService";
import { HttpService, RequestState } from "../services/HttpService";
-import {
- Choice,
- RouteDataResponse,
- fetchLimit,
- fetchUsers,
- getIdFromString,
- getPageFromString,
- getUpdatedSearchId,
- myAuth,
- personToChoice,
- setIsoData,
-} from "../utils";
import { HtmlTags } from "./common/html-tags";
import { Icon, Spinner } from "./common/icon";
import { MomentTime } from "./common/moment-time";
+import {
+ commentsToFlatNodes,
+ editCommentReply,
+ editMention,
+ editPrivateMessage,
+ editWith,
+ enableDownvotes,
+ getCommentParentId,
+ myAuth,
+ myAuthRequired,
+ setIsoData,
+ updatePersonBlock,
+} from "@utils/app";
+import { RouteDataResponse } from "@utils/types";
import { Component, linkEvent } from "inferno";
import {
AddAdmin,
SaveComment,
TransferCommunity,
} from "lemmy-js-client";
+import { fetchLimit, relTags } from "../../config";
import { i18n } from "../../i18next";
import { CommentViewType, InitialFetchRequest } from "../../interfaces";
import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
-import {
- RouteDataResponse,
- commentsToFlatNodes,
- editCommentReply,
- editMention,
- editPrivateMessage,
- editWith,
- enableDownvotes,
- fetchLimit,
- getCommentParentId,
- myAuth,
- myAuthRequired,
- relTags,
- setIsoData,
- toast,
- updatePersonBlock,
-} from "../../utils";
+import { toast } from "../../toast";
import { CommentNodes } from "../comment/comment-nodes";
import { CommentSortSelect } from "../common/comment-sort-select";
import { HtmlTags } from "../common/html-tags";
+import { myAuth, setIsoData } from "@utils/app";
+import { capitalizeFirstLetter } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { HttpService, UserService } from "../../services";
import { RequestState } from "../../services/HttpService";
-import { capitalizeFirstLetter, myAuth, setIsoData } from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
+import { commentsToFlatNodes } from "@utils/app";
import { Component } from "inferno";
import {
AddAdmin,
TransferCommunity,
} from "lemmy-js-client";
import { CommentViewType, PersonDetailsView } from "../../interfaces";
-import { commentsToFlatNodes, setupTippy } from "../../utils";
+import { setupTippy } from "../../tippy";
import { CommentNodes } from "../comment/comment-nodes";
import { Paginator } from "../common/paginator";
import { PostListing } from "../post/post-listing";
+import { showAvatars } from "@utils/app";
+import { hostname, isCakeDay } from "@utils/helpers";
import classNames from "classnames";
import { Component } from "inferno";
import { Link } from "inferno-router";
import { Person } from "lemmy-js-client";
-import { hostname, isCakeDay, relTags, showAvatars } from "../../utils";
+import { relTags } from "../../config";
import { PictrsImage } from "../common/pictrs-image";
import { CakeDay } from "./cake-day";
-import { getQueryParams, getQueryString } from "@utils/helpers";
+import {
+ editComment,
+ editPost,
+ editWith,
+ enableDownvotes,
+ enableNsfw,
+ getCommentParentId,
+ myAuth,
+ myAuthRequired,
+ setIsoData,
+ updatePersonBlock,
+} from "@utils/app";
+import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
+import {
+ capitalizeFirstLetter,
+ futureDaysToUnixTime,
+ getPageFromString,
+ getQueryParams,
+ getQueryString,
+ numToSI,
+} from "@utils/helpers";
import { canMod, isAdmin, isBanned } from "@utils/roles";
import type { QueryParams } from "@utils/types";
+import { RouteDataResponse } from "@utils/types";
import classNames from "classnames";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
TransferCommunity,
} from "lemmy-js-client";
import moment from "moment";
+import { fetchLimit, relTags } from "../../config";
import { i18n } from "../../i18next";
import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
+import { mdToHtml } from "../../markdown";
import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
-import {
- RouteDataResponse,
- capitalizeFirstLetter,
- editComment,
- editPost,
- editWith,
- enableDownvotes,
- enableNsfw,
- fetchLimit,
- futureDaysToUnixTime,
- getCommentParentId,
- getPageFromString,
- mdToHtml,
- myAuth,
- myAuthRequired,
- numToSI,
- relTags,
- restoreScrollPosition,
- saveScrollPosition,
- setIsoData,
- setupTippy,
- toast,
- updatePersonBlock,
-} from "../../utils";
+import { setupTippy } from "../../tippy";
+import { toast } from "../../toast";
import { BannerIconHeader } from "../common/banner-icon-header";
import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon";
+import {
+ editRegistrationApplication,
+ myAuthRequired,
+ setIsoData,
+} from "@utils/app";
+import { RouteDataResponse } from "@utils/types";
import { Component, linkEvent } from "inferno";
import {
ApproveRegistrationApplication,
ListRegistrationApplicationsResponse,
RegistrationApplicationView,
} from "lemmy-js-client";
+import { fetchLimit } from "../../config";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
-import {
- RouteDataResponse,
- editRegistrationApplication,
- fetchLimit,
- myAuthRequired,
- setIsoData,
- setupTippy,
-} from "../../utils";
+import { setupTippy } from "../../tippy";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import { Paginator } from "../common/paginator";
+import {
+ editCommentReport,
+ editPostReport,
+ editPrivateMessageReport,
+ myAuthRequired,
+ setIsoData,
+} from "@utils/app";
import { amAdmin } from "@utils/roles";
+import { RouteDataResponse } from "@utils/types";
import { Component, linkEvent } from "inferno";
import {
CommentReportResponse,
ResolvePostReport,
ResolvePrivateMessageReport,
} from "lemmy-js-client";
+import { fetchLimit } from "../../config";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
import { HttpService, UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { RequestState } from "../../services/HttpService";
-import {
- RouteDataResponse,
- editCommentReport,
- editPostReport,
- editPrivateMessageReport,
- fetchLimit,
- myAuthRequired,
- setIsoData,
-} from "../../utils";
import { CommentReport } from "../comment/comment-report";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
-import { debounce } from "@utils/helpers";
+import {
+ communityToChoice,
+ fetchCommunities,
+ fetchThemeList,
+ fetchUsers,
+ myAuth,
+ myAuthRequired,
+ personToChoice,
+ setIsoData,
+ setTheme,
+ showLocal,
+ updateCommunityBlock,
+ updatePersonBlock,
+} from "@utils/app";
+import { capitalizeFirstLetter, debounce } from "@utils/helpers";
+import { Choice } from "@utils/types";
import classNames from "classnames";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
PersonBlockView,
SortType,
} from "lemmy-js-client";
+import { elementUrl, emDash, relTags } from "../../config";
import { i18n, languages } from "../../i18next";
import { UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
-import {
- Choice,
- capitalizeFirstLetter,
- communityToChoice,
- elementUrl,
- emDash,
- fetchCommunities,
- fetchThemeList,
- fetchUsers,
- myAuth,
- myAuthRequired,
- personToChoice,
- relTags,
- setIsoData,
- setTheme,
- setupTippy,
- showLocal,
- toast,
- updateCommunityBlock,
- updatePersonBlock,
-} from "../../utils";
+import { setupTippy } from "../../tippy";
+import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form";
+import { setIsoData } from "@utils/app";
import { Component } from "inferno";
import { GetSiteResponse, VerifyEmailResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { HttpService, RequestState } from "../../services/HttpService";
-import { setIsoData, toast } from "../../utils";
+import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
-import { getQueryParams } from "@utils/helpers";
+import { enableDownvotes, enableNsfw, myAuth, setIsoData } from "@utils/app";
+import { getIdFromString, getQueryParams } from "@utils/helpers";
import type { QueryParams } from "@utils/types";
+import { Choice, RouteDataResponse } from "@utils/types";
import { Component } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route";
import {
RequestState,
WrappedLemmyHttp,
} from "../../services/HttpService";
-import {
- Choice,
- RouteDataResponse,
- enableDownvotes,
- enableNsfw,
- getIdFromString,
- myAuth,
- setIsoData,
-} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import { PostForm } from "./post-form";
import { Component, linkEvent } from "inferno";
import { Post } from "lemmy-js-client";
import * as sanitizeHtml from "sanitize-html";
+import { relTags } from "../../config";
import { i18n } from "../../i18next";
-import { relTags } from "../../utils";
import { Icon } from "../common/icon";
interface MetadataCardProps {
-import { debounce } from "@utils/helpers";
+import {
+ communityToChoice,
+ fetchCommunities,
+ myAuth,
+ myAuthRequired,
+} from "@utils/app";
+import {
+ capitalizeFirstLetter,
+ debounce,
+ getIdFromString,
+ validTitle,
+ validURL,
+} from "@utils/helpers";
+import { isImage } from "@utils/media";
+import { Choice } from "@utils/types";
import autosize from "autosize";
import { Component, InfernoNode, linkEvent } from "inferno";
import {
PostView,
SearchResponse,
} from "lemmy-js-client";
-import { i18n } from "../../i18next";
-import { PostFormParams } from "../../interfaces";
-import { UserService } from "../../services";
-import { HttpService, RequestState } from "../../services/HttpService";
import {
- Choice,
archiveTodayUrl,
- capitalizeFirstLetter,
- communityToChoice,
- fetchCommunities,
- getIdFromString,
ghostArchiveUrl,
- isImage,
- myAuth,
- myAuthRequired,
relTags,
- setupTippy,
- toast,
trendingFetchLimit,
- validTitle,
- validURL,
webArchiveUrl,
-} from "../../utils";
+} from "../../config";
+import { i18n } from "../../i18next";
+import { PostFormParams } from "../../interfaces";
+import { UserService } from "../../services";
+import { HttpService, RequestState } from "../../services/HttpService";
+import { setupTippy } from "../../tippy";
+import { toast } from "../../toast";
import { Icon, Spinner } from "../common/icon";
import { LanguageSelect } from "../common/language-select";
import { MarkdownTextArea } from "../common/markdown-textarea";
+import { myAuthRequired, newVote, showScores } from "@utils/app";
import { canShare, share } from "@utils/browser";
+import { futureDaysToUnixTime, hostname, numToSI } from "@utils/helpers";
+import { isImage, isVideo } from "@utils/media";
import {
amAdmin,
amCommunityCreator,
SavePost,
TransferCommunity,
} from "lemmy-js-client";
+import { relTags } from "../../config";
import { getExternalHost, getHttpBase } from "../../env";
import { i18n } from "../../i18next";
import { BanType, PostFormParams, PurgeType, VoteType } from "../../interfaces";
+import { mdNoImages, mdToHtml, mdToHtmlInline } from "../../markdown";
import { UserService } from "../../services";
-import {
- futureDaysToUnixTime,
- hostname,
- isImage,
- isVideo,
- mdNoImages,
- mdToHtml,
- mdToHtmlInline,
- myAuthRequired,
- newVote,
- numToSI,
- relTags,
- setupTippy,
- showScores,
-} from "../../utils";
+import { setupTippy } from "../../tippy";
import { Icon, PurgeWarning, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time";
import { PictrsImage } from "../common/pictrs-image";
+import { myAuthRequired } from "@utils/app";
import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import { PostReportView, PostView, ResolvePostReport } from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { myAuthRequired } from "../../utils";
import { Icon, Spinner } from "../common/icon";
import { PersonListing } from "../person/person-listing";
import { PostListing } from "./post-listing";
-import { isBrowser } from "@utils/browser";
+import {
+ buildCommentsTree,
+ commentsToFlatNodes,
+ editComment,
+ editWith,
+ enableDownvotes,
+ enableNsfw,
+ getCommentIdFromProps,
+ getCommentParentId,
+ getDepthFromComment,
+ getIdFromProps,
+ myAuth,
+ setIsoData,
+ updateCommunityBlock,
+ updatePersonBlock,
+} from "@utils/app";
+import {
+ isBrowser,
+ restoreScrollPosition,
+ saveScrollPosition,
+} from "@utils/browser";
import { debounce } from "@utils/helpers";
+import { isImage } from "@utils/media";
+import { RouteDataResponse } from "@utils/types";
import autosize from "autosize";
-import { Component, createRef, linkEvent, RefObject } from "inferno";
+import { Component, RefObject, createRef, linkEvent } from "inferno";
import {
AddAdmin,
AddModToCommunity,
SavePost,
TransferCommunity,
} from "lemmy-js-client";
+import { commentTreeMaxDepth } from "../../config";
import { i18n } from "../../i18next";
import {
CommentNodeI,
import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
-import {
- buildCommentsTree,
- commentsToFlatNodes,
- commentTreeMaxDepth,
- editComment,
- editWith,
- enableDownvotes,
- enableNsfw,
- getCommentIdFromProps,
- getCommentParentId,
- getDepthFromComment,
- getIdFromProps,
- isImage,
- myAuth,
- restoreScrollPosition,
- RouteDataResponse,
- saveScrollPosition,
- setIsoData,
- setupTippy,
- toast,
- updateCommunityBlock,
- updatePersonBlock,
-} from "../../utils";
+import { setupTippy } from "../../tippy";
+import { toast } from "../../toast";
import { CommentForm } from "../comment/comment-form";
import { CommentNodes } from "../comment/comment-nodes";
import { HtmlTags } from "../common/html-tags";
+import { getRecipientIdFromProps, myAuth, setIsoData } from "@utils/app";
+import { RouteDataResponse } from "@utils/types";
import { Component } from "inferno";
import {
CreatePrivateMessage as CreatePrivateMessageI,
import { InitialFetchRequest } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
-import {
- RouteDataResponse,
- getRecipientIdFromProps,
- myAuth,
- setIsoData,
- toast,
-} from "../../utils";
+import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import { PrivateMessageForm } from "./private-message-form";
+import { myAuthRequired } from "@utils/app";
+import { capitalizeFirstLetter } from "@utils/helpers";
import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import {
Person,
PrivateMessageView,
} from "lemmy-js-client";
+import { relTags } from "../../config";
import { i18n } from "../../i18next";
-import {
- capitalizeFirstLetter,
- myAuthRequired,
- relTags,
- setupTippy,
-} from "../../utils";
+import { setupTippy } from "../../tippy";
import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
import NavigationPrompt from "../common/navigation-prompt";
+import { myAuthRequired } from "@utils/app";
import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import {
ResolvePrivateMessageReport,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { mdToHtml, myAuthRequired } from "../../utils";
+import { mdToHtml } from "../../markdown";
import { Icon, Spinner } from "../common/icon";
import { PersonListing } from "../person/person-listing";
+import { myAuthRequired } from "@utils/app";
import { Component, InfernoNode, linkEvent } from "inferno";
import {
CreatePrivateMessage,
PrivateMessageView,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
+import { mdToHtml } from "../../markdown";
import { UserService } from "../../services";
-import { mdToHtml, myAuthRequired } from "../../utils";
import { Icon, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time";
import { PersonListing } from "../person/person-listing";
-import { debounce, getQueryParams, getQueryString } from "@utils/helpers";
+import {
+ commentsToFlatNodes,
+ communityToChoice,
+ enableDownvotes,
+ enableNsfw,
+ fetchCommunities,
+ fetchUsers,
+ getUpdatedSearchId,
+ myAuth,
+ personToChoice,
+ setIsoData,
+ showLocal,
+} from "@utils/app";
+import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
+import {
+ capitalizeFirstLetter,
+ debounce,
+ getIdFromString,
+ getPageFromString,
+ getQueryParams,
+ getQueryString,
+ numToSI,
+} from "@utils/helpers";
import type { QueryParams } from "@utils/types";
+import { Choice, RouteDataResponse } from "@utils/types";
import type { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import {
SearchType,
SortType,
} from "lemmy-js-client";
+import { fetchLimit } from "../config";
import { i18n } from "../i18next";
import { CommentViewType, InitialFetchRequest } from "../interfaces";
import { FirstLoadService } from "../services/FirstLoadService";
import { HttpService, RequestState } from "../services/HttpService";
-import {
- Choice,
- RouteDataResponse,
- capitalizeFirstLetter,
- commentsToFlatNodes,
- communityToChoice,
- enableDownvotes,
- enableNsfw,
- fetchCommunities,
- fetchLimit,
- fetchUsers,
- getIdFromString,
- getPageFromString,
- getUpdatedSearchId,
- myAuth,
- numToSI,
- personToChoice,
- restoreScrollPosition,
- saveScrollPosition,
- setIsoData,
- showLocal,
-} from "../utils";
import { CommentNodes } from "./comment/comment-nodes";
import { HtmlTags } from "./common/html-tags";
import { Spinner } from "./common/icon";
--- /dev/null
+export const favIconUrl = "/static/assets/icons/favicon.svg";
+export const favIconPngUrl = "/static/assets/icons/apple-touch-icon.png";
+
+export const repoUrl = "https://github.com/LemmyNet";
+export const joinLemmyUrl = "https://join-lemmy.org";
+export const donateLemmyUrl = `${joinLemmyUrl}/donate`;
+export const docsUrl = `${joinLemmyUrl}/docs/en/index.html`;
+export const helpGuideUrl = `${joinLemmyUrl}/docs/en/users/01-getting-started.html`; // TODO find a way to redirect to the non-en folder
+export const markdownHelpUrl = `${joinLemmyUrl}/docs/en/users/02-media.html`;
+export const sortingHelpUrl = `${joinLemmyUrl}/docs/en/users/03-votes-and-ranking.html`;
+export const archiveTodayUrl = "https://archive.today";
+export const ghostArchiveUrl = "https://ghostarchive.org";
+export const webArchiveUrl = "https://web.archive.org";
+export const elementUrl = "https://element.io";
+
+export const postRefetchSeconds: number = 60 * 1000;
+export const trendingFetchLimit = 6;
+export const mentionDropdownFetchLimit = 10;
+export const commentTreeMaxDepth = 8;
+export const markdownFieldCharacterLimit = 50000;
+export const maxUploadImages = 20;
+export const concurrentImageUpload = 4;
+export const updateUnreadCountsInterval = 30000;
+export const fetchLimit = 40;
+export const relTags = "noopener nofollow";
+export const emDash = "\u2014";
+import { ErrorPageData } from "@utils/types";
import { CommentView, GetSiteResponse } from "lemmy-js-client";
import type { ParsedQs } from "qs";
import { RequestState, WrappedLemmyHttp } from "./services/HttpService";
-import { ErrorPageData } from "./utils";
/**
* This contains serialized data, it needs to be deserialized before use.
--- /dev/null
+import { communitySearch, personSearch } from "@utils/app";
+import { isBrowser } from "@utils/browser";
+import { debounce, groupBy } from "@utils/helpers";
+import { CommunityTribute, PersonTribute } from "@utils/types";
+import { Picker } from "emoji-mart";
+import emojiShortName from "emoji-short-name";
+import { CustomEmojiView } from "lemmy-js-client";
+import { default as MarkdownIt } from "markdown-it";
+import markdown_it_container from "markdown-it-container";
+import markdown_it_emoji from "markdown-it-emoji/bare";
+import markdown_it_footnote from "markdown-it-footnote";
+import markdown_it_html5_embed from "markdown-it-html5-embed";
+import markdown_it_sub from "markdown-it-sub";
+import markdown_it_sup from "markdown-it-sup";
+import Renderer from "markdown-it/lib/renderer";
+import Token from "markdown-it/lib/token";
+
+export let Tribute: any;
+
+export let md: MarkdownIt = new MarkdownIt();
+
+export let mdNoImages: MarkdownIt = new MarkdownIt();
+
+export const customEmojis: EmojiMartCategory[] = [];
+
+export let customEmojisLookup: Map<string, CustomEmojiView> = new Map<
+ string,
+ CustomEmojiView
+>();
+
+if (isBrowser()) {
+ Tribute = require("tributejs");
+}
+
+export function mdToHtml(text: string) {
+ return { __html: md.render(text) };
+}
+
+export function mdToHtmlNoImages(text: string) {
+ return { __html: mdNoImages.render(text) };
+}
+
+export function mdToHtmlInline(text: string) {
+ return { __html: md.renderInline(text) };
+}
+
+const spoilerConfig = {
+ validate: (params: string) => {
+ return params.trim().match(/^spoiler\s+(.*)$/);
+ },
+
+ render: (tokens: any, idx: any) => {
+ var m = tokens[idx].info.trim().match(/^spoiler\s+(.*)$/);
+
+ if (tokens[idx].nesting === 1) {
+ // opening tag
+ return `<details><summary> ${md.utils.escapeHtml(m[1])} </summary>\n`;
+ } else {
+ // closing tag
+ return "</details>\n";
+ }
+ },
+};
+
+const html5EmbedConfig = {
+ html5embed: {
+ useImageSyntax: true, // Enables video/audio embed with ![]() syntax (default)
+ attributes: {
+ audio: 'controls preload="metadata"',
+ video: 'width="100%" max-height="100%" controls loop preload="metadata"',
+ },
+ },
+};
+
+export function setupMarkdown() {
+ const markdownItConfig: MarkdownIt.Options = {
+ html: false,
+ linkify: true,
+ typographer: true,
+ };
+
+ const emojiDefs = Array.from(customEmojisLookup.entries()).reduce(
+ (main, [key, value]) => ({ ...main, [key]: value }),
+ {}
+ );
+ md = new MarkdownIt(markdownItConfig)
+ .use(markdown_it_sub)
+ .use(markdown_it_sup)
+ .use(markdown_it_footnote)
+ .use(markdown_it_html5_embed, html5EmbedConfig)
+ .use(markdown_it_container, "spoiler", spoilerConfig)
+ .use(markdown_it_emoji, {
+ defs: emojiDefs,
+ });
+
+ mdNoImages = new MarkdownIt(markdownItConfig)
+ .use(markdown_it_sub)
+ .use(markdown_it_sup)
+ .use(markdown_it_footnote)
+ .use(markdown_it_html5_embed, html5EmbedConfig)
+ .use(markdown_it_container, "spoiler", spoilerConfig)
+ .use(markdown_it_emoji, {
+ defs: emojiDefs,
+ })
+ .disable("image");
+ const defaultRenderer = md.renderer.rules.image;
+ md.renderer.rules.image = function (
+ tokens: Token[],
+ idx: number,
+ options: MarkdownIt.Options,
+ env: any,
+ self: Renderer
+ ) {
+ //Provide custom renderer for our emojis to allow us to add a css class and force size dimensions on them.
+ const item = tokens[idx] as any;
+ const title = item.attrs.length >= 3 ? item.attrs[2][1] : "";
+ const src: string = item.attrs[0][1];
+ const isCustomEmoji = customEmojisLookup.get(title) != undefined;
+ if (!isCustomEmoji) {
+ return defaultRenderer?.(tokens, idx, options, env, self) ?? "";
+ }
+ const alt_text = item.content;
+ return `<img class="icon icon-emoji" src="${src}" title="${title}" alt="${alt_text}"/>`;
+ };
+ md.renderer.rules.table_open = function () {
+ return '<table class="table">';
+ };
+}
+
+export function setupEmojiDataModel(custom_emoji_views: CustomEmojiView[]) {
+ const groupedEmojis = groupBy(
+ custom_emoji_views,
+ x => x.custom_emoji.category
+ );
+ for (const [category, emojis] of Object.entries(groupedEmojis)) {
+ customEmojis.push({
+ id: category,
+ name: category,
+ emojis: emojis.map(emoji => ({
+ id: emoji.custom_emoji.shortcode,
+ name: emoji.custom_emoji.shortcode,
+ keywords: emoji.keywords.map(x => x.keyword),
+ skins: [{ src: emoji.custom_emoji.image_url }],
+ })),
+ });
+ }
+ customEmojisLookup = new Map(
+ custom_emoji_views.map(view => [view.custom_emoji.shortcode, view])
+ );
+}
+
+export function updateEmojiDataModel(custom_emoji_view: CustomEmojiView) {
+ const emoji: EmojiMartCustomEmoji = {
+ id: custom_emoji_view.custom_emoji.shortcode,
+ name: custom_emoji_view.custom_emoji.shortcode,
+ keywords: custom_emoji_view.keywords.map(x => x.keyword),
+ skins: [{ src: custom_emoji_view.custom_emoji.image_url }],
+ };
+ const categoryIndex = customEmojis.findIndex(
+ x => x.id == custom_emoji_view.custom_emoji.category
+ );
+ if (categoryIndex == -1) {
+ customEmojis.push({
+ id: custom_emoji_view.custom_emoji.category,
+ name: custom_emoji_view.custom_emoji.category,
+ emojis: [emoji],
+ });
+ } else {
+ const emojiIndex = customEmojis[categoryIndex].emojis.findIndex(
+ x => x.id == custom_emoji_view.custom_emoji.shortcode
+ );
+ if (emojiIndex == -1) {
+ customEmojis[categoryIndex].emojis.push(emoji);
+ } else {
+ customEmojis[categoryIndex].emojis[emojiIndex] = emoji;
+ }
+ }
+ customEmojisLookup.set(
+ custom_emoji_view.custom_emoji.shortcode,
+ custom_emoji_view
+ );
+}
+
+export function removeFromEmojiDataModel(id: number) {
+ let view: CustomEmojiView | undefined;
+ for (const item of customEmojisLookup.values()) {
+ if (item.custom_emoji.id === id) {
+ view = item;
+ break;
+ }
+ }
+ if (!view) return;
+ const categoryIndex = customEmojis.findIndex(
+ x => x.id == view?.custom_emoji.category
+ );
+ const emojiIndex = customEmojis[categoryIndex].emojis.findIndex(
+ x => x.id == view?.custom_emoji.shortcode
+ );
+ customEmojis[categoryIndex].emojis = customEmojis[
+ categoryIndex
+ ].emojis.splice(emojiIndex, 1);
+
+ customEmojisLookup.delete(view?.custom_emoji.shortcode);
+}
+
+export function getEmojiMart(
+ onEmojiSelect: (e: any) => void,
+ customPickerOptions: any = {}
+) {
+ const pickerOptions = {
+ ...customPickerOptions,
+ onEmojiSelect: onEmojiSelect,
+ custom: customEmojis,
+ };
+ return new Picker(pickerOptions);
+}
+
+export function setupTribute() {
+ return new Tribute({
+ noMatchTemplate: function () {
+ return "";
+ },
+ collection: [
+ // Emojis
+ {
+ trigger: ":",
+ menuItemTemplate: (item: any) => {
+ const shortName = `:${item.original.key}:`;
+ return `${item.original.val} ${shortName}`;
+ },
+ selectTemplate: (item: any) => {
+ const customEmoji = customEmojisLookup.get(
+ item.original.key
+ )?.custom_emoji;
+ if (customEmoji == undefined) return `${item.original.val}`;
+ else
+ return `![${customEmoji.alt_text}](${customEmoji.image_url} "${customEmoji.shortcode}")`;
+ },
+ values: Object.entries(emojiShortName)
+ .map(e => {
+ return { key: e[1], val: e[0] };
+ })
+ .concat(
+ Array.from(customEmojisLookup.entries()).map(k => ({
+ key: k[0],
+ val: `<img class="icon icon-emoji" src="${k[1].custom_emoji.image_url}" title="${k[1].custom_emoji.shortcode}" alt="${k[1].custom_emoji.alt_text}" />`,
+ }))
+ ),
+ allowSpaces: false,
+ autocompleteMode: true,
+ // TODO
+ // menuItemLimit: mentionDropdownFetchLimit,
+ menuShowMinLength: 2,
+ },
+ // Persons
+ {
+ trigger: "@",
+ selectTemplate: (item: any) => {
+ const it: PersonTribute = item.original;
+ return `[${it.key}](${it.view.person.actor_id})`;
+ },
+ values: debounce(async (text: string, cb: any) => {
+ cb(await personSearch(text));
+ }),
+ allowSpaces: false,
+ autocompleteMode: true,
+ // TODO
+ // menuItemLimit: mentionDropdownFetchLimit,
+ menuShowMinLength: 2,
+ },
+
+ // Communities
+ {
+ trigger: "!",
+ selectTemplate: (item: any) => {
+ const it: CommunityTribute = item.original;
+ return `[${it.key}](${it.view.community.actor_id})`;
+ },
+ values: debounce(async (text: string, cb: any) => {
+ cb(await communitySearch(text));
+ }),
+ allowSpaces: false,
+ autocompleteMode: true,
+ // TODO
+ // menuItemLimit: mentionDropdownFetchLimit,
+ menuShowMinLength: 2,
+ },
+ ],
+ });
+}
+
+interface EmojiMartCategory {
+ id: string;
+ name: string;
+ emojis: EmojiMartCustomEmoji[];
+}
+
+interface EmojiMartCustomEmoji {
+ id: string;
+ name: string;
+ keywords: string[];
+ skins: EmojiMartSkin[];
+}
+
+interface EmojiMartSkin {
+ src: string;
+}
import { LemmyHttp } from "lemmy-js-client";
import { getHttpBase } from "../../shared/env";
import { i18n } from "../../shared/i18next";
-import { toast } from "../../shared/utils";
+import { toast } from "../../shared/toast";
type EmptyRequestState = {
state: "empty";
// import Cookies from 'js-cookie';
+import { isAuthPath } from "@utils/app";
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, toast } from "../utils";
+import { toast } from "../toast";
interface Claims {
sub: number;
--- /dev/null
+import { isBrowser } from "@utils/browser";
+import tippy from "tippy.js";
+
+export let tippyInstance: any;
+
+if (isBrowser()) {
+ tippyInstance = tippy("[data-tippy-content]");
+}
+
+export function setupTippy() {
+ if (isBrowser()) {
+ tippyInstance.forEach((e: any) => e.destroy());
+ tippyInstance = tippy("[data-tippy-content]", {
+ delay: [500, 0],
+ // Display on "long press"
+ touch: ["hold", 500],
+ });
+ }
+}
--- /dev/null
+import { isBrowser } from "@utils/browser";
+import { ThemeColor } from "@utils/types";
+import Toastify from "toastify-js";
+import { i18n } from "./i18next";
+
+export function toast(text: string, background: ThemeColor = "success") {
+ if (isBrowser()) {
+ const backgroundColor = `var(--bs-${background})`;
+ Toastify({
+ text: text,
+ backgroundColor: backgroundColor,
+ gravity: "bottom",
+ position: "left",
+ duration: 5000,
+ }).showToast();
+ }
+}
+
+export function pictrsDeleteToast(filename: string, deleteUrl: string) {
+ if (isBrowser()) {
+ const clickToDeleteText = i18n.t("click_to_delete_picture", { filename });
+ const deletePictureText = i18n.t("picture_deleted", {
+ filename,
+ });
+ const failedDeletePictureText = i18n.t("failed_to_delete_picture", {
+ filename,
+ });
+
+ const backgroundColor = `var(--bs-light)`;
+
+ const toast = Toastify({
+ text: clickToDeleteText,
+ backgroundColor: backgroundColor,
+ gravity: "top",
+ position: "right",
+ duration: 10000,
+ onClick: () => {
+ if (toast) {
+ fetch(deleteUrl).then(res => {
+ toast.hideToast();
+ if (res.ok === true) {
+ alert(deletePictureText);
+ } else {
+ alert(failedDeletePictureText);
+ }
+ });
+ }
+ },
+ close: true,
+ });
+
+ toast.showToast();
+ }
+}
+++ /dev/null
-import { isBrowser } from "@utils/browser";
-import { debounce, groupBy } from "@utils/helpers";
-import { Picker } from "emoji-mart";
-import emojiShortName from "emoji-short-name";
-import {
- BlockCommunityResponse,
- BlockPersonResponse,
- CommentAggregates,
- Comment as CommentI,
- CommentReplyView,
- CommentReportView,
- CommentSortType,
- CommentView,
- CommunityView,
- CustomEmojiView,
- GetSiteMetadata,
- GetSiteResponse,
- Language,
- LemmyHttp,
- MyUserInfo,
- PersonMentionView,
- PersonView,
- PostReportView,
- PostView,
- PrivateMessageReportView,
- PrivateMessageView,
- RegistrationApplicationView,
- Search,
- SearchType,
- SortType,
-} from "lemmy-js-client";
-import { default as MarkdownIt } from "markdown-it";
-import markdown_it_container from "markdown-it-container";
-import markdown_it_emoji from "markdown-it-emoji/bare";
-import markdown_it_footnote from "markdown-it-footnote";
-import markdown_it_html5_embed from "markdown-it-html5-embed";
-import markdown_it_sub from "markdown-it-sub";
-import markdown_it_sup from "markdown-it-sup";
-import Renderer from "markdown-it/lib/renderer";
-import Token from "markdown-it/lib/token";
-import moment from "moment";
-import tippy from "tippy.js";
-import Toastify from "toastify-js";
-import { getHttpBase } from "./env";
-import { i18n } from "./i18next";
-import {
- CommentNodeI,
- DataType,
- IsoData,
- RouteData,
- VoteType,
-} from "./interfaces";
-import { HttpService, UserService } from "./services";
-import { RequestState } from "./services/HttpService";
-
-let Tribute: any;
-if (isBrowser()) {
- Tribute = require("tributejs");
-}
-
-export const favIconUrl = "/static/assets/icons/favicon.svg";
-export const favIconPngUrl = "/static/assets/icons/apple-touch-icon.png";
-// TODO
-// export const defaultFavIcon = `${window.location.protocol}//${window.location.host}${favIconPngUrl}`;
-export const repoUrl = "https://github.com/LemmyNet";
-export const joinLemmyUrl = "https://join-lemmy.org";
-export const donateLemmyUrl = `${joinLemmyUrl}/donate`;
-export const docsUrl = `${joinLemmyUrl}/docs/index.html`;
-export const helpGuideUrl = `${joinLemmyUrl}/docs/users/01-getting-started.html`; // TODO find a way to redirect to the non-en folder
-export const markdownHelpUrl = `${joinLemmyUrl}/docs/users/02-media.html`;
-export const sortingHelpUrl = `${joinLemmyUrl}/docs/users/03-votes-and-ranking.html`;
-export const archiveTodayUrl = "https://archive.today";
-export const ghostArchiveUrl = "https://ghostarchive.org";
-export const webArchiveUrl = "https://web.archive.org";
-export const elementUrl = "https://element.io";
-
-export const postRefetchSeconds: number = 60 * 1000;
-export const fetchLimit = 40;
-export const trendingFetchLimit = 6;
-export const mentionDropdownFetchLimit = 10;
-export const commentTreeMaxDepth = 8;
-export const markdownFieldCharacterLimit = 50000;
-export const maxUploadImages = 20;
-export const concurrentImageUpload = 4;
-export const updateUnreadCountsInterval = 30000;
-
-export const relTags = "noopener nofollow";
-
-export const emDash = "\u2014";
-
-export type ThemeColor =
- | "primary"
- | "secondary"
- | "light"
- | "dark"
- | "success"
- | "danger"
- | "warning"
- | "info"
- | "blue"
- | "indigo"
- | "purple"
- | "pink"
- | "red"
- | "orange"
- | "yellow"
- | "green"
- | "teal"
- | "cyan"
- | "white"
- | "gray"
- | "gray-dark";
-
-export interface ErrorPageData {
- error?: string;
- adminMatrixIds?: string[];
-}
-
-const customEmojis: EmojiMartCategory[] = [];
-export let customEmojisLookup: Map<string, CustomEmojiView> = new Map<
- string,
- CustomEmojiView
->();
-
-const DEFAULT_ALPHABET =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
-
-function getRandomCharFromAlphabet(alphabet: string): string {
- return alphabet.charAt(Math.floor(Math.random() * alphabet.length));
-}
-
-export function getIdFromString(id?: string): number | undefined {
- return id && id !== "0" && !Number.isNaN(Number(id)) ? Number(id) : undefined;
-}
-
-export function getPageFromString(page?: string): number {
- return page && !Number.isNaN(Number(page)) ? Number(page) : 1;
-}
-
-export function randomStr(
- idDesiredLength = 20,
- alphabet = DEFAULT_ALPHABET
-): string {
- /**
- * Create n-long array and map it to random chars from given alphabet.
- * Then join individual chars as string
- */
- return Array.from({ length: idDesiredLength })
- .map(() => {
- return getRandomCharFromAlphabet(alphabet);
- })
- .join("");
-}
-
-const html5EmbedConfig = {
- html5embed: {
- useImageSyntax: true, // Enables video/audio embed with ![]() syntax (default)
- attributes: {
- audio: 'controls preload="metadata"',
- video: 'width="100%" max-height="100%" controls loop preload="metadata"',
- },
- },
-};
-
-const spoilerConfig = {
- validate: (params: string) => {
- return params.trim().match(/^spoiler\s+(.*)$/);
- },
-
- render: (tokens: any, idx: any) => {
- var m = tokens[idx].info.trim().match(/^spoiler\s+(.*)$/);
-
- if (tokens[idx].nesting === 1) {
- // opening tag
- return `<details><summary> ${md.utils.escapeHtml(m[1])} </summary>\n`;
- } else {
- // closing tag
- return "</details>\n";
- }
- },
-};
-
-export let md: MarkdownIt = new MarkdownIt();
-
-export let mdNoImages: MarkdownIt = new MarkdownIt();
-
-export function hotRankComment(comment_view: CommentView): number {
- return hotRank(comment_view.counts.score, comment_view.comment.published);
-}
-
-export function hotRankActivePost(post_view: PostView): number {
- return hotRank(post_view.counts.score, post_view.counts.newest_comment_time);
-}
-
-export function hotRankPost(post_view: PostView): number {
- return hotRank(post_view.counts.score, post_view.post.published);
-}
-
-export function hotRank(score: number, timeStr: string): number {
- // Rank = ScaleFactor * sign(Score) * log(1 + abs(Score)) / (Time + 2)^Gravity
- const date: Date = new Date(timeStr + "Z"); // Add Z to convert from UTC date
- const now: Date = new Date();
- const hoursElapsed: number = (now.getTime() - date.getTime()) / 36e5;
-
- const rank =
- (10000 * Math.log10(Math.max(1, 3 + Number(score)))) /
- Math.pow(hoursElapsed + 2, 1.8);
-
- // console.log(`Comment: ${comment.content}\nRank: ${rank}\nScore: ${comment.score}\nHours: ${hoursElapsed}`);
-
- return rank;
-}
-
-export function mdToHtml(text: string) {
- return { __html: md.render(text) };
-}
-
-export function mdToHtmlNoImages(text: string) {
- return { __html: mdNoImages.render(text) };
-}
-
-export function mdToHtmlInline(text: string) {
- return { __html: md.renderInline(text) };
-}
-
-export function getUnixTime(text?: string): number | undefined {
- return text ? new Date(text).getTime() / 1000 : undefined;
-}
-
-export function futureDaysToUnixTime(days?: number): number | undefined {
- return days
- ? Math.trunc(
- new Date(Date.now() + 1000 * 60 * 60 * 24 * days).getTime() / 1000
- )
- : undefined;
-}
-
-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]+/;
-
-export function isImage(url: string) {
- return imageRegex.test(url);
-}
-
-export function isVideo(url: string) {
- return videoRegex.test(url);
-}
-
-export function validURL(str: string) {
- try {
- new URL(str);
- return true;
- } catch {
- return false;
- }
-}
-
-export function validInstanceTLD(str: string) {
- return tldRegex.test(str);
-}
-
-export function communityRSSUrl(actorId: string, sort: string): string {
- const url = new URL(actorId);
- return `${url.origin}/feeds${url.pathname}.xml?sort=${sort}`;
-}
-
-export function validEmail(email: string) {
- const re =
- /^(([^\s"(),.:;<>@[\\\]]+(\.[^\s"(),.:;<>@[\\\]]+)*)|(".+"))@((\[(?:\d{1,3}\.){3}\d{1,3}])|(([\dA-Za-z\-]+\.)+[A-Za-z]{2,}))$/;
- return re.test(String(email).toLowerCase());
-}
-
-export function capitalizeFirstLetter(str: string): string {
- return str.charAt(0).toUpperCase() + str.slice(1);
-}
-
-export async function getSiteMetadata(url: string) {
- const form: GetSiteMetadata = { url };
- const client = new LemmyHttp(getHttpBase());
- return client.getSiteMetadata(form);
-}
-
-export function getDataTypeString(dt: DataType) {
- return dt === DataType.Post ? "Post" : "Comment";
-}
-
-export async function fetchThemeList(): Promise<string[]> {
- return fetch("/css/themelist").then(res => res.json());
-}
-
-export async function setTheme(theme: string, forceReload = false) {
- if (!isBrowser()) {
- return;
- }
- if (theme === "browser" && !forceReload) {
- return;
- }
- // This is only run on a force reload
- if (theme == "browser") {
- theme = "darkly";
- }
-
- const themeList = await fetchThemeList();
-
- // Unload all the other themes
- for (var i = 0; i < themeList.length; i++) {
- const styleSheet = document.getElementById(themeList[i]);
- if (styleSheet) {
- styleSheet.setAttribute("disabled", "disabled");
- }
- }
-
- document
- .getElementById("default-light")
- ?.setAttribute("disabled", "disabled");
- document.getElementById("default-dark")?.setAttribute("disabled", "disabled");
-
- // Load the theme dynamically
- const cssLoc = `/css/themes/${theme}.css`;
-
- loadCss(theme, cssLoc);
- document.getElementById(theme)?.removeAttribute("disabled");
-}
-
-export function loadCss(id: string, loc: string) {
- if (!document.getElementById(id)) {
- var head = document.getElementsByTagName("head")[0];
- var link = document.createElement("link");
- link.id = id;
- link.rel = "stylesheet";
- link.type = "text/css";
- link.href = loc;
- link.media = "all";
- head.appendChild(link);
- }
-}
-
-export function objectFlip(obj: any) {
- const ret = {};
- Object.keys(obj).forEach(key => {
- ret[obj[key]] = key;
- });
- return ret;
-}
-
-export function showAvatars(
- myUserInfo = UserService.Instance.myUserInfo
-): boolean {
- return myUserInfo?.local_user_view.local_user.show_avatars ?? true;
-}
-
-export function showScores(
- myUserInfo = UserService.Instance.myUserInfo
-): boolean {
- return myUserInfo?.local_user_view.local_user.show_scores ?? true;
-}
-
-export function isCakeDay(published: string): boolean {
- // moment(undefined) or moment.utc(undefined) returns the current date/time
- // moment(null) or moment.utc(null) returns null
- const createDate = moment.utc(published).local();
- const currentDate = moment(new Date());
-
- return (
- createDate.date() === currentDate.date() &&
- createDate.month() === currentDate.month() &&
- createDate.year() !== currentDate.year()
- );
-}
-
-export function toast(text: string, background: ThemeColor = "success") {
- if (isBrowser()) {
- const backgroundColor = `var(--bs-${background})`;
- Toastify({
- text: text,
- backgroundColor: backgroundColor,
- gravity: "bottom",
- position: "left",
- duration: 5000,
- }).showToast();
- }
-}
-
-export function pictrsDeleteToast(filename: string, deleteUrl: string) {
- if (isBrowser()) {
- const clickToDeleteText = i18n.t("click_to_delete_picture", { filename });
- const deletePictureText = i18n.t("picture_deleted", {
- filename,
- });
- const failedDeletePictureText = i18n.t("failed_to_delete_picture", {
- filename,
- });
-
- const backgroundColor = `var(--bs-light)`;
-
- const toast = Toastify({
- text: clickToDeleteText,
- backgroundColor: backgroundColor,
- gravity: "top",
- position: "right",
- duration: 10000,
- onClick: () => {
- if (toast) {
- fetch(deleteUrl).then(res => {
- toast.hideToast();
- if (res.ok === true) {
- alert(deletePictureText);
- } else {
- alert(failedDeletePictureText);
- }
- });
- }
- },
- close: true,
- });
-
- toast.showToast();
- }
-}
-
-export function setupTribute() {
- return new Tribute({
- noMatchTemplate: function () {
- return "";
- },
- collection: [
- // Emojis
- {
- trigger: ":",
- menuItemTemplate: (item: any) => {
- const shortName = `:${item.original.key}:`;
- return `${item.original.val} ${shortName}`;
- },
- selectTemplate: (item: any) => {
- const customEmoji = customEmojisLookup.get(
- item.original.key
- )?.custom_emoji;
- if (customEmoji == undefined) return `${item.original.val}`;
- else
- return `![${customEmoji.alt_text}](${customEmoji.image_url} "${customEmoji.shortcode}")`;
- },
- values: Object.entries(emojiShortName)
- .map(e => {
- return { key: e[1], val: e[0] };
- })
- .concat(
- Array.from(customEmojisLookup.entries()).map(k => ({
- key: k[0],
- val: `<img class="icon icon-emoji" src="${k[1].custom_emoji.image_url}" title="${k[1].custom_emoji.shortcode}" alt="${k[1].custom_emoji.alt_text}" />`,
- }))
- ),
- allowSpaces: false,
- autocompleteMode: true,
- // TODO
- // menuItemLimit: mentionDropdownFetchLimit,
- menuShowMinLength: 2,
- },
- // Persons
- {
- trigger: "@",
- selectTemplate: (item: any) => {
- const it: PersonTribute = item.original;
- return `[${it.key}](${it.view.person.actor_id})`;
- },
- values: debounce(async (text: string, cb: any) => {
- cb(await personSearch(text));
- }),
- allowSpaces: false,
- autocompleteMode: true,
- // TODO
- // menuItemLimit: mentionDropdownFetchLimit,
- menuShowMinLength: 2,
- },
-
- // Communities
- {
- trigger: "!",
- selectTemplate: (item: any) => {
- const it: CommunityTribute = item.original;
- return `[${it.key}](${it.view.community.actor_id})`;
- },
- values: debounce(async (text: string, cb: any) => {
- cb(await communitySearch(text));
- }),
- allowSpaces: false,
- autocompleteMode: true,
- // TODO
- // menuItemLimit: mentionDropdownFetchLimit,
- menuShowMinLength: 2,
- },
- ],
- });
-}
-
-function setupEmojiDataModel(custom_emoji_views: CustomEmojiView[]) {
- const groupedEmojis = groupBy(
- custom_emoji_views,
- x => x.custom_emoji.category
- );
- for (const [category, emojis] of Object.entries(groupedEmojis)) {
- customEmojis.push({
- id: category,
- name: category,
- emojis: emojis.map(emoji => ({
- id: emoji.custom_emoji.shortcode,
- name: emoji.custom_emoji.shortcode,
- keywords: emoji.keywords.map(x => x.keyword),
- skins: [{ src: emoji.custom_emoji.image_url }],
- })),
- });
- }
- customEmojisLookup = new Map(
- custom_emoji_views.map(view => [view.custom_emoji.shortcode, view])
- );
-}
-
-export function updateEmojiDataModel(custom_emoji_view: CustomEmojiView) {
- const emoji: EmojiMartCustomEmoji = {
- id: custom_emoji_view.custom_emoji.shortcode,
- name: custom_emoji_view.custom_emoji.shortcode,
- keywords: custom_emoji_view.keywords.map(x => x.keyword),
- skins: [{ src: custom_emoji_view.custom_emoji.image_url }],
- };
- const categoryIndex = customEmojis.findIndex(
- x => x.id == custom_emoji_view.custom_emoji.category
- );
- if (categoryIndex == -1) {
- customEmojis.push({
- id: custom_emoji_view.custom_emoji.category,
- name: custom_emoji_view.custom_emoji.category,
- emojis: [emoji],
- });
- } else {
- const emojiIndex = customEmojis[categoryIndex].emojis.findIndex(
- x => x.id == custom_emoji_view.custom_emoji.shortcode
- );
- if (emojiIndex == -1) {
- customEmojis[categoryIndex].emojis.push(emoji);
- } else {
- customEmojis[categoryIndex].emojis[emojiIndex] = emoji;
- }
- }
- customEmojisLookup.set(
- custom_emoji_view.custom_emoji.shortcode,
- custom_emoji_view
- );
-}
-
-export function removeFromEmojiDataModel(id: number) {
- let view: CustomEmojiView | undefined;
- for (const item of customEmojisLookup.values()) {
- if (item.custom_emoji.id === id) {
- view = item;
- break;
- }
- }
- if (!view) return;
- const categoryIndex = customEmojis.findIndex(
- x => x.id == view?.custom_emoji.category
- );
- const emojiIndex = customEmojis[categoryIndex].emojis.findIndex(
- x => x.id == view?.custom_emoji.shortcode
- );
- customEmojis[categoryIndex].emojis = customEmojis[
- categoryIndex
- ].emojis.splice(emojiIndex, 1);
-
- customEmojisLookup.delete(view?.custom_emoji.shortcode);
-}
-
-function setupMarkdown() {
- const markdownItConfig: MarkdownIt.Options = {
- html: false,
- linkify: true,
- typographer: true,
- };
-
- const emojiDefs = Array.from(customEmojisLookup.entries()).reduce(
- (main, [key, value]) => ({ ...main, [key]: value }),
- {}
- );
- md = new MarkdownIt(markdownItConfig)
- .use(markdown_it_sub)
- .use(markdown_it_sup)
- .use(markdown_it_footnote)
- .use(markdown_it_html5_embed, html5EmbedConfig)
- .use(markdown_it_container, "spoiler", spoilerConfig)
- .use(markdown_it_emoji, {
- defs: emojiDefs,
- });
-
- mdNoImages = new MarkdownIt(markdownItConfig)
- .use(markdown_it_sub)
- .use(markdown_it_sup)
- .use(markdown_it_footnote)
- .use(markdown_it_html5_embed, html5EmbedConfig)
- .use(markdown_it_container, "spoiler", spoilerConfig)
- .use(markdown_it_emoji, {
- defs: emojiDefs,
- })
- .disable("image");
- const defaultRenderer = md.renderer.rules.image;
- md.renderer.rules.image = function (
- tokens: Token[],
- idx: number,
- options: MarkdownIt.Options,
- env: any,
- self: Renderer
- ) {
- //Provide custom renderer for our emojis to allow us to add a css class and force size dimensions on them.
- const item = tokens[idx] as any;
- const title = item.attrs.length >= 3 ? item.attrs[2][1] : "";
- const src: string = item.attrs[0][1];
- const isCustomEmoji = customEmojisLookup.get(title) != undefined;
- if (!isCustomEmoji) {
- return defaultRenderer?.(tokens, idx, options, env, self) ?? "";
- }
- const alt_text = item.content;
- return `<img class="icon icon-emoji" src="${src}" title="${title}" alt="${alt_text}"/>`;
- };
- md.renderer.rules.table_open = function () {
- return '<table class="table">';
- };
-}
-
-export function getEmojiMart(
- onEmojiSelect: (e: any) => void,
- customPickerOptions: any = {}
-) {
- const pickerOptions = {
- ...customPickerOptions,
- onEmojiSelect: onEmojiSelect,
- custom: customEmojis,
- };
- return new Picker(pickerOptions);
-}
-
-var tippyInstance: any;
-if (isBrowser()) {
- tippyInstance = tippy("[data-tippy-content]");
-}
-
-export function setupTippy() {
- if (isBrowser()) {
- tippyInstance.forEach((e: any) => e.destroy());
- tippyInstance = tippy("[data-tippy-content]", {
- delay: [500, 0],
- // Display on "long press"
- touch: ["hold", 500],
- });
- }
-}
-
-interface PersonTribute {
- key: string;
- view: PersonView;
-}
-
-async function personSearch(text: string): Promise<PersonTribute[]> {
- const usersResponse = await fetchUsers(text);
-
- return usersResponse.map(pv => ({
- key: `@${pv.person.name}@${hostname(pv.person.actor_id)}`,
- view: pv,
- }));
-}
-
-interface CommunityTribute {
- key: string;
- view: CommunityView;
-}
-
-async function communitySearch(text: string): Promise<CommunityTribute[]> {
- const communitiesResponse = await fetchCommunities(text);
-
- return communitiesResponse.map(cv => ({
- key: `!${cv.community.name}@${hostname(cv.community.actor_id)}`,
- view: cv,
- }));
-}
-
-export function getRecipientIdFromProps(props: any): number {
- return props.match.params.recipient_id
- ? Number(props.match.params.recipient_id)
- : 1;
-}
-
-export function getIdFromProps(props: any): number | undefined {
- const id = props.match.params.post_id;
- return id ? Number(id) : undefined;
-}
-
-export function getCommentIdFromProps(props: any): number | undefined {
- const id = props.match.params.comment_id;
- return id ? Number(id) : undefined;
-}
-
-type ImmutableListKey =
- | "comment"
- | "comment_reply"
- | "person_mention"
- | "community"
- | "private_message"
- | "post"
- | "post_report"
- | "comment_report"
- | "private_message_report"
- | "registration_application";
-
-function editListImmutable<
- T extends { [key in F]: { id: number } },
- F extends ImmutableListKey
->(fieldName: F, data: T, list: T[]): T[] {
- return [
- ...list.map(c => (c[fieldName].id === data[fieldName].id ? data : c)),
- ];
-}
-
-export function editComment(
- data: CommentView,
- comments: CommentView[]
-): CommentView[] {
- return editListImmutable("comment", data, comments);
-}
-
-export function editCommentReply(
- data: CommentReplyView,
- replies: CommentReplyView[]
-): CommentReplyView[] {
- return editListImmutable("comment_reply", data, replies);
-}
-
-interface WithComment {
- comment: CommentI;
- counts: CommentAggregates;
- my_vote?: number;
- saved: boolean;
-}
-
-export function editMention(
- data: PersonMentionView,
- comments: PersonMentionView[]
-): PersonMentionView[] {
- return editListImmutable("person_mention", data, comments);
-}
-
-export function editCommunity(
- data: CommunityView,
- communities: CommunityView[]
-): CommunityView[] {
- return editListImmutable("community", data, communities);
-}
-
-export function editPrivateMessage(
- data: PrivateMessageView,
- messages: PrivateMessageView[]
-): PrivateMessageView[] {
- return editListImmutable("private_message", data, messages);
-}
-
-export function editPost(data: PostView, posts: PostView[]): PostView[] {
- return editListImmutable("post", data, posts);
-}
-
-export function editPostReport(
- data: PostReportView,
- reports: PostReportView[]
-) {
- return editListImmutable("post_report", data, reports);
-}
-
-export function editCommentReport(
- data: CommentReportView,
- reports: CommentReportView[]
-): CommentReportView[] {
- return editListImmutable("comment_report", data, reports);
-}
-
-export function editPrivateMessageReport(
- data: PrivateMessageReportView,
- reports: PrivateMessageReportView[]
-): PrivateMessageReportView[] {
- return editListImmutable("private_message_report", data, reports);
-}
-
-export function editRegistrationApplication(
- data: RegistrationApplicationView,
- apps: RegistrationApplicationView[]
-): RegistrationApplicationView[] {
- return editListImmutable("registration_application", data, apps);
-}
-
-export function editWith<D extends WithComment, L extends WithComment>(
- { comment, counts, saved, my_vote }: D,
- list: L[]
-) {
- return [
- ...list.map(c =>
- c.comment.id === comment.id
- ? { ...c, comment, counts, saved, my_vote }
- : c
- ),
- ];
-}
-
-export function updatePersonBlock(
- data: BlockPersonResponse,
- myUserInfo: MyUserInfo | undefined = UserService.Instance.myUserInfo
-) {
- if (myUserInfo) {
- if (data.blocked) {
- myUserInfo.person_blocks.push({
- person: myUserInfo.local_user_view.person,
- target: data.person_view.person,
- });
- toast(`${i18n.t("blocked")} ${data.person_view.person.name}`);
- } else {
- myUserInfo.person_blocks = myUserInfo.person_blocks.filter(
- i => i.target.id !== data.person_view.person.id
- );
- toast(`${i18n.t("unblocked")} ${data.person_view.person.name}`);
- }
- }
-}
-
-export function updateCommunityBlock(
- data: BlockCommunityResponse,
- myUserInfo: MyUserInfo | undefined = UserService.Instance.myUserInfo
-) {
- if (myUserInfo) {
- if (data.blocked) {
- myUserInfo.community_blocks.push({
- person: myUserInfo.local_user_view.person,
- community: data.community_view.community,
- });
- toast(`${i18n.t("blocked")} ${data.community_view.community.name}`);
- } else {
- myUserInfo.community_blocks = myUserInfo.community_blocks.filter(
- i => i.community.id !== data.community_view.community.id
- );
- toast(`${i18n.t("unblocked")} ${data.community_view.community.name}`);
- }
- }
-}
-
-export function commentsToFlatNodes(comments: CommentView[]): CommentNodeI[] {
- const nodes: CommentNodeI[] = [];
- for (const comment of comments) {
- nodes.push({ comment_view: comment, children: [], depth: 0 });
- }
- return nodes;
-}
-
-export function convertCommentSortType(sort: SortType): CommentSortType {
- if (
- sort == "TopAll" ||
- sort == "TopDay" ||
- sort == "TopWeek" ||
- sort == "TopMonth" ||
- sort == "TopYear"
- ) {
- return "Top";
- } else if (sort == "New") {
- return "New";
- } else if (sort == "Hot" || sort == "Active") {
- return "Hot";
- } else {
- return "Hot";
- }
-}
-
-export function buildCommentsTree(
- comments: CommentView[],
- parentComment: boolean
-): CommentNodeI[] {
- const map = new Map<number, CommentNodeI>();
- const depthOffset = !parentComment
- ? 0
- : getDepthFromComment(comments[0].comment) ?? 0;
-
- for (const comment_view of comments) {
- const depthI = getDepthFromComment(comment_view.comment) ?? 0;
- const depth = depthI ? depthI - depthOffset : 0;
- const node: CommentNodeI = {
- comment_view,
- children: [],
- depth,
- };
- map.set(comment_view.comment.id, { ...node });
- }
-
- const tree: CommentNodeI[] = [];
-
- // if its a parent comment fetch, then push the first comment to the top node.
- if (parentComment) {
- const cNode = map.get(comments[0].comment.id);
- if (cNode) {
- tree.push(cNode);
- }
- }
-
- for (const comment_view of comments) {
- const child = map.get(comment_view.comment.id);
- if (child) {
- const parent_id = getCommentParentId(comment_view.comment);
- if (parent_id) {
- const parent = map.get(parent_id);
- // Necessary because blocked comment might not exist
- if (parent) {
- parent.children.push(child);
- }
- } else {
- if (!parentComment) {
- tree.push(child);
- }
- }
- }
- }
-
- return tree;
-}
-
-export function getCommentParentId(comment?: CommentI): number | undefined {
- const split = comment?.path.split(".");
- // remove the 0
- split?.shift();
-
- return split && split.length > 1
- ? Number(split.at(split.length - 2))
- : undefined;
-}
-
-export function getDepthFromComment(comment?: CommentI): number | undefined {
- const len = comment?.path.split(".").length;
- return len ? len - 2 : undefined;
-}
-
-// TODO make immutable
-export function insertCommentIntoTree(
- tree: CommentNodeI[],
- cv: CommentView,
- parentComment: boolean
-) {
- // Building a fake node to be used for later
- const node: CommentNodeI = {
- comment_view: cv,
- children: [],
- depth: 0,
- };
-
- const parentId = getCommentParentId(cv.comment);
- if (parentId) {
- const parent_comment = searchCommentTree(tree, parentId);
- if (parent_comment) {
- node.depth = parent_comment.depth + 1;
- parent_comment.children.unshift(node);
- }
- } else if (!parentComment) {
- tree.unshift(node);
- }
-}
-
-export function searchCommentTree(
- tree: CommentNodeI[],
- id: number
-): CommentNodeI | undefined {
- for (const node of tree) {
- if (node.comment_view.comment.id === id) {
- return node;
- }
-
- for (const child of node.children) {
- const res = searchCommentTree([child], id);
-
- if (res) {
- return res;
- }
- }
- }
- return undefined;
-}
-
-export const colorList: string[] = [
- hsl(0),
- hsl(50),
- hsl(100),
- hsl(150),
- hsl(200),
- hsl(250),
- hsl(300),
-];
-
-function hsl(num: number) {
- return `hsla(${num}, 35%, 50%, 0.5)`;
-}
-
-export function hostname(url: string): string {
- const cUrl = new URL(url);
- return cUrl.port ? `${cUrl.hostname}:${cUrl.port}` : `${cUrl.hostname}`;
-}
-
-export function validTitle(title?: string): boolean {
- // Initial title is null, minimum length is taken care of by textarea's minLength={3}
- if (!title || title.length < 3) return true;
-
- const regex = new RegExp(/.*\S.*/, "g");
-
- return regex.test(title);
-}
-
-export function siteBannerCss(banner: string): string {
- return ` \
- background-image: linear-gradient( rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.8) ) ,url("${banner}"); \
- background-attachment: fixed; \
- background-position: top; \
- background-repeat: no-repeat; \
- background-size: 100% cover; \
-
- width: 100%; \
- max-height: 100vh; \
- `;
-}
-
-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()) {
- return window.isoData;
- } else return context.router.staticContext;
-}
-
-moment.updateLocale("en", {
- relativeTime: {
- future: "in %s",
- past: "%s ago",
- s: "<1m",
- ss: "%ds",
- m: "1m",
- mm: "%dm",
- h: "1h",
- hh: "%dh",
- d: "1d",
- dd: "%dd",
- w: "1w",
- ww: "%dw",
- M: "1M",
- MM: "%dM",
- y: "1Y",
- yy: "%dY",
- },
-});
-
-export function saveScrollPosition(context: any) {
- const path: string = context.router.route.location.pathname;
- const y = window.scrollY;
- sessionStorage.setItem(`scrollPosition_${path}`, y.toString());
-}
-
-export function restoreScrollPosition(context: any) {
- const path: string = context.router.route.location.pathname;
- const y = Number(sessionStorage.getItem(`scrollPosition_${path}`));
- window.scrollTo(0, y);
-}
-
-export function showLocal(isoData: IsoData): boolean {
- return isoData.site_res.site_view.local_site.federation_enabled;
-}
-
-export interface Choice {
- value: string;
- label: string;
- disabled?: boolean;
-}
-
-export function getUpdatedSearchId(id?: number | null, urlId?: number | null) {
- return id === null
- ? undefined
- : ((id ?? urlId) === 0 ? undefined : id ?? urlId)?.toString();
-}
-
-export function communityToChoice(cv: CommunityView): Choice {
- return {
- value: cv.community.id.toString(),
- label: communitySelectName(cv),
- };
-}
-
-export function personToChoice(pvs: PersonView): Choice {
- return {
- value: pvs.person.id.toString(),
- label: personSelectName(pvs),
- };
-}
-
-function fetchSearchResults(q: string, type_: SearchType) {
- const form: Search = {
- q,
- type_,
- sort: "TopAll",
- listing_type: "All",
- page: 1,
- limit: fetchLimit,
- auth: myAuth(),
- };
-
- return HttpService.client.search(form);
-}
-
-export async function fetchCommunities(q: string) {
- const res = await fetchSearchResults(q, "Communities");
-
- return res.state === "success" ? res.data.communities : [];
-}
-
-export async function fetchUsers(q: string) {
- const res = await fetchSearchResults(q, "Users");
-
- return res.state === "success" ? res.data.users : [];
-}
-
-export function communitySelectName(cv: CommunityView): string {
- return cv.community.local
- ? cv.community.title
- : `${hostname(cv.community.actor_id)}/${cv.community.title}`;
-}
-
-export function personSelectName({
- person: { display_name, name, local, actor_id },
-}: PersonView): string {
- const pName = display_name ?? name;
- return local ? pName : `${hostname(actor_id)}/${pName}`;
-}
-
-export function initializeSite(site?: GetSiteResponse) {
- UserService.Instance.myUserInfo = site?.my_user;
- i18n.changeLanguage();
- if (site) {
- setupEmojiDataModel(site.custom_emojis ?? []);
- }
- setupMarkdown();
-}
-
-const SHORTNUM_SI_FORMAT = new Intl.NumberFormat("en-US", {
- maximumSignificantDigits: 3,
- //@ts-ignore
- notation: "compact",
- compactDisplay: "short",
-});
-
-export function numToSI(value: number): string {
- return SHORTNUM_SI_FORMAT.format(value);
-}
-
-export function myAuth(): string | undefined {
- return UserService.Instance.auth();
-}
-
-export function myAuthRequired(): string {
- return UserService.Instance.auth(true) ?? "";
-}
-
-export function enableDownvotes(siteRes: GetSiteResponse): boolean {
- return siteRes.site_view.local_site.enable_downvotes;
-}
-
-export function enableNsfw(siteRes: GetSiteResponse): boolean {
- return siteRes.site_view.local_site.enable_nsfw;
-}
-
-export function postToCommentSortType(sort: SortType): CommentSortType {
- switch (sort) {
- case "Active":
- case "Hot":
- return "Hot";
- case "New":
- case "NewComments":
- return "New";
- case "Old":
- return "Old";
- default:
- return "Top";
- }
-}
-
-export function isPostBlocked(
- pv: PostView,
- myUserInfo: MyUserInfo | undefined = UserService.Instance.myUserInfo
-): boolean {
- return (
- (myUserInfo?.community_blocks
- .map(c => c.community.id)
- .includes(pv.community.id) ||
- myUserInfo?.person_blocks
- .map(p => p.target.id)
- .includes(pv.creator.id)) ??
- false
- );
-}
-
-/// Checks to make sure you can view NSFW posts. Returns true if you can.
-export function nsfwCheck(
- pv: PostView,
- myUserInfo = UserService.Instance.myUserInfo
-): boolean {
- const nsfw = pv.post.nsfw || pv.community.nsfw;
- const myShowNsfw = myUserInfo?.local_user_view.local_user.show_nsfw ?? false;
- return !nsfw || (nsfw && myShowNsfw);
-}
-
-export function getRandomFromList<T>(list: T[]): T | undefined {
- return list.length == 0
- ? undefined
- : list.at(Math.floor(Math.random() * list.length));
-}
-
-/**
- * This shows what language you can select
- *
- * Use showAll for the site form
- * Use showSite for the profile and community forms
- * Use false for both those to filter on your profile and site ones
- */
-export function selectableLanguages(
- allLanguages: Language[],
- siteLanguages: number[],
- showAll?: boolean,
- showSite?: boolean,
- myUserInfo = UserService.Instance.myUserInfo
-): Language[] {
- const allLangIds = allLanguages.map(l => l.id);
- let myLangs = myUserInfo?.discussion_languages ?? allLangIds;
- myLangs = myLangs.length == 0 ? allLangIds : myLangs;
- const siteLangs = siteLanguages.length == 0 ? allLangIds : siteLanguages;
-
- if (showAll) {
- return allLanguages;
- } else {
- if (showSite) {
- return allLanguages.filter(x => siteLangs.includes(x.id));
- } else {
- return allLanguages
- .filter(x => siteLangs.includes(x.id))
- .filter(x => myLangs.includes(x.id));
- }
- }
-}
-
-interface EmojiMartCategory {
- id: string;
- name: string;
- emojis: EmojiMartCustomEmoji[];
-}
-
-interface EmojiMartCustomEmoji {
- id: string;
- name: string;
- keywords: string[];
- skins: EmojiMartSkin[];
-}
-
-interface EmojiMartSkin {
- src: string;
-}
-
-export function isAuthPath(pathname: string) {
- return /create_.*|inbox|settings|admin|reports|registration_applications/g.test(
- pathname
- );
-}
-
-export function newVote(voteType: VoteType, myVote?: number): number {
- if (voteType == VoteType.Upvote) {
- return myVote == 1 ? 0 : 1;
- } else {
- return myVote == -1 ? 0 : -1;
- }
-}
-
-export type RouteDataResponse<T extends Record<string, any>> = {
- [K in keyof T]: RequestState<T[K]>;
-};
--- /dev/null
+import { getCommentParentId, getDepthFromComment } from "@utils/app";
+import { CommentView } from "lemmy-js-client";
+import { CommentNodeI } from "../../interfaces";
+
+export default function buildCommentsTree(
+ comments: CommentView[],
+ parentComment: boolean
+): CommentNodeI[] {
+ const map = new Map<number, CommentNodeI>();
+ const depthOffset = !parentComment
+ ? 0
+ : getDepthFromComment(comments[0].comment) ?? 0;
+
+ for (const comment_view of comments) {
+ const depthI = getDepthFromComment(comment_view.comment) ?? 0;
+ const depth = depthI ? depthI - depthOffset : 0;
+ const node: CommentNodeI = {
+ comment_view,
+ children: [],
+ depth,
+ };
+ map.set(comment_view.comment.id, { ...node });
+ }
+
+ const tree: CommentNodeI[] = [];
+
+ // if its a parent comment fetch, then push the first comment to the top node.
+ if (parentComment) {
+ const cNode = map.get(comments[0].comment.id);
+ if (cNode) {
+ tree.push(cNode);
+ }
+ }
+
+ for (const comment_view of comments) {
+ const child = map.get(comment_view.comment.id);
+ if (child) {
+ const parent_id = getCommentParentId(comment_view.comment);
+ if (parent_id) {
+ const parent = map.get(parent_id);
+ // Necessary because blocked comment might not exist
+ if (parent) {
+ parent.children.push(child);
+ }
+ } else {
+ if (!parentComment) {
+ tree.push(child);
+ }
+ }
+ }
+ }
+
+ return tree;
+}
--- /dev/null
+import { hsl } from "@utils/helpers";
+
+export const colorList: string[] = [
+ hsl(0),
+ hsl(50),
+ hsl(100),
+ hsl(150),
+ hsl(200),
+ hsl(250),
+ hsl(300),
+];
--- /dev/null
+import { CommentView } from "lemmy-js-client";
+import { CommentNodeI } from "../../interfaces";
+
+export default function commentsToFlatNodes(
+ comments: CommentView[]
+): CommentNodeI[] {
+ const nodes: CommentNodeI[] = [];
+ for (const comment of comments) {
+ nodes.push({ comment_view: comment, children: [], depth: 0 });
+ }
+ return nodes;
+}
--- /dev/null
+export default function communityRSSUrl(actorId: string, sort: string): string {
+ const url = new URL(actorId);
+ return `${url.origin}/feeds${url.pathname}.xml?sort=${sort}`;
+}
--- /dev/null
+import { fetchCommunities } from "@utils/app";
+import { hostname } from "@utils/helpers";
+import { CommunityTribute } from "@utils/types";
+
+export default async function communitySearch(
+ text: string
+): Promise<CommunityTribute[]> {
+ const communitiesResponse = await fetchCommunities(text);
+
+ return communitiesResponse.map(cv => ({
+ key: `!${cv.community.name}@${hostname(cv.community.actor_id)}`,
+ view: cv,
+ }));
+}
--- /dev/null
+import { hostname } from "@utils/helpers";
+import { CommunityView } from "lemmy-js-client";
+
+export default function communitySelectName(cv: CommunityView): string {
+ return cv.community.local
+ ? cv.community.title
+ : `${hostname(cv.community.actor_id)}/${cv.community.title}`;
+}
--- /dev/null
+import { communitySelectName } from "@utils/app";
+import { Choice } from "@utils/types";
+import { CommunityView } from "lemmy-js-client";
+
+export default function communityToChoice(cv: CommunityView): Choice {
+ return {
+ value: cv.community.id.toString(),
+ label: communitySelectName(cv),
+ };
+}
--- /dev/null
+import { CommentSortType, SortType } from "lemmy-js-client";
+
+export default function convertCommentSortType(
+ sort: SortType
+): CommentSortType {
+ switch (sort) {
+ case "TopAll":
+ case "TopDay":
+ case "TopWeek":
+ case "TopMonth":
+ case "TopYear": {
+ return "Top";
+ }
+ case "New": {
+ return "New";
+ }
+ case "Hot":
+ case "Active": {
+ return "Hot";
+ }
+ default: {
+ return "Hot";
+ }
+ }
+}
--- /dev/null
+import { editListImmutable } from "@utils/helpers";
+import { CommentReplyView } from "lemmy-js-client";
+
+export default function editCommentReply(
+ data: CommentReplyView,
+ replies: CommentReplyView[]
+): CommentReplyView[] {
+ return editListImmutable("comment_reply", data, replies);
+}
--- /dev/null
+import { editListImmutable } from "@utils/helpers";
+import { CommentReportView } from "lemmy-js-client";
+
+export default function editCommentReport(
+ data: CommentReportView,
+ reports: CommentReportView[]
+): CommentReportView[] {
+ return editListImmutable("comment_report", data, reports);
+}
--- /dev/null
+import { editListImmutable } from "@utils/helpers";
+import { CommentView } from "lemmy-js-client";
+
+export default function editComment(
+ data: CommentView,
+ comments: CommentView[]
+): CommentView[] {
+ return editListImmutable("comment", data, comments);
+}
--- /dev/null
+import { editListImmutable } from "@utils/helpers";
+import { CommunityView } from "lemmy-js-client";
+
+export default function editCommunity(
+ data: CommunityView,
+ communities: CommunityView[]
+): CommunityView[] {
+ return editListImmutable("community", data, communities);
+}
--- /dev/null
+import { editListImmutable } from "@utils/helpers";
+import { PersonMentionView } from "lemmy-js-client";
+
+export default function editMention(
+ data: PersonMentionView,
+ comments: PersonMentionView[]
+): PersonMentionView[] {
+ return editListImmutable("person_mention", data, comments);
+}
--- /dev/null
+import { editListImmutable } from "@utils/helpers";
+import { PostReportView } from "lemmy-js-client";
+
+export default function editPostReport(
+ data: PostReportView,
+ reports: PostReportView[]
+) {
+ return editListImmutable("post_report", data, reports);
+}
--- /dev/null
+import { editListImmutable } from "@utils/helpers";
+import { PostView } from "lemmy-js-client";
+
+export default function editPost(
+ data: PostView,
+ posts: PostView[]
+): PostView[] {
+ return editListImmutable("post", data, posts);
+}
--- /dev/null
+import { editListImmutable } from "@utils/helpers";
+import { PrivateMessageReportView } from "lemmy-js-client";
+
+export default function editPrivateMessageReport(
+ data: PrivateMessageReportView,
+ reports: PrivateMessageReportView[]
+): PrivateMessageReportView[] {
+ return editListImmutable("private_message_report", data, reports);
+}
--- /dev/null
+import { editListImmutable } from "@utils/helpers";
+import { PrivateMessageView } from "lemmy-js-client";
+
+export default function editPrivateMessage(
+ data: PrivateMessageView,
+ messages: PrivateMessageView[]
+): PrivateMessageView[] {
+ return editListImmutable("private_message", data, messages);
+}
--- /dev/null
+import { editListImmutable } from "@utils/helpers";
+import { RegistrationApplicationView } from "lemmy-js-client";
+
+export default function editRegistrationApplication(
+ data: RegistrationApplicationView,
+ apps: RegistrationApplicationView[]
+): RegistrationApplicationView[] {
+ return editListImmutable("registration_application", data, apps);
+}
--- /dev/null
+import { WithComment } from "@utils/types";
+
+export default function editWith<D extends WithComment, L extends WithComment>(
+ { comment, counts, saved, my_vote }: D,
+ list: L[]
+) {
+ return [
+ ...list.map(c =>
+ c.comment.id === comment.id
+ ? { ...c, comment, counts, saved, my_vote }
+ : c
+ ),
+ ];
+}
--- /dev/null
+import { GetSiteResponse } from "lemmy-js-client";
+
+export default function enableDownvotes(siteRes: GetSiteResponse): boolean {
+ return siteRes.site_view.local_site.enable_downvotes;
+}
--- /dev/null
+import { GetSiteResponse } from "lemmy-js-client";
+
+export default function enableNsfw(siteRes: GetSiteResponse): boolean {
+ return siteRes.site_view.local_site.enable_nsfw;
+}
--- /dev/null
+import { fetchSearchResults } from "@utils/app";
+
+export default async function fetchCommunities(q: string) {
+ const res = await fetchSearchResults(q, "Communities");
+
+ return res.state === "success" ? res.data.communities : [];
+}
--- /dev/null
+import { myAuth } from "@utils/app";
+import { Search, SearchType } from "lemmy-js-client";
+import { fetchLimit } from "../../config";
+import { HttpService } from "../../services";
+
+export default function fetchSearchResults(q: string, type_: SearchType) {
+ const form: Search = {
+ q,
+ type_,
+ sort: "TopAll",
+ listing_type: "All",
+ page: 1,
+ limit: fetchLimit,
+ auth: myAuth(),
+ };
+
+ return HttpService.client.search(form);
+}
--- /dev/null
+export default async function fetchThemeList(): Promise<string[]> {
+ return fetch("/css/themelist").then(res => res.json());
+}
--- /dev/null
+import { fetchSearchResults } from "@utils/app";
+
+export default async function fetchUsers(q: string) {
+ const res = await fetchSearchResults(q, "Users");
+
+ return res.state === "success" ? res.data.users : [];
+}
--- /dev/null
+export default function getCommentIdFromProps(props: any): number | undefined {
+ const id = props.match.params.comment_id;
+ return id ? Number(id) : undefined;
+}
--- /dev/null
+import { Comment } from "lemmy-js-client";
+
+export default function getCommentParentId(
+ comment?: Comment
+): number | undefined {
+ const split = comment?.path.split(".");
+ // remove the 0
+ split?.shift();
+
+ return split && split.length > 1
+ ? Number(split.at(split.length - 2))
+ : undefined;
+}
--- /dev/null
+import { DataType } from "../../interfaces";
+
+export default function getDataTypeString(dt: DataType) {
+ return dt === DataType.Post ? "Post" : "Comment";
+}
--- /dev/null
+import { Comment } from "lemmy-js-client";
+
+export default function getDepthFromComment(
+ comment?: Comment
+): number | undefined {
+ const len = comment?.path.split(".").length;
+ return len ? len - 2 : undefined;
+}
--- /dev/null
+export default function getIdFromProps(props: any): number | undefined {
+ const id = props.match.params.post_id;
+ return id ? Number(id) : undefined;
+}
--- /dev/null
+export default function getRecipientIdFromProps(props: any): number {
+ return props.match.params.recipient_id
+ ? Number(props.match.params.recipient_id)
+ : 1;
+}
--- /dev/null
+export default function getUpdatedSearchId(
+ id?: number | null,
+ urlId?: number | null
+) {
+ return id === null
+ ? undefined
+ : ((id ?? urlId) === 0 ? undefined : id ?? urlId)?.toString();
+}
--- /dev/null
+import buildCommentsTree from "./build-comments-tree";
+import { colorList } from "./color-list";
+import commentsToFlatNodes from "./comments-to-flat-nodes";
+import communityRSSUrl from "./community-rss-url";
+import communitySearch from "./community-search";
+import communitySelectName from "./community-select-name";
+import communityToChoice from "./community-to-choice";
+import convertCommentSortType from "./convert-comment-sort-type";
+import editComment from "./edit-comment";
+import editCommentReply from "./edit-comment-reply";
+import editCommentReport from "./edit-comment-report";
+import editCommunity from "./edit-community";
+import editMention from "./edit-mention";
+import editPost from "./edit-post";
+import editPostReport from "./edit-post-report";
+import editPrivateMessage from "./edit-private-message";
+import editPrivateMessageReport from "./edit-private-message-report";
+import editRegistrationApplication from "./edit-registration-application";
+import editWith from "./edit-with";
+import enableDownvotes from "./enable-downvotes";
+import enableNsfw from "./enable-nsfw";
+import fetchCommunities from "./fetch-communities";
+import fetchSearchResults from "./fetch-search-results";
+import fetchThemeList from "./fetch-theme-list";
+import fetchUsers from "./fetch-users";
+import getCommentIdFromProps from "./get-comment-id-from-props";
+import getCommentParentId from "./get-comment-parent-id";
+import getDataTypeString from "./get-data-type-string";
+import getDepthFromComment from "./get-depth-from-comment";
+import getIdFromProps from "./get-id-from-props";
+import getRecipientIdFromProps from "./get-recipient-id-from-props";
+import getUpdatedSearchId from "./get-updated-search-id";
+import initializeSite from "./initialize-site";
+import insertCommentIntoTree from "./insert-comment-into-tree";
+import isAuthPath from "./is-auth-path";
+import isPostBlocked from "./is-post-blocked";
+import myAuth from "./my-auth";
+import myAuthRequired from "./my-auth-required";
+import newVote from "./new-vote";
+import nsfwCheck from "./nsfw-check";
+import personSearch from "./person-search";
+import personSelectName from "./person-select-name";
+import personToChoice from "./person-to-choice";
+import postToCommentSortType from "./post-to-comment-sort-type";
+import searchCommentTree from "./search-comment-tree";
+import selectableLanguages from "./selectable-languages";
+import setIsoData from "./set-iso-data";
+import setTheme from "./set-theme";
+import showAvatars from "./show-avatars";
+import showLocal from "./show-local";
+import showScores from "./show-scores";
+import siteBannerCss from "./site-banner-css";
+import updateCommunityBlock from "./update-community-block";
+import updatePersonBlock from "./update-person-block";
+
+export {
+ buildCommentsTree,
+ colorList,
+ commentsToFlatNodes,
+ communityRSSUrl,
+ communitySearch,
+ communitySelectName,
+ communityToChoice,
+ convertCommentSortType,
+ editComment,
+ editCommentReply,
+ editCommentReport,
+ editCommunity,
+ editMention,
+ editPost,
+ editPostReport,
+ editPrivateMessage,
+ editPrivateMessageReport,
+ editRegistrationApplication,
+ editWith,
+ enableDownvotes,
+ enableNsfw,
+ fetchCommunities,
+ fetchSearchResults,
+ fetchThemeList,
+ fetchUsers,
+ getCommentIdFromProps,
+ getCommentParentId,
+ getDataTypeString,
+ getDepthFromComment,
+ getIdFromProps,
+ getRecipientIdFromProps,
+ getUpdatedSearchId,
+ initializeSite,
+ insertCommentIntoTree,
+ isAuthPath,
+ isPostBlocked,
+ myAuth,
+ myAuthRequired,
+ newVote,
+ nsfwCheck,
+ personSearch,
+ personSelectName,
+ personToChoice,
+ postToCommentSortType,
+ searchCommentTree,
+ selectableLanguages,
+ setIsoData,
+ setTheme,
+ showAvatars,
+ showLocal,
+ showScores,
+ siteBannerCss,
+ updateCommunityBlock,
+ updatePersonBlock,
+};
--- /dev/null
+import { GetSiteResponse } from "lemmy-js-client";
+import { i18n } from "../../i18next";
+import { setupEmojiDataModel, setupMarkdown } from "../../markdown";
+import { UserService } from "../../services";
+
+export default function initializeSite(site?: GetSiteResponse) {
+ UserService.Instance.myUserInfo = site?.my_user;
+ i18n.changeLanguage();
+ if (site) {
+ setupEmojiDataModel(site.custom_emojis ?? []);
+ }
+ setupMarkdown();
+}
--- /dev/null
+import { getCommentParentId, searchCommentTree } from "@utils/app";
+import { CommentView } from "lemmy-js-client";
+import { CommentNodeI } from "../../interfaces";
+
+export default function insertCommentIntoTree(
+ tree: CommentNodeI[],
+ cv: CommentView,
+ parentComment: boolean
+) {
+ // Building a fake node to be used for later
+ const node: CommentNodeI = {
+ comment_view: cv,
+ children: [],
+ depth: 0,
+ };
+
+ const parentId = getCommentParentId(cv.comment);
+ if (parentId) {
+ const parent_comment = searchCommentTree(tree, parentId);
+ if (parent_comment) {
+ node.depth = parent_comment.depth + 1;
+ parent_comment.children.unshift(node);
+ }
+ } else if (!parentComment) {
+ tree.unshift(node);
+ }
+}
--- /dev/null
+export default function isAuthPath(pathname: string) {
+ return /create_.*|inbox|settings|admin|reports|registration_applications/g.test(
+ pathname
+ );
+}
--- /dev/null
+import { MyUserInfo, PostView } from "lemmy-js-client";
+import { UserService } from "../../services";
+
+export default function isPostBlocked(
+ pv: PostView,
+ myUserInfo: MyUserInfo | undefined = UserService.Instance.myUserInfo
+): boolean {
+ return (
+ (myUserInfo?.community_blocks
+ .map(c => c.community.id)
+ .includes(pv.community.id) ||
+ myUserInfo?.person_blocks
+ .map(p => p.target.id)
+ .includes(pv.creator.id)) ??
+ false
+ );
+}
--- /dev/null
+import { UserService } from "../../services";
+
+export default function myAuthRequired(): string {
+ return UserService.Instance.auth(true) ?? "";
+}
--- /dev/null
+import { UserService } from "../../services";
+
+export default function myAuth(): string | undefined {
+ return UserService.Instance.auth();
+}
--- /dev/null
+import { VoteType } from "../../interfaces";
+
+export default function newVote(voteType: VoteType, myVote?: number): number {
+ if (voteType == VoteType.Upvote) {
+ return myVote == 1 ? 0 : 1;
+ } else {
+ return myVote == -1 ? 0 : -1;
+ }
+}
--- /dev/null
+import { PostView } from "lemmy-js-client";
+import { UserService } from "../../services";
+
+export default function nsfwCheck(
+ pv: PostView,
+ myUserInfo = UserService.Instance.myUserInfo
+): boolean {
+ const nsfw = pv.post.nsfw || pv.community.nsfw;
+ const myShowNsfw = myUserInfo?.local_user_view.local_user.show_nsfw ?? false;
+ return !nsfw || (nsfw && myShowNsfw);
+}
--- /dev/null
+import { fetchUsers } from "@utils/app";
+import { hostname } from "@utils/helpers";
+import { PersonTribute } from "@utils/types";
+
+export default async function personSearch(
+ text: string
+): Promise<PersonTribute[]> {
+ const usersResponse = await fetchUsers(text);
+
+ return usersResponse.map(pv => ({
+ key: `@${pv.person.name}@${hostname(pv.person.actor_id)}`,
+ view: pv,
+ }));
+}
--- /dev/null
+import { hostname } from "@utils/helpers";
+import { PersonView } from "lemmy-js-client";
+
+export default function personSelectName({
+ person: { display_name, name, local, actor_id },
+}: PersonView): string {
+ const pName = display_name ?? name;
+ return local ? pName : `${hostname(actor_id)}/${pName}`;
+}
--- /dev/null
+import { personSelectName } from "@utils/app";
+import { Choice } from "@utils/types";
+import { PersonView } from "lemmy-js-client";
+
+export default function personToChoice(pvs: PersonView): Choice {
+ return {
+ value: pvs.person.id.toString(),
+ label: personSelectName(pvs),
+ };
+}
--- /dev/null
+import { CommentSortType, SortType } from "lemmy-js-client";
+
+export default function postToCommentSortType(sort: SortType): CommentSortType {
+ switch (sort) {
+ case "Active":
+ case "Hot":
+ return "Hot";
+ case "New":
+ case "NewComments":
+ return "New";
+ case "Old":
+ return "Old";
+ default:
+ return "Top";
+ }
+}
--- /dev/null
+import { CommentNodeI } from "../../interfaces";
+
+export default function searchCommentTree(
+ tree: CommentNodeI[],
+ id: number
+): CommentNodeI | undefined {
+ for (const node of tree) {
+ if (node.comment_view.comment.id === id) {
+ return node;
+ }
+
+ for (const child of node.children) {
+ const res = searchCommentTree([child], id);
+
+ if (res) {
+ return res;
+ }
+ }
+ }
+ return undefined;
+}
--- /dev/null
+import { Language } from "lemmy-js-client";
+import { UserService } from "../../services";
+
+/**
+ * This shows what language you can select
+ *
+ * Use showAll for the site form
+ * Use showSite for the profile and community forms
+ * Use false for both those to filter on your profile and site ones
+ */
+export default function selectableLanguages(
+ allLanguages: Language[],
+ siteLanguages: number[],
+ showAll?: boolean,
+ showSite?: boolean,
+ myUserInfo = UserService.Instance.myUserInfo
+): Language[] {
+ const allLangIds = allLanguages.map(l => l.id);
+ let myLangs = myUserInfo?.discussion_languages ?? allLangIds;
+ myLangs = myLangs.length == 0 ? allLangIds : myLangs;
+ const siteLangs = siteLanguages.length == 0 ? allLangIds : siteLanguages;
+
+ if (showAll) {
+ return allLanguages;
+ } else {
+ if (showSite) {
+ return allLanguages.filter(x => siteLangs.includes(x.id));
+ } else {
+ return allLanguages
+ .filter(x => siteLangs.includes(x.id))
+ .filter(x => myLangs.includes(x.id));
+ }
+ }
+}
--- /dev/null
+import { isBrowser } from "@utils/browser";
+import { IsoData, RouteData } from "../../interfaces";
+
+export default function setIsoData<T extends RouteData>(
+ context: any
+): IsoData<T> {
+ // If its the browser, you need to deserialize the data from the window
+ if (isBrowser()) {
+ return window.isoData;
+ } else return context.router.staticContext;
+}
--- /dev/null
+import { fetchThemeList } from "@utils/app";
+import { isBrowser, loadCss } from "@utils/browser";
+
+export default async function setTheme(theme: string, forceReload = false) {
+ if (!isBrowser()) {
+ return;
+ }
+ if (theme === "browser" && !forceReload) {
+ return;
+ }
+ // This is only run on a force reload
+ if (theme == "browser") {
+ theme = "darkly";
+ }
+
+ const themeList = await fetchThemeList();
+
+ // Unload all the other themes
+ for (var i = 0; i < themeList.length; i++) {
+ const styleSheet = document.getElementById(themeList[i]);
+ if (styleSheet) {
+ styleSheet.setAttribute("disabled", "disabled");
+ }
+ }
+
+ document
+ .getElementById("default-light")
+ ?.setAttribute("disabled", "disabled");
+ document.getElementById("default-dark")?.setAttribute("disabled", "disabled");
+
+ // Load the theme dynamically
+ const cssLoc = `/css/themes/${theme}.css`;
+
+ loadCss(theme, cssLoc);
+ document.getElementById(theme)?.removeAttribute("disabled");
+}
--- /dev/null
+import { UserService } from "../../services";
+
+export default function showAvatars(
+ myUserInfo = UserService.Instance.myUserInfo
+): boolean {
+ return myUserInfo?.local_user_view.local_user.show_avatars ?? true;
+}
--- /dev/null
+import { IsoData } from "../../interfaces";
+
+export default function showLocal(isoData: IsoData): boolean {
+ return isoData.site_res.site_view.local_site.federation_enabled;
+}
--- /dev/null
+import { UserService } from "../../services";
+
+export default function showScores(
+ myUserInfo = UserService.Instance.myUserInfo
+): boolean {
+ return myUserInfo?.local_user_view.local_user.show_scores ?? true;
+}
--- /dev/null
+export default function siteBannerCss(banner: string): string {
+ return ` \
+ background-image: linear-gradient( rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.8) ) ,url("${banner}"); \
+ background-attachment: fixed; \
+ background-position: top; \
+ background-repeat: no-repeat; \
+ background-size: 100% cover; \
+
+ width: 100%; \
+ max-height: 100vh; \
+ `;
+}
--- /dev/null
+import { BlockCommunityResponse, MyUserInfo } from "lemmy-js-client";
+import { i18n } from "../../i18next";
+import { UserService } from "../../services";
+import { toast } from "../../toast";
+
+export default function updateCommunityBlock(
+ data: BlockCommunityResponse,
+ myUserInfo: MyUserInfo | undefined = UserService.Instance.myUserInfo
+) {
+ if (myUserInfo) {
+ if (data.blocked) {
+ myUserInfo.community_blocks.push({
+ person: myUserInfo.local_user_view.person,
+ community: data.community_view.community,
+ });
+ toast(`${i18n.t("blocked")} ${data.community_view.community.name}`);
+ } else {
+ myUserInfo.community_blocks = myUserInfo.community_blocks.filter(
+ i => i.community.id !== data.community_view.community.id
+ );
+ toast(`${i18n.t("unblocked")} ${data.community_view.community.name}`);
+ }
+ }
+}
--- /dev/null
+import { BlockPersonResponse, MyUserInfo } from "lemmy-js-client";
+import { i18n } from "../../i18next";
+import { UserService } from "../../services";
+import { toast } from "../../toast";
+
+export default function updatePersonBlock(
+ data: BlockPersonResponse,
+ myUserInfo: MyUserInfo | undefined = UserService.Instance.myUserInfo
+) {
+ if (myUserInfo) {
+ if (data.blocked) {
+ myUserInfo.person_blocks.push({
+ person: myUserInfo.local_user_view.person,
+ target: data.person_view.person,
+ });
+ toast(`${i18n.t("blocked")} ${data.person_view.person.name}`);
+ } else {
+ myUserInfo.person_blocks = myUserInfo.person_blocks.filter(
+ i => i.target.id !== data.person_view.person.id
+ );
+ toast(`${i18n.t("unblocked")} ${data.person_view.person.name}`);
+ }
+ }
+}
import canShare from "./can-share";
import isBrowser from "./is-browser";
+import loadCss from "./load-css";
+import restoreScrollPosition from "./restore-scroll-position";
+import saveScrollPosition from "./save-scroll-position";
import share from "./share";
-export { canShare, isBrowser, share };
+export {
+ canShare,
+ isBrowser,
+ loadCss,
+ restoreScrollPosition,
+ saveScrollPosition,
+ share,
+};
--- /dev/null
+export default function loadCss(id: string, loc: string) {
+ if (!document.getElementById(id)) {
+ var head = document.getElementsByTagName("head")[0];
+ var link = document.createElement("link");
+ link.id = id;
+ link.rel = "stylesheet";
+ link.type = "text/css";
+ link.href = loc;
+ link.media = "all";
+ head.appendChild(link);
+ }
+}
--- /dev/null
+export default function restoreScrollPosition(context: any) {
+ const path: string = context.router.route.location.pathname;
+ const y = Number(sessionStorage.getItem(`scrollPosition_${path}`));
+ window.scrollTo(0, y);
+}
--- /dev/null
+export default function saveScrollPosition(context: any) {
+ const path: string = context.router.route.location.pathname;
+ const y = window.scrollY;
+ sessionStorage.setItem(`scrollPosition_${path}`, y.toString());
+}
--- /dev/null
+export default function capitalizeFirstLetter(str: string): string {
+ return str.charAt(0).toUpperCase() + str.slice(1);
+}
--- /dev/null
+type ImmutableListKey =
+ | "comment"
+ | "comment_reply"
+ | "person_mention"
+ | "community"
+ | "private_message"
+ | "post"
+ | "post_report"
+ | "comment_report"
+ | "private_message_report"
+ | "registration_application";
+
+export default function editListImmutable<
+ T extends { [key in F]: { id: number } },
+ F extends ImmutableListKey
+>(fieldName: F, data: T, list: T[]): T[] {
+ return [
+ ...list.map(c => (c[fieldName].id === data[fieldName].id ? data : c)),
+ ];
+}
--- /dev/null
+export default function futureDaysToUnixTime(
+ days?: number
+): number | undefined {
+ return days
+ ? Math.trunc(
+ new Date(Date.now() + 1000 * 60 * 60 * 24 * days).getTime() / 1000
+ )
+ : undefined;
+}
--- /dev/null
+export default function getIdFromString(id?: string): number | undefined {
+ return id && id !== "0" && !Number.isNaN(Number(id)) ? Number(id) : undefined;
+}
--- /dev/null
+export default function getPageFromString(page?: string): number {
+ return page && !Number.isNaN(Number(page)) ? Number(page) : 1;
+}
--- /dev/null
+export default function getRandomCharFromAlphabet(alphabet: string): string {
+ return alphabet.charAt(Math.floor(Math.random() * alphabet.length));
+}
--- /dev/null
+export default function getRandomFromList<T>(list: T[]): T | undefined {
+ return list.length == 0
+ ? undefined
+ : list.at(Math.floor(Math.random() * list.length));
+}
--- /dev/null
+export default function getUnixTime(text?: string): number | undefined {
+ return text ? new Date(text).getTime() / 1000 : undefined;
+}
--- /dev/null
+export default function hostname(url: string): string {
+ const cUrl = new URL(url);
+ return cUrl.port ? `${cUrl.hostname}:${cUrl.port}` : `${cUrl.hostname}`;
+}
--- /dev/null
+export default function hsl(num: number) {
+ return `hsla(${num}, 35%, 50%, 0.5)`;
+}
+import capitalizeFirstLetter from "./capitalize-first-letter";
import debounce from "./debounce";
+import editListImmutable from "./edit-list-immutable";
+import futureDaysToUnixTime from "./future-days-to-unix-time";
+import getIdFromString from "./get-id-from-string";
+import getPageFromString from "./get-page-from-string";
import getQueryParams from "./get-query-params";
import getQueryString from "./get-query-string";
+import getRandomCharFromAlphabet from "./get-random-char-from-alphabet";
+import getRandomFromList from "./get-random-from-list";
+import getUnixTime from "./get-unix-time";
import { groupBy } from "./group-by";
+import hostname from "./hostname";
+import hsl from "./hsl";
+import isCakeDay from "./is-cake-day";
+import numToSI from "./num-to-si";
import poll from "./poll";
+import randomStr from "./random-str";
import sleep from "./sleep";
+import validEmail from "./valid-email";
+import validInstanceTLD from "./valid-instance-tld";
+import validTitle from "./valid-title";
+import validURL from "./valid-url";
-export { debounce, getQueryParams, getQueryString, groupBy, poll, sleep };
+export {
+ capitalizeFirstLetter,
+ debounce,
+ editListImmutable,
+ futureDaysToUnixTime,
+ getIdFromString,
+ getPageFromString,
+ getQueryParams,
+ getQueryString,
+ getRandomCharFromAlphabet,
+ getRandomFromList,
+ getUnixTime,
+ groupBy,
+ hostname,
+ hsl,
+ isCakeDay,
+ numToSI,
+ poll,
+ randomStr,
+ sleep,
+ validEmail,
+ validInstanceTLD,
+ validTitle,
+ validURL,
+};
--- /dev/null
+import moment from "moment";
+
+moment.updateLocale("en", {
+ relativeTime: {
+ future: "in %s",
+ past: "%s ago",
+ s: "<1m",
+ ss: "%ds",
+ m: "1m",
+ mm: "%dm",
+ h: "1h",
+ hh: "%dh",
+ d: "1d",
+ dd: "%dd",
+ w: "1w",
+ ww: "%dw",
+ M: "1M",
+ MM: "%dM",
+ y: "1Y",
+ yy: "%dY",
+ },
+});
+
+export default function isCakeDay(published: string): boolean {
+ const createDate = moment.utc(published).local();
+ const currentDate = moment(new Date());
+
+ return (
+ createDate.date() === currentDate.date() &&
+ createDate.month() === currentDate.month() &&
+ createDate.year() !== currentDate.year()
+ );
+}
--- /dev/null
+const SHORTNUM_SI_FORMAT = new Intl.NumberFormat("en-US", {
+ maximumSignificantDigits: 3,
+ //@ts-ignore
+ notation: "compact",
+ compactDisplay: "short",
+});
+
+export default function numToSI(value: number): string {
+ return SHORTNUM_SI_FORMAT.format(value);
+}
--- /dev/null
+import { getRandomCharFromAlphabet } from "@utils/helpers";
+
+const DEFAULT_ALPHABET =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+export default function randomStr(
+ idDesiredLength = 20,
+ alphabet = DEFAULT_ALPHABET
+): string {
+ /**
+ * Create n-long array and map it to random chars from given alphabet.
+ * Then join individual chars as string
+ */
+ return Array.from({ length: idDesiredLength })
+ .map(() => {
+ return getRandomCharFromAlphabet(alphabet);
+ })
+ .join("");
+}
--- /dev/null
+export default function validEmail(email: string) {
+ const re =
+ /^(([^\s"(),.:;<>@[\\\]]+(\.[^\s"(),.:;<>@[\\\]]+)*)|(".+"))@((\[(?:\d{1,3}\.){3}\d{1,3}])|(([\dA-Za-z\-]+\.)+[A-Za-z]{2,}))$/;
+ return re.test(String(email).toLowerCase());
+}
--- /dev/null
+const tldRegex = /([a-z0-9]+\.)*[a-z0-9]+\.[a-z]+/;
+
+export default function validInstanceTLD(str: string) {
+ return tldRegex.test(str);
+}
--- /dev/null
+export default function validTitle(title?: string): boolean {
+ // Initial title is null, minimum length is taken care of by textarea's minLength={3}
+ if (!title || title.length < 3) return true;
+
+ const regex = new RegExp(/.*\S.*/, "g");
+
+ return regex.test(title);
+}
--- /dev/null
+export default function validURL(str: string) {
+ try {
+ new URL(str);
+ return true;
+ } catch {
+ return false;
+ }
+}
--- /dev/null
+import isImage from "./is-image";
+import isVideo from "./is-video";
+
+export { isImage, isVideo };
--- /dev/null
+const imageRegex = /(http)?s?:?(\/\/[^"']*\.(?:jpg|jpeg|gif|png|svg|webp))/;
+
+export default function isImage(url: string) {
+ return imageRegex.test(url);
+}
--- /dev/null
+const videoRegex = /(http)?s?:?(\/\/[^"']*\.(?:mp4|webm))/;
+
+export default function isVideo(url: string) {
+ return videoRegex.test(url);
+}
--- /dev/null
+export default interface Choice {
+ value: string;
+ label: string;
+ disabled?: boolean;
+}
--- /dev/null
+import { CommunityView } from "lemmy-js-client";
+
+export default interface CommunityTribute {
+ key: string;
+ view: CommunityView;
+}
--- /dev/null
+export default interface ErrorPageData {
+ error?: string;
+ adminMatrixIds?: string[];
+}
+import Choice from "./choice";
+import CommunityTribute from "./community-tribute";
+import ErrorPageData from "./error-page-data";
+import PersonTribute from "./person-tribute";
import { QueryParams } from "./query-params";
+import { RouteDataResponse } from "./route-data-response";
+import { ThemeColor } from "./theme-color";
+import WithComment from "./with-comment";
-export { QueryParams };
+export {
+ Choice,
+ CommunityTribute,
+ ErrorPageData,
+ PersonTribute,
+ QueryParams,
+ RouteDataResponse,
+ ThemeColor,
+ WithComment,
+};
--- /dev/null
+import { PersonView } from "lemmy-js-client";
+
+export default interface PersonTribute {
+ key: string;
+ view: PersonView;
+}
--- /dev/null
+import { RequestState } from "../../services/HttpService";
+
+export type RouteDataResponse<T extends Record<string, any>> = {
+ [K in keyof T]: RequestState<T[K]>;
+};
--- /dev/null
+export type ThemeColor =
+ | "primary"
+ | "secondary"
+ | "light"
+ | "dark"
+ | "success"
+ | "danger"
+ | "warning"
+ | "info"
+ | "blue"
+ | "indigo"
+ | "purple"
+ | "pink"
+ | "red"
+ | "orange"
+ | "yellow"
+ | "green"
+ | "teal"
+ | "cyan"
+ | "white"
+ | "gray"
+ | "gray-dark";
--- /dev/null
+import { Comment, CommentAggregates } from "lemmy-js-client";
+
+export default interface WithComment {
+ comment: Comment;
+ counts: CommentAggregates;
+ my_vote?: number;
+ saved: boolean;
+}