From: Alec Armbruster <35377827+alectrocute@users.noreply.github.com> Date: Wed, 21 Jun 2023 22:28:24 +0000 (-0400) Subject: `utils.ts` organization, round two (#1427) X-Git-Url: http://these/git/%24%7Bsubmission.url%7D?a=commitdiff_plain;h=043b522ff1b0a395eda7d5e2b01fd035c5ec6b6e;p=lemmy-ui.git `utils.ts` organization, round two (#1427) * 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 * prettier pass --------- Co-authored-by: SleeplessOne1917 --- diff --git a/src/client/index.tsx b/src/client/index.tsx index 860c075..4ff794e 100644 --- a/src/client/index.tsx +++ b/src/client/index.tsx @@ -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"; diff --git a/src/server/handlers/catch-all-handler.tsx b/src/server/handlers/catch-all-handler.tsx index 025aaa6..b9ff13b 100644 --- a/src/server/handlers/catch-all-handler.tsx +++ b/src/server/handlers/catch-all-handler.tsx @@ -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"; diff --git a/src/server/utils/create-ssr-html.tsx b/src/server/utils/create-ssr-html.tsx index 2c35aa2..569d83a 100644 --- a/src/server/utils/create-ssr-html.tsx +++ b/src/server/utils/create-ssr-html.tsx @@ -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"] || ""; diff --git a/src/server/utils/get-error-page-data.ts b/src/server/utils/get-error-page-data.ts index 3c82372..fc37cca 100644 --- a/src/server/utils/get-error-page-data.ts +++ b/src/server/utils/get-error-page-data.ts @@ -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 = {}; diff --git a/src/shared/components/app/app.tsx b/src/shared/components/app/app.tsx index 79aa77e..e615bb3 100644 --- a/src/shared/components/app/app.tsx +++ b/src/shared/components/app/app.tsx @@ -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"; diff --git a/src/shared/components/app/error-page.tsx b/src/shared/components/app/error-page.tsx index 191c322..7d4e297 100644 --- a/src/shared/components/app/error-page.tsx +++ b/src/shared/components/app/error-page.tsx @@ -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 { private isoData: IsoDataOptionalSite = setIsoData(this.context); diff --git a/src/shared/components/app/footer.tsx b/src/shared/components/app/footer.tsx index 8ea647c..601045a 100644 --- a/src/shared/components/app/footer.tsx +++ b/src/shared/components/app/footer.tsx @@ -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 { diff --git a/src/shared/components/app/navbar.tsx b/src/shared/components/app/navbar.tsx index 5fa7580..12ca05d 100644 --- a/src/shared/components/app/navbar.tsx +++ b/src/shared/components/app/navbar.tsx @@ -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"; diff --git a/src/shared/components/comment/comment-form.tsx b/src/shared/components/comment/comment-form.tsx index 2638a40..c399fb0 100644 --- a/src/shared/components/comment/comment-form.tsx +++ b/src/shared/components/comment/comment-form.tsx @@ -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"; diff --git a/src/shared/components/comment/comment-node.tsx b/src/shared/components/comment/comment-node.tsx index 15c68f7..f6cb4b2 100644 --- a/src/shared/components/comment/comment-node.tsx +++ b/src/shared/components/comment/comment-node.tsx @@ -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"; diff --git a/src/shared/components/comment/comment-nodes.tsx b/src/shared/components/comment/comment-nodes.tsx index 8c0a236..02e621b 100644 --- a/src/shared/components/comment/comment-nodes.tsx +++ b/src/shared/components/comment/comment-nodes.tsx @@ -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 { diff --git a/src/shared/components/comment/comment-report.tsx b/src/shared/components/comment/comment-report.tsx index 2f765e0..b363009 100644 --- a/src/shared/components/comment/comment-report.tsx +++ b/src/shared/components/comment/comment-report.tsx @@ -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"; diff --git a/src/shared/components/common/badges.tsx b/src/shared/components/common/badges.tsx index 17ae53f..ed9aecf 100644 --- a/src/shared/components/common/badges.tsx +++ b/src/shared/components/common/badges.tsx @@ -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; diff --git a/src/shared/components/common/comment-sort-select.tsx b/src/shared/components/common/comment-sort-select.tsx index e9885af..18eaed2 100644 --- a/src/shared/components/common/comment-sort-select.tsx +++ b/src/shared/components/common/comment-sort-select.tsx @@ -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 { diff --git a/src/shared/components/common/emoji-mart.tsx b/src/shared/components/common/emoji-mart.tsx index dff8c3a..6ee3aa8 100644 --- a/src/shared/components/common/emoji-mart.tsx +++ b/src/shared/components/common/emoji-mart.tsx @@ -1,5 +1,5 @@ import { Component } from "inferno"; -import { getEmojiMart } from "../../utils"; +import { getEmojiMart } from "../../markdown"; interface EmojiMartProps { onEmojiClick?(val: any): any; diff --git a/src/shared/components/common/error-guard.tsx b/src/shared/components/common/error-guard.tsx index 3012154..6f0302a 100644 --- a/src/shared/components/common/error-guard.tsx +++ b/src/shared/components/common/error-guard.tsx @@ -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 { diff --git a/src/shared/components/common/html-tags.tsx b/src/shared/components/common/html-tags.tsx index f32b0fc..63eb461 100644 --- a/src/shared/components/common/html-tags.tsx +++ b/src/shared/components/common/html-tags.tsx @@ -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; diff --git a/src/shared/components/common/image-upload-form.tsx b/src/shared/components/common/image-upload-form.tsx index 98b51c7..44fa5a9 100644 --- a/src/shared/components/common/image-upload-form.tsx +++ b/src/shared/components/common/image-upload-form.tsx @@ -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 { diff --git a/src/shared/components/common/language-select.tsx b/src/shared/components/common/language-select.tsx index 02deb43..625379e 100644 --- a/src/shared/components/common/language-select.tsx +++ b/src/shared/components/common/language-select.tsx @@ -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 { diff --git a/src/shared/components/common/listing-type-select.tsx b/src/shared/components/common/listing-type-select.tsx index 4885b6c..d6ed378 100644 --- a/src/shared/components/common/listing-type-select.tsx +++ b/src/shared/components/common/listing-type-select.tsx @@ -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; diff --git a/src/shared/components/common/markdown-textarea.tsx b/src/shared/components/common/markdown-textarea.tsx index 519a9fd..bdd42f3 100644 --- a/src/shared/components/common/markdown-textarea.tsx +++ b/src/shared/components/common/markdown-textarea.tsx @@ -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"; diff --git a/src/shared/components/common/moment-time.tsx b/src/shared/components/common/moment-time.tsx index 511c4b4..4df6c82 100644 --- a/src/shared/components/common/moment-time.tsx +++ b/src/shared/components/common/moment-time.tsx @@ -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 { diff --git a/src/shared/components/common/progress-bar.tsx b/src/shared/components/common/progress-bar.tsx index 88aee7e..f9cde3e 100644 --- a/src/shared/components/common/progress-bar.tsx +++ b/src/shared/components/common/progress-bar.tsx @@ -1,5 +1,5 @@ +import { ThemeColor } from "@utils/types"; import classNames from "classnames"; -import { ThemeColor } from "../../utils"; interface ProgressBarProps { className?: string; diff --git a/src/shared/components/common/registration-application.tsx b/src/shared/components/common/registration-application.tsx index d81af3b..6e6914b 100644 --- a/src/shared/components/common/registration-application.tsx +++ b/src/shared/components/common/registration-application.tsx @@ -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"; diff --git a/src/shared/components/common/searchable-select.tsx b/src/shared/components/common/searchable-select.tsx index 1d98de3..cf3a0f6 100644 --- a/src/shared/components/common/searchable-select.tsx +++ b/src/shared/components/common/searchable-select.tsx @@ -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 { diff --git a/src/shared/components/common/sort-select.tsx b/src/shared/components/common/sort-select.tsx index 7b27571..546b3ae 100644 --- a/src/shared/components/common/sort-select.tsx +++ b/src/shared/components/common/sort-select.tsx @@ -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 { diff --git a/src/shared/components/community/communities.tsx b/src/shared/components/community/communities.tsx index bf89782..9a4e836 100644 --- a/src/shared/components/community/communities.tsx +++ b/src/shared/components/community/communities.tsx @@ -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"; diff --git a/src/shared/components/community/community-form.tsx b/src/shared/components/community/community-form.tsx index 655a075..ab19da8 100644 --- a/src/shared/components/community/community-form.tsx +++ b/src/shared/components/community/community-form.tsx @@ -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"; diff --git a/src/shared/components/community/community-link.tsx b/src/shared/components/community/community-link.tsx index 4f45a2b..9583333 100644 --- a/src/shared/components/community/community-link.tsx +++ b/src/shared/components/community/community-link.tsx @@ -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 { diff --git a/src/shared/components/community/community.tsx b/src/shared/components/community/community.tsx index e03b699..195ff68 100644 --- a/src/shared/components/community/community.tsx +++ b/src/shared/components/community/community.tsx @@ -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"; diff --git a/src/shared/components/community/create-community.tsx b/src/shared/components/community/create-community.tsx index a061ff0..8a3b198 100644 --- a/src/shared/components/community/create-community.tsx +++ b/src/shared/components/community/create-community.tsx @@ -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"; diff --git a/src/shared/components/community/sidebar.tsx b/src/shared/components/community/sidebar.tsx index 915e789..8bc54c0 100644 --- a/src/shared/components/community/sidebar.tsx +++ b/src/shared/components/community/sidebar.tsx @@ -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"; diff --git a/src/shared/components/home/admin-settings.tsx b/src/shared/components/home/admin-settings.tsx index 23454ab..9b14310 100644 --- a/src/shared/components/home/admin-settings.tsx +++ b/src/shared/components/home/admin-settings.tsx @@ -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"; diff --git a/src/shared/components/home/emojis-form.tsx b/src/shared/components/home/emojis-form.tsx index ac07ba1..569abd0 100644 --- a/src/shared/components/home/emojis-form.tsx +++ b/src/shared/components/home/emojis-form.tsx @@ -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"; diff --git a/src/shared/components/home/home.tsx b/src/shared/components/home/home.tsx index 4270bd0..a844138 100644 --- a/src/shared/components/home/home.tsx +++ b/src/shared/components/home/home.tsx @@ -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"; diff --git a/src/shared/components/home/instances.tsx b/src/shared/components/home/instances.tsx index 2d8d8d5..aba7109 100644 --- a/src/shared/components/home/instances.tsx +++ b/src/shared/components/home/instances.tsx @@ -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"; diff --git a/src/shared/components/home/legal.tsx b/src/shared/components/home/legal.tsx index be11fd7..90c461a 100644 --- a/src/shared/components/home/legal.tsx +++ b/src/shared/components/home/legal.tsx @@ -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 { diff --git a/src/shared/components/home/login.tsx b/src/shared/components/home/login.tsx index 1601750..3d602f9 100644 --- a/src/shared/components/home/login.tsx +++ b/src/shared/components/home/login.tsx @@ -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"; diff --git a/src/shared/components/home/rate-limit-form.tsx b/src/shared/components/home/rate-limit-form.tsx index 11c1a8e..619e70d 100644 --- a/src/shared/components/home/rate-limit-form.tsx +++ b/src/shared/components/home/rate-limit-form.tsx @@ -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"; diff --git a/src/shared/components/home/setup.tsx b/src/shared/components/home/setup.tsx index 3b71047..b595e14 100644 --- a/src/shared/components/home/setup.tsx +++ b/src/shared/components/home/setup.tsx @@ -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"; diff --git a/src/shared/components/home/signup.tsx b/src/shared/components/home/signup.tsx index 7d50418..817dcf8 100644 --- a/src/shared/components/home/signup.tsx +++ b/src/shared/components/home/signup.tsx @@ -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"; diff --git a/src/shared/components/home/site-form.tsx b/src/shared/components/home/site-form.tsx index 6e24dd3..eb30cbe 100644 --- a/src/shared/components/home/site-form.tsx +++ b/src/shared/components/home/site-form.tsx @@ -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"; diff --git a/src/shared/components/home/site-sidebar.tsx b/src/shared/components/home/site-sidebar.tsx index 8f8b177..639e102 100644 --- a/src/shared/components/home/site-sidebar.tsx +++ b/src/shared/components/home/site-sidebar.tsx @@ -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"; diff --git a/src/shared/components/home/tagline-form.tsx b/src/shared/components/home/tagline-form.tsx index dfd1351..60986c5 100644 --- a/src/shared/components/home/tagline-form.tsx +++ b/src/shared/components/home/tagline-form.tsx @@ -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"; diff --git a/src/shared/components/modlog.tsx b/src/shared/components/modlog.tsx index 91da558..edced0f 100644 --- a/src/shared/components/modlog.tsx +++ b/src/shared/components/modlog.tsx @@ -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"; diff --git a/src/shared/components/person/inbox.tsx b/src/shared/components/person/inbox.tsx index 415c3e3..91bbee0 100644 --- a/src/shared/components/person/inbox.tsx +++ b/src/shared/components/person/inbox.tsx @@ -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"; diff --git a/src/shared/components/person/password-change.tsx b/src/shared/components/person/password-change.tsx index 3977feb..e20c313 100644 --- a/src/shared/components/person/password-change.tsx +++ b/src/shared/components/person/password-change.tsx @@ -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"; diff --git a/src/shared/components/person/person-details.tsx b/src/shared/components/person/person-details.tsx index 6efebaa..3771b84 100644 --- a/src/shared/components/person/person-details.tsx +++ b/src/shared/components/person/person-details.tsx @@ -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"; diff --git a/src/shared/components/person/person-listing.tsx b/src/shared/components/person/person-listing.tsx index cf3802c..6631a8e 100644 --- a/src/shared/components/person/person-listing.tsx +++ b/src/shared/components/person/person-listing.tsx @@ -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"; diff --git a/src/shared/components/person/profile.tsx b/src/shared/components/person/profile.tsx index 6f6ede3..763947e 100644 --- a/src/shared/components/person/profile.tsx +++ b/src/shared/components/person/profile.tsx @@ -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"; diff --git a/src/shared/components/person/registration-applications.tsx b/src/shared/components/person/registration-applications.tsx index 23b27b3..0e636fc 100644 --- a/src/shared/components/person/registration-applications.tsx +++ b/src/shared/components/person/registration-applications.tsx @@ -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"; diff --git a/src/shared/components/person/reports.tsx b/src/shared/components/person/reports.tsx index e3d100c..6fe59f1 100644 --- a/src/shared/components/person/reports.tsx +++ b/src/shared/components/person/reports.tsx @@ -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"; diff --git a/src/shared/components/person/settings.tsx b/src/shared/components/person/settings.tsx index 9acba57..5f149df 100644 --- a/src/shared/components/person/settings.tsx +++ b/src/shared/components/person/settings.tsx @@ -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"; diff --git a/src/shared/components/person/verify-email.tsx b/src/shared/components/person/verify-email.tsx index c4687c0..7ef5382 100644 --- a/src/shared/components/person/verify-email.tsx +++ b/src/shared/components/person/verify-email.tsx @@ -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"; diff --git a/src/shared/components/post/create-post.tsx b/src/shared/components/post/create-post.tsx index 5a9a167..aa69038 100644 --- a/src/shared/components/post/create-post.tsx +++ b/src/shared/components/post/create-post.tsx @@ -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"; diff --git a/src/shared/components/post/metadata-card.tsx b/src/shared/components/post/metadata-card.tsx index e6a864a..16415d2 100644 --- a/src/shared/components/post/metadata-card.tsx +++ b/src/shared/components/post/metadata-card.tsx @@ -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 { diff --git a/src/shared/components/post/post-form.tsx b/src/shared/components/post/post-form.tsx index 2475d49..9385179 100644 --- a/src/shared/components/post/post-form.tsx +++ b/src/shared/components/post/post-form.tsx @@ -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"; diff --git a/src/shared/components/post/post-listing.tsx b/src/shared/components/post/post-listing.tsx index 44dde4a..46a31ba 100644 --- a/src/shared/components/post/post-listing.tsx +++ b/src/shared/components/post/post-listing.tsx @@ -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"; diff --git a/src/shared/components/post/post-report.tsx b/src/shared/components/post/post-report.tsx index 6586f55..8af525d 100644 --- a/src/shared/components/post/post-report.tsx +++ b/src/shared/components/post/post-report.tsx @@ -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"; diff --git a/src/shared/components/post/post.tsx b/src/shared/components/post/post.tsx index b87cfc8..31fc2e7 100644 --- a/src/shared/components/post/post.tsx +++ b/src/shared/components/post/post.tsx @@ -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"; diff --git a/src/shared/components/private_message/create-private-message.tsx b/src/shared/components/private_message/create-private-message.tsx index ead2b6c..0bc704c 100644 --- a/src/shared/components/private_message/create-private-message.tsx +++ b/src/shared/components/private_message/create-private-message.tsx @@ -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"; diff --git a/src/shared/components/private_message/private-message-form.tsx b/src/shared/components/private_message/private-message-form.tsx index d7b27d7..1b9cb50 100644 --- a/src/shared/components/private_message/private-message-form.tsx +++ b/src/shared/components/private_message/private-message-form.tsx @@ -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"; diff --git a/src/shared/components/private_message/private-message-report.tsx b/src/shared/components/private_message/private-message-report.tsx index 7fa4ae0..38a20d8 100644 --- a/src/shared/components/private_message/private-message-report.tsx +++ b/src/shared/components/private_message/private-message-report.tsx @@ -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"; diff --git a/src/shared/components/private_message/private-message.tsx b/src/shared/components/private_message/private-message.tsx index b7426f3..db40604 100644 --- a/src/shared/components/private_message/private-message.tsx +++ b/src/shared/components/private_message/private-message.tsx @@ -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"; diff --git a/src/shared/components/search.tsx b/src/shared/components/search.tsx index f0931b1..72ea05a 100644 --- a/src/shared/components/search.tsx +++ b/src/shared/components/search.tsx @@ -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 index 0000000..384e86c --- /dev/null +++ b/src/shared/config.ts @@ -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"; diff --git a/src/shared/interfaces.ts b/src/shared/interfaces.ts index 6afebb6..6946fd3 100644 --- a/src/shared/interfaces.ts +++ b/src/shared/interfaces.ts @@ -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 index 0000000..d63dc8a --- /dev/null +++ b/src/shared/markdown.ts @@ -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 = 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 `
${md.utils.escapeHtml(m[1])} \n`; + } else { + // closing tag + return "
\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 `${alt_text}`; + }; + md.renderer.rules.table_open = function () { + return ''; + }; +} + +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: `${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; +} diff --git a/src/shared/services/HttpService.ts b/src/shared/services/HttpService.ts index cdcf11d..f6c3016 100644 --- a/src/shared/services/HttpService.ts +++ b/src/shared/services/HttpService.ts @@ -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"; diff --git a/src/shared/services/UserService.ts b/src/shared/services/UserService.ts index 346d833..61abc2e 100644 --- a/src/shared/services/UserService.ts +++ b/src/shared/services/UserService.ts @@ -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 index 0000000..6bb8760 --- /dev/null +++ b/src/shared/tippy.ts @@ -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 index 0000000..b8ab062 --- /dev/null +++ b/src/shared/toast.ts @@ -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 index 33658d1..0000000 --- a/src/shared/utils.ts +++ /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 = 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 `
${md.utils.escapeHtml(m[1])} \n`; - } else { - // closing tag - return "
\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 { - 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: `${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 `${alt_text}`; - }; - md.renderer.rules.table_open = function () { - return '
'; - }; -} - -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 { - 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 { - 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( - { 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(); - 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(context: any): IsoData { - // 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(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> = { - [K in keyof T]: RequestState; -}; diff --git a/src/shared/utils/app/build-comments-tree.ts b/src/shared/utils/app/build-comments-tree.ts new file mode 100644 index 0000000..8857fa4 --- /dev/null +++ b/src/shared/utils/app/build-comments-tree.ts @@ -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(); + 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 index 0000000..9194864 --- /dev/null +++ b/src/shared/utils/app/color-list.ts @@ -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 index 0000000..bc80015 --- /dev/null +++ b/src/shared/utils/app/comments-to-flat-nodes.ts @@ -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 index 0000000..2c930c3 --- /dev/null +++ b/src/shared/utils/app/community-rss-url.ts @@ -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 index 0000000..4661c30 --- /dev/null +++ b/src/shared/utils/app/community-search.ts @@ -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 { + 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 index 0000000..9404e87 --- /dev/null +++ b/src/shared/utils/app/community-select-name.ts @@ -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 index 0000000..6220ed6 --- /dev/null +++ b/src/shared/utils/app/community-to-choice.ts @@ -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 index 0000000..3a89a23 --- /dev/null +++ b/src/shared/utils/app/convert-comment-sort-type.ts @@ -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 index 0000000..fe1eb62 --- /dev/null +++ b/src/shared/utils/app/edit-comment-reply.ts @@ -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 index 0000000..c57b4d5 --- /dev/null +++ b/src/shared/utils/app/edit-comment-report.ts @@ -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 index 0000000..90c9c1b --- /dev/null +++ b/src/shared/utils/app/edit-comment.ts @@ -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 index 0000000..f902142 --- /dev/null +++ b/src/shared/utils/app/edit-community.ts @@ -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 index 0000000..ce372b8 --- /dev/null +++ b/src/shared/utils/app/edit-mention.ts @@ -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 index 0000000..721a141 --- /dev/null +++ b/src/shared/utils/app/edit-post-report.ts @@ -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 index 0000000..0c78fce --- /dev/null +++ b/src/shared/utils/app/edit-post.ts @@ -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 index 0000000..2fb001f --- /dev/null +++ b/src/shared/utils/app/edit-private-message-report.ts @@ -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 index 0000000..8bb8ed9 --- /dev/null +++ b/src/shared/utils/app/edit-private-message.ts @@ -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 index 0000000..9a100cb --- /dev/null +++ b/src/shared/utils/app/edit-registration-application.ts @@ -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 index 0000000..6aa09e3 --- /dev/null +++ b/src/shared/utils/app/edit-with.ts @@ -0,0 +1,14 @@ +import { WithComment } from "@utils/types"; + +export default function editWith( + { 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 index 0000000..c6ba459 --- /dev/null +++ b/src/shared/utils/app/enable-downvotes.ts @@ -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 index 0000000..352b40b --- /dev/null +++ b/src/shared/utils/app/enable-nsfw.ts @@ -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 index 0000000..3bf395f --- /dev/null +++ b/src/shared/utils/app/fetch-communities.ts @@ -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 index 0000000..5183546 --- /dev/null +++ b/src/shared/utils/app/fetch-search-results.ts @@ -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 index 0000000..d308ef9 --- /dev/null +++ b/src/shared/utils/app/fetch-theme-list.ts @@ -0,0 +1,3 @@ +export default async function fetchThemeList(): Promise { + 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 index 0000000..3cb8853 --- /dev/null +++ b/src/shared/utils/app/fetch-users.ts @@ -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 index 0000000..548cd29 --- /dev/null +++ b/src/shared/utils/app/get-comment-id-from-props.ts @@ -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 index 0000000..051446d --- /dev/null +++ b/src/shared/utils/app/get-comment-parent-id.ts @@ -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 index 0000000..b8d0f9e --- /dev/null +++ b/src/shared/utils/app/get-data-type-string.ts @@ -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 index 0000000..caf757b --- /dev/null +++ b/src/shared/utils/app/get-depth-from-comment.ts @@ -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 index 0000000..345a25e --- /dev/null +++ b/src/shared/utils/app/get-id-from-props.ts @@ -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 index 0000000..5dae458 --- /dev/null +++ b/src/shared/utils/app/get-recipient-id-from-props.ts @@ -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 index 0000000..47863be --- /dev/null +++ b/src/shared/utils/app/get-updated-search-id.ts @@ -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 index 0000000..cdae267 --- /dev/null +++ b/src/shared/utils/app/index.ts @@ -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 index 0000000..0f2a2df --- /dev/null +++ b/src/shared/utils/app/initialize-site.ts @@ -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 index 0000000..a74f727 --- /dev/null +++ b/src/shared/utils/app/insert-comment-into-tree.ts @@ -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 index 0000000..0ec963a --- /dev/null +++ b/src/shared/utils/app/is-auth-path.ts @@ -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 index 0000000..a0c6957 --- /dev/null +++ b/src/shared/utils/app/is-post-blocked.ts @@ -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 index 0000000..e82f21c --- /dev/null +++ b/src/shared/utils/app/my-auth-required.ts @@ -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 index 0000000..d536d8a --- /dev/null +++ b/src/shared/utils/app/my-auth.ts @@ -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 index 0000000..030fa79 --- /dev/null +++ b/src/shared/utils/app/new-vote.ts @@ -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 index 0000000..a710775 --- /dev/null +++ b/src/shared/utils/app/nsfw-check.ts @@ -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 index 0000000..2356466 --- /dev/null +++ b/src/shared/utils/app/person-search.ts @@ -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 { + 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 index 0000000..fb630b2 --- /dev/null +++ b/src/shared/utils/app/person-select-name.ts @@ -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 index 0000000..4c16129 --- /dev/null +++ b/src/shared/utils/app/person-to-choice.ts @@ -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 index 0000000..0219eb9 --- /dev/null +++ b/src/shared/utils/app/post-to-comment-sort-type.ts @@ -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 index 0000000..be1016c --- /dev/null +++ b/src/shared/utils/app/search-comment-tree.ts @@ -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 index 0000000..8079abd --- /dev/null +++ b/src/shared/utils/app/selectable-languages.ts @@ -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 index 0000000..1e149bb --- /dev/null +++ b/src/shared/utils/app/set-iso-data.ts @@ -0,0 +1,11 @@ +import { isBrowser } from "@utils/browser"; +import { IsoData, RouteData } from "../../interfaces"; + +export default function setIsoData( + context: any +): IsoData { + // 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 index 0000000..6d9d46c --- /dev/null +++ b/src/shared/utils/app/set-theme.ts @@ -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 index 0000000..34cf943 --- /dev/null +++ b/src/shared/utils/app/show-avatars.ts @@ -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 index 0000000..57da4d4 --- /dev/null +++ b/src/shared/utils/app/show-local.ts @@ -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 index 0000000..ea26634 --- /dev/null +++ b/src/shared/utils/app/show-scores.ts @@ -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 index 0000000..825f983 --- /dev/null +++ b/src/shared/utils/app/site-banner-css.ts @@ -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 index 0000000..7042527 --- /dev/null +++ b/src/shared/utils/app/update-community-block.ts @@ -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 index 0000000..3b5223b --- /dev/null +++ b/src/shared/utils/app/update-person-block.ts @@ -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}`); + } + } +} diff --git a/src/shared/utils/browser/index.ts b/src/shared/utils/browser/index.ts index a7a08a5..b12737c 100644 --- a/src/shared/utils/browser/index.ts +++ b/src/shared/utils/browser/index.ts @@ -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 index 0000000..4b4b86e --- /dev/null +++ b/src/shared/utils/browser/load-css.ts @@ -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 index 0000000..f153464 --- /dev/null +++ b/src/shared/utils/browser/restore-scroll-position.ts @@ -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 index 0000000..4835328 --- /dev/null +++ b/src/shared/utils/browser/save-scroll-position.ts @@ -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 index 0000000..17435b5 --- /dev/null +++ b/src/shared/utils/helpers/capitalize-first-letter.ts @@ -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 index 0000000..7ebce70 --- /dev/null +++ b/src/shared/utils/helpers/edit-list-immutable.ts @@ -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 index 0000000..e9d4371 --- /dev/null +++ b/src/shared/utils/helpers/future-days-to-unix-time.ts @@ -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 index 0000000..6eb7426 --- /dev/null +++ b/src/shared/utils/helpers/get-id-from-string.ts @@ -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 index 0000000..6d83cdc --- /dev/null +++ b/src/shared/utils/helpers/get-page-from-string.ts @@ -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 index 0000000..0b6ad32 --- /dev/null +++ b/src/shared/utils/helpers/get-random-char-from-alphabet.ts @@ -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 index 0000000..065eb7a --- /dev/null +++ b/src/shared/utils/helpers/get-random-from-list.ts @@ -0,0 +1,5 @@ +export default function getRandomFromList(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 index 0000000..ec9d019 --- /dev/null +++ b/src/shared/utils/helpers/get-unix-time.ts @@ -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 index 0000000..89d9aca --- /dev/null +++ b/src/shared/utils/helpers/hostname.ts @@ -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 index 0000000..78fc18d --- /dev/null +++ b/src/shared/utils/helpers/hsl.ts @@ -0,0 +1,3 @@ +export default function hsl(num: number) { + return `hsla(${num}, 35%, 50%, 0.5)`; +} diff --git a/src/shared/utils/helpers/index.ts b/src/shared/utils/helpers/index.ts index 663afbf..36ae83f 100644 --- a/src/shared/utils/helpers/index.ts +++ b/src/shared/utils/helpers/index.ts @@ -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 index 0000000..694be17 --- /dev/null +++ b/src/shared/utils/helpers/is-cake-day.ts @@ -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 index 0000000..4c5911f --- /dev/null +++ b/src/shared/utils/helpers/num-to-si.ts @@ -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 index 0000000..b4be718 --- /dev/null +++ b/src/shared/utils/helpers/random-str.ts @@ -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 index 0000000..187d800 --- /dev/null +++ b/src/shared/utils/helpers/valid-email.ts @@ -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 index 0000000..20c90a6 --- /dev/null +++ b/src/shared/utils/helpers/valid-instance-tld.ts @@ -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 index 0000000..8b146d3 --- /dev/null +++ b/src/shared/utils/helpers/valid-title.ts @@ -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 index 0000000..caedbc2 --- /dev/null +++ b/src/shared/utils/helpers/valid-url.ts @@ -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 index 0000000..bf831c1 --- /dev/null +++ b/src/shared/utils/media/index.ts @@ -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 index 0000000..c828f59 --- /dev/null +++ b/src/shared/utils/media/is-image.ts @@ -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 index 0000000..045b44e --- /dev/null +++ b/src/shared/utils/media/is-video.ts @@ -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 index 0000000..e86617e --- /dev/null +++ b/src/shared/utils/types/choice.ts @@ -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 index 0000000..6546fc1 --- /dev/null +++ b/src/shared/utils/types/community-tribute.ts @@ -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 index 0000000..95f10f0 --- /dev/null +++ b/src/shared/utils/types/error-page-data.ts @@ -0,0 +1,4 @@ +export default interface ErrorPageData { + error?: string; + adminMatrixIds?: string[]; +} diff --git a/src/shared/utils/types/index.ts b/src/shared/utils/types/index.ts index 9b4a1ce..2086b96 100644 --- a/src/shared/utils/types/index.ts +++ b/src/shared/utils/types/index.ts @@ -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 index 0000000..0b318ea --- /dev/null +++ b/src/shared/utils/types/person-tribute.ts @@ -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 index 0000000..a4f1722 --- /dev/null +++ b/src/shared/utils/types/route-data-response.ts @@ -0,0 +1,5 @@ +import { RequestState } from "../../services/HttpService"; + +export type RouteDataResponse> = { + [K in keyof T]: RequestState; +}; diff --git a/src/shared/utils/types/theme-color.ts b/src/shared/utils/types/theme-color.ts new file mode 100644 index 0000000..11346a4 --- /dev/null +++ b/src/shared/utils/types/theme-color.ts @@ -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 index 0000000..703f5e0 --- /dev/null +++ b/src/shared/utils/types/with-comment.ts @@ -0,0 +1,8 @@ +import { Comment, CommentAggregates } from "lemmy-js-client"; + +export default interface WithComment { + comment: Comment; + counts: CommentAggregates; + my_vote?: number; + saved: boolean; +}