From 9858514bb4605070096637e87a8109bceb0d6d85 Mon Sep 17 00:00:00 2001 From: David Palmer Date: Wed, 21 Jun 2023 16:15:02 +1200 Subject: [PATCH 01/10] wrap video embeds in the ratio container This should correctly size the embedded video iframe to the full available width, which looks better and is compatible with mobile devices. Also add the "allowfullscreen" modifier to the iframe so that the video can be expanded to the browser's fullscreen mode. Also add the post.embed_title as the iframe "title" attribute. --- src/shared/components/post/metadata-card.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/shared/components/post/metadata-card.tsx b/src/shared/components/post/metadata-card.tsx index e6a864af..3c6f6bbb 100644 --- a/src/shared/components/post/metadata-card.tsx +++ b/src/shared/components/post/metadata-card.tsx @@ -75,10 +75,14 @@ export class MetadataCard extends Component< )} {this.state.expanded && post.embed_video_url && ( - +
+ +
)} ); From c16c00db0dffbe9503c3c61cb8067614f8424549 Mon Sep 17 00:00:00 2001 From: David Palmer Date: Wed, 21 Jun 2023 16:28:27 +1200 Subject: [PATCH 02/10] add a prefix to hint to screenreaders what this iframe is --- src/shared/components/post/metadata-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/components/post/metadata-card.tsx b/src/shared/components/post/metadata-card.tsx index 3c6f6bbb..80ce173b 100644 --- a/src/shared/components/post/metadata-card.tsx +++ b/src/shared/components/post/metadata-card.tsx @@ -80,7 +80,7 @@ export class MetadataCard extends Component< allowFullScreen className="post-metadata-iframe" src={post.embed_video_url} - title={post.embed_title} + title={"Embedded Video: " + post.embed_title} > )} From 0c78cf22a7b73d9d66e8f77a7e94692edc600f14 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 21 Jun 2023 14:44:37 -0400 Subject: [PATCH 03/10] Centering emojimart in view. --- src/shared/components/common/emoji-picker.tsx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/shared/components/common/emoji-picker.tsx b/src/shared/components/common/emoji-picker.tsx index a7fc7d4f..6c603754 100644 --- a/src/shared/components/common/emoji-picker.tsx +++ b/src/shared/components/common/emoji-picker.tsx @@ -38,16 +38,18 @@ export class EmojiPicker extends Component { {this.state.showPicker && ( <> -
- +
+
+ +
+
-
)} From 9480e6337ba6ce1f5c8d10fafc62534a4412931f Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 21 Jun 2023 14:46:29 -0400 Subject: [PATCH 04/10] Commenting out markdown_it_emoji, because it breaks bolds. - Fixes #1439 --- src/shared/utils.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/shared/utils.ts b/src/shared/utils.ts index 33658d17..f2face76 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -31,7 +31,7 @@ import { } 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_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"; @@ -576,19 +576,19 @@ function setupMarkdown() { typographer: true, }; - const emojiDefs = Array.from(customEmojisLookup.entries()).reduce( - (main, [key, value]) => ({ ...main, [key]: value }), - {} - ); + // 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, - }); + .use(markdown_it_container, "spoiler", spoilerConfig); + // .use(markdown_it_emoji, { + // defs: emojiDefs, + // }); mdNoImages = new MarkdownIt(markdownItConfig) .use(markdown_it_sub) @@ -596,9 +596,9 @@ function setupMarkdown() { .use(markdown_it_footnote) .use(markdown_it_html5_embed, html5EmbedConfig) .use(markdown_it_container, "spoiler", spoilerConfig) - .use(markdown_it_emoji, { - defs: emojiDefs, - }) + // .use(markdown_it_emoji, { + // defs: emojiDefs, + // }) .disable("image"); const defaultRenderer = md.renderer.rules.image; md.renderer.rules.image = function ( From 3e6236ada3c52f65d238b2b25e9b43043c9ca49e Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 21 Jun 2023 15:02:23 -0400 Subject: [PATCH 05/10] Cleaning up new comment badge. Fixes #1443 --- src/shared/components/post/post-listing.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/shared/components/post/post-listing.tsx b/src/shared/components/post/post-listing.tsx index 5249e8fa..44dde4af 100644 --- a/src/shared/components/post/post-listing.tsx +++ b/src/shared/components/post/post-listing.tsx @@ -746,10 +746,12 @@ export class PostListing extends Component { to={`/post/${post_view.post.id}?scrollToComments=true`} data-tippy-content={title} > - - {post_view.counts.comments} + + + {post_view.counts.comments} + {this.unreadCount && ( - + ({this.unreadCount} {i18n.t("new")}) )} From 043b522ff1b0a395eda7d5e2b01fd035c5ec6b6e Mon Sep 17 00:00:00 2001 From: Alec Armbruster <35377827+alectrocute@users.noreply.github.com> Date: Wed, 21 Jun 2023 18:28:24 -0400 Subject: [PATCH 06/10] `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 --- src/client/index.tsx | 2 +- src/server/handlers/catch-all-handler.tsx | 3 +- src/server/utils/create-ssr-html.tsx | 2 +- src/server/utils/get-error-page-data.ts | 2 +- src/shared/components/app/app.tsx | 4 +- src/shared/components/app/error-page.tsx | 2 +- src/shared/components/app/footer.tsx | 2 +- src/shared/components/app/navbar.tsx | 13 +- .../components/comment/comment-form.tsx | 3 +- .../components/comment/comment-node.tsx | 26 +- .../components/comment/comment-nodes.tsx | 2 +- .../components/comment/comment-report.tsx | 2 +- src/shared/components/common/badges.tsx | 2 +- .../components/common/comment-sort-select.tsx | 3 +- src/shared/components/common/emoji-mart.tsx | 2 +- src/shared/components/common/error-guard.tsx | 2 +- src/shared/components/common/html-tags.tsx | 2 +- .../components/common/image-upload-form.tsx | 3 +- .../components/common/language-select.tsx | 3 +- .../components/common/listing-type-select.tsx | 2 +- .../components/common/markdown-textarea.tsx | 18 +- src/shared/components/common/moment-time.tsx | 2 +- src/shared/components/common/progress-bar.tsx | 2 +- .../common/registration-application.tsx | 3 +- .../components/common/searchable-select.tsx | 2 +- src/shared/components/common/sort-select.tsx | 3 +- .../components/community/communities.tsx | 25 +- .../components/community/community-form.tsx | 3 +- .../components/community/community-link.tsx | 4 +- src/shared/components/community/community.tsx | 53 +- .../components/community/create-community.tsx | 2 +- src/shared/components/community/sidebar.tsx | 4 +- src/shared/components/home/admin-settings.tsx | 21 +- src/shared/components/home/emojis-form.tsx | 10 +- src/shared/components/home/home.tsx | 54 +- src/shared/components/home/instances.tsx | 4 +- src/shared/components/home/legal.tsx | 3 +- src/shared/components/home/login.tsx | 4 +- .../components/home/rate-limit-form.tsx | 3 +- src/shared/components/home/setup.tsx | 2 +- src/shared/components/home/signup.tsx | 13 +- src/shared/components/home/site-form.tsx | 7 +- src/shared/components/home/site-sidebar.tsx | 2 +- src/shared/components/home/tagline-form.tsx | 3 +- src/shared/components/modlog.tsx | 29 +- src/shared/components/person/inbox.tsx | 33 +- .../components/person/password-change.tsx | 3 +- .../components/person/person-details.tsx | 3 +- .../components/person/person-listing.tsx | 4 +- src/shared/components/person/profile.tsx | 51 +- .../person/registration-applications.tsx | 16 +- src/shared/components/person/reports.tsx | 18 +- src/shared/components/person/settings.tsx | 41 +- src/shared/components/person/verify-email.tsx | 3 +- src/shared/components/post/create-post.tsx | 13 +- src/shared/components/post/metadata-card.tsx | 2 +- src/shared/components/post/post-form.tsx | 44 +- src/shared/components/post/post-listing.tsx | 21 +- src/shared/components/post/post-report.tsx | 2 +- src/shared/components/post/post.tsx | 52 +- .../create-private-message.tsx | 10 +- .../private_message/private-message-form.tsx | 10 +- .../private-message-report.tsx | 3 +- .../private_message/private-message.tsx | 3 +- src/shared/components/search.tsx | 48 +- src/shared/config.ts | 26 + src/shared/interfaces.ts | 2 +- src/shared/markdown.ts | 307 ++++ src/shared/services/HttpService.ts | 2 +- src/shared/services/UserService.ts | 3 +- src/shared/tippy.ts | 19 + src/shared/toast.ts | 54 + src/shared/utils.ts | 1281 ----------------- src/shared/utils/app/build-comments-tree.ts | 54 + src/shared/utils/app/color-list.ts | 11 + .../utils/app/comments-to-flat-nodes.ts | 12 + src/shared/utils/app/community-rss-url.ts | 4 + src/shared/utils/app/community-search.ts | 14 + src/shared/utils/app/community-select-name.ts | 8 + src/shared/utils/app/community-to-choice.ts | 10 + .../utils/app/convert-comment-sort-type.ts | 25 + src/shared/utils/app/edit-comment-reply.ts | 9 + src/shared/utils/app/edit-comment-report.ts | 9 + src/shared/utils/app/edit-comment.ts | 9 + src/shared/utils/app/edit-community.ts | 9 + src/shared/utils/app/edit-mention.ts | 9 + src/shared/utils/app/edit-post-report.ts | 9 + src/shared/utils/app/edit-post.ts | 9 + .../utils/app/edit-private-message-report.ts | 9 + src/shared/utils/app/edit-private-message.ts | 9 + .../app/edit-registration-application.ts | 9 + src/shared/utils/app/edit-with.ts | 14 + src/shared/utils/app/enable-downvotes.ts | 5 + src/shared/utils/app/enable-nsfw.ts | 5 + src/shared/utils/app/fetch-communities.ts | 7 + src/shared/utils/app/fetch-search-results.ts | 18 + src/shared/utils/app/fetch-theme-list.ts | 3 + src/shared/utils/app/fetch-users.ts | 7 + .../utils/app/get-comment-id-from-props.ts | 4 + src/shared/utils/app/get-comment-parent-id.ts | 13 + src/shared/utils/app/get-data-type-string.ts | 5 + .../utils/app/get-depth-from-comment.ts | 8 + src/shared/utils/app/get-id-from-props.ts | 4 + .../utils/app/get-recipient-id-from-props.ts | 5 + src/shared/utils/app/get-updated-search-id.ts | 8 + src/shared/utils/app/index.ts | 111 ++ src/shared/utils/app/initialize-site.ts | 13 + .../utils/app/insert-comment-into-tree.ts | 27 + src/shared/utils/app/is-auth-path.ts | 5 + src/shared/utils/app/is-post-blocked.ts | 17 + src/shared/utils/app/my-auth-required.ts | 5 + src/shared/utils/app/my-auth.ts | 5 + src/shared/utils/app/new-vote.ts | 9 + src/shared/utils/app/nsfw-check.ts | 11 + src/shared/utils/app/person-search.ts | 14 + src/shared/utils/app/person-select-name.ts | 9 + src/shared/utils/app/person-to-choice.ts | 10 + .../utils/app/post-to-comment-sort-type.ts | 16 + src/shared/utils/app/search-comment-tree.ts | 21 + src/shared/utils/app/selectable-languages.ts | 34 + src/shared/utils/app/set-iso-data.ts | 11 + src/shared/utils/app/set-theme.ts | 36 + src/shared/utils/app/show-avatars.ts | 7 + src/shared/utils/app/show-local.ts | 5 + src/shared/utils/app/show-scores.ts | 7 + src/shared/utils/app/site-banner-css.ts | 12 + .../utils/app/update-community-block.ts | 24 + src/shared/utils/app/update-person-block.ts | 24 + src/shared/utils/browser/index.ts | 12 +- src/shared/utils/browser/load-css.ts | 12 + .../utils/browser/restore-scroll-position.ts | 5 + .../utils/browser/save-scroll-position.ts | 5 + .../utils/helpers/capitalize-first-letter.ts | 3 + .../utils/helpers/edit-list-immutable.ts | 20 + .../utils/helpers/future-days-to-unix-time.ts | 9 + .../utils/helpers/get-id-from-string.ts | 3 + .../utils/helpers/get-page-from-string.ts | 3 + .../helpers/get-random-char-from-alphabet.ts | 3 + .../utils/helpers/get-random-from-list.ts | 5 + src/shared/utils/helpers/get-unix-time.ts | 3 + src/shared/utils/helpers/hostname.ts | 4 + src/shared/utils/helpers/hsl.ts | 3 + src/shared/utils/helpers/index.ts | 43 +- src/shared/utils/helpers/is-cake-day.ts | 33 + src/shared/utils/helpers/num-to-si.ts | 10 + src/shared/utils/helpers/random-str.ts | 19 + src/shared/utils/helpers/valid-email.ts | 5 + .../utils/helpers/valid-instance-tld.ts | 5 + src/shared/utils/helpers/valid-title.ts | 8 + src/shared/utils/helpers/valid-url.ts | 8 + src/shared/utils/media/index.ts | 4 + src/shared/utils/media/is-image.ts | 5 + src/shared/utils/media/is-video.ts | 5 + src/shared/utils/types/choice.ts | 5 + src/shared/utils/types/community-tribute.ts | 6 + src/shared/utils/types/error-page-data.ts | 4 + src/shared/utils/types/index.ts | 18 +- src/shared/utils/types/person-tribute.ts | 6 + src/shared/utils/types/route-data-response.ts | 5 + src/shared/utils/types/theme-color.ts | 22 + src/shared/utils/types/with-comment.ts | 8 + 161 files changed, 1849 insertions(+), 1659 deletions(-) create mode 100644 src/shared/config.ts create mode 100644 src/shared/markdown.ts create mode 100644 src/shared/tippy.ts create mode 100644 src/shared/toast.ts delete mode 100644 src/shared/utils.ts create mode 100644 src/shared/utils/app/build-comments-tree.ts create mode 100644 src/shared/utils/app/color-list.ts create mode 100644 src/shared/utils/app/comments-to-flat-nodes.ts create mode 100644 src/shared/utils/app/community-rss-url.ts create mode 100644 src/shared/utils/app/community-search.ts create mode 100644 src/shared/utils/app/community-select-name.ts create mode 100644 src/shared/utils/app/community-to-choice.ts create mode 100644 src/shared/utils/app/convert-comment-sort-type.ts create mode 100644 src/shared/utils/app/edit-comment-reply.ts create mode 100644 src/shared/utils/app/edit-comment-report.ts create mode 100644 src/shared/utils/app/edit-comment.ts create mode 100644 src/shared/utils/app/edit-community.ts create mode 100644 src/shared/utils/app/edit-mention.ts create mode 100644 src/shared/utils/app/edit-post-report.ts create mode 100644 src/shared/utils/app/edit-post.ts create mode 100644 src/shared/utils/app/edit-private-message-report.ts create mode 100644 src/shared/utils/app/edit-private-message.ts create mode 100644 src/shared/utils/app/edit-registration-application.ts create mode 100644 src/shared/utils/app/edit-with.ts create mode 100644 src/shared/utils/app/enable-downvotes.ts create mode 100644 src/shared/utils/app/enable-nsfw.ts create mode 100644 src/shared/utils/app/fetch-communities.ts create mode 100644 src/shared/utils/app/fetch-search-results.ts create mode 100644 src/shared/utils/app/fetch-theme-list.ts create mode 100644 src/shared/utils/app/fetch-users.ts create mode 100644 src/shared/utils/app/get-comment-id-from-props.ts create mode 100644 src/shared/utils/app/get-comment-parent-id.ts create mode 100644 src/shared/utils/app/get-data-type-string.ts create mode 100644 src/shared/utils/app/get-depth-from-comment.ts create mode 100644 src/shared/utils/app/get-id-from-props.ts create mode 100644 src/shared/utils/app/get-recipient-id-from-props.ts create mode 100644 src/shared/utils/app/get-updated-search-id.ts create mode 100644 src/shared/utils/app/index.ts create mode 100644 src/shared/utils/app/initialize-site.ts create mode 100644 src/shared/utils/app/insert-comment-into-tree.ts create mode 100644 src/shared/utils/app/is-auth-path.ts create mode 100644 src/shared/utils/app/is-post-blocked.ts create mode 100644 src/shared/utils/app/my-auth-required.ts create mode 100644 src/shared/utils/app/my-auth.ts create mode 100644 src/shared/utils/app/new-vote.ts create mode 100644 src/shared/utils/app/nsfw-check.ts create mode 100644 src/shared/utils/app/person-search.ts create mode 100644 src/shared/utils/app/person-select-name.ts create mode 100644 src/shared/utils/app/person-to-choice.ts create mode 100644 src/shared/utils/app/post-to-comment-sort-type.ts create mode 100644 src/shared/utils/app/search-comment-tree.ts create mode 100644 src/shared/utils/app/selectable-languages.ts create mode 100644 src/shared/utils/app/set-iso-data.ts create mode 100644 src/shared/utils/app/set-theme.ts create mode 100644 src/shared/utils/app/show-avatars.ts create mode 100644 src/shared/utils/app/show-local.ts create mode 100644 src/shared/utils/app/show-scores.ts create mode 100644 src/shared/utils/app/site-banner-css.ts create mode 100644 src/shared/utils/app/update-community-block.ts create mode 100644 src/shared/utils/app/update-person-block.ts create mode 100644 src/shared/utils/browser/load-css.ts create mode 100644 src/shared/utils/browser/restore-scroll-position.ts create mode 100644 src/shared/utils/browser/save-scroll-position.ts create mode 100644 src/shared/utils/helpers/capitalize-first-letter.ts create mode 100644 src/shared/utils/helpers/edit-list-immutable.ts create mode 100644 src/shared/utils/helpers/future-days-to-unix-time.ts create mode 100644 src/shared/utils/helpers/get-id-from-string.ts create mode 100644 src/shared/utils/helpers/get-page-from-string.ts create mode 100644 src/shared/utils/helpers/get-random-char-from-alphabet.ts create mode 100644 src/shared/utils/helpers/get-random-from-list.ts create mode 100644 src/shared/utils/helpers/get-unix-time.ts create mode 100644 src/shared/utils/helpers/hostname.ts create mode 100644 src/shared/utils/helpers/hsl.ts create mode 100644 src/shared/utils/helpers/is-cake-day.ts create mode 100644 src/shared/utils/helpers/num-to-si.ts create mode 100644 src/shared/utils/helpers/random-str.ts create mode 100644 src/shared/utils/helpers/valid-email.ts create mode 100644 src/shared/utils/helpers/valid-instance-tld.ts create mode 100644 src/shared/utils/helpers/valid-title.ts create mode 100644 src/shared/utils/helpers/valid-url.ts create mode 100644 src/shared/utils/media/index.ts create mode 100644 src/shared/utils/media/is-image.ts create mode 100644 src/shared/utils/media/is-video.ts create mode 100644 src/shared/utils/types/choice.ts create mode 100644 src/shared/utils/types/community-tribute.ts create mode 100644 src/shared/utils/types/error-page-data.ts create mode 100644 src/shared/utils/types/person-tribute.ts create mode 100644 src/shared/utils/types/route-data-response.ts create mode 100644 src/shared/utils/types/theme-color.ts create mode 100644 src/shared/utils/types/with-comment.ts diff --git a/src/client/index.tsx b/src/client/index.tsx index 860c0756..4ff794ec 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 025aaa68..b9ff13bf 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 2c35aa29..569d83ac 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 3c82372f..fc37ccac 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 79aa77eb..e615bb3a 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 191c322b..7d4e2970 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 8ea647c6..601045a4 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 5fa7580c..12ca05db 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 2638a40f..c399fb06 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 15c68f75..f6cb4b22 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 8c0a236e..02e621b7 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 2f765e03..b3630096 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 17ae53fb..ed9aecf8 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 e9885afa..18eaed2a 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 dff8c3ac..6ee3aa83 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 30121541..6f0302a0 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 f32b0fc0..63eb461f 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 98b51c7a..44fa5a9e 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 02deb434..625379e2 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 4885b6c6..d6ed378f 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 519a9fda..bdd42f38 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 511c4b46..4df6c82e 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 88aee7ed..f9cde3ea 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 d81af3b6..6e6914b3 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 1d98de3d..cf3a0f62 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 7b275718..546b3aec 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 bf897823..9a4e836a 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 655a0752..ab19da82 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 4f45a2b5..95833333 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 e03b6990..195ff687 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 a061ff0d..8a3b1985 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 915e789a..8bc54c02 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 23454ab9..9b14310c 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 ac07ba11..569abd04 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 4270bd0b..a8441380 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 2d8d8d5d..aba71099 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 be11fd75..90c461a8 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 1601750e..3d602f91 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 11c1a8e8..619e70d8 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 3b71047d..b595e14d 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 7d504184..817dcf8c 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 6e24dd3e..eb30cbe1 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 8f8b177e..639e1022 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 dfd13514..60986c55 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 91da558f..edced0f4 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 415c3e3f..91bbee03 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 3977feb4..e20c3138 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 6efebaa1..3771b844 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 cf3802c4..6631a8ea 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 6f6ede3e..763947e8 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 23b27b37..0e636fc7 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 e3d100c7..6fe59f1c 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 9acba57a..5f149dfe 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 c4687c00..7ef53823 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 5a9a1673..aa690381 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 e6a864af..16415d2d 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 2475d49d..93851798 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 { + archiveTodayUrl, + ghostArchiveUrl, + relTags, + trendingFetchLimit, + webArchiveUrl, +} from "../../config"; 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"; +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 44dde4af..46a31ba6 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 6586f550..8af525d0 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 b87cfc8b..31fc2e70 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 ead2b6c7..0bc704cf 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 d7b27d74..1b9cb50c 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 7fa4ae0a..38a20d85 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 b7426f30..db40604c 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 f0931b12..72ea05a0 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 00000000..384e86c3 --- /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 6afebb62..6946fd32 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 00000000..d63dc8a6 --- /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 cdcf11d7..f6c30167 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 346d833a..61abc2eb 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 00000000..6bb8760d --- /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 00000000..b8ab0623 --- /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 33658d17..00000000 --- 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 00000000..8857fa42 --- /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 00000000..91948642 --- /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 00000000..bc80015b --- /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 00000000..2c930c3a --- /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 00000000..4661c30c --- /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 00000000..9404e87b --- /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 00000000..6220ed6d --- /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 00000000..3a89a23c --- /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 00000000..fe1eb62a --- /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 00000000..c57b4d54 --- /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 00000000..90c9c1bf --- /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 00000000..f9021428 --- /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 00000000..ce372b84 --- /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 00000000..721a1413 --- /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 00000000..0c78fce8 --- /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 00000000..2fb001f8 --- /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 00000000..8bb8ed95 --- /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 00000000..9a100cb3 --- /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 00000000..6aa09e37 --- /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 00000000..c6ba4599 --- /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 00000000..352b40b5 --- /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 00000000..3bf395f5 --- /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 00000000..51835466 --- /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 00000000..d308ef96 --- /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 00000000..3cb8853d --- /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 00000000..548cd294 --- /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 00000000..051446d9 --- /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 00000000..b8d0f9e5 --- /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 00000000..caf757bb --- /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 00000000..345a25e6 --- /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 00000000..5dae458c --- /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 00000000..47863be1 --- /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 00000000..cdae2677 --- /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 00000000..0f2a2dfe --- /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 00000000..a74f7275 --- /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 00000000..0ec963a2 --- /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 00000000..a0c6957a --- /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 00000000..e82f21cd --- /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 00000000..d536d8a2 --- /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 00000000..030fa79f --- /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 00000000..a710775d --- /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 00000000..2356466e --- /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 00000000..fb630b29 --- /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 00000000..4c161294 --- /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 00000000..0219eb98 --- /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 00000000..be1016ca --- /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 00000000..8079abdc --- /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 00000000..1e149bb2 --- /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 00000000..6d9d46c0 --- /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 00000000..34cf9434 --- /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 00000000..57da4d4c --- /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 00000000..ea26634e --- /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 00000000..825f9833 --- /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 00000000..70425272 --- /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 00000000..3b5223bc --- /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 a7a08a50..b12737ce 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 00000000..4b4b86e3 --- /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 00000000..f1534644 --- /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 00000000..48353287 --- /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 00000000..17435b55 --- /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 00000000..7ebce703 --- /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 00000000..e9d43713 --- /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 00000000..6eb74264 --- /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 00000000..6d83cdc9 --- /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 00000000..0b6ad32d --- /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 00000000..065eb7a9 --- /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 00000000..ec9d0191 --- /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 00000000..89d9acaf --- /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 00000000..78fc18dd --- /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 663afbf9..36ae83fa 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 00000000..694be170 --- /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 00000000..4c5911f8 --- /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 00000000..b4be7188 --- /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 00000000..187d8006 --- /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 00000000..20c90a60 --- /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 00000000..8b146d33 --- /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 00000000..caedbc2b --- /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 00000000..bf831c1e --- /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 00000000..c828f59a --- /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 00000000..045b44e5 --- /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 00000000..e86617e6 --- /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 00000000..6546fc15 --- /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 00000000..95f10f0f --- /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 9b4a1cec..2086b966 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 00000000..0b318eab --- /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 00000000..a4f1722e --- /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 00000000..11346a4f --- /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 00000000..703f5e0c --- /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; +} From 3f4f3191601e1f09b0d2d7b978cc307014e27db4 Mon Sep 17 00:00:00 2001 From: Jay Sitter Date: Wed, 21 Jun 2023 19:24:01 -0400 Subject: [PATCH 07/10] fix: Fix i18n UserService import issue --- src/shared/i18next.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/i18next.ts b/src/shared/i18next.ts index ff5f77f1..aab43014 100644 --- a/src/shared/i18next.ts +++ b/src/shared/i18next.ts @@ -1,6 +1,6 @@ import { isBrowser } from "@utils/browser"; import i18next, { i18nTyped, Resource } from "i18next"; -import { UserService } from "./services"; +import { UserService } from "./services/UserService"; import { ar } from "./translations/ar"; import { bg } from "./translations/bg"; import { ca } from "./translations/ca"; From f7df8314990b3f0152993eac42d125a65b54552b Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 21 Jun 2023 19:48:43 -0400 Subject: [PATCH 08/10] v0.18.0-rc.5 --- package.json | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9a285463..1055d853 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lemmy-ui", - "version": "0.18.0-rc.4", + "version": "0.18.0-rc.5", "description": "An isomorphic UI for lemmy", "repository": "https://github.com/LemmyNet/lemmy-ui", "license": "AGPL-3.0", @@ -22,9 +22,16 @@ "translations:update": "git submodule update --remote --recursive" }, "lint-staged": { - "*.{ts,tsx,js}": ["prettier --write", "eslint --fix"], - "*.{css, scss}": ["prettier --write"], - "package.json": ["sortpack"] + "*.{ts,tsx,js}": [ + "prettier --write", + "eslint --fix" + ], + "*.{css, scss}": [ + "prettier --write" + ], + "package.json": [ + "sortpack" + ] }, "dependencies": { "@babel/plugin-proposal-decorators": "^7.21.0", From 924e6706993c5d291aa9f269ad933d83da4a4147 Mon Sep 17 00:00:00 2001 From: David Palmer Date: Thu, 22 Jun 2023 12:23:36 +1200 Subject: [PATCH 09/10] remove embed prefix until a translation can be added --- src/shared/components/post/metadata-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/components/post/metadata-card.tsx b/src/shared/components/post/metadata-card.tsx index 0b28b28f..bc6576a4 100644 --- a/src/shared/components/post/metadata-card.tsx +++ b/src/shared/components/post/metadata-card.tsx @@ -80,7 +80,7 @@ export class MetadataCard extends Component< allowFullScreen className="post-metadata-iframe" src={post.embed_video_url} - title={"Embedded Video: " + post.embed_title} + title={post.embed_title} > )} From 3664b0600b3a196209248ff7c5898a78ee4c0795 Mon Sep 17 00:00:00 2001 From: SleeplessOne1917 Date: Thu, 22 Jun 2023 01:04:28 +0000 Subject: [PATCH 10/10] (Hopefully) fix webmanifest different origin issue (#1457) Co-authored-by: Dessalines --- src/server/index.tsx | 2 +- src/server/utils/create-ssr-html.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/index.tsx b/src/server/index.tsx index 144b596e..b65506b0 100644 --- a/src/server/index.tsx +++ b/src/server/index.tsx @@ -25,7 +25,7 @@ if (!process.env["LEMMY_UI_DISABLE_CSP"] && !process.env["LEMMY_UI_DEBUG"]) { server.get("/robots.txt", RobotsHandler); server.get("/service-worker.js", ServiceWorkerHandler); -server.get("/manifest", ManifestHandler); +server.get("/manifest.webmanifest", ManifestHandler); server.get("/css/themes/:name", ThemeHandler); server.get("/css/themelist", ThemesListHandler); server.get("/*", CatchAllHandler); diff --git a/src/server/utils/create-ssr-html.tsx b/src/server/utils/create-ssr-html.tsx index 569d83ac..ae766b3a 100644 --- a/src/server/utils/create-ssr-html.tsx +++ b/src/server/utils/create-ssr-html.tsx @@ -77,7 +77,7 @@ export async function createSsrHtml( /> - +