Merge branch 'LemmyNet:main' into userpage

This commit is contained in:
mahanstreamer 2021-10-28 20:50:04 -04:00 committed by GitHub
commit 0eb4649c19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 523 additions and 475 deletions

@ -1 +1 @@
Subproject commit 762cb699a98d11032fe924cf50bf09e252413f6e Subproject commit 0d6ef6791f9175fb98ec99598c724d83e8e0d4ef

View file

@ -17,13 +17,13 @@
}, },
"repository": "https://github.com/LemmyNet/lemmy-ui", "repository": "https://github.com/LemmyNet/lemmy-ui",
"dependencies": { "dependencies": {
"@typescript-eslint/parser": "^4.32.0", "@typescript-eslint/parser": "^5.1.0",
"autosize": "^5.0.1", "autosize": "^5.0.1",
"check-password-strength": "^2.0.3", "check-password-strength": "^2.0.3",
"choices.js": "^9.0.1", "choices.js": "^9.0.1",
"emoji-short-name": "^1.0.0", "emoji-short-name": "^1.0.0",
"express": "~4.17.1", "express": "~4.17.1",
"i18next": "^21.1.1", "i18next": "^21.3.2",
"inferno": "^7.4.10", "inferno": "^7.4.10",
"inferno-create-element": "^7.4.10", "inferno-create-element": "^7.4.10",
"inferno-helmet": "^5.2.1", "inferno-helmet": "^5.2.1",
@ -41,41 +41,41 @@
"moment": "^2.29.1", "moment": "^2.29.1",
"reconnecting-websocket": "^4.4.0", "reconnecting-websocket": "^4.4.0",
"register-service-worker": "^1.7.2", "register-service-worker": "^1.7.2",
"rxjs": "^7.2.0", "rxjs": "^7.4.0",
"serialize-javascript": "^6.0.0", "serialize-javascript": "^6.0.0",
"tippy.js": "^6.3.1", "tippy.js": "^6.3.2",
"toastify-js": "^1.11.1", "toastify-js": "^1.11.2",
"tributejs": "^5.1.3", "tributejs": "^5.1.3",
"ws": "^8.2.2" "ws": "^8.2.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.15.5", "@babel/core": "^7.15.8",
"@babel/plugin-transform-runtime": "^7.14.5", "@babel/plugin-transform-runtime": "^7.15.8",
"@babel/plugin-transform-typescript": "^7.15.4", "@babel/plugin-transform-typescript": "^7.15.8",
"@babel/preset-env": "7.15.6", "@babel/preset-env": "7.15.8",
"@babel/preset-typescript": "^7.14.5", "@babel/preset-typescript": "^7.14.5",
"@babel/runtime": "^7.15.4", "@babel/runtime": "^7.15.4",
"@types/autosize": "^4.0.0", "@types/autosize": "^4.0.0",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/node": "^16.10.1", "@types/node": "^16.11.1",
"@types/node-fetch": "^2.5.11", "@types/node-fetch": "^2.5.11",
"@types/serialize-javascript": "^5.0.1", "@types/serialize-javascript": "^5.0.1",
"@typescript-eslint/eslint-plugin": "^4.32.0", "@typescript-eslint/eslint-plugin": "^5.1.0",
"babel-loader": "^8.2.2", "babel-loader": "^8.2.2",
"babel-plugin-inferno": "^6.3.0", "babel-plugin-inferno": "^6.3.0",
"bootstrap": "^5.1.1", "bootstrap": "^5.1.3",
"bootswatch": "^5.1.1", "bootswatch": "^5.1.3",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^9.0.1", "copy-webpack-plugin": "^9.0.1",
"css-loader": "^6.3.0", "css-loader": "^6.4.0",
"eslint": "^7.30.0", "eslint": "^8.0.1",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"husky": "^7.0.2", "husky": "^7.0.2",
"import-sort-style-module": "^6.0.0", "import-sort-style-module": "^6.0.0",
"iso-639-1": "^2.1.9", "iso-639-1": "^2.1.9",
"lemmy-js-client": "0.13.1-rc.1", "lemmy-js-client": "0.13.4-rc.1",
"lint-staged": "^11.0.1", "lint-staged": "^11.2.3",
"mini-css-extract-plugin": "^2.3.0", "mini-css-extract-plugin": "^2.4.2",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"node-sass": "^6.0.1", "node-sass": "^6.0.1",
"prettier": "^2.4.1", "prettier": "^2.4.1",
@ -84,14 +84,14 @@
"prettier-plugin-packagejson": "^2.2.13", "prettier-plugin-packagejson": "^2.2.13",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"run-node-webpack-plugin": "^1.3.0", "run-node-webpack-plugin": "^1.3.0",
"sass-loader": "^12.1.0", "sass-loader": "^12.2.0",
"sortpack": "^2.2.0", "sortpack": "^2.2.0",
"style-loader": "^3.3.0", "style-loader": "^3.3.0",
"terser": "^5.9.0", "terser": "^5.9.0",
"typescript": "^4.4.3", "typescript": "^4.4.4",
"webpack": "5.54.0", "webpack": "5.58.2",
"webpack-cli": "^4.7.2", "webpack-cli": "^4.9.1",
"webpack-dev-server": "4.3.0", "webpack-dev-server": "4.3.1",
"webpack-node-externals": "^3.0.0" "webpack-node-externals": "^3.0.0"
}, },
"engines": { "engines": {

View file

@ -2,19 +2,12 @@ import { Component, createRef, linkEvent, RefObject } from "inferno";
import { Link } from "inferno-router"; import { Link } from "inferno-router";
import { import {
CommentResponse, CommentResponse,
CommentView,
GetPersonMentions,
GetPersonMentionsResponse,
GetPrivateMessages,
GetReplies,
GetRepliesResponse,
GetReportCount, GetReportCount,
GetReportCountResponse, GetReportCountResponse,
GetSiteResponse, GetSiteResponse,
GetUnreadCount,
GetUnreadCountResponse,
PrivateMessageResponse, PrivateMessageResponse,
PrivateMessagesResponse,
PrivateMessageView,
SortType,
UserOperation, UserOperation,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { Subscription } from "rxjs"; import { Subscription } from "rxjs";
@ -23,7 +16,6 @@ import { UserService, WebSocketService } from "../../services";
import { import {
authField, authField,
donateLemmyUrl, donateLemmyUrl,
fetchLimit,
getLanguage, getLanguage,
isBrowser, isBrowser,
notifyComment, notifyComment,
@ -47,9 +39,6 @@ interface NavbarProps {
interface NavbarState { interface NavbarState {
isLoggedIn: boolean; isLoggedIn: boolean;
expanded: boolean; expanded: boolean;
replies: CommentView[];
mentions: CommentView[];
messages: PrivateMessageView[];
unreadInboxCount: number; unreadInboxCount: number;
unreadReportCount: number; unreadReportCount: number;
searchParam: string; searchParam: string;
@ -68,9 +57,6 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
isLoggedIn: !!this.props.site_res.my_user, isLoggedIn: !!this.props.site_res.my_user,
unreadInboxCount: 0, unreadInboxCount: 0,
unreadReportCount: 0, unreadReportCount: 0,
replies: [],
mentions: [],
messages: [],
expanded: false, expanded: false,
searchParam: "", searchParam: "",
toggleSearch: false, toggleSearch: false,
@ -577,30 +563,10 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
}) })
); );
this.fetchUnreads(); this.fetchUnreads();
} else if (op == UserOperation.GetReplies) { } else if (op == UserOperation.GetUnreadCount) {
let data = wsJsonToRes<GetRepliesResponse>(msg).data; let data = wsJsonToRes<GetUnreadCountResponse>(msg).data;
let unreadReplies = data.replies.filter(r => !r.comment.read); this.state.unreadInboxCount =
data.replies + data.mentions + data.private_messages;
this.state.replies = unreadReplies;
this.state.unreadInboxCount = this.calculateUnreadInboxCount();
this.setState(this.state);
this.sendUnreadCount();
} else if (op == UserOperation.GetPersonMentions) {
let data = wsJsonToRes<GetPersonMentionsResponse>(msg).data;
let unreadMentions = data.mentions.filter(r => !r.comment.read);
this.state.mentions = unreadMentions;
this.state.unreadInboxCount = this.calculateUnreadInboxCount();
this.setState(this.state);
this.sendUnreadCount();
} else if (op == UserOperation.GetPrivateMessages) {
let data = wsJsonToRes<PrivateMessagesResponse>(msg).data;
let unreadMessages = data.private_messages.filter(
r => !r.private_message.read
);
this.state.messages = unreadMessages;
this.state.unreadInboxCount = this.calculateUnreadInboxCount();
this.setState(this.state); this.setState(this.state);
this.sendUnreadCount(); this.sendUnreadCount();
} else if (op == UserOperation.GetReportCount) { } else if (op == UserOperation.GetReportCount) {
@ -628,7 +594,6 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
UserService.Instance.myUserInfo.local_user_view.local_user.id UserService.Instance.myUserInfo.local_user_view.local_user.id
) )
) { ) {
this.state.replies.push(data.comment_view);
this.state.unreadInboxCount++; this.state.unreadInboxCount++;
this.setState(this.state); this.setState(this.state);
this.sendUnreadCount(); this.sendUnreadCount();
@ -643,7 +608,6 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
data.private_message_view.recipient.id == data.private_message_view.recipient.id ==
UserService.Instance.myUserInfo.local_user_view.person.id UserService.Instance.myUserInfo.local_user_view.person.id
) { ) {
this.state.messages.push(data.private_message_view);
this.state.unreadInboxCount++; this.state.unreadInboxCount++;
this.setState(this.state); this.setState(this.state);
this.sendUnreadCount(); this.sendUnreadCount();
@ -654,41 +618,13 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
} }
fetchUnreads() { fetchUnreads() {
// TODO we should just add a count call to the API for these, because this is a limited fetch,
// and it shouldn't have to fetch the actual content
if (this.currentLocation !== "/inbox") {
console.log("Fetching inbox unreads..."); console.log("Fetching inbox unreads...");
let repliesForm: GetReplies = {
sort: SortType.New, let unreadForm: GetUnreadCount = {
unread_only: true,
page: 1,
limit: fetchLimit,
auth: authField(), auth: authField(),
}; };
let personMentionsForm: GetPersonMentions = { WebSocketService.Instance.send(wsClient.getUnreadCount(unreadForm));
sort: SortType.New,
unread_only: true,
page: 1,
limit: fetchLimit,
auth: authField(),
};
let privateMessagesForm: GetPrivateMessages = {
unread_only: true,
page: 1,
limit: fetchLimit,
auth: authField(),
};
WebSocketService.Instance.send(wsClient.getReplies(repliesForm));
WebSocketService.Instance.send(
wsClient.getPersonMentions(personMentionsForm)
);
WebSocketService.Instance.send(
wsClient.getPrivateMessages(privateMessagesForm)
);
}
console.log("Fetching reports..."); console.log("Fetching reports...");
@ -713,14 +649,6 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
); );
} }
calculateUnreadInboxCount(): number {
return (
this.state.replies.filter(r => !r.comment.read).length +
this.state.mentions.filter(r => !r.comment.read).length +
this.state.messages.filter(r => !r.private_message.read).length
);
}
get canAdmin(): boolean { get canAdmin(): boolean {
return ( return (
UserService.Instance.myUserInfo && UserService.Instance.myUserInfo &&

View file

@ -182,6 +182,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{i18n.t("creator")} {i18n.t("creator")}
</div> </div>
)} )}
{cv.creator.bot_account && (
<div className="badge badge-light d-none d-sm-inline mr-2">
{i18n.t("bot_account").toLowerCase()}
</div>
)}
{(cv.creator_banned_from_community || cv.creator.banned) && ( {(cv.creator_banned_from_community || cv.creator.banned) && (
<div className="badge badge-danger mr-2"> <div className="badge badge-danger mr-2">
{i18n.t("banned")} {i18n.t("banned")}

View file

@ -23,13 +23,27 @@ export class MomentTime extends Component<MomentTimeProps, any> {
moment.locale(lang); moment.locale(lang);
} }
createdAndModifiedTimes() {
let created = this.props.data.published || this.props.data.when_;
return `
<div>
<div>
${capitalizeFirstLetter(i18n.t("created"))}: ${this.format(created)}
</div>
<div>
${capitalizeFirstLetter(i18n.t("modified"))} ${this.format(
this.props.data.updated
)}
</div>
</div>`;
}
render() { render() {
if (!this.props.ignoreUpdated && this.props.data.updated) { if (!this.props.ignoreUpdated && this.props.data.updated) {
return ( return (
<span <span
data-tippy-content={`${capitalizeFirstLetter( data-tippy-content={this.createdAndModifiedTimes()}
i18n.t("modified") data-tippy-allowHtml={true}
)} ${this.format(this.props.data.updated)}`}
className="font-italics pointer unselectable" className="font-italics pointer unselectable"
> >
<Icon icon="edit-2" classes="icon-inline mr-1" /> <Icon icon="edit-2" classes="icon-inline mr-1" />
@ -37,13 +51,13 @@ export class MomentTime extends Component<MomentTimeProps, any> {
</span> </span>
); );
} else { } else {
let str = this.props.data.published || this.props.data.when_; let created = this.props.data.published || this.props.data.when_;
return ( return (
<span <span
className="pointer unselectable" className="pointer unselectable"
data-tippy-content={this.format(str)} data-tippy-content={this.format(created)}
> >
{moment.utc(str).fromNow(!this.props.showAgo)} {moment.utc(created).fromNow(!this.props.showAgo)}
</span> </span>
); );
} }

View file

@ -31,7 +31,7 @@ import { Icon, Spinner } from "../common/icon";
const passwordStrengthOptions: Options<string> = [ const passwordStrengthOptions: Options<string> = [
{ {
id: 0, id: 0,
value: "too_weak", value: "very_weak",
minDiversity: 0, minDiversity: 0,
minLength: 0, minLength: 0,
}, },

View file

@ -533,11 +533,20 @@ export class Inbox extends Component<any, InboxState> {
i.state.replies = []; i.state.replies = [];
i.state.mentions = []; i.state.mentions = [];
i.state.messages = []; i.state.messages = [];
i.sendUnreadCount(); UserService.Instance.unreadInboxCountSub.next(0);
window.scrollTo(0, 0); window.scrollTo(0, 0);
i.setState(i.state); i.setState(i.state);
} }
sendUnreadCount(read: boolean) {
let urcs = UserService.Instance.unreadInboxCountSub;
if (read) {
urcs.next(urcs.getValue() - 1);
} else {
urcs.next(urcs.getValue() + 1);
}
}
parseMessage(msg: any) { parseMessage(msg: any) {
let op = wsUserOp(msg); let op = wsUserOp(msg);
console.log(msg); console.log(msg);
@ -551,7 +560,6 @@ export class Inbox extends Component<any, InboxState> {
this.state.replies = data.replies; this.state.replies = data.replies;
this.state.combined = this.buildCombined(); this.state.combined = this.buildCombined();
this.state.loading = false; this.state.loading = false;
this.sendUnreadCount();
window.scrollTo(0, 0); window.scrollTo(0, 0);
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
@ -559,7 +567,6 @@ export class Inbox extends Component<any, InboxState> {
let data = wsJsonToRes<GetPersonMentionsResponse>(msg).data; let data = wsJsonToRes<GetPersonMentionsResponse>(msg).data;
this.state.mentions = data.mentions; this.state.mentions = data.mentions;
this.state.combined = this.buildCombined(); this.state.combined = this.buildCombined();
this.sendUnreadCount();
window.scrollTo(0, 0); window.scrollTo(0, 0);
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
@ -567,7 +574,6 @@ export class Inbox extends Component<any, InboxState> {
let data = wsJsonToRes<PrivateMessagesResponse>(msg).data; let data = wsJsonToRes<PrivateMessagesResponse>(msg).data;
this.state.messages = data.private_messages; this.state.messages = data.private_messages;
this.state.combined = this.buildCombined(); this.state.combined = this.buildCombined();
this.sendUnreadCount();
window.scrollTo(0, 0); window.scrollTo(0, 0);
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
@ -635,7 +641,7 @@ export class Inbox extends Component<any, InboxState> {
data.private_message_view.private_message.read; data.private_message_view.private_message.read;
} }
} }
this.sendUnreadCount(); this.sendUnreadCount(data.private_message_view.private_message.read);
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.MarkAllAsRead) { } else if (op == UserOperation.MarkAllAsRead) {
// Moved to be instant // Moved to be instant
@ -671,7 +677,8 @@ export class Inbox extends Component<any, InboxState> {
found.comment.read = combinedView.comment.read = found.comment.read = combinedView.comment.read =
data.comment_view.comment.read; data.comment_view.comment.read;
} }
this.sendUnreadCount();
this.sendUnreadCount(data.comment_view.comment.read);
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} else if (op == UserOperation.MarkPersonMentionAsRead) { } else if (op == UserOperation.MarkPersonMentionAsRead) {
@ -719,7 +726,7 @@ export class Inbox extends Component<any, InboxState> {
data.person_mention_view.person_mention.read; data.person_mention_view.person_mention.read;
} }
} }
this.sendUnreadCount(); this.sendUnreadCount(data.person_mention_view.person_mention.read);
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.CreateComment) { } else if (op == UserOperation.CreateComment) {
let data = wsJsonToRes<CommentResponse>(msg).data; let data = wsJsonToRes<CommentResponse>(msg).data;
@ -736,6 +743,37 @@ export class Inbox extends Component<any, InboxState> {
data.comment_view.creator.id == data.comment_view.creator.id ==
UserService.Instance.myUserInfo.local_user_view.person.id UserService.Instance.myUserInfo.local_user_view.person.id
) { ) {
// If youre in the unread view, just remove it from the list
if (this.state.unreadOrAll == UnreadOrAll.Unread) {
this.state.replies = this.state.replies.filter(
r => r.comment.id !== data.comment_view.comment.parent_id
);
this.state.mentions = this.state.mentions.filter(
m => m.comment.id !== data.comment_view.comment.parent_id
);
this.state.combined = this.state.combined.filter(r => {
if (this.isMention(r.view))
return r.view.comment.id !== data.comment_view.comment.parent_id;
else return r.id !== data.comment_view.comment.parent_id;
});
} else {
let mention_found = this.state.mentions.find(
i => i.comment.id == data.comment_view.comment.parent_id
);
if (mention_found) {
mention_found.person_mention.read = true;
}
let reply_found = this.state.replies.find(
i => i.comment.id == data.comment_view.comment.parent_id
);
if (reply_found) {
reply_found.comment.read = true;
}
this.state.combined = this.buildCombined();
}
this.sendUnreadCount(true);
this.setState(this.state);
setupTippy();
// TODO this seems wrong, you should be using form_id // TODO this seems wrong, you should be using form_id
toast(i18n.t("reply_sent")); toast(i18n.t("reply_sent"));
} }
@ -776,22 +814,7 @@ export class Inbox extends Component<any, InboxState> {
} }
} }
sendUnreadCount() { isMention(view: any): view is PersonMentionView {
UserService.Instance.unreadInboxCountSub.next(this.unreadCount()); return (view as PersonMentionView).person_mention !== undefined;
}
unreadCount(): number {
return (
this.state.replies.filter(r => !r.comment.read).length +
this.state.mentions.filter(r => !r.person_mention.read).length +
this.state.messages.filter(
r =>
UserService.Instance.myUserInfo &&
!r.private_message.read &&
// TODO also seems very strange and wrong
r.creator.id !==
UserService.Instance.myUserInfo.local_user_view.person.id
).length
);
} }
} }

