diff --git a/lang/default.json b/lang/default.json index e6e7c50331..1ba85317b7 100644 --- a/lang/default.json +++ b/lang/default.json @@ -761,9 +761,6 @@ "defaultMessage": "Wallet connected", "description": "src/components/Forms/WalletAuthForm/Connect.tsx" }, - "KuP/6B": { - "defaultMessage": "Images have a {sizeInMB} megabyte (MB) size limit." - }, "KxVlDj": { "defaultMessage": "Settings - Blocked Users", "description": "src/views/Me/Settings/Blocked/index.tsx" @@ -1263,6 +1260,9 @@ "defaultMessage": "Unblock", "description": "src/components/BlockUser/Button/index.tsx" }, + "bSqeXm": { + "defaultMessage": "{ext, select, gif {GIF format i} other {I} }mages have a {sizeInMB} megabyte (MB) size limit." + }, "beLe/F": { "defaultMessage": "Broadcast" }, diff --git a/lang/en.json b/lang/en.json index abed5a1bc5..80476c7242 100644 --- a/lang/en.json +++ b/lang/en.json @@ -761,9 +761,6 @@ "defaultMessage": "Wallet connected", "description": "src/components/Forms/WalletAuthForm/Connect.tsx" }, - "KuP/6B": { - "defaultMessage": "Images have a {sizeInMB} megabyte (MB) size limit." - }, "KxVlDj": { "defaultMessage": "Settings - Blocked Users", "description": "src/views/Me/Settings/Blocked/index.tsx" @@ -1263,6 +1260,9 @@ "defaultMessage": "Unblock", "description": "src/components/BlockUser/Button/index.tsx" }, + "bSqeXm": { + "defaultMessage": "{ext, select, gif {GIF format i} other {I} }mages have a {sizeInMB} megabyte (MB) size limit." + }, "beLe/F": { "defaultMessage": "Broadcast" }, diff --git a/lang/zh-Hans.json b/lang/zh-Hans.json index 5768620b83..03f66d1a69 100644 --- a/lang/zh-Hans.json +++ b/lang/zh-Hans.json @@ -761,9 +761,6 @@ "defaultMessage": "钱包已绑定", "description": "src/components/Forms/WalletAuthForm/Connect.tsx" }, - "KuP/6B": { - "defaultMessage": "图片大小不得超过 {sizeInMB}MB" - }, "KxVlDj": { "defaultMessage": "设置 - 已屏蔽用户", "description": "src/views/Me/Settings/Blocked/index.tsx" @@ -1263,6 +1260,9 @@ "defaultMessage": "取消屏蔽", "description": "src/components/BlockUser/Button/index.tsx" }, + "bSqeXm": { + "defaultMessage": "{ext, select, gif {GIF 格式} other {} }图片大小不得超过 {sizeInMB}MB" + }, "beLe/F": { "defaultMessage": "广播" }, diff --git a/lang/zh-Hant.json b/lang/zh-Hant.json index d23f24ae73..24f8e8ddf9 100644 --- a/lang/zh-Hant.json +++ b/lang/zh-Hant.json @@ -761,9 +761,6 @@ "defaultMessage": "錢包已綁定", "description": "src/components/Forms/WalletAuthForm/Connect.tsx" }, - "KuP/6B": { - "defaultMessage": "圖片大小不得超過 {sizeInMB}MB" - }, "KxVlDj": { "defaultMessage": "設定 - 已封鎖用戶", "description": "src/views/Me/Settings/Blocked/index.tsx" @@ -1263,6 +1260,9 @@ "defaultMessage": "取消封鎖", "description": "src/components/BlockUser/Button/index.tsx" }, + "bSqeXm": { + "defaultMessage": "{ext, select, gif {GIF 格式} other {} }圖片大小不得超過 {sizeInMB}MB" + }, "beLe/F": { "defaultMessage": "廣播" }, diff --git a/package-lock.json b/package-lock.json index 3e7485477c..a1630f4930 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,6 +53,7 @@ "embla-carousel-react": "^7.1.0", "express": "^4.17.1", "fastest-levenshtein": "^1.0.16", + "file-type": "^16.5.4", "fingerprintjs2": "^2.1.4", "firebase": "^9.22.0", "formik": "^2.4.2", @@ -13710,6 +13711,11 @@ "@tiptap/pm": "^2.0.0" } }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -21953,6 +21959,15 @@ "node": ">=10" } }, + "node_modules/download/node_modules/file-type": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-11.1.0.tgz", + "integrity": "sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/download/node_modules/get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -24475,12 +24490,19 @@ } }, "node_modules/file-type": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-11.1.0.tgz", - "integrity": "sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g==", - "dev": true, + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" } }, "node_modules/file-uri-to-path": { @@ -33464,6 +33486,18 @@ "node": ">=0.12" } }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/peek-stream": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", @@ -36176,6 +36210,21 @@ "node": ">= 6" } }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "dependencies": { + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -38547,6 +38596,22 @@ "node": ">=0.10.0" } }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/style-loader": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.2.tgz", @@ -39650,6 +39715,22 @@ "node": ">=0.6" } }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/totalist": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", @@ -52007,6 +52088,11 @@ "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-2.1.0-rc.9.tgz", "integrity": "sha512-GN0ooEMzkUe/+iid9wpksZMRCcgDz8vImfZ7PACsu9hkhhgNd2oDYBxljWH4o89nXnOib/OCeIh88b32H+Xy2A==" }, + "@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, "@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -58581,6 +58667,12 @@ "pify": "^4.0.1" }, "dependencies": { + "file-type": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-11.1.0.tgz", + "integrity": "sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g==", + "dev": true + }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -60452,10 +60544,14 @@ } }, "file-type": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-11.1.0.tgz", - "integrity": "sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g==", - "dev": true + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "requires": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + } }, "file-uri-to-path": { "version": "1.0.0", @@ -67349,6 +67445,11 @@ "sha.js": "^2.4.8" } }, + "peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==" + }, "peek-stream": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", @@ -69211,6 +69312,14 @@ "util-deprecate": "^1.0.1" } }, + "readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "requires": { + "readable-stream": "^3.6.0" + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -71076,6 +71185,15 @@ "escape-string-regexp": "^1.0.2" } }, + "strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "requires": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + } + }, "style-loader": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.2.tgz", @@ -71900,6 +72018,15 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, + "token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "requires": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + } + }, "totalist": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", diff --git a/package.json b/package.json index ba4f74bc2e..2b2621735c 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "embla-carousel-react": "^7.1.0", "express": "^4.17.1", "fastest-levenshtein": "^1.0.16", + "file-type": "^16.5.4", "fingerprintjs2": "^2.1.4", "firebase": "^9.22.0", "formik": "^2.4.2", diff --git a/src/common/utils/form/image.tsx b/src/common/utils/form/image.tsx index 6251de3b96..f868725bde 100644 --- a/src/common/utils/form/image.tsx +++ b/src/common/utils/form/image.tsx @@ -1,3 +1,4 @@ +import FileType from 'file-type/browser' import { FormattedMessage } from 'react-intl' import { @@ -8,68 +9,80 @@ import { } from '~/common/enums' import { toast } from '~/components' +export const getFileType = ( + file: File +): Promise => FileType.fromBlob(file) + +// return meme type or null if not valid export const validateImage = (image: File, isAvatar: boolean = false) => - new Promise((resolve, reject) => { + new Promise((resolve, reject) => { // size limits - const isGIF = image.type === 'image/gif' - const sizeLimit = - isAvatar && isGIF ? UPLOAD_GIF_AVATAR_SIZE_LIMIT : UPLOAD_IMAGE_SIZE_LIMIT - const isExceedSizeLimit = image.size > sizeLimit - if (isExceedSizeLimit) { - toast.error({ - message: ( - - ), - }) - return resolve(false) - } + getFileType(image).then((fileType) => { + if (!fileType) { + return resolve(null) + } + const isGIF = fileType.mime === 'image/gif' + const sizeLimit = + isAvatar && isGIF + ? UPLOAD_GIF_AVATAR_SIZE_LIMIT + : UPLOAD_IMAGE_SIZE_LIMIT + const isExceedSizeLimit = image.size > sizeLimit + if (isExceedSizeLimit) { + toast.error({ + message: ( + + ), + }) + return resolve(null) + } - // dimension limits - try { - const $img = new Image() + // dimension limits + try { + const $img = new Image() - $img.onload = () => { - const { width, height } = $img - window.URL.revokeObjectURL($img.src) + $img.onload = () => { + const { width, height } = $img + window.URL.revokeObjectURL($img.src) - const isExceedDimensionLimit = - width > UPLOAD_IMAGE_DIMENSION_LIMIT || - height > UPLOAD_IMAGE_DIMENSION_LIMIT - const isExceedAreaLimit = width * height > UPLOAD_IMAGE_AREA_LIMIT + const isExceedDimensionLimit = + width > UPLOAD_IMAGE_DIMENSION_LIMIT || + height > UPLOAD_IMAGE_DIMENSION_LIMIT + const isExceedAreaLimit = width * height > UPLOAD_IMAGE_AREA_LIMIT - if (isExceedDimensionLimit) { - toast.error({ - message: ( - - ), - }) - return resolve(false) - } + if (isExceedDimensionLimit) { + toast.error({ + message: ( + + ), + }) + return resolve(null) + } + + if (isExceedAreaLimit) { + toast.error({ + message: ( + + ), + }) + return resolve(null) + } - if (isExceedAreaLimit) { - toast.error({ - message: ( - - ), - }) - return resolve(false) + return resolve(fileType.mime) } - return resolve(true) + $img.src = window.URL.createObjectURL(image) + } catch (exception) { + return reject(exception) } - - $img.src = window.URL.createObjectURL(image) - } catch (exception) { - return reject(exception) - } + }) }) diff --git a/src/components/Editor/Article/FloatingMenu/UploadImageButton.tsx b/src/components/Editor/Article/FloatingMenu/UploadImageButton.tsx index 7ce4e1b0c2..a437d53eae 100644 --- a/src/components/Editor/Article/FloatingMenu/UploadImageButton.tsx +++ b/src/components/Editor/Article/FloatingMenu/UploadImageButton.tsx @@ -5,7 +5,7 @@ import { useContext, useState } from 'react' import { ReactComponent as IconEditorMenuImage } from '@/public/static/icons/32px/editor-menu-image.svg' import { ACCEPTED_UPLOAD_IMAGE_TYPES, ASSET_TYPE } from '~/common/enums' -import { translate, validateImage } from '~/common/utils' +import { getFileType, translate, validateImage } from '~/common/utils' import { IconSpinner16, LanguageContext, @@ -22,6 +22,7 @@ export type UploadImageButtonProps = { file?: any url?: string type?: ASSET_TYPE.embed | ASSET_TYPE.embedaudio + mime?: string }) => Promise<{ id: string path: string @@ -59,7 +60,8 @@ const UploadImageButton: React.FC = ({ setUploading(true) for (const file of files) { - const { path } = await upload({ file, type: ASSET_TYPE.embed }) + const mime = (await getFileType(file))!.mime + const { path } = await upload({ file, type: ASSET_TYPE.embed, mime }) editor.chain().focus().setFigureImage({ src: path }).run() toast.success({ message: , diff --git a/src/components/Editor/SetCover/Uploader.tsx b/src/components/Editor/SetCover/Uploader.tsx index 44d922c519..67abfcae82 100644 --- a/src/components/Editor/SetCover/Uploader.tsx +++ b/src/components/Editor/SetCover/Uploader.tsx @@ -96,8 +96,8 @@ const Uploader: React.FC = ({ const file = event.target.files[0] event.target.value = '' - const isValidImage = await validateImage(file) - if (!isValidImage) { + const mime = await validateImage(file) + if (!mime) { return } @@ -118,6 +118,7 @@ const Uploader: React.FC = ({ type: ASSET_TYPE.cover, entityId, entityType, + mime, }, } const { data } = await upload({ diff --git a/src/components/FileUploader/AvatarUploader/index.tsx b/src/components/FileUploader/AvatarUploader/index.tsx index 7e32202ba0..0b6a584cc0 100644 --- a/src/components/FileUploader/AvatarUploader/index.tsx +++ b/src/components/FileUploader/AvatarUploader/index.tsx @@ -85,8 +85,8 @@ export const AvatarUploader: React.FC = ({ const file = event.target.files[0] event.target.value = '' - const isValidImage = await validateImage(file, true) - if (!isValidImage) { + const mime = await validateImage(file, true) + if (!mime) { return } @@ -101,6 +101,7 @@ export const AvatarUploader: React.FC = ({ type: isCircle ? ASSET_TYPE.circleAvatar : ASSET_TYPE.avatar, entityType: isCircle ? ENTITY_TYPE.circle : ENTITY_TYPE.user, entityId, + mime, }, } const { data } = await upload({ diff --git a/src/components/FileUploader/CoverUploader/index.tsx b/src/components/FileUploader/CoverUploader/index.tsx index 7bc6bf1dcb..2327ca1231 100644 --- a/src/components/FileUploader/CoverUploader/index.tsx +++ b/src/components/FileUploader/CoverUploader/index.tsx @@ -112,8 +112,8 @@ export const CoverUploader = ({ const file = event.target.files[0] event.target.value = '' - const isValidImage = await validateImage(file) - if (!isValidImage) { + const mime = await validateImage(file) + if (!mime) { return } @@ -123,7 +123,7 @@ export const CoverUploader = ({ } const variables = { - input: { file, type: assetType, entityId, entityType }, + input: { file, mime, type: assetType, entityId, entityType }, } const { data } = await upload({ variables: _omit(variables, ['input.file']), diff --git a/src/components/GQL/mutations/uploadFile.ts b/src/components/GQL/mutations/uploadFile.ts index cc3005f18a..6c21232733 100644 --- a/src/components/GQL/mutations/uploadFile.ts +++ b/src/components/GQL/mutations/uploadFile.ts @@ -12,7 +12,7 @@ export const SINGLE_FILE_UPLOAD = gql` ` export const DIRECT_IMAGE_UPLOAD = gql` - mutation DirectImageUpload($input: SingleFileUploadInput!) { + mutation DirectImageUpload($input: DirectImageUploadInput!) { directImageUpload(input: $input) { ...Asset }