From e68c56b334107862ab2eec5635c11c40c050f00b Mon Sep 17 00:00:00 2001 From: Krishan <33421343+kfiven@users.noreply.github.com> Date: Sun, 4 Aug 2024 20:15:10 +1000 Subject: [PATCH 1/9] Release v4.1.0 (#1867) --- package-lock.json | 4 ++-- package.json | 2 +- src/app/pages/auth/AuthFooter.tsx | 2 +- src/app/pages/client/WelcomePage.tsx | 2 +- src/client/state/cons.js | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8b00ff07..f5aca0e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cinny", - "version": "4.0.3", + "version": "4.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cinny", - "version": "4.0.3", + "version": "4.1.0", "license": "AGPL-3.0-only", "dependencies": { "@atlaskit/pragmatic-drag-and-drop": "1.1.6", diff --git a/package.json b/package.json index d06b9601..dc4a040b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cinny", - "version": "4.0.3", + "version": "4.1.0", "description": "Yet another matrix client", "main": "index.js", "type": "module", diff --git a/src/app/pages/auth/AuthFooter.tsx b/src/app/pages/auth/AuthFooter.tsx index 4483ec15..b6e94d2f 100644 --- a/src/app/pages/auth/AuthFooter.tsx +++ b/src/app/pages/auth/AuthFooter.tsx @@ -15,7 +15,7 @@ export function AuthFooter() { target="_blank" rel="noreferrer" > - v4.0.3 + v4.1.0 Twitter diff --git a/src/app/pages/client/WelcomePage.tsx b/src/app/pages/client/WelcomePage.tsx index 8cf6b9ff..1e146a04 100644 --- a/src/app/pages/client/WelcomePage.tsx +++ b/src/app/pages/client/WelcomePage.tsx @@ -24,7 +24,7 @@ export function WelcomePage() { target="_blank" rel="noreferrer noopener" > - v4.0.3 + v4.1.0 } diff --git a/src/client/state/cons.js b/src/client/state/cons.js index 157d4a62..f7f65db2 100644 --- a/src/client/state/cons.js +++ b/src/client/state/cons.js @@ -1,5 +1,5 @@ const cons = { - version: '4.0.3', + version: '4.1.0', secretKey: { ACCESS_TOKEN: 'cinny_access_token', DEVICE_ID: 'cinny_device_id', From b4ce8a7cab727c51327c9434ef4bdd765fd7778b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 23:21:11 +1000 Subject: [PATCH 2/9] Bump docker/build-push-action from 6.5.0 to 6.6.1 (#1891) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.5.0 to 6.6.1. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v6.5.0...v6.6.1) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-pr.yml | 2 +- .github/workflows/prod-deploy.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-pr.yml b/.github/workflows/docker-pr.yml index 7b1e2abd..827ca66e 100644 --- a/.github/workflows/docker-pr.yml +++ b/.github/workflows/docker-pr.yml @@ -13,7 +13,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4.1.7 - name: Build Docker image - uses: docker/build-push-action@v6.5.0 + uses: docker/build-push-action@v6.6.1 with: context: . push: false diff --git a/.github/workflows/prod-deploy.yml b/.github/workflows/prod-deploy.yml index 24a1be01..d340cb77 100644 --- a/.github/workflows/prod-deploy.yml +++ b/.github/workflows/prod-deploy.yml @@ -90,7 +90,7 @@ jobs: ${{ secrets.DOCKER_USERNAME }}/cinny ghcr.io/${{ github.repository }} - name: Build and push Docker image - uses: docker/build-push-action@v6.5.0 + uses: docker/build-push-action@v6.6.1 with: context: . platforms: linux/amd64,linux/arm64 From ac1797344c0b8d0ded6019f33f0c9f17973de746 Mon Sep 17 00:00:00 2001 From: aceArt-GmbH <33117017+aceArt-GmbH@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:29:34 +0200 Subject: [PATCH 3/9] Add translation support using i18next (#1576) Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com> --- package-lock.json | 134 +++++++++++++++++++++++-- package.json | 4 + public/locales/de.json | 7 ++ public/locales/en.json | 7 ++ src/app/features/room/RoomTimeline.tsx | 4 +- src/app/i18n.ts | 31 ++++++ src/index.tsx | 3 + vite.config.js | 4 + 8 files changed, 184 insertions(+), 10 deletions(-) create mode 100644 public/locales/de.json create mode 100644 public/locales/en.json create mode 100644 src/app/i18n.ts diff --git a/package-lock.json b/package-lock.json index f5aca0e7..061d3ded 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,9 @@ "formik": "2.4.6", "html-dom-parser": "4.0.0", "html-react-parser": "4.2.0", + "i18next": "23.12.2", + "i18next-browser-languagedetector": "8.0.0", + "i18next-http-backend": "2.5.2", "immer": "9.0.16", "is-hotkey": "0.2.0", "jotai": "2.6.0", @@ -54,6 +57,7 @@ "react-dom": "18.2.0", "react-error-boundary": "4.0.13", "react-google-recaptcha": "2.1.0", + "react-i18next": "15.0.0", "react-modal": "3.16.1", "react-range": "1.8.14", "react-router-dom": "6.20.0", @@ -438,11 +442,12 @@ } }, "node_modules/@babel/runtime": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", - "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -467,11 +472,6 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "dev": true }, - "node_modules/@babel/runtime/node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, "node_modules/@babel/template": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", @@ -6146,6 +6146,15 @@ "entities": "^4.5.0" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-react-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-4.2.0.tgz", @@ -6191,6 +6200,76 @@ "node": ">= 6" } }, + "node_modules/i18next": { + "version": "23.12.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.12.2.tgz", + "integrity": "sha512-XIeh5V+bi8SJSWGL3jqbTEBW5oD6rbP5L+E7dVQh1MNTxxYef0x15rhJVcRb7oiuq4jLtgy2SD8eFlf6P2cmqg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz", + "integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-http-backend": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.5.2.tgz", + "integrity": "sha512-+K8HbDfrvc1/2X8jpb7RLhI9ZxBDpx3xogYkQwGKlWAUXLSEGXzgdt3EcUjLlBCdMwdQY+K+EUF6oh8oB6rwHw==", + "license": "MIT", + "dependencies": { + "cross-fetch": "4.0.0" + } + }, + "node_modules/i18next-http-backend/node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/i18next-http-backend/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -7717,6 +7796,28 @@ "react": ">=16.4.1" } }, + "node_modules/react-i18next": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.0.tgz", + "integrity": "sha512-2O3IgF4zivg57Q6p6i+ChDgJ371IDcEWbuWC6gvoh5NbkDMs0Q+O7RPr4v61+Se32E0V+LmtwePAeqWZW0bi6g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.8", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -7824,6 +7925,12 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -9294,6 +9401,15 @@ "@esbuild/win32-x64": "0.19.12" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/warning": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", diff --git a/package.json b/package.json index dc4a040b..8c66068b 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,9 @@ "formik": "2.4.6", "html-dom-parser": "4.0.0", "html-react-parser": "4.2.0", + "i18next": "23.12.2", + "i18next-browser-languagedetector": "8.0.0", + "i18next-http-backend": "2.5.2", "immer": "9.0.16", "is-hotkey": "0.2.0", "jotai": "2.6.0", @@ -65,6 +68,7 @@ "react-dom": "18.2.0", "react-error-boundary": "4.0.13", "react-google-recaptcha": "2.1.0", + "react-i18next": "15.0.0", "react-modal": "3.16.1", "react-range": "1.8.14", "react-router-dom": "6.20.0", diff --git a/public/locales/de.json b/public/locales/de.json new file mode 100644 index 00000000..43a37160 --- /dev/null +++ b/public/locales/de.json @@ -0,0 +1,7 @@ +{ + "Organisms": { + "RoomCommon": { + "changed_room_name": " hat den Raum Name geƤndert" + } + } +} diff --git a/public/locales/en.json b/public/locales/en.json new file mode 100644 index 00000000..7a2534b8 --- /dev/null +++ b/public/locales/en.json @@ -0,0 +1,7 @@ +{ + "Organisms": { + "RoomCommon": { + "changed_room_name": " changed room name" + } + } +} diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index 6e503703..84ce8af1 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -46,6 +46,7 @@ import { } from 'folds'; import { isKeyHotkey } from 'is-hotkey'; import { Opts as LinkifyOpts } from 'linkifyjs'; +import { useTranslation } from 'react-i18next'; import { decryptFile, eventWithShortcode, @@ -958,6 +959,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli }, [editor] ); + const { t } = useTranslation(); const renderMatrixEvent = useMatrixEventRenderer< [string, MatrixEvent, number, EventTimelineSet, boolean] @@ -1273,7 +1275,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli {senderName} - {' changed room name'} + {t('Organisms.RoomCommon.changed_room_name')} } diff --git a/src/app/i18n.ts b/src/app/i18n.ts new file mode 100644 index 00000000..9e83805d --- /dev/null +++ b/src/app/i18n.ts @@ -0,0 +1,31 @@ +import i18n from 'i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; +import Backend, { HttpBackendOptions } from 'i18next-http-backend'; +import { initReactI18next } from 'react-i18next'; +import { trimTrailingSlash } from './utils/common'; + +i18n + // i18next-http-backend + // loads translations from your server + // https://github.com/i18next/i18next-http-backend + .use(Backend) + // detect user language + // learn more: https://github.com/i18next/i18next-browser-languageDetector + .use(LanguageDetector) + // pass the i18n instance to react-i18next. + .use(initReactI18next) + // init i18next + // for all options read: https://www.i18next.com/overview/configuration-options + .init({ + debug: false, + fallbackLng: 'en', + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + }, + load: 'languageOnly', + backend: { + loadPath: `${trimTrailingSlash(import.meta.env.BASE_URL)}/public/locales/{{lng}}.json`, + }, + }); + +export default i18n; diff --git a/src/index.tsx b/src/index.tsx index 1d864203..a289ed1c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -14,6 +14,9 @@ import settings from './client/state/settings'; import App from './app/pages/App'; +// import i18n (needs to be bundled ;)) +import './app/i18n'; + document.body.classList.add(configClass, varsClass); settings.applyTheme(); diff --git a/vite.config.js b/vite.config.js index 1255f81c..7b5ec250 100644 --- a/vite.config.js +++ b/vite.config.js @@ -35,6 +35,10 @@ const copyFiles = { src: 'public/res/android', dest: 'public/', }, + { + src: 'public/locales', + dest: 'public/', + }, ], }; From 7e7bee8f48229a7ebac0c64d5f4f744d81eca202 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 23:38:35 +1000 Subject: [PATCH 4/9] Bump actions/upload-artifact from 4.3.4 to 4.3.6 (#1890) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.4 to 4.3.6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4.3.4...v4.3.6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-pull-request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml index 67ae5f1e..ae1097be 100644 --- a/.github/workflows/build-pull-request.yml +++ b/.github/workflows/build-pull-request.yml @@ -25,7 +25,7 @@ jobs: NODE_OPTIONS: '--max_old_space_size=4096' run: npm run build - name: Upload artifact - uses: actions/upload-artifact@v4.3.4 + uses: actions/upload-artifact@v4.3.6 with: name: preview path: dist @@ -33,7 +33,7 @@ jobs: - name: Save pr number run: echo ${PR_NUMBER} > ./pr.txt - name: Upload pr number - uses: actions/upload-artifact@v4.3.4 + uses: actions/upload-artifact@v4.3.6 with: name: pr path: ./pr.txt From 830d05e217fd6640c219d4eb419895ca8669e111 Mon Sep 17 00:00:00 2001 From: greentore <117551249+greentore@users.noreply.github.com> Date: Thu, 15 Aug 2024 16:52:32 +0200 Subject: [PATCH 5/9] Add basic `m.thread` support (#1349) * Add basic `m.thread` support * Fix types * Update to v4 * Fix auto formatting mess * Add threaded reply indicators * Fix reply overflow * Fix replying to edited threaded replies * Add thread indicator to room input * Fix editing encrypted events * Use `toRem` function for converting units --------- Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com> --- src/app/components/message/Reply.css.ts | 19 +++++ src/app/components/message/Reply.tsx | 81 +++++++++++-------- .../message-search/SearchResultGroup.tsx | 16 ++-- src/app/features/room/RoomInput.tsx | 40 +++++---- src/app/features/room/RoomTimeline.tsx | 33 ++++---- src/app/pages/client/inbox/Notifications.tsx | 13 +-- src/app/state/room/roomInputDrafts.ts | 4 +- src/app/utils/room.ts | 19 +++-- 8 files changed, 140 insertions(+), 85 deletions(-) diff --git a/src/app/components/message/Reply.css.ts b/src/app/components/message/Reply.css.ts index 014a2840..06799391 100644 --- a/src/app/components/message/Reply.css.ts +++ b/src/app/components/message/Reply.css.ts @@ -5,6 +5,25 @@ export const ReplyBend = style({ flexShrink: 0, }); +export const ThreadIndicator = style({ + opacity: config.opacity.P300, + gap: toRem(2), + + selectors: { + 'button&': { + cursor: 'pointer', + }, + ':hover&': { + opacity: config.opacity.P500, + }, + }, +}); + +export const ThreadIndicatorIcon = style({ + width: toRem(14), + height: toRem(14), +}); + export const Reply = style({ marginBottom: toRem(1), minWidth: 0, diff --git a/src/app/components/message/Reply.tsx b/src/app/components/message/Reply.tsx index 85383cdb..82a9d919 100644 --- a/src/app/components/message/Reply.tsx +++ b/src/app/components/message/Reply.tsx @@ -1,7 +1,7 @@ import { Box, Icon, Icons, Text, as, color, toRem } from 'folds'; import { EventTimelineSet, MatrixClient, MatrixEvent, Room } from 'matrix-js-sdk'; import { CryptoBackend } from 'matrix-js-sdk/lib/common-crypto/CryptoBackend'; -import React, { ReactNode, useEffect, useMemo, useState } from 'react'; +import React, { MouseEventHandler, ReactNode, useEffect, useMemo, useState } from 'react'; import to from 'await-to-js'; import classNames from 'classnames'; import colorMXID from '../../../util/colorMXID'; @@ -22,6 +22,7 @@ export const ReplyLayout = as<'div', ReplyLayoutProps>( ( ) ); +export const ThreadIndicator = as<'div'>(({ ...props }, ref) => ( + + + Threaded reply + +)); + type ReplyProps = { mx: MatrixClient; room: Room; - timelineSet?: EventTimelineSet; - eventId: string; + timelineSet?: EventTimelineSet | undefined; + replyEventId: string; + threadRootId?: string | undefined; + onClick?: MouseEventHandler | undefined; }; -export const Reply = as<'div', ReplyProps>(({ mx, room, timelineSet, eventId, ...props }, ref) => { +export const Reply = as<'div', ReplyProps>((_, ref) => { + const { mx, room, timelineSet, replyEventId, threadRootId, onClick, ...props } = _; const [replyEvent, setReplyEvent] = useState( - timelineSet?.findEventById(eventId) + timelineSet?.findEventById(replyEventId) ); const placeholderWidth = useMemo(() => randomNumberBetween(40, 400), []); @@ -62,7 +73,7 @@ export const Reply = as<'div', ReplyProps>(({ mx, room, timelineSet, eventId, .. useEffect(() => { let disposed = false; const loadEvent = async () => { - const [err, evt] = await to(mx.fetchRoomEvent(room.roomId, eventId)); + const [err, evt] = await to(mx.fetchRoomEvent(room.roomId, replyEventId)); const mEvent = new MatrixEvent(evt); if (disposed) return; if (err) { @@ -78,37 +89,43 @@ export const Reply = as<'div', ReplyProps>(({ mx, room, timelineSet, eventId, .. return () => { disposed = true; }; - }, [replyEvent, mx, room, eventId]); + }, [replyEvent, mx, room, replyEventId]); const badEncryption = replyEvent?.getContent().msgtype === 'm.bad.encrypted'; const bodyJSX = body ? scaleSystemEmoji(trimReplyFromBody(body)) : fallbackBody; return ( - - {getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender)} - - ) - } - {...props} - ref={ref} - > - {replyEvent !== undefined ? ( - - {badEncryption ? : bodyJSX} - - ) : ( - + + {threadRootId && ( + )} - + + {getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender)} + + ) + } + data-event-id={replyEventId} + onClick={onClick} + > + {replyEvent !== undefined ? ( + + {badEncryption ? : bodyJSX} + + ) : ( + + )} + + ); }); diff --git a/src/app/features/message-search/SearchResultGroup.tsx b/src/app/features/message-search/SearchResultGroup.tsx index 2b2a816a..84ba3a76 100644 --- a/src/app/features/message-search/SearchResultGroup.tsx +++ b/src/app/features/message-search/SearchResultGroup.tsx @@ -148,7 +148,7 @@ export function SearchResultGroup({ } ); - const handleOpenClick: MouseEventHandler = (evt) => { + const handleOpenClick: MouseEventHandler = (evt) => { const eventId = evt.currentTarget.getAttribute('data-event-id'); if (!eventId) return; onOpen(room.roomId, eventId); @@ -183,15 +183,16 @@ export function SearchResultGroup({ event.sender; const senderAvatarMxc = getMemberAvatarMxc(room, event.sender); + const relation = event.content['m.relates_to']; const mainEventId = - event.content['m.relates_to']?.rel_type === RelationType.Replace - ? event.content['m.relates_to'].event_id - : event.event_id; + relation?.rel_type === RelationType.Replace ? relation.event_id : event.event_id; const getContent = (() => event.content['m.new_content'] ?? event.content) as GetContentCallback; - const replyEventId = event.content['m.relates_to']?.['m.in_reply_to']?.event_id; + const replyEventId = relation?.['m.in_reply_to']?.event_id; + const threadRootId = + relation?.rel_type === RelationType.Thread ? relation.event_id : undefined; return ( {replyEventId && ( )} diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index 8375d2f7..3c78ff3e 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -10,7 +10,7 @@ import React, { } from 'react'; import { useAtom, useAtomValue } from 'jotai'; import { isKeyHotkey } from 'is-hotkey'; -import { EventType, IContent, MsgType, Room } from 'matrix-js-sdk'; +import { EventType, IContent, MsgType, RelationType, Room } from 'matrix-js-sdk'; import { ReactEditor } from 'slate-react'; import { Transforms, Editor } from 'slate'; import { @@ -106,7 +106,7 @@ import { CommandAutocomplete } from './CommandAutocomplete'; import { Command, SHRUG, useCommands } from '../../hooks/useCommands'; import { mobileOrTablet } from '../../utils/user-agent'; import { useElementSizeObserver } from '../../hooks/useElementSizeObserver'; -import { ReplyLayout } from '../../components/message'; +import { ReplyLayout, ThreadIndicator } from '../../components/message'; import { roomToParentsAtom } from '../../state/room/roomToParents'; interface RoomInputProps { @@ -310,6 +310,11 @@ export const RoomInput = forwardRef( event_id: replyDraft.eventId, }, }; + if (replyDraft.relation?.rel_type === RelationType.Thread) { + content['m.relates_to'].event_id = replyDraft.relation.event_id; + content['m.relates_to'].rel_type = RelationType.Thread; + content['m.relates_to'].is_falling_back = false; + } } mx.sendMessage(roomId, content); resetEditor(editor); @@ -489,22 +494,25 @@ export const RoomInput = forwardRef( > - + {replyDraft.relation?.rel_type === RelationType.Thread && } + + + {getMemberDisplayName(room, replyDraft.userId) ?? + getMxIdLocalPart(replyDraft.userId) ?? + replyDraft.userId} + + + } + > - - {getMemberDisplayName(room, replyDraft.userId) ?? - getMxIdLocalPart(replyDraft.userId) ?? - replyDraft.userId} - + {trimReplyFromBody(replyDraft.body)} - } - > - - {trimReplyFromBody(replyDraft.body)} - - + + ) diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index 84ce8af1..01ba14f5 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -16,6 +16,7 @@ import { EventTimeline, EventTimelineSet, EventTimelineSetHandlerMap, + IContent, IEncryptedFile, MatrixClient, MatrixEvent, @@ -837,13 +838,13 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli markAsRead(mx, room.roomId); }; - const handleOpenReply: MouseEventHandler = useCallback( + const handleOpenReply: MouseEventHandler = useCallback( async (evt) => { - const replyId = evt.currentTarget.getAttribute('data-reply-id'); - if (typeof replyId !== 'string') return; - const replyTimeline = getEventTimeline(room, replyId); + const targetId = evt.currentTarget.getAttribute('data-event-id'); + if (!targetId) return; + const replyTimeline = getEventTimeline(room, targetId); const absoluteIndex = - replyTimeline && getEventIdAbsoluteIndex(timeline.linkedTimelines, replyTimeline, replyId); + replyTimeline && getEventIdAbsoluteIndex(timeline.linkedTimelines, replyTimeline, targetId); if (typeof absoluteIndex === 'number') { scrollToItem(absoluteIndex, { @@ -858,7 +859,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli }); } else { setTimeline(getEmptyTimeline()); - loadEventTimeline(replyId); + loadEventTimeline(targetId); } }, [room, timeline, scrollToItem, loadEventTimeline] @@ -909,8 +910,9 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli const replyEvt = room.findEventById(replyId); if (!replyEvt) return; const editedReply = getEditedEvent(replyId, replyEvt, room.getUnfilteredTimelineSet()); - const { body, formatted_body: formattedBody }: Record = - editedReply?.getContent()['m.new_content'] ?? replyEvt.getContent(); + const content: IContent = editedReply?.getContent()['m.new_content'] ?? replyEvt.getContent(); + const { body, formatted_body: formattedBody } = content; + const { 'm.relates_to': relation } = replyEvt.getOriginalContent(); const senderId = replyEvt.getSender(); if (senderId && typeof body === 'string') { setReplyDraft({ @@ -918,6 +920,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli eventId: replyId, body, formattedBody, + relation, }); setTimeout(() => ReactEditor.focus(editor), 100); } @@ -969,7 +972,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli const reactionRelations = getEventReactions(timelineSet, mEventId); const reactions = reactionRelations && reactionRelations.getSortedAnnotationsByKey(); const hasReactions = reactions && reactions.length > 0; - const { replyEventId } = mEvent; + const { replyEventId, threadRootId } = mEvent; const highlighted = focusItem?.index === item && focusItem.highlight; const editedEvent = getEditedEvent(mEventId, mEvent, timelineSet); @@ -1004,12 +1007,11 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli reply={ replyEventId && ( ) @@ -1050,7 +1052,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli const reactionRelations = getEventReactions(timelineSet, mEventId); const reactions = reactionRelations && reactionRelations.getSortedAnnotationsByKey(); const hasReactions = reactions && reactions.length > 0; - const { replyEventId } = mEvent; + const { replyEventId, threadRootId } = mEvent; const highlighted = focusItem?.index === item && focusItem.highlight; return ( @@ -1077,12 +1079,11 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli reply={ replyEventId && ( ) diff --git a/src/app/pages/client/inbox/Notifications.tsx b/src/app/pages/client/inbox/Notifications.tsx index 6a8160d8..aa878216 100644 --- a/src/app/pages/client/inbox/Notifications.tsx +++ b/src/app/pages/client/inbox/Notifications.tsx @@ -20,6 +20,7 @@ import { IRoomEvent, JoinRule, Method, + RelationType, Room, } from 'matrix-js-sdk'; import { useVirtualizer } from '@tanstack/react-virtual'; @@ -352,7 +353,7 @@ function RoomNotificationsGroupComp({ } ); - const handleOpenClick: MouseEventHandler = (evt) => { + const handleOpenClick: MouseEventHandler = (evt) => { const eventId = evt.currentTarget.getAttribute('data-event-id'); if (!eventId) return; onOpen(room.roomId, eventId); @@ -403,7 +404,10 @@ function RoomNotificationsGroupComp({ const senderAvatarMxc = getMemberAvatarMxc(room, event.sender); const getContent = (() => event.content) as GetContentCallback; - const replyEventId = event.content['m.relates_to']?.['m.in_reply_to']?.event_id; + const relation = event.content['m.relates_to']; + const replyEventId = relation?.['m.in_reply_to']?.event_id; + const threadRootId = + relation?.rel_type === RelationType.Thread ? relation.event_id : undefined; return ( {replyEventId && ( )} diff --git a/src/app/state/room/roomInputDrafts.ts b/src/app/state/room/roomInputDrafts.ts index 60b42fdb..33bd0607 100644 --- a/src/app/state/room/roomInputDrafts.ts +++ b/src/app/state/room/roomInputDrafts.ts @@ -2,6 +2,7 @@ import { atom } from 'jotai'; import { atomFamily } from 'jotai/utils'; import { Descendant } from 'slate'; import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment'; +import { IEventRelation } from 'matrix-js-sdk'; import { TListAtom, createListAtom } from '../list'; import { createUploadAtomFamily } from '../upload'; import { TUploadContent } from '../../utils/matrix'; @@ -39,7 +40,8 @@ export type IReplyDraft = { userId: string; eventId: string; body: string; - formattedBody?: string; + formattedBody?: string | undefined; + relation?: IEventRelation | undefined; }; const createReplyDraftAtom = () => atom(undefined); export type TReplyDraftAtom = ReturnType; diff --git a/src/app/utils/room.ts b/src/app/utils/room.ts index 750dd6ca..8cf33a8f 100644 --- a/src/app/utils/room.ts +++ b/src/app/utils/room.ts @@ -389,13 +389,18 @@ export const getEditedEvent = ( return edits && getLatestEdit(mEvent, edits.getRelations()); }; -export const canEditEvent = (mx: MatrixClient, mEvent: MatrixEvent) => - mEvent.getSender() === mx.getUserId() && - !mEvent.isRelation() && - mEvent.getType() === MessageEvent.RoomMessage && - (mEvent.getContent().msgtype === MsgType.Text || - mEvent.getContent().msgtype === MsgType.Emote || - mEvent.getContent().msgtype === MsgType.Notice); +export const canEditEvent = (mx: MatrixClient, mEvent: MatrixEvent) => { + const content = mEvent.getContent(); + const relationType = content['m.relates_to']?.rel_type; + return ( + mEvent.getSender() === mx.getUserId() && + (!relationType || relationType === RelationType.Thread) && + mEvent.getType() === MessageEvent.RoomMessage && + (content.msgtype === MsgType.Text || + content.msgtype === MsgType.Emote || + content.msgtype === MsgType.Notice) + ); +}; export const getLatestEditableEvt = ( timeline: EventTimeline, From 16be69c104f760e491e36684a48492ef661cd01b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 00:21:25 +1000 Subject: [PATCH 6/9] Bump docker/build-push-action from 6.6.1 to 6.7.0 (#1906) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.6.1 to 6.7.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v6.6.1...v6.7.0) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-pr.yml | 2 +- .github/workflows/prod-deploy.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-pr.yml b/.github/workflows/docker-pr.yml index 827ca66e..7c67c27a 100644 --- a/.github/workflows/docker-pr.yml +++ b/.github/workflows/docker-pr.yml @@ -13,7 +13,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4.1.7 - name: Build Docker image - uses: docker/build-push-action@v6.6.1 + uses: docker/build-push-action@v6.7.0 with: context: . push: false diff --git a/.github/workflows/prod-deploy.yml b/.github/workflows/prod-deploy.yml index d340cb77..f8f13a7e 100644 --- a/.github/workflows/prod-deploy.yml +++ b/.github/workflows/prod-deploy.yml @@ -90,7 +90,7 @@ jobs: ${{ secrets.DOCKER_USERNAME }}/cinny ghcr.io/${{ github.repository }} - name: Build and push Docker image - uses: docker/build-push-action@v6.6.1 + uses: docker/build-push-action@v6.7.0 with: context: . platforms: linux/amd64,linux/arm64 From bdba0332e1494c8a71be3d0fe6837238f3d967d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 00:22:26 +1000 Subject: [PATCH 7/9] Bump cla-assistant/github-action from 2.4.0 to 2.5.1 (#1905) Bumps [cla-assistant/github-action](https://github.com/cla-assistant/github-action) from 2.4.0 to 2.5.1. - [Release notes](https://github.com/cla-assistant/github-action/releases) - [Commits](https://github.com/cla-assistant/github-action/compare/v2.4.0...v2.5.1) --- updated-dependencies: - dependency-name: cla-assistant/github-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cla.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 2ad3d145..65513427 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -12,7 +12,7 @@ jobs: - name: 'CLA Assistant' if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' # Beta Release - uses: cla-assistant/github-action@v2.4.0 + uses: cla-assistant/github-action@v2.5.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # the below token should have repo scope and must be manually added by you in the repository's secret From 22b7f6dd7d211b9d66b0ff8e237eade9eb473b6e Mon Sep 17 00:00:00 2001 From: Krishan <33421343+kfiven@users.noreply.github.com> Date: Wed, 21 Aug 2024 20:13:40 +1000 Subject: [PATCH 8/9] Create Code of Conduct (#1908) --- CODE_OF_CONDUCT.md | 128 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..f1ef5d46 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +cinnyapp@gmail.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. From 5c9ee1a988eedf8efa34b7065164f10018002009 Mon Sep 17 00:00:00 2001 From: utf <25370216+utf-4096@users.noreply.github.com> Date: Fri, 23 Aug 2024 12:56:03 +0200 Subject: [PATCH 9/9] Fix IPv6 support for the Docker container (#1884) * Fix `docker-nginx.conf` indentation * Listen on IPv4 and IPv6 inside Docker --- docker-nginx.conf | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docker-nginx.conf b/docker-nginx.conf index 6994b8c8..efe21fac 100644 --- a/docker-nginx.conf +++ b/docker-nginx.conf @@ -1,8 +1,11 @@ server { - location / { - root /usr/share/nginx/html; + listen 80; + listen [::]:80; - rewrite ^/config.json$ /config.json break; + location / { + root /usr/share/nginx/html; + + rewrite ^/config.json$ /config.json break; rewrite ^/manifest.json$ /manifest.json break; rewrite ^.*/olm.wasm$ /olm.wasm break; @@ -12,5 +15,5 @@ server { rewrite ^/assets/(.*)$ /assets/$1 break; rewrite ^(.+)$ /index.html break; - } -} \ No newline at end of file + } +}