]> Untitled Git - lemmy-ui.git/commitdiff
Hide create community (#787)
authorDessalines <dessalines@users.noreply.github.com>
Thu, 22 Sep 2022 15:14:58 +0000 (11:14 -0400)
committerGitHub <noreply@github.com>
Thu, 22 Sep 2022 15:14:58 +0000 (11:14 -0400)
* Adding post and comment language tagging. Fixes #771

* Hiding create community button. Fixes #754

30 files changed:
lemmy-translations
package.json
src/shared/components/app/navbar.tsx
src/shared/components/comment/comment-form.tsx
src/shared/components/comment/comment-node.tsx
src/shared/components/comment/comment-nodes.tsx
src/shared/components/comment/comment-report.tsx
src/shared/components/common/language-select.tsx [new file with mode: 0644]
src/shared/components/common/markdown-textarea.tsx
src/shared/components/common/registration-application.tsx
src/shared/components/community/community-form.tsx
src/shared/components/community/community.tsx
src/shared/components/home/home.tsx
src/shared/components/home/signup.tsx
src/shared/components/home/site-form.tsx
src/shared/components/person/inbox.tsx
src/shared/components/person/person-details.tsx
src/shared/components/person/profile.tsx
src/shared/components/person/settings.tsx
src/shared/components/post/create-post.tsx
src/shared/components/post/metadata-card.tsx
src/shared/components/post/post-form.tsx
src/shared/components/post/post-listing.tsx
src/shared/components/post/post-listings.tsx
src/shared/components/post/post-report.tsx
src/shared/components/post/post.tsx
src/shared/components/private_message/private-message-form.tsx
src/shared/components/search.tsx
src/shared/utils.ts
yarn.lock

index 7ac48ae98271b3b573e28c90b87f9704492e0b62..05fb028e8b85de9e7e9d516abb1ebb8a01aad060 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 7ac48ae98271b3b573e28c90b87f9704492e0b62
+Subproject commit 05fb028e8b85de9e7e9d516abb1ebb8a01aad060
index 903974e96ed5470b9defdf7a9f93f55715ad6000..f7cd0480d999a60581c337f09e4c564d7031b763 100644 (file)
@@ -78,7 +78,7 @@
     "eslint-plugin-prettier": "^4.2.1",
     "husky": "^8.0.1",
     "import-sort-style-module": "^6.0.0",
-    "lemmy-js-client": "0.17.0-rc.43",
+    "lemmy-js-client": "0.17.0-rc.44",
     "lint-staged": "^13.0.3",
     "mini-css-extract-plugin": "^2.6.1",
     "node-fetch": "^2.6.1",
index 345003533acae9cba7b2f9c88f38a39fade3a5a3..dbf978e14535a48baf0c0049f59645041297103e 100644 (file)
@@ -21,6 +21,7 @@ import { UserService, WebSocketService } from "../../services";
 import {
   amAdmin,
   auth,
+  canCreateCommunity,
   donateLemmyUrl,
   isBrowser,
   notifyComment,
@@ -274,7 +275,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
                   {i18n.t("create_post")}
                 </NavLink>
               </li>
-              {this.canCreateCommunity && (
+              {canCreateCommunity(this.props.siteRes) && (
                 <li className="nav-item">
                   <NavLink
                     to="/create_community"
@@ -528,13 +529,6 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
     return amAdmin(Some(this.props.siteRes.admins));
   }
 
-  get canCreateCommunity(): boolean {
-    let adminOnly = this.props.siteRes.site_view
-      .map(s => s.site.community_creation_admin_only)
-      .unwrapOr(false);
-    return !adminOnly || this.amAdmin;
-  }
-
   handleToggleExpandNavbar(i: Navbar) {
     i.setState({ expanded: !i.state.expanded });
   }
index d91e86c17c69b771bf492f4e412aae24ee88ef7a..d9630fda0759ebd88eee6111cd36104d760dca63 100644 (file)
@@ -7,6 +7,7 @@ import {
   CommentResponse,
   CreateComment,
   EditComment,
+  Language,
   UserOperation,
   wsJsonToRes,
   wsUserOp,
@@ -17,6 +18,7 @@ import { UserService, WebSocketService } from "../../services";
 import {
   auth,
   capitalizeFirstLetter,
+  myFirstDiscussionLanguageId,
   wsClient,
   wsSubscribe,
 } from "../../utils";
@@ -32,6 +34,7 @@ interface CommentFormProps {
   disabled?: boolean;
   focus?: boolean;
   onReplyCancel?(): any;
+  allLanguages: Language[];
 }
 
 interface CommentFormState {
@@ -74,11 +77,19 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
         this.props.edit ? Some(node.comment_view.comment.content) : None,
       right: () => None,
     });
+
+    let selectedLang = this.props.node
+      .left()
+      .map(n => n.comment_view.comment.language_id)
+      .or(myFirstDiscussionLanguageId(UserService.Instance.myUserInfo));
+
     return (
       <div className="mb-3">
         {UserService.Instance.myUserInfo.isSome() ? (
           <MarkdownTextArea
             initialContent={initialContent}
+            initialLanguageId={selectedLang}
+            showLanguage
             buttonTitle={Some(this.state.buttonTitle)}
             maxLength={None}
             finished={this.state.finished}
@@ -88,6 +99,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
             onSubmit={this.handleCommentSubmit}
             onReplyCancel={this.handleReplyCancel}
             placeholder={Some(i18n.t("comment_here"))}
+            allLanguages={this.props.allLanguages}
           />
         ) : (
           <div className="alert alert-warning" role="alert">
@@ -104,27 +116,34 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
     );
   }
 
-  handleCommentSubmit(msg: { val: string; formId: string }) {
+  handleCommentSubmit(msg: {
+    val: Option<string>;
+    formId: string;
+    languageId: Option<number>;
+  }) {
     let content = msg.val;
+    let language_id = msg.languageId;
     this.setState({ formId: Some(msg.formId) });
 
     this.props.node.match({
       left: node => {
         if (this.props.edit) {
           let form = new EditComment({
-            content: Some(content),
+            content,
             distinguished: None,
             form_id: this.state.formId,
             comment_id: node.comment_view.comment.id,
+            language_id,
             auth: auth().unwrap(),
           });
           WebSocketService.Instance.send(wsClient.editComment(form));
         } else {
           let form = new CreateComment({
-            content,
+            content: content.unwrap(),
             form_id: this.state.formId,
             post_id: node.comment_view.post.id,
             parent_id: Some(node.comment_view.comment.id),
+            language_id,
             auth: auth().unwrap(),
           });
           WebSocketService.Instance.send(wsClient.createComment(form));
@@ -132,10 +151,11 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
       },
       right: postId => {
         let form = new CreateComment({
-          content,
+          content: content.unwrap(),
           form_id: this.state.formId,
           post_id: postId,
           parent_id: None,
+          language_id,
           auth: auth().unwrap(),
         });
         WebSocketService.Instance.send(wsClient.createComment(form));
index f5fc69813d0c6b31d5c280fc16be4b339b39029d..6d1621c18ed9de241efd2468706614b0c02813c6 100644 (file)
@@ -17,6 +17,7 @@ import {
   DeleteComment,
   EditComment,
   GetComments,
+  Language,
   ListingType,
   MarkCommentReplyAsRead,
   MarkPersonMentionAsRead,
@@ -101,6 +102,7 @@ interface CommentNodeProps {
   showCommunity?: boolean;
   enableDownvotes: boolean;
   viewType: CommentViewType;
+  allLanguages: Language[];
 }
 
 export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
@@ -324,6 +326,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                 onReplyCancel={this.handleReplyCancel}
                 disabled={this.props.locked}
                 focus
+                allLanguages={this.props.allLanguages}
               />
             )}
             {!this.state.showEdit && !this.state.collapsed && (
@@ -1007,6 +1010,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
             onReplyCancel={this.handleReplyCancel}
             disabled={this.props.locked}
             focus
+            allLanguages={this.props.allLanguages}
           />
         )}
         {!this.state.collapsed && node.children.length > 0 && (
@@ -1018,6 +1022,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
             maxCommentsShown={None}
             enableDownvotes={this.props.enableDownvotes}
             viewType={this.props.viewType}
+            allLanguages={this.props.allLanguages}
           />
         )}
         {/* A collapsed clearfix */}
@@ -1264,6 +1269,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
       form_id: None, // TODO not sure about this
       content: None,
       distinguished: Some(!comment.distinguished),
+      language_id: Some(comment.language_id),
       auth: auth().unwrap(),
     });
     WebSocketService.Instance.send(wsClient.editComment(form));
index f9484c2d4497e15a1d8d18a9beaf8ee1435702de..708105895a76f7e7add80835b35bac7cfa7a07f9 100644 (file)
@@ -3,6 +3,7 @@ import { Component } from "inferno";
 import {
   CommentNode as CommentNodeI,
   CommunityModeratorView,
+  Language,
   PersonViewSafe,
 } from "lemmy-js-client";
 import { CommentViewType } from "../../interfaces";
@@ -22,6 +23,7 @@ interface CommentNodesProps {
   showCommunity?: boolean;
   enableDownvotes?: boolean;
   viewType: CommentViewType;
+  allLanguages: Language[];
 }
 
 export class CommentNodes extends Component<CommentNodesProps, any> {
@@ -51,6 +53,7 @@ export class CommentNodes extends Component<CommentNodesProps, any> {
             showCommunity={this.props.showCommunity}
             enableDownvotes={this.props.enableDownvotes}
             viewType={this.props.viewType}
+            allLanguages={this.props.allLanguages}
           />
         ))}
       </div>
index a2d2b10c19008a70299f9f1f09d8ea8efe6444f0..f72f8a6a09c7c734122d83a3954524d908fc445f 100644 (file)
@@ -64,6 +64,7 @@ export class CommentReport extends Component<CommentReportProps, any> {
           enableDownvotes={true}
           viewOnly={true}
           showCommunity={true}
+          allLanguages={[]}
         />
         <div>
           {i18n.t("reporter")}: <PersonListing person={r.creator} />
diff --git a/src/shared/components/common/language-select.tsx b/src/shared/components/common/language-select.tsx
new file mode 100644 (file)
index 0000000..746fea4
--- /dev/null
@@ -0,0 +1,109 @@
+import { Option } from "@sniptt/monads";
+import classNames from "classnames";
+import { Component, linkEvent } from "inferno";
+import { Language } from "lemmy-js-client";
+import { i18n } from "../../i18next";
+import { randomStr } from "../../utils";
+import { Icon } from "./icon";
+
+interface LanguageSelectProps {
+  allLanguages: Language[];
+  selectedLanguageIds: Option<number[]>;
+  multiple: boolean;
+  onChange(val: number[]): any;
+}
+
+export class LanguageSelect extends Component<LanguageSelectProps, any> {
+  private id = `language-select-${randomStr()}`;
+
+  constructor(props: any, context: any) {
+    super(props, context);
+  }
+
+  componentDidMount() {
+    this.setSelectedValues();
+  }
+
+  // Necessary because there is no HTML way to set selected for multiple in value=
+  setSelectedValues() {
+    this.props.selectedLanguageIds.map(toString).match({
+      some: ids => {
+        var select = (document.getElementById(this.id) as HTMLSelectElement)
+          .options;
+        for (let i = 0; i < select.length; i++) {
+          let o = select[i];
+          if (ids.includes(o.value)) {
+            o.selected = true;
+          }
+        }
+      },
+      none: void 0,
+    });
+  }
+
+  render() {
+    let selectedLangs = this.props.selectedLanguageIds;
+
+    return (
+      <div className="form-group row">
+        <label
+          className={classNames("col-form-label", {
+            "col-sm-3": this.props.multiple,
+            "col-sm-2": !this.props.multiple,
+          })}
+          htmlFor={this.id}
+        >
+          {i18n.t(this.props.multiple ? "language_plural" : "language")}
+        </label>
+        <div
+          className={classNames("input-group", {
+            "col-sm-9": this.props.multiple,
+            "col-sm-10": !this.props.multiple,
+          })}
+        >
+          <select
+            className="form-control custom-select"
+            id={this.id}
+            onChange={linkEvent(this, this.handleLanguageChange)}
+            aria-label="action"
+            multiple={this.props.multiple}
+          >
+            {this.props.allLanguages.map(l => (
+              <option
+                key={l.id}
+                value={l.id}
+                selected={selectedLangs.unwrapOr([]).includes(l.id)}
+              >
+                {l.name}
+              </option>
+            ))}
+          </select>
+          {this.props.multiple && (
+            <div className="input-group-append">
+              <button
+                className="input-group-text"
+                onClick={linkEvent(this, this.handleDeselectAll)}
+              >
+                <Icon icon="x" />
+              </button>
+            </div>
+          )}
+        </div>
+      </div>
+    );
+  }
+
+  handleLanguageChange(i: LanguageSelect, event: any) {
+    let options: HTMLOptionElement[] = Array.from(event.target.options);
+    let selected: number[] = options
+      .filter(o => o.selected)
+      .map(o => Number(o.value));
+
+    i.props.onChange(selected);
+  }
+
+  handleDeselectAll(i: LanguageSelect, event: any) {
+    event.preventDefault();
+    i.props.onChange([]);
+  }
+}
index b3374847feaa0ee1d7060f356124791b4e33f08f..69bcd6df2c009bc592068c2ddeb2b31916422372 100644 (file)
@@ -2,7 +2,7 @@ import { None, Option, Some } from "@sniptt/monads";
 import autosize from "autosize";
 import { Component, linkEvent } from "inferno";
 import { Prompt } from "inferno-router";
-import { toUndefined } from "lemmy-js-client";
+import { Language, toUndefined } from "lemmy-js-client";
 import { pictrsUri } from "../../env";
 import { i18n } from "../../i18next";
 import { UserService } from "../../services";
@@ -18,9 +18,11 @@ import {
   toast,
 } from "../../utils";
 import { Icon, Spinner } from "./icon";
+import { LanguageSelect } from "./language-select";
 
 interface MarkdownTextAreaProps {
   initialContent: Option<string>;
+  initialLanguageId: Option<number>;
   placeholder: Option<string>;
   buttonTitle: Option<string>;
   maxLength: Option<number>;
@@ -28,14 +30,21 @@ interface MarkdownTextAreaProps {
   focus?: boolean;
   disabled?: boolean;
   finished?: boolean;
+  showLanguage?: boolean;
   hideNavigationWarnings?: boolean;
   onContentChange?(val: string): any;
   onReplyCancel?(): any;
-  onSubmit?(msg: { val: string; formId: string }): any;
+  onSubmit?(msg: {
+    val: Option<string>;
+    formId: string;
+    languageId: Option<number>;
+  }): any;
+  allLanguages: Language[];
 }
 
 interface MarkdownTextAreaState {
   content: Option<string>;
+  languageId: Option<number>;
   previewMode: boolean;
   loading: boolean;
   imageLoading: boolean;
@@ -50,6 +59,7 @@ export class MarkdownTextArea extends Component<
   private tribute: any;
   private emptyState: MarkdownTextAreaState = {
     content: this.props.initialContent,
+    languageId: this.props.initialLanguageId,
     previewMode: false,
     loading: false,
     imageLoading: false,
@@ -58,6 +68,8 @@ export class MarkdownTextArea extends Component<
   constructor(props: any, context: any) {
     super(props, context);
 
+    this.handleLanguageChange = this.handleLanguageChange.bind(this);
+
     if (isBrowser()) {
       this.tribute = setupTribute();
     }
@@ -149,6 +161,18 @@ export class MarkdownTextArea extends Component<
             {i18n.t("body")}
           </label>
         </div>
+        {this.props.showLanguage && (
+          <div className="row justify-content-end">
+            <div className="col-sm-8">
+              <LanguageSelect
+                allLanguages={this.props.allLanguages}
+                selectedLanguageIds={this.state.languageId.map(Array.of)}
+                multiple={false}
+                onChange={this.handleLanguageChange}
+              />
+            </div>
+          </div>
+        )}
         <div className="row">
           <div className="col-sm-12 d-flex flex-wrap">
             {this.props.buttonTitle.match({
@@ -394,10 +418,18 @@ export class MarkdownTextArea extends Component<
     i.setState({ previewMode: !i.state.previewMode });
   }
 
+  handleLanguageChange(val: number[]) {
+    this.setState({ languageId: Some(val[0]) });
+  }
+
   handleSubmit(i: MarkdownTextArea, event: any) {
     event.preventDefault();
     i.setState({ loading: true });
-    let msg = { val: toUndefined(i.state.content), formId: i.formId };
+    let msg = {
+      val: i.state.content,
+      formId: i.formId,
+      languageId: i.state.languageId,
+    };
     i.props.onSubmit(msg);
   }
 
index cac317eef1e49e5554f99a8eb16f34a6d4e0e1c7..07fbf203926ebbd8aac31e112d5ac4b531a6afde 100644 (file)
@@ -95,11 +95,13 @@ export class RegistrationApplication extends Component<
             <div className="col-sm-10">
               <MarkdownTextArea
                 initialContent={this.state.denyReason}
+                initialLanguageId={None}
                 onContentChange={this.handleDenyReasonChange}
                 placeholder={None}
                 buttonTitle={None}
                 maxLength={None}
                 hideNavigationWarnings
+                allLanguages={[]}
               />
             </div>
           </div>
index 8f70f36f0f74d7d6eb85708e17dba3d83d317481..db7cff08a508e3644fd1a04aad75b50c8813bb6f 100644 (file)
@@ -212,10 +212,12 @@ export class CommunityForm extends Component<
             <div className="col-12 col-sm-10">
               <MarkdownTextArea
                 initialContent={this.state.communityForm.description}
+                initialLanguageId={None}
                 placeholder={Some("description")}
                 buttonTitle={None}
                 maxLength={None}
                 onContentChange={this.handleCommunityDescriptionChange}
+                allLanguages={[]}
               />
             </div>
           </div>
index 5c3746e1b0bcc7cd6dffae792ed3e0bfa92e1f18..a509df1577abdc36981f7a7a6afd37950fa79907 100644 (file)
@@ -391,6 +391,7 @@ export class Community extends Component<any, State> {
           removeDuplicates
           enableDownvotes={enableDownvotes(this.state.siteRes)}
           enableNsfw={enableNsfw(this.state.siteRes)}
+          allLanguages={this.state.siteRes.all_languages}
         />
       )
     ) : this.state.commentsLoading ? (
@@ -407,6 +408,7 @@ export class Community extends Component<any, State> {
         moderators={this.state.communityRes.map(r => r.moderators)}
         admins={Some(this.state.siteRes.admins)}
         maxCommentsShown={None}
+        allLanguages={this.state.siteRes.all_languages}
       />
     );
   }
index 69de7a8585171c0c019125ad6e7affd52fd2e5f1..3761998ea5a7f55ec2b3e08e26262ed79718d7dd 100644 (file)
@@ -38,6 +38,7 @@ import {
 import { UserService, WebSocketService } from "../../services";
 import {
   auth,
+  canCreateCommunity,
   commentsToFlatNodes,
   createCommentLikeRes,
   createPostLikeFindRes,
@@ -430,7 +431,8 @@ export class Home extends Component<any, HomeState> {
             <div className="card border-secondary mb-3">
               <div className="card-body">
                 {this.trendingCommunities()}
-                {this.createCommunityButton()}
+                {canCreateCommunity(this.state.siteRes) &&
+                  this.createCommunityButton()}
                 {this.exploreCommunitiesButton()}
               </div>
             </div>
@@ -579,6 +581,7 @@ export class Home extends Component<any, HomeState> {
         removeDuplicates
         enableDownvotes={enableDownvotes(this.state.siteRes)}
         enableNsfw={enableNsfw(this.state.siteRes)}
+        allLanguages={this.state.siteRes.all_languages}
       />
     ) : (
       <CommentNodes
@@ -591,6 +594,7 @@ export class Home extends Component<any, HomeState> {
         showCommunity
         showContext
         enableDownvotes={enableDownvotes(this.state.siteRes)}
+        allLanguages={this.state.siteRes.all_languages}
       />
     );
   }
index d7dc2a27627b19489fec1c5e6bf1bd6bc5af4592..aa40aa0c38ab2e0cb4be3772896efd7fda9bbc16 100644 (file)
@@ -295,11 +295,13 @@ export class Signup extends Component<any, State> {
                 <div className="col-sm-10">
                   <MarkdownTextArea
                     initialContent={None}
+                    initialLanguageId={None}
                     placeholder={None}
                     buttonTitle={None}
                     maxLength={None}
                     onContentChange={this.handleAnswerChange}
                     hideNavigationWarnings
+                    allLanguages={[]}
                   />
                 </div>
               </div>
index 4f275cfd4fa032c994b012bab5f4d20b6e5e4fbc..74eef0a33eb0a02d3c5b22f87cf2ccb58d2a1d2e 100644 (file)
@@ -212,11 +212,13 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
             <div className="col-12">
               <MarkdownTextArea
                 initialContent={this.state.siteForm.sidebar}
+                initialLanguageId={None}
                 placeholder={None}
                 buttonTitle={None}
                 maxLength={None}
                 onContentChange={this.handleSiteSidebarChange}
                 hideNavigationWarnings
+                allLanguages={[]}
               />
             </div>
           </div>
@@ -227,11 +229,13 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
             <div className="col-12">
               <MarkdownTextArea
                 initialContent={this.state.siteForm.legal_information}
+                initialLanguageId={None}
                 placeholder={None}
                 buttonTitle={None}
                 maxLength={None}
                 onContentChange={this.handleSiteLegalInfoChange}
                 hideNavigationWarnings
+                allLanguages={[]}
               />
             </div>
           </div>
@@ -243,11 +247,13 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
               <div className="col-12">
                 <MarkdownTextArea
                   initialContent={this.state.siteForm.application_question}
+                  initialLanguageId={None}
                   placeholder={None}
                   buttonTitle={None}
                   maxLength={None}
                   onContentChange={this.handleSiteApplicationQuestionChange}
                   hideNavigationWarnings
+                  allLanguages={[]}
                 />
               </div>
             </div>
index 858364f945f90ac8cdc22f229148f9d602cd23fa..2ba6dce3131a51167041bba0f848167c597ff664 100644 (file)
@@ -398,6 +398,7 @@ export class Inbox extends Component<any, InboxState> {
             showCommunity
             showContext
             enableDownvotes={enableDownvotes(this.state.siteRes)}
+            allLanguages={this.state.siteRes.all_languages}
           />
         );
       case ReplyEnum.Mention:
@@ -420,6 +421,7 @@ export class Inbox extends Component<any, InboxState> {
             showCommunity
             showContext
             enableDownvotes={enableDownvotes(this.state.siteRes)}
+            allLanguages={this.state.siteRes.all_languages}
           />
         );
       case ReplyEnum.Message:
@@ -452,6 +454,7 @@ export class Inbox extends Component<any, InboxState> {
           showCommunity
           showContext
           enableDownvotes={enableDownvotes(this.state.siteRes)}
+          allLanguages={this.state.siteRes.all_languages}
         />
       </div>
     );
@@ -473,6 +476,7 @@ export class Inbox extends Component<any, InboxState> {
             showCommunity
             showContext
             enableDownvotes={enableDownvotes(this.state.siteRes)}
+            allLanguages={this.state.siteRes.all_languages}
           />
         ))}
       </div>
index 8eaa5f0ac00b57c74a4b21bc1b737a8dbc19e858..e228cbdf678a0bdfd8760195960f9279b8414c3a 100644 (file)
@@ -3,6 +3,7 @@ import { Component } from "inferno";
 import {
   CommentView,
   GetPersonDetailsResponse,
+  Language,
   PersonViewSafe,
   PostView,
   SortType,
@@ -16,6 +17,7 @@ import { PostListing } from "../post/post-listing";
 interface PersonDetailsProps {
   personRes: GetPersonDetailsResponse;
   admins: PersonViewSafe[];
+  allLanguages: Language[];
   page: number;
   limit: number;
   sort: SortType;
@@ -99,6 +101,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
             showCommunity
             showContext
             enableDownvotes={this.props.enableDownvotes}
+            allLanguages={this.props.allLanguages}
           />
         );
       }
@@ -114,6 +117,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
             showCommunity
             enableDownvotes={this.props.enableDownvotes}
             enableNsfw={this.props.enableNsfw}
+            allLanguages={this.props.allLanguages}
           />
         );
       }
@@ -171,6 +175,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
           showCommunity
           showContext
           enableDownvotes={this.props.enableDownvotes}
+          allLanguages={this.props.allLanguages}
         />
       </div>
     );
@@ -189,6 +194,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
               moderators={None}
               enableDownvotes={this.props.enableDownvotes}
               enableNsfw={this.props.enableNsfw}
+              allLanguages={this.props.allLanguages}
             />
             <hr className="my-3" />
           </>
index 0a8e4c09ffe138b370c062df1cc6217ceedd6895..b93872847ca319f220757ec05fc0117ce6a76991 100644 (file)
@@ -283,6 +283,7 @@ export class Profile extends Component<any, ProfileState> {
                     enableNsfw={enableNsfw(this.state.siteRes)}
                     view={this.state.view}
                     onPageChange={this.handlePageChange}
+                    allLanguages={this.state.siteRes.all_languages}
                   />
                 </div>
 
index 838c1ec1081b415d26d49f12a54009d914f7535e..e6d8896f69ec3ec9d12fa7f238df6fd6d530e050 100644 (file)
@@ -54,6 +54,7 @@ import {
 import { HtmlTags } from "../common/html-tags";
 import { Icon, Spinner } from "../common/icon";
 import { ImageUploadForm } from "../common/image-upload-form";
+import { LanguageSelect } from "../common/language-select";
 import { ListingTypeSelect } from "../common/listing-type-select";
 import { MarkdownTextArea } from "../common/markdown-textarea";
 import { SortSelect } from "../common/sort-select";
@@ -99,7 +100,8 @@ export class Settings extends Component<any, SettingsState> {
       default_sort_type: None,
       default_listing_type: None,
       theme: None,
-      lang: None,
+      interface_language: None,
+      discussion_languages: None,
       avatar: None,
       banner: None,
       display_name: None,
@@ -140,6 +142,8 @@ export class Settings extends Component<any, SettingsState> {
     this.handleSortTypeChange = this.handleSortTypeChange.bind(this);
     this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
     this.handleBioChange = this.handleBioChange.bind(this);
+    this.handleDiscussionLanguageChange =
+      this.handleDiscussionLanguageChange.bind(this);
 
     this.handleAvatarUpload = this.handleAvatarUpload.bind(this);
     this.handleAvatarRemove = this.handleAvatarRemove.bind(this);
@@ -163,7 +167,7 @@ export class Settings extends Component<any, SettingsState> {
           theme: Some(luv.local_user.theme ? luv.local_user.theme : "browser"),
           default_sort_type: Some(luv.local_user.default_sort_type),
           default_listing_type: Some(luv.local_user.default_listing_type),
-          lang: Some(luv.local_user.lang),
+          interface_language: Some(luv.local_user.interface_language),
           avatar: luv.person.avatar,
           banner: luv.person.banner,
           display_name: luv.person.display_name,
@@ -479,6 +483,8 @@ export class Settings extends Component<any, SettingsState> {
   }
 
   saveUserSettingsHtmlForm() {
+    let selectedLangs = this.state.saveUserSettingsForm.discussion_languages;
+
     return (
       <>
         <h5>{i18n.t("settings")}</h5>
@@ -509,11 +515,13 @@ export class Settings extends Component<any, SettingsState> {
             <div className="col-sm-9">
               <MarkdownTextArea
                 initialContent={this.state.saveUserSettingsForm.bio}
+                initialLanguageId={None}
                 onContentChange={this.handleBioChange}
                 maxLength={Some(300)}
                 placeholder={None}
                 buttonTitle={None}
                 hideNavigationWarnings
+                allLanguages={this.state.siteRes.all_languages}
               />
             </div>
           </div>
@@ -578,17 +586,19 @@ export class Settings extends Component<any, SettingsState> {
           </div>
           <div className="form-group row">
             <label className="col-sm-3" htmlFor="user-language">
-              {i18n.t("language")}
+              {i18n.t("interface_language")}
             </label>
             <div className="col-sm-9">
               <select
                 id="user-language"
-                value={toUndefined(this.state.saveUserSettingsForm.lang)}
-                onChange={linkEvent(this, this.handleLangChange)}
+                value={toUndefined(
+                  this.state.saveUserSettingsForm.interface_language
+                )}
+                onChange={linkEvent(this, this.handleInterfaceLangChange)}
                 className="custom-select w-auto"
               >
                 <option disabled aria-hidden="true">
-                  {i18n.t("language")}
+                  {i18n.t("interface_language")}
                 </option>
                 <option value="browser">{i18n.t("browser_default")}</option>
                 <option disabled aria-hidden="true">
@@ -604,6 +614,12 @@ export class Settings extends Component<any, SettingsState> {
               </select>
             </div>
           </div>
+          <LanguageSelect
+            allLanguages={this.state.siteRes.all_languages}
+            selectedLanguageIds={selectedLangs}
+            multiple={true}
+            onChange={this.handleDiscussionLanguageChange}
+          />
           <div className="form-group row">
             <label className="col-sm-3" htmlFor="user-theme">
               {i18n.t("theme")}
@@ -1040,14 +1056,20 @@ export class Settings extends Component<any, SettingsState> {
     i.setState(i.state);
   }
 
-  handleLangChange(i: Settings, event: any) {
-    i.state.saveUserSettingsForm.lang = Some(event.target.value);
+  handleInterfaceLangChange(i: Settings, event: any) {
+    i.state.saveUserSettingsForm.interface_language = Some(event.target.value);
     i18n.changeLanguage(
-      getLanguages(i.state.saveUserSettingsForm.lang.unwrap())[0]
+      getLanguages(i.state.saveUserSettingsForm.interface_language.unwrap())[0]
     );
     i.setState(i.state);
   }
 
+  handleDiscussionLanguageChange(val: number[]) {
+    this.setState(
+      s => ((s.saveUserSettingsForm.discussion_languages = Some(val)), s)
+    );
+  }
+
   handleSortTypeChange(val: SortType) {
     this.setState(
       s => (
index 6bb2f4d28df97f02ace4f1754538a5789b52a788..3d3396c040745c9b8dc875f4b3ff2dce5ed4ae6d 100644 (file)
@@ -151,6 +151,7 @@ export class CreatePost extends Component<any, CreatePostState> {
                     params={Some(this.params)}
                     enableDownvotes={enableDownvotes(this.state.siteRes)}
                     enableNsfw={enableNsfw(this.state.siteRes)}
+                    allLanguages={this.state.siteRes.all_languages}
                   />
                 </div>
               </div>
index a3b5b75ec046183a2384e569b5e7da5f26d816c5..4dc68fdf0f5df4e590200495a7450b8c02db3f2a 100644 (file)
@@ -72,7 +72,7 @@ export class MetadataCard extends Component<
                             ),
                             none: <></>,
                           })}
-                          {post.embed_html.isSome() && (
+                          {post.embed_video_url.isSome() && (
                             <button
                               className="mt-2 btn btn-secondary text-monospace"
                               onClick={linkEvent(this, this.handleIframeExpand)}
@@ -91,7 +91,7 @@ export class MetadataCard extends Component<
             none: <></>,
           })}
         {this.state.expanded &&
-          post.embed_html.match({
+          post.embed_video_url.match({
             some: html => (
               <div
                 className="mt-3 mb-2"
index 286378b114ab194ef1e2ed471eb2fa03e7e1ae55..dd2c59f65ffb7890852e775eff434eefc9254aea 100644 (file)
@@ -6,6 +6,7 @@ import {
   CommunityView,
   CreatePost,
   EditPost,
+  Language,
   ListingType,
   PostResponse,
   PostView,
@@ -36,6 +37,7 @@ import {
   ghostArchiveUrl,
   isBrowser,
   isImage,
+  myFirstDiscussionLanguageId,
   pictrsDeleteToast,
   relTags,
   setupTippy,
@@ -48,6 +50,7 @@ import {
   wsSubscribe,
 } from "../../utils";
 import { Icon, Spinner } from "../common/icon";
+import { LanguageSelect } from "../common/language-select";
 import { MarkdownTextArea } from "../common/markdown-textarea";
 import { PostListings } from "./post-listings";
 
@@ -60,6 +63,7 @@ const MAX_POST_TITLE_LENGTH = 200;
 
 interface PostFormProps {
   post_view: Option<PostView>; // If a post is given, that means this is an edit
+  allLanguages: Language[];
   communities: Option<CommunityView[]>;
   params: Option<PostFormParams>;
   onCancel?(): any;
@@ -90,6 +94,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
       url: None,
       body: None,
       honeypot: None,
+      language_id: None,
       auth: undefined,
     }),
     loading: false,
@@ -105,6 +110,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
     this.fetchSimilarPosts = debounce(this.fetchSimilarPosts.bind(this));
     this.fetchPageTitle = debounce(this.fetchPageTitle.bind(this));
     this.handlePostBodyChange = this.handlePostBodyChange.bind(this);
+    this.handleLanguageChange = this.handleLanguageChange.bind(this);
 
     this.state = this.emptyState;
 
@@ -124,6 +130,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
           url: pv.post.url,
           nsfw: Some(pv.post.nsfw),
           honeypot: None,
+          language_id: Some(pv.post.language_id),
           auth: auth().unwrap(),
         }),
       };
@@ -172,6 +179,10 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
   }
 
   render() {
+    let selectedLangs = this.state.postForm.language_id
+      .or(myFirstDiscussionLanguageId(UserService.Instance.myUserInfo))
+      .map(Array.of);
+
     return (
       <div>
         <Prompt
@@ -284,6 +295,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
                         posts={xPosts}
                         enableDownvotes={this.props.enableDownvotes}
                         enableNsfw={this.props.enableNsfw}
+                        allLanguages={this.props.allLanguages}
                       />
                     </>
                   ),
@@ -325,6 +337,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
                         posts={sPosts}
                         enableDownvotes={this.props.enableDownvotes}
                         enableNsfw={this.props.enableNsfw}
+                        allLanguages={this.props.allLanguages}
                       />
                     </>
                   ),
@@ -338,10 +351,12 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
             <div className="col-sm-10">
               <MarkdownTextArea
                 initialContent={this.state.postForm.body}
+                initialLanguageId={None}
                 onContentChange={this.handlePostBodyChange}
                 placeholder={None}
                 buttonTitle={None}
                 maxLength={None}
+                allLanguages={this.props.allLanguages}
               />
             </div>
           </div>
@@ -388,6 +403,12 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
               </div>
             </div>
           )}
+          <LanguageSelect
+            allLanguages={this.props.allLanguages}
+            selectedLanguageIds={selectedLangs}
+            multiple={false}
+            onChange={this.handleLanguageChange}
+          />
           <input
             tabIndex={-1}
             autoComplete="false"
@@ -453,6 +474,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
           body: pForm.body,
           nsfw: pForm.nsfw,
           post_id: pv.post.id,
+          language_id: Some(pv.post.language_id),
           auth: auth().unwrap(),
         });
         WebSocketService.Instance.send(wsClient.editPost(form));
@@ -559,6 +581,10 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
     i.setState(s => ((s.postForm.nsfw = Some(event.target.checked)), s));
   }
 
+  handleLanguageChange(val: number[]) {
+    this.setState(s => ((s.postForm.language_id = Some(val[0])), s));
+  }
+
   handleHoneyPotChange(i: PostForm, event: any) {
     i.setState(s => ((s.postForm.honeypot = Some(event.target.value)), s));
   }
index e2468247674d793be4057b3a98626e8bb55cd9d8..fd94d4d0137e78be5d94f5c855c0175b1eaed241 100644 (file)
@@ -12,6 +12,7 @@ import {
   CreatePostLike,
   CreatePostReport,
   DeletePost,
+  Language,
   LockPost,
   PersonViewSafe,
   PostView,
@@ -88,6 +89,7 @@ interface PostListingProps {
   duplicates: Option<PostView[]>;
   moderators: Option<CommunityModeratorView[]>;
   admins: Option<PersonViewSafe[]>;
+  allLanguages: Language[];
   showCommunity?: boolean;
   showBody?: boolean;
   enableDownvotes?: boolean;
@@ -169,6 +171,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
               onCancel={this.handleEditCancel}
               enableNsfw={this.props.enableNsfw}
               enableDownvotes={this.props.enableDownvotes}
+              allLanguages={this.props.allLanguages}
             />
           </div>
         )}
index 158bd8e425cbaa0a13295f2e950a62c4630968f7..39d46a71a563ee897b47a8f105f272b361346a9d 100644 (file)
@@ -2,12 +2,13 @@ import { None, Some } from "@sniptt/monads";
 import { Component } from "inferno";
 import { T } from "inferno-i18next-dess";
 import { Link } from "inferno-router";
-import { PostView } from "lemmy-js-client";
+import { Language, PostView } from "lemmy-js-client";
 import { i18n } from "../../i18next";
 import { PostListing } from "./post-listing";
 
 interface PostListingsProps {
   posts: PostView[];
+  allLanguages: Language[];
   showCommunity?: boolean;
   removeDuplicates?: boolean;
   enableDownvotes: boolean;
@@ -41,6 +42,7 @@ export class PostListings extends Component<PostListingsProps, any> {
                 showCommunity={this.props.showCommunity}
                 enableDownvotes={this.props.enableDownvotes}
                 enableNsfw={this.props.enableNsfw}
+                allLanguages={this.props.allLanguages}
               />
               <hr className="my-3" />
             </>
index 220a5a9eefab8082726b34d55159db7a4711cbc0..e4b6d60fe2324ce1b094bced60affc53e82703fa 100644 (file)
@@ -58,6 +58,7 @@ export class PostReport extends Component<PostReportProps, any> {
           enableDownvotes={true}
           enableNsfw={true}
           viewOnly={true}
+          allLanguages={[]}
         />
         <div>
           {i18n.t("reporter")}: <PersonListing person={r.creator} />
index 2d3f4245af6522172b50f6d5392a5ab332ddd42c..a7ffad5831c1787b2a26c91064503536aebc5287 100644 (file)
@@ -370,11 +370,13 @@ export class Post extends Component<any, PostState> {
                     admins={Some(this.state.siteRes.admins)}
                     enableDownvotes={enableDownvotes(this.state.siteRes)}
                     enableNsfw={enableNsfw(this.state.siteRes)}
+                    allLanguages={this.state.siteRes.all_languages}
                   />
                   <div ref={this.state.commentSectionRef} className="mb-2" />
                   <CommentForm
                     node={Right(res.post_view.post.id)}
                     disabled={res.post_view.post.locked}
+                    allLanguages={this.state.siteRes.all_languages}
                   />
                   <div className="d-block d-md-none">
                     <button
@@ -508,6 +510,7 @@ export class Post extends Component<any, PostState> {
                 admins={Some(this.state.siteRes.admins)}
                 enableDownvotes={enableDownvotes(this.state.siteRes)}
                 showContext
+                allLanguages={this.state.siteRes.all_languages}
               />
             </div>
           ),
@@ -611,6 +614,7 @@ export class Post extends Component<any, PostState> {
             moderators={Some(res.moderators)}
             admins={Some(this.state.siteRes.admins)}
             enableDownvotes={enableDownvotes(this.state.siteRes)}
+            allLanguages={this.state.siteRes.all_languages}
           />
         </div>
       ),
index 46953d723746a3f4c161565be04c44f824d303d1..6c74e2c8bbb96c6e8780a24e881d975c41e29f6b 100644 (file)
@@ -130,10 +130,12 @@ export class PrivateMessageForm extends Component<
             <div className="col-sm-10">
               <MarkdownTextArea
                 initialContent={Some(this.state.privateMessageForm.content)}
+                initialLanguageId={None}
                 placeholder={None}
                 buttonTitle={None}
                 maxLength={None}
                 onContentChange={this.handleContentChange}
+                allLanguages={[]}
               />
             </div>
           </div>
index 09f28f9b9389162cbb5c72a53dbee0c4ff394520..6712b95d852f34bc99a666a26c58cc668b1fff66 100644 (file)
@@ -597,6 +597,7 @@ export class Search extends Component<any, SearchState> {
                   showCommunity
                   enableDownvotes={enableDownvotes(this.state.siteRes)}
                   enableNsfw={enableNsfw(this.state.siteRes)}
+                  allLanguages={this.state.siteRes.all_languages}
                   viewOnly
                 />
               )}
@@ -618,6 +619,7 @@ export class Search extends Component<any, SearchState> {
                   locked
                   noIndent
                   enableDownvotes={enableDownvotes(this.state.siteRes)}
+                  allLanguages={this.state.siteRes.all_languages}
                 />
               )}
               {i.type_ == "communities" && (
@@ -656,6 +658,7 @@ export class Search extends Component<any, SearchState> {
         admins={None}
         maxCommentsShown={None}
         enableDownvotes={enableDownvotes(this.state.siteRes)}
+        allLanguages={this.state.siteRes.all_languages}
       />
     );
   }
@@ -685,6 +688,7 @@ export class Search extends Component<any, SearchState> {
                 admins={None}
                 enableDownvotes={enableDownvotes(this.state.siteRes)}
                 enableNsfw={enableNsfw(this.state.siteRes)}
+                allLanguages={this.state.siteRes.all_languages}
                 viewOnly
               />
             </div>
index 69452f798ca10d746becc6a1bde1ea146cef9cd8..6c6db1fdf55f1de51a94b483b4db9632f362f83e 100644 (file)
@@ -1,4 +1,4 @@
-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 {
@@ -418,7 +418,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;
 
@@ -1461,3 +1461,29 @@ export function postToCommentSortType(sort: SortType): CommentSortType {
     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): boolean {
+  let adminOnly = siteRes.site_view
+    .map(s => s.site.community_creation_admin_only)
+    .unwrapOr(false);
+  return !adminOnly || amAdmin(Some(siteRes.admins));
+}
index 8d0e3f52529642cf815e0e57984f2997f549a107..d3a1c9f4014e76af88350f1e2d707dd47a7f665f 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -5224,10 +5224,10 @@ lcid@^1.0.0:
   dependencies:
     invert-kv "^1.0.0"
 
-lemmy-js-client@0.17.0-rc.43:
-  version "0.17.0-rc.43"
-  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.43.tgz#30e985365a93d72184646fdfe359f39ccc2e2091"
-  integrity sha512-/+TOTZazoi74zwc8H2AJMd/Znrdnqfi0+TrfnmqvQ3fzrOl741ojEURxAHw3NsgW9b8HkubXZFLsi1RVR99UqA==
+lemmy-js-client@0.17.0-rc.44:
+  version "0.17.0-rc.44"
+  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.44.tgz#a5bd870b73bc25c3d8b47569ddcd66ef506b73ee"
+  integrity sha512-xXxjmDhRWCjRtfAIy8LwHDheR+VzQ4Co5xJyop1mXHtxrlUJx0mrcvXu84LnJ00zrqGa8lpE5R2IpbsRg9DsZA==
 
 levn@^0.4.1:
   version "0.4.1"