diff --git a/web/app/@types/markdown-toolbar-element/index.d.ts b/web/app/@types/markdown-toolbar-element/index.d.ts index a8812c83..f4ed19d2 100644 --- a/web/app/@types/markdown-toolbar-element/index.d.ts +++ b/web/app/@types/markdown-toolbar-element/index.d.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ declare namespace JSX { interface IntrinsicElements { 'markdown-toolbar': { diff --git a/web/app/common/fetcher.ts b/web/app/common/fetcher.ts index a72c4c0d..908bdd04 100644 --- a/web/app/common/fetcher.ts +++ b/web/app/common/fetcher.ts @@ -81,7 +81,9 @@ const fetcher = methods.reduce>((acc, method) => { try { err = JSON.parse(text); } catch (e) { - throw text; + // eslint-disable-next-line no-console + console.error(err); + throw 'Something went wrong.'; } throw err; }); diff --git a/web/app/components/comment/comment.tsx b/web/app/components/comment/comment.tsx index 575d0db8..392c2eb1 100644 --- a/web/app/components/comment/comment.tsx +++ b/web/app/components/comment/comment.tsx @@ -12,7 +12,8 @@ import { StaticStore } from '@app/common/static_store'; import debounce from '@app/utils/debounce'; import copy from '@app/common/copy'; import { Theme, BlockTTL, Comment as CommentType, PostInfo, User, CommentMode, Image } from '@app/common/types'; -import { extractErrorMessageFromResponse, FetcherResponse } from '@app/utils/errorUtils'; +import { extractErrorMessageFromResponse, FetcherError } from '@app/utils/errorUtils'; +import { isUserAnonymous } from '@app/utils/isUserAnonymous'; import { Input } from '@app/components/input'; import { AvatarIcon } from '@app/components/avatar-icon'; @@ -213,7 +214,7 @@ export class Comment extends Component { } } - handleVoteError(e: FetcherResponse, originalScore: number, originalDelta: number) { + handleVoteError(e: FetcherError, originalScore: number, originalDelta: number) { this.setState({ scoreDelta: originalDelta, cachedScore: originalScore, @@ -315,7 +316,7 @@ export class Comment extends Component { * Defines whether current client is logged in via `Anonymous provider` */ isAnonymous(): boolean { - return this.props.user! && this.props.user!.id.substr(0, 10) === 'anonymous_'; + return isUserAnonymous(this.props.user); } /** @@ -369,6 +370,7 @@ export class Comment extends Component { const isUpvotingDisabled = upvotingDisabledReason !== null; const editable = props.repliesCount === 0 && state.editDeadline; const scoreSignEnabled = !StaticStore.config.positive_score; + const uploadImageHandler = this.isAnonymous() ? undefined : this.props.uploadImage; /** * CommentType adapted for rendering @@ -676,7 +678,7 @@ export class Comment extends Component { onCancel={this.toggleReplying} getPreview={this.props.getPreview!} autofocus={true} - uploadImage={this.props.uploadImage!} + uploadImage={uploadImageHandler} /> )} @@ -691,7 +693,7 @@ export class Comment extends Component { getPreview={this.props.getPreview!} errorMessage={state.editDeadline === null ? 'Editing time has expired.' : undefined} autofocus={true} - uploadImage={this.props.uploadImage!} + uploadImage={uploadImageHandler} /> )} diff --git a/web/app/components/input/input.tsx b/web/app/components/input/input.tsx index e669be04..158982e8 100644 --- a/web/app/components/input/input.tsx +++ b/web/app/components/input/input.tsx @@ -38,7 +38,7 @@ interface Props { getPreview(text: string): Promise; /** action on cancel. optional as root input has no cancel option */ onCancel?: () => void; - uploadImage: (image: File) => Promise; + uploadImage?: (image: File) => Promise; } interface State { @@ -194,6 +194,7 @@ export class Input extends Component { } onDragOver(e: DragEvent) { + if (!this.props.uploadImage) return; if (StaticStore.config.max_image_size === 0) return; if (!this.textAreaRef) return; if (!e.dataTransfer) return; @@ -204,6 +205,7 @@ export class Input extends Component { } onDrop(e: DragEvent) { + if (!this.props.uploadImage) return; if (StaticStore.config.max_image_size === 0) return; if (!e.dataTransfer) return; @@ -217,20 +219,19 @@ export class Input extends Component { /** wrapper with error handling for props.uploadImage */ uploadImage(file: File): Promise { - return this.props - .uploadImage(file) - .catch( - (e: ApiError | string) => - new Error( - typeof e === 'string' - ? `${file.name} upload failed with "${e}"` - : `${file.name} upload failed with "${e.error}"` - ) - ); + return this.props.uploadImage!(file).catch( + (e: ApiError | string) => + new Error( + typeof e === 'string' + ? `${file.name} upload failed with "${e}"` + : `${file.name} upload failed with "${e.error}"` + ) + ); } /** performs upload process */ async uploadImages(files: File[]) { + if (!this.props.uploadImage) return; if (!this.textAreaRef) return; /** Human readable image size limit, i.e 5MB */ diff --git a/web/app/components/root/root.tsx b/web/app/components/root/root.tsx index 568822bb..9c41d3d1 100644 --- a/web/app/components/root/root.tsx +++ b/web/app/components/root/root.tsx @@ -49,6 +49,7 @@ import { Input } from '@app/components/input'; import Preloader from '@app/components/preloader'; import { Thread } from '@app/components/thread'; import { uploadImage } from '@app/common/api'; +import { isUserAnonymous } from '@app/utils/isUserAnonymous'; interface Props { user: User | null; @@ -192,6 +193,13 @@ export class Root extends Component { }); } + /** + * Defines whether current client is logged in via `Anonymous provider` + */ + isAnonymous(): boolean { + return isUserAnonymous(this.props.user); + } + render(props: RenderableProps, { isLoaded, isCommentsListLoading, commentsShown }: State) { if (!isLoaded) { return ( @@ -205,6 +213,7 @@ export class Root extends Component { const isGuest = !props.user; const isCommentsDisabled = !!props.info.read_only; + const imageUploadHandler = this.isAnonymous() ? undefined : this.props.uploadImage; return (
@@ -235,7 +244,7 @@ export class Root extends Component { userId={this.props.user!.id} onSubmit={(text, title) => this.props.addComment(text, title)} getPreview={this.props.getPreview} - uploadImage={this.props.uploadImage} + uploadImage={imageUploadHandler} /> )} diff --git a/web/app/utils/errorUtils.ts b/web/app/utils/errorUtils.ts index 400612fe..4b85e081 100644 --- a/web/app/utils/errorUtils.ts +++ b/web/app/utils/errorUtils.ts @@ -20,14 +20,15 @@ const errorMessageForCodes = new Map([ [18, 'Requested file cannot be found.'], ]); -export type FetcherResponse = +export type FetcherError = | string | { code?: number; details?: string; + error: string; }; -export function extractErrorMessageFromResponse(response: FetcherResponse): string { +export function extractErrorMessageFromResponse(response: FetcherError): string { const defaultErrorMessage = 'Something went wrong. Please try again a bit later.'; if (!response) { return defaultErrorMessage; diff --git a/web/app/utils/isUserAnonymous.ts b/web/app/utils/isUserAnonymous.ts new file mode 100644 index 00000000..e64aa9e6 --- /dev/null +++ b/web/app/utils/isUserAnonymous.ts @@ -0,0 +1,8 @@ +import { User } from '@app/common/types'; + +/** + * Defines whether current client is logged in via `Anonymous provider` + */ +export function isUserAnonymous(user?: User | null): boolean { + return user! && user!.id.substr(0, 10) === 'anonymous_'; +} diff --git a/web/package-lock.json b/web/package-lock.json index d6838b69..324d49c8 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -4854,7 +4854,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -4875,12 +4876,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4895,17 +4898,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5022,7 +5028,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5034,6 +5041,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5048,6 +5056,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5055,12 +5064,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5079,6 +5090,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5159,7 +5171,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5171,6 +5184,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5256,7 +5270,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5292,6 +5307,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5311,6 +5327,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5354,12 +5371,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/web/package.json b/web/package.json index 51ebfdcd..426972c3 100644 --- a/web/package.json +++ b/web/package.json @@ -5,7 +5,7 @@ "build": "webpack --mode production", "start": "webpack-dev-server --mode development", "check": "tsc -p tsconfig.json --noEmit --skipLibCheck", - "lint": "eslint --ext=.ts,.tsx,.js,.jsx .", + "lint": "eslint --max-warnings=0 --ext=.ts,.tsx,.js,.jsx .", "test": "jest", "test:coverage": "jest --coverage", "prettier": "prettier --write \"./**/*.{js,jsx,ts,tsx,scss}\""