From: Dessalines Date: Wed, 17 Aug 2022 23:28:40 +0000 (-0400) Subject: Updating translations. X-Git-Url: http://these/git/?a=commitdiff_plain;h=c2f628312f76c726f002d3bafbe8eb9f78109947;hp=dbb5c10da9b17c5530ce19357d5f286d3a8adb56;p=lemmy-ui.git Updating translations. --- diff --git a/README.md b/README.md index a6969d0..e1e6e1f 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,19 @@ The official web app for [Lemmy](https://github.com/LemmyNet/lemmy), written in inferno. Based off of MrFoxPro's [inferno-isomorphic-template](https://github.com/MrFoxPro/inferno-isomorphic-template). + +## Configuration + +The following environment variables can be used to configure lemmy-ui: + +`ENV_VAR` | type | default | description +--- | --- | --- | --- +`LEMMY_UI_HOST` | `string` | `0.0.0.0:1234` | The IP / port that the lemmy-ui isomorphic node server is hosted at. +`LEMMY_UI_LEMMY_INTERNAL_HOST` | `string` | `0.0.0.0:8536` | The internal IP / port that lemmy is hosted at. Often `lemmy:8536` if using docker. +`LEMMY_UI_LEMMY_EXTERNAL_HOST` | `string` | `0.0.0.0:8536` | The external IP / port that lemmy is hosted at. Often `DOMAIN.TLD`. +`LEMMY_UI_LEMMY_WS_HOST` | `string` | `0.0.0.0:8536` | An alternate location for lemmy's websocket address. Not usually necessary. +`LEMMY_UI_HTTPS` | `bool` | `false` | Whether to use https. +`LEMMY_UI_EXTRA_THEMES_FOLDER` | `string` | `./extra_themes` | A location for additional lemmy css themes. +`LEMMY_UI_DEBUG` | `bool` | `false` | Loads the [Eruda](https://github.com/liriliri/eruda) debugging utility. +`LEMMY_UI_DISABLE_CSP` | `bool` | `false` | Disables CSP security headers +`LEMMY_UI_CUSTOM_HTML_HEADER` | `string` | | Injects a custom script into ``. diff --git a/lemmy-translations b/lemmy-translations index 29c689a..9ba11fa 160000 --- a/lemmy-translations +++ b/lemmy-translations @@ -1 +1 @@ -Subproject commit 29c689af8d16417c1b84d9491f6bcea888720a87 +Subproject commit 9ba11fa31001baac6bacb4eea651942cc48d41a9 diff --git a/package.json b/package.json index f1e4d23..853c4fa 100644 --- a/package.json +++ b/package.json @@ -17,14 +17,14 @@ }, "repository": "https://github.com/LemmyNet/lemmy-ui", "dependencies": { - "@typescript-eslint/parser": "^5.21.0", + "@typescript-eslint/parser": "^5.31.0", "autosize": "^5.0.1", - "check-password-strength": "^2.0.5", + "check-password-strength": "^2.0.7", "choices.js": "^10.1.0", "classnames": "^2.3.1", "emoji-short-name": "^2.0.0", - "express": "~4.18.0", - "i18next": "^21.6.16", + "express": "~4.18.1", + "i18next": "^21.8.14", "inferno": "^7.4.11", "inferno-create-element": "^7.4.11", "inferno-helmet": "^5.2.1", @@ -40,62 +40,62 @@ "markdown-it-html5-embed": "^1.0.0", "markdown-it-sub": "^1.0.0", "markdown-it-sup": "^1.0.0", - "moment": "^2.29.3", + "moment": "^2.29.4", "register-service-worker": "^1.7.2", - "rxjs": "^7.5.5", - "sass": "^1.51.0", + "rxjs": "^7.5.6", + "sass": "^1.54.0", "serialize-javascript": "^6.0.0", "tippy.js": "^6.3.7", - "toastify-js": "^1.11.2", + "toastify-js": "^1.12.0", "tributejs": "^5.1.3", "websocket-ts": "^1.1.1" }, "devDependencies": { - "@babel/core": "^7.17.9", - "@babel/plugin-proposal-decorators": "^7.18.2", - "@babel/plugin-transform-runtime": "^7.17.0", - "@babel/plugin-transform-typescript": "^7.16.1", - "@babel/preset-env": "7.16.11", - "@babel/preset-typescript": "^7.16.0", - "@babel/runtime": "^7.17.9", + "@babel/core": "^7.18.9", + "@babel/plugin-proposal-decorators": "^7.18.9", + "@babel/plugin-transform-runtime": "^7.18.9", + "@babel/plugin-transform-typescript": "^7.18.8", + "@babel/preset-env": "7.18.9", + "@babel/preset-typescript": "^7.18.6", + "@babel/runtime": "^7.18.9", "@sniptt/monads": "^0.5.10", "@types/autosize": "^4.0.0", "@types/express": "^4.17.13", - "@types/node": "^17.0.29", - "@types/node-fetch": "^2.6.1", + "@types/node": "^18.6.2", + "@types/node-fetch": "^2.6.2", "@types/serialize-javascript": "^5.0.1", - "@typescript-eslint/eslint-plugin": "^5.21.0", + "@typescript-eslint/eslint-plugin": "^5.31.0", "babel-loader": "^8.2.5", - "babel-plugin-inferno": "^6.4.0", - "bootstrap": "^5.1.3", - "bootswatch": "^5.1.3", + "babel-plugin-inferno": "^6.5.0", + "bootstrap": "^5.2.0", + "bootswatch": "^5.2.0", "class-transformer": "^0.5.1", "clean-webpack-plugin": "^4.0.0", - "copy-webpack-plugin": "^10.2.4", + "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.7.1", - "eslint": "^8.14.0", - "eslint-plugin-prettier": "^4.0.0", - "husky": "^7.0.4", + "eslint": "^8.20.0", + "eslint-plugin-prettier": "^4.2.1", + "husky": "^8.0.1", "import-sort-style-module": "^6.0.0", - "lemmy-js-client": "0.17.0-rc.33", - "lint-staged": "^12.4.1", - "mini-css-extract-plugin": "^2.6.0", + "lemmy-js-client": "0.17.0-rc.41", + "lint-staged": "^13.0.3", + "mini-css-extract-plugin": "^2.6.1", "node-fetch": "^2.6.1", - "prettier": "^2.6.2", + "prettier": "^2.7.1", "prettier-plugin-import-sort": "^0.0.7", - "prettier-plugin-organize-imports": "^2.3.4", - "prettier-plugin-packagejson": "^2.2.17", + "prettier-plugin-organize-imports": "^3.0.0", + "prettier-plugin-packagejson": "^2.2.18", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "run-node-webpack-plugin": "^1.3.0", - "sass-loader": "^12.6.0", - "sortpack": "^2.2.0", + "sass-loader": "^13.0.2", + "sortpack": "^2.3.0", "style-loader": "^3.3.1", - "terser": "^5.13.0", - "typescript": "^4.6.3", - "webpack": "5.72.0", - "webpack-cli": "^4.9.2", - "webpack-dev-server": "4.8.1", + "terser": "^5.14.2", + "typescript": "^4.7.4", + "webpack": "5.74.0", + "webpack-cli": "^4.10.0", + "webpack-dev-server": "4.9.3", "webpack-node-externals": "^3.0.0" }, "engines": { diff --git a/src/server/index.tsx b/src/server/index.tsx index 374fb03..fbac4ee 100644 --- a/src/server/index.tsx +++ b/src/server/index.tsx @@ -13,7 +13,7 @@ import process from "process"; import serialize from "serialize-javascript"; import { App } from "../shared/components/app/app"; import { SYMBOLS } from "../shared/components/common/symbols"; -import { httpBaseInternal, wsUriBase } from "../shared/env"; +import { httpBaseInternal } from "../shared/env"; import { ILemmyConfig, InitialFetchRequest, @@ -29,11 +29,11 @@ const [hostname, port] = process.env["LEMMY_UI_HOST"] const extraThemesFolder = process.env["LEMMY_UI_EXTRA_THEMES_FOLDER"] || "./extra_themes"; -if (!process.env["LEMMY_UI_DEBUG"]) { +if (!process.env["LEMMY_UI_DISABLE_CSP"] && !process.env["LEMMY_UI_DEBUG"]) { server.use(function (_req, res, next) { res.setHeader( "Content-Security-Policy", - `default-src 'none'; connect-src 'self' ${wsUriBase}; img-src * data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; form-action 'self'; base-uri 'self'` + `default-src 'none'; connect-src *; img-src * data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; form-action 'self'; base-uri 'self'` ); next(); }); @@ -189,7 +189,7 @@ server.get("/*", async (req, res) => { const symbols = renderToString(SYMBOLS); const helmet = Helmet.renderStatic(); - const config: ILemmyConfig = { wsHost: process.env.LEMMY_WS_HOST }; + const config: ILemmyConfig = { wsHost: process.env.LEMMY_UI_LEMMY_WS_HOST }; res.send(` diff --git a/src/shared/components/comment/comment-form.tsx b/src/shared/components/comment/comment-form.tsx index 7abf39b..6778b68 100644 --- a/src/shared/components/comment/comment-form.tsx +++ b/src/shared/components/comment/comment-form.tsx @@ -3,6 +3,7 @@ import { Component } from "inferno"; import { T } from "inferno-i18next-dess"; import { Link } from "inferno-router"; import { + CommentNode as CommentNodeI, CommentResponse, CreateComment, EditComment, @@ -12,7 +13,6 @@ import { } from "lemmy-js-client"; import { Subscription } from "rxjs"; import { i18n } from "../../i18next"; -import { CommentNode as CommentNodeI } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { auth, @@ -112,7 +112,8 @@ export class CommentForm extends Component { left: node => { if (this.props.edit) { let form = new EditComment({ - content, + content: Some(content), + distinguished: None, form_id: this.state.formId, comment_id: node.comment_view.comment.id, auth: auth().unwrap(), diff --git a/src/shared/components/comment/comment-node.tsx b/src/shared/components/comment/comment-node.tsx index 27e209e..f654b65 100644 --- a/src/shared/components/comment/comment-node.tsx +++ b/src/shared/components/comment/comment-node.tsx @@ -8,12 +8,17 @@ import { BanFromCommunity, BanPerson, BlockPerson, + CommentNode as CommentNodeI, + CommentReplyView, CommentView, CommunityModeratorView, CreateCommentLike, CreateCommentReport, DeleteComment, - MarkCommentAsRead, + EditComment, + GetComments, + ListingType, + MarkCommentReplyAsRead, MarkPersonMentionAsRead, PersonMentionView, PersonViewSafe, @@ -26,11 +31,7 @@ import { } from "lemmy-js-client"; import moment from "moment"; import { i18n } from "../../i18next"; -import { - BanType, - CommentNode as CommentNodeI, - PurgeType, -} from "../../interfaces"; +import { BanType, CommentViewType, PurgeType } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { amCommunityCreator, @@ -38,6 +39,7 @@ import { canAdmin, canMod, colorList, + commentTreeMaxDepth, futureDaysToUnixTime, isAdmin, isBanned, @@ -82,7 +84,6 @@ interface CommentNodeState { score: number; upvotes: number; downvotes: number; - borderColor: string; readLoading: boolean; saveLoading: boolean; } @@ -99,6 +100,7 @@ interface CommentNodeProps { showContext?: boolean; showCommunity?: boolean; enableDownvotes: boolean; + viewType: CommentViewType; } export class CommentNode extends Component { @@ -129,9 +131,6 @@ export class CommentNode extends Component { score: this.props.node.comment_view.counts.score, upvotes: this.props.node.comment_view.counts.upvotes, downvotes: this.props.node.comment_view.counts.downvotes, - borderColor: this.props.node.depth - ? colorList[this.props.node.depth % colorList.length] - : colorList[0], readLoading: false, saveLoading: false, }; @@ -173,7 +172,20 @@ export class CommentNode extends Component { this.props.admins, cv.creator.id ); + let canModOnSelf = canMod( + this.props.moderators, + this.props.admins, + cv.creator.id, + UserService.Instance.myUserInfo, + true + ); let canAdmin_ = canAdmin(this.props.admins, cv.creator.id); + let canAdminOnSelf = canAdmin( + this.props.admins, + cv.creator.id, + UserService.Instance.myUserInfo, + true + ); let isMod_ = isMod(this.props.moderators, cv.creator.id); let isAdmin_ = isAdmin(this.props.admins, cv.creator.id); let amCommunityCreator_ = amCommunityCreator( @@ -181,33 +193,49 @@ export class CommentNode extends Component { cv.creator.id ); + let borderColor = this.props.node.depth + ? colorList[(this.props.node.depth - 1) % colorList.length] + : colorList[0]; + let moreRepliesBorderColor = this.props.node.depth + ? colorList[this.props.node.depth % colorList.length] + : colorList[0]; + + let showMoreChildren = + this.props.viewType == CommentViewType.Tree && + !this.state.collapsed && + node.children.length == 0 && + node.comment_view.counts.child_count > 0; + return (
+ {showMoreChildren && ( +
+ +
+ )} {/* end of details */} {this.state.showRemoveDialog && (
{ focus /> )} - {!this.state.collapsed && node.children && ( + {!this.state.collapsed && node.children.length > 0 && ( { admins={this.props.admins} maxCommentsShown={None} enableDownvotes={this.props.enableDownvotes} + viewType={this.props.viewType} /> )} {/* A collapsed clearfix */} @@ -947,11 +1020,16 @@ export class CommentNode extends Component { ); } - get commentOrMentionRead() { + get commentReplyOrMentionRead(): boolean { let cv = this.props.node.comment_view; - return this.isPersonMentionType(cv) - ? cv.person_mention.read - : cv.comment.read; + + if (this.isPersonMentionType(cv)) { + return cv.person_mention.read; + } else if (this.isCommentReplyType(cv)) { + return cv.comment_reply.read; + } else { + return false; + } } linkBtn(small = false) { @@ -968,7 +1046,7 @@ export class CommentNode extends Component { <> @@ -1061,7 +1139,7 @@ export class CommentNode extends Component { this.setState(this.state); } - handleCommentUpvote(i: CommentNodeI, event: any) { + handleCommentUpvote(event: any) { event.preventDefault(); let myVote = this.state.my_vote.unwrapOr(0); let newVote = myVote == 1 ? 0 : 1; @@ -1081,17 +1159,16 @@ export class CommentNode extends Component { this.state.my_vote = Some(newVote); let form = new CreateCommentLike({ - comment_id: i.comment_view.comment.id, + comment_id: this.props.node.comment_view.comment.id, score: newVote, auth: auth().unwrap(), }); - WebSocketService.Instance.send(wsClient.likeComment(form)); this.setState(this.state); setupTippy(); } - handleCommentDownvote(i: CommentNodeI, event: any) { + handleCommentDownvote(event: any) { event.preventDefault(); let myVote = this.state.my_vote.unwrapOr(0); let newVote = myVote == -1 ? 0 : -1; @@ -1111,7 +1188,7 @@ export class CommentNode extends Component { this.state.my_vote = Some(newVote); let form = new CreateCommentLike({ - comment_id: i.comment_view.comment.id, + comment_id: this.props.node.comment_view.comment.id, score: newVote, auth: auth().unwrap(), }); @@ -1174,12 +1251,31 @@ export class CommentNode extends Component { i.setState(i.state); } + handleDistinguishClick(i: CommentNode) { + let comment = i.props.node.comment_view.comment; + let form = new EditComment({ + comment_id: comment.id, + form_id: None, // TODO not sure about this + content: None, + distinguished: Some(!comment.distinguished), + auth: auth().unwrap(), + }); + WebSocketService.Instance.send(wsClient.editComment(form)); + i.setState(i.state); + } + isPersonMentionType( - item: CommentView | PersonMentionView + item: CommentView | PersonMentionView | CommentReplyView ): item is PersonMentionView { return (item as PersonMentionView).person_mention?.id !== undefined; } + isCommentReplyType( + item: CommentView | PersonMentionView | CommentReplyView + ): item is CommentReplyView { + return (item as CommentReplyView).comment_reply?.id !== undefined; + } + handleMarkRead(i: CommentNode) { if (i.isPersonMentionType(i.props.node.comment_view)) { let form = new MarkPersonMentionAsRead({ @@ -1188,13 +1284,13 @@ export class CommentNode extends Component { auth: auth().unwrap(), }); WebSocketService.Instance.send(wsClient.markPersonMentionAsRead(form)); - } else { - let form = new MarkCommentAsRead({ - comment_id: i.props.node.comment_view.comment.id, - read: !i.props.node.comment_view.comment.read, + } else if (i.isCommentReplyType(i.props.node.comment_view)) { + let form = new MarkCommentReplyAsRead({ + comment_reply_id: i.props.node.comment_view.comment_reply.id, + read: !i.props.node.comment_view.comment_reply.read, auth: auth().unwrap(), }); - WebSocketService.Instance.send(wsClient.markCommentAsRead(form)); + WebSocketService.Instance.send(wsClient.markCommentReplyAsRead(form)); } i.state.readLoading = true; @@ -1419,6 +1515,24 @@ export class CommentNode extends Component { setupTippy(); } + handleFetchChildren(i: CommentNode) { + let form = new GetComments({ + post_id: Some(i.props.node.comment_view.post.id), + parent_id: Some(i.props.node.comment_view.comment.id), + max_depth: Some(commentTreeMaxDepth), + page: None, + sort: None, + limit: Some(999), + type_: Some(ListingType.All), + community_name: None, + community_id: None, + saved_only: Some(false), + auth: auth(false).ok(), + }); + + WebSocketService.Instance.send(wsClient.getComments(form)); + } + get scoreColor() { if (this.state.my_vote.unwrapOr(0) == 1) { return "text-info"; diff --git a/src/shared/components/comment/comment-nodes.tsx b/src/shared/components/comment/comment-nodes.tsx index 62167ec..f9484c2 100644 --- a/src/shared/components/comment/comment-nodes.tsx +++ b/src/shared/components/comment/comment-nodes.tsx @@ -1,7 +1,11 @@ import { Option } from "@sniptt/monads"; import { Component } from "inferno"; -import { CommunityModeratorView, PersonViewSafe } from "lemmy-js-client"; -import { CommentNode as CommentNodeI } from "../../interfaces"; +import { + CommentNode as CommentNodeI, + CommunityModeratorView, + PersonViewSafe, +} from "lemmy-js-client"; +import { CommentViewType } from "../../interfaces"; import { CommentNode } from "./comment-node"; interface CommentNodesProps { @@ -17,6 +21,7 @@ interface CommentNodesProps { showContext?: boolean; showCommunity?: boolean; enableDownvotes?: boolean; + viewType: CommentViewType; } export class CommentNodes extends Component { @@ -45,6 +50,7 @@ export class CommentNodes extends Component { showContext={this.props.showContext} showCommunity={this.props.showCommunity} enableDownvotes={this.props.enableDownvotes} + viewType={this.props.viewType} /> ))}
diff --git a/src/shared/components/comment/comment-report.tsx b/src/shared/components/comment/comment-report.tsx index 0a65226..a2d2b10 100644 --- a/src/shared/components/comment/comment-report.tsx +++ b/src/shared/components/comment/comment-report.tsx @@ -2,13 +2,14 @@ import { None } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { T } from "inferno-i18next-dess"; import { + CommentNode as CommentNodeI, CommentReportView, CommentView, ResolveCommentReport, SubscribedType, } from "lemmy-js-client"; import { i18n } from "../../i18next"; -import { CommentNode as CommentNodeI } from "../../interfaces"; +import { CommentViewType } from "../../interfaces"; import { WebSocketService } from "../../services"; import { auth, wsClient } from "../../utils"; import { Icon } from "../common/icon"; @@ -44,18 +45,20 @@ export class CommentReport extends Component { subscribed: SubscribedType.NotSubscribed, saved: false, creator_blocked: false, - recipient: None, my_vote: r.my_vote, }; let node: CommentNodeI = { comment_view, + children: [], + depth: 0, }; return (
{ + private id = `sort-select-${randomStr()}`; + private emptyState: CommentSortSelectState = { + sort: this.props.sort, + }; + + constructor(props: any, context: any) { + super(props, context); + this.state = this.emptyState; + } + + static getDerivedStateFromProps(props: any): CommentSortSelectState { + return { + sort: props.sort, + }; + } + + render() { + return ( + <> + + + + + + ); + } + + handleSortChange(i: CommentSortSelect, event: any) { + i.props.onChange(event.target.value); + } +} diff --git a/src/shared/components/common/registration-application.tsx b/src/shared/components/common/registration-application.tsx index 36875b8..f3c2cf5 100644 --- a/src/shared/components/common/registration-application.tsx +++ b/src/shared/components/common/registration-application.tsx @@ -104,7 +104,7 @@ export class RegistrationApplication extends Component<
)} - {(!ra.admin_id || (ra.admin_id && !accepted)) && ( + {(ra.admin_id.isNone() || (ra.admin_id.isSome() && !accepted)) && ( )} {subscribed == SubscribedType.Pending && ( - {i18n.t("subscribe_pending")} - + )} {community.removed && ( @@ -289,16 +288,33 @@ export class Sidebar extends Component { subscribe() { let community_view = this.props.community_view; + let blocked = this.props.community_view.blocked; return (
{community_view.subscribed == SubscribedType.NotSubscribed && ( - - {i18n.t("subscribe")} - + <> + + {blocked ? ( + + ) : ( + + )} + )}
); @@ -641,4 +657,24 @@ export class Sidebar extends Component { i.state.purgeLoading = true; i.setState(i.state); } + + handleBlock(i: Sidebar, event: any) { + event.preventDefault(); + let blockCommunityForm = new BlockCommunity({ + community_id: i.props.community_view.community.id, + block: true, + auth: auth().unwrap(), + }); + WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm)); + } + + handleUnblock(i: Sidebar, event: any) { + event.preventDefault(); + let blockCommunityForm = new BlockCommunity({ + community_id: i.props.community_view.community.id, + block: false, + auth: auth().unwrap(), + }); + WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm)); + } } diff --git a/src/shared/components/home/home.tsx b/src/shared/components/home/home.tsx index 6618034..53559df 100644 --- a/src/shared/components/home/home.tsx +++ b/src/shared/components/home/home.tsx @@ -30,7 +30,11 @@ import { } from "lemmy-js-client"; import { Subscription } from "rxjs"; import { i18n } from "../../i18next"; -import { DataType, InitialFetchRequest } from "../../interfaces"; +import { + CommentViewType, + DataType, + InitialFetchRequest, +} from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { auth, @@ -48,6 +52,7 @@ import { getSortTypeFromProps, isBrowser, notifyPost, + postToCommentSortType, relTags, restoreScrollPosition, saveCommentRes, @@ -263,9 +268,12 @@ export class Home extends Component { community_name: None, page, limit: Some(fetchLimit), - sort, + max_depth: None, + sort: sort.map(postToCommentSortType), type_, saved_only: Some(false), + post_id: None, + parent_id: None, auth: req.auth, }); promises.push(Promise.resolve()); @@ -565,6 +573,7 @@ export class Home extends Component { ) : ( { community_name: None, page: Some(this.state.page), limit: Some(fetchLimit), - sort: Some(this.state.sort), + max_depth: None, + sort: Some(postToCommentSortType(this.state.sort)), saved_only: Some(false), + post_id: None, + parent_id: None, auth: auth(false).ok(), type_: Some(this.state.listingType), }); diff --git a/src/shared/components/home/site-form.tsx b/src/shared/components/home/site-form.tsx index e2266fe..e9a5b14 100644 --- a/src/shared/components/home/site-form.tsx +++ b/src/shared/components/home/site-form.tsx @@ -446,7 +446,9 @@ export class SiteForm extends Component { class="form-check-input" id="create-site-hide-modlog-mod-names" type="checkbox" - checked={toUndefined(this.state.siteForm.hide_modlog_mod_names)} + checked={toUndefined( + this.state.siteForm.hide_modlog_mod_names + )} onChange={linkEvent(this, this.handleSiteHideModlogModNames)} />