]> Untitled Git - lemmy-ui.git/commitdiff
Merge remote-tracking branch 'origin/main' into feat/vote-components
authorJay Sitter <jay@jaysitter.com>
Thu, 22 Jun 2023 20:56:08 +0000 (16:56 -0400)
committerJay Sitter <jay@jaysitter.com>
Thu, 22 Jun 2023 20:56:08 +0000 (16:56 -0400)
* origin/main: (26 commits)
  Adding jsit to codeowners.
  Cleanup, only check for /u/ if /c/ and /m/ checks fail
  Rename function to be more generic, since it parses users
  Typescript linter fixes
  bandaid fix our video embeds
  Remove pipe from community link regex
  Add missing classes
  Use shorter regex in community link parser
  Move regex pattern to config
  Update community link markdown parsing
  Fix avatar alignment issue (#1475)
  Omit user-scalable to use default
  Update getHttpBase dependency reference
  Enable users to zoom on mobile
  rethink it a bit
  rethink it a bit
  add fallback style tag
  Add community link class
  prettier
  Add local community link parser plugin for Markdown-It
  ...

12 files changed:
.github/CODEOWNERS
src/server/utils/create-ssr-html.tsx
src/shared/components/common/pictrs-image.tsx
src/shared/components/common/sort-select.tsx
src/shared/components/community/community.tsx
src/shared/components/home/home.tsx
src/shared/components/post/post-listing.tsx
src/shared/config.ts
src/shared/markdown.ts
src/shared/utils/app/convert-comment-sort-type.ts
src/shared/utils/browser/restore-scroll-position.ts
src/shared/utils/browser/save-scroll-position.ts

index 76916e604aeda28ef62a8e3bbd4ab7f8d13a8e82..ee3d7a5420401eed580da61fa6ffd41ea9154255 100644 (file)
@@ -1 +1 @@
-* @dessalines @SleeplessOne1917 @alectrocute
+* @dessalines @SleeplessOne1917 @alectrocute @jsit
index ae766b3a0e73294f6e019875ab6056575903a16e..13775981b2972d97ff1561b264bbd0093b6d7ee2 100644 (file)
@@ -4,6 +4,7 @@ import serialize from "serialize-javascript";
 import sharp from "sharp";
 import { favIconPngUrl, favIconUrl } from "../../shared/config";
 import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces";
+import { buildThemeList } from "./build-themes-list";
 import { fetchIconPng } from "./fetch-icon-png";
 
 const customHtmlHeader = process.env["LEMMY_UI_CUSTOM_HTML_HEADER"] || "";
@@ -16,6 +17,10 @@ export async function createSsrHtml(
 ) {
   const site = isoData.site_res;
 
+  const fallbackTheme = `<link rel="stylesheet" type="text/css" href="/css/themes/${
+    (await buildThemeList())[0]
+  }.css" />`;
+
   if (!appleTouchIcon) {
     appleTouchIcon = site?.site_view.site.icon
       ? `data:image/png;base64,${sharp(
@@ -68,7 +73,7 @@ export async function createSsrHtml(
     <!-- Required meta tags -->
     <meta name="Description" content="Lemmy">
     <meta charset="utf-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
     <link
        id="favicon"
        rel="shortcut icon"
@@ -85,7 +90,7 @@ export async function createSsrHtml(
     <link rel="stylesheet" type="text/css" href="/static/styles/styles.css" />
   
     <!-- Current theme and more -->
-    ${helmet.link.toString()}
+    ${helmet.link.toString() || fallbackTheme}
     
     </head>
   
index 7443749094970cd427bf9ea8cc30a07349d413cc..31fb12299663f27ec9df5d43dec9cf0966783d1e 100644 (file)
@@ -22,7 +22,7 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
 
   render() {
     return (
-      <picture className="pictrs-image d-inline-block overflow-hidden">
+      <picture>
         <source srcSet={this.src("webp")} type="image/webp" />
         <source srcSet={this.props.src} />
         <source srcSet={this.src("jpg")} type="image/jpeg" />
@@ -31,7 +31,7 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
           alt={this.alt()}
           title={this.alt()}
           loading="lazy"
-          className={classNames({
+          className={classNames("overflow-hidden pictrs-image", {
             "img-fluid": !this.props.icon && !this.props.iconOverlay,
             banner: this.props.banner,
             "thumbnail rounded":
index 90515d5648992452114a0af158eb5d70ecdbfc5d..4d03ab5d662fcfade85a738f5a67867a43f68f03 100644 (file)
@@ -67,6 +67,13 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
           <option disabled aria-hidden="true">
             ─────
           </option>
+          <option value={"TopHour"}>{I18NextService.i18n.t("top_hour")}</option>
+          <option value={"TopSixHour"}>
+            {I18NextService.i18n.t("top_six_hours")}
+          </option>
+          <option value={"TopTwelveHour"}>
+            {I18NextService.i18n.t("top_twelve_hours")}
+          </option>
           <option value={"TopDay"}>{I18NextService.i18n.t("top_day")}</option>
           <option value={"TopWeek"}>{I18NextService.i18n.t("top_week")}</option>
           <option value={"TopMonth"}>
index 7eefe0eb96ea0c8a2932e5b9f94d0bef371adf1f..111b47cd9d4747be7da88e94fa729413096456ec 100644 (file)
@@ -15,7 +15,6 @@ import {
   updateCommunityBlock,
   updatePersonBlock,
 } from "@utils/app";
-import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
 import {
   getPageFromString,
   getQueryParams,
@@ -229,10 +228,6 @@ export class Community extends Component<
     setupTippy();
   }
 
-  componentWillUnmount() {
-    saveScrollPosition(this.context);
-  }
-
   static async fetchInitialData({
     client,
     path,
@@ -609,7 +604,6 @@ export class Community extends Component<
       });
     }
 
-    restoreScrollPosition(this.context);
     setupTippy();
   }
 
index 0d91bdbcc28815b0aaa427a2bc44c64e8ed33dff..4a84664bf08d2c0961e409dcc5c04ba443a02ac0 100644 (file)
@@ -13,7 +13,6 @@ import {
   showLocal,
   updatePersonBlock,
 } from "@utils/app";
-import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
 import {
   getPageFromString,
   getQueryParams,
@@ -293,10 +292,6 @@ export class Home extends Component<any, HomeState> {
     setupTippy();
   }
 
-  componentWillUnmount() {
-    saveScrollPosition(this.context);
-  }
-
   static async fetchInitialData({
     client,
     auth,
@@ -800,7 +795,6 @@ export class Home extends Component<any, HomeState> {
       });
     }
 
-    restoreScrollPosition(this.context);
     setupTippy();
   }
 
index 7eed489de5eb424e976d0bf3631303dc33dea008..a913f534069ade1cde7edc50cc306329a2ab5e49 100644 (file)
@@ -239,25 +239,40 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
   }
 
   get img() {
-    return this.imageSrc ? (
-      <>
-        <div className="offset-sm-3 my-2 d-none d-sm-block">
-          <a href={this.imageSrc} className="d-inline-block">
-            <PictrsImage src={this.imageSrc} />
-          </a>
-        </div>
-        <div className="my-2 d-block d-sm-none">
-          <a
-            className="d-inline-block"
-            onClick={linkEvent(this, this.handleImageExpandClick)}
-          >
-            <PictrsImage src={this.imageSrc} />
-          </a>
+    if (this.imageSrc) {
+      return (
+        <>
+          <div className="offset-sm-3 my-2 d-none d-sm-block">
+            <a href={this.imageSrc} className="d-inline-block">
+              <PictrsImage src={this.imageSrc} />
+            </a>
+          </div>
+          <div className="my-2 d-block d-sm-none">
+            <a
+              className="d-inline-block"
+              onClick={linkEvent(this, this.handleImageExpandClick)}
+            >
+              <PictrsImage src={this.imageSrc} />
+            </a>
+          </div>
+        </>
+      );
+    }
+
+    const { post } = this.postView;
+    const { url } = post;
+
+    if (url && isVideo(url)) {
+      return (
+        <div className="embed-responsive mt-3">
+          <video muted controls className="embed-responsive-item col-12">
+            <source src={url} type="video/mp4" />
+          </video>
         </div>
-      </>
-    ) : (
-      <></>
-    );
+      );
+    }
+
+    return <></>;
   }
 
   imgThumb(src: string) {
@@ -325,17 +340,19 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
     } else if (url) {
       if (!this.props.hideImage && isVideo(url)) {
         return (
-          <div className="embed-responsive embed-responsive-16by9">
-            <video
-              playsInline
-              muted
-              loop
-              controls
-              className="embed-responsive-item"
-            >
-              <source src={url} type="video/mp4" />
-            </video>
-          </div>
+          <a
+            className="text-body"
+            href={url}
+            title={url}
+            rel={relTags}
+            data-tippy-content={I18NextService.i18n.t("expand_here")}
+            onClick={linkEvent(this, this.handleImageExpandClick)}
+            aria-label={I18NextService.i18n.t("expand_here")}
+          >
+            <div className="thumbnail rounded bg-light d-flex justify-content-center">
+              <Icon icon="play" classes="d-flex align-items-center" />
+            </div>
+          </a>
         );
       } else {
         return (
index 28e8ce5119a5046125e62c4f9b0e98f088926188..c56c64b0c2c3f1afce182f3598d1afebf432f1c5 100644 (file)
@@ -25,4 +25,14 @@ export const fetchLimit = 40;
 export const relTags = "noopener nofollow";
 export const emDash = "\u2014";
 
+/**
+ * Accepted formats:
+ * !community@server.com
+ * /c/community@server.com
+ * /m/community@server.com
+ * /u/username@server.com
+ */
+export const instanceLinkRegex =
+  /(\/[cmu]\/|!)[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
+
 export const testHost = "0.0.0.0:8536";
index 8f4d5c238ac5bf8a71015ece3520deedc50ac399..9f1ec733f74c52236f4b32cb03f24193ebf55009 100644 (file)
@@ -14,6 +14,7 @@ import markdown_it_sub from "markdown-it-sub";
 import markdown_it_sup from "markdown-it-sup";
 import Renderer from "markdown-it/lib/renderer";
 import Token from "markdown-it/lib/token";
+import { instanceLinkRegex } from "./config";
 
 export let Tribute: any;
 
@@ -72,6 +73,75 @@ const html5EmbedConfig = {
   },
 };
 
+function localInstanceLinkParser(md: MarkdownIt) {
+  md.core.ruler.push("replace-text", state => {
+    for (let i = 0; i < state.tokens.length; i++) {
+      if (state.tokens[i].type !== "inline") {
+        continue;
+      }
+      const inlineTokens: Token[] = state.tokens[i].children || [];
+      for (let j = inlineTokens.length - 1; j >= 0; j--) {
+        if (
+          inlineTokens[j].type === "text" &&
+          new RegExp(instanceLinkRegex).test(inlineTokens[j].content)
+        ) {
+          const text = inlineTokens[j].content;
+          const matches = Array.from(text.matchAll(instanceLinkRegex));
+
+          let lastIndex = 0;
+          const newTokens: Token[] = [];
+
+          let linkClass = "community-link";
+
+          for (const match of matches) {
+            // If there is plain text before the match, add it as a separate token
+            if (match.index !== undefined && match.index > lastIndex) {
+              const textToken = new state.Token("text", "", 0);
+              textToken.content = text.slice(lastIndex, match.index);
+              newTokens.push(textToken);
+            }
+
+            let href;
+            if (match[0].startsWith("!")) {
+              href = "/c/" + match[0].substring(1);
+            } else if (match[0].startsWith("/m/")) {
+              href = "/c/" + match[0].substring(3);
+            } else {
+              href = match[0];
+              if (match[0].startsWith("/u/")) {
+                linkClass = "user-link";
+              }
+            }
+
+            const linkOpenToken = new state.Token("link_open", "a", 1);
+            linkOpenToken.attrs = [
+              ["href", href],
+              ["class", linkClass],
+            ];
+            const textToken = new state.Token("text", "", 0);
+            textToken.content = match[0];
+            const linkCloseToken = new state.Token("link_close", "a", -1);
+
+            newTokens.push(linkOpenToken, textToken, linkCloseToken);
+
+            lastIndex =
+              (match.index !== undefined ? match.index : 0) + match[0].length;
+          }
+
+          // If there is plain text after the last match, add it as a separate token
+          if (lastIndex < text.length) {
+            const textToken = new state.Token("text", "", 0);
+            textToken.content = text.slice(lastIndex);
+            newTokens.push(textToken);
+          }
+
+          inlineTokens.splice(j, 1, ...newTokens);
+        }
+      }
+    }
+  });
+}
+
 export function setupMarkdown() {
   const markdownItConfig: MarkdownIt.Options = {
     html: false,
@@ -88,7 +158,8 @@ export function setupMarkdown() {
     .use(markdown_it_sup)
     .use(markdown_it_footnote)
     .use(markdown_it_html5_embed, html5EmbedConfig)
-    .use(markdown_it_container, "spoiler", spoilerConfig);
+    .use(markdown_it_container, "spoiler", spoilerConfig)
+    .use(localInstanceLinkParser);
   // .use(markdown_it_emoji, {
   //   defs: emojiDefs,
   // });
@@ -99,6 +170,7 @@ export function setupMarkdown() {
     .use(markdown_it_footnote)
     .use(markdown_it_html5_embed, html5EmbedConfig)
     .use(markdown_it_container, "spoiler", spoilerConfig)
+    .use(localInstanceLinkParser)
     // .use(markdown_it_emoji, {
     //   defs: emojiDefs,
     // })
index 3a89a23c774a7c4fd34d9f175cf93add02da45b9..665ab46269aa99451b2a0e823829f8106a206f25 100644 (file)
@@ -5,6 +5,9 @@ export default function convertCommentSortType(
 ): CommentSortType {
   switch (sort) {
     case "TopAll":
+    case "TopHour":
+    case "TopSixHour":
+    case "TopTwelveHour":
     case "TopDay":
     case "TopWeek":
     case "TopMonth":
index f1534644d9c24a0cfc78ef9e2468d0068576c06f..6986ae597f1a6086784010e71beee7dc8ad01b44 100644 (file)
@@ -1,5 +1,6 @@
 export default function restoreScrollPosition(context: any) {
   const path: string = context.router.route.location.pathname;
   const y = Number(sessionStorage.getItem(`scrollPosition_${path}`));
+
   window.scrollTo(0, y);
 }
index 48353287dccfb4c1a7928ffb47713b9e3282590e..596d5d9f529eb0f378b2a8cbc9897990f0266eb4 100644 (file)
@@ -1,5 +1,6 @@
 export default function saveScrollPosition(context: any) {
   const path: string = context.router.route.location.pathname;
   const y = window.scrollY;
+
   sessionStorage.setItem(`scrollPosition_${path}`, y.toString());
 }