diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 16036541f88..ebde627fde8 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,7 +2,7 @@ ## Checklist -- [ ] Tests written for new code (and old code if feasible). -- [ ] New or updated `public`/`exported` symbols have accurate [TSDoc](https://tsdoc.org/) documentation. -- [ ] Linter and other CI checks pass. -- [ ] I have licensed the changes to Element by completing the [Contributor License Agreement (CLA)](https://cla-assistant.io/element-hq/element-web) +- [ ] Tests written for new code (and old code if feasible). +- [ ] New or updated `public`/`exported` symbols have accurate [TSDoc](https://tsdoc.org/) documentation. +- [ ] Linter and other CI checks pass. +- [ ] I have licensed the changes to Element by completing the [Contributor License Agreement (CLA)](https://cla-assistant.io/element-hq/element-web) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49741b073c8..fa887929fb6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,26 +20,26 @@ Definitely don't use the GitHub default of "Update file.ts". As for your PR description, it should include these things: -- References to any bugs fixed by the change (in GitHub's `Fixes` notation) -- Describe the why and what is changing in the PR description so it's easy for - onlookers and reviewers to onboard and context switch. This information is - also helpful when we come back to look at this in 6 months and ask "why did - we do it like that?" we have a chance of finding out. - - Why didn't it work before? Why does it work now? What use cases does it - unlock? - - If you find yourself adding information on how the code works or why you - chose to do it the way you did, make sure this information is instead - written as comments in the code itself. - - Sometimes a PR can change considerably as it is developed. In this case, - the description should be updated to reflect the most recent state of - the PR. (It can be helpful to retain the old content under a suitable - heading, for additional context.) -- Include both **before** and **after** screenshots to easily compare and discuss - what's changing. -- Include a step-by-step testing strategy so that a reviewer can check out the - code locally and easily get to the point of testing your change. -- Add comments to the diff for the reviewer that might help them to understand - why the change is necessary or how they might better understand and review it. +- References to any bugs fixed by the change (in GitHub's `Fixes` notation) +- Describe the why and what is changing in the PR description so it's easy for + onlookers and reviewers to onboard and context switch. This information is + also helpful when we come back to look at this in 6 months and ask "why did + we do it like that?" we have a chance of finding out. + - Why didn't it work before? Why does it work now? What use cases does it + unlock? + - If you find yourself adding information on how the code works or why you + chose to do it the way you did, make sure this information is instead + written as comments in the code itself. + - Sometimes a PR can change considerably as it is developed. In this case, + the description should be updated to reflect the most recent state of + the PR. (It can be helpful to retain the old content under a suitable + heading, for additional context.) +- Include both **before** and **after** screenshots to easily compare and discuss + what's changing. +- Include a step-by-step testing strategy so that a reviewer can check out the + code locally and easily get to the point of testing your change. +- Add comments to the diff for the reviewer that might help them to understand + why the change is necessary or how they might better understand and review it. ### Changelogs @@ -79,8 +79,8 @@ element-web notes: Fix a bug where the 'Herd' button only worked on Tuesdays This example is for Element Web. You can specify: -- element-web -- element-desktop +- element-web +- element-desktop If your PR introduces a breaking change, use the `Notes` section in the same way, additionally adding the `X-Breaking-Change` label (see below). There's no need @@ -96,10 +96,10 @@ Notes: Remove legacy `Camelopard` class. `Giraffe` should be used instead. Other metadata can be added using labels. -- `X-Breaking-Change`: A breaking change - adding this label will mean the change causes a _major_ version bump. -- `T-Enhancement`: A new feature - adding this label will mean the change causes a _minor_ version bump. -- `T-Defect`: A bug fix (in either code or docs). -- `T-Task`: No user-facing changes, eg. code comments, CI fixes, refactors or tests. Won't have a changelog entry unless you specify one. +- `X-Breaking-Change`: A breaking change - adding this label will mean the change causes a _major_ version bump. +- `T-Enhancement`: A new feature - adding this label will mean the change causes a _minor_ version bump. +- `T-Defect`: A bug fix (in either code or docs). +- `T-Task`: No user-facing changes, eg. code comments, CI fixes, refactors or tests. Won't have a changelog entry unless you specify one. If you don't have permission to add labels, your PR reviewer(s) can work with you to add them: ask in the PR description or comments. diff --git a/README.md b/README.md index fa4ac89ff99..87e451c9ffa 100644 --- a/README.md +++ b/README.md @@ -16,28 +16,28 @@ JS SDK](https://github.com/matrix-org/matrix-js-sdk). Element has several tiers of support for different environments: -- Supported - - Definition: - - Issues **actively triaged**, regressions **block** the release - - Last 2 major versions of Chrome, Firefox, and Edge on desktop OSes - - Last 2 versions of Safari - - Latest release of official Element Desktop app on desktop OSes - - Desktop OSes means macOS, Windows, and Linux versions for desktop devices - that are actively supported by the OS vendor and receive security updates -- Best effort - - Definition: - - Issues **accepted**, regressions **do not block** the release - - The wider Element Products(including Element Call and the Enterprise Server Suite) do still not officially support these browsers. - - The element web project and its contributors should keep the client functioning and gracefully degrade where other sibling features (E.g. Element Call) may not function. - - Last major release of Firefox ESR and Chrome/Edge Extended Stable -- Community Supported - - Definition: - - Issues **accepted**, regressions **do not block** the release - - Community contributions are welcome to support these issues - - Mobile web for current stable version of Chrome, Firefox, and Safari on Android, iOS, and iPadOS -- Not supported - - Definition: Issues only affecting unsupported environments are **closed** - - Everything else +- Supported + - Definition: + - Issues **actively triaged**, regressions **block** the release + - Last 2 major versions of Chrome, Firefox, and Edge on desktop OSes + - Last 2 versions of Safari + - Latest release of official Element Desktop app on desktop OSes + - Desktop OSes means macOS, Windows, and Linux versions for desktop devices + that are actively supported by the OS vendor and receive security updates +- Best effort + - Definition: + - Issues **accepted**, regressions **do not block** the release + - The wider Element Products(including Element Call and the Enterprise Server Suite) do still not officially support these browsers. + - The element web project and its contributors should keep the client functioning and gracefully degrade where other sibling features (E.g. Element Call) may not function. + - Last major release of Firefox ESR and Chrome/Edge Extended Stable +- Community Supported + - Definition: + - Issues **accepted**, regressions **do not block** the release + - Community contributions are welcome to support these issues + - Mobile web for current stable version of Chrome, Firefox, and Safari on Android, iOS, and iPadOS +- Not supported + - Definition: Issues only affecting unsupported environments are **closed** + - Everything else The period of support for these tiers should last until the releases specified above, plus 1 app release cycle(2 weeks). In the case of Firefox ESR this is extended further to allow it land in Debian Stable. @@ -74,16 +74,16 @@ situation, but it's still not good practice to do it in the first place. See Unless you have special requirements, you will want to add the following to your web server configuration when hosting Element Web: -- The `X-Frame-Options: SAMEORIGIN` header, to prevent Element Web from being - framed and protect from [clickjacking][owasp-clickjacking]. -- The `frame-ancestors 'self'` directive to your `Content-Security-Policy` - header, as the modern replacement for `X-Frame-Options` (though both should be - included since not all browsers support it yet, see - [this][owasp-clickjacking-csp]). -- The `X-Content-Type-Options: nosniff` header, to [disable MIME - sniffing][mime-sniffing]. -- The `X-XSS-Protection: 1; mode=block;` header, for basic XSS protection in - legacy browsers. +- The `X-Frame-Options: SAMEORIGIN` header, to prevent Element Web from being + framed and protect from [clickjacking][owasp-clickjacking]. +- The `frame-ancestors 'self'` directive to your `Content-Security-Policy` + header, as the modern replacement for `X-Frame-Options` (though both should be + included since not all browsers support it yet, see + [this][owasp-clickjacking-csp]). +- The `X-Content-Type-Options: nosniff` header, to [disable MIME + sniffing][mime-sniffing]. +- The `X-XSS-Protection: 1; mode=block;` header, for basic XSS protection in + legacy browsers. [mime-sniffing]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#mime_sniffing [owasp-clickjacking-csp]: https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html#content-security-policy-frame-ancestors-examples diff --git a/code_style.md b/code_style.md index e5f7485cec9..9aa6836442f 100644 --- a/code_style.md +++ b/code_style.md @@ -3,9 +3,9 @@ This code style applies to projects which the element-web team directly maintains or is reasonably adjacent to. As of writing, these are: -- element-desktop -- element-web -- matrix-js-sdk +- element-desktop +- element-web +- matrix-js-sdk Other projects might extend this code style for increased strictness. For example, matrix-events-sdk has stricter code organization to reduce the maintenance burden. These projects will declare their code diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 23229bf00a6..57b017bc1c6 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -1,55 +1,55 @@ # Summary -- [Introduction](../README.md) +- [Introduction](../README.md) # Usage -- [Betas](betas.md) -- [Labs](labs.md) +- [Betas](betas.md) +- [Labs](labs.md) # Setup -- [Install](install.md) -- [Config](config.md) -- [Custom home page](custom-home.md) -- [Kubernetes](kubernetes.md) -- [Jitsi](jitsi.md) -- [Encryption](e2ee.md) +- [Install](install.md) +- [Config](config.md) +- [Custom home page](custom-home.md) +- [Kubernetes](kubernetes.md) +- [Jitsi](jitsi.md) +- [Encryption](e2ee.md) # Build -- [Customisations](customisations.md) -- [Modules](modules.md) -- [Native Node modules](native-node-modules.md) +- [Customisations](customisations.md) +- [Modules](modules.md) +- [Native Node modules](native-node-modules.md) # Contribution -- [Choosing an issue](choosing-an-issue.md) -- [Translation](translating.md) -- [Netlify builds](pr-previews.md) -- [Code review](review.md) +- [Choosing an issue](choosing-an-issue.md) +- [Translation](translating.md) +- [Netlify builds](pr-previews.md) +- [Code review](review.md) # Development -- [App load order](app-load.md) -- [Translation](translating-dev.md) -- [Theming](theming.md) -- [Playwright end to end tests](playwright.md) -- [Memory profiling](memory-profiles-and-leaks.md) -- [Jitsi](jitsi-dev.md) -- [Feature flags](feature-flags.md) -- [OIDC and delegated authentication](oidc.md) -- [Release Process](release.md) +- [App load order](app-load.md) +- [Translation](translating-dev.md) +- [Theming](theming.md) +- [Playwright end to end tests](playwright.md) +- [Memory profiling](memory-profiles-and-leaks.md) +- [Jitsi](jitsi-dev.md) +- [Feature flags](feature-flags.md) +- [OIDC and delegated authentication](oidc.md) +- [Release Process](release.md) # Deep dive -- [Skinning](skinning.md) -- [Cider editor](ciderEditor.md) -- [Iconography](icons.md) -- [Jitsi](jitsi.md) -- [Local echo](local-echo-dev.md) -- [Media](media-handling.md) -- [Room List Store](room-list-store.md) -- [Scrolling](scrolling.md) -- [Usercontent](usercontent.md) -- [Widget layouts](widget-layouts.md) +- [Skinning](skinning.md) +- [Cider editor](ciderEditor.md) +- [Iconography](icons.md) +- [Jitsi](jitsi.md) +- [Local echo](local-echo-dev.md) +- [Media](media-handling.md) +- [Room List Store](room-list-store.md) +- [Scrolling](scrolling.md) +- [Usercontent](usercontent.md) +- [Widget layouts](widget-layouts.md) diff --git a/docs/app-load.md b/docs/app-load.md index 849e95cb8d4..7f72b3fea4d 100644 --- a/docs/app-load.md +++ b/docs/app-load.md @@ -61,18 +61,18 @@ flowchart TD Key: -- Parallelogram: async/await task -- Box: sync task -- Diamond: conditional branch -- Circle: user interaction -- Blue arrow: async task is allowed to settle but allowed to fail -- Red arrow: async task success is asserted +- Parallelogram: async/await task +- Box: sync task +- Diamond: conditional branch +- Circle: user interaction +- Blue arrow: async task is allowed to settle but allowed to fail +- Red arrow: async task success is asserted Notes: -- A task begins when all its dependencies (arrows going into it) are fulfilled. -- The success of setting up rageshake is never asserted, element-web has a fallback path for running without IDB (and thus rageshake). -- Everything is awaited to be settled before the Modernizr check, to allow it to make use of things like i18n if they are successful. +- A task begins when all its dependencies (arrows going into it) are fulfilled. +- The success of setting up rageshake is never asserted, element-web has a fallback path for running without IDB (and thus rageshake). +- Everything is awaited to be settled before the Modernizr check, to allow it to make use of things like i18n if they are successful. Underlying dependencies: diff --git a/docs/choosing-an-issue.md b/docs/choosing-an-issue.md index 9d008782a18..ca179793674 100644 --- a/docs/choosing-an-issue.md +++ b/docs/choosing-an-issue.md @@ -32,19 +32,19 @@ someone to add something. When you're looking through the list, here are some things that might make an issue a **GOOD** choice: -- It is a problem or feature you care about. -- It concerns a type of code you know a little about. -- You think you can understand what's needed. -- It already has approval from Element Web's designers (look for comments from - members of the - [Product](https://github.com/orgs/element-hq/teams/product/members) or - [Design](https://github.com/orgs/element-hq/teams/design/members) teams). +- It is a problem or feature you care about. +- It concerns a type of code you know a little about. +- You think you can understand what's needed. +- It already has approval from Element Web's designers (look for comments from + members of the + [Product](https://github.com/orgs/element-hq/teams/product/members) or + [Design](https://github.com/orgs/element-hq/teams/design/members) teams). Here are some things that might make it a **BAD** choice: -- You don't understand it (maybe add a comment asking a clarifying question). -- It sounds difficult, or is part of a larger change you don't know about. -- **It is tagged with `X-Needs-Design` or `X-Needs-Product`.** +- You don't understand it (maybe add a comment asking a clarifying question). +- It sounds difficult, or is part of a larger change you don't know about. +- **It is tagged with `X-Needs-Design` or `X-Needs-Product`.** **Element Web's Design and Product teams tend to be very busy**, so if you make changes that require approval from one of those teams, you will probably have diff --git a/docs/config.md b/docs/config.md index cc40179740f..7a5445f3035 100644 --- a/docs/config.md +++ b/docs/config.md @@ -455,7 +455,7 @@ If you would like to use Scalar, the integration manager maintained by Element, For widgets in general (from an integration manager or not) there is also: -- `default_widget_container_height` +- `default_widget_container_height` This controls the height that the top widget panel initially appears as and is the height in pixels, default 280. @@ -551,38 +551,38 @@ preferences. Currently, the following UI feature flags are supported: -- `UIFeature.urlPreviews` - Whether URL previews are enabled across the entire application. -- `UIFeature.feedback` - Whether prompts to supply feedback are shown. -- `UIFeature.voip` - Whether or not VoIP is shown readily to the user. When disabled, - Jitsi widgets will still work though they cannot easily be added. -- `UIFeature.widgets` - Whether or not widgets will be shown. -- `UIFeature.advancedSettings` - Whether or not sections titled "advanced" in room and - user settings are shown to the user. -- `UIFeature.shareQrCode` - Whether or not the QR code on the share room/event dialog - is shown. -- `UIFeature.shareSocial` - Whether or not the social icons on the share room/event dialog - are shown. -- `UIFeature.identityServer` - Whether or not functionality requiring an identity server - is shown. When disabled, the user will not be able to interact with the identity - server (sharing email addresses, 3PID invites, etc). -- `UIFeature.thirdPartyId` - Whether or not UI relating to third party identifiers (3PIDs) - is shown. Typically this is considered "contact information" on the homeserver, and is - not directly related to the identity server. -- `UIFeature.registration` - Whether or not the registration page is accessible. Typically - useful if accounts are managed externally. -- `UIFeature.passwordReset` - Whether or not the password reset page is accessible. Typically - useful if accounts are managed externally. -- `UIFeature.deactivate` - Whether or not the deactivate account button is accessible. Typically - useful if accounts are managed externally. -- `UIFeature.advancedEncryption` - Whether or not advanced encryption options are shown to the - user. -- `UIFeature.roomHistorySettings` - Whether or not the room history settings are shown to the user. - This should only be used if the room history visibility options are managed by the server. -- `UIFeature.TimelineEnableRelativeDates` - Display relative date separators (eg: 'Today', 'Yesterday') in the - timeline for recent messages. When false day dates will be used. -- `UIFeature.BulkUnverifiedSessionsReminder` - Display popup reminders to verify or remove unverified sessions. Defaults - to true. -- `UIFeature.locationSharing` - Whether or not location sharing menus will be shown. +- `UIFeature.urlPreviews` - Whether URL previews are enabled across the entire application. +- `UIFeature.feedback` - Whether prompts to supply feedback are shown. +- `UIFeature.voip` - Whether or not VoIP is shown readily to the user. When disabled, + Jitsi widgets will still work though they cannot easily be added. +- `UIFeature.widgets` - Whether or not widgets will be shown. +- `UIFeature.advancedSettings` - Whether or not sections titled "advanced" in room and + user settings are shown to the user. +- `UIFeature.shareQrCode` - Whether or not the QR code on the share room/event dialog + is shown. +- `UIFeature.shareSocial` - Whether or not the social icons on the share room/event dialog + are shown. +- `UIFeature.identityServer` - Whether or not functionality requiring an identity server + is shown. When disabled, the user will not be able to interact with the identity + server (sharing email addresses, 3PID invites, etc). +- `UIFeature.thirdPartyId` - Whether or not UI relating to third party identifiers (3PIDs) + is shown. Typically this is considered "contact information" on the homeserver, and is + not directly related to the identity server. +- `UIFeature.registration` - Whether or not the registration page is accessible. Typically + useful if accounts are managed externally. +- `UIFeature.passwordReset` - Whether or not the password reset page is accessible. Typically + useful if accounts are managed externally. +- `UIFeature.deactivate` - Whether or not the deactivate account button is accessible. Typically + useful if accounts are managed externally. +- `UIFeature.advancedEncryption` - Whether or not advanced encryption options are shown to the + user. +- `UIFeature.roomHistorySettings` - Whether or not the room history settings are shown to the user. + This should only be used if the room history visibility options are managed by the server. +- `UIFeature.TimelineEnableRelativeDates` - Display relative date separators (eg: 'Today', 'Yesterday') in the + timeline for recent messages. When false day dates will be used. +- `UIFeature.BulkUnverifiedSessionsReminder` - Display popup reminders to verify or remove unverified sessions. Defaults + to true. +- `UIFeature.locationSharing` - Whether or not location sharing menus will be shown. ## Undocumented / developer options diff --git a/docs/customisations.md b/docs/customisations.md index a6f72ab1abc..42cb8c7c5c8 100644 --- a/docs/customisations.md +++ b/docs/customisations.md @@ -50,9 +50,9 @@ that properties/state machines won't change. UI for some actions can be hidden via the ComponentVisibility customisation: -- inviting users to rooms and spaces, -- creating rooms, -- creating spaces, +- inviting users to rooms and spaces, +- creating rooms, +- creating spaces, To customise visibility create a customisation module from [ComponentVisibility](https://github.com/element-hq/element-web/blob/master/src/customisations/ComponentVisibility.ts) following the instructions above. diff --git a/docs/e2ee.md b/docs/e2ee.md index 1229f55f38b..835c38a1d59 100644 --- a/docs/e2ee.md +++ b/docs/e2ee.md @@ -31,9 +31,9 @@ Set the following on your homeserver's When `force_disable` is true: -- all rooms will be created with encryption disabled, and it will not be possible to enable - encryption from room settings. -- any `io.element.e2ee.default` value will be disregarded. +- all rooms will be created with encryption disabled, and it will not be possible to enable + encryption from room settings. +- any `io.element.e2ee.default` value will be disregarded. Note: If the server is configured to forcibly enable encryption for some or all rooms, this behaviour will be overridden. diff --git a/docs/feature-flags.md b/docs/feature-flags.md index 46e5f1243e2..54d54e3b1bf 100644 --- a/docs/feature-flags.md +++ b/docs/feature-flags.md @@ -5,10 +5,10 @@ flexibility and control over when and where those features are enabled. For example, flags make the following things possible: -- Extended testing of a feature via labs on develop -- Enabling features when ready instead of the first moment the code is released -- Testing a feature with a specific set of users (by enabling only on a specific - Element instance) +- Extended testing of a feature via labs on develop +- Enabling features when ready instead of the first moment the code is released +- Testing a feature with a specific set of users (by enabling only on a specific + Element instance) The size of the feature controlled by a feature flag may vary widely: it could be a large project like reactions or a smaller change to an existing algorithm. diff --git a/docs/features/composer.md b/docs/features/composer.md index 408c78a8d9c..1af4c9c8940 100644 --- a/docs/features/composer.md +++ b/docs/features/composer.md @@ -2,37 +2,37 @@ ## Auto Complete -- Hitting tab tries to auto-complete the word before the caret as a room member - - If no matching name is found, a visual bell is shown -- @ + a letter opens auto complete for members starting with the given letter - - When inserting a user pill at the start in the composer, a colon and space is appended to the pill - - When inserting a user pill anywhere else in composer, only a space is appended to the pill -- # + a letter opens auto complete for rooms starting with the given letter -- : open auto complete for emoji -- Pressing arrow-up/arrow-down while the autocomplete is open navigates between auto complete options -- Pressing tab while the autocomplete is open goes to the next autocomplete option, - wrapping around at the end after reverting to the typed text first. +- Hitting tab tries to auto-complete the word before the caret as a room member + - If no matching name is found, a visual bell is shown +- @ + a letter opens auto complete for members starting with the given letter + - When inserting a user pill at the start in the composer, a colon and space is appended to the pill + - When inserting a user pill anywhere else in composer, only a space is appended to the pill +- # + a letter opens auto complete for rooms starting with the given letter +- : open auto complete for emoji +- Pressing arrow-up/arrow-down while the autocomplete is open navigates between auto complete options +- Pressing tab while the autocomplete is open goes to the next autocomplete option, + wrapping around at the end after reverting to the typed text first. ## Formatting -- When selecting text, a formatting bar appears above the selection. -- The formatting bar allows to format the selected test as: - bold, italic, strikethrough, a block quote, and a code block (inline if no linebreak is selected). -- Formatting is applied as markdown syntax. -- Hitting ctrl/cmd+B also marks the selected text as bold -- Hitting ctrl/cmd+I also marks the selected text as italic -- Hitting ctrl/cmd+> also marks the selected text as a blockquote +- When selecting text, a formatting bar appears above the selection. +- The formatting bar allows to format the selected test as: + bold, italic, strikethrough, a block quote, and a code block (inline if no linebreak is selected). +- Formatting is applied as markdown syntax. +- Hitting ctrl/cmd+B also marks the selected text as bold +- Hitting ctrl/cmd+I also marks the selected text as italic +- Hitting ctrl/cmd+> also marks the selected text as a blockquote ## Misc -- When hitting the arrow-up button while having the caret at the start in the composer, - the last message sent by the syncing user is edited. -- Clicking a display name on an event in the timeline inserts a user pill into the composer -- Emoticons (like :-), >:-), :-/, ...) are replaced by emojis while typing if the relevant setting is enabled -- Typing in the composer sends typing notifications in the room -- Pressing ctrl/mod+z and ctrl/mod+y undoes/redoes modifications -- Pressing shift+enter inserts a line break -- Pressing enter sends the message. -- Choosing "Quote" in the context menu of an event inserts a quote of the event body in the composer. -- Choosing "Reply" in the context menu of an event shows a preview above the composer to reply to. -- Pressing alt+arrow up/arrow down navigates in previously sent messages, putting them in the composer. +- When hitting the arrow-up button while having the caret at the start in the composer, + the last message sent by the syncing user is edited. +- Clicking a display name on an event in the timeline inserts a user pill into the composer +- Emoticons (like :-), >:-), :-/, ...) are replaced by emojis while typing if the relevant setting is enabled +- Typing in the composer sends typing notifications in the room +- Pressing ctrl/mod+z and ctrl/mod+y undoes/redoes modifications +- Pressing shift+enter inserts a line break +- Pressing enter sends the message. +- Choosing "Quote" in the context menu of an event inserts a quote of the event body in the composer. +- Choosing "Reply" in the context menu of an event shows a preview above the composer to reply to. +- Pressing alt+arrow up/arrow down navigates in previously sent messages, putting them in the composer. diff --git a/docs/icons.md b/docs/icons.md index b0582356ce9..449663e24a4 100644 --- a/docs/icons.md +++ b/docs/icons.md @@ -8,9 +8,9 @@ Icons have `role="presentation"` and `aria-hidden` automatically applied. These SVG file recommendations: -- Colours should not be defined absolutely. Use `currentColor` instead. -- SVG files should be taken from the design compound as they are. Some icons contain special padding. - This means that there should be icons for each size, e.g. warning-16px and warning-32px. +- Colours should not be defined absolutely. Use `currentColor` instead. +- SVG files should be taken from the design compound as they are. Some icons contain special padding. + This means that there should be icons for each size, e.g. warning-16px and warning-32px. Example usage: diff --git a/docs/jitsi.md b/docs/jitsi.md index 48d1a7bf3eb..20e64db3798 100644 --- a/docs/jitsi.md +++ b/docs/jitsi.md @@ -81,27 +81,27 @@ which takes several parameters: _Query string_: -- `widgetId`: The ID of the widget. This is needed for communication back to the - react-sdk. -- `parentUrl`: The URL of the parent window. This is also needed for - communication back to the react-sdk. +- `widgetId`: The ID of the widget. This is needed for communication back to the + react-sdk. +- `parentUrl`: The URL of the parent window. This is also needed for + communication back to the react-sdk. _Hash/fragment (formatted as a query string)_: -- `conferenceDomain`: The domain to connect Jitsi Meet to. -- `conferenceId`: The room or conference ID to connect Jitsi Meet to. -- `isAudioOnly`: Boolean for whether this is a voice-only conference. May not - be present, should default to `false`. -- `startWithAudioMuted`: Boolean for whether the calls start with audio - muted. May not be present. -- `startWithVideoMuted`: Boolean for whether the calls start with video - muted. May not be present. -- `displayName`: The display name of the user viewing the widget. May not - be present or could be null. -- `avatarUrl`: The HTTP(S) URL for the avatar of the user viewing the widget. May - not be present or could be null. -- `userId`: The MXID of the user viewing the widget. May not be present or could - be null. +- `conferenceDomain`: The domain to connect Jitsi Meet to. +- `conferenceId`: The room or conference ID to connect Jitsi Meet to. +- `isAudioOnly`: Boolean for whether this is a voice-only conference. May not + be present, should default to `false`. +- `startWithAudioMuted`: Boolean for whether the calls start with audio + muted. May not be present. +- `startWithVideoMuted`: Boolean for whether the calls start with video + muted. May not be present. +- `displayName`: The display name of the user viewing the widget. May not + be present or could be null. +- `avatarUrl`: The HTTP(S) URL for the avatar of the user viewing the widget. May + not be present or could be null. +- `userId`: The MXID of the user viewing the widget. May not be present or could + be null. The react-sdk will assume that `jitsi.html` is at the path of wherever it is currently being served. For example, `https://develop.element.io/jitsi.html` or `vector://webapp/jitsi.html`. diff --git a/docs/playwright.md b/docs/playwright.md index 7eae8e783dc..1454c4868fe 100644 --- a/docs/playwright.md +++ b/docs/playwright.md @@ -2,10 +2,10 @@ ## Contents -- How to run the tests -- How the tests work -- How to write great Playwright tests -- Visual testing +- How to run the tests +- How the tests work +- How to write great Playwright tests +- Visual testing ## Running the Tests @@ -123,15 +123,15 @@ When a Synapse instance is started, it's given a config generated from one of th templates in `playwright/plugins/homeserver/synapse/templates`. There are a couple of special files in these templates: -- `homeserver.yaml`: - Template substitution happens in this file. Template variables are: - - `REGISTRATION_SECRET`: The secret used to register users via the REST API. - - `MACAROON_SECRET_KEY`: Generated each time for security - - `FORM_SECRET`: Generated each time for security - - `PUBLIC_BASEURL`: The localhost url + port combination the synapse is accessible at -- `localhost.signing.key`: A signing key is auto-generated and saved to this file. - Config templates should not contain a signing key and instead assume that one will exist - in this file. +- `homeserver.yaml`: + Template substitution happens in this file. Template variables are: + - `REGISTRATION_SECRET`: The secret used to register users via the REST API. + - `MACAROON_SECRET_KEY`: Generated each time for security + - `FORM_SECRET`: Generated each time for security + - `PUBLIC_BASEURL`: The localhost url + port combination the synapse is accessible at +- `localhost.signing.key`: A signing key is auto-generated and saved to this file. + Config templates should not contain a signing key and instead assume that one will exist + in this file. All other files in the template are copied recursively to `/data/`, so the file `foo.html` in a template can be referenced in the config as `/data/foo.html`. diff --git a/docs/release.md b/docs/release.md index 50740393747..b2c797b66b5 100644 --- a/docs/release.md +++ b/docs/release.md @@ -82,28 +82,28 @@ This label will automagically convert to `X-Release-Blocker` at the conclusion o This release process revolves around our main repositories: -- [Element Desktop](https://github.com/element-hq/element-desktop/) -- [Element Web](https://github.com/element-hq/element-web/) -- [Matrix JS SDK](https://github.com/matrix-org/matrix-js-sdk/) +- [Element Desktop](https://github.com/element-hq/element-desktop/) +- [Element Web](https://github.com/element-hq/element-web/) +- [Matrix JS SDK](https://github.com/matrix-org/matrix-js-sdk/) We own other repositories, but they have more ad-hoc releases and are not part of the bi-weekly cycle: -- https://github.com/matrix-org/matrix-web-i18n/ -- https://github.com/matrix-org/matrix-react-sdk-module-api +- https://github.com/matrix-org/matrix-web-i18n/ +- https://github.com/matrix-org/matrix-react-sdk-module-api

