]> Untitled Git - lemmy-ui.git/commitdiff
`utils.ts` organization, round two (#1427)
authorAlec Armbruster <35377827+alectrocute@users.noreply.github.com>
Wed, 21 Jun 2023 22:28:24 +0000 (18:28 -0400)
committerGitHub <noreply@github.com>
Wed, 21 Jun 2023 22:28:24 +0000 (18:28 -0400)
* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update src/shared/utils/app/convert-comment-sort-type.ts

Co-authored-by: SleeplessOne1917 <abias1122@gmail.com>
* prettier pass

---------

Co-authored-by: SleeplessOne1917 <abias1122@gmail.com>
161 files changed:
src/client/index.tsx
src/server/handlers/catch-all-handler.tsx
src/server/utils/create-ssr-html.tsx
src/server/utils/get-error-page-data.ts
src/shared/components/app/app.tsx
src/shared/components/app/error-page.tsx
src/shared/components/app/footer.tsx
src/shared/components/app/navbar.tsx
src/shared/components/comment/comment-form.tsx
src/shared/components/comment/comment-node.tsx
src/shared/components/comment/comment-nodes.tsx
src/shared/components/comment/comment-report.tsx
src/shared/components/common/badges.tsx
src/shared/components/common/comment-sort-select.tsx
src/shared/components/common/emoji-mart.tsx
src/shared/components/common/error-guard.tsx
src/shared/components/common/html-tags.tsx
src/shared/components/common/image-upload-form.tsx
src/shared/components/common/language-select.tsx
src/shared/components/common/listing-type-select.tsx
src/shared/components/common/markdown-textarea.tsx
src/shared/components/common/moment-time.tsx
src/shared/components/common/progress-bar.tsx
src/shared/components/common/registration-application.tsx
src/shared/components/common/searchable-select.tsx
src/shared/components/common/sort-select.tsx
src/shared/components/community/communities.tsx
src/shared/components/community/community-form.tsx
src/shared/components/community/community-link.tsx
src/shared/components/community/community.tsx
src/shared/components/community/create-community.tsx
src/shared/components/community/sidebar.tsx
src/shared/components/home/admin-settings.tsx
src/shared/components/home/emojis-form.tsx
src/shared/components/home/home.tsx
src/shared/components/home/instances.tsx
src/shared/components/home/legal.tsx
src/shared/components/home/login.tsx
src/shared/components/home/rate-limit-form.tsx
src/shared/components/home/setup.tsx
src/shared/components/home/signup.tsx
src/shared/components/home/site-form.tsx
src/shared/components/home/site-sidebar.tsx
src/shared/components/home/tagline-form.tsx
src/shared/components/modlog.tsx
src/shared/components/person/inbox.tsx
src/shared/components/person/password-change.tsx
src/shared/components/person/person-details.tsx
src/shared/components/person/person-listing.tsx
src/shared/components/person/profile.tsx
src/shared/components/person/registration-applications.tsx
src/shared/components/person/reports.tsx
src/shared/components/person/settings.tsx
src/shared/components/person/verify-email.tsx
src/shared/components/post/create-post.tsx
src/shared/components/post/metadata-card.tsx
src/shared/components/post/post-form.tsx
src/shared/components/post/post-listing.tsx
src/shared/components/post/post-report.tsx
src/shared/components/post/post.tsx
src/shared/components/private_message/create-private-message.tsx
src/shared/components/private_message/private-message-form.tsx
src/shared/components/private_message/private-message-report.tsx
src/shared/components/private_message/private-message.tsx
src/shared/components/search.tsx
src/shared/config.ts [new file with mode: 0644]
src/shared/interfaces.ts
src/shared/markdown.ts [new file with mode: 0644]
src/shared/services/HttpService.ts
src/shared/services/UserService.ts
src/shared/tippy.ts [new file with mode: 0644]
src/shared/toast.ts [new file with mode: 0644]
src/shared/utils.ts [deleted file]
src/shared/utils/app/build-comments-tree.ts [new file with mode: 0644]
src/shared/utils/app/color-list.ts [new file with mode: 0644]
src/shared/utils/app/comments-to-flat-nodes.ts [new file with mode: 0644]
src/shared/utils/app/community-rss-url.ts [new file with mode: 0644]
src/shared/utils/app/community-search.ts [new file with mode: 0644]
src/shared/utils/app/community-select-name.ts [new file with mode: 0644]
src/shared/utils/app/community-to-choice.ts [new file with mode: 0644]
src/shared/utils/app/convert-comment-sort-type.ts [new file with mode: 0644]
src/shared/utils/app/edit-comment-reply.ts [new file with mode: 0644]
src/shared/utils/app/edit-comment-report.ts [new file with mode: 0644]
src/shared/utils/app/edit-comment.ts [new file with mode: 0644]
src/shared/utils/app/edit-community.ts [new file with mode: 0644]
src/shared/utils/app/edit-mention.ts [new file with mode: 0644]
src/shared/utils/app/edit-post-report.ts [new file with mode: 0644]
src/shared/utils/app/edit-post.ts [new file with mode: 0644]
src/shared/utils/app/edit-private-message-report.ts [new file with mode: 0644]
src/shared/utils/app/edit-private-message.ts [new file with mode: 0644]
src/shared/utils/app/edit-registration-application.ts [new file with mode: 0644]
src/shared/utils/app/edit-with.ts [new file with mode: 0644]
src/shared/utils/app/enable-downvotes.ts [new file with mode: 0644]
src/shared/utils/app/enable-nsfw.ts [new file with mode: 0644]
src/shared/utils/app/fetch-communities.ts [new file with mode: 0644]
src/shared/utils/app/fetch-search-results.ts [new file with mode: 0644]
src/shared/utils/app/fetch-theme-list.ts [new file with mode: 0644]
src/shared/utils/app/fetch-users.ts [new file with mode: 0644]
src/shared/utils/app/get-comment-id-from-props.ts [new file with mode: 0644]
src/shared/utils/app/get-comment-parent-id.ts [new file with mode: 0644]
src/shared/utils/app/get-data-type-string.ts [new file with mode: 0644]
src/shared/utils/app/get-depth-from-comment.ts [new file with mode: 0644]
src/shared/utils/app/get-id-from-props.ts [new file with mode: 0644]
src/shared/utils/app/get-recipient-id-from-props.ts [new file with mode: 0644]
src/shared/utils/app/get-updated-search-id.ts [new file with mode: 0644]
src/shared/utils/app/index.ts [new file with mode: 0644]
src/shared/utils/app/initialize-site.ts [new file with mode: 0644]
src/shared/utils/app/insert-comment-into-tree.ts [new file with mode: 0644]
src/shared/utils/app/is-auth-path.ts [new file with mode: 0644]
src/shared/utils/app/is-post-blocked.ts [new file with mode: 0644]
src/shared/utils/app/my-auth-required.ts [new file with mode: 0644]
src/shared/utils/app/my-auth.ts [new file with mode: 0644]
src/shared/utils/app/new-vote.ts [new file with mode: 0644]
src/shared/utils/app/nsfw-check.ts [new file with mode: 0644]
src/shared/utils/app/person-search.ts [new file with mode: 0644]
src/shared/utils/app/person-select-name.ts [new file with mode: 0644]
src/shared/utils/app/person-to-choice.ts [new file with mode: 0644]
src/shared/utils/app/post-to-comment-sort-type.ts [new file with mode: 0644]
src/shared/utils/app/search-comment-tree.ts [new file with mode: 0644]
src/shared/utils/app/selectable-languages.ts [new file with mode: 0644]
src/shared/utils/app/set-iso-data.ts [new file with mode: 0644]
src/shared/utils/app/set-theme.ts [new file with mode: 0644]
src/shared/utils/app/show-avatars.ts [new file with mode: 0644]
src/shared/utils/app/show-local.ts [new file with mode: 0644]
src/shared/utils/app/show-scores.ts [new file with mode: 0644]
src/shared/utils/app/site-banner-css.ts [new file with mode: 0644]
src/shared/utils/app/update-community-block.ts [new file with mode: 0644]
src/shared/utils/app/update-person-block.ts [new file with mode: 0644]
src/shared/utils/browser/index.ts
src/shared/utils/browser/load-css.ts [new file with mode: 0644]
src/shared/utils/browser/restore-scroll-position.ts [new file with mode: 0644]
src/shared/utils/browser/save-scroll-position.ts [new file with mode: 0644]
src/shared/utils/helpers/capitalize-first-letter.ts [new file with mode: 0644]
src/shared/utils/helpers/edit-list-immutable.ts [new file with mode: 0644]
src/shared/utils/helpers/future-days-to-unix-time.ts [new file with mode: 0644]
src/shared/utils/helpers/get-id-from-string.ts [new file with mode: 0644]
src/shared/utils/helpers/get-page-from-string.ts [new file with mode: 0644]
src/shared/utils/helpers/get-random-char-from-alphabet.ts [new file with mode: 0644]
src/shared/utils/helpers/get-random-from-list.ts [new file with mode: 0644]
src/shared/utils/helpers/get-unix-time.ts [new file with mode: 0644]
src/shared/utils/helpers/hostname.ts [new file with mode: 0644]
src/shared/utils/helpers/hsl.ts [new file with mode: 0644]
src/shared/utils/helpers/index.ts
src/shared/utils/helpers/is-cake-day.ts [new file with mode: 0644]
src/shared/utils/helpers/num-to-si.ts [new file with mode: 0644]
src/shared/utils/helpers/random-str.ts [new file with mode: 0644]
src/shared/utils/helpers/valid-email.ts [new file with mode: 0644]
src/shared/utils/helpers/valid-instance-tld.ts [new file with mode: 0644]
src/shared/utils/helpers/valid-title.ts [new file with mode: 0644]
src/shared/utils/helpers/valid-url.ts [new file with mode: 0644]
src/shared/utils/media/index.ts [new file with mode: 0644]
src/shared/utils/media/is-image.ts [new file with mode: 0644]
src/shared/utils/media/is-video.ts [new file with mode: 0644]
src/shared/utils/types/choice.ts [new file with mode: 0644]
src/shared/utils/types/community-tribute.ts [new file with mode: 0644]
src/shared/utils/types/error-page-data.ts [new file with mode: 0644]
src/shared/utils/types/index.ts
src/shared/utils/types/person-tribute.ts [new file with mode: 0644]
src/shared/utils/types/route-data-response.ts [new file with mode: 0644]
src/shared/utils/types/theme-color.ts [new file with mode: 0644]
src/shared/utils/types/with-comment.ts [new file with mode: 0644]

index 860c07565dbad01861283893d209de7aad2099a4..4ff794eca850952e590632da944e79d8070d0a85 100644 (file)
@@ -1,8 +1,8 @@
+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";
index 025aaa6833c33451c6c5b03c64049c0e91443c7d..b9ff13bfb3de92732fdb45baa44abde3aae997e3 100644 (file)
@@ -1,3 +1,5 @@
+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";
@@ -15,7 +17,6 @@ import {
   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";
index 2c35aa29a9f676197e7d575ce8a3a8ba52e5f2da..569d83acaaf3814b0569a129152ff9e299d39ed2 100644 (file)
@@ -2,8 +2,8 @@ import { Helmet } from "inferno-helmet";
 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"] || "";
index 3c82372f18592ed2576f6eca0ddc028ab6e0a9c1..fc37ccac8477be18be3329a3d5682937b521d45a 100644 (file)
@@ -1,5 +1,5 @@
+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 = {};
index 79aa77eb2dc07a0d2930f4c6ec3dfa6098839fc7..e615bb3a4fe8466a118210979f2f416481e8e54d 100644 (file)
@@ -1,10 +1,10 @@
-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";
index 191c322b229d1c673aac4aa272eceb610eb5bd0e..7d4e297053eca00021d7c14366f6167b5112373c 100644 (file)
@@ -1,9 +1,9 @@
+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);
index 8ea647c67138ca94e1245f5aab6534a1fad5a8e0..601045a408708d2c7ff296e99aa6848ccbfae896 100644 (file)
@@ -1,8 +1,8 @@
 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 {
index 5fa7580c798d674350859b21eff8ad37e96b8544..12ca05db9cb504a49cc236ecbbd5c0c38c96a7b1 100644 (file)
@@ -1,5 +1,6 @@
+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";
@@ -9,17 +10,11 @@ import {
   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";
 
index 2638a40f85be635e8782761314070f9434fe0709..c399fb06a666ef42537b1b69618539441349ec83 100644 (file)
@@ -1,3 +1,5 @@
+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";
@@ -5,7 +7,6 @@ import { CreateComment, EditComment, Language } from "lemmy-js-client";
 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";
 
index 15c68f7517b95f8da7220e919652f39380a7b2ce..f6cb4b221f77f16b0eb4a65d655883f88befd923 100644 (file)
@@ -1,3 +1,12 @@
+import {
+  colorList,
+  getCommentParentId,
+  myAuth,
+  myAuthRequired,
+  newVote,
+  showScores,
+} from "@utils/app";
+import { futureDaysToUnixTime, numToSI } from "@utils/helpers";
 import {
   amCommunityCreator,
   canAdmin,
@@ -38,6 +47,7 @@ import {
   TransferCommunity,
 } from "lemmy-js-client";
 import moment from "moment";
+import { commentTreeMaxDepth } from "../../config";
 import { i18n } from "../../i18next";
 import {
   BanType,
@@ -46,21 +56,9 @@ import {
   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";
index 8c0a236eff78e4b23c2cc137d67990ee27730dad..02e621b7fcf2c7b775afe52583359ce8bddd7b4c 100644 (file)
@@ -1,3 +1,4 @@
+import { colorList } from "@utils/app";
 import classNames from "classnames";
 import { Component } from "inferno";
 import {
@@ -26,7 +27,6 @@ import {
   TransferCommunity,
 } from "lemmy-js-client";
 import { CommentNodeI, CommentViewType } from "../../interfaces";
-import { colorList } from "../../utils";
 import { CommentNode } from "./comment-node";
 
 interface CommentNodesProps {
index 2f765e03915ade04e20c7d83d92c5eae4ff94fb5..b3630096e37cde8a4dac48d8e29e1e765b153ec4 100644 (file)
@@ -1,3 +1,4 @@
+import { myAuthRequired } from "@utils/app";
 import { Component, InfernoNode, linkEvent } from "inferno";
 import { T } from "inferno-i18next-dess";
 import {
@@ -7,7 +8,6 @@ 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";
index 17ae53fb03da34f32ec12a1f969bc77a85152ba1..ed9aecf874e9a714b3f7e7176ab2a67d60a0d6bc 100644 (file)
@@ -1,3 +1,4 @@
+import { numToSI } from "@utils/helpers";
 import { Link } from "inferno-router";
 import {
   CommunityAggregates,
@@ -5,7 +6,6 @@ import {
   SiteAggregates,
 } from "lemmy-js-client";
 import { i18n } from "../../i18next";
-import { numToSI } from "../../utils";
 
 interface BadgesProps {
   counts: CommunityAggregates | SiteAggregates;
index e9885afa052aff42086e1f548dbb35398494eef9..18eaed2aa34548a707a28ab93c1ee67b3e7b439a 100644 (file)
@@ -1,7 +1,8 @@
+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 {
index dff8c3ac9c61924656e796d5cd03fa2afa629846..6ee3aa837e7483d0c10eff58ca06aae41f79b03b 100644 (file)
@@ -1,5 +1,5 @@
 import { Component } from "inferno";
-import { getEmojiMart } from "../../utils";
+import { getEmojiMart } from "../../markdown";
 
 interface EmojiMartProps {
   onEmojiClick?(val: any): any;
index 30121541b23097d01325507621bff7d184f183ab..6f0302a059753eaed95f04cc10241169a948b463 100644 (file)
@@ -1,5 +1,5 @@
+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> {
index f32b0fc048a04687102fc740f08912c7fb0701c3..63eb461f24e4bb7666dad304ac8983dab913ac20 100644 (file)
@@ -3,7 +3,7 @@ import { Component } from "inferno";
 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;
index 98b51c7a0d514f5bbbea0758a5e43e65c1979d70..44fa5a9efff9c869e9517eef9af389d6c378af8e 100644 (file)
@@ -1,7 +1,8 @@
+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 {
index 02deb434a5ef0ca830342f2846b50b0a9f03ef42..625379e26a4c6155299cb03949acb51b95f40e0d 100644 (file)
@@ -1,9 +1,10 @@
+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 {
index 4885b6c6487f330dd051d4895c1ff796c44e33c5..d6ed378fe05060189ddef23ff8dccbb0b12c19ab 100644 (file)
@@ -1,8 +1,8 @@
+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;
index 519a9fda32c73f46f821c296137a3cdc63c782e9..bdd42f38c35b6bf07ee6983edda3a13d1bcaf61d 100644 (file)
@@ -1,26 +1,22 @@
 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";
index 511c4b467703ec41e57c984fe5d7f4a9ad34c71c..4df6c82e30e676d6c9c44eda639a488f9d4d9c6c 100644 (file)
@@ -1,7 +1,7 @@
+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 {
index 88aee7edba66de470ec7168b9e7b879622eb38d1..f9cde3ea3e99e296b2fd32ac8a7b6e604fc93511 100644 (file)
@@ -1,5 +1,5 @@
+import { ThemeColor } from "@utils/types";
 import classNames from "classnames";
-import { ThemeColor } from "../../utils";
 
 interface ProgressBarProps {
   className?: string;
index d81af3b6b0908c4c5047389538ab81fcb8a88742..6e6914b3fb616396f79fae51191a33334e4c63e9 100644 (file)
@@ -1,3 +1,4 @@
+import { myAuthRequired } from "@utils/app";
 import { Component, InfernoNode, linkEvent } from "inferno";
 import { T } from "inferno-i18next-dess";
 import {
@@ -5,7 +6,7 @@ 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";
index 1d98de3d625d75687b5b09bc1e9d707bb60632c1..cf3a0f6290c57ff6f288aad01f64dab56e5073f1 100644 (file)
@@ -1,3 +1,4 @@
+import { Choice } from "@utils/types";
 import classNames from "classnames";
 import {
   ChangeEvent,
@@ -7,7 +8,6 @@ import {
   RefObject,
 } from "inferno";
 import { i18n } from "../../i18next";
-import { Choice } from "../../utils";
 import { Icon, Spinner } from "./icon";
 
 interface SearchableSelectProps {
index 7b275718c262bd181b440539d50bb6d07eda673a..546b3aec228a511adb68a1ebac636c5cb4e46d41 100644 (file)
@@ -1,7 +1,8 @@
+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 {
index bf8978238bcfba4db8c5e417a65690f28309e3b7..9a4e836a46c73647d28974267648512dd3af9d0a 100644 (file)
@@ -1,5 +1,18 @@
-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,
@@ -12,16 +25,6 @@ import { i18n } from "../../i18next";
 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";
index 655a07522b8e31fa9baf952da19bf0a8b64e18c3..ab19da82c87216d813e57e30ea5c1408f7f6bfae 100644 (file)
@@ -1,3 +1,5 @@
+import { myAuthRequired } from "@utils/app";
+import { capitalizeFirstLetter, randomStr } from "@utils/helpers";
 import { Component, linkEvent } from "inferno";
 import {
   CommunityView,
@@ -6,7 +8,6 @@ import {
   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";
index 4f45a2b59c9ed0dfc55074b93416ccb68739dcc1..958333332d1c4c2803c8ef1259a3db458a7621fe 100644 (file)
@@ -1,7 +1,9 @@
+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 {
index e03b6990078cd13398073a84c8431ad37b5425c8..195ff687011463f96c8899e960e1b9991dc4ea26 100644 (file)
@@ -1,5 +1,28 @@
-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 {
@@ -54,6 +77,7 @@ import {
   SortType,
   TransferCommunity,
 } from "lemmy-js-client";
+import { fetchLimit, relTags } from "../../config";
 import { i18n } from "../../i18next";
 import {
   CommentViewType,
@@ -63,31 +87,8 @@ import {
 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";
index a061ff0d4ebcf44bdd314e4ca7cc75356f001b30..8a3b198512c6e5e0239d75145f6f9669143bbf0f 100644 (file)
@@ -1,3 +1,4 @@
+import { enableNsfw, setIsoData } from "@utils/app";
 import { Component } from "inferno";
 import {
   CreateCommunity as CreateCommunityI,
@@ -5,7 +6,6 @@ import {
 } 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";
 
index 915e789abd624f6a528542275d46531c2053b78f..8bc54c02438ff57af9ac1c8fdcd1963012e7af1a 100644 (file)
@@ -1,3 +1,5 @@
+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";
@@ -16,8 +18,8 @@ import {
   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";
index 23454ab9db8934c98fc0e560a01ac0b497d8e4c6..9b14310ce3e2f6cdd3b146aafb86aa5bcb5f903b 100644 (file)
@@ -1,3 +1,11 @@
+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 {
@@ -12,19 +20,10 @@ 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";
index ac07ba112588b11cc4a8e750b665e0f094d6759c..569abd0407df2891fa855ec1f8192d30b75e28aa 100644 (file)
@@ -1,3 +1,4 @@
+import { myAuthRequired, setIsoData } from "@utils/app";
 import { Component, linkEvent } from "inferno";
 import {
   CreateCustomEmoji,
@@ -6,14 +7,9 @@ import {
   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";
index 4270bd0bee42d40546dbbc4285d8f6da24573eca..a8441380aeab5d21f98a5c9c3fd516be2a0c4440 100644 (file)
@@ -1,6 +1,28 @@
-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";
@@ -50,41 +72,19 @@ import {
   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";
index 2d8d8d5d2999658f639a5a6101c9259fd85f498f..aba71099323ef4983f4e9ea843cbb824183cf899 100644 (file)
@@ -1,14 +1,16 @@
+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";
 
index be11fd7574fd542bf5d44b65d36ea1eb6ef6660f..90c461a8d94d8848edec76cb64dab979e6e4a33f 100644 (file)
@@ -1,7 +1,8 @@
+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 {
index 1601750ef1d43a7311bbc73a6b8deaafe7e9be8e..3d602f916ddac3c4bed94d9e74d1eae84a1b0b61 100644 (file)
@@ -1,10 +1,12 @@
+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";
 
index 11c1a8e85b17f936eb148e98d3969823016b9c43..619e70d8dd501b2adfce1616e2364d6c561b1f13 100644 (file)
@@ -1,8 +1,9 @@
+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";
 
index 3b71047d9c90b23802ae6abdd5f6548bb3d48919..b595e14d65f3975617af42d2d792976eec624034 100644 (file)
@@ -1,3 +1,4 @@
+import { fetchThemeList, setIsoData } from "@utils/app";
 import { Component, linkEvent } from "inferno";
 import { Helmet } from "inferno-helmet";
 import {
@@ -9,7 +10,6 @@ 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";
 
index 7d504184f7fd2d5e9d366a295c5b55330c0813e8..817dcf8c888a8a5a9d9602c5ff5b1421639d8133 100644 (file)
@@ -1,4 +1,6 @@
+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";
@@ -10,17 +12,12 @@ import {
   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";
index 6e24dd3e850735ebb8bdbd1ae812e6a2615aea81..eb30cbe192dcc991ed4c5f8332d6f50c078a06b0 100644 (file)
@@ -1,3 +1,5 @@
+import { myAuthRequired } from "@utils/app";
+import { capitalizeFirstLetter, validInstanceTLD } from "@utils/helpers";
 import {
   Component,
   InfernoKeyboardEvent,
@@ -12,11 +14,6 @@ import {
   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";
index 8f8b177e7f62922d8040a4675530fc0c8258720a..639e1022b96f46d1863eb4b232393a98d5b03bb7 100644 (file)
@@ -1,7 +1,7 @@
 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";
index dfd13514dff5fc0911046182527286bab3798001..60986c55dba43e0c80022dccc71a6678ce7f84c6 100644 (file)
@@ -1,7 +1,8 @@
+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";
index 91da558ff42b8da0291f168c7f6e2a1a47b6846c..edced0f4b484b2be1e9fe72b5889a5bced8ebeca 100644 (file)
@@ -1,6 +1,20 @@
-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";
@@ -31,22 +45,11 @@ import {
   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";
index 415c3e3fd6c9ee216ba5b37b9d5b9b266449276c..91bbee03eea14a241285b8eb69338552bc63a65c 100644 (file)
@@ -1,3 +1,17 @@
+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,
@@ -44,28 +58,13 @@ import {
   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";
index 3977feb41a47b6b285b06fb89324f031db97d28f..e20c31382d2fa1f469f2ab5cb419652ca39df809 100644 (file)
@@ -1,9 +1,10 @@
+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";
 
index 6efebaa18de65c508eff52461d526e96ea9d38c6..3771b844a18f7a0040a7d9a05bcd60c83b3a596d 100644 (file)
@@ -1,3 +1,4 @@
+import { commentsToFlatNodes } from "@utils/app";
 import { Component } from "inferno";
 import {
   AddAdmin,
@@ -37,7 +38,7 @@ import {
   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";
index cf3802c44708da8dcee6bb2e2ed8da380b0429df..6631a8eab53f9491afbe5d4ceab13448b6968052 100644 (file)
@@ -1,8 +1,10 @@
+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";
 
index 6f6ede3e7370970fcdef760fd9b4d4a807720a74..763947e8e1b07f50e1fe78d158c34876588fe9b1 100644 (file)
@@ -1,6 +1,27 @@
-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";
@@ -50,35 +71,15 @@ import {
   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";
index 23b27b37c94214d6692684e4163a400eb7d7f223..0e636fc77352a95afc6765a9ba4df99c3dd610b6 100644 (file)
@@ -1,3 +1,9 @@
+import {
+  editRegistrationApplication,
+  myAuthRequired,
+  setIsoData,
+} from "@utils/app";
+import { RouteDataResponse } from "@utils/types";
 import { Component, linkEvent } from "inferno";
 import {
   ApproveRegistrationApplication,
@@ -5,19 +11,13 @@ import {
   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";
index e3d100c78b9146ccbe4e8ad80f90e31487ab5f6c..6fe59f1c4bafb2a74835e4168ad8a0d7e0bb6f0d 100644 (file)
@@ -1,4 +1,12 @@
+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,
@@ -18,20 +26,12 @@ import {
   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";
index 9acba57abb95dccaa5a995703737d8b75081c445..5f149dfe2960b569f17262c9874cd3a9819175db 100644 (file)
@@ -1,4 +1,19 @@
-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";
@@ -13,30 +28,12 @@ import {
   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";
index c4687c00be2069e5f37eaf5c9a95a66a8e9fd30c..7ef5382392e8f9ec059cc710c0c062163a1ebf7c 100644 (file)
@@ -1,8 +1,9 @@
+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";
 
index 5a9a1673ebfd8e856b58d5a309d0d1d1c9960428..aa69038128c88abff8825f7f4494e96c4cab19c2 100644 (file)
@@ -1,5 +1,7 @@
-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 {
@@ -17,15 +19,6 @@ 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";
index e6a864af6afb62620b39ac2b5757cbb50424301e..16415d2dd78ceef885483a6393c0df5e6d8e7dd8 100644 (file)
@@ -1,8 +1,8 @@
 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 {
index 2475d49d982bab5e34885596824fc1f897c441bb..9385179836ba6e320556a303b663ca0601364bb6 100644 (file)
@@ -1,4 +1,18 @@
-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 {
@@ -10,29 +24,19 @@ 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";
index 44dde4afb933757cb20c6f80576c3b670888c671..46a31ba6ce2098e48777401f7abed757828c2453 100644 (file)
@@ -1,4 +1,7 @@
+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,
@@ -34,25 +37,13 @@ import {
   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";
index 6586f550e78bb2f2d3d899719f8160a4fabeb722..8af525d0d283414ef6cb0c87f622464fc54f5212 100644 (file)
@@ -1,8 +1,8 @@
+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";
index b87cfc8bf2aafb22610257ac61d9725fa5a58891..31fc2e7023c472b50853e35b755c24f2c12b15ad 100644 (file)
@@ -1,7 +1,29 @@
-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,
@@ -53,6 +75,7 @@ import {
   SavePost,
   TransferCommunity,
 } from "lemmy-js-client";
+import { commentTreeMaxDepth } from "../../config";
 import { i18n } from "../../i18next";
 import {
   CommentNodeI,
@@ -62,29 +85,8 @@ import {
 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";
index ead2b6c78cb1a5a20fb07749ad63f55a0207c6ba..0bc704cf78601f3b6fc643ac4fdf2ebe044e707a 100644 (file)
@@ -1,3 +1,5 @@
+import { getRecipientIdFromProps, myAuth, setIsoData } from "@utils/app";
+import { RouteDataResponse } from "@utils/types";
 import { Component } from "inferno";
 import {
   CreatePrivateMessage as CreatePrivateMessageI,
@@ -9,13 +11,7 @@ import { i18n } from "../../i18next";
 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";
index d7b27d74b1cff0029ee05249ce5d25d650904c47..1b9cb50c74c79c4b4758171ae0e4b73e46e59a7e 100644 (file)
@@ -1,3 +1,5 @@
+import { myAuthRequired } from "@utils/app";
+import { capitalizeFirstLetter } from "@utils/helpers";
 import { Component, InfernoNode, linkEvent } from "inferno";
 import { T } from "inferno-i18next-dess";
 import {
@@ -6,13 +8,9 @@ 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";
index 7fa4ae0a75c1b91584a26b397210a31bc5ecff0f..38a20d857755ab615cfcae2c14dfdc13855d78a7 100644 (file)
@@ -1,3 +1,4 @@
+import { myAuthRequired } from "@utils/app";
 import { Component, InfernoNode, linkEvent } from "inferno";
 import { T } from "inferno-i18next-dess";
 import {
@@ -5,7 +6,7 @@ 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";
 
index b7426f30947673712c14262c505e146fa7e1f7d2..db40604c3fac3455d8a0d3ab7f52eba0d155504b 100644 (file)
@@ -1,3 +1,4 @@
+import { myAuthRequired } from "@utils/app";
 import { Component, InfernoNode, linkEvent } from "inferno";
 import {
   CreatePrivateMessage,
@@ -9,8 +10,8 @@ import {
   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";
index f0931b125095487e036c2529b93cea571ce682fd..72ea05a0a9c604ac6927aee0150db4e4df8a6673 100644 (file)
@@ -1,5 +1,28 @@
-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 {
@@ -22,32 +45,11 @@ 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";
diff --git a/src/shared/config.ts b/src/shared/config.ts
new file mode 100644 (file)
index 0000000..384e86c
--- /dev/null
@@ -0,0 +1,26 @@
+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";
index 6afebb62c2a68b6c5ae19acf7dac07c20e7ac671..6946fd32730f1a835e9b28a0d39acc8b1d099e68 100644 (file)
@@ -1,7 +1,7 @@
+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.
diff --git a/src/shared/markdown.ts b/src/shared/markdown.ts
new file mode 100644 (file)
index 0000000..d63dc8a
--- /dev/null
@@ -0,0 +1,307 @@
+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;
+}
index cdcf11d7632a0089c9757c51c5d80abfa0a22f2a..f6c30167d9843bfc4ac0f341050083d4daf1949f 100644 (file)
@@ -1,7 +1,7 @@
 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";
index 346d833adcde6955a8874f5423ba6bc2021c86f0..61abc2ebff9bf8475649f3d15b1bd54d818f85f5 100644 (file)
@@ -1,11 +1,12 @@
 // 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;
diff --git a/src/shared/tippy.ts b/src/shared/tippy.ts
new file mode 100644 (file)
index 0000000..6bb8760
--- /dev/null
@@ -0,0 +1,19 @@
+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],
+    });
+  }
+}
diff --git a/src/shared/toast.ts b/src/shared/toast.ts
new file mode 100644 (file)
index 0000000..b8ab062
--- /dev/null
@@ -0,0 +1,54 @@
+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();
+  }
+}
diff --git a/src/shared/utils.ts b/src/shared/utils.ts
deleted file mode 100644 (file)
index 33658d1..0000000
+++ /dev/null
@@ -1,1281 +0,0 @@
-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]>;
-};
diff --git a/src/shared/utils/app/build-comments-tree.ts b/src/shared/utils/app/build-comments-tree.ts
new file mode 100644 (file)
index 0000000..8857fa4
--- /dev/null
@@ -0,0 +1,54 @@
+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;
+}
diff --git a/src/shared/utils/app/color-list.ts b/src/shared/utils/app/color-list.ts
new file mode 100644 (file)
index 0000000..9194864
--- /dev/null
@@ -0,0 +1,11 @@
+import { hsl } from "@utils/helpers";
+
+export const colorList: string[] = [
+  hsl(0),
+  hsl(50),
+  hsl(100),
+  hsl(150),
+  hsl(200),
+  hsl(250),
+  hsl(300),
+];
diff --git a/src/shared/utils/app/comments-to-flat-nodes.ts b/src/shared/utils/app/comments-to-flat-nodes.ts
new file mode 100644 (file)
index 0000000..bc80015
--- /dev/null
@@ -0,0 +1,12 @@
+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;
+}
diff --git a/src/shared/utils/app/community-rss-url.ts b/src/shared/utils/app/community-rss-url.ts
new file mode 100644 (file)
index 0000000..2c930c3
--- /dev/null
@@ -0,0 +1,4 @@
+export default function communityRSSUrl(actorId: string, sort: string): string {
+  const url = new URL(actorId);
+  return `${url.origin}/feeds${url.pathname}.xml?sort=${sort}`;
+}
diff --git a/src/shared/utils/app/community-search.ts b/src/shared/utils/app/community-search.ts
new file mode 100644 (file)
index 0000000..4661c30
--- /dev/null
@@ -0,0 +1,14 @@
+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,
+  }));
+}
diff --git a/src/shared/utils/app/community-select-name.ts b/src/shared/utils/app/community-select-name.ts
new file mode 100644 (file)
index 0000000..9404e87
--- /dev/null
@@ -0,0 +1,8 @@
+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}`;
+}
diff --git a/src/shared/utils/app/community-to-choice.ts b/src/shared/utils/app/community-to-choice.ts
new file mode 100644 (file)
index 0000000..6220ed6
--- /dev/null
@@ -0,0 +1,10 @@
+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),
+  };
+}
diff --git a/src/shared/utils/app/convert-comment-sort-type.ts b/src/shared/utils/app/convert-comment-sort-type.ts
new file mode 100644 (file)
index 0000000..3a89a23
--- /dev/null
@@ -0,0 +1,25 @@
+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";
+    }
+  }
+}
diff --git a/src/shared/utils/app/edit-comment-reply.ts b/src/shared/utils/app/edit-comment-reply.ts
new file mode 100644 (file)
index 0000000..fe1eb62
--- /dev/null
@@ -0,0 +1,9 @@
+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);
+}
diff --git a/src/shared/utils/app/edit-comment-report.ts b/src/shared/utils/app/edit-comment-report.ts
new file mode 100644 (file)
index 0000000..c57b4d5
--- /dev/null
@@ -0,0 +1,9 @@
+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);
+}
diff --git a/src/shared/utils/app/edit-comment.ts b/src/shared/utils/app/edit-comment.ts
new file mode 100644 (file)
index 0000000..90c9c1b
--- /dev/null
@@ -0,0 +1,9 @@
+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);
+}
diff --git a/src/shared/utils/app/edit-community.ts b/src/shared/utils/app/edit-community.ts
new file mode 100644 (file)
index 0000000..f902142
--- /dev/null
@@ -0,0 +1,9 @@
+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);
+}
diff --git a/src/shared/utils/app/edit-mention.ts b/src/shared/utils/app/edit-mention.ts
new file mode 100644 (file)
index 0000000..ce372b8
--- /dev/null
@@ -0,0 +1,9 @@
+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);
+}
diff --git a/src/shared/utils/app/edit-post-report.ts b/src/shared/utils/app/edit-post-report.ts
new file mode 100644 (file)
index 0000000..721a141
--- /dev/null
@@ -0,0 +1,9 @@
+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);
+}
diff --git a/src/shared/utils/app/edit-post.ts b/src/shared/utils/app/edit-post.ts
new file mode 100644 (file)
index 0000000..0c78fce
--- /dev/null
@@ -0,0 +1,9 @@
+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);
+}
diff --git a/src/shared/utils/app/edit-private-message-report.ts b/src/shared/utils/app/edit-private-message-report.ts
new file mode 100644 (file)
index 0000000..2fb001f
--- /dev/null
@@ -0,0 +1,9 @@
+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);
+}
diff --git a/src/shared/utils/app/edit-private-message.ts b/src/shared/utils/app/edit-private-message.ts
new file mode 100644 (file)
index 0000000..8bb8ed9
--- /dev/null
@@ -0,0 +1,9 @@
+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);
+}
diff --git a/src/shared/utils/app/edit-registration-application.ts b/src/shared/utils/app/edit-registration-application.ts
new file mode 100644 (file)
index 0000000..9a100cb
--- /dev/null
@@ -0,0 +1,9 @@
+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);
+}
diff --git a/src/shared/utils/app/edit-with.ts b/src/shared/utils/app/edit-with.ts
new file mode 100644 (file)
index 0000000..6aa09e3
--- /dev/null
@@ -0,0 +1,14 @@
+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
+    ),
+  ];
+}
diff --git a/src/shared/utils/app/enable-downvotes.ts b/src/shared/utils/app/enable-downvotes.ts
new file mode 100644 (file)
index 0000000..c6ba459
--- /dev/null
@@ -0,0 +1,5 @@
+import { GetSiteResponse } from "lemmy-js-client";
+
+export default function enableDownvotes(siteRes: GetSiteResponse): boolean {
+  return siteRes.site_view.local_site.enable_downvotes;
+}
diff --git a/src/shared/utils/app/enable-nsfw.ts b/src/shared/utils/app/enable-nsfw.ts
new file mode 100644 (file)
index 0000000..352b40b
--- /dev/null
@@ -0,0 +1,5 @@
+import { GetSiteResponse } from "lemmy-js-client";
+
+export default function enableNsfw(siteRes: GetSiteResponse): boolean {
+  return siteRes.site_view.local_site.enable_nsfw;
+}
diff --git a/src/shared/utils/app/fetch-communities.ts b/src/shared/utils/app/fetch-communities.ts
new file mode 100644 (file)
index 0000000..3bf395f
--- /dev/null
@@ -0,0 +1,7 @@
+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 : [];
+}
diff --git a/src/shared/utils/app/fetch-search-results.ts b/src/shared/utils/app/fetch-search-results.ts
new file mode 100644 (file)
index 0000000..5183546
--- /dev/null
@@ -0,0 +1,18 @@
+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);
+}
diff --git a/src/shared/utils/app/fetch-theme-list.ts b/src/shared/utils/app/fetch-theme-list.ts
new file mode 100644 (file)
index 0000000..d308ef9
--- /dev/null
@@ -0,0 +1,3 @@
+export default async function fetchThemeList(): Promise<string[]> {
+  return fetch("/css/themelist").then(res => res.json());
+}
diff --git a/src/shared/utils/app/fetch-users.ts b/src/shared/utils/app/fetch-users.ts
new file mode 100644 (file)
index 0000000..3cb8853
--- /dev/null
@@ -0,0 +1,7 @@
+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 : [];
+}
diff --git a/src/shared/utils/app/get-comment-id-from-props.ts b/src/shared/utils/app/get-comment-id-from-props.ts
new file mode 100644 (file)
index 0000000..548cd29
--- /dev/null
@@ -0,0 +1,4 @@
+export default function getCommentIdFromProps(props: any): number | undefined {
+  const id = props.match.params.comment_id;
+  return id ? Number(id) : undefined;
+}
diff --git a/src/shared/utils/app/get-comment-parent-id.ts b/src/shared/utils/app/get-comment-parent-id.ts
new file mode 100644 (file)
index 0000000..051446d
--- /dev/null
@@ -0,0 +1,13 @@
+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;
+}
diff --git a/src/shared/utils/app/get-data-type-string.ts b/src/shared/utils/app/get-data-type-string.ts
new file mode 100644 (file)
index 0000000..b8d0f9e
--- /dev/null
@@ -0,0 +1,5 @@
+import { DataType } from "../../interfaces";
+
+export default function getDataTypeString(dt: DataType) {
+  return dt === DataType.Post ? "Post" : "Comment";
+}
diff --git a/src/shared/utils/app/get-depth-from-comment.ts b/src/shared/utils/app/get-depth-from-comment.ts
new file mode 100644 (file)
index 0000000..caf757b
--- /dev/null
@@ -0,0 +1,8 @@
+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;
+}
diff --git a/src/shared/utils/app/get-id-from-props.ts b/src/shared/utils/app/get-id-from-props.ts
new file mode 100644 (file)
index 0000000..345a25e
--- /dev/null
@@ -0,0 +1,4 @@
+export default function getIdFromProps(props: any): number | undefined {
+  const id = props.match.params.post_id;
+  return id ? Number(id) : undefined;
+}
diff --git a/src/shared/utils/app/get-recipient-id-from-props.ts b/src/shared/utils/app/get-recipient-id-from-props.ts
new file mode 100644 (file)
index 0000000..5dae458
--- /dev/null
@@ -0,0 +1,5 @@
+export default function getRecipientIdFromProps(props: any): number {
+  return props.match.params.recipient_id
+    ? Number(props.match.params.recipient_id)
+    : 1;
+}
diff --git a/src/shared/utils/app/get-updated-search-id.ts b/src/shared/utils/app/get-updated-search-id.ts
new file mode 100644 (file)
index 0000000..47863be
--- /dev/null
@@ -0,0 +1,8 @@
+export default function getUpdatedSearchId(
+  id?: number | null,
+  urlId?: number | null
+) {
+  return id === null
+    ? undefined
+    : ((id ?? urlId) === 0 ? undefined : id ?? urlId)?.toString();
+}
diff --git a/src/shared/utils/app/index.ts b/src/shared/utils/app/index.ts
new file mode 100644 (file)
index 0000000..cdae267
--- /dev/null
@@ -0,0 +1,111 @@
+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,
+};
diff --git a/src/shared/utils/app/initialize-site.ts b/src/shared/utils/app/initialize-site.ts
new file mode 100644 (file)
index 0000000..0f2a2df
--- /dev/null
@@ -0,0 +1,13 @@
+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();
+}
diff --git a/src/shared/utils/app/insert-comment-into-tree.ts b/src/shared/utils/app/insert-comment-into-tree.ts
new file mode 100644 (file)
index 0000000..a74f727
--- /dev/null
@@ -0,0 +1,27 @@
+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);
+  }
+}
diff --git a/src/shared/utils/app/is-auth-path.ts b/src/shared/utils/app/is-auth-path.ts
new file mode 100644 (file)
index 0000000..0ec963a
--- /dev/null
@@ -0,0 +1,5 @@
+export default function isAuthPath(pathname: string) {
+  return /create_.*|inbox|settings|admin|reports|registration_applications/g.test(
+    pathname
+  );
+}
diff --git a/src/shared/utils/app/is-post-blocked.ts b/src/shared/utils/app/is-post-blocked.ts
new file mode 100644 (file)
index 0000000..a0c6957
--- /dev/null
@@ -0,0 +1,17 @@
+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
+  );
+}
diff --git a/src/shared/utils/app/my-auth-required.ts b/src/shared/utils/app/my-auth-required.ts
new file mode 100644 (file)
index 0000000..e82f21c
--- /dev/null
@@ -0,0 +1,5 @@
+import { UserService } from "../../services";
+
+export default function myAuthRequired(): string {
+  return UserService.Instance.auth(true) ?? "";
+}
diff --git a/src/shared/utils/app/my-auth.ts b/src/shared/utils/app/my-auth.ts
new file mode 100644 (file)
index 0000000..d536d8a
--- /dev/null
@@ -0,0 +1,5 @@
+import { UserService } from "../../services";
+
+export default function myAuth(): string | undefined {
+  return UserService.Instance.auth();
+}
diff --git a/src/shared/utils/app/new-vote.ts b/src/shared/utils/app/new-vote.ts
new file mode 100644 (file)
index 0000000..030fa79
--- /dev/null
@@ -0,0 +1,9 @@
+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;
+  }
+}
diff --git a/src/shared/utils/app/nsfw-check.ts b/src/shared/utils/app/nsfw-check.ts
new file mode 100644 (file)
index 0000000..a710775
--- /dev/null
@@ -0,0 +1,11 @@
+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);
+}
diff --git a/src/shared/utils/app/person-search.ts b/src/shared/utils/app/person-search.ts
new file mode 100644 (file)
index 0000000..2356466
--- /dev/null
@@ -0,0 +1,14 @@
+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,
+  }));
+}
diff --git a/src/shared/utils/app/person-select-name.ts b/src/shared/utils/app/person-select-name.ts
new file mode 100644 (file)
index 0000000..fb630b2
--- /dev/null
@@ -0,0 +1,9 @@
+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}`;
+}
diff --git a/src/shared/utils/app/person-to-choice.ts b/src/shared/utils/app/person-to-choice.ts
new file mode 100644 (file)
index 0000000..4c16129
--- /dev/null
@@ -0,0 +1,10 @@
+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),
+  };
+}
diff --git a/src/shared/utils/app/post-to-comment-sort-type.ts b/src/shared/utils/app/post-to-comment-sort-type.ts
new file mode 100644 (file)
index 0000000..0219eb9
--- /dev/null
@@ -0,0 +1,16 @@
+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";
+  }
+}
diff --git a/src/shared/utils/app/search-comment-tree.ts b/src/shared/utils/app/search-comment-tree.ts
new file mode 100644 (file)
index 0000000..be1016c
--- /dev/null
@@ -0,0 +1,21 @@
+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;
+}
diff --git a/src/shared/utils/app/selectable-languages.ts b/src/shared/utils/app/selectable-languages.ts
new file mode 100644 (file)
index 0000000..8079abd
--- /dev/null
@@ -0,0 +1,34 @@
+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));
+    }
+  }
+}
diff --git a/src/shared/utils/app/set-iso-data.ts b/src/shared/utils/app/set-iso-data.ts
new file mode 100644 (file)
index 0000000..1e149bb
--- /dev/null
@@ -0,0 +1,11 @@
+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;
+}
diff --git a/src/shared/utils/app/set-theme.ts b/src/shared/utils/app/set-theme.ts
new file mode 100644 (file)
index 0000000..6d9d46c
--- /dev/null
@@ -0,0 +1,36 @@
+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");
+}
diff --git a/src/shared/utils/app/show-avatars.ts b/src/shared/utils/app/show-avatars.ts
new file mode 100644 (file)
index 0000000..34cf943
--- /dev/null
@@ -0,0 +1,7 @@
+import { UserService } from "../../services";
+
+export default function showAvatars(
+  myUserInfo = UserService.Instance.myUserInfo
+): boolean {
+  return myUserInfo?.local_user_view.local_user.show_avatars ?? true;
+}
diff --git a/src/shared/utils/app/show-local.ts b/src/shared/utils/app/show-local.ts
new file mode 100644 (file)
index 0000000..57da4d4
--- /dev/null
@@ -0,0 +1,5 @@
+import { IsoData } from "../../interfaces";
+
+export default function showLocal(isoData: IsoData): boolean {
+  return isoData.site_res.site_view.local_site.federation_enabled;
+}
diff --git a/src/shared/utils/app/show-scores.ts b/src/shared/utils/app/show-scores.ts
new file mode 100644 (file)
index 0000000..ea26634
--- /dev/null
@@ -0,0 +1,7 @@
+import { UserService } from "../../services";
+
+export default function showScores(
+  myUserInfo = UserService.Instance.myUserInfo
+): boolean {
+  return myUserInfo?.local_user_view.local_user.show_scores ?? true;
+}
diff --git a/src/shared/utils/app/site-banner-css.ts b/src/shared/utils/app/site-banner-css.ts
new file mode 100644 (file)
index 0000000..825f983
--- /dev/null
@@ -0,0 +1,12 @@
+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; \
+      `;
+}
diff --git a/src/shared/utils/app/update-community-block.ts b/src/shared/utils/app/update-community-block.ts
new file mode 100644 (file)
index 0000000..7042527
--- /dev/null
@@ -0,0 +1,24 @@
+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}`);
+    }
+  }
+}
diff --git a/src/shared/utils/app/update-person-block.ts b/src/shared/utils/app/update-person-block.ts
new file mode 100644 (file)
index 0000000..3b5223b
--- /dev/null
@@ -0,0 +1,24 @@
+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}`);
+    }
+  }
+}
index a7a08a505f4f486c4a2fc85fe23cc329ad1aef98..b12737ce0be9e9407665328c0b4d1289fe27148d 100644 (file)
@@ -1,5 +1,15 @@
 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,
+};
diff --git a/src/shared/utils/browser/load-css.ts b/src/shared/utils/browser/load-css.ts
new file mode 100644 (file)
index 0000000..4b4b86e
--- /dev/null
@@ -0,0 +1,12 @@
+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);
+  }
+}
diff --git a/src/shared/utils/browser/restore-scroll-position.ts b/src/shared/utils/browser/restore-scroll-position.ts
new file mode 100644 (file)
index 0000000..f153464
--- /dev/null
@@ -0,0 +1,5 @@
+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);
+}
diff --git a/src/shared/utils/browser/save-scroll-position.ts b/src/shared/utils/browser/save-scroll-position.ts
new file mode 100644 (file)
index 0000000..4835328
--- /dev/null
@@ -0,0 +1,5 @@
+export default function saveScrollPosition(context: any) {
+  const path: string = context.router.route.location.pathname;
+  const y = window.scrollY;
+  sessionStorage.setItem(`scrollPosition_${path}`, y.toString());
+}
diff --git a/src/shared/utils/helpers/capitalize-first-letter.ts b/src/shared/utils/helpers/capitalize-first-letter.ts
new file mode 100644 (file)
index 0000000..17435b5
--- /dev/null
@@ -0,0 +1,3 @@
+export default function capitalizeFirstLetter(str: string): string {
+  return str.charAt(0).toUpperCase() + str.slice(1);
+}
diff --git a/src/shared/utils/helpers/edit-list-immutable.ts b/src/shared/utils/helpers/edit-list-immutable.ts
new file mode 100644 (file)
index 0000000..7ebce70
--- /dev/null
@@ -0,0 +1,20 @@
+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)),
+  ];
+}
diff --git a/src/shared/utils/helpers/future-days-to-unix-time.ts b/src/shared/utils/helpers/future-days-to-unix-time.ts
new file mode 100644 (file)
index 0000000..e9d4371
--- /dev/null
@@ -0,0 +1,9 @@
+export default function futureDaysToUnixTime(
+  days?: number
+): number | undefined {
+  return days
+    ? Math.trunc(
+        new Date(Date.now() + 1000 * 60 * 60 * 24 * days).getTime() / 1000
+      )
+    : undefined;
+}
diff --git a/src/shared/utils/helpers/get-id-from-string.ts b/src/shared/utils/helpers/get-id-from-string.ts
new file mode 100644 (file)
index 0000000..6eb7426
--- /dev/null
@@ -0,0 +1,3 @@
+export default function getIdFromString(id?: string): number | undefined {
+  return id && id !== "0" && !Number.isNaN(Number(id)) ? Number(id) : undefined;
+}
diff --git a/src/shared/utils/helpers/get-page-from-string.ts b/src/shared/utils/helpers/get-page-from-string.ts
new file mode 100644 (file)
index 0000000..6d83cdc
--- /dev/null
@@ -0,0 +1,3 @@
+export default function getPageFromString(page?: string): number {
+  return page && !Number.isNaN(Number(page)) ? Number(page) : 1;
+}
diff --git a/src/shared/utils/helpers/get-random-char-from-alphabet.ts b/src/shared/utils/helpers/get-random-char-from-alphabet.ts
new file mode 100644 (file)
index 0000000..0b6ad32
--- /dev/null
@@ -0,0 +1,3 @@
+export default function getRandomCharFromAlphabet(alphabet: string): string {
+  return alphabet.charAt(Math.floor(Math.random() * alphabet.length));
+}
diff --git a/src/shared/utils/helpers/get-random-from-list.ts b/src/shared/utils/helpers/get-random-from-list.ts
new file mode 100644 (file)
index 0000000..065eb7a
--- /dev/null
@@ -0,0 +1,5 @@
+export default function getRandomFromList<T>(list: T[]): T | undefined {
+  return list.length == 0
+    ? undefined
+    : list.at(Math.floor(Math.random() * list.length));
+}
diff --git a/src/shared/utils/helpers/get-unix-time.ts b/src/shared/utils/helpers/get-unix-time.ts
new file mode 100644 (file)
index 0000000..ec9d019
--- /dev/null
@@ -0,0 +1,3 @@
+export default function getUnixTime(text?: string): number | undefined {
+  return text ? new Date(text).getTime() / 1000 : undefined;
+}
diff --git a/src/shared/utils/helpers/hostname.ts b/src/shared/utils/helpers/hostname.ts
new file mode 100644 (file)
index 0000000..89d9aca
--- /dev/null
@@ -0,0 +1,4 @@
+export default function hostname(url: string): string {
+  const cUrl = new URL(url);
+  return cUrl.port ? `${cUrl.hostname}:${cUrl.port}` : `${cUrl.hostname}`;
+}
diff --git a/src/shared/utils/helpers/hsl.ts b/src/shared/utils/helpers/hsl.ts
new file mode 100644 (file)
index 0000000..78fc18d
--- /dev/null
@@ -0,0 +1,3 @@
+export default function hsl(num: number) {
+  return `hsla(${num}, 35%, 50%, 0.5)`;
+}
index 663afbf9ed068dbae116122dc72e4c3c280a0619..36ae83fa36e50ac9a50bbb847c933072568ca8f6 100644 (file)
@@ -1,8 +1,49 @@
+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,
+};
diff --git a/src/shared/utils/helpers/is-cake-day.ts b/src/shared/utils/helpers/is-cake-day.ts
new file mode 100644 (file)
index 0000000..694be17
--- /dev/null
@@ -0,0 +1,33 @@
+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()
+  );
+}
diff --git a/src/shared/utils/helpers/num-to-si.ts b/src/shared/utils/helpers/num-to-si.ts
new file mode 100644 (file)
index 0000000..4c5911f
--- /dev/null
@@ -0,0 +1,10 @@
+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);
+}
diff --git a/src/shared/utils/helpers/random-str.ts b/src/shared/utils/helpers/random-str.ts
new file mode 100644 (file)
index 0000000..b4be718
--- /dev/null
@@ -0,0 +1,19 @@
+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("");
+}
diff --git a/src/shared/utils/helpers/valid-email.ts b/src/shared/utils/helpers/valid-email.ts
new file mode 100644 (file)
index 0000000..187d800
--- /dev/null
@@ -0,0 +1,5 @@
+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());
+}
diff --git a/src/shared/utils/helpers/valid-instance-tld.ts b/src/shared/utils/helpers/valid-instance-tld.ts
new file mode 100644 (file)
index 0000000..20c90a6
--- /dev/null
@@ -0,0 +1,5 @@
+const tldRegex = /([a-z0-9]+\.)*[a-z0-9]+\.[a-z]+/;
+
+export default function validInstanceTLD(str: string) {
+  return tldRegex.test(str);
+}
diff --git a/src/shared/utils/helpers/valid-title.ts b/src/shared/utils/helpers/valid-title.ts
new file mode 100644 (file)
index 0000000..8b146d3
--- /dev/null
@@ -0,0 +1,8 @@
+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);
+}
diff --git a/src/shared/utils/helpers/valid-url.ts b/src/shared/utils/helpers/valid-url.ts
new file mode 100644 (file)
index 0000000..caedbc2
--- /dev/null
@@ -0,0 +1,8 @@
+export default function validURL(str: string) {
+  try {
+    new URL(str);
+    return true;
+  } catch {
+    return false;
+  }
+}
diff --git a/src/shared/utils/media/index.ts b/src/shared/utils/media/index.ts
new file mode 100644 (file)
index 0000000..bf831c1
--- /dev/null
@@ -0,0 +1,4 @@
+import isImage from "./is-image";
+import isVideo from "./is-video";
+
+export { isImage, isVideo };
diff --git a/src/shared/utils/media/is-image.ts b/src/shared/utils/media/is-image.ts
new file mode 100644 (file)
index 0000000..c828f59
--- /dev/null
@@ -0,0 +1,5 @@
+const imageRegex = /(http)?s?:?(\/\/[^"']*\.(?:jpg|jpeg|gif|png|svg|webp))/;
+
+export default function isImage(url: string) {
+  return imageRegex.test(url);
+}
diff --git a/src/shared/utils/media/is-video.ts b/src/shared/utils/media/is-video.ts
new file mode 100644 (file)
index 0000000..045b44e
--- /dev/null
@@ -0,0 +1,5 @@
+const videoRegex = /(http)?s?:?(\/\/[^"']*\.(?:mp4|webm))/;
+
+export default function isVideo(url: string) {
+  return videoRegex.test(url);
+}
diff --git a/src/shared/utils/types/choice.ts b/src/shared/utils/types/choice.ts
new file mode 100644 (file)
index 0000000..e86617e
--- /dev/null
@@ -0,0 +1,5 @@
+export default interface Choice {
+  value: string;
+  label: string;
+  disabled?: boolean;
+}
diff --git a/src/shared/utils/types/community-tribute.ts b/src/shared/utils/types/community-tribute.ts
new file mode 100644 (file)
index 0000000..6546fc1
--- /dev/null
@@ -0,0 +1,6 @@
+import { CommunityView } from "lemmy-js-client";
+
+export default interface CommunityTribute {
+  key: string;
+  view: CommunityView;
+}
diff --git a/src/shared/utils/types/error-page-data.ts b/src/shared/utils/types/error-page-data.ts
new file mode 100644 (file)
index 0000000..95f10f0
--- /dev/null
@@ -0,0 +1,4 @@
+export default interface ErrorPageData {
+  error?: string;
+  adminMatrixIds?: string[];
+}
index 9b4a1cec4a74f9ec2a62468794451791ba430d6c..2086b966990f5a928843fc75126306e940cdcb1d 100644 (file)
@@ -1,3 +1,19 @@
+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,
+};
diff --git a/src/shared/utils/types/person-tribute.ts b/src/shared/utils/types/person-tribute.ts
new file mode 100644 (file)
index 0000000..0b318ea
--- /dev/null
@@ -0,0 +1,6 @@
+import { PersonView } from "lemmy-js-client";
+
+export default interface PersonTribute {
+  key: string;
+  view: PersonView;
+}
diff --git a/src/shared/utils/types/route-data-response.ts b/src/shared/utils/types/route-data-response.ts
new file mode 100644 (file)
index 0000000..a4f1722
--- /dev/null
@@ -0,0 +1,5 @@
+import { RequestState } from "../../services/HttpService";
+
+export type RouteDataResponse<T extends Record<string, any>> = {
+  [K in keyof T]: RequestState<T[K]>;
+};
diff --git a/src/shared/utils/types/theme-color.ts b/src/shared/utils/types/theme-color.ts
new file mode 100644 (file)
index 0000000..11346a4
--- /dev/null
@@ -0,0 +1,22 @@
+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";
diff --git a/src/shared/utils/types/with-comment.ts b/src/shared/utils/types/with-comment.ts
new file mode 100644 (file)
index 0000000..703f5e0
--- /dev/null
@@ -0,0 +1,8 @@
+import { Comment, CommentAggregates } from "lemmy-js-client";
+
+export default interface WithComment {
+  comment: Comment;
+  counts: CommentAggregates;
+  my_vote?: number;
+  saved: boolean;
+}