]> Untitled Git - lemmy-ui.git/blobdiff - src/shared/utils.ts
Merge branch 'LemmyNet:main' into multiple-images-upload
[lemmy-ui.git] / src / shared / utils.ts
index a91162177ef8d147fbdc27e397651cbeac6b783c..d0b9b4bf6de888d1802a9271aef856c0d6f870ad 100644 (file)
@@ -1,10 +1,13 @@
-import { None, Option, Result, Some } from "@sniptt/monads";
+import { Err, None, Ok, Option, Result, Some } from "@sniptt/monads";
 import { ClassConstructor, deserialize, serialize } from "class-transformer";
 import emojiShortName from "emoji-short-name";
 import {
   BlockCommunityResponse,
   BlockPersonResponse,
+  Comment as CommentI,
+  CommentNode as CommentNodeI,
   CommentReportView,
+  CommentSortType,
   CommentView,
   CommunityBlockView,
   CommunityModeratorView,
@@ -20,6 +23,7 @@ import {
   PersonViewSafe,
   PostReportView,
   PostView,
+  PrivateMessageReportView,
   PrivateMessageView,
   RegistrationApplicationView,
   Search,
@@ -39,12 +43,7 @@ import tippy from "tippy.js";
 import Toastify from "toastify-js";
 import { httpBase } from "./env";
 import { i18n, languages } from "./i18next";
-import {
-  CommentNode as CommentNodeI,
-  CommentSortType,
-  DataType,
-  IsoData,
-} from "./interfaces";
+import { DataType, IsoData } from "./interfaces";
 import { UserService, WebSocketService } from "./services";
 
 var Tribute: any;
@@ -71,9 +70,10 @@ export const webArchiveUrl = "https://web.archive.org";
 export const elementUrl = "https://element.io";
 
 export const postRefetchSeconds: number = 60 * 1000;
-export const fetchLimit = 20;
+export const fetchLimit = 40;
 export const trendingFetchLimit = 6;
 export const mentionDropdownFetchLimit = 10;
+export const commentTreeMaxDepth = 8;
 
 export const relTags = "noopener nofollow";
 
@@ -211,9 +211,10 @@ export function canMod(
 export function canAdmin(
   admins: Option<PersonViewSafe[]>,
   creator_id: number,
-  myUserInfo = UserService.Instance.myUserInfo
+  myUserInfo = UserService.Instance.myUserInfo,
+  onSelf = false
 ): boolean {
-  return canMod(None, admins, creator_id, myUserInfo);
+  return canMod(None, admins, creator_id, myUserInfo, onSelf);
 }
 
 export function isMod(
@@ -246,14 +247,10 @@ export function isAdmin(
   });
 }
 
-export function amAdmin(
-  admins: Option<PersonViewSafe[]>,
-  myUserInfo = UserService.Instance.myUserInfo
-): boolean {
-  return myUserInfo.match({
-    some: mui => isAdmin(admins, mui.local_user_view.person.id),
-    none: false,
-  });
+export function amAdmin(myUserInfo = UserService.Instance.myUserInfo): boolean {
+  return myUserInfo
+    .map(mui => mui.local_user_view.person.admin)
+    .unwrapOr(false);
 }
 
 export function amCommunityCreator(
@@ -418,7 +415,7 @@ export function getLanguages(
   myUserInfo = UserService.Instance.myUserInfo
 ): string[] {
   let myLang = myUserInfo
-    .map(m => m.local_user_view.local_user.lang)
+    .map(m => m.local_user_view.local_user.interface_language)
     .unwrapOr("browser");
   let lang = override || myLang;
 
@@ -543,6 +540,7 @@ export function toast(text: string, background = "success") {
 export function pictrsDeleteToast(
   clickToDeleteText: string,
   deletePictureText: string,
+  failedDeletePictureText: string,
   deleteUrl: string
 ) {
   if (isBrowser()) {
@@ -555,9 +553,16 @@ export function pictrsDeleteToast(
       duration: 10000,
       onClick: () => {
         if (toast) {
-          window.location.replace(deleteUrl);
-          alert(deletePictureText);
-          toast.hideToast();
+          fetch(deleteUrl, {})
+          .then( res => {
+            console.log(res)
+            toast.hideToast();
+            if (res.ok === true){
+              alert(deletePictureText);
+            } else{
+              alert(failedDeletePictureText);
+            }
+          })
         }
       },
       close: true,
@@ -611,7 +616,7 @@ export function notifyComment(comment_view: CommentView, router: any) {
   let info: NotifyInfo = {
     name: comment_view.creator.name,
     icon: comment_view.creator.avatar,
-    link: `/post/${comment_view.post.id}/comment/${comment_view.comment.id}`,
+    link: `/comment/${comment_view.comment.id}`,
     body: comment_view.comment.content,
   };
   notify(info, router);
@@ -630,21 +635,18 @@ export function notifyPrivateMessage(pmv: PrivateMessageView, router: any) {
 function notify(info: NotifyInfo, router: any) {
   messageToastify(info, router);
 
-  // TODO absolute nightmare bug, but notifs are currently broken.
-  // Notification.new will try to do a browser fetch ???
-
-  // if (Notification.permission !== "granted") Notification.requestPermission();
-  // else {
-  //   var notification = new Notification(info.name, {
-  //     icon: info.icon,
-  //     body: info.body,
-  //   });
+  if (Notification.permission !== "granted") Notification.requestPermission();
+  else {
+    var notification = new Notification(info.name, {
+      ...{ body: info.body },
+      ...(info.icon.isSome() && { icon: info.icon.unwrap() }),
+    });
 
-  //   notification.onclick = (ev: Event): any => {
-  //     ev.preventDefault();
-  //     router.history.push(info.link);
-  //   };
-  // }
+    notification.onclick = (ev: Event): any => {
+      ev.preventDefault();
+      router.history.push(info.link);
+    };
+  }
 }
 
 export function setupTribute() {
@@ -813,12 +815,14 @@ export function getRecipientIdFromProps(props: any): number {
     : 1;
 }
 
-export function getIdFromProps(props: any): number {
-  return Number(props.match.params.id);
+export function getIdFromProps(props: any): Option<number> {
+  let id: string = props.match.params.post_id;
+  return id ? Some(Number(id)) : None;
 }
 
-export function getCommentIdFromProps(props: any): number {
-  return Number(props.match.params.comment_id);
+export function getCommentIdFromProps(props: any): Option<number> {
+  let id: string = props.match.params.comment_id;
+  return id ? Some(Number(id)) : None;
 }
 
 export function getUsernameFromProps(props: any): string {
@@ -829,6 +833,7 @@ export function editCommentRes(data: CommentView, comments: CommentView[]) {
   let found = comments.find(c => c.comment.id == data.comment.id);
   if (found) {
     found.comment.content = data.comment.content;
+    found.comment.distinguished = data.comment.distinguished;
     found.comment.updated = data.comment.updated;
     found.comment.removed = data.comment.removed;
     found.comment.deleted = data.comment.deleted;
@@ -948,6 +953,7 @@ export function editPostRes(data: PostView, post: PostView) {
   }
 }
 
+// TODO possible to make these generic?
 export function updatePostReportRes(
   data: PostReportView,
   reports: PostReportView[]
@@ -968,6 +974,18 @@ export function updateCommentReportRes(
   }
 }
 
+export function updatePrivateMessageReportRes(
+  data: PrivateMessageReportView,
+  reports: PrivateMessageReportView[]
+) {
+  let found = reports.find(
+    c => c.private_message_report.id == data.private_message_report.id
+  );
+  if (found) {
+    found.private_message_report = data.private_message_report;
+  }
+}
+
 export function updateRegistrationApplicationRes(
   data: RegistrationApplicationView,
   applications: RegistrationApplicationView[]
@@ -985,61 +1003,12 @@ export function updateRegistrationApplicationRes(
 export function commentsToFlatNodes(comments: CommentView[]): CommentNodeI[] {
   let nodes: CommentNodeI[] = [];
   for (let comment of comments) {
-    nodes.push({ comment_view: comment });
+    nodes.push({ comment_view: comment, children: [], depth: 0 });
   }
   return nodes;
 }
 
-function commentSort(tree: CommentNodeI[], sort: CommentSortType) {
-  // First, put removed and deleted comments at the bottom, then do your other sorts
-  if (sort == CommentSortType.Top) {
-    tree.sort(
-      (a, b) =>
-        +a.comment_view.comment.removed - +b.comment_view.comment.removed ||
-        +a.comment_view.comment.deleted - +b.comment_view.comment.deleted ||
-        b.comment_view.counts.score - a.comment_view.counts.score
-    );
-  } else if (sort == CommentSortType.New) {
-    tree.sort(
-      (a, b) =>
-        +a.comment_view.comment.removed - +b.comment_view.comment.removed ||
-        +a.comment_view.comment.deleted - +b.comment_view.comment.deleted ||
-        b.comment_view.comment.published.localeCompare(
-          a.comment_view.comment.published
-        )
-    );
-  } else if (sort == CommentSortType.Old) {
-    tree.sort(
-      (a, b) =>
-        +a.comment_view.comment.removed - +b.comment_view.comment.removed ||
-        +a.comment_view.comment.deleted - +b.comment_view.comment.deleted ||
-        a.comment_view.comment.published.localeCompare(
-          b.comment_view.comment.published
-        )
-    );
-  } else if (sort == CommentSortType.Hot) {
-    tree.sort(
-      (a, b) =>
-        +a.comment_view.comment.removed - +b.comment_view.comment.removed ||
-        +a.comment_view.comment.deleted - +b.comment_view.comment.deleted ||
-        hotRankComment(b.comment_view as CommentView) -
-          hotRankComment(a.comment_view as CommentView)
-    );
-  }
-
-  // Go through the children recursively
-  for (let node of tree) {
-    if (node.children) {
-      commentSort(node.children, sort);
-    }
-  }
-}
-
-export function commentSortSortType(tree: CommentNodeI[], sort: SortType) {
-  commentSort(tree, convertCommentSortType(sort));
-}
-
-function convertCommentSortType(sort: SortType): CommentSortType {
+export function convertCommentSortType(sort: SortType): CommentSortType {
   if (
     sort == SortType.TopAll ||
     sort == SortType.TopDay ||
@@ -1059,21 +1028,32 @@ function convertCommentSortType(sort: SortType): CommentSortType {
 
 export function buildCommentsTree(
   comments: CommentView[],
-  commentSortType: CommentSortType
+  parentComment: boolean
 ): CommentNodeI[] {
   let map = new Map<number, CommentNodeI>();
+  let depthOffset = !parentComment
+    ? 0
+    : getDepthFromComment(comments[0].comment);
+
   for (let comment_view of comments) {
     let node: CommentNodeI = {
       comment_view: comment_view,
       children: [],
-      depth: 0,
+      depth: getDepthFromComment(comment_view.comment) - depthOffset,
     };
     map.set(comment_view.comment.id, { ...node });
   }
+
   let tree: CommentNodeI[] = [];
+
+  // if its a parent comment fetch, then push the first comment to the top node.
+  if (parentComment) {
+    tree.push(map.get(comments[0].comment.id));
+  }
+
   for (let comment_view of comments) {
     let child = map.get(comment_view.comment.id);
-    let parent_id = comment_view.comment.parent_id;
+    let parent_id = getCommentParentId(comment_view.comment);
     parent_id.match({
       some: parentId => {
         let parent = map.get(parentId);
@@ -1083,26 +1063,37 @@ export function buildCommentsTree(
         }
       },
       none: () => {
-        tree.push(child);
+        if (!parentComment) {
+          tree.push(child);
+        }
       },
     });
-
-    setDepth(child);
   }
 
-  commentSort(tree, commentSortType);
-
   return tree;
 }
 
-function setDepth(node: CommentNodeI, i = 0) {
-  for (let child of node.children) {
-    child.depth = i;
-    setDepth(child, i + 1);
+export function getCommentParentId(comment: CommentI): Option<number> {
+  let split = comment.path.split(".");
+  // remove the 0
+  split.shift();
+
+  if (split.length > 1) {
+    return Some(Number(split[split.length - 2]));
+  } else {
+    return None;
   }
 }
 
-export function insertCommentIntoTree(tree: CommentNodeI[], cv: CommentView) {
+export function getDepthFromComment(comment: CommentI): number {
+  return comment.path.split(".").length - 2;
+}
+
+export function insertCommentIntoTree(
+  tree: CommentNodeI[],
+  cv: CommentView,
+  parentComment: boolean
+) {
   // Building a fake node to be used for later
   let node: CommentNodeI = {
     comment_view: cv,
@@ -1110,7 +1101,7 @@ export function insertCommentIntoTree(tree: CommentNodeI[], cv: CommentView) {
     depth: 0,
   };
 
-  cv.comment.parent_id.match({
+  getCommentParentId(cv.comment).match({
     some: parentId => {
       let parentComment = searchCommentTree(tree, parentId);
       parentComment.match({
@@ -1122,7 +1113,9 @@ export function insertCommentIntoTree(tree: CommentNodeI[], cv: CommentView) {
       });
     },
     none: () => {
-      tree.unshift(node);
+      if (!parentComment) {
+        tree.unshift(node);
+      }
     },
   });
 }
@@ -1149,6 +1142,7 @@ export function searchCommentTree(
 
 export const colorList: string[] = [
   hsl(0),
+  hsl(50),
   hsl(100),
   hsl(150),
   hsl(200),
@@ -1292,7 +1286,7 @@ export function showLocal(isoData: IsoData): boolean {
     .unwrapOr(false);
 }
 
-interface ChoicesValue {
+export interface ChoicesValue {
   value: string;
   label: string;
 }
@@ -1351,13 +1345,14 @@ export const choicesConfig = {
   shouldSort: false,
   searchResultLimit: fetchLimit,
   classNames: {
-    containerOuter: "choices",
-    containerInner: "choices__inner bg-secondary border-0",
+    containerOuter: "choices custom-select px-0",
+    containerInner:
+      "choices__inner bg-secondary border-0 py-0 modlog-choices-font-size",
     input: "form-control",
     inputCloned: "choices__input--cloned",
     list: "choices__list",
     listItems: "choices__list--multiple",
-    listSingle: "choices__list--single",
+    listSingle: "choices__list--single py-0",
     listDropdown: "choices__list--dropdown",
     item: "choices__item bg-secondary",
     itemSelectable: "choices__item--selectable",
@@ -1439,3 +1434,74 @@ export function enableDownvotes(siteRes: GetSiteResponse): boolean {
 export function enableNsfw(siteRes: GetSiteResponse): boolean {
   return siteRes.site_view.map(s => s.site.enable_nsfw).unwrapOr(false);
 }
+
+export function postToCommentSortType(sort: SortType): CommentSortType {
+  if ([SortType.Active, SortType.Hot].includes(sort)) {
+    return CommentSortType.Hot;
+  } else if ([SortType.New, SortType.NewComments].includes(sort)) {
+    return CommentSortType.New;
+  } else if (sort == SortType.Old) {
+    return CommentSortType.Old;
+  } else {
+    return CommentSortType.Top;
+  }
+}
+
+export function arrayGet<T>(arr: Array<T>, index: number): Result<T, string> {
+  let out = arr.at(index);
+  if (out == undefined) {
+    return Err("Index undefined");
+  } else {
+    return Ok(out);
+  }
+}
+
+export function myFirstDiscussionLanguageId(
+  myUserInfo = UserService.Instance.myUserInfo
+): Option<number> {
+  return myUserInfo.andThen(mui =>
+    arrayGet(mui.discussion_languages, 0)
+      .ok()
+      .map(i => i.id)
+  );
+}
+
+export function canCreateCommunity(
+  siteRes: GetSiteResponse,
+  myUserInfo = UserService.Instance.myUserInfo
+): boolean {
+  let adminOnly = siteRes.site_view
+    .map(s => s.site.community_creation_admin_only)
+    .unwrapOr(false);
+  return !adminOnly || amAdmin(myUserInfo);
+}
+
+export function isPostBlocked(
+  pv: PostView,
+  myUserInfo = UserService.Instance.myUserInfo
+): boolean {
+  return myUserInfo
+    .map(
+      mui =>
+        mui.community_blocks
+          .map(c => c.community.id)
+          .includes(pv.community.id) ||
+        mui.person_blocks.map(p => p.target.id).includes(pv.creator.id)
+    )
+    .unwrapOr(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 {
+  let nsfw = pv.post.nsfw || pv.community.nsfw;
+  return (
+    !nsfw ||
+    (nsfw &&
+      myUserInfo
+        .map(m => m.local_user_view.local_user.show_nsfw)
+        .unwrapOr(false))
+  );
+}