]> Untitled Git - lemmy-ui.git/commitdiff
Merge branch 'main' into route-data-refactor
authorabias <abias1122@gmail.com>
Fri, 16 Jun 2023 22:17:17 +0000 (18:17 -0400)
committerabias <abias1122@gmail.com>
Fri, 16 Jun 2023 22:17:17 +0000 (18:17 -0400)
25 files changed:
.github/CODEOWNERS
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml
Dockerfile
dev.dockerfile
package.json
src/shared/components/common/html-tags.tsx
src/shared/components/common/markdown-textarea.tsx
src/shared/components/common/moment-time.tsx
src/shared/components/community/community.tsx
src/shared/components/community/sidebar.tsx
src/shared/components/home/admin-settings.tsx
src/shared/components/home/emojis-form.tsx
src/shared/components/home/home.tsx
src/shared/components/home/rate-limit-form.tsx
src/shared/components/home/setup.tsx
src/shared/components/home/site-form.tsx
src/shared/components/home/site-sidebar.tsx
src/shared/components/home/tagline-form.tsx
src/shared/components/person/settings.tsx
src/shared/components/post/create-post.tsx
src/shared/components/post/post.tsx
src/shared/i18next.ts
src/shared/utils.ts
yarn.lock

index 6df17d57c4f6b962bb01c55f4aadaed0f6756774..76916e604aeda28ef62a8e3bbd4ab7f8d13a8e82 100644 (file)
@@ -1 +1 @@
-* @dessalines @SleeplessOne1917
+* @dessalines @SleeplessOne1917 @alectrocute
index 2273a13823ab59f49a6b8bc70dbd5ffbf2d93e3a..ae2d4e51f0a74a9ffb498c83f10a0f8acced7bfc 100644 (file)
@@ -21,7 +21,7 @@ body:
         - label: Is this only a single bug? Do not put multiple bugs in one issue.
           required: true
         - label: Is this a server side (not related to the UI) issue? Use the [Lemmy back end](https://github.com/LemmyNet/lemmy) repo.
-          required: true
+          required: false
   - type: textarea
     id: summary
     attributes:
index 2f6f3fc1f9dae8cc7bc265ade72d3ef93f140605..3c75050ab8a964b03a93030ba90821c505ef5de4 100644 (file)
@@ -19,7 +19,7 @@ body:
         - label: Is this only a feature request? Do not put multiple feature requests in one issue.
           required: true
         - label: Is this a server side (not related to the UI) issue? Use the [Lemmy back end](https://github.com/LemmyNet/lemmy) repo.
-          required: true
+          required: false
   - type: textarea
     id: problem
     attributes:
index 3d6d6212d8dc4401373873cf3d9bb67cf5cfbecb..2b36581d28d6ceebe2a891d35abe15fb9da410a3 100644 (file)
@@ -1,4 +1,4 @@
-FROM node:alpine as builder\r
+FROM node:20.2-alpine as builder\r
 RUN apk update && apk add curl yarn python3 build-base gcc wget git --no-cache\r
 RUN curl -sf https://gobinaries.com/tj/node-prune | sh\r
 \r
index 0e925c0a90dfca99dceb40c7186bc82e79fb757d..3bfc10daed7f14b92853e908e5e16c488f45e1e3 100644 (file)
@@ -1,4 +1,4 @@
-FROM node:alpine as builder\r
+FROM node:20.2-alpine as builder\r
 RUN apk update && apk add curl yarn python3 build-base gcc wget git --no-cache\r
 \r
 WORKDIR /usr/src/app\r
index df264eb03ace26314e24d12bc89bcc6f3b5655cd..20e5645d7a6429f3f8ff2526f1ef5577c76afca5 100644 (file)
@@ -61,7 +61,7 @@
     "inferno-server": "^8.1.1",
     "isomorphic-cookie": "^1.2.4",
     "jwt-decode": "^3.1.2",
-    "lemmy-js-client": "0.17.2-rc.24",
+    "lemmy-js-client": "0.18.0-rc.1",
     "lodash": "^4.17.21",
     "markdown-it": "^13.0.1",
     "markdown-it-container": "^3.0.0",
index 0e6cb2d03ad381528ed3f78447e0c0bc18d2b1bf..f32b0fc048a04687102fc740f08912c7fb0701c3 100644 (file)
@@ -2,7 +2,8 @@ import { htmlToText } from "html-to-text";
 import { Component } from "inferno";
 import { Helmet } from "inferno-helmet";
 import { httpExternalPath } from "../../env";
-import { getLanguages, md } from "../../utils";
+import { i18n } from "../../i18next";
+import { md } from "../../utils";
 
 interface HtmlTagsProps {
   title: string;
@@ -17,11 +18,10 @@ export class HtmlTags extends Component<HtmlTagsProps, any> {
     const url = httpExternalPath(this.props.path);
     const desc = this.props.description;
     const image = this.props.image;
-    const lang = getLanguages()[0];
 
     return (
       <Helmet title={this.props.title}>
-        <html lang={lang == "browser" ? "en" : lang} />
+        <html lang={i18n.resolvedLanguage} />
 
         {["title", "og:title", "twitter:title"].map(t => (
           <meta key={t} property={t} content={this.props.title} />
index 9318d3bb8f972b25b19a48711fcf05aed2ccb8b3..a4459ac0236dfef97de988603172a9f05763cc78 100644 (file)
@@ -184,53 +184,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)}
@@ -283,6 +236,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>
     );
index 10714f5bb48fea50e3b18766ac608f3f44fb521b..30c1682c9eaaf6d7980768b35036bfd0e8a060df 100644 (file)
@@ -1,7 +1,7 @@
 import { Component } from "inferno";
 import moment from "moment";
 import { i18n } from "../../i18next";
-import { capitalizeFirstLetter, getLanguages } from "../../utils";
+import { capitalizeFirstLetter } from "../../utils";
 import { Icon } from "./icon";
 
 interface MomentTimeProps {
@@ -15,9 +15,7 @@ export class MomentTime extends Component<MomentTimeProps, any> {
   constructor(props: any, context: any) {
     super(props, context);
 
-    const lang = getLanguages();
-
-    moment.locale(lang);
+    moment.locale([...i18n.languages]);
   }
 
   createdAndModifiedTimes() {
index 6b4eecff64bc19082b232e2571d7ae31eb2713bd..6f3c9112f782231e9af853b199414b2174541a10 100644 (file)
@@ -375,7 +375,6 @@ export class Community extends Component<
           community_view={res.community_view}
           moderators={res.moderators}
           admins={site_res.admins}
-          online={res.online}
           enableNsfw={enableNsfw(site_res)}
           editable
           allLanguages={site_res.all_languages}
@@ -662,6 +661,12 @@ export class Community extends Component<
     const blockCommunityRes = await HttpService.client.blockCommunity(form);
     if (blockCommunityRes.state == "success") {
       updateCommunityBlock(blockCommunityRes.data);
+      this.setState(s => {
+        if (s.communityRes.state == "success") {
+          s.communityRes.data.community_view.blocked =
+            blockCommunityRes.data.blocked;
+        }
+      });
     }
   }
 
index a5c620f3b4b5704e8763a4d549af8fbd97c877d8..56b1ef27398eda6340b614c87cae345a8598fc52 100644 (file)
@@ -1,4 +1,5 @@
 import { Component, InfernoNode, linkEvent } from "inferno";
+import { T } from "inferno-i18next-dess";
 import { Link } from "inferno-router";
 import {
   AddModToCommunity,
@@ -38,7 +39,6 @@ interface SidebarProps {
   allLanguages: Language[];
   siteLanguages: number[];
   communityLanguages?: number[];
-  online: number;
   enableNsfw?: boolean;
   showIcon?: boolean;
   editable?: boolean;
@@ -63,7 +63,6 @@ interface SidebarState {
   removeCommunityLoading: boolean;
   leaveModTeamLoading: boolean;
   followCommunityLoading: boolean;
-  blockCommunityLoading: boolean;
   purgeCommunityLoading: boolean;
 }
 
@@ -77,7 +76,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
     removeCommunityLoading: false,
     leaveModTeamLoading: false,
     followCommunityLoading: false,
-    blockCommunityLoading: false,
     purgeCommunityLoading: false,
   };
 
@@ -104,7 +102,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
         removeCommunityLoading: false,
         leaveModTeamLoading: false,
         followCommunityLoading: false,
-        blockCommunityLoading: false,
         purgeCommunityLoading: false,
       });
     }
@@ -144,10 +141,15 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
             {myUSerInfo && this.blockCommunity()}
             {!myUSerInfo && (
               <div className="alert alert-info" role="alert">
-                {i18n.t("community_not_logged_in_alert", {
-                  community: name,
-                  instance: hostname(actor_id),
-                })}
+                <T
+                  i18nKey="community_not_logged_in_alert"
+                  interpolation={{
+                    community: name,
+                    instance: hostname(actor_id),
+                  }}
+                >
+                  #<code className="user-select-all">#</code>#
+                </T>
               </div>
             )}
           </div>
@@ -234,12 +236,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
     const counts = community_view.counts;
     return (
       <ul className="my-1 list-inline">
-        <li className="list-inline-item badge badge-secondary">
-          {i18n.t("number_online", {
-            count: this.props.online,
-            formattedCount: numToSI(this.props.online),
-          })}
-        </li>
         <li
           className="list-inline-item badge badge-secondary pointer"
           data-tippy-content={i18n.t("active_users_in_the_last_day", {
@@ -370,35 +366,18 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
   }
 
   blockCommunity() {
-    const community_view = this.props.community_view;
-    const blocked = this.props.community_view.blocked;
+    const { subscribed, blocked } = this.props.community_view;
 
     return (
       <div className="mb-2">
-        {community_view.subscribed == "NotSubscribed" &&
-          (blocked ? (
-            <button
-              className="btn btn-danger btn-block"
-              onClick={linkEvent(this, this.handleBlockCommunity)}
-            >
-              {this.state.blockCommunityLoading ? (
-                <Spinner />
-              ) : (
-                i18n.t("unblock_community")
-              )}
-            </button>
-          ) : (
-            <button
-              className="btn btn-danger btn-block"
-              onClick={linkEvent(this, this.handleBlockCommunity)}
-            >
-              {this.state.blockCommunityLoading ? (
-                <Spinner />
-              ) : (
-                i18n.t("block_community")
-              )}
-            </button>
-          ))}
+        {subscribed == "NotSubscribed" && (
+          <button
+            className="btn btn-danger btn-block"
+            onClick={linkEvent(this, this.handleBlockCommunity)}
+          >
+            {i18n.t(blocked ? "unblock_community" : "block_community")}
+          </button>
+        )}
       </div>
     );
   }
@@ -662,10 +641,11 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
   }
 
   handleBlockCommunity(i: Sidebar) {
-    i.setState({ blockCommunityLoading: true });
+    const { community, blocked } = i.props.community_view;
+
     i.props.onBlockCommunity({
-      community_id: 0,
-      block: !i.props.community_view.blocked,
+      community_id: community.id,
+      block: !blocked,
       auth: myAuthRequired(),
     });
   }
index 91ba727f2bae7292b83124d750d190a15e2acca8..ad7ac931aead7fbcfb5298535f5eba9f7798d737 100644 (file)
@@ -45,6 +45,8 @@ interface AdminSettingsState {
   instancesRes: RequestState<GetFederatedInstancesResponse>;
   bannedRes: RequestState<BannedPersonsResponse>;
   leaveAdminTeamRes: RequestState<GetSiteResponse>;
+  emojiLoading: boolean;
+  loading: boolean;
   themeList: string[];
   isIsomorphic: boolean;
 }
@@ -58,6 +60,8 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
     bannedRes: { state: "empty" },
     instancesRes: { state: "empty" },
     leaveAdminTeamRes: { state: "empty" },
+    emojiLoading: false,
+    loading: false,
     themeList: [],
     isIsomorphic: false,
   };
@@ -136,6 +140,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
                       onSaveSite={this.handleEditSite}
                       siteRes={this.state.siteRes}
                       themeList={this.state.themeList}
+                      loading={this.state.loading}
                     />
                   </div>
                   <div className="col-12 col-md-6">
@@ -154,6 +159,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
                     this.state.siteRes.site_view.local_site_rate_limit
                   }
                   onSaveSite={this.handleEditSite}
+                  loading={this.state.loading}
                 />
               ),
             },
@@ -165,6 +171,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
                   <TaglineForm
                     taglines={this.state.siteRes.taglines}
                     onSaveSite={this.handleEditSite}
+                    loading={this.state.loading}
                   />
                 </div>
               ),
@@ -178,6 +185,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
                     onCreate={this.handleCreateEmoji}
                     onDelete={this.handleDeleteEmoji}
                     onEdit={this.handleEditEmoji}
+                    loading={this.state.emojiLoading}
                   />
                 </div>
               ),
@@ -268,6 +276,8 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
   }
 
   async handleEditSite(form: EditSite) {
+    this.setState({ loading: true });
+
     const editRes = await HttpService.client.editSite(form);
 
     if (editRes.state === "success") {
@@ -280,6 +290,8 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
       toast(i18n.t("site_saved"));
     }
 
+    this.setState({ loading: false });
+
     return editRes;
   }
 
