mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-22 20:31:13 +00:00
Merge branch 'main' into route-data-refactor
This commit is contained in:
commit
88842a52c0
11 changed files with 143 additions and 125 deletions
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "lemmy-ui",
|
||||
"version": "0.18.0-beta.6",
|
||||
"version": "0.18.0-rc.1",
|
||||
"description": "An isomorphic UI for lemmy",
|
||||
"repository": "https://github.com/LemmyNet/lemmy-ui",
|
||||
"license": "AGPL-3.0",
|
||||
|
|
|
@ -80,30 +80,6 @@
|
|||
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 {
|
||||
margin-top: -6.5px;
|
||||
}
|
||||
|
@ -218,6 +194,11 @@ blockquote {
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.comments {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
object-fit: cover;
|
||||
min-height: 60px;
|
||||
|
|
|
@ -160,7 +160,7 @@ server.get("/*", async (req, res) => {
|
|||
site = try_site.data;
|
||||
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");
|
||||
}
|
||||
|
||||
|
@ -434,7 +434,7 @@ async function createSsrHtml(root: string, isoData: IsoDataOptionalSite) {
|
|||
<!-- 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">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no">
|
||||
<link
|
||||
id="favicon"
|
||||
rel="shortcut icon"
|
||||
|
|
|
@ -16,8 +16,10 @@ import {
|
|||
isBrowser,
|
||||
myAuth,
|
||||
numToSI,
|
||||
poll,
|
||||
showAvatars,
|
||||
toast,
|
||||
updateUnreadCountsInterval,
|
||||
} from "../../utils";
|
||||
import { Icon } from "../common/icon";
|
||||
import { PictrsImage } from "../common/pictrs-image";
|
||||
|
@ -64,7 +66,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
if (isBrowser()) {
|
||||
// On the first load, check the unreads
|
||||
this.requestNotificationPermission();
|
||||
await this.fetchUnreads();
|
||||
this.fetchUnreads();
|
||||
this.requestNotificationPermission();
|
||||
|
||||
document.addEventListener("mouseup", this.handleOutsideMenuClick);
|
||||
|
@ -406,35 +408,36 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
return amAdmin() || moderatesS;
|
||||
}
|
||||
|
||||
async fetchUnreads() {
|
||||
const auth = myAuth();
|
||||
if (auth) {
|
||||
this.setState({ unreadInboxCountRes: { state: "loading" } });
|
||||
this.setState({
|
||||
unreadInboxCountRes: await HttpService.client.getUnreadCount({
|
||||
auth,
|
||||
}),
|
||||
});
|
||||
|
||||
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({
|
||||
fetchUnreads() {
|
||||
poll(async () => {
|
||||
if (window.document.visibilityState !== "hidden") {
|
||||
const auth = myAuth();
|
||||
if (auth) {
|
||||
this.setState({
|
||||
unreadInboxCountRes: await HttpService.client.getUnreadCount({
|
||||
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 {
|
||||
|
|
|
@ -270,9 +270,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
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
|
||||
? colorList[this.props.node.depth % colorList.length]
|
||||
: colorList[0];
|
||||
|
@ -284,26 +281,17 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
node.comment_view.counts.child_count > 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`comment ${
|
||||
this.props.node.depth && !this.props.noIndent ? "ml-1" : ""
|
||||
}`}
|
||||
>
|
||||
<li className="comment" role="comment">
|
||||
<div
|
||||
id={`comment-${cv.comment.id}`}
|
||||
className={classNames(`details comment-node py-2`, {
|
||||
"border-top border-light": !this.props.noBorder,
|
||||
mark: this.isCommentNew || this.commentView.comment.distinguished,
|
||||
})}
|
||||
style={
|
||||
!this.props.noIndent && this.props.node.depth
|
||||
? `border-left: 2px ${borderColor} solid !important`
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<div
|
||||
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">
|
||||
|
@ -959,9 +947,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
</div>
|
||||
{showMoreChildren && (
|
||||
<div
|
||||
className={`details ml-1 comment-node py-2 ${
|
||||
!this.props.noBorder ? "border-top border-light" : ""
|
||||
}`}
|
||||
className={classNames("details ml-1 comment-node py-2", {
|
||||
"border-top border-light": !this.props.noBorder,
|
||||
})}
|
||||
style={`border-left: 2px ${moreRepliesBorderColor} solid !important`}
|
||||
>
|
||||
<button
|
||||
|
@ -1169,6 +1157,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
allLanguages={this.props.allLanguages}
|
||||
siteLanguages={this.props.siteLanguages}
|
||||
hideImages={this.props.hideImages}
|
||||
isChild={!this.props.noIndent}
|
||||
depth={this.props.node.depth + 1}
|
||||
finished={this.props.finished}
|
||||
onCommentReplyRead={this.props.onCommentReplyRead}
|
||||
onPersonMentionRead={this.props.onPersonMentionRead}
|
||||
|
@ -1192,8 +1182,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
/>
|
||||
)}
|
||||
{/* A collapsed clearfix */}
|
||||
{this.state.collapsed && <div className="row col-12"></div>}
|
||||
</div>
|
||||
{this.state.collapsed && <div className="row col-12" />}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1211,6 +1201,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
|
||||
linkBtn(small = false) {
|
||||
const cv = this.commentView;
|
||||
|
||||
const classnames = classNames("btn btn-link btn-animate text-muted", {
|
||||
"btn-sm": small,
|
||||
});
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import classNames from "classnames";
|
||||
import { Component } from "inferno";
|
||||
import {
|
||||
AddAdmin,
|
||||
|
@ -25,6 +26,7 @@ import {
|
|||
TransferCommunity,
|
||||
} from "lemmy-js-client";
|
||||
import { CommentNodeI, CommentViewType } from "../../interfaces";
|
||||
import { colorList } from "../../utils";
|
||||
import { CommentNode } from "./comment-node";
|
||||
|
||||
interface CommentNodesProps {
|
||||
|
@ -44,6 +46,8 @@ interface CommentNodesProps {
|
|||
allLanguages: Language[];
|
||||
siteLanguages: number[];
|
||||
hideImages?: boolean;
|
||||
isChild?: boolean;
|
||||
depth?: number;
|
||||
finished: Map<CommentId, boolean | undefined>;
|
||||
onSaveComment(form: SaveComment): void;
|
||||
onCommentReplyRead(form: MarkCommentReplyAsRead): void;
|
||||
|
@ -74,49 +78,61 @@ export class CommentNodes extends Component<CommentNodesProps, any> {
|
|||
render() {
|
||||
const maxComments = this.props.maxCommentsShown ?? this.props.nodes.length;
|
||||
|
||||
const borderColor = this.props.depth
|
||||
? colorList[this.props.depth % colorList.length]
|
||||
: colorList[0];
|
||||
|
||||
return (
|
||||
<div className="comments">
|
||||
{this.props.nodes.slice(0, maxComments).map(node => (
|
||||
<CommentNode
|
||||
key={node.comment_view.comment.id}
|
||||
node={node}
|
||||
noBorder={this.props.noBorder}
|
||||
noIndent={this.props.noIndent}
|
||||
viewOnly={this.props.viewOnly}
|
||||
locked={this.props.locked}
|
||||
moderators={this.props.moderators}
|
||||
admins={this.props.admins}
|
||||
markable={this.props.markable}
|
||||
showContext={this.props.showContext}
|
||||
showCommunity={this.props.showCommunity}
|
||||
enableDownvotes={this.props.enableDownvotes}
|
||||
viewType={this.props.viewType}
|
||||
allLanguages={this.props.allLanguages}
|
||||
siteLanguages={this.props.siteLanguages}
|
||||
hideImages={this.props.hideImages}
|
||||
onCommentReplyRead={this.props.onCommentReplyRead}
|
||||
onPersonMentionRead={this.props.onPersonMentionRead}
|
||||
finished={this.props.finished}
|
||||
onCreateComment={this.props.onCreateComment}
|
||||
onEditComment={this.props.onEditComment}
|
||||
onCommentVote={this.props.onCommentVote}
|
||||
onBlockPerson={this.props.onBlockPerson}
|
||||
onSaveComment={this.props.onSaveComment}
|
||||
onDeleteComment={this.props.onDeleteComment}
|
||||
onRemoveComment={this.props.onRemoveComment}
|
||||
onDistinguishComment={this.props.onDistinguishComment}
|
||||
onAddModToCommunity={this.props.onAddModToCommunity}
|
||||
onAddAdmin={this.props.onAddAdmin}
|
||||
onBanPersonFromCommunity={this.props.onBanPersonFromCommunity}
|
||||
onBanPerson={this.props.onBanPerson}
|
||||
onTransferCommunity={this.props.onTransferCommunity}
|
||||
onFetchChildren={this.props.onFetchChildren}
|
||||
onCommentReport={this.props.onCommentReport}
|
||||
onPurgePerson={this.props.onPurgePerson}
|
||||
onPurgeComment={this.props.onPurgeComment}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
this.props.nodes.length > 0 && (
|
||||
<ul
|
||||
className={classNames("comments", {
|
||||
"ms-1": !!this.props.isChild,
|
||||
"border-top border-light": !this.props.noBorder,
|
||||
})}
|
||||
style={`border-left: 2px solid ${borderColor} !important;`}
|
||||
>
|
||||
{this.props.nodes.slice(0, maxComments).map(node => (
|
||||
<CommentNode
|
||||
key={node.comment_view.comment.id}
|
||||
node={node}
|
||||
noBorder={this.props.noBorder}
|
||||
noIndent={this.props.noIndent}
|
||||
viewOnly={this.props.viewOnly}
|
||||
locked={this.props.locked}
|
||||
moderators={this.props.moderators}
|
||||
admins={this.props.admins}
|
||||
markable={this.props.markable}
|
||||
showContext={this.props.showContext}
|
||||
showCommunity={this.props.showCommunity}
|
||||
enableDownvotes={this.props.enableDownvotes}
|
||||
viewType={this.props.viewType}
|
||||
allLanguages={this.props.allLanguages}
|
||||
siteLanguages={this.props.siteLanguages}
|
||||
hideImages={this.props.hideImages}
|
||||
onCommentReplyRead={this.props.onCommentReplyRead}
|
||||
onPersonMentionRead={this.props.onPersonMentionRead}
|
||||
finished={this.props.finished}
|
||||
onCreateComment={this.props.onCreateComment}
|
||||
onEditComment={this.props.onEditComment}
|
||||
onCommentVote={this.props.onCommentVote}
|
||||
onBlockPerson={this.props.onBlockPerson}
|
||||
onSaveComment={this.props.onSaveComment}
|
||||
onDeleteComment={this.props.onDeleteComment}
|
||||
onRemoveComment={this.props.onRemoveComment}
|
||||
onDistinguishComment={this.props.onDistinguishComment}
|
||||
onAddModToCommunity={this.props.onAddModToCommunity}
|
||||
onAddAdmin={this.props.onAddAdmin}
|
||||
onBanPersonFromCommunity={this.props.onBanPersonFromCommunity}
|
||||
onBanPerson={this.props.onBanPerson}
|
||||
onTransferCommunity={this.props.onTransferCommunity}
|
||||
onFetchChildren={this.props.onFetchChildren}
|
||||
onCommentReport={this.props.onCommentReport}
|
||||
onPurgePerson={this.props.onPurgePerson}
|
||||
onPurgeComment={this.props.onPurgeComment}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -266,9 +266,10 @@ export class Home extends Component<any, HomeState> {
|
|||
}
|
||||
|
||||
async componentDidMount() {
|
||||
if (!this.state.isIsomorphic) {
|
||||
if (!this.state.isIsomorphic || !this.isoData.routeData.length) {
|
||||
await Promise.all([this.fetchTrendingCommunities(), this.fetchData()]);
|
||||
}
|
||||
|
||||
setupTippy();
|
||||
}
|
||||
|
||||
|
@ -480,7 +481,7 @@ export class Home extends Component<any, HomeState> {
|
|||
}
|
||||
|
||||
trendingCommunities(isMobile = false) {
|
||||
switch (this.state.trendingCommunitiesRes.state) {
|
||||
switch (this.state.trendingCommunitiesRes?.state) {
|
||||
case "loading":
|
||||
return (
|
||||
<h5>
|
||||
|
@ -597,7 +598,7 @@ export class Home extends Component<any, HomeState> {
|
|||
const siteRes = this.state.siteRes;
|
||||
|
||||
if (dataType === DataType.Post) {
|
||||
switch (this.state.postsRes.state) {
|
||||
switch (this.state.postsRes?.state) {
|
||||
case "loading":
|
||||
return (
|
||||
<h5>
|
||||
|
|
|
@ -186,7 +186,9 @@ export class Login extends Component<any, State> {
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -1382,9 +1382,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
}
|
||||
|
||||
showMobilePreview() {
|
||||
const body = this.postView.post.body;
|
||||
const { body, id } = this.postView.post;
|
||||
|
||||
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>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
|
|
|
@ -68,7 +68,7 @@ export class PostListings extends Component<PostListingsProps, any> {
|
|||
return (
|
||||
<div>
|
||||
{this.posts.length > 0 ? (
|
||||
this.posts.map(post_view => (
|
||||
this.posts.map((post_view, idx) => (
|
||||
<>
|
||||
<PostListing
|
||||
post_view={post_view}
|
||||
|
@ -96,7 +96,9 @@ export class PostListings extends Component<PostListingsProps, any> {
|
|||
onAddAdmin={this.props.onAddAdmin}
|
||||
onTransferCommunity={this.props.onTransferCommunity}
|
||||
/>
|
||||
<hr className="my-3" />
|
||||
{idx + 1 !== this.posts.length && (
|
||||
<hr className="my-3 border border-primary" />
|
||||
)}
|
||||
</>
|
||||
))
|
||||
) : (
|
||||
|
|
|
@ -76,6 +76,7 @@ export const commentTreeMaxDepth = 8;
|
|||
export const markdownFieldCharacterLimit = 50000;
|
||||
export const maxUploadImages = 20;
|
||||
export const concurrentImageUpload = 4;
|
||||
export const updateUnreadCountsInterval = 30000;
|
||||
|
||||
export const relTags = "noopener nofollow";
|
||||
|
||||
|
@ -739,7 +740,7 @@ function setupMarkdown() {
|
|||
defs: emojiDefs,
|
||||
})
|
||||
.disable("image");
|
||||
var defaultRenderer = md.renderer.rules.image;
|
||||
const defaultRenderer = md.renderer.rules.image;
|
||||
md.renderer.rules.image = function (
|
||||
tokens: Token[],
|
||||
idx: number,
|
||||
|
@ -758,6 +759,9 @@ function setupMarkdown() {
|
|||
const alt_text = item.content;
|
||||
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(
|
||||
|
@ -1128,7 +1132,7 @@ export const colorList: string[] = [
|
|||
];
|
||||
|
||||
function hsl(num: number) {
|
||||
return `hsla(${num}, 35%, 50%, 1)`;
|
||||
return `hsla(${num}, 35%, 50%, 0.5)`;
|
||||
}
|
||||
|
||||
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>> = {
|
||||
[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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue