mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-22 06:36:17 +00:00
Merge branch 'main' into update-issue-template
This commit is contained in:
commit
11da1152ee
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -1 +1 @@
|
||||||
* @dessalines @SleeplessOne1917 @alectrocute
|
* @dessalines @SleeplessOne1917 @alectrocute @jsit
|
||||||
|
|
|
@ -239,25 +239,40 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
get img() {
|
get img() {
|
||||||
return this.imageSrc ? (
|
if (this.imageSrc) {
|
||||||
<>
|
return (
|
||||||
<div className="offset-sm-3 my-2 d-none d-sm-block">
|
<>
|
||||||
<a href={this.imageSrc} className="d-inline-block">
|
<div className="offset-sm-3 my-2 d-none d-sm-block">
|
||||||
<PictrsImage src={this.imageSrc} />
|
<a href={this.imageSrc} className="d-inline-block">
|
||||||
</a>
|
<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>
|
</div>
|
||||||
<div className="my-2 d-block d-sm-none">
|
);
|
||||||
<a
|
}
|
||||||
className="d-inline-block"
|
|
||||||
onClick={linkEvent(this, this.handleImageExpandClick)}
|
return <></>;
|
||||||
>
|
|
||||||
<PictrsImage src={this.imageSrc} />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
imgThumb(src: string) {
|
imgThumb(src: string) {
|
||||||
|
@ -325,17 +340,19 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
} else if (url) {
|
} else if (url) {
|
||||||
if (!this.props.hideImage && isVideo(url)) {
|
if (!this.props.hideImage && isVideo(url)) {
|
||||||
return (
|
return (
|
||||||
<div className="embed-responsive embed-responsive-16by9">
|
<a
|
||||||
<video
|
className="text-body"
|
||||||
playsInline
|
href={url}
|
||||||
muted
|
title={url}
|
||||||
loop
|
rel={relTags}
|
||||||
controls
|
data-tippy-content={I18NextService.i18n.t("expand_here")}
|
||||||
className="embed-responsive-item"
|
onClick={linkEvent(this, this.handleImageExpandClick)}
|
||||||
>
|
aria-label={I18NextService.i18n.t("expand_here")}
|
||||||
<source src={url} type="video/mp4" />
|
>
|
||||||
</video>
|
<div className="thumbnail rounded bg-light d-flex justify-content-center">
|
||||||
</div>
|
<Icon icon="play" classes="d-flex align-items-center" />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
|
@ -364,33 +381,30 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
createdLine() {
|
createdLine() {
|
||||||
const post_view = this.postView;
|
const post_view = this.postView;
|
||||||
return (
|
return (
|
||||||
<ul className="list-inline mb-1 text-muted small mt-2">
|
<span className="small">
|
||||||
<li className="list-inline-item">
|
<PersonListing person={post_view.creator} />
|
||||||
<PersonListing person={post_view.creator} />
|
{this.creatorIsMod_ && (
|
||||||
|
<span className="mx-1 badge text-bg-light">
|
||||||
{this.creatorIsMod_ && (
|
{I18NextService.i18n.t("mod")}
|
||||||
<span className="mx-1 badge text-bg-light">
|
</span>
|
||||||
{I18NextService.i18n.t("mod")}
|
)}
|
||||||
</span>
|
{this.creatorIsAdmin_ && (
|
||||||
)}
|
<span className="mx-1 badge text-bg-light">
|
||||||
{this.creatorIsAdmin_ && (
|
{I18NextService.i18n.t("admin")}
|
||||||
<span className="mx-1 badge text-bg-light">
|
</span>
|
||||||
{I18NextService.i18n.t("admin")}
|
)}
|
||||||
</span>
|
{post_view.creator.bot_account && (
|
||||||
)}
|
<span className="mx-1 badge text-bg-light">
|
||||||
{post_view.creator.bot_account && (
|
{I18NextService.i18n.t("bot_account").toLowerCase()}
|
||||||
<span className="mx-1 badge text-bg-light">
|
</span>
|
||||||
{I18NextService.i18n.t("bot_account").toLowerCase()}
|
)}
|
||||||
</span>
|
{this.props.showCommunity && (
|
||||||
)}
|
<>
|
||||||
{this.props.showCommunity && (
|
{" "}
|
||||||
<>
|
{I18NextService.i18n.t("to")}{" "}
|
||||||
{" "}
|
<CommunityLink community={post_view.community} />
|
||||||
{I18NextService.i18n.t("to")}{" "}
|
</>
|
||||||
<CommunityLink community={post_view.community} />
|
)}
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
{post_view.post.language_id !== 0 && (
|
{post_view.post.language_id !== 0 && (
|
||||||
<span className="mx-1 badge text-bg-light">
|
<span className="mx-1 badge text-bg-light">
|
||||||
{
|
{
|
||||||
|
@ -399,17 +413,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
)?.name
|
)?.name
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}{" "}
|
||||||
<li className="list-inline-item">•</li>
|
•{" "}
|
||||||
<li className="list-inline-item">
|
<MomentTime
|
||||||
<span>
|
published={post_view.post.published}
|
||||||
<MomentTime
|
updated={post_view.post.updated}
|
||||||
published={post_view.post.published}
|
/>
|
||||||
updated={post_view.post.updated}
|
</span>
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -750,10 +760,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
to={`/post/${post_view.post.id}?scrollToComments=true`}
|
to={`/post/${post_view.post.id}?scrollToComments=true`}
|
||||||
data-tippy-content={title}
|
data-tippy-content={title}
|
||||||
>
|
>
|
||||||
<span className="me-1">
|
<Icon icon="message-square" classes="me-1" inline />
|
||||||
<Icon icon="message-square" classes="me-1" inline />
|
{post_view.counts.comments}
|
||||||
{post_view.counts.comments}
|
|
||||||
</span>
|
|
||||||
{this.unreadCount && (
|
{this.unreadCount && (
|
||||||
<span className="text-muted fst-italic">
|
<span className="text-muted fst-italic">
|
||||||
({this.unreadCount} {I18NextService.i18n.t("new")})
|
({this.unreadCount} {I18NextService.i18n.t("new")})
|
||||||
|
@ -1087,7 +1095,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
const post_view = this.postView;
|
const post_view = this.postView;
|
||||||
return (
|
return (
|
||||||
this.state.showAdvanced && (
|
this.state.showAdvanced && (
|
||||||
<>
|
<div className="mt-3">
|
||||||
{this.canMod_ && (
|
{this.canMod_ && (
|
||||||
<>
|
<>
|
||||||
{!this.creatorIsMod_ &&
|
{!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;
|
const { body, id } = this.postView.post;
|
||||||
|
|
||||||
return !this.showBody && body ? (
|
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>
|
<div className="md-div mb-1 preview-lines">{body}</div>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
|
@ -1468,7 +1476,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
{this.mobileThumbnail()}
|
{this.mobileThumbnail()}
|
||||||
|
|
||||||
{/* Show a preview of the post body */}
|
{/* Show a preview of the post body */}
|
||||||
{this.showMobilePreview()}
|
{this.showBodyPreview()}
|
||||||
|
|
||||||
{this.commentsLine(true)}
|
{this.commentsLine(true)}
|
||||||
{this.userActionsLine()}
|
{this.userActionsLine()}
|
||||||
|
@ -1490,6 +1498,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
{this.postTitleLine()}
|
{this.postTitleLine()}
|
||||||
{this.createdLine()}
|
{this.createdLine()}
|
||||||
|
{this.showBodyPreview()}
|
||||||
{this.commentsLine()}
|
{this.commentsLine()}
|
||||||
{this.duplicatesLine()}
|
{this.duplicatesLine()}
|
||||||
{this.userActionsLine()}
|
{this.userActionsLine()}
|
||||||
|
|
|
@ -25,4 +25,14 @@ export const fetchLimit = 40;
|
||||||
export const relTags = "noopener nofollow";
|
export const relTags = "noopener nofollow";
|
||||||
export const emDash = "\u2014";
|
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";
|
export const testHost = "0.0.0.0:8536";
|
||||||
|
|
|
@ -14,6 +14,7 @@ import markdown_it_sub from "markdown-it-sub";
|
||||||
import markdown_it_sup from "markdown-it-sup";
|
import markdown_it_sup from "markdown-it-sup";
|
||||||
import Renderer from "markdown-it/lib/renderer";
|
import Renderer from "markdown-it/lib/renderer";
|
||||||
import Token from "markdown-it/lib/token";
|
import Token from "markdown-it/lib/token";
|
||||||
|
import { instanceLinkRegex } from "./config";
|
||||||
|
|
||||||
export let Tribute: any;
|
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() {
|
export function setupMarkdown() {
|
||||||
const markdownItConfig: MarkdownIt.Options = {
|
const markdownItConfig: MarkdownIt.Options = {
|
||||||
html: false,
|
html: false,
|
||||||
|
@ -88,7 +158,8 @@ export function setupMarkdown() {
|
||||||
.use(markdown_it_sup)
|
.use(markdown_it_sup)
|
||||||
.use(markdown_it_footnote)
|
.use(markdown_it_footnote)
|
||||||
.use(markdown_it_html5_embed, html5EmbedConfig)
|
.use(markdown_it_html5_embed, html5EmbedConfig)
|
||||||
.use(markdown_it_container, "spoiler", spoilerConfig);
|
.use(markdown_it_container, "spoiler", spoilerConfig)
|
||||||
|
.use(localInstanceLinkParser);
|
||||||
// .use(markdown_it_emoji, {
|
// .use(markdown_it_emoji, {
|
||||||
// defs: emojiDefs,
|
// defs: emojiDefs,
|
||||||
// });
|
// });
|
||||||
|
@ -99,6 +170,7 @@ export function setupMarkdown() {
|
||||||
.use(markdown_it_footnote)
|
.use(markdown_it_footnote)
|
||||||
.use(markdown_it_html5_embed, html5EmbedConfig)
|
.use(markdown_it_html5_embed, html5EmbedConfig)
|
||||||
.use(markdown_it_container, "spoiler", spoilerConfig)
|
.use(markdown_it_container, "spoiler", spoilerConfig)
|
||||||
|
.use(localInstanceLinkParser)
|
||||||
// .use(markdown_it_emoji, {
|
// .use(markdown_it_emoji, {
|
||||||
// defs: emojiDefs,
|
// defs: emojiDefs,
|
||||||
// })
|
// })
|
||||||
|
|
Loading…
Reference in a new issue