@@ -302,23 +314,35 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
   }
 
   async handleEditEmoji(form: EditCustomEmoji) {
+    this.setState({ emojiLoading: true });
+
     const res = await HttpService.client.editCustomEmoji(form);
     if (res.state === "success") {
       updateEmojiDataModel(res.data.custom_emoji);
     }
+
+    this.setState({ emojiLoading: false });
   }
 
   async handleDeleteEmoji(form: DeleteCustomEmoji) {
+    this.setState({ emojiLoading: true });
+
     const res = await HttpService.client.deleteCustomEmoji(form);
     if (res.state === "success") {
       removeFromEmojiDataModel(res.data.id);
     }
+
+    this.setState({ emojiLoading: false });
   }
 
   async handleCreateEmoji(form: CreateCustomEmoji) {
+    this.setState({ emojiLoading: true });
+
     const res = await HttpService.client.createCustomEmoji(form);
     if (res.state === "success") {
       updateEmojiDataModel(res.data.custom_emoji);
     }
+
+    this.setState({ emojiLoading: false });
   }
 }
index 171b7c99b2d8f4e71543e14612377d7cb1be1888..f77f51258e14c584ce9cc3192873a5b7ac1bd726 100644 (file)
@@ -23,12 +23,12 @@ interface EmojiFormProps {
   onEdit(form: EditCustomEmoji): void;
   onCreate(form: CreateCustomEmoji): void;
   onDelete(form: DeleteCustomEmoji): void;
+  loading: boolean;
 }
 
 interface EmojiFormState {
   siteRes: GetSiteResponse;
   customEmojis: CustomEmojiViewForm[];
-  loading: boolean;
   page: number;
 }
 