View file

@ -396,6 +396,16 @@ export class Profile extends Component<any, ProfileState> {
{i18n.t("banned")} {i18n.t("banned")}
</li> </li>
)} )}
{pv.person.admin && (
<li className="list-inline-item badge badge-light">
{i18n.t("admin")}
</li>
)}
{pv.person.bot_account && (
<li className="list-inline-item badge badge-light">
{i18n.t("bot_account").toLowerCase()}
</li>
)}
</ul> </ul>
</div> </div>
<div className="flex-grow-1 unselectable pointer mx-2"></div> <div className="flex-grow-1 unselectable pointer mx-2"></div>

View file

@ -288,6 +288,11 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{this.isAdmin && ( {this.isAdmin && (
<span className="mx-1 badge badge-light">{i18n.t("admin")}</span> <span className="mx-1 badge badge-light">{i18n.t("admin")}</span>
)} )}
{post_view.creator.bot_account && (
<span className="mx-1 badge badge-light">
{i18n.t("bot_account").toLowerCase()}
</span>
)}
{(post_view.creator_banned_from_community || {(post_view.creator_banned_from_community ||
post_view.creator.banned) && ( post_view.creator.banned) && (
<span className="mx-1 badge badge-danger">{i18n.t("banned")}</span> <span className="mx-1 badge badge-danger">{i18n.t("banned")}</span>

View file

@ -13,18 +13,35 @@ interface PostListingsProps {
enableNsfw: boolean; enableNsfw: boolean;
} }
export class PostListings extends Component<PostListingsProps, any> { interface PostListingsState {
private duplicatesMap = new Map<number, PostView[]>(); posts: PostView[];
}
export class PostListings extends Component<
PostListingsProps,
PostListingsState
> {
duplicatesMap = new Map<number, PostView[]>();
private emptyState: PostListingsState = {
posts: [],
};
constructor(props: any, context: any) { constructor(props: any, context: any) {
super(props, context); super(props, context);
this.state = this.emptyState;
if (this.props.removeDuplicates) {
this.state.posts = this.removeDuplicates();
} else {
this.state.posts = this.props.posts;
}
} }
render() { render() {
return ( return (
<div> <div>
{this.props.posts.length > 0 ? ( {this.state.posts.length > 0 ? (
this.outer().map(post_view => ( this.state.posts.map(post_view => (
<> <>
<PostListing <PostListing
post_view={post_view} post_view={post_view}
@ -50,16 +67,10 @@ export class PostListings extends Component<PostListingsProps, any> {
); );
} }
outer(): PostView[] { removeDuplicates(): PostView[] {
let out = this.props.posts; // Must use a spread to clone the props, because splice will fail below otherwise.
if (this.props.removeDuplicates) { let posts = [...this.props.posts];
out = this.removeDuplicates(out);
}
return out;
}
removeDuplicates(posts: PostView[]): PostView[] {
// A map from post url to list of posts (dupes) // A map from post url to list of posts (dupes)
let urlMap = new Map<string, PostView[]>(); let urlMap = new Map<string, PostView[]>();

View file

@ -103,6 +103,7 @@ function format(value: any, format: any): any {
i18next.init({ i18next.init({
debug: false, debug: false,
compatibilityJSON: "v3",
// load: 'languageOnly', // load: 'languageOnly',
// initImmediate: false, // initImmediate: false,

697
yarn.lock

File diff suppressed because it is too large Load diff