]> Untitled Git - lemmy-ui.git/blobdiff - src/shared/components/common/markdown-textarea.tsx
Merge branch 'main' into breakout-role-utils
[lemmy-ui.git] / src / shared / components / common / markdown-textarea.tsx
index 38a2c2ed5ae7c30c5ac1332598430880d0e39a1b..4e1bca11f82c9f0b434705350f34665eb83ca541 100644 (file)
@@ -1,14 +1,12 @@
 import autosize from "autosize";
 import { NoOptionI18nKeys } from "i18next";
 import { Component, linkEvent } from "inferno";
-import { Prompt } from "inferno-router";
 import { Language } from "lemmy-js-client";
 import { i18n } from "../../i18next";
-import { UserService } from "../../services";
+import { HttpService, UserService } from "../../services";
 import {
   concurrentImageUpload,
   customEmojisLookup,
-  isBrowser,
   markdownFieldCharacterLimit,
   markdownHelpUrl,
   maxUploadImages,
@@ -20,13 +18,13 @@ import {
   setupTippy,
   setupTribute,
   toast,
-  uploadImage,
 } from "../../utils";
+import { isBrowser } from "../../utils/browser/is-browser";
 import { EmojiPicker } from "./emoji-picker";
 import { Icon, Spinner } from "./icon";
 import { LanguageSelect } from "./language-select";
+import NavigationPrompt from "./navigation-prompt";
 import ProgressBar from "./progress-bar";
-
 interface MarkdownTextAreaProps {
   initialContent?: string;
   initialLanguageId?: number;
@@ -39,9 +37,9 @@ interface MarkdownTextAreaProps {
   finished?: boolean;
   showLanguage?: boolean;
   hideNavigationWarnings?: boolean;
-  onContentChange?(val: string): any;
-  onReplyCancel?(): any;
-  onSubmit?(msg: { val?: string; formId: string; languageId?: number }): any;
+  onContentChange?(val: string): void;
+  onReplyCancel?(): void;
+  onSubmit?(content: string, formId: string, languageId?: number): void;
   allLanguages: Language[]; // TODO should probably be nullable
   siteLanguages: number[]; // TODO same
 }
@@ -55,8 +53,9 @@ interface MarkdownTextAreaState {
   content?: string;
   languageId?: number;
   previewMode: boolean;
-  loading: boolean;
   imageUploadStatus?: ImageUploadStatus;
+  loading: boolean;
+  submitted: boolean;
 }
 
 export class MarkdownTextArea extends Component<
@@ -72,6 +71,7 @@ export class MarkdownTextArea extends Component<
     languageId: this.props.initialLanguageId,
     previewMode: false,
     loading: false,
+    submitted: false,
   };
 
   constructor(props: any, context: any) {
@@ -105,17 +105,14 @@ export class MarkdownTextArea extends Component<
     }
   }
 
-  componentDidUpdate() {
-    if (!this.props.hideNavigationWarnings && this.state.content) {
-      window.onbeforeunload = () => true;
-    } else {
-      window.onbeforeunload = null;
-    }
-  }
-
   componentWillReceiveProps(nextProps: MarkdownTextAreaProps) {
     if (nextProps.finished) {
-      this.setState({ previewMode: false, loading: false, content: undefined });
+      this.setState({
+        previewMode: false,
+        imageUploadStatus: undefined,
+        loading: false,
+        content: undefined,
+      });
       if (this.props.replyType) {
         this.props.onReplyCancel?.();
       }
@@ -127,18 +124,22 @@ export class MarkdownTextArea extends Component<
     }
   }
 