@@ -47,7 +47,6 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
   private isoData = setIsoData(this.context);
   private itemsPerPage = 15;
   private emptyState: EmojiFormState = {
-    loading: false,
     siteRes: this.isoData.site_res,
     customEmojis: this.isoData.site_res.custom_emojis.map((x, index) => ({
       id: x.custom_emoji.id,
@@ -223,7 +222,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
                             data-tippy-content={i18n.t("save")}
                             aria-label={i18n.t("save")}
                             disabled={
-                              this.state.loading ||
+                              this.props.loading ||
                               !this.canEdit(cv) ||
                               !cv.changed
                             }
@@ -243,7 +242,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
                           )}
                           data-tippy-content={i18n.t("delete")}
                           aria-label={i18n.t("delete")}
-                          disabled={this.state.loading}
+                          disabled={this.props.loading}
                           title={i18n.t("delete")}
                         >
                           <Icon
index ed379b846af0374628291939b74d76a565c119f1..67b02e460660dcf0e4db0d72156aca70fb4a4993 100644 (file)
@@ -411,7 +411,6 @@ export class Home extends Component<any, HomeState> {
       siteRes: {
         site_view: { counts, site },
         admins,
-        online,
       },
       showSubscribedMobile,
       showTrendingMobile,
@@ -443,7 +442,6 @@ export class Home extends Component<any, HomeState> {
               site={site}
               admins={admins}
               counts={counts}
-              online={online}
               showLocal={showLocal(this.isoData)}
             />
           )}
@@ -467,7 +465,6 @@ export class Home extends Component<any, HomeState> {
       siteRes: {
         site_view: { counts, site },
         admins,
-        online,
       },
     } = this.state;
 
@@ -493,7 +490,6 @@ export class Home extends Component<any, HomeState> {
             site={site}
             admins={admins}
             counts={counts}
-            online={online}
             showLocal={showLocal(this.isoData)}
           />
           {this.hasFollows && (
index 74ed18e32c81bd19ab67325997561950cb3fd8f3..0ce01260088f3b616e56cfb3a5700348f7889367 100644 (file)
@@ -24,6 +24,7 @@ interface RateLimitsProps {
 interface RateLimitFormProps {
   rateLimits: LocalSiteRateLimit;
   onSaveSite(form: EditSite): void;
+  loading: boolean;
 }
 
 interface RateLimitFormState {
@@ -41,7 +42,6 @@ interface RateLimitFormState {
     register?: number;
     register_per_second?: number;
   };
-  loading: boolean;
 }
 
 function RateLimits({
@@ -117,7 +117,6 @@ function submitRateLimitForm(i: RateLimitsForm, event: any) {
     }
   );
 
-  i.setState({ loading: true });
   i.props.onSaveSite(form);
 }
 
@@ -126,7 +125,6 @@ export default class RateLimitsForm extends Component<
   RateLimitFormState
 > {
   state: RateLimitFormState = {
-    loading: false,
     form: this.props.rateLimits,
   };
   constructor(props: RateLimitFormProps, context: any) {
@@ -164,9 +162,9 @@ export default class RateLimitsForm extends Component<
             <button
               type="submit"
               className="btn btn-secondary mr-2"
-              disabled={this.state.loading}
+              disabled={this.props.loading}
             >
-              {this.state.loading ? (
+              {this.props.loading ? (
                 <Spinner />
               ) : (
                 capitalizeFirstLetter(i18n.t("save"))
index 14350a58805afe362b4d3158f9d28b658872806d..b658bd2432cc1703346b4150f31ce925f019d3b5 100644 (file)
@@ -73,6 +73,7 @@ export class Setup extends Component<any, State> {
                 onSaveSite={this.handleCreateSite}
                 siteRes={this.state.siteRes}
                 themeList={this.state.themeList}
+                loading={false}
               />
             )}
           </div>
index 3b451e66ab10bd04493faea8b1ece5e4deda08e7..4035c74f1db97b0a23d02eb4efb20e4f7468193b 100644 (file)
@@ -12,7 +12,11 @@ import {
   ListingType,
 } from "lemmy-js-client";
 import { i18n } from "../../i18next";
-import { capitalizeFirstLetter, myAuthRequired } from "../../utils";
+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";
@@ -27,11 +31,11 @@ interface SiteFormProps {
   themeList?: string[];
   onSaveSite(form: EditSite): void;
   siteRes: GetSiteResponse;
+  loading: boolean;
 }
 
 interface SiteFormState {
   siteForm: EditSite;
-  loading: boolean;
   instance_select: {
     allowed_instances: string;
     blocked_instances: string;
@@ -44,7 +48,6 @@ type InstanceKey = "allowed_instances" | "blocked_instances";
 export class SiteForm extends Component<SiteFormProps, SiteFormState> {
   state: SiteFormState = {
     siteForm: this.initSiteForm(),
-    loading: false,
     instance_select: {
       allowed_instances: "",
       blocked_instances: "",
@@ -78,7 +81,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
       slur_filter_regex: ls.slur_filter_regex,
       actor_name_max_length: ls.actor_name_max_length,
       federation_enabled: ls.federation_enabled,
-      federation_debug: ls.federation_debug,
       federation_worker_count: ls.federation_worker_count,
       captcha_enabled: ls.captcha_enabled,
       captcha_difficulty: ls.captcha_difficulty,
@@ -107,23 +109,21 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
 
     this.handleDiscussionLanguageChange =
       this.handleDiscussionLanguageChange.bind(this);
+
     this.handleAddInstance = this.handleAddInstance.bind(this);
+    this.handleRemoveInstance = this.handleRemoveInstance.bind(this);
+
     this.handleInstanceEnterPress = this.handleInstanceEnterPress.bind(this);
     this.handleInstanceTextChange = this.handleInstanceTextChange.bind(this);
   }
 
-  // Necessary to stop the loading
-  componentWillReceiveProps() {
-    this.setState({ loading: false });
-  }
-
   render() {
     const siteSetup = this.props.siteRes.site_view.local_site.site_setup;
     return (
       <form onSubmit={linkEvent(this, this.handleSaveSiteSubmit)}>
         <NavigationPrompt
           when={
-            !this.state.loading &&
+            !this.props.loading &&
             !siteSetup &&
             !!(
               this.state.siteForm.name ||
@@ -136,8 +136,8 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
         />
         <h5>{`${
           siteSetup
-            ? capitalizeFirstLetter(i18n.t("save"))
-            : capitalizeFirstLetter(i18n.t("name"))
+            ? capitalizeFirstLetter(i18n.t("edit"))
+            : capitalizeFirstLetter(i18n.t("setup"))
         } ${i18n.t("your_site")}`}</h5>
         <div className="form-group row">
           <label className="col-12 col-form-label" htmlFor="create-site-name">
@@ -157,7 +157,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
           </div>
         </div>
         <div className="form-group">
-          <label>{i18n.t("icon")}</label>
+          <label className="mr-2">{i18n.t("icon")}</label>
           <ImageUploadForm
             uploadTitle={i18n.t("upload_icon")}
             imageSrc={this.state.siteForm.icon}
@@ -167,7 +167,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
           />
         </div>
         <div className="form-group">
-          <label>{i18n.t("banner")}</label>
+          <label className="mr-2">{i18n.t("banner")}</label>
           <ImageUploadForm
             uploadTitle={i18n.t("upload_banner")}
             imageSrc={this.state.siteForm.banner}
@@ -609,9 +609,9 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
             <button
               type="submit"
               className="btn btn-secondary mr-2"
-              disabled={this.state.loading}
+              disabled={this.props.loading}
             >
-              {this.state.loading ? (
+              {this.props.loading ? (
                 <Spinner />
               ) : siteSetup ? (
                 capitalizeFirstLetter(i18n.t("save"))
@@ -717,7 +717,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
     event.preventDefault();
     const auth = myAuthRequired();
     i.setState(s => ((s.siteForm.auth = auth), s));
-    i.setState({ loading: true, submitted: true });
+    i.setState({ submitted: true });
 
     const stateSiteForm = i.state.siteForm;
 
@@ -780,6 +780,11 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
 
   handleAddInstance(key: InstanceKey) {
     const instance = this.state.instance_select[key].trim();
+
+    if (!validInstanceTLD(instance)) {
+      return;
+    }
+
     if (!this.state.siteForm[key]?.includes(instance)) {
       this.setState(s => ({
         ...s,
index 051c3afd480c3d0676ecf176dde3ca919c0b3abd..be7cdf76180ee3da727ec424ac087484681a7401 100644 (file)
@@ -12,7 +12,6 @@ interface SiteSidebarProps {
   showLocal: boolean;
   counts?: SiteAggregates;
   admins?: PersonView[];
-  online?: number;
 }
 
 interface SiteSidebarState {
@@ -99,15 +98,8 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
 
   badges(siteAggregates: SiteAggregates) {
     const counts = siteAggregates;
-    const online = this.props.online ?? 1;
     return (
       <ul className="my-2 list-inline">
-        <li className="list-inline-item badge badge-secondary">
-          {i18n.t("number_online", {
-            count: online,
-            formattedCount: numToSI(online),
-          })}
-        </li>
         <li
           className="list-inline-item badge badge-secondary pointer"
           data-tippy-content={i18n.t("active_users_in_the_last_day", {
index 44ca4fc02f4c1172689da10913e6d39f0a3182ab..59e8dec4a2e7b2e7b99c710a830edf43963780f1 100644 (file)
@@ -9,17 +9,16 @@ import { MarkdownTextArea } from "../common/markdown-textarea";
 interface TaglineFormProps {
   taglines: Array<Tagline>;
   onSaveSite(form: EditSite): void;
+  loading: boolean;
 }
 
 interface TaglineFormState {
   taglines: Array<string>;
-  loading: boolean;
   editingRow?: number;
 }
 
 export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
   state: TaglineFormState = {
-    loading: false,
     editingRow: undefined,
     taglines: this.props.taglines.map(x => x.content),
   };
@@ -30,10 +29,6 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
     return i18n.t("taglines");
   }
 
-  componentWillReceiveProps() {
-    this.setState({ loading: false });
-  }
-
   render() {
     return (
       <div className="col-12">
@@ -110,9 +105,9 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
               <button
                 onClick={linkEvent(this, this.handleSaveClick)}
                 className="btn btn-secondary mr-2"
-                disabled={this.state.loading}
+                disabled={this.props.loading}
               >
-                {this.state.loading ? (
+                {this.props.loading ? (
                   <Spinner />
                 ) : (
                   capitalizeFirstLetter(i18n.t("save"))
@@ -153,7 +148,6 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
   }
 
   async handleSaveClick(i: TaglineForm) {
-    i.setState({ loading: true });
     i.props.onSaveSite({
       taglines: i.state.taglines,
       auth: myAuthRequired(),
index a29f61b008102ba4d60e3ab0d4988bd13e8f34d4..56d57a7a56850778c7e1a65923d36b4b38e2e0e9 100644 (file)
@@ -25,7 +25,6 @@ import {
   fetchCommunities,
   fetchThemeList,
   fetchUsers,
-  getLanguages,
   myAuth,
   myAuthRequired,
   personToChoice,
@@ -1058,12 +1057,12 @@ export class Settings extends Component<any, SettingsState> {
   }
 
   handleInterfaceLangChange(i: Settings, event: any) {
+    const newLang = event.target.value ?? "browser";
+    i18n.changeLanguage(newLang === "browser" ? navigator.languages : newLang);
+
     i.setState(
       s => ((s.saveUserSettingsForm.interface_language = event.target.value), s)
     );
-    i18n.changeLanguage(
-      getLanguages(i.state.saveUserSettingsForm.interface_language).at(0)
-    );
   }
 
   handleDiscussionLanguageChange(val: number[]) {
index 05279c35d0d7a47249e3534d943d52e1ecb7b472..046bc2de5e2c4dd0eeaccf15ae2388ffbde6c55c 100644 (file)
@@ -232,6 +232,10 @@ export class CreatePost extends Component<
     if (res.state === "success") {
       const postId = res.data.post_view.post.id;
       this.props.history.replace(`/post/${postId}`);
+    } else {
+      this.setState({
+        loading: false,
+      });
     }
   }
 
index 501c06dcb675011132bf4a05181e72ef0e766483..7eff1b4a4496d2e7115be1851df6e8de4aac59db 100644 (file)
@@ -557,7 +557,6 @@ export class Post extends Component<any, PostState> {
             community_view={res.data.community_view}
             moderators={res.data.moderators}
             admins={this.state.siteRes.admins}
-            online={res.data.online}
             enableNsfw={enableNsfw(this.state.siteRes)}
             showIcon
             allLanguages={this.state.siteRes.all_languages}
@@ -734,19 +733,14 @@ export class Post extends Component<any, PostState> {
 
   async handleBlockCommunity(form: BlockCommunity) {
     const blockCommunityRes = await HttpService.client.blockCommunity(form);
-    // TODO Probably isn't necessary
-    this.setState(s => {
-      if (
-        s.postRes.state == "success" &&
-        blockCommunityRes.state == "success"
-      ) {
-        s.postRes.data.community_view = blockCommunityRes.data.community_view;
-      }
-      return s;
-    });
-
     if (blockCommunityRes.state == "success") {
       updateCommunityBlock(blockCommunityRes.data);
+      this.setState(s => {
+        if (s.postRes.state == "success") {
+          s.postRes.data.community_view.blocked =
+            blockCommunityRes.data.blocked;
+        }
+      });
     }
   }
 
index eaedbbf817b4fada361b862b4c7d7c33c26b3083..47ca6501542b536b0a2eead58434f2c1f59eda4f 100644 (file)
@@ -1,4 +1,5 @@
 import i18next, { i18nTyped, Resource } from "i18next";
+import { UserService } from "./services";
 import { ar } from "./translations/ar";
 import { bg } from "./translations/bg";
 import { ca } from "./translations/ca";
@@ -30,7 +31,7 @@ import { sv } from "./translations/sv";
 import { vi } from "./translations/vi";
 import { zh } from "./translations/zh";
 import { zh_Hant } from "./translations/zh_Hant";
-import { getLanguages } from "./utils";
+import { isBrowser } from "./utils";
 
 export const languages = [
   { resource: ar, code: "ar", name: "العربية" },
@@ -73,12 +74,31 @@ function format(value: any, format: any): any {
   return format === "uppercase" ? value.toUpperCase() : value;
 }
 
-i18next.init({
+class LanguageDetector {
+  static readonly type = "languageDetector";
+
+  detect() {
+    const langs: string[] = [];
+
+    const myLang =
+      UserService.Instance.myUserInfo?.local_user_view.local_user
+        .interface_language ?? "browser";
+
+    if (myLang !== "browser") langs.push(myLang);
+
+    if (isBrowser()) langs.push(...navigator.languages);
+
+    return langs;
+  }
+}
+
+i18next.use(LanguageDetector).init({
   debug: false,
   compatibilityJSON: "v3",
+  supportedLngs: languages.map(l => l.code),
+  nonExplicitSupportedLngs: true,
   // load: 'languageOnly',
   // initImmediate: false,
-  lng: getLanguages()[0],
   fallbackLng: "en",
   resources,
   interpolation: { format },
index 97d05fca56933ffe9f9cef648c1f86d630a8eaba..7007a214ffdeb67a48338ae3b6ed2b5dae29ee38 100644 (file)
@@ -42,7 +42,7 @@ import moment from "moment";
 import tippy from "tippy.js";
 import Toastify from "toastify-js";
 import { getHttpBase } from "./env";
-import { i18n, languages } from "./i18next";
+import { i18n } from "./i18next";
 import {
   CommentNodeI,
   DataType,
@@ -323,6 +323,7 @@ export function amTopMod(
 
 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);
@@ -336,6 +337,10 @@ export function validURL(str: string) {
   return !!new URL(str);
 }
 
+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}`;
@@ -406,31 +411,6 @@ export function debounce<T extends any[], R>(
   } 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());
 }
@@ -1283,7 +1263,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 ?? []);
   }
index f783f07f84ce5007655e700c23fcd2c67c295de5..7cd6447438f39b8c4f0dcfbe67541aab44abf06d 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -5615,10 +5615,10 @@ leac@^0.6.0:
   resolved "https://registry.yarnpkg.com/leac/-/leac-0.6.0.tgz#dcf136e382e666bd2475f44a1096061b70dc0912"
   integrity sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==
 
-lemmy-js-client@0.17.2-rc.24:
-  version "0.17.2-rc.24"
-  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.2-rc.24.tgz#3b09233a6d89286e559be2e840d81c0c549562ad"
-  integrity sha512-aSHz7UTcwnwnNd9poY8tEXP7RA9ieZm9MAfSljcbCNU5ds9CASXYNodmraUVJiqCmT4HWnj7IeVmBC9r7nTHnw==
+lemmy-js-client@0.18.0-rc.1:
+  version "0.18.0-rc.1"
+  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.18.0-rc.1.tgz#fd0c88810572d90413696011ebaed19e3b8162d8"
+  integrity sha512-lQe443Nr5UCSoY+IxmT7mBe0IRF6EAZ/4PJSRoPSL+U8A+egMMBPbuxnisHzLsC+eDOWRUIgOqZlwlaRnbmuig==
   dependencies:
     cross-fetch "^3.1.5"
     form-data "^4.0.0"