diff --git a/.github/workflows/docker-pr.yml b/.github/workflows/docker-pr.yml index 5e4f2fc5..1e7a71f3 100644 --- a/.github/workflows/docker-pr.yml +++ b/.github/workflows/docker-pr.yml @@ -15,7 +15,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3.0.2 - name: Build Docker image - uses: docker/build-push-action@v3.1.0 + uses: docker/build-push-action@v3.1.1 with: context: . push: false diff --git a/.github/workflows/prod-deploy.yml b/.github/workflows/prod-deploy.yml index 857f9363..7147c759 100644 --- a/.github/workflows/prod-deploy.yml +++ b/.github/workflows/prod-deploy.yml @@ -86,7 +86,7 @@ jobs: with: images: ajbura/cinny - name: Build and push Docker image - uses: docker/build-push-action@v3.1.0 + uses: docker/build-push-action@v3.1.1 with: context: . platforms: linux/amd64,linux/arm64 diff --git a/olm.wasm b/olm.wasm old mode 100644 new mode 100755 index 3e308efc..eb0f50ad Binary files a/olm.wasm and b/olm.wasm differ diff --git a/package-lock.json b/package-lock.json index f258f7d1..46422b0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "cinny", - "version": "2.0.4", + "version": "2.1.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cinny", - "version": "2.0.4", + "version": "2.1.2", "license": "MIT", "dependencies": { "@alienfast/i18next-loader": "^1.1.4", - "@fontsource/inter": "^4.5.11", - "@fontsource/roboto": "^4.5.7", - "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz", + "@fontsource/inter": "^4.5.12", + "@fontsource/roboto": "^4.5.8", + "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.12.tgz", "@tippyjs/react": "^4.2.6", "babel-polyfill": "^6.26.0", "blurhash": "^1.1.5", @@ -29,7 +29,7 @@ "katex": "^0.16.0", "linkify-html": "^4.0.0-beta.5", "linkifyjs": "^4.0.0-beta.5", - "matrix-js-sdk": "^18.1.0", + "matrix-js-sdk": "^19.2.0", "micromark": "^3.0.10", "micromark-extension-gfm": "^2.0.1", "micromark-extension-math": "^2.0.2", @@ -75,7 +75,7 @@ "html-webpack-plugin": "^5.3.1", "mini-css-extract-plugin": "^2.6.1", "path-browserify": "^1.0.1", - "sass": "^1.54.1", + "sass": "^1.54.3", "sass-loader": "^13.0.2", "stream-browserify": "^3.0.0", "style-loader": "^3.3.1", @@ -1897,14 +1897,14 @@ } }, "node_modules/@fontsource/inter": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.11.tgz", - "integrity": "sha512-toizzQkfXL8YJcG/f8j3EYXYGQe4OxiDEItThSigvHU+cYNDw8HPp3wLYQX745hddsnHqOGCM4exitFSBOU8+w==" + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.12.tgz", + "integrity": "sha512-bGKk4/8tube/nCk8hav0ZDBVbzJzc7m0Vt4xF5p15IN4YImwGdtKG38Oq5bU8xHNS+VfvbFFCepgQNj7Pr/Lvg==" }, "node_modules/@fontsource/roboto": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.7.tgz", - "integrity": "sha512-m57UMER23Mk6Drg9OjtHW1Y+0KPGyZfE5XJoPTOsLARLar6013kJj4X2HICt+iFLJqIgTahA/QAvSn9lwF1EEw==" + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz", + "integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA==" }, "node_modules/@humanwhocodes/config-array": { "version": "0.10.4", @@ -2485,9 +2485,9 @@ "dev": true }, "node_modules/@matrix-org/olm": { - "version": "3.2.8", - "resolved": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz", - "integrity": "sha512-yCJzEYY2aG1z+7nxKYZC4DFYwQO/5iG019qgotJhauYJRhEG9gLrKTvXO6lRHS8TjnZzsZFZyO/hQUlI4Dryig==", + "version": "3.2.12", + "resolved": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.12.tgz", + "integrity": "sha512-muHkYUAXyRDg88YVFlmFY35vgLPovK2YPkuEtBfgnmBcxJvLpV9UMcMMxNkf8opjMV1k/NJ4niFQMzwd4UQOiA==", "license": "Apache-2.0" }, "node_modules/@nodelib/fs.scandir": { @@ -3076,8 +3076,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", "dev": true, - "optional": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3093,9 +3091,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "optional": true, - "peer": true + "dev": true }, "node_modules/ajv-keywords": { "version": "3.5.2", @@ -3527,12 +3523,9 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -3877,11 +3870,11 @@ } }, "node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", "dependencies": { - "base-x": "^3.0.2" + "base-x": "^4.0.0" } }, "node_modules/buffer": { @@ -9291,18 +9284,18 @@ "integrity": "sha512-9jl4wtWanUFSy2sr2lCjErN/oC8KTAtaeaozJtrgot1JiQcEI4Rda9OLgQ7nLKaqb4Z/QUx/fR3XpDzm5Jy1JA==" }, "node_modules/matrix-js-sdk": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-18.1.0.tgz", - "integrity": "sha512-O5D36paIsY7a2M2mOo7KKU7v1Mb3PVkmYKupXYcXd9gB/Ki1K4mds+vSDEhgkKyKwk6MK1AV/vgvp0xJCsttvg==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-19.2.0.tgz", + "integrity": "sha512-alvTasCTCo/XXSIkKEj8xKe1NMsyiVDDVIQdU9ZHI1aePq+DrAcx8CqB7L/dgjk842v+63Eke1f/jZuFWvjn4w==", "dependencies": { "@babel/runtime": "^7.12.5", "another-json": "^0.2.0", "browser-request": "^0.3.3", - "bs58": "^4.0.1", + "bs58": "^5.0.0", "content-type": "^1.0.4", "loglevel": "^1.7.1", "matrix-events-sdk": "^0.0.1-beta.7", - "p-retry": "^4.5.0", + "p-retry": "4", "qs": "^6.9.6", "request": "^2.88.2", "unhomoglyph": "^1.0.6" @@ -12388,9 +12381,9 @@ } }, "node_modules/sass": { - "version": "1.54.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.1.tgz", - "integrity": "sha512-GHJJr31Me32RjjUBagyzx8tzjKBUcDwo5239XANIRBq0adDu5iIG0aFO0i/TBb/4I9oyxkEv44nq/kL1DxdDhA==", + "version": "1.54.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.3.tgz", + "integrity": "sha512-fLodey5Qd41Pxp/Tk7Al97sViYwF/TazRc5t6E65O7JOk4XF8pzwIW7CvCxYVOfJFFI/1x5+elDyBIixrp+zrw==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -15985,14 +15978,14 @@ } }, "@fontsource/inter": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.11.tgz", - "integrity": "sha512-toizzQkfXL8YJcG/f8j3EYXYGQe4OxiDEItThSigvHU+cYNDw8HPp3wLYQX745hddsnHqOGCM4exitFSBOU8+w==" + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.12.tgz", + "integrity": "sha512-bGKk4/8tube/nCk8hav0ZDBVbzJzc7m0Vt4xF5p15IN4YImwGdtKG38Oq5bU8xHNS+VfvbFFCepgQNj7Pr/Lvg==" }, "@fontsource/roboto": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.7.tgz", - "integrity": "sha512-m57UMER23Mk6Drg9OjtHW1Y+0KPGyZfE5XJoPTOsLARLar6013kJj4X2HICt+iFLJqIgTahA/QAvSn9lwF1EEw==" + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz", + "integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA==" }, "@humanwhocodes/config-array": { "version": "0.10.4", @@ -16444,8 +16437,8 @@ "dev": true }, "@matrix-org/olm": { - "version": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz", - "integrity": "sha512-yCJzEYY2aG1z+7nxKYZC4DFYwQO/5iG019qgotJhauYJRhEG9gLrKTvXO6lRHS8TjnZzsZFZyO/hQUlI4Dryig==" + "version": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.12.tgz", + "integrity": "sha512-muHkYUAXyRDg88YVFlmFY35vgLPovK2YPkuEtBfgnmBcxJvLpV9UMcMMxNkf8opjMV1k/NJ4niFQMzwd4UQOiA==" }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -16967,14 +16960,15 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, - "requires": {}, + "requires": { + "ajv": "^8.0.0" + }, "dependencies": { "ajv": { - "version": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", "dev": true, - "optional": true, - "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -16986,9 +16980,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "optional": true, - "peer": true + "dev": true } } }, @@ -17347,12 +17339,9 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "requires": { - "safe-buffer": "^5.0.1" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" }, "base64-js": { "version": "1.5.1", @@ -17632,11 +17621,11 @@ } }, "bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", "requires": { - "base-x": "^3.0.2" + "base-x": "^4.0.0" } }, "buffer": { @@ -21794,18 +21783,18 @@ "integrity": "sha512-9jl4wtWanUFSy2sr2lCjErN/oC8KTAtaeaozJtrgot1JiQcEI4Rda9OLgQ7nLKaqb4Z/QUx/fR3XpDzm5Jy1JA==" }, "matrix-js-sdk": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-18.1.0.tgz", - "integrity": "sha512-O5D36paIsY7a2M2mOo7KKU7v1Mb3PVkmYKupXYcXd9gB/Ki1K4mds+vSDEhgkKyKwk6MK1AV/vgvp0xJCsttvg==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-19.2.0.tgz", + "integrity": "sha512-alvTasCTCo/XXSIkKEj8xKe1NMsyiVDDVIQdU9ZHI1aePq+DrAcx8CqB7L/dgjk842v+63Eke1f/jZuFWvjn4w==", "requires": { "@babel/runtime": "^7.12.5", "another-json": "^0.2.0", "browser-request": "^0.3.3", - "bs58": "^4.0.1", + "bs58": "^5.0.0", "content-type": "^1.0.4", "loglevel": "^1.7.1", "matrix-events-sdk": "^0.0.1-beta.7", - "p-retry": "^4.5.0", + "p-retry": "4", "qs": "^6.9.6", "request": "^2.88.2", "unhomoglyph": "^1.0.6" @@ -23996,9 +23985,9 @@ } }, "sass": { - "version": "1.54.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.1.tgz", - "integrity": "sha512-GHJJr31Me32RjjUBagyzx8tzjKBUcDwo5239XANIRBq0adDu5iIG0aFO0i/TBb/4I9oyxkEv44nq/kL1DxdDhA==", + "version": "1.54.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.3.tgz", + "integrity": "sha512-fLodey5Qd41Pxp/Tk7Al97sViYwF/TazRc5t6E65O7JOk4XF8pzwIW7CvCxYVOfJFFI/1x5+elDyBIixrp+zrw==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", diff --git a/package.json b/package.json index 314c8fce..efc9db5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cinny", - "version": "2.0.4", + "version": "2.1.2", "description": "Yet another matrix client", "main": "index.js", "engines": { @@ -16,9 +16,9 @@ "license": "MIT", "dependencies": { "@alienfast/i18next-loader": "^1.1.4", - "@fontsource/inter": "^4.5.11", - "@fontsource/roboto": "^4.5.7", - "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz", + "@fontsource/inter": "^4.5.12", + "@fontsource/roboto": "^4.5.8", + "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.12.tgz", "@tippyjs/react": "^4.2.6", "babel-polyfill": "^6.26.0", "blurhash": "^1.1.5", @@ -35,7 +35,7 @@ "katex": "^0.16.0", "linkify-html": "^4.0.0-beta.5", "linkifyjs": "^4.0.0-beta.5", - "matrix-js-sdk": "^18.1.0", + "matrix-js-sdk": "^19.2.0", "micromark": "^3.0.10", "micromark-extension-gfm": "^2.0.1", "micromark-extension-math": "^2.0.2", @@ -81,7 +81,7 @@ "html-webpack-plugin": "^5.3.1", "mini-css-extract-plugin": "^2.6.1", "path-browserify": "^1.0.1", - "sass": "^1.54.1", + "sass": "^1.54.3", "sass-loader": "^13.0.2", "stream-browserify": "^3.0.0", "style-loader": "^3.3.1", diff --git a/src/app/atoms/input/Input.jsx b/src/app/atoms/input/Input.jsx index d9f79eb0..96c94967 100644 --- a/src/app/atoms/input/Input.jsx +++ b/src/app/atoms/input/Input.jsx @@ -16,6 +16,7 @@ function Input({ { resizable ? ( ) : ( { if (typeof key !== 'string') return undefined; - let newKey = key?.replace(/\s/g, '-'); + let newKey = key?.replace(/\s/g, '_'); if (pack.getImages().get(newKey)) { newKey = suffixRename( newKey, diff --git a/src/app/molecules/image-pack/ImagePackUpload.jsx b/src/app/molecules/image-pack/ImagePackUpload.jsx index 645d5d77..a22955c0 100644 --- a/src/app/molecules/image-pack/ImagePackUpload.jsx +++ b/src/app/molecules/image-pack/ImagePackUpload.jsx @@ -44,11 +44,13 @@ function ImagePackUpload({ onUpload }) { const img = evt.target.files[0]; if (!img) return; setImgFile(img); + shortcodeRef.current.value = img.name.slice(0, img.name.indexOf('.')); shortcodeRef.current.focus(); }; const handleRemove = () => { setImgFile(null); inputRef.current.value = null; + shortcodeRef.current.value = ''; }; return ( diff --git a/src/app/molecules/media/Media.jsx b/src/app/molecules/media/Media.jsx index 08c4d5b3..e9f2e457 100644 --- a/src/app/molecules/media/Media.jsx +++ b/src/app/molecules/media/Media.jsx @@ -15,43 +15,7 @@ import ExternalSVG from '../../../../public/res/ic/outlined/external.svg'; import PlaySVG from '../../../../public/res/ic/outlined/play.svg'; import '../../i18n'; - -// https://github.com/matrix-org/matrix-react-sdk/blob/cd15e08fc285da42134817cce50de8011809cd53/src/utils/blobs.ts#L73 -const ALLOWED_BLOB_MIMETYPES = [ - 'image/jpeg', - 'image/gif', - 'image/png', - 'image/apng', - 'image/webp', - 'image/avif', - - 'video/mp4', - 'video/webm', - 'video/ogg', - 'video/quicktime', - - 'audio/mp4', - 'audio/webm', - 'audio/aac', - 'audio/mpeg', - 'audio/ogg', - 'audio/wave', - 'audio/wav', - 'audio/x-wav', - 'audio/x-pn-wav', - 'audio/flac', - 'audio/x-flac', -]; -function getBlobSafeMimeType(mimetype) { - if (!ALLOWED_BLOB_MIMETYPES.includes(mimetype)) { - return 'application/octet-stream'; - } - // Required for Chromium browsers - if (mimetype === 'video/quicktime') { - return 'video/mp4'; - } - return mimetype; -} +import { getBlobSafeMimeType } from '../../../util/mimetypes'; async function getDecryptedBlob(response, type, decryptData) { const arrayBuffer = await response.arrayBuffer(); @@ -163,6 +127,7 @@ function Image({ name, width, height, link, file, type, blurhash, }) { const [url, setUrl] = useState(null); + const [blur, setBlur] = useState(true); useEffect(() => { let unmounted = false; @@ -181,8 +146,8 @@ function Image({
- { blurhash && } - { url !== null && {name}} + { blurhash && blur && } + { url !== null && setBlur(false)} src={url || link} alt={name} />}
); @@ -231,11 +196,11 @@ function Sticker({ Sticker.defaultProps = { file: null, type: '', + width: null, + height: null, }; Sticker.propTypes = { name: PropTypes.string.isRequired, - width: null, - height: null, width: PropTypes.number, height: PropTypes.number, link: PropTypes.string.isRequired, @@ -295,6 +260,7 @@ function Video({ const [isLoading, setIsLoading] = useState(false); const [url, setUrl] = useState(null); const [thumbUrl, setThumbUrl] = useState(null); + const [blur, setBlur] = useState(true); useEffect(() => { let unmounted = false; @@ -309,16 +275,16 @@ function Video({ }; }, []); - async function loadVideo() { + const loadVideo = async () => { const myUrl = await getUrl(link, type, file); setUrl(myUrl); setIsLoading(false); - } + }; - function handlePlayVideo() { + const handlePlayVideo = () => { setIsLoading(true); loadVideo(); - } + }; const { t } = useTranslation(); @@ -331,15 +297,17 @@ function Video({ }} className="video-container" > - { url === null && blurhash && } - { url === null && thumbUrl !== null && ( - /* eslint-disable-next-line jsx-a11y/alt-text */ - - )} - { url === null && isLoading && } - { url === null && !isLoading && } - { url !== null && ( - /* eslint-disable-next-line jsx-a11y/media-has-caption */ + { url === null ? ( + <> + { blurhash && blur && } + { thumbUrl !== null && ( + setBlur(false)} alt={name} /> + )} + {isLoading && } + {!isLoading && } + + ) : ( + /* eslint-disable-next-line jsx-a11y/media-has-caption */ diff --git a/src/app/molecules/media/Media.scss b/src/app/molecules/media/Media.scss index b26b232a..7c73305a 100644 --- a/src/app/molecules/media/Media.scss +++ b/src/app/molecules/media/Media.scss @@ -27,14 +27,21 @@ white-space: initial; } +.sticker-container { + display: inline-flex; + max-width: 128px; + width: 100%; + & img { + width: 100% !important; + } +} + .image-container, .video-container, .audio-container { font-size: 0; line-height: 0; - position: relative; - display: flex; justify-content: center; align-items: center; @@ -48,7 +55,6 @@ .video-container { & img, & canvas { - position: absolute; max-width: unset !important; width: 100% !important; height: 100%; @@ -57,18 +63,13 @@ } } -.sticker-container { - display: inline-flex; - max-width: 128px; - width: 100%; - & img { - width: 100% !important; - } -} - .video-container { + position: relative; & .ic-btn-surface { background-color: var(--bg-surface-low); + } + & .ic-btn-surface, + & .donut-spinner { position: absolute; } video { diff --git a/src/app/molecules/message/Message.jsx b/src/app/molecules/message/Message.jsx index d60a0c83..e21103a3 100644 --- a/src/app/molecules/message/Message.jsx +++ b/src/app/molecules/message/Message.jsx @@ -38,6 +38,7 @@ import CmdIC from '../../../../public/res/ic/outlined/cmd.svg'; import BinIC from '../../../../public/res/ic/outlined/bin.svg'; import { confirmDialog } from '../confirm-dialog/ConfirmDialog'; +import { getBlobSafeMimeType } from '../../../util/mimetypes'; import '../../i18n'; @@ -210,7 +211,13 @@ const MessageBody = React.memo(({ let content = null; if (isCustomHTML) { try { - content = twemojify(sanitizeCustomHtml(body), undefined, true, false, true); + content = twemojify( + sanitizeCustomHtml(initMatrix.matrixClient, body), + undefined, + true, + false, + true, + ); } catch { console.error('Malformed custom html: ', body); content = twemojify(body, undefined); @@ -626,7 +633,12 @@ function genMediaContent(mE) { if (typeof mediaMXC === 'undefined' || mediaMXC === '') return Malformed event; let msgType = mE.getContent()?.msgtype; - if (mE.getType() === 'm.sticker') msgType = 'm.sticker'; + const safeMimetype = getBlobSafeMimeType(mContent.info?.mimetype); + if (mE.getType() === 'm.sticker') { + msgType = 'm.sticker'; + } else if (safeMimetype === 'application/octet-stream') { + msgType = 'm.file'; + } const blurhash = mContent?.info?.['xyz.amorgan.blurhash']; diff --git a/src/app/organisms/emoji-board/EmojiBoard.jsx b/src/app/organisms/emoji-board/EmojiBoard.jsx index b97cab0a..d9762323 100644 --- a/src/app/organisms/emoji-board/EmojiBoard.jsx +++ b/src/app/organisms/emoji-board/EmojiBoard.jsx @@ -252,6 +252,58 @@ function EmojiBoard({ onSelect, searchRef }) { return (
+ +
+ {recentEmojis.length > 0 && ( + openGroup(0)} + src={RecentClockIC} + tooltip="Recent" + tooltipPlacement="left" + /> + )} +
+ { + availableEmojis.map((pack) => { + const src = initMatrix.matrixClient + .mxcUrlToHttp(pack.avatarUrl ?? pack.getEmojis()[0].mxc); + return ( + openGroup(recentOffset + pack.packIndex)} + src={src} + key={pack.packIndex} + tooltip={pack.displayName ?? 'Unknown'} + tooltipPlacement="left" + isImage + /> + ); + }) + } +
+
+ { + [ + [0, EmojiIC, 'Smilies'], + [1, DogIC, 'Animals'], + [2, CupIC, 'Food'], + [3, BallIC, 'Activities'], + [4, PhotoIC, 'Travel'], + [5, BulbIC, 'Objects'], + [6, PeaceIC, 'Symbols'], + [7, FlagIC, 'Flags'], + ].map(([indx, ico, name]) => ( + openGroup(recentOffset + availableEmojis.length + indx)} + key={indx} + src={ico} + tooltip={name} + tooltipPlacement="left" + /> + )) + } +
+
+
@@ -285,58 +337,6 @@ function EmojiBoard({ onSelect, searchRef }) { :slight_smile:
- -
- {recentEmojis.length > 0 && ( - openGroup(0)} - src={RecentClockIC} - tooltip="Recent" - tooltipPlacement="right" - /> - )} -
- { - availableEmojis.map((pack) => { - const src = initMatrix.matrixClient - .mxcUrlToHttp(pack.avatarUrl ?? pack.getEmojis()[0].mxc); - return ( - openGroup(recentOffset + pack.packIndex)} - src={src} - key={pack.packIndex} - tooltip={pack.displayName ?? 'Unknown'} - tooltipPlacement="right" - isImage - /> - ); - }) - } -
-
- { - [ - [0, EmojiIC, 'Smilies'], - [1, DogIC, 'Animals'], - [2, CupIC, 'Food'], - [3, BallIC, 'Activities'], - [4, PhotoIC, 'Travel'], - [5, BulbIC, 'Objects'], - [6, PeaceIC, 'Symbols'], - [7, FlagIC, 'Flags'], - ].map(([indx, ico, name]) => ( - openGroup(recentOffset + availableEmojis.length + indx)} - key={indx} - src={ico} - tooltip={name} - tooltipPlacement="right" - /> - )) - } -
-
-
); } diff --git a/src/app/organisms/emoji-board/EmojiBoard.scss b/src/app/organisms/emoji-board/EmojiBoard.scss index 6883e18e..7f2e2384 100644 --- a/src/app/organisms/emoji-board/EmojiBoard.scss +++ b/src/app/organisms/emoji-board/EmojiBoard.scss @@ -25,8 +25,7 @@ min-height: 100%; padding: 4px 6px; - background-color: var(--bg-surface-low); - @include dir.side(border, 1px solid var(--bg-surface-border), none); + @include dir.side(border, none, 1px solid var(--bg-surface-border)); position: relative; @@ -122,8 +121,11 @@ @include dir.side(margin, var(--left-margin), var(--right-margin)); } & .emoji { - width: 38px; - height: 38px; + max-width: 38px; + max-height: 38px; + width: 100%; + height: 100%; + overflow: hidden; object-fit: contain; padding: var(--emoji-padding); cursor: pointer; diff --git a/src/app/organisms/room/RoomViewInput.jsx b/src/app/organisms/room/RoomViewInput.jsx index afeefcbb..d0827bae 100644 --- a/src/app/organisms/room/RoomViewInput.jsx +++ b/src/app/organisms/room/RoomViewInput.jsx @@ -342,6 +342,7 @@ function RoomViewInput({ {u1Jsx}, {u2Jsx}, {u3Jsx} and {othersCount} other are {actionStr}; + return <>{u1Jsx}, {u2Jsx}, {u3Jsx} and {othersCount} others are {actionStr}; } function parseTimelineChange(mEvent) { diff --git a/src/app/organisms/sticker-board/StickerBoard.jsx b/src/app/organisms/sticker-board/StickerBoard.jsx index 53b75635..91e25918 100644 --- a/src/app/organisms/sticker-board/StickerBoard.jsx +++ b/src/app/organisms/sticker-board/StickerBoard.jsx @@ -1,6 +1,6 @@ /* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable jsx-a11y/no-static-element-interactions */ -import React from 'react'; +import React, { useRef } from 'react'; import PropTypes from 'prop-types'; import './StickerBoard.scss'; @@ -9,10 +9,12 @@ import { getRelevantPacks } from '../emoji-board/custom-emoji'; import Text from '../../atoms/text/Text'; import ScrollView from '../../atoms/scroll/ScrollView'; +import IconButton from '../../atoms/button/IconButton'; function StickerBoard({ roomId, onSelect }) { const mx = initMatrix.matrixClient; const room = mx.getRoom(roomId); + const scrollRef = useRef(null); const parentIds = initMatrix.roomList.getAllParentSpaces(room.roomId); const parentRooms = [...parentIds].map((id) => mx.getRoom(id)); @@ -38,6 +40,11 @@ function StickerBoard({ roomId, onSelect }) { onSelect(stickerData); }; + const openGroup = (groupIndex) => { + const scrollContent = scrollRef.current.firstElementChild; + scrollContent.children[groupIndex].scrollIntoView(); + }; + const renderPack = (pack) => (
{pack.displayName ?? 'Unknown'} @@ -50,6 +57,7 @@ function StickerBoard({ roomId, onSelect }) { alt={sticker.shortcode} title={sticker.body ?? sticker.shortcode} data-mx-sticker={sticker.mxc} + loading="lazy" /> ))}
@@ -58,8 +66,27 @@ function StickerBoard({ roomId, onSelect }) { return (
+ {packs.length > 0 && ( + +
+ {packs.map((pack, index) => { + const src = mx.mxcUrlToHttp(pack.avatarUrl ?? pack.getStickers()[0].mxc); + return ( + openGroup(index)} + src={src} + tooltip={pack.displayName || 'Unknown'} + tooltipPlacement="left" + isImage + /> + ); + })} +
+
+ )}
- +
.scrollbar { + width: initial; + height: var(--sticker-board-height); + } + + &__sidebar { + display: flex; + flex-direction: column; + min-height: 100%; + padding: 4px 6px; + @include dir.side(border, none, 1px solid var(--bg-surface-border)); + } &__container { flex-grow: 1; diff --git a/src/client/action/logout.js b/src/client/action/logout.js index 3c7b8486..c4047bb7 100644 --- a/src/client/action/logout.js +++ b/src/client/action/logout.js @@ -1,13 +1,16 @@ import initMatrix from '../initMatrix'; -function logout() { +async function logout() { const mx = initMatrix.matrixClient; mx.stopClient(); - mx.logout().then(() => { - mx.clearStores(); - window.localStorage.clear(); - window.location.reload(); - }); + try { + await mx.logout(); + } catch { + // ignore if failed to logout + } + mx.clearStores(); + window.localStorage.clear(); + window.location.reload(); } export default logout; diff --git a/src/client/initMatrix.js b/src/client/initMatrix.js index 2118be56..936334ce 100644 --- a/src/client/initMatrix.js +++ b/src/client/initMatrix.js @@ -33,7 +33,6 @@ class InitMatrix extends EventEmitter { accessToken: secret.accessToken, userId: secret.userId, store: indexedDBStore, - sessionStore: new sdk.WebStorageSessionStore(global.localStorage), cryptoStore: new sdk.IndexedDBCryptoStore(global.indexedDB, 'crypto-store'), deviceId: secret.deviceId, timelineSupport: true, diff --git a/src/client/state/RoomsInput.js b/src/client/state/RoomsInput.js index b9215c85..9b662880 100644 --- a/src/client/state/RoomsInput.js +++ b/src/client/state/RoomsInput.js @@ -6,10 +6,14 @@ import { math } from 'micromark-extension-math'; import { encode } from 'blurhash'; import { getShortcodeToEmoji } from '../../app/organisms/emoji-board/custom-emoji'; import { mathExtensionHtml, spoilerExtension, spoilerExtensionHtml } from '../../util/markdown'; +import { getBlobSafeMimeType } from '../../util/mimetypes'; +import { sanitizeText } from '../../util/sanitize'; import cons from './cons'; import settings from './settings'; const blurhashField = 'xyz.amorgan.blurhash'; +const MXID_REGEX = /\B@\S+:\S+\.\S+[^.,:;?!\s]/g; +const SHORTCODE_REGEX = /\B:([\w-]+):\B/g; function encodeBlurhash(img) { const canvas = document.createElement('canvas'); @@ -130,38 +134,45 @@ function bindReplyToContent(roomId, reply, content) { return newContent; } -function formatAndEmojifyText(mx, roomList, roomId, text) { - const room = mx.getRoom(roomId); +function findAndReplace(text, regex, filter, replace) { + let copyText = text; + Array.from(copyText.matchAll(regex)) + .filter(filter) + .reverse() /* to replace backward to forward */ + .forEach((match) => { + const matchText = match[0]; + const tag = replace(match); + + copyText = copyText.substr(0, match.index) + + tag + + copyText.substr(match.index + matchText.length); + }); + return copyText; +} + +function formatUserPill(room, text) { const { userIdsToDisplayNames } = room.currentState; - const parentIds = roomList.getAllParentSpaces(roomId); + return findAndReplace( + text, + MXID_REGEX, + (match) => userIdsToDisplayNames[match[0]], + (match) => ( + `@${userIdsToDisplayNames[match[0]]}` + ), + ); +} + +function formatEmoji(mx, room, roomList, text) { + const parentIds = roomList.getAllParentSpaces(room.roomId); const parentRooms = [...parentIds].map((id) => mx.getRoom(id)); const allEmoji = getShortcodeToEmoji(mx, [room, ...parentRooms]); - let formattedText; - if (settings.isMarkdown) { - formattedText = getFormattedBody(text); - } else { - formattedText = text; - } - - const MXID_REGEX = /\B@\S+:\S+\.\S+[^.,:;?!\s]/g; - Array.from(formattedText.matchAll(MXID_REGEX)) - .filter((mxidMatch) => userIdsToDisplayNames[mxidMatch[0]]) - .reverse() - .forEach((mxidMatch) => { - const tag = `${userIdsToDisplayNames[mxidMatch[0]]}`; - - formattedText = formattedText.substr(0, mxidMatch.index) - + tag - + formattedText.substr(mxidMatch.index + mxidMatch[0].length); - }); - - const SHORTCODE_REGEX = /\B:([\w-]+):\B/g; - Array.from(formattedText.matchAll(SHORTCODE_REGEX)) - .filter((shortcodeMatch) => allEmoji.has(shortcodeMatch[1])) - .reverse() /* Reversing the array ensures that indices are preserved as we start replacing */ - .forEach((shortcodeMatch) => { - const emoji = allEmoji.get(shortcodeMatch[1]); + return findAndReplace( + text, + SHORTCODE_REGEX, + (match) => allEmoji.has(match[1]), + (match) => { + const emoji = allEmoji.get(match[1]); let tag; if (emoji.mxc) { @@ -175,13 +186,9 @@ function formatAndEmojifyText(mx, roomList, roomId, text) { } else { tag = emoji.unicode; } - - formattedText = formattedText.substr(0, shortcodeMatch.index) - + tag - + formattedText.substr(shortcodeMatch.index + shortcodeMatch[0].length); - }); - - return formattedText; + return tag; + }, + ); } class RoomsInput extends EventEmitter { @@ -274,6 +281,7 @@ class RoomsInput extends EventEmitter { } async sendInput(roomId) { + const room = this.matrixClient.getRoom(roomId); const input = this.getInput(roomId); input.isSending = true; this.roomIdToInput.set(roomId, input); @@ -283,19 +291,27 @@ class RoomsInput extends EventEmitter { } if (this.getMessage(roomId).trim() !== '') { + const rawMessage = input.message; let content = { - body: input.message, + body: rawMessage, msgtype: 'm.text', }; // Apply formatting if relevant - const formattedBody = formatAndEmojifyText( - this.matrixClient, - this.roomList, - roomId, - input.message, + let formattedBody = settings.isMarkdown + ? getFormattedBody(rawMessage) + : sanitizeText(rawMessage); + + formattedBody = formatUserPill(room, formattedBody); + formattedBody = formatEmoji(this.matrixClient, room, this.roomList, formattedBody); + + content.body = findAndReplace( + content.body, + MXID_REGEX, + (match) => room.currentState.userIdsToDisplayNames[match[0]], + (match) => `@${room.currentState.userIdsToDisplayNames[match[0]]}`, ); - if (formattedBody !== input.message) { + if (formattedBody !== sanitizeText(rawMessage)) { // Formatting was applied, and we need to switch to custom HTML content.format = 'org.matrix.custom.html'; content.formatted_body = formattedBody; @@ -340,7 +356,7 @@ class RoomsInput extends EventEmitter { } async sendFile(roomId, file) { - const fileType = file.type.slice(0, file.type.indexOf('/')); + const fileType = getBlobSafeMimeType(file.type).slice(0, file.type.indexOf('/')); const info = { mimetype: file.type, size: file.size, @@ -446,6 +462,7 @@ class RoomsInput extends EventEmitter { } async sendEditedMessage(roomId, mEvent, editedBody) { + const room = this.matrixClient.getRoom(roomId); const isReply = typeof mEvent.getWireContent()['m.relates_to']?.['m.in_reply_to'] !== 'undefined'; const content = { @@ -462,13 +479,19 @@ class RoomsInput extends EventEmitter { }; // Apply formatting if relevant - const formattedBody = formatAndEmojifyText( - this.matrixClient, - this.roomList, - roomId, - editedBody, + let formattedBody = settings.isMarkdown + ? getFormattedBody(editedBody) + : sanitizeText(editedBody); + formattedBody = formatUserPill(room, formattedBody); + formattedBody = formatEmoji(this.matrixClient, room, this.roomList, formattedBody); + + content.body = findAndReplace( + content.body, + MXID_REGEX, + (match) => room.currentState.userIdsToDisplayNames[match[0]], + (match) => `@${room.currentState.userIdsToDisplayNames[match[0]]}`, ); - if (formattedBody !== editedBody) { + if (formattedBody !== sanitizeText(editedBody)) { content.formatted_body = ` * ${formattedBody}`; content.format = 'org.matrix.custom.html'; content['m.new_content'].formatted_body = formattedBody; diff --git a/src/client/state/cons.js b/src/client/state/cons.js index 49d32a84..0dc01d49 100644 --- a/src/client/state/cons.js +++ b/src/client/state/cons.js @@ -1,5 +1,5 @@ const cons = { - version: '2.0.4', + version: '2.1.2', secretKey: { ACCESS_TOKEN: 'cinny_access_token', DEVICE_ID: 'cinny_device_id', diff --git a/src/util/common.js b/src/util/common.js index 57891a9d..83fd20fe 100644 --- a/src/util/common.js +++ b/src/util/common.js @@ -166,6 +166,9 @@ export function scaleDownImage(imageFile, width, height) { img.onload = () => { let newWidth = img.width; let newHeight = img.height; + if (newHeight <= height && newWidth <= width) { + resolve(imageFile); + } if (newHeight > height) { newWidth = Math.floor(newWidth * (height / newHeight)); diff --git a/src/util/mimetypes.js b/src/util/mimetypes.js new file mode 100644 index 00000000..121ae069 --- /dev/null +++ b/src/util/mimetypes.js @@ -0,0 +1,37 @@ +// https://github.com/matrix-org/matrix-react-sdk/blob/cd15e08fc285da42134817cce50de8011809cd53/src/utils/blobs.ts +export const ALLOWED_BLOB_MIMETYPES = [ + 'image/jpeg', + 'image/gif', + 'image/png', + 'image/apng', + 'image/webp', + 'image/avif', + + 'video/mp4', + 'video/webm', + 'video/ogg', + 'video/quicktime', + + 'audio/mp4', + 'audio/webm', + 'audio/aac', + 'audio/mpeg', + 'audio/ogg', + 'audio/wave', + 'audio/wav', + 'audio/x-wav', + 'audio/x-pn-wav', + 'audio/flac', + 'audio/x-flac', +]; + +export function getBlobSafeMimeType(mimetype) { + if (!ALLOWED_BLOB_MIMETYPES.includes(mimetype)) { + return 'application/octet-stream'; + } + // Required for Chromium browsers + if (mimetype === 'video/quicktime') { + return 'video/mp4'; + } + return mimetype; +} diff --git a/src/util/sanitize.js b/src/util/sanitize.js index 5351a1a0..0f88c62b 100644 --- a/src/util/sanitize.js +++ b/src/util/sanitize.js @@ -1,7 +1,7 @@ import sanitizeHtml from 'sanitize-html'; -import initMatrix from '../client/initMatrix'; const MAX_TAG_NESTING = 100; +let mx = null; const permittedHtmlTags = [ 'font', 'del', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', @@ -54,7 +54,7 @@ function transformATag(tagName, attribs) { 'data-mx-pill': userId, }, }; - if (userId === initMatrix.matrixClient.getUserId()) { + if (userId === mx?.getUserId()) { pill.attribs['data-mx-ping'] = undefined; } return pill; @@ -76,17 +76,28 @@ function transformATag(tagName, attribs) { function transformImgTag(tagName, attribs) { const { src } = attribs; - const mx = initMatrix.matrixClient; + if (src.startsWith('mxc://') === false) { + return { + tagName: 'a', + attribs: { + href: src, + rel: 'noopener', + target: '_blank', + }, + text: attribs.alt || src, + }; + } return { tagName, attribs: { ...attribs, - src: src.startsWith('mxc://') ? mx.mxcUrlToHttp(src) : src, + src: mx?.mxcUrlToHttp(src), }, }; } -export function sanitizeCustomHtml(body) { +export function sanitizeCustomHtml(matrixClient, body) { + mx = matrixClient; return sanitizeHtml(body, { allowedTags: permittedHtmlTags, allowedAttributes: permittedTagToAttributes,