]> Untitled Git - lemmy-ui.git/commitdiff
Merge branch 'main' into update-issue-template
authorAlec Armbruster <35377827+alectrocute@users.noreply.github.com>
Thu, 22 Jun 2023 22:07:09 +0000 (18:07 -0400)
committerGitHub <noreply@github.com>
Thu, 22 Jun 2023 22:07:09 +0000 (18:07 -0400)
.github/CODEOWNERS
src/shared/components/post/post-listing.tsx
src/shared/config.ts
src/shared/markdown.ts

index 76916e604aeda28ef62a8e3bbd4ab7f8d13a8e82..ee3d7a5420401eed580da61fa6ffd41ea9154255 100644 (file)
@@ -1 +1 @@
-* @dessalines @SleeplessOne1917 @alectrocute
+* @dessalines @SleeplessOne1917 @alectrocute @jsit
index 4d0951bb60ae4619da1347b0ead74fc2a7f71b32..d5ddc2f20e5f285d42d00bf23930120e7ac34b10 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 (
@@ -364,33 +381,30 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
   createdLine() {
     const post_view = this.postView;
     return (
-      <ul className="list-inline mb-1 text-muted small mt-2">
-        <li className="list-inline-item">
-          <PersonListing person={post_view.creator} />
-
-          {this.creatorIsMod_ && (
-            <span className="mx-1 badge text-bg-light">
-              {I18NextService.i18n.t("mod")}
-            </span>
-          )}
-          {this.creatorIsAdmin_ && (
-            <span className="mx-1 badge text-bg-light">
-              {I18NextService.i18n.t("admin")}
-            </span>
-          )}
-          {post_view.creator.bot_account && (
-            <span className="mx-1 badge text-bg-light">
-              {I18NextService.i18n.t("bot_account").toLowerCase()}
-            </span>
-          )}
-          {this.props.showCommunity && (
-            <>
-              {" "}
-              {I18NextService.i18n.t("to")}{" "}
-              <CommunityLink community={post_view.community} />
-            </>
-          )}
-        </li>
+      <span className="small">
+        <PersonListing person={post_view.creator} />
+        {this.creatorIsMod_ && (
+          <span className="mx-1 badge text-bg-light">
+            {I18NextService.i18n.t("mod")}
+          </span>
+        )}
+        {this.creatorIsAdmin_ && (
+          <span className="mx-1 badge text-bg-light">
+            {I18NextService.i18n.t("admin")}
+          </span>
+        )}
+        {post_view.creator.bot_account && (
+          <span className="mx-1 badge text-bg-light">
+            {I18NextService.i18n.t("bot_account").toLowerCase()}
+          </span>
+        )}
+        {this.props.showCommunity && (
+          <>
+            {" "}
+            {I18NextService.i18n.t("to")}{" "}
+            <CommunityLink community={post_view.community} />
+          </>
+        )}
         {post_view.post.language_id !== 0 && (
           <span className="mx-1 badge text-bg-light">
             {
@@ -399,17 +413,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
               )?.name
             }
           </span>
-        )}
-        <li className="list-inline-item">•</li>
-        <li className="list-inline-item">
-          <span>
-            <MomentTime
-              published={post_view.post.published}
-              updated={post_view.post.updated}
-            />
-          </span>
-        </li>
-      </ul>
+        )}{" "}
+        •{" "}
+        <MomentTime
+          published={post_view.post.published}
+          updated={post_view.post.updated}
+        />
+      </span>
     );
   }
 
@@ -750,10 +760,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
         to={`/post/${post_view.post.id}?scrollToComments=true`}
         data-tippy-content={title}
       >
-        <span className="me-1">
-          <Icon icon="message-square" classes="me-1" inline />
-          {post_view.counts.comments}
-        </span>
+        <Icon icon="message-square" classes="me-1" inline />
+        {post_view.counts.comments}
         {this.unreadCount && (
           <span className="text-muted fst-italic">
             ({this.unreadCount} {I18NextService.i18n.t("new")})
@@ -1087,7 +1095,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
     const post_view = this.postView;
     return (
       this.state.showAdvanced && (
-        <>
+        <div className="mt-3">
           {this.canMod_ && (
             <>
               {!this.creatorIsMod_ &&
@@ -1248,7 +1256,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
               )}
             </>
           )}
-        </>
+        </div>
       )
     );
   }
@@ -1443,11 +1451,11 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
     );
   }
 
-  showMobilePreview() {
+  showBodyPreview() {
     const { body, id } = this.postView.post;
 
     return !this.showBody && body ? (
-      <Link className="text-body" to={`/post/${id}`}>
+      <Link className="text-body mt-2 d-block" to={`/post/${id}`}>
         <div className="md-div mb-1 preview-lines">{body}</div>
       </Link>
     ) : (
@@ -1468,7 +1476,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
               {this.mobileThumbnail()}
 
               {/* Show a preview of the post body */}
-              {this.showMobilePreview()}
+              {this.showBodyPreview()}
 
               {this.commentsLine(true)}
               {this.userActionsLine()}
@@ -1490,6 +1498,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                 <div className="col-12">
                   {this.postTitleLine()}
                   {this.createdLine()}
+                  {this.showBodyPreview()}
                   {this.commentsLine()}
                   {this.duplicatesLine()}
                   {this.userActionsLine()}
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,
     // })