-  componentWillUnmount() {
-    window.onbeforeunload = null;
-  }
-
   render() {
     const languageId = this.state.languageId;
 
+    // TODO add these prompts back in at some point
+    // <Prompt
+    //   when={!this.props.hideNavigationWarnings && this.state.content}
+    //   message={i18n.t("block_leaving")}
+    // />
     return (
       <form id={this.formId} onSubmit={linkEvent(this, this.handleSubmit)}>
-        <Prompt
-          when={!this.props.hideNavigationWarnings && this.state.content}
-          message={i18n.t("block_leaving")}
+        <NavigationPrompt
+          when={
+            !this.props.hideNavigationWarnings &&
+            !!this.state.content &&
+            !this.state.submitted
+          }
         />
         <div className="form-group row">
           <div className={`col-sm-12`}>
@@ -182,53 +183,6 @@ export class MarkdownTextArea extends Component<
         </div>
         <div className="row">
           <div className="col-sm-12 d-flex flex-wrap">
-            {this.props.buttonTitle && (
-              <button
-                type="submit"
-                className="btn btn-sm btn-secondary mr-2"
-                disabled={this.isDisabled}
-              >
-                {this.state.loading ? (
-                  <Spinner />
-                ) : (
-                  <span>{this.props.buttonTitle}</span>
-                )}
-              </button>
-            )}
-            {this.props.replyType && (
-              <button
-                type="button"
-                className="btn btn-sm btn-secondary mr-2"
-                onClick={linkEvent(this, this.handleReplyCancel)}
-              >
-                {i18n.t("cancel")}
-              </button>
-            )}
-            {this.state.content && (
-              <button
-                className={`btn btn-sm btn-secondary mr-2 ${
-                  this.state.previewMode && "active"
-                }`}
-                onClick={linkEvent(this, this.handlePreviewToggle)}
-              >
-                {this.state.previewMode ? i18n.t("edit") : i18n.t("preview")}
-              </button>
-            )}
-            {/* A flex expander */}
-            <div className="flex-grow-1"></div>
-
-            {this.props.showLanguage && (
-              <LanguageSelect
-                iconVersion
-                allLanguages={this.props.allLanguages}
-                selectedLanguageIds={
-                  languageId ? Array.of(languageId) : undefined
-                }
-                siteLanguages={this.props.siteLanguages}
-                onChange={this.handleLanguageChange}
-                disabled={this.isDisabled}
-              />
-            )}
             {this.getFormatButton("bold", this.handleInsertBold)}
             {this.getFormatButton("italic", this.handleInsertItalic)}
             {this.getFormatButton("link", this.handleInsertLink)}
@@ -281,6 +235,57 @@ export class MarkdownTextArea extends Component<
               <Icon icon="help-circle" classes="icon-inline" />
             </a>
           </div>
+
+          <div className="col-sm-12 d-flex align-items-center flex-wrap">
+            {this.props.showLanguage && (
+              <LanguageSelect
+                iconVersion
+                allLanguages={this.props.allLanguages}
+                selectedLanguageIds={
+                  languageId ? Array.of(languageId) : undefined
+                }
+                siteLanguages={this.props.siteLanguages}
+                onChange={this.handleLanguageChange}
+                disabled={this.isDisabled}
+              />
+            )}
+
+            {/* A flex expander */}
+            <div className="flex-grow-1"></div>
+
+            {this.props.buttonTitle && (
+              <button
+                type="submit"
+                className="btn btn-sm btn-secondary mr-2"
+                disabled={this.isDisabled}
+              >
+                {this.state.loading ? (
+                  <Spinner />
+                ) : (
+                  <span>{this.props.buttonTitle}</span>
+                )}
+              </button>
+            )}
+            {this.props.replyType && (
+              <button
+                type="button"
+                className="btn btn-sm btn-secondary mr-2"
+                onClick={linkEvent(this, this.handleReplyCancel)}
+              >
+                {i18n.t("cancel")}
+              </button>
+            )}
+            {this.state.content && (
+              <button
+                className={`btn btn-sm btn-secondary mr-2 ${
+                  this.state.previewMode && "active"
+                }`}
+                onClick={linkEvent(this, this.handlePreviewToggle)}
+              >
+                {this.state.previewMode ? i18n.t("edit") : i18n.t("preview")}
+              </button>
+            )}
+          </div>
         </div>
       </form>
     );
@@ -393,29 +398,29 @@ export class MarkdownTextArea extends Component<
     }
   }
 
-  async uploadSingleImage(i: MarkdownTextArea, file: File) {
-    try {
-      const res = await uploadImage(file);
-      console.log("pictrs upload:");
-      console.log(res);
-      if (res.msg === "ok") {
-        const imageMarkdown = `![](${res.url})`;
+  async uploadSingleImage(i: MarkdownTextArea, image: File) {
+    const res = await HttpService.client.uploadImage({ image });
+    console.log("pictrs upload:");
+    console.log(res);
+    if (res.state === "success") {
+      if (res.data.msg === "ok") {
+        const imageMarkdown = `![](${res.data.url})`;
         i.setState(({ content }) => ({
           content: content ? `${content}\n${imageMarkdown}` : imageMarkdown,
         }));
         i.contentChange();
         const textarea: any = document.getElementById(i.id);
         autosize.update(textarea);
-        pictrsDeleteToast(file.name, res.delete_url as string);
+        pictrsDeleteToast(image.name, res.data.delete_url as string);
       } else {
-        throw JSON.stringify(res);
+        throw JSON.stringify(res.data);
       }
-    } catch (error) {
+    } else if (res.state === "failed") {
       i.setState({ imageUploadStatus: undefined });
-      console.error(error);
-      toast(error, "danger");
+      console.error(res.msg);
+      toast(res.msg, "danger");
 
-      throw error;
+      throw res.msg;
     }
   }
 
@@ -489,13 +494,10 @@ export class MarkdownTextArea extends Component<
 
   handleSubmit(i: MarkdownTextArea, event: any) {
     event.preventDefault();
-    i.setState({ loading: true });
-    const msg = {
-      val: i.state.content,
-      formId: i.formId,
-      languageId: i.state.languageId,
-    };
-    i.props.onSubmit?.(msg);
+    if (i.state.content) {
+      i.setState({ loading: true, submitted: true });
+      i.props.onSubmit?.(i.state.content, i.formId, i.state.languageId);
+    }
   }
 
   handleReplyCancel(i: MarkdownTextArea) {