Prerequisites

-- You must be part of the 2 Releasers GitHub groups: - - - - -- You will need access to the **VPN** ([docs](https://gitlab.matrix.org/new-vector/internal/-/wikis/SRE/Tailscale)) to be able to follow the instructions under Deploy below. -- You will need the ability to **SSH** in to the production machines to be able to follow the instructions under Deploy below. Ensure that your SSH key has a non-empty passphrase, and you registered your SSH key with Ops. Log a ticket at https://github.com/matrix-org/matrix-ansible-private and ask for: - - Two-factor authentication to be set up on your SSH key. (This is needed to get access to production). - - SSH access to `horme` (staging.element.io and app.element.io) - - Permission to sudo on horme as the user `element` -- You need "**jumphost**" configuration in your local `~/.ssh/config`. This should have been set up as part of your onboarding. +- You must be part of the 2 Releasers GitHub groups: + - + - +- You will need access to the **VPN** ([docs](https://gitlab.matrix.org/new-vector/internal/-/wikis/SRE/Tailscale)) to be able to follow the instructions under Deploy below. +- You will need the ability to **SSH** in to the production machines to be able to follow the instructions under Deploy below. Ensure that your SSH key has a non-empty passphrase, and you registered your SSH key with Ops. Log a ticket at https://github.com/matrix-org/matrix-ansible-private and ask for: + - Two-factor authentication to be set up on your SSH key. (This is needed to get access to production). + - SSH access to `horme` (staging.element.io and app.element.io) + - Permission to sudo on horme as the user `element` +- You need "**jumphost**" configuration in your local `~/.ssh/config`. This should have been set up as part of your onboarding.
@@ -177,7 +177,7 @@ For security, you may wish to merge the security advisory private fork or apply It is worth noting that at the end of the Final/Hotfix/Security release `staging` is merged to `master` which is merged back into `develop` - this means that any commit which goes to `staging` will eventually make its way back to the default branch. -- [ ] The staging branch is prepared +- [ ] The staging branch is prepared # Releasing @@ -192,21 +192,21 @@ switched back to the version of the dependency from the master branch to not lea ### Matrix JS SDK -- [ ] Check the draft release which has been generated by [the automation](https://github.com/matrix-org/matrix-js-sdk/actions/workflows/release-drafter.yml) -- [ ] Make any changes to the release notes in the draft release as are necessary - **Do not click publish, only save draft** -- [ ] Kick off a release using [the automation](https://github.com/matrix-org/matrix-js-sdk/actions/workflows/release.yml) - making sure to select the right type of release. For anything other than an RC: choose final. You should not need to ever switch off either of the Publishing options. +- [ ] Check the draft release which has been generated by [the automation](https://github.com/matrix-org/matrix-js-sdk/actions/workflows/release-drafter.yml) +- [ ] Make any changes to the release notes in the draft release as are necessary - **Do not click publish, only save draft** +- [ ] Kick off a release using [the automation](https://github.com/matrix-org/matrix-js-sdk/actions/workflows/release.yml) - making sure to select the right type of release. For anything other than an RC: choose final. You should not need to ever switch off either of the Publishing options. ### Element Web -- [ ] Check the draft release which has been generated by [the automation](https://github.com/element-hq/element-web/actions/workflows/release-drafter.yml) -- [ ] Make any changes to the release notes in the draft release as are necessary - **Do not click publish, only save draft** -- [ ] Kick off a release using [the automation](https://github.com/element-hq/element-web/actions/workflows/release.yml) - making sure to select the right type of release. For anything other than an RC: choose final. You should not need to ever switch off either of the Publishing options. +- [ ] Check the draft release which has been generated by [the automation](https://github.com/element-hq/element-web/actions/workflows/release-drafter.yml) +- [ ] Make any changes to the release notes in the draft release as are necessary - **Do not click publish, only save draft** +- [ ] Kick off a release using [the automation](https://github.com/element-hq/element-web/actions/workflows/release.yml) - making sure to select the right type of release. For anything other than an RC: choose final. You should not need to ever switch off either of the Publishing options. ### Element Desktop -- [ ] Check the draft release which has been generated by [the automation](https://github.com/element-hq/element-desktop/actions/workflows/release-drafter.yml) -- [ ] Make any changes to the release notes in the draft release as are necessary - **Do not click publish, only save draft** -- [ ] Kick off a release using [the automation](https://github.com/element-hq/element-desktop/actions/workflows/release.yml) - making sure to select the right type of release. For anything other than an RC: choose final. You should not need to ever switch off either of the Publishing options. +- [ ] Check the draft release which has been generated by [the automation](https://github.com/element-hq/element-desktop/actions/workflows/release-drafter.yml) +- [ ] Make any changes to the release notes in the draft release as are necessary - **Do not click publish, only save draft** +- [ ] Kick off a release using [the automation](https://github.com/element-hq/element-desktop/actions/workflows/release.yml) - making sure to select the right type of release. For anything other than an RC: choose final. You should not need to ever switch off either of the Publishing options. # Deploying @@ -214,23 +214,23 @@ We ship the SDKs to npm, this happens as part of the release process. We ship Element Web to dockerhub, `*.element.io`, and packages.element.io. We ship Element Desktop to packages.element.io. -- [ ] Check that element-web has shipped to dockerhub -- [ ] Deploy staging.element.io. [See docs.](https://handbook.element.io/books/element-web-team/page/deploying-appstagingelementio) -- [ ] Test staging.element.io +- [ ] Check that element-web has shipped to dockerhub +- [ ] Deploy staging.element.io. [See docs.](https://handbook.element.io/books/element-web-team/page/deploying-appstagingelementio) +- [ ] Test staging.element.io For final releases additionally do these steps: -- [ ] Deploy app.element.io. [See docs.](https://handbook.element.io/books/element-web-team/page/deploying-appstagingelementio) -- [ ] Test app.element.io -- [ ] Ensure Element Web package has shipped to packages.element.io -- [ ] Ensure Element Desktop packages have shipped to packages.element.io +- [ ] Deploy app.element.io. [See docs.](https://handbook.element.io/books/element-web-team/page/deploying-appstagingelementio) +- [ ] Test app.element.io +- [ ] Ensure Element Web package has shipped to packages.element.io +- [ ] Ensure Element Desktop packages have shipped to packages.element.io # Housekeeping We have some manual housekeeping to do in order to prepare for the next release. -- [ ] Update topics using [the automation](https://github.com/element-hq/element-web/actions/workflows/update-topics.yaml). It will autodetect the current latest version. Don't forget the date you supply should be e.g. September 5th (including the "th") for the script to work. -- [ ] Announce the release in [#element-web-announcements:matrix.org](https://matrix.to/#/#element-web-announcements:matrix.org) +- [ ] Update topics using [the automation](https://github.com/element-hq/element-web/actions/workflows/update-topics.yaml). It will autodetect the current latest version. Don't forget the date you supply should be e.g. September 5th (including the "th") for the script to work. +- [ ] Announce the release in [#element-web-announcements:matrix.org](https://matrix.to/#/#element-web-announcements:matrix.org)
(show) @@ -246,15 +246,15 @@ With wording like: For the first RC of a given release cycle do these steps: -- [ ] Go to the [matrix-js-sdk Renovate dashboard](https://github.com/matrix-org/matrix-js-sdk/issues/2406) and click the checkbox to create/update its PRs. +- [ ] Go to the [matrix-js-sdk Renovate dashboard](https://github.com/matrix-org/matrix-js-sdk/issues/2406) and click the checkbox to create/update its PRs. -- [ ] Go to the [element-web Renovate dashboard](https://github.com/element-hq/element-web/issues/22941) and click the checkbox to create/update its PRs. +- [ ] Go to the [element-web Renovate dashboard](https://github.com/element-hq/element-web/issues/22941) and click the checkbox to create/update its PRs. -- [ ] Go to the [element-desktop Renovate dashboard](https://github.com/element-hq/element-desktop/issues/465) and click the checkbox to create/update its PRs. +- [ ] Go to the [element-desktop Renovate dashboard](https://github.com/element-hq/element-desktop/issues/465) and click the checkbox to create/update its PRs. -- [ ] Later, check back and merge the PRs that succeeded to build. The ones that failed will get picked up by the [maintainer](https://docs.google.com/document/d/1V5VINWXATMpz9UBw4IKmVVB8aw3CxM0Jt7igtHnDfSk/edit#). +- [ ] Later, check back and merge the PRs that succeeded to build. The ones that failed will get picked up by the [maintainer](https://docs.google.com/document/d/1V5VINWXATMpz9UBw4IKmVVB8aw3CxM0Jt7igtHnDfSk/edit#). For final releases additionally do these steps: -- [ ] Archive done column on the [team board](https://github.com/orgs/element-hq/projects/67/views/34) _Note: this should be automated_ -- [ ] Add entry to the [milestones diary](https://docs.google.com/document/d/1cpRFJdfNCo2Ps6jqzQmatzbYEToSrQpyBug0aP_iwZE/edit#heading=h.6y55fw4t283z). The document says only to add significant releases, but we add all of them just in case. +- [ ] Archive done column on the [team board](https://github.com/orgs/element-hq/projects/67/views/34) _Note: this should be automated_ +- [ ] Add entry to the [milestones diary](https://docs.google.com/document/d/1cpRFJdfNCo2Ps6jqzQmatzbYEToSrQpyBug0aP_iwZE/edit#heading=h.6y55fw4t283z). The document says only to add significant releases, but we add all of them just in case. diff --git a/docs/review.md b/docs/review.md index 8f8dc5f09bb..c565db52978 100644 --- a/docs/review.md +++ b/docs/review.md @@ -10,53 +10,53 @@ When reviewing code, here are some things we look for and also things we avoid: ### We review for -- Correctness -- Performance -- Accessibility -- Security -- Quality via automated and manual testing -- Comments and documentation where needed -- Sharing knowledge of different areas among the team -- Ensuring it's something we're comfortable maintaining for the long term -- Progress indicators and local echo where appropriate with network activity +- Correctness +- Performance +- Accessibility +- Security +- Quality via automated and manual testing +- Comments and documentation where needed +- Sharing knowledge of different areas among the team +- Ensuring it's something we're comfortable maintaining for the long term +- Progress indicators and local echo where appropriate with network activity ### We should avoid -- Style nits that are already handled by the linter -- Dramatically increasing scope +- Style nits that are already handled by the linter +- Dramatically increasing scope ### Good practices -- Use empathetic language - - See also [Mindful Communication in Code - Reviews](https://kickstarter.engineering/a-guide-to-mindful-communication-in-code-reviews-48aab5282e5e) - and [How to Do Code Reviews Like a Human](https://mtlynch.io/human-code-reviews-1/) -- Authors should prefer smaller commits for easier reviewing and bisection -- Reviewers should be explicit about required versus optional changes - - Reviews are conversations and the PR author should feel comfortable - discussing and pushing back on changes before making them -- Reviewers are encouraged to ask for tests where they believe it is reasonable -- Core team should lead by example through their tone and language -- Take the time to thank and point out good code changes -- Using softer language like "please" and "what do you think?" goes a long way - towards making others feel like colleagues working towards a common goal +- Use empathetic language + - See also [Mindful Communication in Code + Reviews](https://kickstarter.engineering/a-guide-to-mindful-communication-in-code-reviews-48aab5282e5e) + and [How to Do Code Reviews Like a Human](https://mtlynch.io/human-code-reviews-1/) +- Authors should prefer smaller commits for easier reviewing and bisection +- Reviewers should be explicit about required versus optional changes + - Reviews are conversations and the PR author should feel comfortable + discussing and pushing back on changes before making them +- Reviewers are encouraged to ask for tests where they believe it is reasonable +- Core team should lead by example through their tone and language +- Take the time to thank and point out good code changes +- Using softer language like "please" and "what do you think?" goes a long way + towards making others feel like colleagues working towards a common goal ### Workflow -- Authors should request review from the element-web team by default (if someone on - the team is clearly the expert in an area, a direct review request to them may - be more appropriate) -- Reviewers should remove the team review request and request review from - themselves when starting a review to avoid double review -- If there are multiple related PRs authors should reference each of the PRs in - the others before requesting review. Reviewers might start reviewing from - different places and could miss other required PRs. -- Avoid force pushing to a PR after the first round of review -- Use the GitHub default of merge commits when landing (avoid alternate options - like squash or rebase) -- PR author merges after review (assuming they have write access) -- Assign issues only when in progress to indicate to others what can be picked - up +- Authors should request review from the element-web team by default (if someone on + the team is clearly the expert in an area, a direct review request to them may + be more appropriate) +- Reviewers should remove the team review request and request review from + themselves when starting a review to avoid double review +- If there are multiple related PRs authors should reference each of the PRs in + the others before requesting review. Reviewers might start reviewing from + different places and could miss other required PRs. +- Avoid force pushing to a PR after the first round of review +- Use the GitHub default of merge commits when landing (avoid alternate options + like squash or rebase) +- PR author merges after review (assuming they have write access) +- Assign issues only when in progress to indicate to others what can be picked + up ## Code Quality @@ -64,10 +64,10 @@ In the past, we have occasionally written different kinds of tests for Element and the SDKs, but it hasn't been a consistent focus. Going forward, we'd like to change that. -- For new features, code reviewers will expect some form of automated testing to - be included by default -- For bug fixes, regression tests are of course great to have, but we don't want - to block fixes on this, so we won't require them at this time +- For new features, code reviewers will expect some form of automated testing to + be included by default +- For bug fixes, regression tests are of course great to have, but we don't want + to block fixes on this, so we won't require them at this time The above policy is not a strict rule, but instead it's meant to be a conversation between the author and reviewer. As an author, try to think about @@ -104,10 +104,10 @@ perspective. In more detail, our usual process for changes that affect the UI or alter user functionality is: -- For changes that will go live when merged, always flag Design and Product - teams as appropriate -- For changes guarded by a feature flag, Design and Product review is not - required (though may still be useful) since we can continue tweaking +- For changes that will go live when merged, always flag Design and Product + teams as appropriate +- For changes guarded by a feature flag, Design and Product review is not + required (though may still be useful) since we can continue tweaking As it can be difficult to review design work from looking at just the changed files in a PR, a [preview site](./pr-previews.md) that includes your changes diff --git a/docs/room-list-store.md b/docs/room-list-store.md index b87bf5f7bd2..4e131ee309d 100644 --- a/docs/room-list-store.md +++ b/docs/room-list-store.md @@ -6,11 +6,11 @@ It's so complicated it needs its own README. Legend: -- Orange = External event. -- Purple = Deterministic flow. -- Green = Algorithm definition. -- Red = Exit condition/point. -- Blue = Process definition. +- Orange = External event. +- Purple = Deterministic flow. +- Green = Algorithm definition. +- Red = Exit condition/point. +- Blue = Process definition. ## Algorithms involved @@ -68,14 +68,14 @@ simply get the manual sorting algorithm applied to them with no further involvem algorithm. There are 4 categories: Red, Grey, Bold, and Idle. Each has their own definition based off relative (perceived) importance to the user: -- **Red**: The room has unread mentions waiting for the user. -- **Grey**: The room has unread notifications waiting for the user. Notifications are simply unread - messages which cause a push notification or badge count. Typically, this is the default as rooms get - set to 'All Messages'. -- **Bold**: The room has unread messages waiting for the user. Essentially this is a grey room without - a badge/notification count (or 'Mentions Only'/'Muted'). -- **Idle**: No useful (see definition of useful above) activity has occurred in the room since the user - last read it. +- **Red**: The room has unread mentions waiting for the user. +- **Grey**: The room has unread notifications waiting for the user. Notifications are simply unread + messages which cause a push notification or badge count. Typically, this is the default as rooms get + set to 'All Messages'. +- **Bold**: The room has unread messages waiting for the user. Essentially this is a grey room without + a badge/notification count (or 'Mentions Only'/'Muted'). +- **Idle**: No useful (see definition of useful above) activity has occurred in the room since the user + last read it. Conveniently, each tag gets ordered by those categories as presented: red rooms appear above grey, grey above bold, etc. diff --git a/docs/settings.md b/docs/settings.md index 3f0636d3801..e555cd7c1e8 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -10,13 +10,13 @@ of dealing with the different levels and exposes easy to use getters and setters Granular Settings rely on a series of known levels in order to use the correct value for the scenario. These levels, in order of priority, are: -- `device` - The current user's device -- `room-device` - The current user's device, but only when in a specific room -- `room-account` - The current user's account, but only when in a specific room -- `account` - The current user's account -- `room` - A specific room (setting for all members of the room) -- `config` - Values are defined by the `setting_defaults` key (usually) in `config.json` -- `default` - The hardcoded default for the settings +- `device` - The current user's device +- `room-device` - The current user's device, but only when in a specific room +- `room-account` - The current user's account, but only when in a specific room +- `account` - The current user's account +- `room` - A specific room (setting for all members of the room) +- `config` - Values are defined by the `setting_defaults` key (usually) in `config.json` +- `default` - The hardcoded default for the settings Individual settings may control which levels are appropriate for them as part of the defaults. This is often to ensure that room administrators cannot force account-only settings upon participants. diff --git a/docs/translating-dev.md b/docs/translating-dev.md index e2a8e2c82a9..fd1ac232940 100644 --- a/docs/translating-dev.md +++ b/docs/translating-dev.md @@ -2,9 +2,9 @@ ## Requirements -- A working [Development Setup](../README.md#setting-up-a-dev-environment) -- Latest LTS version of Node.js installed -- Be able to understand English +- A working [Development Setup](../README.md#setting-up-a-dev-environment) +- Latest LTS version of Node.js installed +- Be able to understand English ## Translating strings vs. marking strings for translation @@ -65,17 +65,17 @@ There you can also require all translations to be redone if the meaning of the s 1. Add it to the array in `_t` for example `_t(TKEY, {variable: this.variable})` 1. Add the variable inside the string. The syntax for variables is `%(variable)s`. Please note the _s_ at the end. The name of the variable has to match the previous used name. -- You can use the special `count` variable to choose between multiple versions of the same string, in order to get the correct pluralization. E.g. `_t('You have %(count)s new messages', { count: 2 })` would show 'You have 2 new messages', while `_t('You have %(count)s new messages', { count: 1 })` would show 'You have one new message' (assuming a singular version of the string has been added to the translation file. See above). Passing in `count` is much preferred over having an if-statement choose the correct string to use, because some languages have much more complicated plural rules than english (e.g. they might need a completely different form if there are three things rather than two). -- If you want to translate text that includes e.g. hyperlinks or other HTML you have to also use tag substitution, e.g. `_t('Click here!', {}, { 'a': (sub) => {sub} })`. If you don't do the tag substitution you will end up showing literally '' rather than making a hyperlink. -- You can also use React components with normal variable substitution if you want to insert HTML markup, e.g. `_t('Your email address is %(emailAddress)s', { emailAddress: {userEmailAddress} })`. +- You can use the special `count` variable to choose between multiple versions of the same string, in order to get the correct pluralization. E.g. `_t('You have %(count)s new messages', { count: 2 })` would show 'You have 2 new messages', while `_t('You have %(count)s new messages', { count: 1 })` would show 'You have one new message' (assuming a singular version of the string has been added to the translation file. See above). Passing in `count` is much preferred over having an if-statement choose the correct string to use, because some languages have much more complicated plural rules than english (e.g. they might need a completely different form if there are three things rather than two). +- If you want to translate text that includes e.g. hyperlinks or other HTML you have to also use tag substitution, e.g. `_t('Click here!', {}, { 'a': (sub) => {sub} })`. If you don't do the tag substitution you will end up showing literally '' rather than making a hyperlink. +- You can also use React components with normal variable substitution if you want to insert HTML markup, e.g. `_t('Your email address is %(emailAddress)s', { emailAddress: {userEmailAddress} })`. ## Things to know/Style Guides -- Do not use `_t()` inside `getDefaultProps`: the translations aren't loaded when `getDefaultProps` is called, leading to missing translations. Use `_td()` to indicate that `_t()` will be called on the string later. -- If using translated strings as constants, translated strings can't be in constants loaded at class-load time since the translations won't be loaded. Mark the strings using `_td()` instead and perform the actual translation later. -- If a string is presented in the UI with punctuation like a full stop, include this in the translation strings, since punctuation varies between languages too. -- Avoid "translation in parts", i.e. concatenating translated strings or using translated strings in variable substitutions. Context is important for translations, and translating partial strings this way is simply not always possible. -- Concatenating strings often also introduces an implicit assumption about word order (e.g. that the subject of the sentence comes first), which is incorrect for many languages. -- Translation 'smell test': If you have a string that does not begin with a capital letter (is not the start of a sentence) or it ends with e.g. ':' or a preposition (e.g. 'to') you should recheck that you are not trying to translate a partial sentence. -- If you have multiple strings, that are almost identical, except some part (e.g. a word or two) it is still better to translate the full sentence multiple times. It may seem like inefficient repetition, but unlike programming where you try to minimize repetition, translation is much faster if you have many, full, clear, sentences to work with, rather than fewer, but incomplete sentence fragments. -- Don't forget curly braces when you assign an expression to JSX attributes in the render method) +- Do not use `_t()` inside `getDefaultProps`: the translations aren't loaded when `getDefaultProps` is called, leading to missing translations. Use `_td()` to indicate that `_t()` will be called on the string later. +- If using translated strings as constants, translated strings can't be in constants loaded at class-load time since the translations won't be loaded. Mark the strings using `_td()` instead and perform the actual translation later. +- If a string is presented in the UI with punctuation like a full stop, include this in the translation strings, since punctuation varies between languages too. +- Avoid "translation in parts", i.e. concatenating translated strings or using translated strings in variable substitutions. Context is important for translations, and translating partial strings this way is simply not always possible. +- Concatenating strings often also introduces an implicit assumption about word order (e.g. that the subject of the sentence comes first), which is incorrect for many languages. +- Translation 'smell test': If you have a string that does not begin with a capital letter (is not the start of a sentence) or it ends with e.g. ':' or a preposition (e.g. 'to') you should recheck that you are not trying to translate a partial sentence. +- If you have multiple strings, that are almost identical, except some part (e.g. a word or two) it is still better to translate the full sentence multiple times. It may seem like inefficient repetition, but unlike programming where you try to minimize repetition, translation is much faster if you have many, full, clear, sentences to work with, rather than fewer, but incomplete sentence fragments. +- Don't forget curly braces when you assign an expression to JSX attributes in the render method) diff --git a/docs/translating.md b/docs/translating.md index 657b8cebbcd..2b82453f933 100644 --- a/docs/translating.md +++ b/docs/translating.md @@ -2,9 +2,9 @@ ## Requirements -- Web Browser -- Be able to understand English -- Be able to understand the language you want to translate Element into +- Web Browser +- Be able to understand English +- Be able to understand the language you want to translate Element into ## Join #element-translations:matrix.org diff --git a/playwright/e2e/read-receipts/readme.md b/playwright/e2e/read-receipts/readme.md index 4e4dce297f5..33bcfeb93d7 100644 --- a/playwright/e2e/read-receipts/readme.md +++ b/playwright/e2e/read-receipts/readme.md @@ -2,19 +2,19 @@ Tips for writing these tests: -- Break up your tests into the smallest test case possible. The purpose of - these tests is to understand hard-to-find bugs, so small tests are necessary. - We know that Playwright recommends combining tests together for performance, but - that will frustrate our goals here. (We will need to find a different way to - reduce CI time.) +- Break up your tests into the smallest test case possible. The purpose of + these tests is to understand hard-to-find bugs, so small tests are necessary. + We know that Playwright recommends combining tests together for performance, but + that will frustrate our goals here. (We will need to find a different way to + reduce CI time.) -- Try to assert something after every action, to make sure it has completed. - E.g.: - markAsRead(room2); - assertRead(room2); - You should especially follow this rule if you are jumping to a different - room or similar straight afterward. +- Try to assert something after every action, to make sure it has completed. + E.g.: + markAsRead(room2); + assertRead(room2); + You should especially follow this rule if you are jumping to a different + room or similar straight afterward. -- Use assertStillRead() if you are asserting something is read when it was - also read before. This waits a little while to make sure you're not getting a - false positive. +- Use assertStillRead() if you are asserting something is read when it was + also read before. This waits a little while to make sure you're not getting a + false positive. diff --git a/playwright/plugins/oauth_server/README.md b/playwright/plugins/oauth_server/README.md index 541756384f9..5260704a663 100644 --- a/playwright/plugins/oauth_server/README.md +++ b/playwright/plugins/oauth_server/README.md @@ -4,16 +4,16 @@ A very simple OAuth identity provider server. The following endpoints are exposed: -- `/oauth/auth.html`: An OAuth2 [authorization endpoint](https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint). - In a proper OAuth2 system, this would prompt the user to log in; we just give a big "Submit" button (and an - auth code that can be changed if we want the next step to fail). It redirects back to the calling application - with a "code". +- `/oauth/auth.html`: An OAuth2 [authorization endpoint](https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint). + In a proper OAuth2 system, this would prompt the user to log in; we just give a big "Submit" button (and an + auth code that can be changed if we want the next step to fail). It redirects back to the calling application + with a "code". -- `/oauth/token`: An OAuth2 [token endpoint](https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint). - Receives the code issued by "auth.html" and, if it is valid, exchanges it for an OAuth2 access token. +- `/oauth/token`: An OAuth2 [token endpoint](https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint). + Receives the code issued by "auth.html" and, if it is valid, exchanges it for an OAuth2 access token. -- `/oauth/userinfo`: An OAuth2 [userinfo endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo). - Returns details about the owner of the offered access token. +- `/oauth/userinfo`: An OAuth2 [userinfo endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo). + Returns details about the owner of the offered access token. To start the server, do: diff --git a/res/themes/light/css/_light.pcss b/res/themes/light/css/_light.pcss index 5f278c6f160..32629a55f75 100644 --- a/res/themes/light/css/_light.pcss +++ b/res/themes/light/css/_light.pcss @@ -10,8 +10,8 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: "Inter", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", sans-serif, - "Noto Color Emoji"; +$font-family: "Inter", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", + sans-serif, "Noto Color Emoji"; $monospace-font-family: "Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier", monospace, "Noto Color Emoji"; diff --git a/src/components/structures/EmbeddedPage.tsx b/src/components/structures/EmbeddedPage.tsx index 5c7e81caf54..c471565d912 100644 --- a/src/components/structures/EmbeddedPage.tsx +++ b/src/components/structures/EmbeddedPage.tsx @@ -36,7 +36,7 @@ interface IState { export default class EmbeddedPage extends React.PureComponent { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private unmounted = false; private dispatcherRef?: string; diff --git a/src/components/structures/FilePanel.tsx b/src/components/structures/FilePanel.tsx index 07a20315f5f..2a823524472 100644 --- a/src/components/structures/FilePanel.tsx +++ b/src/components/structures/FilePanel.tsx @@ -51,7 +51,7 @@ interface IState { */ class FilePanel extends React.Component { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; // This is used to track if a decrypted event was a live event and should be // added to the timeline. diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index b26de2e645d..d2133f4f130 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -196,7 +196,7 @@ interface IReadReceiptForUser { */ export default class MessagePanel extends React.Component { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public static defaultProps = { disableGrouping: false, diff --git a/src/components/structures/NotificationPanel.tsx b/src/components/structures/NotificationPanel.tsx index edec675b140..d0460412db2 100644 --- a/src/components/structures/NotificationPanel.tsx +++ b/src/components/structures/NotificationPanel.tsx @@ -33,7 +33,7 @@ interface IState { */ export default class NotificationPanel extends React.PureComponent { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private card = React.createRef(); diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 9a9f29f82e5..d0c683e0fc5 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -63,7 +63,7 @@ interface IState { export default class RightPanel extends React.Component { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: Props, context: React.ContextType) { super(props, context); diff --git a/src/components/structures/RoomStatusBar.tsx b/src/components/structures/RoomStatusBar.tsx index 76f3b0c2298..3bd69148aee 100644 --- a/src/components/structures/RoomStatusBar.tsx +++ b/src/components/structures/RoomStatusBar.tsx @@ -89,7 +89,7 @@ interface IState { export default class RoomStatusBar extends React.PureComponent { private unmounted = false; public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: IProps, context: React.ContextType) { super(props, context); diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 54c2de76191..c87580490e4 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -378,7 +378,7 @@ export class RoomView extends React.Component { private roomViewBody = createRef(); public static contextType = SDKContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: IRoomProps, context: React.ContextType) { super(props, context); diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 3ea2a03c1a4..58c7d6d3fd0 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -597,7 +597,7 @@ const SpaceSetupPrivateInvite: React.FC<{ export default class SpaceRoomView extends React.PureComponent { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private dispatcherRef?: string; diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index be538a66693..a4dbd2bfe72 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -75,7 +75,7 @@ interface IState { export default class ThreadView extends React.Component { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private dispatcherRef?: string; private layoutWatcherRef?: string; diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index 1063cceffd1..a28089c9895 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -229,7 +229,7 @@ interface IEventIndexOpts { */ class TimelinePanel extends React.Component { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; // a map from room id to read marker event timestamp public static roomReadMarkerTsMap: Record = {}; diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index b2c79907468..5cd6ea7484c 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -79,7 +79,7 @@ const below = (rect: PartialDOMRect): MenuProps => { export default class UserMenu extends React.Component { public static contextType = SDKContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private dispatcherRef?: string; private themeWatcherRef?: string; diff --git a/src/components/structures/UserView.tsx b/src/components/structures/UserView.tsx index 635115877e7..0b4cd4e6330 100644 --- a/src/components/structures/UserView.tsx +++ b/src/components/structures/UserView.tsx @@ -32,7 +32,7 @@ interface IState { export default class UserView extends React.Component { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: IProps, context: React.ContextType) { super(props, context); diff --git a/src/components/structures/auth/SoftLogout.tsx b/src/components/structures/auth/SoftLogout.tsx index 117485df7ef..db72a0a04bb 100644 --- a/src/components/structures/auth/SoftLogout.tsx +++ b/src/components/structures/auth/SoftLogout.tsx @@ -64,7 +64,7 @@ interface IState { export default class SoftLogout extends React.Component { public static contextType = SDKContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: IProps, context: React.ContextType) { super(props, context); diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index d5749658c98..2b559aa74c3 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -126,7 +126,7 @@ interface IState { export default class MessageContextMenu extends React.Component { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private reactButtonRef = createRef(); // XXX Ref to a functional component diff --git a/src/components/views/elements/AppTile.tsx b/src/components/views/elements/AppTile.tsx index dae452fd5d0..56754f14a6b 100644 --- a/src/components/views/elements/AppTile.tsx +++ b/src/components/views/elements/AppTile.tsx @@ -116,7 +116,7 @@ interface IState { export default class AppTile extends React.Component { public static contextType = MatrixClientContext; - public declare context: ContextType; + declare public context: ContextType; public static defaultProps: Partial = { waitForIframeLoad: true, diff --git a/src/components/views/elements/EventListSummary.tsx b/src/components/views/elements/EventListSummary.tsx index 7562f992c10..776908375a1 100644 --- a/src/components/views/elements/EventListSummary.tsx +++ b/src/components/views/elements/EventListSummary.tsx @@ -73,7 +73,7 @@ export default class EventListSummary extends React.Component< IProps & Required> > { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public static defaultProps = { summaryLength: 1, diff --git a/src/components/views/elements/PersistentApp.tsx b/src/components/views/elements/PersistentApp.tsx index 6fbf0d31bc8..5f720dc85e8 100644 --- a/src/components/views/elements/PersistentApp.tsx +++ b/src/components/views/elements/PersistentApp.tsx @@ -25,7 +25,7 @@ interface IProps { export default class PersistentApp extends React.Component { public static contextType = MatrixClientContext; - public declare context: ContextType; + declare public context: ContextType; private room: Room; public constructor(props: IProps, context: ContextType) { diff --git a/src/components/views/elements/ReplyChain.tsx b/src/components/views/elements/ReplyChain.tsx index 71846d60658..8e10ca3af90 100644 --- a/src/components/views/elements/ReplyChain.tsx +++ b/src/components/views/elements/ReplyChain.tsx @@ -65,7 +65,7 @@ interface IState { // be low as each event being loaded (after the first) is triggered by an explicit user action. export default class ReplyChain extends React.Component { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private unmounted = false; private room: Room; diff --git a/src/components/views/elements/RoomAliasField.tsx b/src/components/views/elements/RoomAliasField.tsx index f092f9d25f9..faa0ccf1a64 100644 --- a/src/components/views/elements/RoomAliasField.tsx +++ b/src/components/views/elements/RoomAliasField.tsx @@ -33,7 +33,7 @@ interface IState { // Controlled form component wrapping Field for inputting a room alias scoped to a given domain export default class RoomAliasField extends React.PureComponent { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private fieldRef = createRef(); diff --git a/src/components/views/emojipicker/ReactionPicker.tsx b/src/components/views/emojipicker/ReactionPicker.tsx index b62df99e254..bd166344904 100644 --- a/src/components/views/emojipicker/ReactionPicker.tsx +++ b/src/components/views/emojipicker/ReactionPicker.tsx @@ -29,7 +29,7 @@ interface IState { class ReactionPicker extends React.Component { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: IProps, context: React.ContextType) { super(props, context); diff --git a/src/components/views/emojipicker/Search.tsx b/src/components/views/emojipicker/Search.tsx index 87397b6d4b2..bce045cb8c6 100644 --- a/src/components/views/emojipicker/Search.tsx +++ b/src/components/views/emojipicker/Search.tsx @@ -23,7 +23,7 @@ interface IProps { class Search extends React.PureComponent { public static contextType = RovingTabIndexContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private inputRef = React.createRef(); diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx index c45521830d6..e812f1c6bd9 100644 --- a/src/components/views/location/LocationPicker.tsx +++ b/src/components/views/location/LocationPicker.tsx @@ -42,7 +42,7 @@ const isSharingOwnLocation = (shareType: LocationShareType): boolean => class LocationPicker extends React.Component { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private map?: maplibregl.Map; private geolocate?: maplibregl.GeolocateControl; private marker?: maplibregl.Marker; diff --git a/src/components/views/messages/EditHistoryMessage.tsx b/src/components/views/messages/EditHistoryMessage.tsx index 8316d0835b3..fb6f04c08f0 100644 --- a/src/components/views/messages/EditHistoryMessage.tsx +++ b/src/components/views/messages/EditHistoryMessage.tsx @@ -45,7 +45,7 @@ interface IState { export default class EditHistoryMessage extends React.PureComponent { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private content = createRef(); private pills = new ReactRootManager(); diff --git a/src/components/views/messages/MAudioBody.tsx b/src/components/views/messages/MAudioBody.tsx index 326b1c38c8b..bf0cc9ee541 100644 --- a/src/components/views/messages/MAudioBody.tsx +++ b/src/components/views/messages/MAudioBody.tsx @@ -30,7 +30,7 @@ interface IState { export default class MAudioBody extends React.PureComponent { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public state: IState = {}; diff --git a/src/components/views/messages/MFileBody.tsx b/src/components/views/messages/MFileBody.tsx index fde3ea01841..1235b73b4bd 100644 --- a/src/components/views/messages/MFileBody.tsx +++ b/src/components/views/messages/MFileBody.tsx @@ -102,7 +102,7 @@ interface IState { export default class MFileBody extends React.Component { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public state: IState = {}; diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index 7d45e71a4b7..f77451108b9 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -57,7 +57,7 @@ interface IState { export default class MImageBody extends React.Component { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private unmounted = false; private image = createRef(); diff --git a/src/components/views/messages/MLocationBody.tsx b/src/components/views/messages/MLocationBody.tsx index b226476fa85..7735e64b031 100644 --- a/src/components/views/messages/MLocationBody.tsx +++ b/src/components/views/messages/MLocationBody.tsx @@ -30,7 +30,7 @@ interface IState { export default class MLocationBody extends React.Component { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private unmounted = false; private mapId: string; diff --git a/src/components/views/messages/MPollBody.tsx b/src/components/views/messages/MPollBody.tsx index 9e173e5f4a3..ba3962779f3 100644 --- a/src/components/views/messages/MPollBody.tsx +++ b/src/components/views/messages/MPollBody.tsx @@ -139,7 +139,7 @@ export function launchPollEditor(mxEvent: MatrixEvent, getRelationsForEvent?: Ge export default class MPollBody extends React.Component { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private seenEventIds: string[] = []; // Events we have already seen public constructor(props: IBodyProps, context: React.ContextType) { diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index 4036b9ddecf..822d2c3f593 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -34,7 +34,7 @@ interface IState { export default class MVideoBody extends React.PureComponent { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private videoRef = React.createRef(); private sizeWatcher?: string; diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index fdd02004292..bf92c993c56 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -262,7 +262,7 @@ interface IMessageActionBarProps { export default class MessageActionBar extends React.PureComponent { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public componentDidMount(): void { if (this.props.mxEvent.status && this.props.mxEvent.status !== EventStatus.SENT) { diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx index 60fcce6493e..dfb3fbb009e 100644 --- a/src/components/views/messages/MessageEvent.tsx +++ b/src/components/views/messages/MessageEvent.tsx @@ -85,7 +85,7 @@ export default class MessageEvent extends React.Component implements IMe private evTypes = new Map>(baseEvTypes.entries()); public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: IProps, context: React.ContextType) { super(props, context); diff --git a/src/components/views/messages/ReactionsRow.tsx b/src/components/views/messages/ReactionsRow.tsx index eba9499606b..605e6a7dfe0 100644 --- a/src/components/views/messages/ReactionsRow.tsx +++ b/src/components/views/messages/ReactionsRow.tsx @@ -75,7 +75,7 @@ interface IState { export default class ReactionsRow extends React.PureComponent { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: IProps, context: React.ContextType) { super(props, context); diff --git a/src/components/views/messages/ReactionsRowButton.tsx b/src/components/views/messages/ReactionsRowButton.tsx index 4a1d8d67fed..709edeffd82 100644 --- a/src/components/views/messages/ReactionsRowButton.tsx +++ b/src/components/views/messages/ReactionsRowButton.tsx @@ -38,7 +38,7 @@ export interface IProps { export default class ReactionsRowButton extends React.PureComponent { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public onClick = (): void => { const { mxEvent, myReactionEvent, content } = this.props; diff --git a/src/components/views/messages/ReactionsRowButtonTooltip.tsx b/src/components/views/messages/ReactionsRowButtonTooltip.tsx index 9790356762e..5f407e2e208 100644 --- a/src/components/views/messages/ReactionsRowButtonTooltip.tsx +++ b/src/components/views/messages/ReactionsRowButtonTooltip.tsx @@ -28,7 +28,7 @@ interface IProps { export default class ReactionsRowButtonTooltip extends React.PureComponent> { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public render(): React.ReactNode { const { content, reactionEvents, mxEvent, children } = this.props; diff --git a/src/components/views/messages/TextualBody.tsx b/src/components/views/messages/TextualBody.tsx index 0c05236176f..1a87d43a45a 100644 --- a/src/components/views/messages/TextualBody.tsx +++ b/src/components/views/messages/TextualBody.tsx @@ -55,7 +55,7 @@ export default class TextualBody extends React.Component { private ref = createRef(); public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public state = { links: [], diff --git a/src/components/views/messages/TextualEvent.tsx b/src/components/views/messages/TextualEvent.tsx index 1c1ba26d080..1c54963f76f 100644 --- a/src/components/views/messages/TextualEvent.tsx +++ b/src/components/views/messages/TextualEvent.tsx @@ -19,7 +19,7 @@ interface IProps { export default class TextualEvent extends React.Component { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public render(): React.ReactNode { const text = TextForEvent.textForEvent( diff --git a/src/components/views/right_panel/TimelineCard.tsx b/src/components/views/right_panel/TimelineCard.tsx index e0988eeaa5d..e745f7a2275 100644 --- a/src/components/views/right_panel/TimelineCard.tsx +++ b/src/components/views/right_panel/TimelineCard.tsx @@ -68,7 +68,7 @@ interface IState { export default class TimelineCard extends React.Component { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private dispatcherRef?: string; private layoutWatcherRef?: string; diff --git a/src/components/views/room_settings/AliasSettings.tsx b/src/components/views/room_settings/AliasSettings.tsx index 3c1a745530f..0bb29b7f895 100644 --- a/src/components/views/room_settings/AliasSettings.tsx +++ b/src/components/views/room_settings/AliasSettings.tsx @@ -94,7 +94,7 @@ interface IState { export default class AliasSettings extends React.Component { public static contextType = MatrixClientContext; - public declare context: ContextType; + declare public context: ContextType; public static defaultProps = { canSetAliases: false, diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index 423b5c62723..3ffd6648ea8 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -49,7 +49,7 @@ export default class Autocomplete extends React.PureComponent { private completionRefs: Record> = {}; public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: IProps, context: React.ContextType) { super(props, context); diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index d62a451b8b0..c6321ad53a3 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -121,7 +121,7 @@ interface IState { class EditMessageComposer extends React.Component { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private readonly editorRef = createRef(); private dispatcherRef?: string; diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 78d0ca5b28f..d66d90e0e28 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -296,7 +296,7 @@ export class UnwrappedEventTile extends React.Component }; public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private unmounted = false; diff --git a/src/components/views/rooms/MemberList.tsx b/src/components/views/rooms/MemberList.tsx index e503ce23633..5587b56bf82 100644 --- a/src/components/views/rooms/MemberList.tsx +++ b/src/components/views/rooms/MemberList.tsx @@ -75,7 +75,7 @@ export default class MemberList extends React.Component { private unmounted = false; public static contextType = SDKContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private tiles: Map = new Map(); public constructor(props: IProps, context: React.ContextType) { diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index 27189000d1c..9dcd107fed4 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -123,7 +123,7 @@ export class MessageComposer extends React.Component { private _voiceRecording: Optional; public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public static defaultProps = { compact: false, diff --git a/src/components/views/rooms/MessageComposerButtons.tsx b/src/components/views/rooms/MessageComposerButtons.tsx index 003c2afed96..65e53148f4a 100644 --- a/src/components/views/rooms/MessageComposerButtons.tsx +++ b/src/components/views/rooms/MessageComposerButtons.tsx @@ -290,7 +290,7 @@ interface IPollButtonProps { class PollButton extends React.PureComponent { public static contextType = OverflowMenuContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private onCreateClick = (): void => { this.context?.(); // close overflow menu diff --git a/src/components/views/rooms/ReplyPreview.tsx b/src/components/views/rooms/ReplyPreview.tsx index c820154b2b5..7851f7914d6 100644 --- a/src/components/views/rooms/ReplyPreview.tsx +++ b/src/components/views/rooms/ReplyPreview.tsx @@ -31,7 +31,7 @@ interface IProps { export default class ReplyPreview extends React.Component { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public render(): JSX.Element | null { if (!this.props.replyToEvent) return null; diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 853bebc4fef..f3bde66af97 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -424,7 +424,7 @@ export default class RoomList extends React.PureComponent { private treeRef = createRef(); public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: IProps, context: React.ContextType) { super(props, context); diff --git a/src/components/views/rooms/RoomUpgradeWarningBar.tsx b/src/components/views/rooms/RoomUpgradeWarningBar.tsx index 66519fa7669..e92be96cb20 100644 --- a/src/components/views/rooms/RoomUpgradeWarningBar.tsx +++ b/src/components/views/rooms/RoomUpgradeWarningBar.tsx @@ -25,7 +25,7 @@ interface IState { export default class RoomUpgradeWarningBar extends React.PureComponent { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: IProps, context: React.ContextType) { super(props, context); diff --git a/src/components/views/rooms/SearchResultTile.tsx b/src/components/views/rooms/SearchResultTile.tsx index 5ebbaffdd92..94f5e6da9db 100644 --- a/src/components/views/rooms/SearchResultTile.tsx +++ b/src/components/views/rooms/SearchResultTile.tsx @@ -36,7 +36,7 @@ interface IProps { export default class SearchResultTile extends React.Component { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; // A map of private callEventGroupers = new Map(); diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 252957c2c77..6533415a83f 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -241,7 +241,7 @@ interface ISendMessageComposerProps extends MatrixClientProps { export class SendMessageComposer extends React.Component { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private readonly prepareToEncrypt?: DebouncedFunc<() => void>; private readonly editorRef = createRef(); diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index a6d4a2fc276..a8335a9902b 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -53,7 +53,7 @@ interface IState { */ export default class VoiceRecordComposerTile extends React.PureComponent { public static contextType = RoomContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private voiceRecordingId: string; public constructor(props: IProps, context: React.ContextType) { diff --git a/src/components/views/settings/CryptographyPanel.tsx b/src/components/views/settings/CryptographyPanel.tsx index b418c0b05df..fbd696f243f 100644 --- a/src/components/views/settings/CryptographyPanel.tsx +++ b/src/components/views/settings/CryptographyPanel.tsx @@ -32,7 +32,7 @@ interface IState { export default class CryptographyPanel extends React.Component { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: IProps, context: React.ContextType) { super(props); diff --git a/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx b/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx index d29d82853aa..0da257607ee 100644 --- a/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx @@ -28,7 +28,7 @@ interface IProps { export default class BridgeSettingsTab extends React.Component { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private renderBridgeCard(event: MatrixEvent, room: Room | null): ReactNode { const content = event.getContent(); diff --git a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx index 048fe5df9d9..31c361de1bd 100644 --- a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx @@ -33,7 +33,7 @@ interface IState { export default class GeneralRoomSettingsTab extends React.Component { public static contextType = MatrixClientContext; - public declare context: ContextType; + declare public context: ContextType; public constructor(props: IProps, context: ContextType) { super(props, context); diff --git a/src/components/views/settings/tabs/room/NotificationSettingsTab.tsx b/src/components/views/settings/tabs/room/NotificationSettingsTab.tsx index f668b1ff076..9aabf1edb0d 100644 --- a/src/components/views/settings/tabs/room/NotificationSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/NotificationSettingsTab.tsx @@ -42,7 +42,7 @@ export default class NotificationsSettingsTab extends React.Component(); public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: IProps, context: React.ContextType) { super(props, context); diff --git a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx index 8261bfd3eb4..5fada0b6bc4 100644 --- a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx @@ -81,7 +81,7 @@ interface IBannedUserProps { export class BannedUser extends React.Component { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; private onUnbanClick = (): void => { this.context.unban(this.props.member.roomId, this.props.member.userId).catch((err) => { @@ -134,7 +134,7 @@ interface RolesRoomSettingsTabState { export default class RolesRoomSettingsTab extends React.Component { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: IProps) { super(props); diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx index 7d9f75f828a..ece6a7deafc 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx @@ -60,7 +60,7 @@ interface IState { export default class SecurityRoomSettingsTab extends React.Component { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: IProps, context: React.ContextType) { super(props, context); diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx index 7866131a01e..f19343be206 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx @@ -32,7 +32,7 @@ interface IState { export default class HelpUserSettingsTab extends React.Component { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: IProps, context: React.ContextType) { super(props, context); diff --git a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx index 36d336faa32..9711159a10e 100644 --- a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx @@ -51,7 +51,7 @@ const mapDeviceKindToHandlerValue = (deviceKind: MediaDeviceKindEnum): string | export default class VoiceUserSettingsTab extends React.Component<{}, IState> { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public constructor(props: {}, context: React.ContextType) { super(props, context); diff --git a/src/stores/room-list/previews/PollStartEventPreview.ts b/src/stores/room-list/previews/PollStartEventPreview.ts index bb005f4a940..7548cf12f71 100644 --- a/src/stores/room-list/previews/PollStartEventPreview.ts +++ b/src/stores/room-list/previews/PollStartEventPreview.ts @@ -18,7 +18,7 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; export class PollStartEventPreview implements IPreview { public static contextType = MatrixClientContext; - public declare context: React.ContextType; + declare public context: React.ContextType; public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null { let eventContent = event.getContent(); diff --git a/test/unit-tests/TestSdkContext.ts b/test/unit-tests/TestSdkContext.ts index d4bda4f8898..0d7e806514c 100644 --- a/test/unit-tests/TestSdkContext.ts +++ b/test/unit-tests/TestSdkContext.ts @@ -27,18 +27,18 @@ import { * replace individual stores. This is useful for tests which need to mock out stores. */ export class TestSdkContext extends SdkContextClass { - public declare _RightPanelStore?: RightPanelStore; - public declare _RoomNotificationStateStore?: RoomNotificationStateStore; - public declare _RoomViewStore?: RoomViewStore; - public declare _WidgetPermissionStore?: WidgetPermissionStore; - public declare _WidgetLayoutStore?: WidgetLayoutStore; - public declare _WidgetStore?: WidgetStore; - public declare _PosthogAnalytics?: PosthogAnalytics; - public declare _SlidingSyncManager?: SlidingSyncManager; - public declare _SpaceStore?: SpaceStoreClass; - public declare _VoiceBroadcastRecordingsStore?: VoiceBroadcastRecordingsStore; - public declare _VoiceBroadcastPreRecordingStore?: VoiceBroadcastPreRecordingStore; - public declare _VoiceBroadcastPlaybacksStore?: VoiceBroadcastPlaybacksStore; + declare public _RightPanelStore?: RightPanelStore; + declare public _RoomNotificationStateStore?: RoomNotificationStateStore; + declare public _RoomViewStore?: RoomViewStore; + declare public _WidgetPermissionStore?: WidgetPermissionStore; + declare public _WidgetLayoutStore?: WidgetLayoutStore; + declare public _WidgetStore?: WidgetStore; + declare public _PosthogAnalytics?: PosthogAnalytics; + declare public _SlidingSyncManager?: SlidingSyncManager; + declare public _SpaceStore?: SpaceStoreClass; + declare public _VoiceBroadcastRecordingsStore?: VoiceBroadcastRecordingsStore; + declare public _VoiceBroadcastPreRecordingStore?: VoiceBroadcastPreRecordingStore; + declare public _VoiceBroadcastPlaybacksStore?: VoiceBroadcastPlaybacksStore; constructor() { super();