Merge branch 'main' into route-data-refactor

This commit is contained in:
abias 2023-06-15 22:11:19 -04:00
commit 88842a52c0
11 changed files with 143 additions and 125 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "lemmy-ui", "name": "lemmy-ui",
"version": "0.18.0-beta.6", "version": "0.18.0-rc.1",
"description": "An isomorphic UI for lemmy", "description": "An isomorphic UI for lemmy",
"repository": "https://github.com/LemmyNet/lemmy-ui", "repository": "https://github.com/LemmyNet/lemmy-ui",
"license": "AGPL-3.0", "license": "AGPL-3.0",

View file

@ -80,30 +80,6 @@
overflow-x: auto; overflow-x: auto;
} }
.md-div table {
border-collapse: collapse;
width: 100%;
margin-bottom: 1rem;
border: 1px solid var(--dark);
}
.md-div table th,
.md-div table td {
padding: 0.3rem;
vertical-align: top;
border-top: 1px solid var(--dark);
border: 1px solid var(--dark);
}
.md-div table thead th {
vertical-align: bottom;
border-bottom: 2px solid var(--dark);
}
.md-div table tbody + tbody {
border-top: 2px solid var(--dark);
}
.vote-bar { .vote-bar {
margin-top: -6.5px; margin-top: -6.5px;
} }
@ -218,6 +194,11 @@ blockquote {
overflow-y: auto; overflow-y: auto;
} }
.comments {
list-style: none;
padding: 0;
}
.thumbnail { .thumbnail {
object-fit: cover; object-fit: cover;
min-height: 60px; min-height: 60px;

View file

@ -160,7 +160,7 @@ server.get("/*", async (req, res) => {
site = try_site.data; site = try_site.data;
initializeSite(site); initializeSite(site);
if (path != "/setup" && !site.site_view.local_site.site_setup) { if (path !== "/setup" && !site.site_view.local_site.site_setup) {
return res.redirect("/setup"); return res.redirect("/setup");
} }
@ -434,7 +434,7 @@ async function createSsrHtml(root: string, isoData: IsoDataOptionalSite) {
<!-- Required meta tags --> <!-- Required meta tags -->
<meta name="Description" content="Lemmy"> <meta name="Description" content="Lemmy">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no">
<link <link
id="favicon" id="favicon"
rel="shortcut icon" rel="shortcut icon"

View file

@ -16,8 +16,10 @@ import {
isBrowser, isBrowser,
myAuth, myAuth,
numToSI, numToSI,
poll,
showAvatars, showAvatars,
toast, toast,
updateUnreadCountsInterval,
} from "../../utils"; } from "../../utils";
import { Icon } from "../common/icon"; import { Icon } from "../common/icon";
import { PictrsImage } from "../common/pictrs-image"; import { PictrsImage } from "../common/pictrs-image";
@ -64,7 +66,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
if (isBrowser()) { if (isBrowser()) {
// On the first load, check the unreads // On the first load, check the unreads
this.requestNotificationPermission(); this.requestNotificationPermission();
await this.fetchUnreads(); this.fetchUnreads();
this.requestNotificationPermission(); this.requestNotificationPermission();
document.addEventListener("mouseup", this.handleOutsideMenuClick); document.addEventListener("mouseup", this.handleOutsideMenuClick);
@ -406,35 +408,36 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
return amAdmin() || moderatesS; return amAdmin() || moderatesS;
} }
async fetchUnreads() { fetchUnreads() {
const auth = myAuth(); poll(async () => {
if (auth) { if (window.document.visibilityState !== "hidden") {
this.setState({ unreadInboxCountRes: { state: "loading" } }); const auth = myAuth();
this.setState({ if (auth) {
unreadInboxCountRes: await HttpService.client.getUnreadCount({ this.setState({
auth, unreadInboxCountRes: await HttpService.client.getUnreadCount({
}),
});
if (this.moderatesSomething) {
this.setState({ unreadReportCountRes: { state: "loading" } });
this.setState({
unreadReportCountRes: await HttpService.client.getReportCount({
auth,
}),
});
}
if (amAdmin()) {
this.setState({ unreadApplicationCountRes: { state: "loading" } });
this.setState({
unreadApplicationCountRes:
await HttpService.client.getUnreadRegistrationApplicationCount({
auth, auth,
}), }),
}); });
if (this.moderatesSomething) {
this.setState({
unreadReportCountRes: await HttpService.client.getReportCount({
auth,
}),
});
}
if (amAdmin()) {
this.setState({
unreadApplicationCountRes:
await HttpService.client.getUnreadRegistrationApplicationCount({
auth,
}),
});
}
}
} }
} }, updateUnreadCountsInterval);
} }
get unreadInboxCount(): number { get unreadInboxCount(): number {

View file

@ -270,9 +270,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.props.moderators this.props.moderators
); );
const borderColor = this.props.node.depth
? colorList[(this.props.node.depth - 1) % colorList.length]
: colorList[0];
const moreRepliesBorderColor = this.props.node.depth const moreRepliesBorderColor = this.props.node.depth
? colorList[this.props.node.depth % colorList.length] ? colorList[this.props.node.depth % colorList.length]
: colorList[0]; : colorList[0];
@ -284,26 +281,17 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
node.comment_view.counts.child_count > 0; node.comment_view.counts.child_count > 0;
return ( return (
<div <li className="comment" role="comment">
className={`comment ${
this.props.node.depth && !this.props.noIndent ? "ml-1" : ""
}`}
>
<div <div
id={`comment-${cv.comment.id}`} id={`comment-${cv.comment.id}`}
className={classNames(`details comment-node py-2`, { className={classNames(`details comment-node py-2`, {
"border-top border-light": !this.props.noBorder, "border-top border-light": !this.props.noBorder,
mark: this.isCommentNew || this.commentView.comment.distinguished, mark: this.isCommentNew || this.commentView.comment.distinguished,
})} })}
style={
!this.props.noIndent && this.props.node.depth
? `border-left: 2px ${borderColor} solid !important`
: ""
}
> >
<div <div
className={classNames({ className={classNames({
"ml-2": !this.props.noIndent && this.props.node.depth, "ml-2": !this.props.noIndent,
})} })}
> >
<div className="d-flex flex-wrap align-items-center text-muted small"> <div className="d-flex flex-wrap align-items-center text-muted small">
@ -959,9 +947,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
</div> </div>
{showMoreChildren && ( {showMoreChildren && (
<div <div
className={`details ml-1 comment-node py-2 ${ className={classNames("details ml-1 comment-node py-2", {
!this.props.noBorder ? "border-top border-light" : "" "border-top border-light": !this.props.noBorder,
}`} })}
style={`border-left: 2px ${moreRepliesBorderColor} solid !important`} style={`border-left: 2px ${moreRepliesBorderColor} solid !important`}
> >
<button <button
@ -1169,6 +1157,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
allLanguages={this.props.allLanguages} allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages} siteLanguages={this.props.siteLanguages}
hideImages={this.props.hideImages} hideImages={this.props.hideImages}
isChild={!this.props.noIndent}
depth={this.props.node.depth + 1}
finished={this.props.finished} finished={this.props.finished}
onCommentReplyRead={this.props.onCommentReplyRead} onCommentReplyRead={this.props.onCommentReplyRead}
onPersonMentionRead={this.props.onPersonMentionRead} onPersonMentionRead={this.props.onPersonMentionRead}
@ -1192,8 +1182,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
/> />
)} )}
{/* A collapsed clearfix */} {/* A collapsed clearfix */}
{this.state.collapsed && <div className="row col-12"></div>} {this.state.collapsed && <div className="row col-12" />}
</div> </li>
); );
} }
@ -1211,6 +1201,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
linkBtn(small = false) { linkBtn(small = false) {
const cv = this.commentView; const cv = this.commentView;
const classnames = classNames("btn btn-link btn-animate text-muted", { const classnames = classNames("btn btn-link btn-animate text-muted", {
"btn-sm": small, "btn-sm": small,
}); });

View file

@ -1,3 +1,4 @@
import classNames from "classnames";
import { Component } from "inferno"; import { Component } from "inferno";
import { import {
AddAdmin, AddAdmin,
@ -25,6 +26,7 @@ import {
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { CommentNodeI, CommentViewType } from "../../interfaces"; import { CommentNodeI, CommentViewType } from "../../interfaces";
import { colorList } from "../../utils";
import { CommentNode } from "./comment-node"; import { CommentNode } from "./comment-node";
interface CommentNodesProps { interface CommentNodesProps {
@ -44,6 +46,8 @@ interface CommentNodesProps {
allLanguages: Language[]; allLanguages: Language[];
siteLanguages: number[]; siteLanguages: number[];
hideImages?: boolean; hideImages?: boolean;
isChild?: boolean;
depth?: number;
finished: Map<CommentId, boolean | undefined>; finished: Map<CommentId, boolean | undefined>;
onSaveComment(form: SaveComment): void; onSaveComment(form: SaveComment): void;
onCommentReplyRead(form: MarkCommentReplyAsRead): void; onCommentReplyRead(form: MarkCommentReplyAsRead): void;
@ -74,49 +78,61 @@ export class CommentNodes extends Component<CommentNodesProps, any> {
render() { render() {
const maxComments = this.props.maxCommentsShown ?? this.props.nodes.length; const maxComments = this.props.maxCommentsShown ?? this.props.nodes.length;
const borderColor = this.props.depth
? colorList[this.props.depth % colorList.length]
: colorList[0];
return ( return (
<div className="comments"> this.props.nodes.length > 0 && (
{this.props.nodes.slice(0, maxComments).map(node => ( <ul
<CommentNode className={classNames("comments", {
key={node.comment_view.comment.id} "ms-1": !!this.props.isChild,
node={node} "border-top border-light": !this.props.noBorder,
noBorder={this.props.noBorder} })}
noIndent={this.props.noIndent} style={`border-left: 2px solid ${borderColor} !important;`}
viewOnly={this.props.viewOnly} >
locked={this.props.locked} {this.props.nodes.slice(0, maxComments).map(node => (
moderators={this.props.moderators} <CommentNode
admins={this.props.admins} key={node.comment_view.comment.id}
markable={this.props.markable} node={node}
showContext={this.props.showContext} noBorder={this.props.noBorder}
showCommunity={this.props.showCommunity} noIndent={this.props.noIndent}
enableDownvotes={this.props.enableDownvotes} viewOnly={this.props.viewOnly}
viewType={this.props.viewType} locked={this.props.locked}
allLanguages={this.props.allLanguages} moderators={this.props.moderators}
siteLanguages={this.props.siteLanguages} admins={this.props.admins}
hideImages={this.props.hideImages} markable={this.props.markable}
onCommentReplyRead={this.props.onCommentReplyRead} showContext={this.props.showContext}
onPersonMentionRead={this.props.onPersonMentionRead} showCommunity={this.props.showCommunity}
finished={this.props.finished} enableDownvotes={this.props.enableDownvotes}
onCreateComment={this.props.onCreateComment} viewType={this.props.viewType}
onEditComment={this.props.onEditComment} allLanguages={this.props.allLanguages}
onCommentVote={this.props.onCommentVote} siteLanguages={this.props.siteLanguages}
onBlockPerson={this.props.onBlockPerson} hideImages={this.props.hideImages}
onSaveComment={this.props.onSaveComment} onCommentReplyRead={this.props.onCommentReplyRead}
onDeleteComment={this.props.onDeleteComment} onPersonMentionRead={this.props.onPersonMentionRead}
onRemoveComment={this.props.onRemoveComment} finished={this.props.finished}
onDistinguishComment={this.props.onDistinguishComment} onCreateComment={this.props.onCreateComment}
onAddModToCommunity={this.props.onAddModToCommunity} onEditComment={this.props.onEditComment}
onAddAdmin={this.props.onAddAdmin} onCommentVote={this.props.onCommentVote}
onBanPersonFromCommunity={this.props.onBanPersonFromCommunity} onBlockPerson={this.props.onBlockPerson}
onBanPerson={this.props.onBanPerson} onSaveComment={this.props.onSaveComment}
onTransferCommunity={this.props.onTransferCommunity} onDeleteComment={this.props.onDeleteComment}
onFetchChildren={this.props.onFetchChildren} onRemoveComment={this.props.onRemoveComment}
onCommentReport={this.props.onCommentReport} onDistinguishComment={this.props.onDistinguishComment}
onPurgePerson={this.props.onPurgePerson} onAddModToCommunity={this.props.onAddModToCommunity}
onPurgeComment={this.props.onPurgeComment} onAddAdmin={this.props.onAddAdmin}
/> onBanPersonFromCommunity={this.props.onBanPersonFromCommunity}
))} onBanPerson={this.props.onBanPerson}
</div> onTransferCommunity={this.props.onTransferCommunity}
onFetchChildren={this.props.onFetchChildren}
onCommentReport={this.props.onCommentReport}
onPurgePerson={this.props.onPurgePerson}
onPurgeComment={this.props.onPurgeComment}
/>
))}
</ul>
)
); );
} }
} }

View file

@ -266,9 +266,10 @@ export class Home extends Component<any, HomeState> {
} }
async componentDidMount() { async componentDidMount() {
if (!this.state.isIsomorphic) { if (!this.state.isIsomorphic || !this.isoData.routeData.length) {
await Promise.all([this.fetchTrendingCommunities(), this.fetchData()]); await Promise.all([this.fetchTrendingCommunities(), this.fetchData()]);
} }
setupTippy(); setupTippy();
} }
@ -480,7 +481,7 @@ export class Home extends Component<any, HomeState> {
} }
trendingCommunities(isMobile = false) { trendingCommunities(isMobile = false) {
switch (this.state.trendingCommunitiesRes.state) { switch (this.state.trendingCommunitiesRes?.state) {
case "loading": case "loading":
return ( return (
<h5> <h5>
@ -597,7 +598,7 @@ export class Home extends Component<any, HomeState> {
const siteRes = this.state.siteRes; const siteRes = this.state.siteRes;
if (dataType === DataType.Post) { if (dataType === DataType.Post) {
switch (this.state.postsRes.state) { switch (this.state.postsRes?.state) {
case "loading": case "loading":
return ( return (
<h5> <h5>

View file

@ -186,7 +186,9 @@ export class Login extends Component<any, State> {
UserService.Instance.myUserInfo = site.data.my_user; UserService.Instance.myUserInfo = site.data.my_user;
} }
i.props.history.replace("/"); i.props.history.action === "PUSH"
? i.props.history.back()
: i.props.history.replace("/");
break; break;
} }

View file

@ -1382,9 +1382,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
showMobilePreview() { showMobilePreview() {
const body = this.postView.post.body; const { body, id } = this.postView.post;
return !this.showBody && body ? ( return !this.showBody && body ? (
<div className="md-div mb-1 preview-lines">{body}</div> <Link className="text-body" to={`/post/${id}`}>
<div className="md-div mb-1 preview-lines">{body}</div>
</Link>
) : ( ) : (
<></> <></>
); );

View file

@ -68,7 +68,7 @@ export class PostListings extends Component<PostListingsProps, any> {
return ( return (
<div> <div>
{this.posts.length > 0 ? ( {this.posts.length > 0 ? (
this.posts.map(post_view => ( this.posts.map((post_view, idx) => (
<> <>
<PostListing <PostListing
post_view={post_view} post_view={post_view}
@ -96,7 +96,9 @@ export class PostListings extends Component<PostListingsProps, any> {
onAddAdmin={this.props.onAddAdmin} onAddAdmin={this.props.onAddAdmin}
onTransferCommunity={this.props.onTransferCommunity} onTransferCommunity={this.props.onTransferCommunity}
/> />
<hr className="my-3" /> {idx + 1 !== this.posts.length && (
<hr className="my-3 border border-primary" />
)}
</> </>
)) ))
) : ( ) : (

View file

@ -76,6 +76,7 @@ export const commentTreeMaxDepth = 8;
export const markdownFieldCharacterLimit = 50000; export const markdownFieldCharacterLimit = 50000;
export const maxUploadImages = 20; export const maxUploadImages = 20;
export const concurrentImageUpload = 4; export const concurrentImageUpload = 4;
export const updateUnreadCountsInterval = 30000;
export const relTags = "noopener nofollow"; export const relTags = "noopener nofollow";
@ -739,7 +740,7 @@ function setupMarkdown() {
defs: emojiDefs, defs: emojiDefs,
}) })
.disable("image"); .disable("image");
var defaultRenderer = md.renderer.rules.image; const defaultRenderer = md.renderer.rules.image;
md.renderer.rules.image = function ( md.renderer.rules.image = function (
tokens: Token[], tokens: Token[],
idx: number, idx: number,
@ -758,6 +759,9 @@ function setupMarkdown() {
const alt_text = item.content; const alt_text = item.content;
return `<img class="icon icon-emoji" src="${src}" title="${title}" alt="${alt_text}"/>`; return `<img class="icon icon-emoji" src="${src}" title="${title}" alt="${alt_text}"/>`;
}; };
md.renderer.rules.table_open = function () {
return '<table class="table">';
};
} }
export function getEmojiMart( export function getEmojiMart(
@ -1128,7 +1132,7 @@ export const colorList: string[] = [
]; ];
function hsl(num: number) { function hsl(num: number) {
return `hsla(${num}, 35%, 50%, 1)`; return `hsla(${num}, 35%, 50%, 0.5)`;
} }
export function hostname(url: string): string { export function hostname(url: string): string {
@ -1497,3 +1501,18 @@ export function newVote(voteType: VoteType, myVote?: number): number {
export type RouteDataResponse<T extends Record<string, any>> = { export type RouteDataResponse<T extends Record<string, any>> = {
[K in keyof T]: RequestState<Exclude<T[K], undefined>>; [K in keyof T]: RequestState<Exclude<T[K], undefined>>;
}; };
function sleep(millis: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, millis));
}
/**
* Polls / repeatedly runs a promise, every X milliseconds
*/
export async function poll(promiseFn: any, millis: number) {
if (window.document.visibilityState !== "hidden") {
await promiseFn();
}
await sleep(millis);
return poll(promiseFn, millis);
}