diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 76916e60..ee3d7a54 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @dessalines @SleeplessOne1917 @alectrocute +* @dessalines @SleeplessOne1917 @alectrocute @jsit diff --git a/src/shared/components/post/post-listing.tsx b/src/shared/components/post/post-listing.tsx index 4d0951bb..d5ddc2f2 100644 --- a/src/shared/components/post/post-listing.tsx +++ b/src/shared/components/post/post-listing.tsx @@ -239,25 +239,40 @@ export class PostListing extends Component { } get img() { - return this.imageSrc ? ( - <> -
- - - + if (this.imageSrc) { + return ( + <> +
+ + + +
+
+ + + +
+ + ); + } + + const { post } = this.postView; + const { url } = post; + + if (url && isVideo(url)) { + return ( +
+
-
- - - -
- - ) : ( - <> - ); + ); + } + + return <>; } imgThumb(src: string) { @@ -325,17 +340,19 @@ export class PostListing extends Component { } else if (url) { if (!this.props.hideImage && isVideo(url)) { return ( -
- -
+ +
+ +
+
); } else { return ( @@ -364,33 +381,30 @@ export class PostListing extends Component { createdLine() { const post_view = this.postView; return ( -
    -
  • - - - {this.creatorIsMod_ && ( - - {I18NextService.i18n.t("mod")} - - )} - {this.creatorIsAdmin_ && ( - - {I18NextService.i18n.t("admin")} - - )} - {post_view.creator.bot_account && ( - - {I18NextService.i18n.t("bot_account").toLowerCase()} - - )} - {this.props.showCommunity && ( - <> - {" "} - {I18NextService.i18n.t("to")}{" "} - - - )} -
  • + + + {this.creatorIsMod_ && ( + + {I18NextService.i18n.t("mod")} + + )} + {this.creatorIsAdmin_ && ( + + {I18NextService.i18n.t("admin")} + + )} + {post_view.creator.bot_account && ( + + {I18NextService.i18n.t("bot_account").toLowerCase()} + + )} + {this.props.showCommunity && ( + <> + {" "} + {I18NextService.i18n.t("to")}{" "} + + + )} {post_view.post.language_id !== 0 && ( { @@ -399,17 +413,13 @@ export class PostListing extends Component { )?.name } - )} -
  • -
  • - - - -
  • -
+ )}{" "} + •{" "} + + ); } @@ -750,10 +760,8 @@ export class PostListing extends Component { to={`/post/${post_view.post.id}?scrollToComments=true`} data-tippy-content={title} > - - - {post_view.counts.comments} - + + {post_view.counts.comments} {this.unreadCount && ( ({this.unreadCount} {I18NextService.i18n.t("new")}) @@ -1087,7 +1095,7 @@ export class PostListing extends Component { const post_view = this.postView; return ( this.state.showAdvanced && ( - <> +
{this.canMod_ && ( <> {!this.creatorIsMod_ && @@ -1248,7 +1256,7 @@ export class PostListing extends Component { )} )} - +
) ); } @@ -1443,11 +1451,11 @@ export class PostListing extends Component { ); } - showMobilePreview() { + showBodyPreview() { const { body, id } = this.postView.post; return !this.showBody && body ? ( - +
{body}
) : ( @@ -1468,7 +1476,7 @@ export class PostListing extends Component { {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 {
{this.postTitleLine()} {this.createdLine()} + {this.showBodyPreview()} {this.commentsLine()} {this.duplicatesLine()} {this.userActionsLine()} diff --git a/src/shared/config.ts b/src/shared/config.ts index 28e8ce51..c56c64b0 100644 --- a/src/shared/config.ts +++ b/src/shared/config.ts @@ -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"; diff --git a/src/shared/markdown.ts b/src/shared/markdown.ts index 8f4d5c23..9f1ec733 100644 --- a/src/shared/markdown.ts +++ b/src/shared/markdown.ts @@ -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, // })