]> Untitled Git - lemmy-ui.git/blobdiff - src/shared/utils.ts
Merge branch 'main' into main
[lemmy-ui.git] / src / shared / utils.ts
index 54b5a3f4ac37aa6f448fbf677125f34db142356f..d42243d6ff82ca569ffd50597b9428a181d70b52 100644 (file)
@@ -1,3 +1,5 @@
+import { isBrowser } from "@utils/browser";
+import { debounce, groupBy } from "@utils/helpers";
 import { Picker } from "emoji-mart";
 import emojiShortName from "emoji-short-name";
 import {
@@ -40,11 +42,16 @@ import moment from "moment";
 import tippy from "tippy.js";
 import Toastify from "toastify-js";
 import { getHttpBase } from "./env";
-import { i18n, languages } from "./i18next";
-import { CommentNodeI, DataType, IsoData, VoteType } from "./interfaces";
+import { i18n } from "./i18next";
+import {
+  CommentNodeI,
+  DataType,
+  IsoData,
+  RouteData,
+  VoteType,
+} from "./interfaces";
 import { HttpService, UserService } from "./services";
-import { isBrowser } from "./utils/browser/is-browser";
-import { groupBy } from "./utils/helpers/group-by";
+import { RequestState } from "./services/HttpService";
 
 let Tribute: any;
 if (isBrowser()) {
@@ -58,10 +65,10 @@ 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 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";
@@ -230,6 +237,7 @@ export function futureDaysToUnixTime(days?: number): number | 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);
@@ -240,7 +248,16 @@ export function isVideo(url: string) {
 }
 
 export function validURL(str: string) {
-  return !!new URL(str);
+  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 {
@@ -268,76 +285,6 @@ export function getDataTypeString(dt: DataType) {
   return dt === DataType.Post ? "Post" : "Comment";
 }
 
-export function debounce<T extends any[], R>(
-  func: (...e: T) => R,
-  wait = 1000,
-  immediate = false
-) {
-  // 'private' variable for instance
-  // The returned function will be able to reference this due to closure.
-  // Each call to the returned function will share this common timer.
-  let timeout: NodeJS.Timeout | null;
-
-  // Calling debounce returns a new anonymous function
-  return function () {
-    // reference the context and args for the setTimeout function
-    const args = arguments;
-
-    // Should the function be called now? If immediate is true
-    //   and not already in a timeout then the answer is: Yes
-    const callNow = immediate && !timeout;
-
-    // This is the basic debounce behavior where you can call this
-    //   function several times, but it will only execute once
-    //   [before or after imposing a delay].
-    //   Each time the returned function is called, the timer starts over.
-    clearTimeout(timeout ?? undefined);
-
-    // Set the new timeout
-    timeout = setTimeout(function () {
-      // Inside the timeout function, clear the timeout variable
-      // which will let the next execution run when in 'immediate' mode
-      timeout = null;
-
-      // Check if the function already ran with the immediate flag
-      if (!immediate) {
-        // Call the original function with apply
-        // apply lets you define the 'this' object as well as the arguments
-        //    (both captured before setTimeout)
-        func.apply(this, args);
-      }
-    }, wait);
-
-    // Immediate mode and no wait timer? Execute the function..
-    if (callNow) func.apply(this, args);
-  } as (...e: T) => R;
-}
-
-export function getLanguages(
-  override?: string,
-  myUserInfo = UserService.Instance.myUserInfo
-): string[] {
-  const myLang = myUserInfo?.local_user_view.local_user.interface_language;
-  const lang = override || myLang || "browser";
-
-  if (lang == "browser" && isBrowser()) {
-    return getBrowserLanguages();
-  } else {
-    return [lang];
-  }
-}
-
-function getBrowserLanguages(): string[] {
-  // Intersect lemmy's langs, with the browser langs
-  const langs = languages ? languages.map(l => l.code) : ["en"];
-
-  // NOTE, mobile browsers seem to be missing this list, so append en
-  const allowedLangs = navigator.languages
-    .concat("en")
-    .filter(v => langs.includes(v));
-  return allowedLangs;
-}
-
 export async function fetchThemeList(): Promise<string[]> {
   return fetch("/css/themelist").then(res => res.json());
 }
@@ -424,7 +371,7 @@ export function isCakeDay(published: string): boolean {
 
 export function toast(text: string, background: ThemeColor = "success") {
   if (isBrowser()) {
-    const backgroundColor = `var(--${background})`;
+    const backgroundColor = `var(--bs-${background})`;
     Toastify({
       text: text,
       backgroundColor: backgroundColor,
@@ -445,7 +392,7 @@ export function pictrsDeleteToast(filename: string, deleteUrl: string) {
       filename,
     });
 
-    const backgroundColor = `var(--light)`;
+    const backgroundColor = `var(--bs-light)`;
 
     const toast = Toastify({
       text: clickToDeleteText,
@@ -653,7 +600,7 @@ function setupMarkdown() {
       defs: emojiDefs,
     })
     .disable("image");
-  var defaultRenderer = md.renderer.rules.image;
+  const defaultRenderer = md.renderer.rules.image;
   md.renderer.rules.image = function (
     tokens: Token[],
     idx: number,
@@ -672,6 +619,9 @@ function setupMarkdown() {
     const alt_text = item.content;
     return `<img class="icon icon-emoji" src="${src}" title="${title}" alt="${alt_text}"/>`;
   };
+  md.renderer.rules.table_open = function () {
+    return '<table class="table">';
+  };
 }
 
 export function getEmojiMart(
@@ -903,21 +853,28 @@ export function commentsToFlatNodes(comments: CommentView[]): CommentNodeI[] {
 }
 
 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";
-  }
+    switch(sort) {
+        case "TopAll":
+        case "TopHour":
+        case "TopSixHour":
+        case "TopTwelveHour":
+        case "TopDay":
+        case "TopWeek":
+        case "TopMonth":
+        case "TopYear": {
+            return "Top";
+        }
+        case "New": {
+            return "New";
+        }
+        case "Hot":
+        case "Active": {
+            return "Hot";
+        }
+        default: {
+            return "Hot";
+        }
+    }
 }
 
 export function buildCommentsTree(
@@ -1072,7 +1029,7 @@ export function siteBannerCss(banner: string): string {
     `;
 }
 
-export function setIsoData(context: any): IsoData {
+export function setIsoData<T extends RouteData>(context: any): IsoData<T> {
   // If its the browser, you need to deserialize the data from the window
   if (isBrowser()) {
     return window.isoData;
@@ -1183,7 +1140,7 @@ export function personSelectName({
 
 export function initializeSite(site?: GetSiteResponse) {
   UserService.Instance.myUserInfo = site?.my_user;
-  i18n.changeLanguage(getLanguages()[0]);
+  i18n.changeLanguage();
   if (site) {
     setupEmojiDataModel(site.custom_emojis ?? []);
   }
@@ -1325,3 +1282,7 @@ export function newVote(voteType: VoteType, myVote?: number): number {
     return myVote == -1 ? 0 : -1;
   }
 }
+
+export type RouteDataResponse<T extends Record<string, any>> = {
+  [K in keyof T]: RequestState<T[K]>;
+};