diff --git a/.eslintrc.js b/.eslintrc.js index 827b3739493..9d689422289 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -63,6 +63,11 @@ module.exports = { "@typescript-eslint/ban-ts-comment": "off", }, }], + settings: { + react: { + version: "detect", + } + } }; function buildRestrictedPropertiesOptions(properties, message) { diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 0ae59da09a5..4f9826391ae 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -10,6 +10,8 @@ on: jobs: end-to-end: runs-on: ubuntu-latest + env: + PR_NUMBER: ${{github.event.number}} container: vectorim/element-web-ci-e2etests-env:latest steps: - name: Checkout code diff --git a/.stylelintrc.js b/.stylelintrc.js index 0e6de7000fd..c044b19a632 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -17,6 +17,7 @@ module.exports = { "selector-list-comma-newline-after": null, "at-rule-no-unknown": null, "no-descending-specificity": null, + "no-empty-first-line": true, "scss/at-rule-no-unknown": [true, { // https://github.com/vector-im/element-web/issues/10544 "ignoreAtRules": ["define-mixin"], diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fcf11e37e2..fc368aed4a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,127 @@ -Changes in [3.29.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.29.1) (2021-09-13) +Changes in [3.30.0](https://github.com/vector-im/element-desktop/releases/tag/v3.30.0) (2021-09-14) =================================================================================================== -## 🔒 SECURITY FIXES - * Fix a security issue with message key sharing. See https://matrix.org/blog/2021/09/13/vulnerability-disclosure-key-sharing - for details. +## ✨ Features + * Add bubble highlight styling ([\#6582](https://github.com/matrix-org/matrix-react-sdk/pull/6582)). Fixes vector-im/element-web#18295 and vector-im/element-web#18295. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * [Release] Add config option to turn on in-room event sending timing metrics ([\#6773](https://github.com/matrix-org/matrix-react-sdk/pull/6773)). + * Create narrow mode for Composer ([\#6682](https://github.com/matrix-org/matrix-react-sdk/pull/6682)). Fixes vector-im/element-web#18533 and vector-im/element-web#18533. + * Prefer matrix.to alias links over room id in spaces & share ([\#6745](https://github.com/matrix-org/matrix-react-sdk/pull/6745)). Fixes vector-im/element-web#18796 and vector-im/element-web#18796. + * Stop automatic playback of voice messages if a non-voice message is encountered ([\#6728](https://github.com/matrix-org/matrix-react-sdk/pull/6728)). Fixes vector-im/element-web#18850 and vector-im/element-web#18850. + * Show call length during a call ([\#6700](https://github.com/matrix-org/matrix-react-sdk/pull/6700)). Fixes vector-im/element-web#18566 and vector-im/element-web#18566. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Serialize and retry mass-leave when leaving space ([\#6737](https://github.com/matrix-org/matrix-react-sdk/pull/6737)). Fixes vector-im/element-web#18789 and vector-im/element-web#18789. + * Improve form handling in and around space creation ([\#6739](https://github.com/matrix-org/matrix-react-sdk/pull/6739)). Fixes vector-im/element-web#18775 and vector-im/element-web#18775. + * Split autoplay GIFs and videos into different settings ([\#6726](https://github.com/matrix-org/matrix-react-sdk/pull/6726)). Fixes vector-im/element-web#5771 and vector-im/element-web#5771. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Add autoplay for voice messages ([\#6710](https://github.com/matrix-org/matrix-react-sdk/pull/6710)). Fixes vector-im/element-web#18804, vector-im/element-web#18715, vector-im/element-web#18714 vector-im/element-web#17961 and vector-im/element-web#18804. + * Allow to use basic html to format invite messages ([\#6703](https://github.com/matrix-org/matrix-react-sdk/pull/6703)). Fixes vector-im/element-web#15738 and vector-im/element-web#15738. Contributed by [skolmer](https://github.com/skolmer). + * Allow widgets, when eligible, to interact with more rooms as per MSC2762 ([\#6684](https://github.com/matrix-org/matrix-react-sdk/pull/6684)). + * Remove arbitrary limits from send/receive events for widgets ([\#6719](https://github.com/matrix-org/matrix-react-sdk/pull/6719)). Fixes vector-im/element-web#17994 and vector-im/element-web#17994. + * Reload suggested rooms if we see the state change down /sync ([\#6715](https://github.com/matrix-org/matrix-react-sdk/pull/6715)). Fixes vector-im/element-web#18761 and vector-im/element-web#18761. + * When creating private spaces, make the initial rooms restricted if supported ([\#6721](https://github.com/matrix-org/matrix-react-sdk/pull/6721)). Fixes vector-im/element-web#18722 and vector-im/element-web#18722. + * Threading exploration work ([\#6658](https://github.com/matrix-org/matrix-react-sdk/pull/6658)). Fixes vector-im/element-web#18532 and vector-im/element-web#18532. + * Default to `Don't leave any` when leaving a space ([\#6697](https://github.com/matrix-org/matrix-react-sdk/pull/6697)). Fixes vector-im/element-web#18592 and vector-im/element-web#18592. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Special case redaction event sending from widgets per MSC2762 ([\#6686](https://github.com/matrix-org/matrix-react-sdk/pull/6686)). Fixes vector-im/element-web#18573 and vector-im/element-web#18573. + * Add active speaker indicators ([\#6639](https://github.com/matrix-org/matrix-react-sdk/pull/6639)). Fixes vector-im/element-web#17627 and vector-im/element-web#17627. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Increase general app performance by optimizing layers ([\#6644](https://github.com/matrix-org/matrix-react-sdk/pull/6644)). Fixes vector-im/element-web#18730 and vector-im/element-web#18730. Contributed by [Palid](https://github.com/Palid). + +## 🐛 Bug Fixes + * Fix autocomplete not having y-scroll ([\#6802](https://github.com/matrix-org/matrix-react-sdk/pull/6802)). + * Fix emoji picker and stickerpicker not appearing correctly when opened ([\#6801](https://github.com/matrix-org/matrix-react-sdk/pull/6801)). + * Debounce read marker update on scroll ([\#6774](https://github.com/matrix-org/matrix-react-sdk/pull/6774)). + * Fix Space creation wizard go to my first room button behaviour ([\#6748](https://github.com/matrix-org/matrix-react-sdk/pull/6748)). Fixes vector-im/element-web#18764 and vector-im/element-web#18764. + * Fix scroll being stuck at bottom ([\#6751](https://github.com/matrix-org/matrix-react-sdk/pull/6751)). Fixes vector-im/element-web#18903 and vector-im/element-web#18903. + * Fix widgets not remembering identity verification when asked to. ([\#6742](https://github.com/matrix-org/matrix-react-sdk/pull/6742)). Fixes vector-im/element-web#15631 and vector-im/element-web#15631. + * Add missing pluralisation i18n strings for Spaces ([\#6738](https://github.com/matrix-org/matrix-react-sdk/pull/6738)). Fixes vector-im/element-web#18780 and vector-im/element-web#18780. + * Make ForgotPassword UX slightly more user friendly ([\#6636](https://github.com/matrix-org/matrix-react-sdk/pull/6636)). Fixes vector-im/element-web#11531 and vector-im/element-web#11531. Contributed by [Palid](https://github.com/Palid). + * Don't context switch room on SpaceStore ready as it can break permalinks ([\#6730](https://github.com/matrix-org/matrix-react-sdk/pull/6730)). Fixes vector-im/element-web#17974 and vector-im/element-web#17974. + * Fix explore rooms button not working during space creation wizard ([\#6729](https://github.com/matrix-org/matrix-react-sdk/pull/6729)). Fixes vector-im/element-web#18762 and vector-im/element-web#18762. + * Fix bug where one party's media would sometimes not be shown ([\#6731](https://github.com/matrix-org/matrix-react-sdk/pull/6731)). + * Only make the initial space rooms suggested by default ([\#6714](https://github.com/matrix-org/matrix-react-sdk/pull/6714)). Fixes vector-im/element-web#18760 and vector-im/element-web#18760. + * Replace fake username in EventTilePreview with a proper loading state ([\#6702](https://github.com/matrix-org/matrix-react-sdk/pull/6702)). Fixes vector-im/element-web#15897 and vector-im/element-web#15897. Contributed by [skolmer](https://github.com/skolmer). + * Don't send prehistorical events to widgets during decryption at startup ([\#6695](https://github.com/matrix-org/matrix-react-sdk/pull/6695)). Fixes vector-im/element-web#18060 and vector-im/element-web#18060. + * When creating subspaces properly set restricted join rule ([\#6725](https://github.com/matrix-org/matrix-react-sdk/pull/6725)). Fixes vector-im/element-web#18797 and vector-im/element-web#18797. + * Fix the Image View not openning for some pinned messages ([\#6723](https://github.com/matrix-org/matrix-react-sdk/pull/6723)). Fixes vector-im/element-web#18422 and vector-im/element-web#18422. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Show autocomplete sections vertically ([\#6722](https://github.com/matrix-org/matrix-react-sdk/pull/6722)). Fixes vector-im/element-web#18860 and vector-im/element-web#18860. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix EmojiPicker filtering to lower case emojibase data strings ([\#6717](https://github.com/matrix-org/matrix-react-sdk/pull/6717)). Fixes vector-im/element-web#18686 and vector-im/element-web#18686. + * Clear currentRoomId when viewing home page, fixing document title ([\#6716](https://github.com/matrix-org/matrix-react-sdk/pull/6716)). Fixes vector-im/element-web#18668 and vector-im/element-web#18668. + * Fix membership updates to Spaces not applying in real-time ([\#6713](https://github.com/matrix-org/matrix-react-sdk/pull/6713)). Fixes vector-im/element-web#18737 and vector-im/element-web#18737. + * Don't show a double stacked invite modals when inviting to Spaces ([\#6698](https://github.com/matrix-org/matrix-react-sdk/pull/6698)). Fixes vector-im/element-web#18745 and vector-im/element-web#18745. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Remove non-functional DuckDuckGo Autocomplete Provider ([\#6712](https://github.com/matrix-org/matrix-react-sdk/pull/6712)). Fixes vector-im/element-web#18778 and vector-im/element-web#18778. + * Filter members on `MemberList` load ([\#6708](https://github.com/matrix-org/matrix-react-sdk/pull/6708)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix improper voice messages being produced in Firefox and sometimes other browsers. ([\#6696](https://github.com/matrix-org/matrix-react-sdk/pull/6696)). Fixes vector-im/element-web#18587 and vector-im/element-web#18587. + * Fix client forgetting which capabilities a widget was approved for ([\#6685](https://github.com/matrix-org/matrix-react-sdk/pull/6685)). Fixes vector-im/element-web#18786 and vector-im/element-web#18786. + * Fix left panel widgets not remembering collapsed state ([\#6687](https://github.com/matrix-org/matrix-react-sdk/pull/6687)). Fixes vector-im/element-web#17803 and vector-im/element-web#17803. + * Fix changelog link colour back to blue ([\#6692](https://github.com/matrix-org/matrix-react-sdk/pull/6692)). Fixes vector-im/element-web#18726 and vector-im/element-web#18726. + * Soften codeblock border color ([\#6564](https://github.com/matrix-org/matrix-react-sdk/pull/6564)). Fixes vector-im/element-web#18367 and vector-im/element-web#18367. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Pause ringing more aggressively ([\#6691](https://github.com/matrix-org/matrix-react-sdk/pull/6691)). Fixes vector-im/element-web#18588 and vector-im/element-web#18588. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix command autocomplete ([\#6680](https://github.com/matrix-org/matrix-react-sdk/pull/6680)). Fixes vector-im/element-web#18670 and vector-im/element-web#18670. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Don't re-sort the room-list based on profile/status changes ([\#6595](https://github.com/matrix-org/matrix-react-sdk/pull/6595)). Fixes vector-im/element-web#110 and vector-im/element-web#110. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix codeblock formatting with syntax highlighting on ([\#6681](https://github.com/matrix-org/matrix-react-sdk/pull/6681)). Fixes vector-im/element-web#18739 vector-im/element-web#18365 and vector-im/element-web#18739. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Add padding to the Add button in the notification settings ([\#6665](https://github.com/matrix-org/matrix-react-sdk/pull/6665)). Fixes vector-im/element-web#18706 and vector-im/element-web#18706. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + +Changes in [3.30.0-rc.2](https://github.com/vector-im/element-desktop/releases/tag/v3.30.0-rc.2) (2021-09-09) +============================================================================================================= + +## ✨ Features + * [Release] Add config option to turn on in-room event sending timing metrics ([\#6773](https://github.com/matrix-org/matrix-react-sdk/pull/6773)). + +## 🐛 Bug Fixes + * Debounce read marker update on scroll ([\#6774](https://github.com/matrix-org/matrix-react-sdk/pull/6774)). + +Changes in [3.30.0-rc.1](https://github.com/vector-im/element-desktop/releases/tag/v3.30.0-rc.1) (2021-09-07) +============================================================================================================= + +## ✨ Features + * Create narrow mode for Composer ([\#6682](https://github.com/matrix-org/matrix-react-sdk/pull/6682)). Fixes vector-im/element-web#18533 and vector-im/element-web#18533. + * Prefer matrix.to alias links over room id in spaces & share ([\#6745](https://github.com/matrix-org/matrix-react-sdk/pull/6745)). Fixes vector-im/element-web#18796 and vector-im/element-web#18796. + * Add bubble highlight styling ([\#6582](https://github.com/matrix-org/matrix-react-sdk/pull/6582)). Fixes vector-im/element-web#18295 and vector-im/element-web#18295. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Stop automatic playback of voice messages if a non-voice message is encountered ([\#6728](https://github.com/matrix-org/matrix-react-sdk/pull/6728)). Fixes vector-im/element-web#18850 and vector-im/element-web#18850. + * Show call length during a call ([\#6700](https://github.com/matrix-org/matrix-react-sdk/pull/6700)). Fixes vector-im/element-web#18566 and vector-im/element-web#18566. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Serialize and retry mass-leave when leaving space ([\#6737](https://github.com/matrix-org/matrix-react-sdk/pull/6737)). Fixes vector-im/element-web#18789 and vector-im/element-web#18789. + * Improve form handling in and around space creation ([\#6739](https://github.com/matrix-org/matrix-react-sdk/pull/6739)). Fixes vector-im/element-web#18775 and vector-im/element-web#18775. + * Split autoplay GIFs and videos into different settings ([\#6726](https://github.com/matrix-org/matrix-react-sdk/pull/6726)). Fixes vector-im/element-web#5771 and vector-im/element-web#5771. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Add autoplay for voice messages ([\#6710](https://github.com/matrix-org/matrix-react-sdk/pull/6710)). Fixes vector-im/element-web#18804, vector-im/element-web#18715, vector-im/element-web#18714 vector-im/element-web#17961 and vector-im/element-web#18804. + * Allow to use basic html to format invite messages ([\#6703](https://github.com/matrix-org/matrix-react-sdk/pull/6703)). Fixes vector-im/element-web#15738 and vector-im/element-web#15738. Contributed by [skolmer](https://github.com/skolmer). + * Allow widgets, when eligible, to interact with more rooms as per MSC2762 ([\#6684](https://github.com/matrix-org/matrix-react-sdk/pull/6684)). + * Remove arbitrary limits from send/receive events for widgets ([\#6719](https://github.com/matrix-org/matrix-react-sdk/pull/6719)). Fixes vector-im/element-web#17994 and vector-im/element-web#17994. + * Reload suggested rooms if we see the state change down /sync ([\#6715](https://github.com/matrix-org/matrix-react-sdk/pull/6715)). Fixes vector-im/element-web#18761 and vector-im/element-web#18761. + * When creating private spaces, make the initial rooms restricted if supported ([\#6721](https://github.com/matrix-org/matrix-react-sdk/pull/6721)). Fixes vector-im/element-web#18722 and vector-im/element-web#18722. + * Threading exploration work ([\#6658](https://github.com/matrix-org/matrix-react-sdk/pull/6658)). Fixes vector-im/element-web#18532 and vector-im/element-web#18532. + * Default to `Don't leave any` when leaving a space ([\#6697](https://github.com/matrix-org/matrix-react-sdk/pull/6697)). Fixes vector-im/element-web#18592 and vector-im/element-web#18592. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Special case redaction event sending from widgets per MSC2762 ([\#6686](https://github.com/matrix-org/matrix-react-sdk/pull/6686)). Fixes vector-im/element-web#18573 and vector-im/element-web#18573. + * Add active speaker indicators ([\#6639](https://github.com/matrix-org/matrix-react-sdk/pull/6639)). Fixes vector-im/element-web#17627 and vector-im/element-web#17627. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Increase general app performance by optimizing layers ([\#6644](https://github.com/matrix-org/matrix-react-sdk/pull/6644)). Fixes vector-im/element-web#18730 and vector-im/element-web#18730. Contributed by [Palid](https://github.com/Palid). + +## 🐛 Bug Fixes + * Fix Space creation wizard go to my first room button behaviour ([\#6748](https://github.com/matrix-org/matrix-react-sdk/pull/6748)). Fixes vector-im/element-web#18764 and vector-im/element-web#18764. + * Fix scroll being stuck at bottom ([\#6751](https://github.com/matrix-org/matrix-react-sdk/pull/6751)). Fixes vector-im/element-web#18903 and vector-im/element-web#18903. + * Fix widgets not remembering identity verification when asked to. ([\#6742](https://github.com/matrix-org/matrix-react-sdk/pull/6742)). Fixes vector-im/element-web#15631 and vector-im/element-web#15631. + * Add missing pluralisation i18n strings for Spaces ([\#6738](https://github.com/matrix-org/matrix-react-sdk/pull/6738)). Fixes vector-im/element-web#18780 and vector-im/element-web#18780. + * Make ForgotPassword UX slightly more user friendly ([\#6636](https://github.com/matrix-org/matrix-react-sdk/pull/6636)). Fixes vector-im/element-web#11531 and vector-im/element-web#11531. Contributed by [Palid](https://github.com/Palid). + * Don't context switch room on SpaceStore ready as it can break permalinks ([\#6730](https://github.com/matrix-org/matrix-react-sdk/pull/6730)). Fixes vector-im/element-web#17974 and vector-im/element-web#17974. + * Fix explore rooms button not working during space creation wizard ([\#6729](https://github.com/matrix-org/matrix-react-sdk/pull/6729)). Fixes vector-im/element-web#18762 and vector-im/element-web#18762. + * Fix bug where one party's media would sometimes not be shown ([\#6731](https://github.com/matrix-org/matrix-react-sdk/pull/6731)). + * Only make the initial space rooms suggested by default ([\#6714](https://github.com/matrix-org/matrix-react-sdk/pull/6714)). Fixes vector-im/element-web#18760 and vector-im/element-web#18760. + * Replace fake username in EventTilePreview with a proper loading state ([\#6702](https://github.com/matrix-org/matrix-react-sdk/pull/6702)). Fixes vector-im/element-web#15897 and vector-im/element-web#15897. Contributed by [skolmer](https://github.com/skolmer). + * Don't send prehistorical events to widgets during decryption at startup ([\#6695](https://github.com/matrix-org/matrix-react-sdk/pull/6695)). Fixes vector-im/element-web#18060 and vector-im/element-web#18060. + * When creating subspaces properly set restricted join rule ([\#6725](https://github.com/matrix-org/matrix-react-sdk/pull/6725)). Fixes vector-im/element-web#18797 and vector-im/element-web#18797. + * Fix the Image View not openning for some pinned messages ([\#6723](https://github.com/matrix-org/matrix-react-sdk/pull/6723)). Fixes vector-im/element-web#18422 and vector-im/element-web#18422. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Show autocomplete sections vertically ([\#6722](https://github.com/matrix-org/matrix-react-sdk/pull/6722)). Fixes vector-im/element-web#18860 and vector-im/element-web#18860. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix EmojiPicker filtering to lower case emojibase data strings ([\#6717](https://github.com/matrix-org/matrix-react-sdk/pull/6717)). Fixes vector-im/element-web#18686 and vector-im/element-web#18686. + * Clear currentRoomId when viewing home page, fixing document title ([\#6716](https://github.com/matrix-org/matrix-react-sdk/pull/6716)). Fixes vector-im/element-web#18668 and vector-im/element-web#18668. + * Fix membership updates to Spaces not applying in real-time ([\#6713](https://github.com/matrix-org/matrix-react-sdk/pull/6713)). Fixes vector-im/element-web#18737 and vector-im/element-web#18737. + * Don't show a double stacked invite modals when inviting to Spaces ([\#6698](https://github.com/matrix-org/matrix-react-sdk/pull/6698)). Fixes vector-im/element-web#18745 and vector-im/element-web#18745. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Remove non-functional DuckDuckGo Autocomplete Provider ([\#6712](https://github.com/matrix-org/matrix-react-sdk/pull/6712)). Fixes vector-im/element-web#18778 and vector-im/element-web#18778. + * Filter members on `MemberList` load ([\#6708](https://github.com/matrix-org/matrix-react-sdk/pull/6708)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix improper voice messages being produced in Firefox and sometimes other browsers. ([\#6696](https://github.com/matrix-org/matrix-react-sdk/pull/6696)). Fixes vector-im/element-web#18587 and vector-im/element-web#18587. + * Fix client forgetting which capabilities a widget was approved for ([\#6685](https://github.com/matrix-org/matrix-react-sdk/pull/6685)). Fixes vector-im/element-web#18786 and vector-im/element-web#18786. + * Fix left panel widgets not remembering collapsed state ([\#6687](https://github.com/matrix-org/matrix-react-sdk/pull/6687)). Fixes vector-im/element-web#17803 and vector-im/element-web#17803. + * Fix changelog link colour back to blue ([\#6692](https://github.com/matrix-org/matrix-react-sdk/pull/6692)). Fixes vector-im/element-web#18726 and vector-im/element-web#18726. + * Soften codeblock border color ([\#6564](https://github.com/matrix-org/matrix-react-sdk/pull/6564)). Fixes vector-im/element-web#18367 and vector-im/element-web#18367. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Pause ringing more aggressively ([\#6691](https://github.com/matrix-org/matrix-react-sdk/pull/6691)). Fixes vector-im/element-web#18588 and vector-im/element-web#18588. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix command autocomplete ([\#6680](https://github.com/matrix-org/matrix-react-sdk/pull/6680)). Fixes vector-im/element-web#18670 and vector-im/element-web#18670. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Don't re-sort the room-list based on profile/status changes ([\#6595](https://github.com/matrix-org/matrix-react-sdk/pull/6595)). Fixes vector-im/element-web#110 and vector-im/element-web#110. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix codeblock formatting with syntax highlighting on ([\#6681](https://github.com/matrix-org/matrix-react-sdk/pull/6681)). Fixes vector-im/element-web#18739 vector-im/element-web#18365 and vector-im/element-web#18739. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Add padding to the Add button in the notification settings ([\#6665](https://github.com/matrix-org/matrix-react-sdk/pull/6665)). Fixes vector-im/element-web#18706 and vector-im/element-web#18706. Contributed by [SimonBrandner](https://github.com/SimonBrandner). Changes in [3.29.0](https://github.com/vector-im/element-desktop/releases/tag/v3.29.0) (2021-08-31) =================================================================================================== diff --git a/package.json b/package.json index 2bba17397fa..a24a509b24b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.29.1", + "version": "3.30.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -82,8 +82,8 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "12.4.1", - "matrix-widget-api": "^0.1.0-beta.15", + "matrix-js-sdk": "12.5.0", + "matrix-widget-api": "^0.1.0-beta.16", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", "pako": "^2.0.3", @@ -151,7 +151,7 @@ "@typescript-eslint/eslint-plugin": "^4.17.0", "@typescript-eslint/parser": "^4.17.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1", - "allchange": "^1.0.0", + "allchange": "^1.0.3", "babel-jest": "^26.6.3", "chokidar": "^3.5.1", "concurrently": "^5.3.0", diff --git a/res/css/_common.scss b/res/css/_common.scss index d01532a4a2a..a16e7d4d8f8 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -53,8 +53,8 @@ html { body { font-family: $font-family; font-size: $font-15px; - background-color: $primary-bg-color; - color: $primary-fg-color; + background-color: $background; + color: $primary-content; border: 0px; margin: 0px; @@ -89,7 +89,7 @@ b { } h2 { - color: $primary-fg-color; + color: $primary-content; font-weight: 400; font-size: $font-18px; margin-top: 16px; @@ -142,12 +142,12 @@ textarea::placeholder { input[type=text], input[type=password], textarea { background-color: transparent; - color: $primary-fg-color; + color: $primary-content; } /* Required by Firefox */ textarea { - color: $primary-fg-color; + color: $primary-content; } input[type=text]:focus, input[type=password]:focus, textarea:focus { @@ -173,7 +173,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { .mx_textinput > input[type=search] { border: none; flex: 1; - color: $primary-fg-color; + color: $primary-content; } :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text], @@ -184,7 +184,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { background-color: transparent; color: $input-darker-fg-color; border-radius: 4px; - border: 1px solid rgba($primary-fg-color, .1); + border: 1px solid rgba($primary-content, .1); // these things should probably not be defined globally margin: 9px; } @@ -209,7 +209,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search], .mx_textinput { color: $input-darker-fg-color; - background-color: $primary-bg-color; + background-color: $background; border: none; } } @@ -271,7 +271,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { } .mx_Dialog { - background-color: $primary-bg-color; + background-color: $background; color: $light-fg-color; z-index: 4012; font-weight: 300; @@ -379,13 +379,8 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { .mx_Dialog_content { margin: 24px 0 68px; font-size: $font-14px; - color: $primary-fg-color; + color: $primary-content; word-wrap: break-word; - - a { - color: $accent-color; - cursor: pointer; - } } .mx_Dialog_buttons { @@ -493,8 +488,8 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { border-radius: 3px; border: 1px solid $input-border-color; padding: 9px; - color: $primary-fg-color; - background-color: $primary-bg-color; + color: $primary-content; + background-color: $background; } .mx_textButton { diff --git a/res/css/_components.scss b/res/css/_components.scss index 566b84a7c8f..ffaec43b680 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -132,6 +132,7 @@ @import "./views/elements/_EditableItemList.scss"; @import "./views/elements/_ErrorBoundary.scss"; @import "./views/elements/_EventListSummary.scss"; +@import "./views/elements/_EventTilePreview.scss"; @import "./views/elements/_FacePile.scss"; @import "./views/elements/_Field.scss"; @import "./views/elements/_ImageView.scss"; diff --git a/res/css/structures/_ContextualMenu.scss b/res/css/structures/_ContextualMenu.scss index d7f2cb76e8e..9f2b9e24b80 100644 --- a/res/css/structures/_ContextualMenu.scss +++ b/res/css/structures/_ContextualMenu.scss @@ -34,7 +34,7 @@ limitations under the License. border-radius: 8px; box-shadow: 4px 4px 12px 0 $menu-box-shadow-color; background-color: $menu-bg-color; - color: $primary-fg-color; + color: $primary-content; position: absolute; font-size: $font-14px; z-index: 5001; diff --git a/res/css/structures/_CreateRoom.scss b/res/css/structures/_CreateRoom.scss index e859beb20e6..3d23ccc4b25 100644 --- a/res/css/structures/_CreateRoom.scss +++ b/res/css/structures/_CreateRoom.scss @@ -18,7 +18,7 @@ limitations under the License. width: 960px; margin-left: auto; margin-right: auto; - color: $primary-fg-color; + color: $primary-content; } .mx_CreateRoom input, diff --git a/res/css/structures/_GroupFilterPanel.scss b/res/css/structures/_GroupFilterPanel.scss index 2961eebda27..cc0e760031b 100644 --- a/res/css/structures/_GroupFilterPanel.scss +++ b/res/css/structures/_GroupFilterPanel.scss @@ -92,13 +92,13 @@ $groupFilterPanelWidth: 56px; // only applies in this file, used for calculation } .mx_GroupFilterPanel .mx_TagTile.mx_TagTile_selected_prototype { - background-color: $primary-bg-color; + background-color: $background; border-radius: 6px; } .mx_TagTile_selected_prototype { .mx_TagTile_homeIcon::before { - background-color: $primary-fg-color; // dark-on-light + background-color: $primary-content; // dark-on-light } } diff --git a/res/css/structures/_GroupView.scss b/res/css/structures/_GroupView.scss index fb660f41941..5e224b1f389 100644 --- a/res/css/structures/_GroupView.scss +++ b/res/css/structures/_GroupView.scss @@ -132,7 +132,7 @@ limitations under the License. width: 100%; height: 31px; overflow: hidden; - color: $primary-fg-color; + color: $primary-content; font-weight: bold; font-size: $font-22px; padding-left: 19px; @@ -397,7 +397,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); - background-color: $secondary-fg-color; + background-color: $secondary-content; } .mx_AccessibleButton_kind_link { @@ -422,7 +422,7 @@ limitations under the License. mask-position: center; mask-size: 8px; mask-image: url('$(res)/img/image-view/close.svg'); - background-color: $secondary-fg-color; + background-color: $secondary-content; } } } diff --git a/res/css/structures/_LeftPanel.scss b/res/css/structures/_LeftPanel.scss index 32d50b9a806..5ddea244f36 100644 --- a/res/css/structures/_LeftPanel.scss +++ b/res/css/structures/_LeftPanel.scss @@ -146,7 +146,7 @@ $roomListCollapsedWidth: 68px; mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background: $secondary-fg-color; + background: $secondary-content; } } @@ -169,7 +169,7 @@ $roomListCollapsedWidth: 68px; mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background: $secondary-fg-color; + background: $secondary-content; } &.mx_LeftPanel_exploreButton_space::before { diff --git a/res/css/structures/_LeftPanelWidget.scss b/res/css/structures/_LeftPanelWidget.scss index 6e2d99bb376..93c2898395b 100644 --- a/res/css/structures/_LeftPanelWidget.scss +++ b/res/css/structures/_LeftPanelWidget.scss @@ -113,7 +113,7 @@ limitations under the License. &:hover .mx_LeftPanelWidget_resizerHandle { opacity: 0.8; - background-color: $primary-fg-color; + background-color: $primary-content; } .mx_LeftPanelWidget_maximizeButton { diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index 8199121420e..407a1c270cf 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -38,7 +38,7 @@ limitations under the License. width: 4px !important; border-radius: 4px !important; - background-color: $primary-fg-color; + background-color: $primary-content; opacity: 0.8; } } diff --git a/res/css/structures/_MatrixChat.scss b/res/css/structures/_MatrixChat.scss index 5232c14e74c..fdf5cb1a038 100644 --- a/res/css/structures/_MatrixChat.scss +++ b/res/css/structures/_MatrixChat.scss @@ -64,7 +64,7 @@ limitations under the License. /* not the left panel, and not the resize handle, so the roomview/groupview/... */ .mx_MatrixChat > :not(.mx_LeftPanel):not(.mx_SpacePanel):not(.mx_ResizeHandle):not(.mx_LeftPanel_wrapper) { - background-color: $primary-bg-color; + background-color: $background; flex: 1 1 0; min-width: 0; @@ -91,7 +91,7 @@ limitations under the License. content: ' '; - background-color: $primary-fg-color; + background-color: $primary-content; opacity: 0.8; } } diff --git a/res/css/structures/_NotificationPanel.scss b/res/css/structures/_NotificationPanel.scss index d271cd2bccd..68e1dd6a9a0 100644 --- a/res/css/structures/_NotificationPanel.scss +++ b/res/css/structures/_NotificationPanel.scss @@ -49,7 +49,7 @@ limitations under the License. bottom: 0; left: 0; right: 0; - background-color: $tertiary-fg-color; + background-color: $tertiary-content; height: 1px; opacity: 0.4; content: ''; @@ -70,7 +70,7 @@ limitations under the License. } .mx_NotificationPanel .mx_EventTile_roomName a { - color: $primary-fg-color; + color: $primary-content; } .mx_NotificationPanel .mx_EventTile_avatar { @@ -79,7 +79,7 @@ limitations under the License. .mx_NotificationPanel .mx_EventTile .mx_SenderProfile, .mx_NotificationPanel .mx_EventTile .mx_MessageTimestamp { - color: $primary-fg-color; + color: $primary-content; font-size: $font-12px; display: inline; } @@ -118,7 +118,7 @@ limitations under the License. } .mx_NotificationPanel .mx_EventTile:hover .mx_EventTile_line { - background-color: $primary-bg-color; + background-color: $background; } .mx_NotificationPanel .mx_EventTile_content { diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index 3222fe936c6..5316cba61d8 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -43,7 +43,7 @@ limitations under the License. .mx_RightPanel_headerButtonGroup { height: 100%; display: flex; - background-color: $primary-bg-color; + background-color: $background; padding: 0 9px; align-items: center; } diff --git a/res/css/structures/_RoomDirectory.scss b/res/css/structures/_RoomDirectory.scss index ec07500af56..fb0f7d10e1c 100644 --- a/res/css/structures/_RoomDirectory.scss +++ b/res/css/structures/_RoomDirectory.scss @@ -28,7 +28,7 @@ limitations under the License. .mx_RoomDirectory { margin-bottom: 12px; - color: $primary-fg-color; + color: $primary-content; word-break: break-word; display: flex; flex-direction: column; @@ -71,14 +71,14 @@ limitations under the License. font-weight: $font-semi-bold; font-size: $font-15px; line-height: $font-18px; - color: $primary-fg-color; + color: $primary-content; } > p { margin: 40px auto 60px; font-size: $font-14px; line-height: $font-20px; - color: $secondary-fg-color; + color: $secondary-content; max-width: 464px; // easier reading } @@ -97,7 +97,7 @@ limitations under the License. } .mx_RoomDirectory_table { - color: $primary-fg-color; + color: $primary-content; display: grid; font-size: $font-12px; grid-template-columns: max-content auto max-content max-content max-content; diff --git a/res/css/structures/_RoomSearch.scss b/res/css/structures/_RoomSearch.scss index 7fdafab5a6d..bbd60a5ff36 100644 --- a/res/css/structures/_RoomSearch.scss +++ b/res/css/structures/_RoomSearch.scss @@ -33,14 +33,14 @@ limitations under the License. height: 16px; mask: url('$(res)/img/element-icons/roomlist/search.svg'); mask-repeat: no-repeat; - background-color: $secondary-fg-color; + background-color: $secondary-content; margin-left: 7px; } .mx_RoomSearch_input { border: none !important; // !important to override default app-wide styles flex: 1 !important; // !important to override default app-wide styles - color: $primary-fg-color !important; // !important to override default app-wide styles + color: $primary-content !important; // !important to override default app-wide styles padding: 0; height: 100%; width: 100%; @@ -48,12 +48,12 @@ limitations under the License. line-height: $font-16px; &:not(.mx_RoomSearch_inputExpanded)::placeholder { - color: $tertiary-fg-color !important; // !important to override default app-wide styles + color: $tertiary-content !important; // !important to override default app-wide styles } } &.mx_RoomSearch_hasQuery { - border-color: $secondary-fg-color; + border-color: $secondary-content; } &.mx_RoomSearch_focused { @@ -62,7 +62,7 @@ limitations under the License. } &.mx_RoomSearch_focused, &.mx_RoomSearch_hasQuery { - background-color: $roomlist-filter-active-bg-color; + background-color: $background; .mx_RoomSearch_clearButton { width: 16px; @@ -71,7 +71,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background-color: $secondary-fg-color; + background-color: $secondary-content; margin-right: 8px; } } diff --git a/res/css/structures/_RoomStatusBar.scss b/res/css/structures/_RoomStatusBar.scss index de9e0491655..bdfbca1afa2 100644 --- a/res/css/structures/_RoomStatusBar.scss +++ b/res/css/structures/_RoomStatusBar.scss @@ -27,7 +27,7 @@ limitations under the License. .mx_RoomStatusBar_typingIndicatorAvatars .mx_BaseAvatar_image { margin-right: -12px; - border: 1px solid $primary-bg-color; + border: 1px solid $background; } .mx_RoomStatusBar_typingIndicatorAvatars .mx_BaseAvatar_initial { @@ -39,7 +39,7 @@ limitations under the License. display: inline-block; color: #acacac; background-color: #ddd; - border: 1px solid $primary-bg-color; + border: 1px solid $background; border-radius: 40px; width: 24px; height: 24px; @@ -171,14 +171,14 @@ limitations under the License. } .mx_RoomStatusBar_connectionLostBar_desc { - color: $primary-fg-color; + color: $primary-content; font-size: $font-13px; opacity: 0.5; padding-bottom: 20px; } .mx_RoomStatusBar_resend_link { - color: $primary-fg-color !important; + color: $primary-content !important; text-decoration: underline !important; cursor: pointer; } @@ -187,7 +187,7 @@ limitations under the License. height: 50px; line-height: $font-50px; - color: $primary-fg-color; + color: $primary-content; opacity: 0.5; overflow-y: hidden; display: block; diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 67529878c9b..86c2efeb4a8 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -52,7 +52,7 @@ limitations under the License. pointer-events: none; - background-color: $primary-bg-color; + background-color: $background; opacity: 0.95; position: absolute; @@ -99,7 +99,7 @@ limitations under the License. left: 0; right: 0; z-index: 3000; - background-color: $primary-bg-color; + background-color: $background; } .mx_RoomView_auxPanel_hiddenHighlights { @@ -172,7 +172,7 @@ limitations under the License. flex: 0 0 auto; max-height: 0px; - background-color: $primary-bg-color; + background-color: $background; z-index: 1000; overflow: hidden; @@ -257,7 +257,7 @@ hr.mx_RoomView_myReadMarker { } .mx_RoomView_callStatusBar .mx_UploadBar_uploadProgressInner { - background-color: $primary-bg-color; + background-color: $background; } .mx_RoomView_callStatusBar .mx_UploadBar_uploadFilename { diff --git a/res/css/structures/_SpaceHierarchy.scss b/res/css/structures/_SpaceHierarchy.scss index 74dfb5da6bc..a5d589f9c2c 100644 --- a/res/css/structures/_SpaceHierarchy.scss +++ b/res/css/structures/_SpaceHierarchy.scss @@ -37,7 +37,7 @@ limitations under the License. > div { font-weight: 400; - color: $secondary-fg-color; + color: $secondary-content; font-size: $font-15px; line-height: $font-24px; } @@ -59,7 +59,7 @@ limitations under the License. > div { font-size: $font-15px; line-height: $font-24px; - color: $secondary-fg-color; + color: $secondary-content; } } @@ -69,7 +69,7 @@ limitations under the License. align-items: center; font-size: $font-15px; line-height: $font-24px; - color: $primary-fg-color; + color: $primary-content; margin-bottom: 12px; > h4 { @@ -128,14 +128,14 @@ limitations under the License. font-weight: $font-semi-bold; font-size: $font-18px; line-height: $font-22px; - color: $primary-fg-color; + color: $primary-content; } > span { margin-left: 8px; font-size: $font-15px; line-height: $font-24px; - color: $secondary-fg-color; + color: $secondary-content; } } @@ -152,7 +152,7 @@ limitations under the License. height: 16px; width: 16px; border-radius: 4px; - background-color: $primary-bg-color; + background-color: $background; &::before { content: ''; @@ -163,7 +163,7 @@ limitations under the License. width: 16px; mask-repeat: no-repeat; mask-position: center; - background-color: $tertiary-fg-color; + background-color: $tertiary-content; mask-size: 16px; transform: rotate(270deg); mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); @@ -206,7 +206,7 @@ limitations under the License. .mx_InfoTooltip { display: inline; margin-left: 12px; - color: $tertiary-fg-color; + color: $tertiary-content; font-size: $font-12px; line-height: $font-15px; @@ -227,7 +227,7 @@ limitations under the License. .mx_SpaceHierarchy_roomTile_info { font-size: $font-14px; line-height: $font-18px; - color: $secondary-fg-color; + color: $secondary-content; grid-row: 2; grid-column: 1/3; display: -webkit-box; @@ -292,7 +292,7 @@ limitations under the License. font-weight: normal; font-size: $font-12px; line-height: $font-15px; - color: $secondary-fg-color; + color: $secondary-content; } } diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss index 7356ef31f80..702936523d9 100644 --- a/res/css/structures/_SpacePanel.scss +++ b/res/css/structures/_SpacePanel.scss @@ -20,7 +20,7 @@ $gutterSize: 16px; $activeBorderTransparentGap: 1px; $activeBackgroundColor: $roomtile-selected-bg-color; -$activeBorderColor: $secondary-fg-color; +$activeBorderColor: $secondary-content; .mx_SpacePanel { background-color: $groupFilterPanel-bg-color; @@ -238,7 +238,7 @@ $activeBorderColor: $secondary-fg-color; mask-size: contain; mask-repeat: no-repeat; mask-image: url('$(res)/img/element-icons/context-menu.svg'); - background: $primary-fg-color; + background: $primary-content; } } } diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index 6e6271d5e77..39eabe2e070 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -32,7 +32,7 @@ $SpaceRoomViewInnerWidth: 428px; } > span { - color: $secondary-fg-color; + color: $secondary-content; } &::before { @@ -45,7 +45,7 @@ $SpaceRoomViewInnerWidth: 428px; mask-position: center; mask-repeat: no-repeat; mask-size: 24px; - background-color: $tertiary-fg-color; + background-color: $tertiary-content; } &:hover { @@ -56,7 +56,7 @@ $SpaceRoomViewInnerWidth: 428px; } > span { - color: $primary-fg-color; + color: $primary-content; } } } @@ -75,13 +75,13 @@ $SpaceRoomViewInnerWidth: 428px; margin: 0; font-size: $font-24px; font-weight: $font-semi-bold; - color: $primary-fg-color; + color: $primary-content; width: max-content; } .mx_SpaceRoomView_description { font-size: $font-15px; - color: $secondary-fg-color; + color: $secondary-content; margin-top: 12px; margin-bottom: 24px; max-width: $SpaceRoomViewInnerWidth; @@ -157,7 +157,7 @@ $SpaceRoomViewInnerWidth: 428px; font-weight: $font-semi-bold; font-size: $font-14px; line-height: $font-24px; - color: $primary-fg-color; + color: $primary-content; margin-top: 24px; position: relative; padding-left: 24px; @@ -179,7 +179,7 @@ $SpaceRoomViewInnerWidth: 428px; mask-position: center; mask-size: contain; mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); - background-color: $secondary-fg-color; + background-color: $secondary-content; } } @@ -210,7 +210,7 @@ $SpaceRoomViewInnerWidth: 428px; .mx_SpaceRoomView_preview_inviter_mxid { line-height: $font-24px; - color: $secondary-fg-color; + color: $secondary-content; } } } @@ -227,7 +227,7 @@ $SpaceRoomViewInnerWidth: 428px; .mx_SpaceRoomView_preview_topic { font-size: $font-14px; line-height: $font-22px; - color: $secondary-fg-color; + color: $secondary-content; margin: 20px 0; max-height: 160px; overflow-y: auto; @@ -261,7 +261,7 @@ $SpaceRoomViewInnerWidth: 428px; .mx_SpaceRoomView_landing_name { margin: 24px 0 16px; font-size: $font-15px; - color: $secondary-fg-color; + color: $secondary-content; > span { display: inline-block; @@ -334,7 +334,7 @@ $SpaceRoomViewInnerWidth: 428px; top: 0; height: 24px; width: 24px; - background: $tertiary-fg-color; + background: $tertiary-content; mask-position: center; mask-size: contain; mask-repeat: no-repeat; @@ -358,7 +358,7 @@ $SpaceRoomViewInnerWidth: 428px; .mx_SpaceFeedbackPrompt { padding: 7px; // 8px - 1px border - border: 1px solid rgba($primary-fg-color, .1); + border: 1px solid rgba($primary-content, .1); border-radius: 8px; width: max-content; margin: 0 0 -40px auto; // collapse its own height to not push other components down @@ -387,7 +387,7 @@ $SpaceRoomViewInnerWidth: 428px; width: 432px; border-radius: 8px; background-color: $info-plinth-bg-color; - color: $secondary-fg-color; + color: $secondary-content; box-sizing: border-box; > h3 { @@ -414,7 +414,7 @@ $SpaceRoomViewInnerWidth: 428px; position: absolute; top: 14px; left: 14px; - background-color: $secondary-fg-color; + background-color: $secondary-content; } } @@ -437,7 +437,7 @@ $SpaceRoomViewInnerWidth: 428px; } .mx_SpaceRoomView_inviteTeammates_buttons { - color: $secondary-fg-color; + color: $secondary-content; margin-top: 28px; .mx_AccessibleButton { @@ -453,7 +453,7 @@ $SpaceRoomViewInnerWidth: 428px; width: 24px; top: 0; left: 0; - background-color: $secondary-fg-color; + background-color: $secondary-content; mask-repeat: no-repeat; mask-position: center; mask-size: contain; @@ -472,7 +472,7 @@ $SpaceRoomViewInnerWidth: 428px; } .mx_SpaceRoomView_info { - color: $secondary-fg-color; + color: $secondary-content; font-size: $font-15px; line-height: $font-24px; margin: 20px 0; @@ -491,7 +491,7 @@ $SpaceRoomViewInnerWidth: 428px; left: -2px; mask-position: center; mask-repeat: no-repeat; - background-color: $tertiary-fg-color; + background-color: $tertiary-content; } } diff --git a/res/css/structures/_TabbedView.scss b/res/css/structures/_TabbedView.scss index 833450a25b8..e185197f250 100644 --- a/res/css/structures/_TabbedView.scss +++ b/res/css/structures/_TabbedView.scss @@ -80,7 +80,7 @@ limitations under the License. .mx_TabbedView_tabLabel_text { font-size: 15px; - color: $tertiary-fg-color; + color: $tertiary-content; } } diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index 5cd938f1ce8..6024df5dc07 100644 --- a/res/css/structures/_ToastContainer.scss +++ b/res/css/structures/_ToastContainer.scss @@ -36,8 +36,8 @@ limitations under the License. .mx_Toast_toast { grid-row: 1 / 3; grid-column: 1; - color: $primary-fg-color; background-color: $system; + color: $primary-content; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); border-radius: 8px; overflow: hidden; @@ -63,7 +63,7 @@ limitations under the License. &.mx_Toast_icon_verification::after { mask-image: url("$(res)/img/e2e/normal.svg"); - background-color: $primary-fg-color; + background-color: $primary-content; } &.mx_Toast_icon_verification_warning { @@ -82,7 +82,7 @@ limitations under the License. &.mx_Toast_icon_secure_backup::after { mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); - background-color: $primary-fg-color; + background-color: $primary-content; } .mx_Toast_title, .mx_Toast_body { @@ -163,7 +163,7 @@ limitations under the License. } .mx_Toast_detail { - color: $secondary-fg-color; + color: $secondary-content; } .mx_Toast_deviceID { diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index 17e6ad75dfa..c10e7f60df2 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -35,7 +35,7 @@ limitations under the License. // we cheat opacity on the theme colour with an after selector here &::after { content: ''; - border-bottom: 1px solid $primary-fg-color; // XXX: Variable abuse + border-bottom: 1px solid $primary-content; opacity: 0.2; display: block; padding-top: 8px; @@ -58,7 +58,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background: $tertiary-fg-color; + background: $tertiary-content; mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } } @@ -176,7 +176,7 @@ limitations under the License. width: 85%; opacity: 0.2; border: none; - border-bottom: 1px solid $primary-fg-color; // XXX: Variable abuse + border-bottom: 1px solid $primary-content; } &.mx_IconizedContextMenu { @@ -292,7 +292,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background: $primary-fg-color; + background: $primary-content; } } diff --git a/res/css/structures/_ViewSource.scss b/res/css/structures/_ViewSource.scss index 248eab5d883..e3d6135ef30 100644 --- a/res/css/structures/_ViewSource.scss +++ b/res/css/structures/_ViewSource.scss @@ -24,7 +24,7 @@ limitations under the License. .mx_ViewSource_heading { font-size: $font-17px; font-weight: 400; - color: $primary-fg-color; + color: $primary-content; margin-top: 0.7em; } diff --git a/res/css/structures/auth/_Login.scss b/res/css/structures/auth/_Login.scss index 9c98ca3a1c1..c4aaaca1d05 100644 --- a/res/css/structures/auth/_Login.scss +++ b/res/css/structures/auth/_Login.scss @@ -96,3 +96,10 @@ div.mx_AccessibleButton_kind_link.mx_Login_forgot { cursor: not-allowed; } } +.mx_Login_spinner { + display: flex; + justify-content: center; + align-items: center; + align-content: center; + padding: 14px; +} diff --git a/res/css/views/audio_messages/_AudioPlayer.scss b/res/css/views/audio_messages/_AudioPlayer.scss index 77dcebbb9ab..3c2551e36a5 100644 --- a/res/css/views/audio_messages/_AudioPlayer.scss +++ b/res/css/views/audio_messages/_AudioPlayer.scss @@ -33,7 +33,7 @@ limitations under the License. } .mx_AudioPlayer_mediaName { - color: $primary-fg-color; + color: $primary-content; font-size: $font-15px; line-height: $font-15px; text-overflow: ellipsis; diff --git a/res/css/views/audio_messages/_SeekBar.scss b/res/css/views/audio_messages/_SeekBar.scss index d13fe4ac6a6..03449d009b6 100644 --- a/res/css/views/audio_messages/_SeekBar.scss +++ b/res/css/views/audio_messages/_SeekBar.scss @@ -27,7 +27,7 @@ limitations under the License. width: 100%; height: 1px; - background: $quaternary-fg-color; + background: $quaternary-content; outline: none; // remove blue selection border position: relative; // for before+after pseudo elements later on @@ -42,7 +42,7 @@ limitations under the License. width: 8px; height: 8px; border-radius: 8px; - background-color: $tertiary-fg-color; + background-color: $tertiary-content; cursor: pointer; } @@ -50,7 +50,7 @@ limitations under the License. width: 8px; height: 8px; border-radius: 8px; - background-color: $tertiary-fg-color; + background-color: $tertiary-content; cursor: pointer; // Firefox adds a border on the thumb @@ -63,7 +63,7 @@ limitations under the License. // in firefox, so it's just wasted CPU/GPU time. &::before { // ::before to ensure it ends up under the thumb content: ''; - background-color: $tertiary-fg-color; + background-color: $tertiary-content; // Absolute positioning to ensure it overlaps with the existing bar position: absolute; @@ -81,7 +81,7 @@ limitations under the License. // This is firefox's built-in support for the above, with 100% less hacks. &::-moz-range-progress { - background-color: $tertiary-fg-color; + background-color: $tertiary-content; height: 1px; } diff --git a/res/css/views/auth/_AuthButtons.scss b/res/css/views/auth/_AuthButtons.scss index 8deb0f80ac3..3a2ad2adf8a 100644 --- a/res/css/views/auth/_AuthButtons.scss +++ b/res/css/views/auth/_AuthButtons.scss @@ -39,7 +39,7 @@ limitations under the License. min-width: 80px; background-color: $accent-color; - color: $primary-bg-color; + color: $background; cursor: pointer; diff --git a/res/css/views/avatars/_DecoratedRoomAvatar.scss b/res/css/views/avatars/_DecoratedRoomAvatar.scss index 257b512579d..49220684621 100644 --- a/res/css/views/avatars/_DecoratedRoomAvatar.scss +++ b/res/css/views/avatars/_DecoratedRoomAvatar.scss @@ -47,7 +47,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background: $secondary-fg-color; + background: $secondary-content; mask-image: url('$(res)/img/globe.svg'); } diff --git a/res/css/views/beta/_BetaCard.scss b/res/css/views/beta/_BetaCard.scss index 2af4e79ecd7..ff6910852c0 100644 --- a/res/css/views/beta/_BetaCard.scss +++ b/res/css/views/beta/_BetaCard.scss @@ -29,7 +29,7 @@ limitations under the License. font-weight: $font-semi-bold; font-size: $font-18px; line-height: $font-22px; - color: $primary-fg-color; + color: $primary-content; margin: 4px 0 14px; .mx_BetaCard_betaPill { @@ -40,7 +40,7 @@ limitations under the License. .mx_BetaCard_caption { font-size: $font-15px; line-height: $font-20px; - color: $secondary-fg-color; + color: $secondary-content; margin-bottom: 20px; } @@ -54,7 +54,7 @@ limitations under the License. .mx_BetaCard_disclaimer { font-size: $font-12px; line-height: $font-15px; - color: $secondary-fg-color; + color: $secondary-content; margin-top: 20px; } } @@ -72,13 +72,13 @@ limitations under the License. margin: 16px 0 0; font-size: $font-15px; line-height: $font-24px; - color: $primary-fg-color; + color: $primary-content; .mx_SettingsFlag_microcopy { margin-top: 4px; font-size: $font-12px; line-height: $font-15px; - color: $secondary-fg-color; + color: $secondary-content; } } } diff --git a/res/css/views/context_menus/_IconizedContextMenu.scss b/res/css/views/context_menus/_IconizedContextMenu.scss index ff176eef7e4..ca40f18cd4c 100644 --- a/res/css/views/context_menus/_IconizedContextMenu.scss +++ b/res/css/views/context_menus/_IconizedContextMenu.scss @@ -36,7 +36,7 @@ limitations under the License. // // Therefore, we just hack in a line and border the thing ourselves &::before { - border-top: 1px solid $primary-fg-color; + border-top: 1px solid $primary-content; opacity: 0.1; content: ''; @@ -63,7 +63,7 @@ limitations under the License. padding-top: 12px; padding-bottom: 12px; text-decoration: none; - color: $primary-fg-color; + color: $primary-content; font-size: $font-15px; line-height: $font-24px; @@ -119,7 +119,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background: $primary-fg-color; + background: $primary-content; } } diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss index 338841cce43..5af748e28d1 100644 --- a/res/css/views/context_menus/_MessageContextMenu.scss +++ b/res/css/views/context_menus/_MessageContextMenu.scss @@ -30,7 +30,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background: $primary-fg-color; + background: $primary-content; } } diff --git a/res/css/views/context_menus/_StatusMessageContextMenu.scss b/res/css/views/context_menus/_StatusMessageContextMenu.scss index fceb7fba34c..1a97fb56c75 100644 --- a/res/css/views/context_menus/_StatusMessageContextMenu.scss +++ b/res/css/views/context_menus/_StatusMessageContextMenu.scss @@ -27,7 +27,7 @@ input.mx_StatusMessageContextMenu_message { border-radius: 4px; border: 1px solid $input-border-color; padding: 6.5px 11px; - background-color: $primary-bg-color; + background-color: $background; font-weight: normal; margin: 0 0 10px; } diff --git a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss index 42e17c8d987..444b29c9bf8 100644 --- a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss +++ b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss @@ -44,7 +44,7 @@ limitations under the License. > h3 { margin: 0; - color: $secondary-fg-color; + color: $secondary-content; font-size: $font-12px; font-weight: $font-semi-bold; line-height: $font-15px; @@ -66,7 +66,7 @@ limitations under the License. flex-grow: 1; font-size: $font-12px; line-height: $font-15px; - color: $secondary-fg-color; + color: $secondary-content; .mx_ProgressBar { height: 8px; @@ -79,7 +79,7 @@ limitations under the License. margin-top: 8px; font-size: $font-15px; line-height: $font-24px; - color: $primary-fg-color; + color: $primary-content; } > * { @@ -105,7 +105,7 @@ limitations under the License. margin-top: 4px; font-size: $font-12px; line-height: $font-15px; - color: $primary-fg-color; + color: $primary-content; } } @@ -126,7 +126,7 @@ limitations under the License. &::before { content: ''; position: absolute; - background-color: $primary-fg-color; + background-color: $primary-content; mask-repeat: no-repeat; mask-position: center; mask-size: contain; @@ -145,7 +145,7 @@ limitations under the License. .mx_AddExistingToSpaceDialog { width: 480px; - color: $primary-fg-color; + color: $primary-content; display: flex; flex-direction: column; flex-wrap: nowrap; @@ -188,7 +188,7 @@ limitations under the License. padding-left: 0; flex: unset; height: unset; - color: $secondary-fg-color; + color: $secondary-content; font-size: $font-15px; line-height: $font-24px; @@ -221,7 +221,7 @@ limitations under the License. } .mx_SubspaceSelector_onlySpace { - color: $secondary-fg-color; + color: $secondary-content; font-size: $font-15px; line-height: $font-24px; } diff --git a/res/css/views/dialogs/_CommunityPrototypeInviteDialog.scss b/res/css/views/dialogs/_CommunityPrototypeInviteDialog.scss index beae03f00f8..5d6c817b14d 100644 --- a/res/css/views/dialogs/_CommunityPrototypeInviteDialog.scss +++ b/res/css/views/dialogs/_CommunityPrototypeInviteDialog.scss @@ -65,7 +65,7 @@ limitations under the License. .mx_CommunityPrototypeInviteDialog_personName { font-weight: 600; font-size: $font-14px; - color: $primary-fg-color; + color: $primary-content; margin-left: 7px; } diff --git a/res/css/views/dialogs/_ConfirmUserActionDialog.scss b/res/css/views/dialogs/_ConfirmUserActionDialog.scss index 284c171f4ee..5ac0f07b146 100644 --- a/res/css/views/dialogs/_ConfirmUserActionDialog.scss +++ b/res/css/views/dialogs/_ConfirmUserActionDialog.scss @@ -35,8 +35,8 @@ limitations under the License. .mx_ConfirmUserActionDialog_reasonField { font-size: $font-14px; - color: $primary-fg-color; - background-color: $primary-bg-color; + color: $primary-content; + background-color: $background; border-radius: 3px; border: solid 1px $input-border-color; diff --git a/res/css/views/dialogs/_CreateGroupDialog.scss b/res/css/views/dialogs/_CreateGroupDialog.scss index f7bfc61a986..ef9c2b73d43 100644 --- a/res/css/views/dialogs/_CreateGroupDialog.scss +++ b/res/css/views/dialogs/_CreateGroupDialog.scss @@ -29,8 +29,8 @@ limitations under the License. border-radius: 3px; border: 1px solid $input-border-color; padding: 9px; - color: $primary-fg-color; - background-color: $primary-bg-color; + color: $primary-content; + background-color: $background; } .mx_CreateGroupDialog_input_hasPrefixAndSuffix { diff --git a/res/css/views/dialogs/_CreateRoomDialog.scss b/res/css/views/dialogs/_CreateRoomDialog.scss index e7cfbf60503..9cfa8ce25a8 100644 --- a/res/css/views/dialogs/_CreateRoomDialog.scss +++ b/res/css/views/dialogs/_CreateRoomDialog.scss @@ -55,8 +55,8 @@ limitations under the License. border-radius: 3px; border: 1px solid $input-border-color; padding: 9px; - color: $primary-fg-color; - background-color: $primary-bg-color; + color: $primary-content; + background-color: $background; width: 100%; } diff --git a/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss b/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss index afa722e05ea..6ff328f6abf 100644 --- a/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss +++ b/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss @@ -23,7 +23,7 @@ limitations under the License. .mx_CreateSpaceFromCommunityDialog { width: 480px; - color: $primary-fg-color; + color: $primary-content; display: flex; flex-direction: column; flex-wrap: nowrap; @@ -73,7 +73,7 @@ limitations under the License. flex-grow: 1; font-size: $font-12px; line-height: $font-15px; - color: $secondary-fg-color; + color: $secondary-content; .mx_ProgressBar { height: 8px; @@ -86,7 +86,7 @@ limitations under the License. margin-top: 8px; font-size: $font-15px; line-height: $font-24px; - color: $primary-fg-color; + color: $primary-content; } > * { @@ -112,7 +112,7 @@ limitations under the License. margin-top: 4px; font-size: $font-12px; line-height: $font-15px; - color: $primary-fg-color; + color: $primary-content; } } @@ -138,7 +138,7 @@ limitations under the License. &::before { content: ''; position: absolute; - background-color: $primary-fg-color; + background-color: $primary-content; mask-repeat: no-repeat; mask-position: center; mask-size: contain; diff --git a/res/css/views/dialogs/_CreateSubspaceDialog.scss b/res/css/views/dialogs/_CreateSubspaceDialog.scss index 1ec4731ae6d..1ed10df35c4 100644 --- a/res/css/views/dialogs/_CreateSubspaceDialog.scss +++ b/res/css/views/dialogs/_CreateSubspaceDialog.scss @@ -23,7 +23,7 @@ limitations under the License. .mx_CreateSubspaceDialog { width: 480px; - color: $primary-fg-color; + color: $primary-content; display: flex; flex-direction: column; flex-wrap: nowrap; @@ -57,7 +57,7 @@ limitations under the License. flex-grow: 1; font-size: $font-12px; line-height: $font-15px; - color: $secondary-fg-color; + color: $secondary-content; > * { vertical-align: middle; diff --git a/res/css/views/dialogs/_FeedbackDialog.scss b/res/css/views/dialogs/_FeedbackDialog.scss index fd225dd882f..74733f7220f 100644 --- a/res/css/views/dialogs/_FeedbackDialog.scss +++ b/res/css/views/dialogs/_FeedbackDialog.scss @@ -33,7 +33,7 @@ limitations under the License. padding-left: 52px; > p { - color: $tertiary-fg-color; + color: $tertiary-content; } .mx_AccessibleButton_kind_link { diff --git a/res/css/views/dialogs/_ForwardDialog.scss b/res/css/views/dialogs/_ForwardDialog.scss index e018f601720..da8ce3de5b0 100644 --- a/res/css/views/dialogs/_ForwardDialog.scss +++ b/res/css/views/dialogs/_ForwardDialog.scss @@ -16,7 +16,7 @@ limitations under the License. .mx_ForwardDialog { width: 520px; - color: $primary-fg-color; + color: $primary-content; display: flex; flex-direction: column; flex-wrap: nowrap; @@ -25,7 +25,7 @@ limitations under the License. > h3 { margin: 0 0 6px; - color: $secondary-fg-color; + color: $secondary-content; font-size: $font-12px; font-weight: $font-semi-bold; line-height: $font-15px; diff --git a/res/css/views/dialogs/_GenericFeatureFeedbackDialog.scss b/res/css/views/dialogs/_GenericFeatureFeedbackDialog.scss index f83eed9c536..ab7496249dc 100644 --- a/res/css/views/dialogs/_GenericFeatureFeedbackDialog.scss +++ b/res/css/views/dialogs/_GenericFeatureFeedbackDialog.scss @@ -16,7 +16,7 @@ limitations under the License. .mx_GenericFeatureFeedbackDialog { .mx_GenericFeatureFeedbackDialog_subheading { - color: $primary-fg-color; + color: $primary-content; font-size: $font-14px; line-height: $font-20px; margin-bottom: 24px; diff --git a/res/css/views/dialogs/_HostSignupDialog.scss b/res/css/views/dialogs/_HostSignupDialog.scss index ac4bc419513..d8a6652a397 100644 --- a/res/css/views/dialogs/_HostSignupDialog.scss +++ b/res/css/views/dialogs/_HostSignupDialog.scss @@ -70,11 +70,11 @@ limitations under the License. } .mx_HostSignupDialog_text_dark { - color: $primary-fg-color; + color: $primary-content; } .mx_HostSignupDialog_text_light { - color: $secondary-fg-color; + color: $secondary-content; } .mx_HostSignup_maximize_button { diff --git a/res/css/views/dialogs/_InviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss index 9fc4b7a15c7..3a2918f9ecb 100644 --- a/res/css/views/dialogs/_InviteDialog.scss +++ b/res/css/views/dialogs/_InviteDialog.scss @@ -56,7 +56,7 @@ limitations under the License. box-sizing: border-box; min-width: 40%; flex: 1 !important; - color: $primary-fg-color !important; + color: $primary-content !important; } } @@ -94,7 +94,7 @@ limitations under the License. } > span { - color: $primary-fg-color; + color: $primary-content; } .mx_InviteDialog_subname { @@ -110,7 +110,7 @@ limitations under the License. font-size: $font-14px; > span { - color: $primary-fg-color; + color: $primary-content; font-weight: 600; } @@ -220,7 +220,7 @@ limitations under the License. .mx_InviteDialog_roomTile_name { font-weight: 600; font-size: $font-14px; - color: $primary-fg-color; + color: $primary-content; margin-left: 7px; } @@ -352,7 +352,7 @@ limitations under the License. border-right: 0; border-radius: 0; margin-top: 0; - border-color: $quaternary-fg-color; + border-color: $quaternary-content; input { font-size: 18px; @@ -418,7 +418,7 @@ limitations under the License. > h4 { font-size: $font-15px; line-height: $font-24px; - color: $secondary-fg-color; + color: $secondary-content; font-weight: normal; } @@ -432,14 +432,14 @@ limitations under the License. font-size: $font-15px; line-height: $font-24px; font-weight: $font-semi-bold; - color: $primary-fg-color; + color: $primary-content; } .mx_InviteDialog_multiInviterError_entry_userId { margin-left: 6px; font-size: $font-12px; line-height: $font-15px; - color: $tertiary-fg-color; + color: $tertiary-content; } } diff --git a/res/css/views/dialogs/_JoinRuleDropdown.scss b/res/css/views/dialogs/_JoinRuleDropdown.scss index c48a79af3c7..91691cf53b6 100644 --- a/res/css/views/dialogs/_JoinRuleDropdown.scss +++ b/res/css/views/dialogs/_JoinRuleDropdown.scss @@ -19,7 +19,7 @@ limitations under the License. font-weight: normal; font-family: $font-family; font-size: $font-14px; - color: $primary-fg-color; + color: $primary-content; .mx_Dropdown_input { border: 1px solid $input-border-color; @@ -44,7 +44,7 @@ limitations under the License. top: 8px; mask-repeat: no-repeat; mask-position: center; - background-color: $secondary-fg-color; + background-color: $secondary-content; } } } diff --git a/res/css/views/dialogs/_LeaveSpaceDialog.scss b/res/css/views/dialogs/_LeaveSpaceDialog.scss index c982f50e52f..0d85a87faff 100644 --- a/res/css/views/dialogs/_LeaveSpaceDialog.scss +++ b/res/css/views/dialogs/_LeaveSpaceDialog.scss @@ -63,7 +63,7 @@ limitations under the License. font-size: $font-12px; line-height: $font-15px; - color: $secondary-fg-color; + color: $secondary-content; &::before { content: ''; @@ -72,7 +72,7 @@ limitations under the License. top: calc(50% - 8px); // vertical centering height: 16px; width: 16px; - background-color: $secondary-fg-color; + background-color: $secondary-content; mask-repeat: no-repeat; mask-size: contain; mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); @@ -81,7 +81,7 @@ limitations under the License. } > p { - color: $primary-fg-color; + color: $primary-content; } } diff --git a/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.scss b/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.scss index 91df76675ad..9a05e7f20a7 100644 --- a/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.scss +++ b/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.scss @@ -23,7 +23,7 @@ limitations under the License. .mx_ManageRestrictedJoinRuleDialog { width: 480px; - color: $primary-fg-color; + color: $primary-content; display: flex; flex-direction: column; flex-wrap: nowrap; @@ -52,7 +52,7 @@ limitations under the License. > h3 { margin: 0; - color: $secondary-fg-color; + color: $secondary-content; font-size: $font-12px; font-weight: $font-semi-bold; line-height: $font-15px; @@ -85,7 +85,7 @@ limitations under the License. margin-top: 8px; font-size: $font-12px; line-height: $font-15px; - color: $tertiary-fg-color; + color: $tertiary-content; } .mx_Checkbox { @@ -113,7 +113,7 @@ limitations under the License. font-size: $font-12px; line-height: $font-15px; - color: $secondary-fg-color; + color: $secondary-content; &::before { content: ''; @@ -122,7 +122,7 @@ limitations under the License. top: calc(50% - 8px); // vertical centering height: 16px; width: 16px; - background-color: $secondary-fg-color; + background-color: $secondary-content; mask-repeat: no-repeat; mask-size: contain; mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); diff --git a/res/css/views/dialogs/_MessageEditHistoryDialog.scss b/res/css/views/dialogs/_MessageEditHistoryDialog.scss index e9d777effd3..4574344a285 100644 --- a/res/css/views/dialogs/_MessageEditHistoryDialog.scss +++ b/res/css/views/dialogs/_MessageEditHistoryDialog.scss @@ -37,7 +37,7 @@ limitations under the License. list-style-type: none; font-size: $font-14px; padding: 0; - color: $primary-fg-color; + color: $primary-content; span.mx_EditHistoryMessage_deletion, span.mx_EditHistoryMessage_insertion { padding: 0px 2px; diff --git a/res/css/views/dialogs/_RegistrationEmailPromptDialog.scss b/res/css/views/dialogs/_RegistrationEmailPromptDialog.scss index 31fc6d7a047..02c89e2e420 100644 --- a/res/css/views/dialogs/_RegistrationEmailPromptDialog.scss +++ b/res/css/views/dialogs/_RegistrationEmailPromptDialog.scss @@ -19,7 +19,7 @@ limitations under the License. .mx_Dialog_content { margin-bottom: 24px; - color: $tertiary-fg-color; + color: $tertiary-content; } .mx_Dialog_primary { diff --git a/res/css/views/dialogs/_RoomSettingsDialogBridges.scss b/res/css/views/dialogs/_RoomSettingsDialogBridges.scss index c97a3b69b79..f18b4917cf1 100644 --- a/res/css/views/dialogs/_RoomSettingsDialogBridges.scss +++ b/res/css/views/dialogs/_RoomSettingsDialogBridges.scss @@ -72,7 +72,7 @@ limitations under the License. margin-top: 0px; margin-bottom: 0px; font-size: 16pt; - color: $primary-fg-color; + color: $primary-content; } > * { @@ -81,7 +81,7 @@ limitations under the License. } .workspace-channel-details { - color: $primary-fg-color; + color: $primary-content; font-weight: 600; .channel { diff --git a/res/css/views/dialogs/_ServerOfflineDialog.scss b/res/css/views/dialogs/_ServerOfflineDialog.scss index ae4b70beb3b..7a1b0bbcab1 100644 --- a/res/css/views/dialogs/_ServerOfflineDialog.scss +++ b/res/css/views/dialogs/_ServerOfflineDialog.scss @@ -17,10 +17,10 @@ limitations under the License. .mx_ServerOfflineDialog { .mx_ServerOfflineDialog_content { padding-right: 85px; - color: $primary-fg-color; + color: $primary-content; hr { - border-color: $primary-fg-color; + border-color: $primary-content; opacity: 0.1; border-bottom: none; } diff --git a/res/css/views/dialogs/_ServerPickerDialog.scss b/res/css/views/dialogs/_ServerPickerDialog.scss index b01b49d7af3..9a05751f918 100644 --- a/res/css/views/dialogs/_ServerPickerDialog.scss +++ b/res/css/views/dialogs/_ServerPickerDialog.scss @@ -22,7 +22,7 @@ limitations under the License. margin-bottom: 0; > p { - color: $secondary-fg-color; + color: $secondary-content; font-size: $font-14px; margin: 16px 0; @@ -38,7 +38,7 @@ limitations under the License. > h4 { font-size: $font-15px; font-weight: $font-semi-bold; - color: $secondary-fg-color; + color: $secondary-content; margin-left: 8px; } diff --git a/res/css/views/dialogs/_SetEmailDialog.scss b/res/css/views/dialogs/_SetEmailDialog.scss index 37bee7a9ff3..a39d51dfce9 100644 --- a/res/css/views/dialogs/_SetEmailDialog.scss +++ b/res/css/views/dialogs/_SetEmailDialog.scss @@ -19,7 +19,7 @@ limitations under the License. border: 1px solid $input-border-color; padding: 9px; color: $input-fg-color; - background-color: $primary-bg-color; + background-color: $background; font-size: $font-15px; width: 100%; max-width: 280px; diff --git a/res/css/views/dialogs/_SpaceSettingsDialog.scss b/res/css/views/dialogs/_SpaceSettingsDialog.scss index fa074fdbe8f..a1fa9d52a85 100644 --- a/res/css/views/dialogs/_SpaceSettingsDialog.scss +++ b/res/css/views/dialogs/_SpaceSettingsDialog.scss @@ -15,7 +15,7 @@ limitations under the License. */ .mx_SpaceSettingsDialog { - color: $primary-fg-color; + color: $primary-content; .mx_SpaceSettings_errorText { font-weight: $font-semi-bold; @@ -50,13 +50,13 @@ limitations under the License. .mx_RadioButton_content { font-weight: $font-semi-bold; line-height: $font-18px; - color: $primary-fg-color; + color: $primary-content; } & + span { font-size: $font-15px; line-height: $font-18px; - color: $secondary-fg-color; + color: $secondary-content; margin-left: 26px; } } diff --git a/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss b/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss index ec3bea0ef71..98edbf8ad88 100644 --- a/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss +++ b/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss @@ -44,7 +44,7 @@ limitations under the License. margin-right: 8px; position: relative; top: 5px; - background-color: $primary-fg-color; + background-color: $primary-content; } .mx_AccessSecretStorageDialog_resetBadge::before { diff --git a/res/css/views/dialogs/security/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/security/_CreateSecretStorageDialog.scss index d30803b1f06..b14206ff6da 100644 --- a/res/css/views/dialogs/security/_CreateSecretStorageDialog.scss +++ b/res/css/views/dialogs/security/_CreateSecretStorageDialog.scss @@ -56,7 +56,7 @@ limitations under the License. margin-right: 8px; position: relative; top: 5px; - background-color: $primary-fg-color; + background-color: $primary-content; } .mx_CreateSecretStorageDialog_secureBackupTitle::before { @@ -101,7 +101,7 @@ limitations under the License. margin-right: 8px; position: relative; top: 5px; - background-color: $primary-fg-color; + background-color: $primary-content; } .mx_CreateSecretStorageDialog_optionIcon_securePhrase { diff --git a/res/css/views/dialogs/security/_KeyBackupFailedDialog.scss b/res/css/views/dialogs/security/_KeyBackupFailedDialog.scss index 05ce1584135..4a480126721 100644 --- a/res/css/views/dialogs/security/_KeyBackupFailedDialog.scss +++ b/res/css/views/dialogs/security/_KeyBackupFailedDialog.scss @@ -26,7 +26,7 @@ limitations under the License. &::before { mask: url("$(res)/img/e2e/lock-warning-filled.svg"); mask-repeat: no-repeat; - background-color: $primary-fg-color; + background-color: $primary-content; content: ""; position: absolute; top: -6px; diff --git a/res/css/views/directory/_NetworkDropdown.scss b/res/css/views/directory/_NetworkDropdown.scss index ae0927386af..93cecd86761 100644 --- a/res/css/views/directory/_NetworkDropdown.scss +++ b/res/css/views/directory/_NetworkDropdown.scss @@ -34,7 +34,7 @@ limitations under the License. box-sizing: border-box; border-radius: 4px; border: 1px solid $dialog-close-fg-color; - background-color: $primary-bg-color; + background-color: $background; max-height: calc(100vh - 20px); // allow 10px padding on both top and bottom overflow-y: auto; } @@ -153,7 +153,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-image: url('$(res)/img/feather-customised/chevron-down-thin.svg'); - background-color: $primary-fg-color; + background-color: $primary-content; } .mx_NetworkDropdown_handle_server { diff --git a/res/css/views/elements/_AddressSelector.scss b/res/css/views/elements/_AddressSelector.scss index 087504390cd..a7d463353b4 100644 --- a/res/css/views/elements/_AddressSelector.scss +++ b/res/css/views/elements/_AddressSelector.scss @@ -16,7 +16,7 @@ limitations under the License. .mx_AddressSelector { position: absolute; - background-color: $primary-bg-color; + background-color: $background; width: 485px; max-height: 116px; overflow-y: auto; @@ -31,8 +31,8 @@ limitations under the License. } .mx_AddressSelector_addressListElement .mx_AddressTile { - background-color: $primary-bg-color; - border: solid 1px $primary-bg-color; + background-color: $background; + border: solid 1px $background; } .mx_AddressSelector_addressListElement.mx_AddressSelector_selected { diff --git a/res/css/views/elements/_AddressTile.scss b/res/css/views/elements/_AddressTile.scss index c42f52f8f4a..90c40842f75 100644 --- a/res/css/views/elements/_AddressTile.scss +++ b/res/css/views/elements/_AddressTile.scss @@ -20,7 +20,7 @@ limitations under the License. background-color: rgba(74, 73, 74, 0.1); border: solid 1px $input-border-color; line-height: $font-26px; - color: $primary-fg-color; + color: $primary-content; font-size: $font-14px; font-weight: normal; margin-right: 4px; diff --git a/res/css/views/elements/_Dropdown.scss b/res/css/views/elements/_Dropdown.scss index 3b67e0191e1..1acac70e421 100644 --- a/res/css/views/elements/_Dropdown.scss +++ b/res/css/views/elements/_Dropdown.scss @@ -16,7 +16,7 @@ limitations under the License. .mx_Dropdown { position: relative; - color: $primary-fg-color; + color: $primary-content; } .mx_Dropdown_disabled { @@ -52,7 +52,7 @@ limitations under the License. padding-right: 9px; mask: url('$(res)/img/feather-customised/dropdown-arrow.svg'); mask-repeat: no-repeat; - background: $primary-fg-color; + background: $primary-content; } .mx_Dropdown_option { @@ -111,7 +111,7 @@ input.mx_Dropdown_option:focus { padding: 0px; border-radius: 4px; border: 1px solid $input-focused-border-color; - background-color: $primary-bg-color; + background-color: $background; max-height: 200px; overflow-y: auto; } diff --git a/res/css/views/elements/_EventTilePreview.scss b/res/css/views/elements/_EventTilePreview.scss new file mode 100644 index 00000000000..6bb726168f1 --- /dev/null +++ b/res/css/views/elements/_EventTilePreview.scss @@ -0,0 +1,22 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_EventTilePreview_loader { + &.mx_IRCLayout, + &.mx_GroupLayout { + padding: 9px 0; + } +} diff --git a/res/css/views/elements/_FacePile.scss b/res/css/views/elements/_FacePile.scss index c691baffb53..875e0e34d57 100644 --- a/res/css/views/elements/_FacePile.scss +++ b/res/css/views/elements/_FacePile.scss @@ -25,7 +25,7 @@ limitations under the License. } .mx_BaseAvatar_image { - border: 1px solid $primary-bg-color; + border: 1px solid $background; } .mx_BaseAvatar_initial { @@ -47,7 +47,7 @@ limitations under the License. left: 0; height: inherit; width: inherit; - background: $tertiary-fg-color; + background: $tertiary-content; mask-position: center; mask-size: 20px; mask-repeat: no-repeat; @@ -60,6 +60,6 @@ limitations under the License. margin-left: 12px; font-size: $font-14px; line-height: $font-24px; - color: $tertiary-fg-color; + color: $tertiary-content; } } diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index 50cd14c4dad..d74c985d4c7 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -46,8 +46,8 @@ limitations under the License. // corners on the field above. border-radius: 4px; padding: 8px 9px; - color: $primary-fg-color; - background-color: $primary-bg-color; + color: $primary-content; + background-color: $background; flex: 1; min-width: 0; } @@ -67,7 +67,7 @@ limitations under the License. height: 6px; mask: url('$(res)/img/feather-customised/dropdown-arrow.svg'); mask-repeat: no-repeat; - background-color: $primary-fg-color; + background-color: $primary-content; z-index: 1; pointer-events: none; } @@ -100,7 +100,7 @@ limitations under the License. color 0.25s ease-out 0.1s, top 0.25s ease-out 0.1s, background-color 0.25s ease-out 0.1s; - color: $primary-fg-color; + color: $primary-content; background-color: transparent; font-size: $font-14px; position: absolute; diff --git a/res/css/views/elements/_InviteReason.scss b/res/css/views/elements/_InviteReason.scss index 2c2e5687e64..8024ed59a33 100644 --- a/res/css/views/elements/_InviteReason.scss +++ b/res/css/views/elements/_InviteReason.scss @@ -32,12 +32,12 @@ limitations under the License. justify-content: center; align-items: center; cursor: pointer; - color: $secondary-fg-color; + color: $secondary-content; &::before { content: ""; margin-right: 8px; - background-color: $secondary-fg-color; + background-color: $secondary-content; mask-image: url('$(res)/img/feather-customised/eye.svg'); display: inline-block; width: 18px; diff --git a/res/css/views/elements/_MiniAvatarUploader.scss b/res/css/views/elements/_MiniAvatarUploader.scss index df4676ab567..46ffd9a01cd 100644 --- a/res/css/views/elements/_MiniAvatarUploader.scss +++ b/res/css/views/elements/_MiniAvatarUploader.scss @@ -37,7 +37,7 @@ limitations under the License. right: -6px; bottom: -6px; - background-color: $primary-bg-color; + background-color: $background; border-radius: 50%; z-index: 1; @@ -45,7 +45,7 @@ limitations under the License. height: 100%; width: 100%; - background-color: $secondary-fg-color; + background-color: $secondary-content; mask-position: center; mask-repeat: no-repeat; mask-image: url('$(res)/img/element-icons/camera.svg'); diff --git a/res/css/views/elements/_RichText.scss b/res/css/views/elements/_RichText.scss index d60282695c8..b9d845ea7af 100644 --- a/res/css/views/elements/_RichText.scss +++ b/res/css/views/elements/_RichText.scss @@ -43,7 +43,7 @@ a.mx_Pill { /* More specific to override `.markdown-body a` color */ .mx_EventTile_content .markdown-body a.mx_UserPill, .mx_UserPill { - color: $primary-fg-color; + color: $primary-content; background-color: $other-user-pill-bg-color; } diff --git a/res/css/views/elements/_SSOButtons.scss b/res/css/views/elements/_SSOButtons.scss index e02816780ff..a98e7b40246 100644 --- a/res/css/views/elements/_SSOButtons.scss +++ b/res/css/views/elements/_SSOButtons.scss @@ -35,7 +35,7 @@ limitations under the License. font-size: $font-14px; font-weight: $font-semi-bold; border: 1px solid $input-border-color; - color: $primary-fg-color; + color: $primary-content; > img { object-fit: contain; diff --git a/res/css/views/elements/_ServerPicker.scss b/res/css/views/elements/_ServerPicker.scss index 188eb5d6550..d828d7cb88c 100644 --- a/res/css/views/elements/_ServerPicker.scss +++ b/res/css/views/elements/_ServerPicker.scss @@ -74,7 +74,7 @@ limitations under the License. .mx_ServerPicker_desc { margin-top: -12px; - color: $tertiary-fg-color; + color: $tertiary-content; grid-column: 1 / 2; grid-row: 3; margin-bottom: 16px; diff --git a/res/css/views/elements/_Spinner.scss b/res/css/views/elements/_Spinner.scss index 93d5e2d96c0..2df46687af4 100644 --- a/res/css/views/elements/_Spinner.scss +++ b/res/css/views/elements/_Spinner.scss @@ -37,7 +37,7 @@ limitations under the License. } .mx_Spinner_icon { - background-color: $primary-fg-color; + background-color: $primary-content; mask: url('$(res)/img/spinner.svg'); mask-size: contain; animation: 1.1s steps(12, end) infinite spin; diff --git a/res/css/views/elements/_TagComposer.scss b/res/css/views/elements/_TagComposer.scss index 2ffd6017655..f5bdb8d2d58 100644 --- a/res/css/views/elements/_TagComposer.scss +++ b/res/css/views/elements/_TagComposer.scss @@ -25,7 +25,7 @@ limitations under the License. .mx_AccessibleButton { min-width: 70px; - padding: 0; // override from button styles + padding: 0 8px; // override from button styles margin-left: 16px; // distance from } @@ -50,7 +50,7 @@ limitations under the License. &::before { content: ''; border-radius: 20px; - background-color: $tertiary-fg-color; + background-color: $tertiary-content; opacity: 0.15; position: absolute; top: 0; diff --git a/res/css/views/elements/_Tooltip.scss b/res/css/views/elements/_Tooltip.scss index d90c818f94e..6c5a7da55af 100644 --- a/res/css/views/elements/_Tooltip.scss +++ b/res/css/views/elements/_Tooltip.scss @@ -84,7 +84,7 @@ limitations under the License. // These tooltips use an older style with a chevron .mx_Field_tooltip { background-color: $menu-bg-color; - color: $primary-fg-color; + color: $primary-content; border: 1px solid $menu-border-color; text-align: unset; diff --git a/res/css/views/emojipicker/_EmojiPicker.scss b/res/css/views/emojipicker/_EmojiPicker.scss index 400e40e233f..91c68158c94 100644 --- a/res/css/views/emojipicker/_EmojiPicker.scss +++ b/res/css/views/emojipicker/_EmojiPicker.scss @@ -57,7 +57,7 @@ limitations under the License. } .mx_EmojiPicker_anchor::before { - background-color: $primary-fg-color; + background-color: $primary-content; content: ''; display: inline-block; mask-size: 100%; @@ -89,7 +89,7 @@ limitations under the License. margin: 8px; border-radius: 4px; border: 1px solid $input-border-color; - background-color: $primary-bg-color; + background-color: $background; display: flex; input { @@ -126,7 +126,7 @@ limitations under the License. .mx_EmojiPicker_search_icon::after { mask: url('$(res)/img/emojipicker/search.svg') no-repeat; mask-size: 100%; - background-color: $primary-fg-color; + background-color: $primary-content; content: ''; display: inline-block; width: 100%; diff --git a/res/css/views/groups/_GroupRoomList.scss b/res/css/views/groups/_GroupRoomList.scss index fefd17849cc..2f6559f7c4a 100644 --- a/res/css/views/groups/_GroupRoomList.scss +++ b/res/css/views/groups/_GroupRoomList.scss @@ -16,7 +16,7 @@ limitations under the License. .mx_GroupRoomTile { position: relative; - color: $primary-fg-color; + color: $primary-content; cursor: pointer; display: flex; align-items: center; diff --git a/res/css/views/messages/_CallEvent.scss b/res/css/views/messages/_CallEvent.scss index afc2c93f79e..7934f8f3c21 100644 --- a/res/css/views/messages/_CallEvent.scss +++ b/res/css/views/messages/_CallEvent.scss @@ -41,7 +41,7 @@ limitations under the License. height: 16px; width: 16px; - background-color: $secondary-fg-color; + background-color: $secondary-content; mask-repeat: no-repeat; mask-size: contain; mask-position: center; @@ -116,7 +116,7 @@ limitations under the License. .mx_CallEvent_type { font-weight: 400; - color: $secondary-fg-color; + color: $secondary-content; font-size: 1.2rem; line-height: $font-13px; display: flex; @@ -132,7 +132,7 @@ limitations under the License. position: absolute; height: 13px; width: 13px; - background-color: $secondary-fg-color; + background-color: $secondary-content; mask-repeat: no-repeat; mask-size: contain; } @@ -145,7 +145,7 @@ limitations under the License. display: flex; flex-direction: row; align-items: center; - color: $secondary-fg-color; + color: $secondary-content; margin-right: 16px; gap: 8px; min-width: max-content; diff --git a/res/css/views/messages/_MImageBody.scss b/res/css/views/messages/_MImageBody.scss index b3a62896b20..920c3011f58 100644 --- a/res/css/views/messages/_MImageBody.scss +++ b/res/css/views/messages/_MImageBody.scss @@ -38,7 +38,7 @@ $timelineImageBorderRadius: 4px; } .mx_no-image-placeholder { - background-color: $primary-bg-color; + background-color: $primary-content; } } @@ -100,5 +100,5 @@ $timelineImageBorderRadius: 4px; } .mx_EventTile:hover .mx_HiddenImagePlaceholder { - background-color: $primary-bg-color; + background-color: $background; } diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index 69f3c672b76..6805036e3d6 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -23,7 +23,7 @@ limitations under the License. height: 32px; line-height: $font-24px; border-radius: 8px; - background: $primary-bg-color; + background: $background; border: 1px solid $input-border-color; top: -32px; right: 8px; @@ -77,11 +77,11 @@ limitations under the License. mask-size: 18px; mask-repeat: no-repeat; mask-position: center; - background-color: $secondary-fg-color; + background-color: $secondary-content; } .mx_MessageActionBar_maskButton:hover::after { - background-color: $primary-fg-color; + background-color: $primary-content; } .mx_MessageActionBar_reactButton::after { @@ -92,6 +92,10 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/message-bar/reply.svg'); } +.mx_MessageActionBar_threadButton::after { + mask-image: url('$(res)/img/element-icons/message/thread.svg'); +} + .mx_MessageActionBar_editButton::after { mask-image: url('$(res)/img/element-icons/room/message-bar/edit.svg'); } diff --git a/res/css/views/messages/_ReactionsRow.scss b/res/css/views/messages/_ReactionsRow.scss index b2bca6dfb38..1b0b8479328 100644 --- a/res/css/views/messages/_ReactionsRow.scss +++ b/res/css/views/messages/_ReactionsRow.scss @@ -16,7 +16,7 @@ limitations under the License. .mx_ReactionsRow { margin: 6px 0; - color: $primary-fg-color; + color: $primary-content; .mx_ReactionsRow_addReactionButton { position: relative; @@ -36,7 +36,7 @@ limitations under the License. mask-size: 16px; mask-repeat: no-repeat; mask-position: center; - background-color: $tertiary-fg-color; + background-color: $tertiary-content; mask-image: url('$(res)/img/element-icons/room/message-bar/emoji.svg'); } @@ -46,7 +46,7 @@ limitations under the License. &:hover, &.mx_ReactionsRow_addReactionButton_active { &::before { - background-color: $primary-fg-color; + background-color: $primary-content; } } } @@ -64,10 +64,10 @@ limitations under the License. vertical-align: middle; &:link, &:visited { - color: $tertiary-fg-color; + color: $tertiary-content; } &:hover { - color: $primary-fg-color; + color: $primary-content; } } diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss index 9a5a59bda8f..8c1a55fe053 100644 --- a/res/css/views/right_panel/_BaseCard.scss +++ b/res/css/views/right_panel/_BaseCard.scss @@ -93,7 +93,7 @@ limitations under the License. } > h1 { - color: $tertiary-fg-color; + color: $tertiary-content; font-size: $font-12px; font-weight: 500; } @@ -145,7 +145,7 @@ limitations under the License. justify-content: space-around; .mx_AccessibleButton_kind_secondary { - color: $secondary-fg-color; + color: $secondary-content; background-color: rgba(141, 151, 165, 0.2); font-weight: $font-semi-bold; font-size: $font-14px; diff --git a/res/css/views/right_panel/_PinnedMessagesCard.scss b/res/css/views/right_panel/_PinnedMessagesCard.scss index 785aee09cae..f3861a3decd 100644 --- a/res/css/views/right_panel/_PinnedMessagesCard.scss +++ b/res/css/views/right_panel/_PinnedMessagesCard.scss @@ -48,7 +48,7 @@ limitations under the License. height: 32px; line-height: $font-24px; border-radius: 8px; - background: $primary-bg-color; + background: $background; border: 1px solid $input-border-color; padding: 1px; width: max-content; @@ -66,7 +66,7 @@ limitations under the License. z-index: 1; &::after { - background-color: $primary-fg-color; + background-color: $primary-content; } } } @@ -75,7 +75,7 @@ limitations under the License. font-weight: $font-semi-bold; font-size: $font-15px; line-height: $font-24px; - color: $primary-fg-color; + color: $primary-content; margin-top: 24px; margin-bottom: 20px; } @@ -83,7 +83,7 @@ limitations under the License. > span { font-size: $font-12px; line-height: $font-15px; - color: $secondary-fg-color; + color: $secondary-content; } } } diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index dc7804d072f..7ac47871110 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -27,7 +27,7 @@ limitations under the License. .mx_RoomSummaryCard_alias { font-size: $font-13px; - color: $secondary-fg-color; + color: $secondary-content; } h2, .mx_RoomSummaryCard_alias { @@ -115,7 +115,7 @@ limitations under the License. // as we will be applying it in its children padding: 0; height: auto; - color: $tertiary-fg-color; + color: $tertiary-content; .mx_RoomSummaryCard_icon_app { padding: 10px 48px 10px 12px; // based on typical mx_RoomSummaryCard_Button padding @@ -128,7 +128,7 @@ limitations under the License. } span { - color: $primary-fg-color; + color: $primary-content; } } @@ -232,6 +232,10 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/files.svg'); } +.mx_RoomSummaryCard_icon_threads::before { + mask-image: url('$(res)/img/element-icons/message/thread.svg'); +} + .mx_RoomSummaryCard_icon_share::before { mask-image: url('$(res)/img/element-icons/room/share.svg'); } diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss index 6632ccddf9c..edc82cfdbf1 100644 --- a/res/css/views/right_panel/_UserInfo.scss +++ b/res/css/views/right_panel/_UserInfo.scss @@ -55,7 +55,7 @@ limitations under the License. } .mx_UserInfo_separator { - border-bottom: 1px solid rgba($primary-fg-color, .1); + border-bottom: 1px solid rgba($primary-content, .1); } .mx_UserInfo_memberDetailsContainer { diff --git a/res/css/views/right_panel/_WidgetCard.scss b/res/css/views/right_panel/_WidgetCard.scss index a90e744a5aa..824f1fcb2f1 100644 --- a/res/css/views/right_panel/_WidgetCard.scss +++ b/res/css/views/right_panel/_WidgetCard.scss @@ -51,7 +51,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-image: url('$(res)/img/element-icons/room/ellipsis.svg'); - background-color: $secondary-fg-color; + background-color: $secondary-content; } } } diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index fd808362377..cfcb0c48a2e 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -64,7 +64,7 @@ $MiniAppTileHeight: 200px; &:hover { .mx_AppsContainer_resizerHandle::after { opacity: 0.8; - background: $primary-fg-color; + background: $primary-content; } .mx_ResizeHandle_horizontal::before { @@ -79,7 +79,7 @@ $MiniAppTileHeight: 200px; content: ''; - background-color: $primary-fg-color; + background-color: $primary-content; opacity: 0.8; } } diff --git a/res/css/views/rooms/_Autocomplete.scss b/res/css/views/rooms/_Autocomplete.scss index ffe1011f517..fcdab37f5a5 100644 --- a/res/css/views/rooms/_Autocomplete.scss +++ b/res/css/views/rooms/_Autocomplete.scss @@ -4,12 +4,12 @@ z-index: 1001; width: 100%; border: 1px solid $primary-hairline-color; - background: $primary-bg-color; + background: $background; border-bottom: none; border-radius: 8px 8px 0 0; - max-height: 35vh; overflow: clip; display: flex; + flex-direction: column; box-shadow: 0px -16px 32px $composer-shadow-color; } @@ -20,13 +20,13 @@ /* a "block" completion takes up a whole line */ .mx_Autocomplete_Completion_block { - height: 34px; + min-height: 34px; display: flex; padding: 0 12px; user-select: none; cursor: pointer; align-items: center; - color: $primary-fg-color; + color: $primary-content; } .mx_Autocomplete_Completion_block * { @@ -42,7 +42,7 @@ user-select: none; cursor: pointer; align-items: center; - color: $primary-fg-color; + color: $primary-content; } .mx_Autocomplete_Completion_pill > * { @@ -63,6 +63,7 @@ margin: 12px; height: 100%; overflow-y: scroll; + max-height: 35vh; } .mx_Autocomplete_Completion_container_truncate { @@ -84,7 +85,7 @@ .mx_Autocomplete_provider_name { margin: 12px; - color: $primary-fg-color; + color: $primary-content; font-weight: 400; opacity: 0.4; } diff --git a/res/css/views/rooms/_BasicMessageComposer.scss b/res/css/views/rooms/_BasicMessageComposer.scss index 544a96daba8..752d3b0a54a 100644 --- a/res/css/views/rooms/_BasicMessageComposer.scss +++ b/res/css/views/rooms/_BasicMessageComposer.scss @@ -31,7 +31,7 @@ limitations under the License. @keyframes visualbell { from { background-color: $visual-bell-bg-color; } - to { background-color: $primary-bg-color; } + to { background-color: $background; } } &.mx_BasicMessageComposer_input_error { diff --git a/res/css/views/rooms/_EditMessageComposer.scss b/res/css/views/rooms/_EditMessageComposer.scss index 214bfc4a1a9..bf3c7c9b425 100644 --- a/res/css/views/rooms/_EditMessageComposer.scss +++ b/res/css/views/rooms/_EditMessageComposer.scss @@ -28,7 +28,7 @@ limitations under the License. .mx_BasicMessageComposer_input { border-radius: 4px; border: solid 1px $primary-hairline-color; - background-color: $primary-bg-color; + background-color: $background; max-height: 200px; padding: 3px 6px; diff --git a/res/css/views/rooms/_EntityTile.scss b/res/css/views/rooms/_EntityTile.scss index 27a4e670893..a2ebd6c11b2 100644 --- a/res/css/views/rooms/_EntityTile.scss +++ b/res/css/views/rooms/_EntityTile.scss @@ -18,7 +18,7 @@ limitations under the License. .mx_EntityTile { display: flex; align-items: center; - color: $primary-fg-color; + color: $primary-content; cursor: pointer; .mx_E2EIcon { @@ -86,12 +86,12 @@ limitations under the License. .mx_EntityTile_ellipsis .mx_EntityTile_name { font-style: italic; - color: $primary-fg-color; + color: $primary-content; } .mx_EntityTile_invitePlaceholder .mx_EntityTile_name { font-style: italic; - color: $primary-fg-color; + color: $primary-content; } .mx_EntityTile_unavailable .mx_EntityTile_avatar, diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index 8c02affbedf..41c9dad394a 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -33,6 +33,14 @@ limitations under the License. margin-top: 2px; } + &.mx_EventTile_highlight { + &::before { + background-color: $event-highlight-bg-color; + } + + color: $event-highlight-fg-color; + } + /* For replies */ .mx_EventTile { padding-top: 0; diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 56cede08951..351abc5cd99 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -55,7 +55,7 @@ $hover-select-border: 4px; } .mx_SenderProfile { - color: $primary-fg-color; + color: $primary-content; font-size: $font-14px; display: inline-block; /* anti-zalgo, with overflow hidden */ overflow: hidden; @@ -161,7 +161,7 @@ $hover-select-border: 4px; // up with the other read receipts &::before { - background-color: $tertiary-fg-color; + background-color: $tertiary-content; mask-repeat: no-repeat; mask-position: center; mask-size: 14px; @@ -480,7 +480,7 @@ $hover-select-border: 4px; } pre code > * { - display: inline-block; + display: inline; } pre { @@ -514,7 +514,7 @@ $hover-select-border: 4px; .mx_EventTile:hover .mx_EventTile_body pre, .mx_EventTile.focus-visible:focus-within .mx_EventTile_body pre { - border: 1px solid #e5e5e5; // deliberate constant as we're behind an invert filter + border: 1px solid $tertiary-content; } .mx_EventTile_pre_container { @@ -618,7 +618,7 @@ $hover-select-border: 4px; } .mx_EventTile_keyRequestInfo_text a { - color: $primary-fg-color; + color: $primary-content; text-decoration: underline; cursor: pointer; } @@ -643,6 +643,7 @@ $hover-select-border: 4px; // Remove some of the default tile padding so that the error is centered margin-right: 0; + .mx_EventTile_line { padding-left: 0; margin-right: 0; @@ -674,3 +675,62 @@ $hover-select-border: 4px; margin-right: 0; } } + +.mx_ThreadInfo:hover { + cursor: pointer; +} + +.mx_ThreadView { + display: flex; + flex-direction: column; + + .mx_ScrollPanel { + margin-top: 20px; + + .mx_RoomView_MessageList { + padding: 0; + } + } + + .mx_EventTile_senderDetails { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 6px; + + a { + flex: 1; + min-width: none; + max-width: 100%; + display: flex; + align-items: center; + + .mx_SenderProfile { + flex: 1; + } + } + } + + .mx_ThreadView_List { + flex: 1; + overflow: scroll; + } + + .mx_EventTile_roomName { + display: none; + } + + .mx_EventTile_line { + padding-left: 0 !important; + order: 10 !important; + } + + .mx_EventTile { + width: 100%; + display: flex; + flex-direction: column; + margin-top: 0; + padding-bottom: 5px; + margin-bottom: 5px; + } +} diff --git a/res/css/views/rooms/_JumpToBottomButton.scss b/res/css/views/rooms/_JumpToBottomButton.scss index a8dc2ce11cd..2b38b509de6 100644 --- a/res/css/views/rooms/_JumpToBottomButton.scss +++ b/res/css/views/rooms/_JumpToBottomButton.scss @@ -56,7 +56,7 @@ limitations under the License. height: 38px; border-radius: 19px; box-sizing: border-box; - background: $primary-bg-color; + background: $background; border: 1.3px solid $muted-fg-color; cursor: pointer; } diff --git a/res/css/views/rooms/_MemberInfo.scss b/res/css/views/rooms/_MemberInfo.scss index 3f7f83d3342..4abd9c7c301 100644 --- a/res/css/views/rooms/_MemberInfo.scss +++ b/res/css/views/rooms/_MemberInfo.scss @@ -111,7 +111,7 @@ limitations under the License. .mx_MemberInfo_field { cursor: pointer; font-size: $font-15px; - color: $primary-fg-color; + color: $primary-content; margin-left: 8px; line-height: $font-23px; } diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index 5e2eff4047c..94452423061 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -130,7 +130,7 @@ limitations under the License. @keyframes visualbell { from { background-color: $visual-bell-bg-color; } - to { background-color: $primary-bg-color; } + to { background-color: $background; } } .mx_MessageComposer_input_error { @@ -160,8 +160,8 @@ limitations under the License. resize: none; outline: none; box-shadow: none; - color: $primary-fg-color; - background-color: $primary-bg-color; + color: $primary-content; + background-color: $background; font-size: $font-14px; max-height: 120px; overflow: auto; @@ -340,3 +340,19 @@ limitations under the License. height: 50px; } } + +/** + * Unstable compact mode + */ + +.mx_MessageComposer.mx_MessageComposer--compact { + margin-right: 0; + + .mx_MessageComposer_wrapper { + padding: 0; + } + + .mx_MessageComposer_button:last-child { + margin-right: 0; + } +} diff --git a/res/css/views/rooms/_NewRoomIntro.scss b/res/css/views/rooms/_NewRoomIntro.scss index e0cccfa885e..f0e471d3844 100644 --- a/res/css/views/rooms/_NewRoomIntro.scss +++ b/res/css/views/rooms/_NewRoomIntro.scss @@ -67,6 +67,6 @@ limitations under the License. > p { margin: 0; font-size: $font-15px; - color: $secondary-fg-color; + color: $secondary-content; } } diff --git a/res/css/views/rooms/_NotificationBadge.scss b/res/css/views/rooms/_NotificationBadge.scss index 64b2623238f..670e057cfa2 100644 --- a/res/css/views/rooms/_NotificationBadge.scss +++ b/res/css/views/rooms/_NotificationBadge.scss @@ -42,7 +42,7 @@ limitations under the License. // These are the 3 background types &.mx_NotificationBadge_dot { - background-color: $primary-fg-color; // increased visibility + background-color: $primary-content; // increased visibility width: 6px; height: 6px; diff --git a/res/css/views/rooms/_PinnedEventTile.scss b/res/css/views/rooms/_PinnedEventTile.scss index 15b3c16faa7..07978a8f655 100644 --- a/res/css/views/rooms/_PinnedEventTile.scss +++ b/res/css/views/rooms/_PinnedEventTile.scss @@ -67,7 +67,7 @@ limitations under the License. //left: 0; height: inherit; width: inherit; - background: $secondary-fg-color; + background: $secondary-content; mask-position: center; mask-size: 8px; mask-repeat: no-repeat; @@ -87,7 +87,7 @@ limitations under the License. .mx_PinnedEventTile_timestamp { font-size: inherit; line-height: inherit; - color: $secondary-fg-color; + color: $secondary-content; } .mx_AccessibleButton_kind_link { diff --git a/res/css/views/rooms/_ReplyPreview.scss b/res/css/views/rooms/_ReplyPreview.scss index 60feb39d11e..70a820e4123 100644 --- a/res/css/views/rooms/_ReplyPreview.scss +++ b/res/css/views/rooms/_ReplyPreview.scss @@ -16,7 +16,7 @@ limitations under the License. .mx_ReplyPreview { border: 1px solid $primary-hairline-color; - background: $primary-bg-color; + background: $background; border-bottom: none; border-radius: 8px 8px 0 0; max-height: 50vh; @@ -28,7 +28,7 @@ limitations under the License. .mx_ReplyPreview_header { margin: 8px; - color: $primary-fg-color; + color: $primary-content; font-weight: 400; opacity: 0.4; } diff --git a/res/css/views/rooms/_ReplyTile.scss b/res/css/views/rooms/_ReplyTile.scss index fd21e5f348b..3ef6491ec98 100644 --- a/res/css/views/rooms/_ReplyTile.scss +++ b/res/css/views/rooms/_ReplyTile.scss @@ -42,7 +42,7 @@ limitations under the License. display: flex; flex-direction: column; text-decoration: none; - color: $primary-fg-color; + color: $primary-content; } .mx_RedactedBody { diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index 4142b0a2efc..81dfa90c968 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -17,7 +17,7 @@ limitations under the License. .mx_RoomHeader { flex: 0 0 50px; border-bottom: 1px solid $primary-hairline-color; - background-color: $roomheader-bg-color; + background-color: $background; .mx_RoomHeader_e2eIcon { height: 12px; @@ -74,7 +74,7 @@ limitations under the License. .mx_RoomHeader_buttons { display: flex; - background-color: $primary-bg-color; + background-color: $background; } .mx_RoomHeader_info { diff --git a/res/css/views/rooms/_RoomList.scss b/res/css/views/rooms/_RoomList.scss index 8eda25d0c99..7d967661a62 100644 --- a/res/css/views/rooms/_RoomList.scss +++ b/res/css/views/rooms/_RoomList.scss @@ -43,11 +43,11 @@ limitations under the License. div:first-child { font-weight: $font-semi-bold; line-height: $font-18px; - color: $primary-fg-color; + color: $primary-content; } .mx_AccessibleButton { - color: $primary-fg-color; + color: $primary-content; position: relative; padding: 8px 8px 8px 32px; font-size: inherit; @@ -64,7 +64,7 @@ limitations under the License. position: absolute; top: 8px; left: 8px; - background: $secondary-fg-color; + background: $secondary-content; mask-position: center; mask-size: contain; mask-repeat: no-repeat; diff --git a/res/css/views/rooms/_RoomSublist.scss b/res/css/views/rooms/_RoomSublist.scss index 146b3edf71e..3fffbfd64c0 100644 --- a/res/css/views/rooms/_RoomSublist.scss +++ b/res/css/views/rooms/_RoomSublist.scss @@ -233,7 +233,7 @@ limitations under the License. &:hover, &.mx_RoomSublist_hasMenuOpen { .mx_RoomSublist_resizerHandle { opacity: 0.8; - background-color: $primary-fg-color; + background-color: $primary-content; } } } diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index b8f4aeb6e7b..0c04f271156 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -124,7 +124,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background: $primary-fg-color; + background: $primary-content; } } diff --git a/res/css/views/rooms/_SearchBar.scss b/res/css/views/rooms/_SearchBar.scss index d9f730a8b6a..e08168a1227 100644 --- a/res/css/views/rooms/_SearchBar.scss +++ b/res/css/views/rooms/_SearchBar.scss @@ -47,7 +47,7 @@ limitations under the License. padding: 5px; font-size: $font-15px; cursor: pointer; - color: $primary-fg-color; + color: $primary-content; border-bottom: 2px solid $accent-color; font-weight: 600; } diff --git a/res/css/views/rooms/_TopUnreadMessagesBar.scss b/res/css/views/rooms/_TopUnreadMessagesBar.scss index 8841b042a0e..7c7d96e713f 100644 --- a/res/css/views/rooms/_TopUnreadMessagesBar.scss +++ b/res/css/views/rooms/_TopUnreadMessagesBar.scss @@ -41,7 +41,7 @@ limitations under the License. height: 38px; border-radius: 19px; box-sizing: border-box; - background: $primary-bg-color; + background: $background; border: 1.3px solid $muted-fg-color; cursor: pointer; } @@ -62,7 +62,7 @@ limitations under the License. display: block; width: 18px; height: 18px; - background: $primary-bg-color; + background: $background; border: 1.3px solid $muted-fg-color; border-radius: 10px; margin: 5px auto; diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.scss b/res/css/views/rooms/_VoiceRecordComposerTile.scss index 8196d5c67a5..69fe292c0a2 100644 --- a/res/css/views/rooms/_VoiceRecordComposerTile.scss +++ b/res/css/views/rooms/_VoiceRecordComposerTile.scss @@ -48,7 +48,7 @@ limitations under the License. .mx_VoiceRecordComposerTile_uploadingState { margin-right: 10px; - color: $secondary-fg-color; + color: $secondary-content; } .mx_VoiceRecordComposerTile_failedState { diff --git a/res/css/views/rooms/_WhoIsTypingTile.scss b/res/css/views/rooms/_WhoIsTypingTile.scss index 1c0dabbeb5f..49655742bb1 100644 --- a/res/css/views/rooms/_WhoIsTypingTile.scss +++ b/res/css/views/rooms/_WhoIsTypingTile.scss @@ -36,7 +36,7 @@ limitations under the License. } .mx_WhoIsTypingTile_avatars .mx_BaseAvatar { - border: 1px solid $primary-bg-color; + border: 1px solid $background; border-radius: 40px; } @@ -45,7 +45,7 @@ limitations under the License. display: inline-block; color: #acacac; background-color: #ddd; - border: 1px solid $primary-bg-color; + border: 1px solid $background; border-radius: 40px; width: 24px; height: 24px; diff --git a/res/css/views/settings/_LayoutSwitcher.scss b/res/css/views/settings/_LayoutSwitcher.scss index 924fe5ae1b1..00fb8aba561 100644 --- a/res/css/views/settings/_LayoutSwitcher.scss +++ b/res/css/views/settings/_LayoutSwitcher.scss @@ -21,7 +21,7 @@ limitations under the License. flex-direction: row; gap: 24px; - color: $primary-fg-color; + color: $primary-content; > .mx_LayoutSwitcher_RadioButton { flex-grow: 0; diff --git a/res/css/views/settings/_Notifications.scss b/res/css/views/settings/_Notifications.scss index f93e0a53a89..a0e46c00717 100644 --- a/res/css/views/settings/_Notifications.scss +++ b/res/css/views/settings/_Notifications.scss @@ -15,7 +15,7 @@ limitations under the License. */ .mx_UserNotifSettings { - color: $primary-fg-color; // override from default settings page styles + color: $primary-content; // override from default settings page styles .mx_UserNotifSettings_pushRulesTable { width: calc(100% + 12px); // +12px to line up center of 'Noisy' column with toggle switches @@ -34,7 +34,7 @@ limitations under the License. } tr > th:nth-child(n + 2) { - color: $secondary-fg-color; + color: $secondary-content; font-size: $font-12px; vertical-align: middle; width: 66px; diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss index 3290a998ab7..5aa9db7e864 100644 --- a/res/css/views/settings/tabs/_SettingsTab.scss +++ b/res/css/views/settings/tabs/_SettingsTab.scss @@ -25,7 +25,7 @@ limitations under the License. .mx_SettingsTab_heading { font-size: $font-20px; font-weight: 600; - color: $primary-fg-color; + color: $primary-content; margin-bottom: 10px; } @@ -37,7 +37,7 @@ limitations under the License. font-size: $font-16px; display: block; font-weight: 600; - color: $primary-fg-color; + color: $primary-content; margin-bottom: 10px; margin-top: 12px; } @@ -72,7 +72,7 @@ limitations under the License. vertical-align: middle; display: inline-block; font-size: $font-14px; - color: $primary-fg-color; + color: $primary-content; max-width: calc(100% - $font-48px); // Force word wrap instead of colliding with the switch box-sizing: border-box; padding-right: 10px; @@ -82,7 +82,7 @@ limitations under the License. margin-top: 4px; font-size: $font-12px; line-height: $font-15px; - color: $secondary-fg-color; + color: $secondary-content; } .mx_SettingsTab_section .mx_SettingsFlag .mx_ToggleSwitch { diff --git a/res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss b/res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss index 2aab201352f..8fd0f144188 100644 --- a/res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss +++ b/res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss @@ -22,7 +22,7 @@ limitations under the License. .mx_SecurityRoomSettingsTab_spacesWithAccess { > h4 { - color: $secondary-fg-color; + color: $secondary-content; font-weight: $font-semi-bold; font-size: $font-12px; line-height: $font-15px; @@ -33,7 +33,7 @@ limitations under the License. font-weight: 500; font-size: $font-14px; line-height: 32px; // matches height of avatar for v-align - color: $secondary-fg-color; + color: $secondary-content; display: inline-block; img.mx_RoomAvatar_isSpaceRoom, @@ -89,7 +89,7 @@ limitations under the License. font-weight: $font-semi-bold; font-size: $font-15px; line-height: $font-24px; - color: $primary-fg-color; + color: $primary-content; display: block; } } @@ -100,7 +100,7 @@ limitations under the License. margin-bottom: 16px; font-size: $font-15px; line-height: $font-24px; - color: $secondary-fg-color; + color: $secondary-content; & + .mx_RadioButton { border-top: 1px solid $menu-border-color; diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss index d8e617a40d1..57c6e9b8654 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -24,7 +24,7 @@ limitations under the License. } .mx_AppearanceUserSettingsTab_fontScaling { - color: $primary-fg-color; + color: $primary-content; } .mx_AppearanceUserSettingsTab_fontSlider { @@ -81,7 +81,7 @@ limitations under the License. .mx_AppearanceUserSettingsTab_themeSection { $radio-bg-color: $input-darker-bg-color; - color: $primary-fg-color; + color: $primary-content; > .mx_ThemeSelectors { display: flex; @@ -156,7 +156,7 @@ limitations under the License. } .mx_AppearanceUserSettingsTab_Advanced { - color: $primary-fg-color; + color: $primary-content; > * { margin-bottom: 16px; diff --git a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss index 4cdfa0b40f3..d1076205ad9 100644 --- a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss @@ -30,7 +30,7 @@ limitations under the License. font-weight: $font-semi-bold; font-size: $font-15px; line-height: $font-18px; - color: $primary-fg-color; + color: $primary-content; margin: 16px 0; .mx_BaseAvatar { diff --git a/res/css/views/spaces/_SpaceBasicSettings.scss b/res/css/views/spaces/_SpaceBasicSettings.scss index c73e0715dd3..bff574ded36 100644 --- a/res/css/views/spaces/_SpaceBasicSettings.scss +++ b/res/css/views/spaces/_SpaceBasicSettings.scss @@ -27,7 +27,7 @@ limitations under the License. position: relative; height: 80px; width: 80px; - background-color: $tertiary-fg-color; + background-color: $tertiary-content; border-radius: 16px; } diff --git a/res/css/views/spaces/_SpaceCreateMenu.scss b/res/css/views/spaces/_SpaceCreateMenu.scss index 41536bc8b1c..3f526a6bba5 100644 --- a/res/css/views/spaces/_SpaceCreateMenu.scss +++ b/res/css/views/spaces/_SpaceCreateMenu.scss @@ -28,7 +28,7 @@ $spacePanelWidth: 71px; padding: 24px; width: 480px; box-sizing: border-box; - background-color: $primary-bg-color; + background-color: $background; position: relative; > div { @@ -40,7 +40,7 @@ $spacePanelWidth: 71px; > p { font-size: $font-15px; - color: $secondary-fg-color; + color: $secondary-content; } .mx_SpaceFeedbackPrompt { @@ -76,7 +76,7 @@ $spacePanelWidth: 71px; width: 28px; top: 0; left: 0; - background-color: $tertiary-fg-color; + background-color: $tertiary-content; transform: rotate(90deg); mask-repeat: no-repeat; mask-position: 2px 3px; @@ -108,7 +108,7 @@ $spacePanelWidth: 71px; line-height: $font-24px; > span { - color: $secondary-fg-color; + color: $secondary-content; position: relative; font-size: inherit; line-height: inherit; diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index eb80f2d5cfc..cb05b1a977b 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -43,7 +43,7 @@ limitations under the License. .mx_CallEvent_type { font-size: $font-12px; line-height: $font-15px; - color: $tertiary-fg-color; + color: $tertiary-content; margin-top: 4px; margin-bottom: 6px; @@ -62,7 +62,7 @@ limitations under the License. position: absolute; height: inherit; width: inherit; - background-color: $tertiary-fg-color; + background-color: $tertiary-content; mask-repeat: no-repeat; mask-size: contain; } @@ -139,7 +139,7 @@ limitations under the License. height: inherit; width: inherit; - background-color: $tertiary-fg-color; + background-color: $tertiary-content; mask-repeat: no-repeat; mask-size: contain; mask-position: center; diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 63ca91267f3..aa0aa4e2a67 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -74,9 +74,9 @@ limitations under the License. > .mx_VideoFeed { width: 100%; height: 100%; + border-width: 0 !important; // Override mx_VideoFeed_speaking &.mx_VideoFeed_voice { - background-color: $inverted-bg-color; display: flex; justify-content: center; align-items: center; diff --git a/res/css/views/voip/_CallViewForRoom.scss b/res/css/views/voip/_CallViewForRoom.scss index 769e00338e8..d23fcc18bc8 100644 --- a/res/css/views/voip/_CallViewForRoom.scss +++ b/res/css/views/voip/_CallViewForRoom.scss @@ -39,7 +39,7 @@ limitations under the License. width: 100%; max-width: 64px; - background-color: $primary-fg-color; + background-color: $primary-content; } } } diff --git a/res/css/views/voip/_CallViewHeader.scss b/res/css/views/voip/_CallViewHeader.scss index 014cfce4789..0575f4f5356 100644 --- a/res/css/views/voip/_CallViewHeader.scss +++ b/res/css/views/voip/_CallViewHeader.scss @@ -53,7 +53,7 @@ limitations under the License. height: 20px; width: 20px; vertical-align: middle; - background-color: $secondary-fg-color; + background-color: $secondary-content; mask-repeat: no-repeat; mask-size: contain; mask-position: center; @@ -90,7 +90,7 @@ limitations under the License. .mx_CallViewHeader_callTypeSmall { font-size: 12px; - color: $secondary-fg-color; + color: $secondary-content; line-height: initial; height: 15px; overflow: hidden; @@ -113,7 +113,7 @@ limitations under the License. height: 16px; width: 16px; - background-color: $secondary-fg-color; + background-color: $secondary-content; mask-repeat: no-repeat; mask-size: contain; mask-position: center; diff --git a/res/css/views/voip/_CallViewSidebar.scss b/res/css/views/voip/_CallViewSidebar.scss index dbadc220282..fd9c76defcb 100644 --- a/res/css/views/voip/_CallViewSidebar.scss +++ b/res/css/views/voip/_CallViewSidebar.scss @@ -33,10 +33,9 @@ limitations under the License. > .mx_VideoFeed { width: 100%; + border-radius: 4px; &.mx_VideoFeed_voice { - border-radius: 4px; - display: flex; align-items: center; justify-content: center; diff --git a/res/css/views/voip/_DialPad.scss b/res/css/views/voip/_DialPad.scss index eefd2e9ba58..288f1f5d31a 100644 --- a/res/css/views/voip/_DialPad.scss +++ b/res/css/views/voip/_DialPad.scss @@ -33,7 +33,7 @@ limitations under the License. width: 40px; height: 40px; - background-color: $dialpad-button-bg-color; + background-color: $quinary-content; border-radius: 40px; font-size: 18px; font-weight: 600; diff --git a/res/css/views/voip/_DialPadContextMenu.scss b/res/css/views/voip/_DialPadContextMenu.scss index 527d223ffc7..d2014241e9b 100644 --- a/res/css/views/voip/_DialPadContextMenu.scss +++ b/res/css/views/voip/_DialPadContextMenu.scss @@ -30,7 +30,7 @@ limitations under the License. margin-right: 20px; /* a separator between the input line and the dial buttons */ - border-bottom: 1px solid $quaternary-fg-color; + border-bottom: 1px solid $quaternary-content; transition: border-bottom 0.25s; } diff --git a/res/css/views/voip/_DialPadModal.scss b/res/css/views/voip/_DialPadModal.scss index b8042f77aee..f378507f900 100644 --- a/res/css/views/voip/_DialPadModal.scss +++ b/res/css/views/voip/_DialPadModal.scss @@ -30,7 +30,7 @@ limitations under the License. margin-right: 40px; /* a separator between the input line and the dial buttons */ - border-bottom: 1px solid $quaternary-fg-color; + border-bottom: 1px solid $quaternary-content; transition: border-bottom 0.25s; } diff --git a/res/css/views/voip/_VideoFeed.scss b/res/css/views/voip/_VideoFeed.scss index 7a8d39dfe31..1f17a546928 100644 --- a/res/css/views/voip/_VideoFeed.scss +++ b/res/css/views/voip/_VideoFeed.scss @@ -17,12 +17,23 @@ limitations under the License. .mx_VideoFeed { overflow: hidden; position: relative; + box-sizing: border-box; + border: transparent 2px solid; + display: flex; &.mx_VideoFeed_voice { background-color: $inverted-bg-color; aspect-ratio: 16 / 9; } + &.mx_VideoFeed_speaking { + border: $accent-color 2px solid; + + .mx_VideoFeed_video { + border-radius: 0; + } + } + .mx_VideoFeed_video { width: 100%; background-color: transparent; diff --git a/res/img/element-icons/message/thread.svg b/res/img/element-icons/message/thread.svg new file mode 100644 index 00000000000..b4a7cc00661 --- /dev/null +++ b/res/img/element-icons/message/thread.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index bdf66e28bc0..4a6db5dd556 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -20,29 +20,18 @@ $space-nav: rgba($panel-base, 0.1); // unified palette // try to use these colors when possible -$bg-color: $background; -$base-color: $bg-color; -$base-text-color: $primary-content; $header-panel-bg-color: #20252B; $header-panel-border-color: #000000; $header-panel-text-primary-color: #B9BEC6; $header-panel-text-secondary-color: #c8c8cd; -$text-primary-color: $primary-content; $text-secondary-color: #B9BEC6; -$quaternary-fg-color: $quaternary-content; $search-bg-color: #181b21; $search-placeholder-color: #61708b; $room-highlight-color: #343a46; // typical text (dark-on-white in light skin) -$primary-fg-color: $text-primary-color; -$primary-bg-color: $bg-color; $muted-fg-color: $header-panel-text-primary-color; -// additional text colors -$secondary-fg-color: $secondary-content; -$tertiary-fg-color: $tertiary-content; - // used for dialog box text $light-fg-color: $header-panel-text-secondary-color; @@ -61,7 +50,7 @@ $info-plinth-fg-color: #888; $preview-bar-bg-color: $header-panel-bg-color; $groupFilterPanel-bg-color: rgba(38, 39, 43, 0.82); -$inverted-bg-color: $base-color; +$inverted-bg-color: $background; // used by AddressSelector $selected-color: $room-highlight-color; @@ -82,7 +71,7 @@ $input-focused-border-color: #238cf5; $input-valid-border-color: $accent-color; $input-invalid-border-color: $warning-color; -$field-focused-label-bg-color: $bg-color; +$field-focused-label-bg-color: $background; $resend-button-divider-color: #b9bec64a; // muted-text with a 4A opacity. @@ -93,15 +82,15 @@ $scrollbar-track-color: transparent; // context menus $menu-border-color: $header-panel-border-color; $menu-bg-color: $header-panel-bg-color; -$menu-box-shadow-color: $bg-color; +$menu-box-shadow-color: $background; $menu-selected-color: $room-highlight-color; $avatar-initial-color: #ffffff; -$avatar-bg-color: $bg-color; +$avatar-bg-color: $background; -$h3-color: $primary-fg-color; +$h3-color: $primary-content; -$dialog-title-fg-color: $base-text-color; +$dialog-title-fg-color: $primary-content; $dialog-backdrop-color: #000; $dialog-shadow-color: rgba(0, 0, 0, 0.48); $dialog-close-fg-color: #9fa9ba; @@ -117,11 +106,10 @@ $settings-profile-button-bg-color: #e7e7e7; $settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; $settings-subsection-fg-color: $text-secondary-color; -$topleftmenu-color: $text-primary-color; -$roomheader-color: $text-primary-color; -$roomheader-bg-color: $bg-color; +$topleftmenu-color: $primary-content; +$roomheader-color: $primary-content; $roomheader-addroom-bg-color: rgba(92, 100, 112, 0.3); -$roomheader-addroom-fg-color: $text-primary-color; +$roomheader-addroom-fg-color: $primary-content; $groupFilterPanel-button-color: $header-panel-text-primary-color; $groupheader-button-color: $header-panel-text-primary-color; $rightpanel-button-color: $header-panel-text-primary-color; @@ -135,18 +123,16 @@ $composer-e2e-icon-color: $header-panel-text-primary-color; // ******************** $theme-button-bg-color: #e3e8f0; -$dialpad-button-bg-color: $quinary-content; $roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons -$roomlist-filter-active-bg-color: $bg-color; $roomlist-bg-color: rgba(33, 38, 44, 0.90); -$roomlist-header-color: $tertiary-fg-color; -$roomsublist-divider-color: $primary-fg-color; +$roomlist-header-color: $tertiary-content; +$roomsublist-divider-color: $primary-content; $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%); $groupFilterPanel-divider-color: $roomlist-header-color; -$roomtile-preview-color: $secondary-fg-color; +$roomtile-preview-color: $secondary-content; $roomtile-default-badge-bg-color: #61708b; $roomtile-selected-bg-color: rgba(141, 151, 165, 0.2); @@ -170,12 +156,12 @@ $event-highlight-bg-color: #25271F; $event-timestamp-color: $text-secondary-color; // Tabbed views -$tab-label-fg-color: $text-primary-color; -$tab-label-active-fg-color: $text-primary-color; +$tab-label-fg-color: $primary-content; +$tab-label-active-fg-color: $primary-content; $tab-label-bg-color: transparent; $tab-label-active-bg-color: $accent-color; -$tab-label-icon-bg-color: $text-primary-color; -$tab-label-active-icon-bg-color: $text-primary-color; +$tab-label-icon-bg-color: $primary-content; +$tab-label-active-icon-bg-color: $primary-content; // Buttons $button-primary-fg-color: $primary-content; @@ -217,21 +203,21 @@ $kbd-border-color: #000000; $tooltip-timeline-bg-color: $groupFilterPanel-bg-color; $tooltip-timeline-fg-color: $primary-content; -$interactive-tooltip-bg-color: $base-color; +$interactive-tooltip-bg-color: $background; $interactive-tooltip-fg-color: $primary-content; $breadcrumb-placeholder-bg-color: #272c35; $user-tile-hover-bg-color: $header-panel-bg-color; -$message-body-panel-fg-color: $secondary-fg-color; +$message-body-panel-fg-color: $secondary-content; $message-body-panel-bg-color: $quinary-content; -$message-body-panel-icon-fg-color: $secondary-fg-color; -$message-body-panel-icon-bg-color: $system; // "System Dark" +$message-body-panel-icon-bg-color: $system; +$message-body-panel-icon-fg-color: $secondary-content; -$voice-record-stop-border-color: $quaternary-fg-color; -$voice-record-waveform-incomplete-fg-color: $quaternary-fg-color; -$voice-record-icon-color: $quaternary-fg-color; +$voice-record-stop-border-color: $quaternary-content; +$voice-record-waveform-incomplete-fg-color: $quaternary-content; +$voice-record-icon-color: $quaternary-content; $voice-playback-button-bg-color: $message-body-panel-icon-bg-color; $voice-playback-button-fg-color: $message-body-panel-icon-fg-color; @@ -249,7 +235,7 @@ $composer-shadow-color: rgba(0, 0, 0, 0.28); $eventbubble-self-bg: #14322E; $eventbubble-others-bg: $event-selected-color; $eventbubble-bg-hover: #1C2026; -$eventbubble-avatar-outline: $bg-color; +$eventbubble-avatar-outline: $background; $eventbubble-reply-color: #C1C6CD; // ***** Mixins! ***** diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 3e3412c6c1f..d5bc5e6dd75 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -24,7 +24,12 @@ $primary-bg-color: $bg-color; $muted-fg-color: $header-panel-text-primary-color; // Legacy theme backports -$quaternary-fg-color: #6F7882; +$primary-content: $primary-fg-color; +$secondary-content: $secondary-fg-color; +$tertiary-content: $tertiary-fg-color; +$quaternary-content: #6F7882; +$quinary-content: $quaternary-content; +$background: $primary-bg-color; // used for dialog box text $light-fg-color: $header-panel-text-secondary-color; @@ -117,7 +122,6 @@ $composer-e2e-icon-color: $header-panel-text-primary-color; // ******************** $theme-button-bg-color: #e3e8f0; -$dialpad-button-bg-color: #6F7882; $roomlist-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 3f722bcb308..47247e5e230 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -32,7 +32,12 @@ $primary-bg-color: #ffffff; $muted-fg-color: #61708b; // Commonly used in headings and relevant alt text // Legacy theme backports -$quaternary-fg-color: #C1C6CD; +$primary-content: $primary-fg-color; +$secondary-content: $secondary-fg-color; +$tertiary-content: $tertiary-fg-color; +$quaternary-content: #C1C6CD; +$quinary-content: #e3e8f0; +$background: $primary-bg-color; // used for dialog box text $light-fg-color: #747474; @@ -186,8 +191,6 @@ $voipcall-plinth-color: $system; // ******************** $theme-button-bg-color: #e3e8f0; -$dialpad-button-bg-color: #e3e8f0; - $roomlist-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons $roomlist-filter-active-bg-color: $roomlist-button-bg-color; diff --git a/res/themes/light-custom/css/_custom.scss b/res/themes/light-custom/css/_custom.scss index 6c37351414b..f4685fe8fae 100644 --- a/res/themes/light-custom/css/_custom.scss +++ b/res/themes/light-custom/css/_custom.scss @@ -38,7 +38,7 @@ $lightbox-border-color: var(--timeline-background-color); $menu-bg-color: var(--timeline-background-color); $avatar-bg-color: var(--timeline-background-color); $message-action-bar-bg-color: var(--timeline-background-color); -$primary-bg-color: var(--timeline-background-color); +$background: var(--timeline-background-color); $togglesw-ball-color: var(--timeline-background-color); $droptarget-bg-color: var(--timeline-background-color-50pct); //still needs alpha at .5 $authpage-modal-bg-color: var(--timeline-background-color-50pct); //still needs alpha at .59 @@ -69,7 +69,7 @@ $roomlist-bg-color: var(--roomlist-background-color); // // --timeline-text-color $message-action-bar-fg-color: var(--timeline-text-color); -$primary-fg-color: var(--timeline-text-color); +$primary-content: var(--timeline-text-color); $settings-profile-overlay-placeholder-fg-color: var(--timeline-text-color); $roomtopic-color: var(--timeline-text-color-50pct); $tab-label-fg-color: var(--timeline-text-color); @@ -139,7 +139,7 @@ $event-selected-color: var(--timeline-highlights-color); $event-highlight-bg-color: var(--timeline-highlights-color); // // redirect some variables away from their hardcoded values in the light theme -$settings-grey-fg-color: $primary-fg-color; +$settings-grey-fg-color: $primary-content; // --eventbubble colors $eventbubble-self-bg: var(--eventbubble-self-bg, $eventbubble-self-bg); diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index e1a64d6d3e2..96e5fd7155d 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -37,14 +37,9 @@ $accent-color: $accent; $accent-bg-color: rgba(3, 179, 129, 0.16); $notice-primary-color: #ff4b55; $notice-primary-bg-color: rgba(255, 75, 85, 0.16); -$primary-fg-color: #2e2f32; -$secondary-fg-color: $secondary-content; -$tertiary-fg-color: #8D99A5; -$quaternary-fg-color: $quaternary-content; $header-panel-bg-color: #f3f8fd; // typical text (dark-on-white in light skin) -$primary-bg-color: $background; $muted-fg-color: #61708b; // Commonly used in headings and relevant alt text // used for dialog box text @@ -59,7 +54,7 @@ $accent-color-50pct: rgba($accent-color, 0.5); $accent-color-darker: #92caad; $accent-color-alt: #238CF5; -$selection-fg-color: $primary-bg-color; +$selection-fg-color: $background; $focus-brightness: 105%; @@ -173,7 +168,6 @@ $rte-group-pill-color: #aaa; $topleftmenu-color: #212121; $roomheader-color: #45474a; -$roomheader-bg-color: $primary-bg-color; $roomheader-addroom-bg-color: rgba(92, 100, 112, 0.2); $roomheader-addroom-fg-color: #5c6470; $groupFilterPanel-button-color: #91A1C0; @@ -191,19 +185,16 @@ $voipcall-plinth-color: $system; // ******************** $theme-button-bg-color: $quinary-content; -$dialpad-button-bg-color: $quinary-content; - $roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons -$roomlist-filter-active-bg-color: $background; $roomlist-bg-color: rgba(245, 245, 245, 0.90); -$roomlist-header-color: $tertiary-fg-color; -$roomsublist-divider-color: $primary-fg-color; +$roomlist-header-color: $tertiary-content; +$roomsublist-divider-color: $primary-content; $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%); $groupFilterPanel-divider-color: $roomlist-header-color; -$roomtile-preview-color: $secondary-fg-color; +$roomtile-preview-color: $secondary-content; $roomtile-default-badge-bg-color: #61708b; $roomtile-selected-bg-color: #FFF; @@ -318,8 +309,8 @@ $authpage-secondary-color: #61708b; $dark-panel-bg-color: $secondary-accent-color; $panel-gradient: rgba(242, 245, 248, 0), rgba(242, 245, 248, 1); -$message-action-bar-bg-color: $primary-bg-color; -$message-action-bar-fg-color: $primary-fg-color; +$message-action-bar-bg-color: $background; +$message-action-bar-fg-color: $primary-content; $message-action-bar-border-color: #e9edf1; $message-action-bar-hover-border-color: $focus-bg-color; @@ -342,10 +333,10 @@ $breadcrumb-placeholder-bg-color: #e8eef5; $user-tile-hover-bg-color: $header-panel-bg-color; -$message-body-panel-fg-color: $secondary-fg-color; +$message-body-panel-fg-color: $secondary-content; $message-body-panel-bg-color: $quinary-content; -$message-body-panel-icon-fg-color: $secondary-fg-color; $message-body-panel-icon-bg-color: $system; +$message-body-panel-icon-fg-color: $secondary-content; // These two don't change between themes. They are the $warning-color, but we don't // want custom themes to affect them by accident. @@ -353,8 +344,8 @@ $voice-record-stop-symbol-color: #ff4b55; $voice-record-live-circle-color: #ff4b55; $voice-record-stop-border-color: $quinary-content; -$voice-record-waveform-incomplete-fg-color: $quaternary-fg-color; -$voice-record-icon-color: $tertiary-fg-color; +$voice-record-waveform-incomplete-fg-color: $quaternary-content; +$voice-record-icon-color: $tertiary-content; $voice-playback-button-bg-color: $message-body-panel-icon-bg-color; $voice-playback-button-fg-color: $message-body-panel-icon-fg-color; @@ -363,7 +354,7 @@ $appearance-tab-border-color: $input-darker-bg-color; // blur amounts for left left panel (only for element theme) :root { - --lp-background-blur: 30px; + --lp-background-blur: 40px; } $composer-shadow-color: rgba(0, 0, 0, 0.04); @@ -371,7 +362,7 @@ $composer-shadow-color: rgba(0, 0, 0, 0.04); $eventbubble-self-bg: #F0FBF8; $eventbubble-others-bg: $system; $eventbubble-bg-hover: #FAFBFD; -$eventbubble-avatar-outline: $primary-bg-color; +$eventbubble-avatar-outline: $background; $eventbubble-reply-color: $quaternary-content; // ***** Mixins! ***** diff --git a/scripts/ci/js-sdk-to-release.sh b/scripts/ci/js-sdk-to-release.sh new file mode 100755 index 00000000000..a03165bd82e --- /dev/null +++ b/scripts/ci/js-sdk-to-release.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# This changes the js-sdk into 'release mode', that is: +# * The entry point for the library is the babel-compiled lib/index.js rather than src/index.ts +# * There's a 'typings' entry referencing the types output by tsc +# We do this so we can test that each PR still builds / type checks correctly when built +# against the released js-sdk, because if you do things like `import { User } from 'matrix-js-sdk';` +# rather than `import { User } from 'matrix-js-sdk/src/models/user';` it will work fine with the +# js-sdk in development mode but then break at release time. +# We can't use the last release of the js-sdk though: it might not be up to date enough. + +cd node_modules/matrix-js-sdk +for i in main typings +do + lib_value=$(jq -r ".matrix_lib_$i" package.json) + if [ "$lib_value" != "null" ]; then + jq ".$i = .matrix_lib_$i" package.json > package.json.new && mv package.json.new package.json + fi +done +yarn run build:compile +yarn run build:types diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh index 0990af70ce4..ec021236d95 100755 --- a/scripts/fetchdep.sh +++ b/scripts/fetchdep.sh @@ -10,6 +10,7 @@ defbranch="$3" rm -r "$defrepo" || true +# A function that clones a branch of a repo based on the org, repo and branch clone() { org=$1 repo=$2 @@ -21,45 +22,38 @@ clone() { fi } -# Try the PR author's branch in case it exists on the deps as well. -# First we check if GITHUB_HEAD_REF is defined, -# Then we check if BUILDKITE_BRANCH is defined, -# if they aren't we can assume this is a Netlify build -if [ -n "$GITHUB_HEAD_REF" ]; then - head=$GITHUB_HEAD_REF -elif [ -n "$BUILDKITE_BRANCH" ]; then - head=$BUILDKITE_BRANCH -else - # Netlify doesn't give us info about the fork so we have to get it from GitHub API - apiEndpoint="https://api.github.com/repos/matrix-org/matrix-react-sdk/pulls/" - apiEndpoint+=$REVIEW_ID - head=$(curl $apiEndpoint | jq -r '.head.label') -fi +# A function that gets info about a PR from the GitHub API based on its number +getPRInfo() { + number=$1 + if [ -n "$number" ]; then + echo "Getting info about a PR with number $number" -# If head is set, it will contain on Buildkite either: -# * "branch" when the author's branch and target branch are in the same repo -# * "fork:branch" when the author's branch is in their fork or if this is a Netlify build -# We can split on `:` into an array to check. -# For GitHub Actions we need to inspect GITHUB_REPOSITORY and GITHUB_ACTOR -# to determine whether the branch is from a fork or not -BRANCH_ARRAY=(${head//:/ }) -if [[ "${#BRANCH_ARRAY[@]}" == "1" ]]; then + apiEndpoint="https://api.github.com/repos/matrix-org/matrix-react-sdk/pulls/" + apiEndpoint+=$number - if [ -n "$GITHUB_HEAD_REF" ]; then - if [[ "$GITHUB_REPOSITORY" == "$deforg"* ]]; then - clone $deforg $defrepo $GITHUB_HEAD_REF - else - REPO_ARRAY=(${GITHUB_REPOSITORY//\// }) - clone $REPO_ARRAY[0] $defrepo $GITHUB_HEAD_REF - fi - else - clone $deforg $defrepo $BUILDKITE_BRANCH + head=$(curl $apiEndpoint | jq -r '.head.label') fi +} -elif [[ "${#BRANCH_ARRAY[@]}" == "2" ]]; then - clone ${BRANCH_ARRAY[0]} $defrepo ${BRANCH_ARRAY[1]} +# Some CIs don't give us enough info, so we just get the PR number and ask the +# GH API for more info - "fork:branch". Some give us this directly. +if [ -n "$BUILDKITE_BRANCH" ]; then + # BuildKite + head=$BUILDKITE_BRANCH +elif [ -n "$PR_NUMBER" ]; then + # GitHub + getPRInfo $PR_NUMBER +elif [ -n "$REVIEW_ID" ]; then + # Netlify + getPRInfo $REVIEW_ID fi +# $head will always be in the format "fork:branch", so we split it by ":" into +# an array. The first element will then be the fork and the second the branch. +# Based on that we clone +BRANCH_ARRAY=(${head//:/ }) +clone ${BRANCH_ARRAY[0]} $defrepo ${BRANCH_ARRAY[1]} + # Try the target branch of the push or PR. if [ -n $GITHUB_BASE_REF ]; then clone $deforg $defrepo $GITHUB_BASE_REF diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index e11e8288647..fe938c99295 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -250,7 +250,15 @@ export default class CallHandler extends EventEmitter { * @returns {boolean} */ private areAnyCallsUnsilenced(): boolean { - return this.calls.size > this.silencedCalls.size; + for (const call of this.calls.values()) { + if ( + call.state === CallState.Ringing && + !this.isCallSilenced(call.callId) + ) { + return true; + } + } + return false; } private async checkProtocols(maxTries) { @@ -477,8 +485,8 @@ export default class CallHandler extends EventEmitter { this.pause(AudioID.Ringback); } - this.calls.set(mappedRoomId, newCall); - this.emit(CallHandlerEvent.CallsChanged, this.calls); + this.removeCallForRoom(mappedRoomId); + this.addCallForRoom(mappedRoomId, newCall); this.setCallListeners(newCall); this.setCallState(newCall, newCall.state); }); @@ -513,8 +521,7 @@ export default class CallHandler extends EventEmitter { this.removeCallForRoom(mappedRoomId); mappedRoomId = newMappedRoomId; console.log("Moving call to room " + mappedRoomId); - this.calls.set(mappedRoomId, call); - this.emit(CallHandlerEvent.CallChangeRoom, call); + this.addCallForRoom(mappedRoomId, call, true); } } }); @@ -748,9 +755,15 @@ export default class CallHandler extends EventEmitter { console.log("Current turn creds expire in " + timeUntilTurnCresExpire + " ms"); const call = MatrixClientPeg.get().createCall(mappedRoomId); - console.log("Adding call for room ", roomId); - this.calls.set(roomId, call); - this.emit(CallHandlerEvent.CallsChanged, this.calls); + try { + this.addCallForRoom(roomId, call); + } catch (e) { + Modal.createTrackedDialog('Call Handler', 'Existing Call with user', ErrorDialog, { + title: _t('Already in call'), + description: _t("You're already in a call with this person."), + }); + return; + } if (transferee) { this.transferees[call.callId] = transferee; } @@ -802,13 +815,8 @@ export default class CallHandler extends EventEmitter { return; } - if (this.getCallForRoom(room.roomId)) { - Modal.createTrackedDialog('Call Handler', 'Existing Call with user', ErrorDialog, { - title: _t('Already in call'), - description: _t("You're already in a call with this person."), - }); - return; - } + // We leave the check for whether there's already a call in this room until later, + // otherwise it can race. const members = room.getJoinedMembers(); if (members.length <= 1) { @@ -862,9 +870,8 @@ export default class CallHandler extends EventEmitter { } Analytics.trackEvent('voip', 'receiveCall', 'type', call.type); - console.log("Adding call for room ", mappedRoomId); - this.calls.set(mappedRoomId, call); - this.emit(CallHandlerEvent.CallsChanged, this.calls); + + this.addCallForRoom(mappedRoomId, call); this.setCallListeners(call); // Explicitly handle first state change this.onCallStateChanged(call.state, null, call); @@ -878,6 +885,8 @@ export default class CallHandler extends EventEmitter { break; case 'hangup': case 'reject': + this.stopRingingIfPossible(this.calls.get(payload.room_id).callId); + if (!this.calls.get(payload.room_id)) { return; // no call to hangup } @@ -890,11 +899,15 @@ export default class CallHandler extends EventEmitter { // the hangup event away) break; case 'hangup_all': + this.stopRingingIfPossible(this.calls.get(payload.room_id).callId); + for (const call of this.calls.values()) { call.hangup(CallErrorCode.UserHangup, false); } break; case 'answer': { + this.stopRingingIfPossible(this.calls.get(payload.room_id).callId); + if (!this.calls.has(payload.room_id)) { return; // no call to answer } @@ -929,6 +942,12 @@ export default class CallHandler extends EventEmitter { } }; + private stopRingingIfPossible(callId: string): void { + this.silencedCalls.delete(callId); + if (this.areAnyCallsUnsilenced()) return; + this.pause(AudioID.Ring); + } + private async dialNumber(number: string) { const results = await this.pstnLookup(number); if (!results || results.length === 0 || !results[0].userid) { @@ -1130,4 +1149,21 @@ export default class CallHandler extends EventEmitter { messaging.transport.send(ElementWidgetActions.HangupCall, {}); }); } + + private addCallForRoom(roomId: string, call: MatrixCall, changedRooms = false): void { + if (this.calls.has(roomId)) { + console.log(`Couldn't add call to room ${roomId}: already have a call for this room`); + throw new Error("Already have a call for room " + roomId); + } + + console.log("setting call for room " + roomId); + this.calls.set(roomId, call); + + // Should we always emit CallsChanged too? + if (changedRooms) { + this.emit(CallHandlerEvent.CallChangeRoom, call); + } else { + this.emit(CallHandlerEvent.CallsChanged, this.calls); + } + } } diff --git a/src/ContentMessages.tsx b/src/ContentMessages.tsx index 14a0c1ed517..40f8e307a56 100644 --- a/src/ContentMessages.tsx +++ b/src/ContentMessages.tsx @@ -39,6 +39,8 @@ import { import { IUpload } from "./models/IUpload"; import { IAbortablePromise, IImageInfo } from "matrix-js-sdk/src/@types/partials"; import { BlurhashEncoder } from "./BlurhashEncoder"; +import SettingsStore from "./settings/SettingsStore"; +import { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics"; const MAX_WIDTH = 800; const MAX_HEIGHT = 600; @@ -539,6 +541,10 @@ export default class ContentMessages { msgtype: "", // set later }; + if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) { + decorateStartSendingTime(content); + } + // if we have a mime type for the file, add it to the message metadata if (file.type) { content.info.mimetype = file.type; @@ -614,6 +620,11 @@ export default class ContentMessages { }).then(function() { if (upload.canceled) throw new UploadCanceledError(); const prom = matrixClient.sendMessage(roomId, content); + if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) { + prom.then(resp => { + sendRoundTripMetric(matrixClient, roomId, resp.event_id); + }); + } CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, false, content); return prom; }, function(err) { diff --git a/src/DateUtils.ts b/src/DateUtils.ts index e8b81ca315a..c81099b893b 100644 --- a/src/DateUtils.ts +++ b/src/DateUtils.ts @@ -136,6 +136,18 @@ export function formatCallTime(delta: Date): string { return output; } +export function formatSeconds(inSeconds: number): string { + const hours = Math.floor(inSeconds / (60 * 60)).toFixed(0).padStart(2, '0'); + const minutes = Math.floor((inSeconds % (60 * 60)) / 60).toFixed(0).padStart(2, '0'); + const seconds = Math.floor(((inSeconds % (60 * 60)) % 60)).toFixed(0).padStart(2, '0'); + + let output = ""; + if (hours !== "00") output += `${hours}:`; + output += `${minutes}:${seconds}`; + + return output; +} + const MILLIS_IN_DAY = 86400000; export function wantsDateSeparator(prevEventDate: Date, nextEventDate: Date): boolean { if (!nextEventDate || !prevEventDate) { diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index 51c624e3c3a..1a551d7813f 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -100,6 +100,7 @@ export default class DeviceListener { * @param {String[]} deviceIds List of device IDs to dismiss notifications for */ async dismissUnverifiedSessions(deviceIds: Iterable) { + console.log("Dismissing unverified sessions: " + Array.from(deviceIds).join(',')); for (const d of deviceIds) { this.dismissed.add(d); } @@ -285,6 +286,9 @@ export default class DeviceListener { } } + console.log("Old unverified sessions: " + Array.from(oldUnverifiedDeviceIds).join(',')); + console.log("New unverified sessions: " + Array.from(newUnverifiedDeviceIds).join(',')); + // Display or hide the batch toast for old unverified sessions if (oldUnverifiedDeviceIds.size > 0) { showBulkUnverifiedSessionsToast(oldUnverifiedDeviceIds); diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index f43351aab29..7d0ff560b7a 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -213,6 +213,7 @@ class MatrixClientPegClass implements IMatrixClientPeg { opts.pendingEventOrdering = PendingEventOrdering.Detached; opts.lazyLoadMembers = true; opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours + opts.experimentalThreadSupport = SettingsStore.getValue("feature_thread"); // Connect the matrix client to the dispatcher and setting handlers MatrixActionCreators.start(this.matrixClient); diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index b4deb6d8c4e..902c82fff84 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -50,7 +50,6 @@ import CallHandler from "./CallHandler"; import { guessAndSetDMRoom } from "./Rooms"; import { upgradeRoom } from './utils/RoomUpgrade'; import UploadConfirmDialog from './components/views/dialogs/UploadConfirmDialog'; -import ErrorDialog from './components/views/dialogs/ErrorDialog'; import DevtoolsDialog from './components/views/dialogs/DevtoolsDialog'; import RoomUpgradeWarningDialog from "./components/views/dialogs/RoomUpgradeWarningDialog"; import InfoDialog from "./components/views/dialogs/InfoDialog"; @@ -245,21 +244,6 @@ export const Commands = [ }, category: CommandCategories.messages, }), - new Command({ - command: 'ddg', - args: '', - description: _td('Searches DuckDuckGo for results'), - runFn: function() { - // TODO Don't explain this away, actually show a search UI here. - Modal.createTrackedDialog('Slash Commands', '/ddg is not a command', ErrorDialog, { - title: _t('/ddg is not a command'), - description: _t('To use it, just wait for autocomplete results to load and tab through them.'), - }); - return success(); - }, - category: CommandCategories.actions, - hideCompletionAfterSpace: true, - }), new Command({ command: 'upgraderoom', args: '', diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx index b9295be3ed4..0e9dc1cf155 100644 --- a/src/TextForEvent.tsx +++ b/src/TextForEvent.tsx @@ -441,6 +441,15 @@ function textForPowerEvent(event: MatrixEvent): () => string | null { }); } +const onPinnedOrUnpinnedMessageClick = (messageId: string, roomId: string): void => { + defaultDispatcher.dispatch({ + action: 'view_room', + event_id: messageId, + highlighted: true, + room_id: roomId, + }); +}; + const onPinnedMessagesClick = (): void => { defaultDispatcher.dispatch({ action: Action.SetRightPanelPhase, @@ -452,17 +461,77 @@ const onPinnedMessagesClick = (): void => { function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => string | JSX.Element | null { if (!SettingsStore.getValue("feature_pinning")) return null; const senderName = event.sender ? event.sender.name : event.getSender(); + const roomId = event.getRoomId(); + + const pinned = event.getContent().pinned ?? []; + const previouslyPinned = event.getPrevContent().pinned ?? []; + const newlyPinned = pinned.filter(item => previouslyPinned.indexOf(item) < 0); + const newlyUnpinned = previouslyPinned.filter(item => pinned.indexOf(item) < 0); + + if (newlyPinned.length === 1 && newlyUnpinned.length === 0) { + // A single message was pinned, include a link to that message. + if (allowJSX) { + const messageId = newlyPinned.pop(); + + return () => ( + + { _t( + "%(senderName)s pinned a message to this room. See all pinned messages.", + { senderName }, + { + "a": (sub) => + onPinnedOrUnpinnedMessageClick(messageId, roomId)}> + { sub } + , + "b": (sub) => + + { sub } + , + }, + ) } + + ); + } + + return () => _t("%(senderName)s pinned a message to this room. See all pinned messages.", { senderName }); + } + + if (newlyUnpinned.length === 1 && newlyPinned.length === 0) { + // A single message was unpinned, include a link to that message. + if (allowJSX) { + const messageId = newlyUnpinned.pop(); + + return () => ( + + { _t( + "%(senderName)s unpinned a message from this room. See all pinned messages.", + { senderName }, + { + "a": (sub) => + onPinnedOrUnpinnedMessageClick(messageId, roomId)}> + { sub } + , + "b": (sub) => + + { sub } + , + }, + ) } + + ); + } + + return () => _t("%(senderName)s unpinned a message from this room. See all pinned messages.", { senderName }); + } if (allowJSX) { return () => ( - { - _t( - "%(senderName)s changed the pinned messages for the room.", - { senderName }, - { "a": (sub) => { sub } }, - ) - } + { _t( + "%(senderName)s changed the pinned messages for the room.", + { senderName }, + { "a": (sub) => { sub } }, + ) } ); } diff --git a/src/audio/ManagedPlayback.ts b/src/audio/ManagedPlayback.ts index bff6ce70887..5db07671f1d 100644 --- a/src/audio/ManagedPlayback.ts +++ b/src/audio/ManagedPlayback.ts @@ -26,7 +26,7 @@ export class ManagedPlayback extends Playback { } public async play(): Promise { - this.manager.playOnly(this); + this.manager.pauseAllExcept(this); return super.play(); } diff --git a/src/audio/Playback.ts b/src/audio/Playback.ts index 9dad828a791..03f3bad7609 100644 --- a/src/audio/Playback.ts +++ b/src/audio/Playback.ts @@ -117,6 +117,8 @@ export class Playback extends EventEmitter implements IDestroyable { } public destroy() { + // Dev note: It's critical that we call stop() during cleanup to ensure that downstream callers + // are aware of the final clock position before the user triggered an unload. // noinspection JSIgnoredPromiseFromCall - not concerned about being called async here this.stop(); this.removeAllListeners(); @@ -177,9 +179,12 @@ export class Playback extends EventEmitter implements IDestroyable { this.waveformObservable.update(this.resampledWaveform); - this.emit(PlaybackState.Stopped); // signal that we're not decoding anymore this.clock.flagLoadTime(); // must happen first because setting the duration fires a clock update this.clock.durationSeconds = this.element ? this.element.duration : this.audioBuf.duration; + + // Signal that we're not decoding anymore. This is done last to ensure the clock is updated for + // when the downstream callers try to use it. + this.emit(PlaybackState.Stopped); // signal that we're not decoding anymore } private onPlaybackEnd = async () => { diff --git a/src/audio/PlaybackClock.ts b/src/audio/PlaybackClock.ts index 712d1bfa946..5716d6ac2f2 100644 --- a/src/audio/PlaybackClock.ts +++ b/src/audio/PlaybackClock.ts @@ -89,9 +89,9 @@ export class PlaybackClock implements IDestroyable { return this.observable; } - private checkTime = () => { + private checkTime = (force = false) => { const now = this.timeSeconds; // calculated dynamically - if (this.lastCheck !== now) { + if (this.lastCheck !== now || force) { this.observable.update([now, this.durationSeconds]); this.lastCheck = now; } @@ -141,7 +141,7 @@ export class PlaybackClock implements IDestroyable { public syncTo(contextTime: number, clipTime: number) { this.clipStart = contextTime - clipTime; this.stopped = false; // count as a mid-stream pause (if we were stopped) - this.checkTime(); + this.checkTime(true); } public destroy() { diff --git a/src/audio/PlaybackManager.ts b/src/audio/PlaybackManager.ts index 58fa61df568..58c0b9b6241 100644 --- a/src/audio/PlaybackManager.ts +++ b/src/audio/PlaybackManager.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { DEFAULT_WAVEFORM, Playback } from "./Playback"; +import { DEFAULT_WAVEFORM, Playback, PlaybackState } from "./Playback"; import { ManagedPlayback } from "./ManagedPlayback"; /** @@ -34,12 +34,14 @@ export class PlaybackManager { } /** - * Stops all other playback instances. If no playback is provided, all instances - * are stopped. + * Pauses all other playback instances. If no playback is provided, all playing + * instances are paused. * @param playback Optional. The playback to leave untouched. */ - public playOnly(playback?: Playback) { - this.instances.filter(p => p !== playback).forEach(p => p.stop()); + public pauseAllExcept(playback?: Playback) { + this.instances + .filter(p => p !== playback && p.currentState === PlaybackState.Playing) + .forEach(p => p.pause()); } public destroyPlaybackInstance(playback: ManagedPlayback) { diff --git a/src/audio/PlaybackQueue.ts b/src/audio/PlaybackQueue.ts new file mode 100644 index 00000000000..611b88938aa --- /dev/null +++ b/src/audio/PlaybackQueue.ts @@ -0,0 +1,219 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixClient } from "matrix-js-sdk/src/client"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { Room } from "matrix-js-sdk/src/models/room"; +import { Playback, PlaybackState } from "./Playback"; +import { UPDATE_EVENT } from "../stores/AsyncStore"; +import { MatrixClientPeg } from "../MatrixClientPeg"; +import { arrayFastClone } from "../utils/arrays"; +import { PlaybackManager } from "./PlaybackManager"; +import { isVoiceMessage } from "../utils/EventUtils"; +import RoomViewStore from "../stores/RoomViewStore"; +import { EventType } from "matrix-js-sdk/src/@types/event"; + +/** + * Audio playback queue management for a given room. This keeps track of where the user + * was at for each playback, what order the playbacks were played in, and triggers subsequent + * playbacks. + * + * Currently this is only intended to be used by voice messages. + * + * The primary mechanics are: + * * Persisted clock state for each playback instance (tied to Event ID). + * * Limited memory of playback order (see code; not persisted). + * * Autoplay of next eligible playback instance. + */ +export class PlaybackQueue { + private static queues = new Map(); // keyed by room ID + + private playbacks = new Map(); // keyed by event ID + private clockStates = new Map(); // keyed by event ID + private playbackIdOrder: string[] = []; // event IDs, last == current + private currentPlaybackId: string; // event ID, broken out from above for ease of use + private recentFullPlays = new Set(); // event IDs + + constructor(private client: MatrixClient, private room: Room) { + this.loadClocks(); + + RoomViewStore.addListener(() => { + if (RoomViewStore.getRoomId() === this.room.roomId) { + // Reset the state of the playbacks before they start mounting and enqueuing updates. + // We reset the entirety of the queue, including order, to ensure the user isn't left + // confused with what order the messages are playing in. + this.currentPlaybackId = null; // this in particular stops autoplay when the room is switched to + this.recentFullPlays = new Set(); + this.playbackIdOrder = []; + } + }); + } + + public static forRoom(roomId: string): PlaybackQueue { + const cli = MatrixClientPeg.get(); + const room = cli.getRoom(roomId); + if (!room) throw new Error("Unknown room"); + if (PlaybackQueue.queues.has(room.roomId)) { + return PlaybackQueue.queues.get(room.roomId); + } + const queue = new PlaybackQueue(cli, room); + PlaybackQueue.queues.set(room.roomId, queue); + return queue; + } + + private persistClocks() { + localStorage.setItem( + `mx_voice_message_clocks_${this.room.roomId}`, + JSON.stringify(Array.from(this.clockStates.entries())), + ); + } + + private loadClocks() { + const val = localStorage.getItem(`mx_voice_message_clocks_${this.room.roomId}`); + if (!!val) { + this.clockStates = new Map(JSON.parse(val)); + } + } + + public unsortedEnqueue(mxEvent: MatrixEvent, playback: Playback) { + // We don't ever detach our listeners: we expect the Playback to clean up for us + this.playbacks.set(mxEvent.getId(), playback); + playback.on(UPDATE_EVENT, (state) => this.onPlaybackStateChange(playback, mxEvent, state)); + playback.clockInfo.liveData.onUpdate((clock) => this.onPlaybackClock(playback, mxEvent, clock)); + } + + private onPlaybackStateChange(playback: Playback, mxEvent: MatrixEvent, newState: PlaybackState) { + // Remember where the user got to in playback + const wasLastPlaying = this.currentPlaybackId === mxEvent.getId(); + if (newState === PlaybackState.Stopped && this.clockStates.has(mxEvent.getId()) && !wasLastPlaying) { + // noinspection JSIgnoredPromiseFromCall + playback.skipTo(this.clockStates.get(mxEvent.getId())); + } else if (newState === PlaybackState.Stopped) { + // Remove the now-useless clock for some space savings + this.clockStates.delete(mxEvent.getId()); + + if (wasLastPlaying) { + this.recentFullPlays.add(this.currentPlaybackId); + const orderClone = arrayFastClone(this.playbackIdOrder); + const last = orderClone.pop(); + if (last === this.currentPlaybackId) { + const next = orderClone.pop(); + if (next) { + const instance = this.playbacks.get(next); + if (!instance) { + console.warn( + "Voice message queue desync: Missing playback for next message: " + + `Current=${this.currentPlaybackId} Last=${last} Next=${next}`, + ); + } else { + this.playbackIdOrder = orderClone; + PlaybackManager.instance.pauseAllExcept(instance); + + // This should cause a Play event, which will re-populate our playback order + // and update our current playback ID. + // noinspection JSIgnoredPromiseFromCall + instance.play(); + } + } else { + // else no explicit next event, so find an event we haven't played that comes next. The live + // timeline is already most recent last, so we can iterate down that. + const timeline = arrayFastClone(this.room.getLiveTimeline().getEvents()); + let scanForVoiceMessage = false; + let nextEv: MatrixEvent; + for (const event of timeline) { + if (event.getId() === mxEvent.getId()) { + scanForVoiceMessage = true; + continue; + } + if (!scanForVoiceMessage) continue; + + if (!isVoiceMessage(event)) { + const evType = event.getType(); + if (evType !== EventType.RoomMessage && evType !== EventType.Sticker) { + continue; // Event can be skipped for automatic playback consideration + } + break; // Stop automatic playback: next useful event is not a voice message + } + + const havePlayback = this.playbacks.has(event.getId()); + const isRecentlyCompleted = this.recentFullPlays.has(event.getId()); + if (havePlayback && !isRecentlyCompleted) { + nextEv = event; + break; + } + } + if (!nextEv) { + // if we don't have anywhere to go, reset the recent playback queue so the user + // can start a new chain of playbacks. + this.recentFullPlays = new Set(); + this.playbackIdOrder = []; + } else { + this.playbackIdOrder = orderClone; + + const instance = this.playbacks.get(nextEv.getId()); + PlaybackManager.instance.pauseAllExcept(instance); + + // This should cause a Play event, which will re-populate our playback order + // and update our current playback ID. + // noinspection JSIgnoredPromiseFromCall + instance.play(); + } + } + } else { + console.warn( + "Voice message queue desync: Expected playback stop to be last in order. " + + `Current=${this.currentPlaybackId} Last=${last} EventID=${mxEvent.getId()}`, + ); + } + } + } + + if (newState === PlaybackState.Playing) { + const order = this.playbackIdOrder; + if (this.currentPlaybackId !== mxEvent.getId() && !!this.currentPlaybackId) { + if (order.length === 0 || order[order.length - 1] !== this.currentPlaybackId) { + const lastInstance = this.playbacks.get(this.currentPlaybackId); + if ( + lastInstance.currentState === PlaybackState.Playing + || lastInstance.currentState === PlaybackState.Paused + ) { + order.push(this.currentPlaybackId); + } + } + } + + this.currentPlaybackId = mxEvent.getId(); + if (order.length === 0 || order[order.length - 1] !== this.currentPlaybackId) { + order.push(this.currentPlaybackId); + } + } + + // Only persist clock information on pause/stop (end) to avoid overwhelming the storage. + // This should get triggered from normal voice message component unmount due to the playback + // stopping itself for cleanup. + if (newState === PlaybackState.Paused || newState === PlaybackState.Stopped) { + this.persistClocks(); + } + } + + private onPlaybackClock(playback: Playback, mxEvent: MatrixEvent, clocks: number[]) { + if (playback.currentState === PlaybackState.Decoding) return; // ignore pre-ready values + + if (playback.currentState !== PlaybackState.Stopped) { + this.clockStates.set(mxEvent.getId(), clocks[0]); // [0] is the current seek position + } + } +} diff --git a/src/audio/RecorderWorklet.ts b/src/audio/RecorderWorklet.ts index 2d1bb0bcd24..73b053db936 100644 --- a/src/audio/RecorderWorklet.ts +++ b/src/audio/RecorderWorklet.ts @@ -45,7 +45,13 @@ class MxVoiceWorklet extends AudioWorkletProcessor { process(inputs, outputs, parameters) { const currentSecond = roundTimeToTargetFreq(currentTime); - if (currentSecond === this.nextAmplitudeSecond) { + // We special case the first ping because there's a fairly good chance that we'll miss the zeroth + // update. Firefox for instance takes 0.06 seconds (roughly) to call this function for the first + // time. Edge and Chrome occasionally lag behind too, but for the most part are on time. + // + // When this doesn't work properly we end up producing a waveform of nulls and no live preview + // of the recorded message. + if (currentSecond === this.nextAmplitudeSecond || this.nextAmplitudeSecond === 0) { // We're expecting exactly one mono input source, so just grab the very first frame of // samples for the analysis. const monoChan = inputs[0][0]; diff --git a/src/autocomplete/Autocompleter.ts b/src/autocomplete/Autocompleter.ts index acc78465108..4c9e82f2900 100644 --- a/src/autocomplete/Autocompleter.ts +++ b/src/autocomplete/Autocompleter.ts @@ -20,7 +20,6 @@ import { Room } from 'matrix-js-sdk/src/models/room'; import CommandProvider from './CommandProvider'; import CommunityProvider from './CommunityProvider'; -import DuckDuckGoProvider from './DuckDuckGoProvider'; import RoomProvider from './RoomProvider'; import UserProvider from './UserProvider'; import EmojiProvider from './EmojiProvider'; @@ -55,7 +54,6 @@ const PROVIDERS = [ EmojiProvider, NotifProvider, CommandProvider, - DuckDuckGoProvider, ]; if (SpaceStore.spacesEnabled) { diff --git a/src/autocomplete/CommandProvider.tsx b/src/autocomplete/CommandProvider.tsx index d56adc026c3..143b7e4cdc6 100644 --- a/src/autocomplete/CommandProvider.tsx +++ b/src/autocomplete/CommandProvider.tsx @@ -53,7 +53,7 @@ export default class CommandProvider extends AutocompleteProvider { // The input looks like a command with arguments, perform exact match const name = command[1].substr(1); // strip leading `/` if (CommandMap.has(name) && CommandMap.get(name).isEnabled()) { - // some commands, namely `me` and `ddg` don't suit having the usage shown whilst typing their arguments + // some commands, namely `me` don't suit having the usage shown whilst typing their arguments if (CommandMap.get(name).hideCompletionAfterSpace) return []; matches = [CommandMap.get(name)]; } @@ -95,7 +95,7 @@ export default class CommandProvider extends AutocompleteProvider { renderCompletions(completions: React.ReactNode[]): React.ReactNode { return (
diff --git a/src/autocomplete/DuckDuckGoProvider.tsx b/src/autocomplete/DuckDuckGoProvider.tsx deleted file mode 100644 index c41a91b97fe..00000000000 --- a/src/autocomplete/DuckDuckGoProvider.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/* -Copyright 2016 Aviral Dasgupta -Copyright 2017 Vector Creations Ltd -Copyright 2017, 2018 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import { _t } from '../languageHandler'; -import AutocompleteProvider from './AutocompleteProvider'; - -import { TextualCompletion } from './Components'; -import { ICompletion, ISelectionRange } from "./Autocompleter"; - -const DDG_REGEX = /\/ddg\s+(.+)$/g; -const REFERRER = 'vector'; - -export default class DuckDuckGoProvider extends AutocompleteProvider { - constructor() { - super(DDG_REGEX); - } - - static getQueryUri(query: string) { - return `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}` - + `&format=json&no_redirect=1&no_html=1&t=${encodeURIComponent(REFERRER)}`; - } - - async getCompletions( - query: string, - selection: ISelectionRange, - force = false, - limit = -1, - ): Promise { - const { command, range } = this.getCurrentCommand(query, selection); - if (!query || !command) { - return []; - } - - const response = await fetch(DuckDuckGoProvider.getQueryUri(command[1]), { - method: 'GET', - }); - const json = await response.json(); - const maxLength = limit > -1 ? limit : json.Results.length; - const results = json.Results.slice(0, maxLength).map((result) => { - return { - completion: result.Text, - component: ( - - ), - range, - }; - }); - if (json.Answer) { - results.unshift({ - completion: json.Answer, - component: ( - - ), - range, - }); - } - if (json.RelatedTopics && json.RelatedTopics.length > 0) { - results.unshift({ - completion: json.RelatedTopics[0].Text, - component: ( - - ), - range, - }); - } - if (json.AbstractText) { - results.unshift({ - completion: json.AbstractText, - component: ( - - ), - range, - }); - } - return results; - } - - getName() { - return '🔍 ' + _t('Results from DuckDuckGo'); - } - - renderCompletions(completions: React.ReactNode[]): React.ReactNode { - return ( -
- { completions } -
- ); - } -} diff --git a/src/components/structures/CallEventGrouper.ts b/src/components/structures/CallEventGrouper.ts index b48bb32efe1..84e004d1de0 100644 --- a/src/components/structures/CallEventGrouper.ts +++ b/src/components/structures/CallEventGrouper.ts @@ -25,6 +25,7 @@ import defaultDispatcher from "../../dispatcher/dispatcher"; export enum CallEventGrouperEvent { StateChanged = "state_changed", SilencedChanged = "silenced_changed", + LengthChanged = "length_changed", } const CONNECTING_STATES = [ @@ -104,8 +105,12 @@ export default class CallEventGrouper extends EventEmitter { return ![...this.events].some((event) => event.sender?.userId === MatrixClientPeg.get().getUserId()); } - private get callId(): string { - return [...this.events][0].getContent().call_id; + private get callId(): string | undefined { + return [...this.events][0]?.getContent()?.call_id; + } + + private get roomId(): string | undefined { + return [...this.events][0]?.getRoomId(); } private onSilencedCallsChanged = () => { @@ -113,19 +118,29 @@ export default class CallEventGrouper extends EventEmitter { this.emit(CallEventGrouperEvent.SilencedChanged, newState); }; + private onLengthChanged = (length: number): void => { + this.emit(CallEventGrouperEvent.LengthChanged, length); + }; + public answerCall = () => { - this.call?.answer(); + defaultDispatcher.dispatch({ + action: 'answer', + room_id: this.roomId, + }); }; public rejectCall = () => { - this.call?.reject(); + defaultDispatcher.dispatch({ + action: 'reject', + room_id: this.roomId, + }); }; public callBack = () => { defaultDispatcher.dispatch({ action: 'place_call', type: this.isVoice ? CallType.Voice : CallType.Video, - room_id: [...this.events][0]?.getRoomId(), + room_id: this.roomId, }); }; @@ -139,6 +154,7 @@ export default class CallEventGrouper extends EventEmitter { private setCallListeners() { if (!this.call) return; this.call.addListener(CallEvent.State, this.setState); + this.call.addListener(CallEvent.LengthChanged, this.onLengthChanged); } private setState = () => { diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index 332b6cd3182..4bd528b8938 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -322,10 +322,16 @@ export class ContextMenu extends React.PureComponent { const menuClasses = classNames({ 'mx_ContextualMenu': true, - 'mx_ContextualMenu_left': !hasChevron && position.left, - 'mx_ContextualMenu_right': !hasChevron && position.right, - 'mx_ContextualMenu_top': !hasChevron && position.top, - 'mx_ContextualMenu_bottom': !hasChevron && position.bottom, + /** + * In some cases we may get the number of 0, which still means that we're supposed to properly + * add the specific position class, but as it was falsy things didn't work as intended. + * In addition, defensively check for counter cases where we may get more than one value, + * even if we shouldn't. + */ + 'mx_ContextualMenu_left': !hasChevron && position.left !== undefined && !position.right, + 'mx_ContextualMenu_right': !hasChevron && position.right !== undefined && !position.left, + 'mx_ContextualMenu_top': !hasChevron && position.top !== undefined && !position.bottom, + 'mx_ContextualMenu_bottom': !hasChevron && position.bottom !== undefined && !position.top, 'mx_ContextualMenu_withChevron_left': chevronFace === ChevronFace.Left, 'mx_ContextualMenu_withChevron_right': chevronFace === ChevronFace.Right, 'mx_ContextualMenu_withChevron_top': chevronFace === ChevronFace.Top, diff --git a/src/components/structures/LeftPanelWidget.tsx b/src/components/structures/LeftPanelWidget.tsx index 144c0e30511..331e4283552 100644 --- a/src/components/structures/LeftPanelWidget.tsx +++ b/src/components/structures/LeftPanelWidget.tsx @@ -115,7 +115,7 @@ const LeftPanelWidget: React.FC = () => { aria-expanded={expanded} aria-level={1} onClick={() => { - setExpanded(e => !e); + setExpanded(!expanded); }} > { this.setStateForNewView({ view: Views.LOGGED_IN, justRegistered, + currentRoomId: null, }); this.setPage(PageTypes.HomePage); this.notifyNewScreen('home'); diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 1691d90651d..7da0b754076 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -173,6 +173,8 @@ interface IProps { onUnfillRequest?(backwards: boolean, scrollToken: string): void; getRelationsForEvent?(eventId: string, relationType: string, eventType: string): Relations; + + hideThreadedMessages?: boolean; } interface IState { @@ -265,6 +267,9 @@ export default class MessagePanel extends React.Component { componentDidMount() { this.calculateRoomMembersCount(); this.props.room?.on("RoomState.members", this.calculateRoomMembersCount); + if (SettingsStore.getValue("feature_thread")) { + this.props.room?.getThreads().forEach(thread => thread.fetchReplyChain()); + } this.isMounted = true; } @@ -443,6 +448,12 @@ export default class MessagePanel extends React.Component { // Always show highlighted event if (this.props.highlightedEventId === mxEv.getId()) return true; + if (mxEv.replyInThread + && this.props.hideThreadedMessages + && SettingsStore.getValue("feature_thread")) { + return false; + } + return !shouldHideEvent(mxEv, this.context); } diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 95d70e913a4..67634c63d22 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -45,17 +45,21 @@ import GroupRoomInfo from "../views/groups/GroupRoomInfo"; import UserInfo from "../views/right_panel/UserInfo"; import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo"; import FilePanel from "./FilePanel"; +import ThreadView from "./ThreadView"; +import ThreadPanel from "./ThreadPanel"; import NotificationPanel from "./NotificationPanel"; import ResizeNotifier from "../../utils/ResizeNotifier"; import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard"; import { throttle } from 'lodash'; import SpaceStore from "../../stores/SpaceStore"; +import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; interface IProps { room?: Room; // if showing panels for a given room, this is set groupId?: string; // if showing panels for a given group, this is set user?: User; // used if we know the user ahead of opening the panel resizeNotifier: ResizeNotifier; + permalinkCreator?: RoomPermalinkCreator; } interface IState { @@ -309,6 +313,22 @@ export default class RightPanel extends React.Component { panel = ; break; + case RightPanelPhases.ThreadView: + panel = ; + break; + + case RightPanelPhases.ThreadPanel: + panel = ; + break; + case RightPanelPhases.RoomSummary: panel = ; break; diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx index 8d8609d1cfd..3c5f99cc7de 100644 --- a/src/components/structures/RoomDirectory.tsx +++ b/src/components/structures/RoomDirectory.tsx @@ -347,7 +347,7 @@ export default class RoomDirectory extends React.Component { }); } - private onRoomClicked = (room: IPublicRoomsChunkRoom, ev: ButtonEvent) => { + private onRoomClicked = (room: IPublicRoomsChunkRoom, ev: React.MouseEvent) => { // If room was shift-clicked, remove it from the room directory if (ev.shiftKey && !this.state.selectedCommunityId) { ev.preventDefault(); diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 474b99262da..8223c12e77e 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1867,7 +1867,7 @@ export default class RoomView extends React.Component { isRoomEncrypted={this.context.isRoomEncrypted(this.state.room.roomId)} />; } else if (showRoomUpgradeBar) { - aux = ; + aux = ; } else if (myMembership !== "join") { // We do have a room object for this room, but we're not currently in it. // We may have a 3rd party invite to it. @@ -2042,7 +2042,6 @@ export default class RoomView extends React.Component { highlight={this.state.room.getUnreadNotificationCount(NotificationCountType.Highlight) > 0} numUnreadMessages={this.state.numUnreadMessages} onScrollToBottomClick={this.jumpToLiveTimeline} - roomId={this.state.roomId} />); } @@ -2052,7 +2051,10 @@ export default class RoomView extends React.Component { const showRightPanel = this.state.room && this.state.showRightPanel; const rightPanel = showRightPanel - ? + ? : null; const timelineClasses = classNames("mx_RoomView_timeline", { diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 767e0999c36..6bb04334486 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -16,7 +16,7 @@ limitations under the License. import React, { RefObject, useContext, useRef, useState } from "react"; import { EventType } from "matrix-js-sdk/src/@types/event"; -import { Preset, JoinRule } from "matrix-js-sdk/src/@types/partials"; +import { JoinRule, Preset } from "matrix-js-sdk/src/@types/partials"; import { Room } from "matrix-js-sdk/src/models/room"; import { EventSubscription } from "fbemitter"; @@ -89,7 +89,7 @@ interface IProps { interface IState { phase: Phase; - createdRooms?: boolean; // internal state for the creation wizard + firstRoomId?: string; // internal state for the creation wizard showRightPanel: boolean; myMembership: string; } @@ -496,6 +496,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => { onChange={ev => setRoomName(i, ev.target.value)} autoFocus={i === 2} disabled={busy} + autoComplete="off" />; }); @@ -505,11 +506,12 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => { setError(""); setBusy(true); try { + const isPublic = space.getJoinRule() === JoinRule.Public; const filteredRoomNames = roomNames.map(name => name.trim()).filter(Boolean); - await Promise.all(filteredRoomNames.map(name => { + const roomIds = await Promise.all(filteredRoomNames.map(name => { return createRoom({ createOpts: { - preset: space.getJoinRule() === "public" ? Preset.PublicChat : Preset.PrivateChat, + preset: isPublic ? Preset.PublicChat : Preset.PrivateChat, name, }, spinner: false, @@ -517,9 +519,11 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => { andView: false, inlineErrors: true, parentSpace: space, + joinRule: !isPublic ? JoinRule.Restricted : undefined, + suggested: true, }); })); - onFinished(filteredRoomNames.length > 0); + onFinished(roomIds[0]); } catch (e) { console.error("Failed to create initial space rooms", e); setError(_t("Failed to create initial space rooms")); @@ -529,7 +533,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => { let onClick = (ev) => { ev.preventDefault(); - onFinished(false); + onFinished(); }; let buttonLabel = _t("Skip for now"); if (roomNames.some(name => name.trim())) { @@ -584,7 +588,11 @@ const SpaceAddExistingRooms = ({ space, onFinished }) => {
; }; -const SpaceSetupPublicShare = ({ justCreatedOpts, space, onFinished, createdRooms }) => { +interface ISpaceSetupPublicShareProps extends Pick { + onFinished(): void; +} + +const SpaceSetupPublicShare = ({ justCreatedOpts, space, onFinished, firstRoomId }: ISpaceSetupPublicShareProps) => { return

{ _t("Share %(name)s", { name: justCreatedOpts?.createOpts?.name || space.name, @@ -597,7 +605,7 @@ const SpaceSetupPublicShare = ({ justCreatedOpts, space, onFinished, createdRoom
- { createdRooms ? _t("Go to my first room") : _t("Go to my space") } + { firstRoomId ? _t("Go to my first room") : _t("Go to my space") }

; @@ -805,6 +813,11 @@ export default class SpaceRoomView extends React.PureComponent { }; private onAction = (payload: ActionPayload) => { + if (payload.action === "view_room" && payload.room_id === this.props.space.roomId) { + this.setState({ phase: Phase.Landing }); + return; + } + if (payload.action !== Action.ViewUser && payload.action !== "view_3pid_invite") return; if (payload.action === Action.ViewUser && payload.member) { @@ -835,35 +848,10 @@ export default class SpaceRoomView extends React.PureComponent { }; private goToFirstRoom = async () => { - // TODO actually go to the first room - - const childRooms = SpaceStore.instance.getChildRooms(this.props.space.roomId); - if (childRooms.length) { - const room = childRooms[0]; - defaultDispatcher.dispatch({ - action: "view_room", - room_id: room.roomId, - }); - return; - } - - let suggestedRooms = SpaceStore.instance.suggestedRooms; - if (SpaceStore.instance.activeSpace !== this.props.space) { - // the space store has the suggested rooms loaded for a different space, fetch the right ones - suggestedRooms = (await SpaceStore.instance.fetchSuggestedRooms(this.props.space, 1)); - } - - if (suggestedRooms.length) { - const room = suggestedRooms[0]; + if (this.state.firstRoomId) { defaultDispatcher.dispatch({ action: "view_room", - room_id: room.room_id, - room_alias: room.canonical_alias || room.aliases?.[0], - via_servers: room.viaServers, - oobData: { - avatarUrl: room.avatar_url, - name: room.name || room.canonical_alias || room.aliases?.[0] || _t("Empty room"), - }, + room_id: this.state.firstRoomId, }); return; } @@ -893,14 +881,14 @@ export default class SpaceRoomView extends React.PureComponent { _t("Let's create a room for each of them.") + "\n" + _t("You can add more later too, including already existing ones.") } - onFinished={(createdRooms: boolean) => this.setState({ phase: Phase.PublicShare, createdRooms })} + onFinished={(firstRoomId: string) => this.setState({ phase: Phase.PublicShare, firstRoomId })} />; case Phase.PublicShare: return ; case Phase.PrivateScope: @@ -922,7 +910,7 @@ export default class SpaceRoomView extends React.PureComponent { title={_t("What projects are you working on?")} description={_t("We'll create rooms for each of them. " + "You can add more later too, including already existing ones.")} - onFinished={(createdRooms: boolean) => this.setState({ phase: Phase.Landing, createdRooms })} + onFinished={(firstRoomId: string) => this.setState({ phase: Phase.Landing, firstRoomId })} />; case Phase.PrivateExistingRooms: return void; + resizeNotifier: ResizeNotifier; +} + +interface IState { + threads?: Thread[]; +} + +@replaceableComponent("structures.ThreadView") +export default class ThreadPanel extends React.Component { + private room: Room; + + constructor(props: IProps) { + super(props); + this.room = MatrixClientPeg.get().getRoom(this.props.roomId); + } + + public componentDidMount(): void { + this.room.on("Thread.update", this.onThreadEventReceived); + this.room.on("Thread.ready", this.onThreadEventReceived); + } + + public componentWillUnmount(): void { + this.room.removeListener("Thread.update", this.onThreadEventReceived); + this.room.removeListener("Thread.ready", this.onThreadEventReceived); + } + + private onThreadEventReceived = () => this.updateThreads(); + + private updateThreads = (callback?: () => void): void => { + this.setState({ + threads: this.room.getThreads(), + }, callback); + }; + + private renderEventTile(event: MatrixEvent): JSX.Element { + return ; + } + + public render(): JSX.Element { + return ( + + { + this.state?.threads.map((thread: Thread) => { + if (thread.ready) { + return this.renderEventTile(thread.rootEvent); + } + }) + } + + ); + } +} diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx new file mode 100644 index 00000000000..134f018aed0 --- /dev/null +++ b/src/components/structures/ThreadView.tsx @@ -0,0 +1,152 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { MatrixEvent, Room } from 'matrix-js-sdk/src'; +import { Thread } from 'matrix-js-sdk/src/models/thread'; + +import BaseCard from "../views/right_panel/BaseCard"; +import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; +import { replaceableComponent } from "../../utils/replaceableComponent"; + +import ResizeNotifier from '../../utils/ResizeNotifier'; +import { TileShape } from '../views/rooms/EventTile'; +import MessageComposer from '../views/rooms/MessageComposer'; +import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; +import { Layout } from '../../settings/Layout'; +import TimelinePanel from './TimelinePanel'; +import dis from "../../dispatcher/dispatcher"; +import { ActionPayload } from '../../dispatcher/payloads'; +import { SetRightPanelPhasePayload } from '../../dispatcher/payloads/SetRightPanelPhasePayload'; +import { Action } from '../../dispatcher/actions'; +import { MatrixClientPeg } from '../../MatrixClientPeg'; + +interface IProps { + room: Room; + onClose: () => void; + resizeNotifier: ResizeNotifier; + mxEvent: MatrixEvent; + permalinkCreator?: RoomPermalinkCreator; +} + +interface IState { + replyToEvent?: MatrixEvent; + thread?: Thread; +} + +@replaceableComponent("structures.ThreadView") +export default class ThreadView extends React.Component { + private dispatcherRef: string; + + constructor(props: IProps) { + super(props); + this.state = {}; + } + + public componentDidMount(): void { + this.setupThread(this.props.mxEvent); + this.dispatcherRef = dis.register(this.onAction); + } + + public componentWillUnmount(): void { + this.teardownThread(); + dis.unregister(this.dispatcherRef); + } + + public componentDidUpdate(prevProps) { + if (prevProps.mxEvent !== this.props.mxEvent) { + this.teardownThread(); + this.setupThread(this.props.mxEvent); + } + + if (prevProps.room !== this.props.room) { + dis.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomSummary, + }); + } + } + + private onAction = (payload: ActionPayload): void => { + if (payload.phase == RightPanelPhases.ThreadView && payload.event) { + if (payload.event !== this.props.mxEvent) { + this.teardownThread(); + this.setupThread(payload.event); + } + } + }; + + private setupThread = (mxEv: MatrixEvent) => { + let thread = mxEv.getThread(); + if (!thread) { + const client = MatrixClientPeg.get(); + thread = new Thread([mxEv], this.props.room, client); + mxEv.setThread(thread); + } + thread.on("Thread.update", this.updateThread); + thread.once("Thread.ready", this.updateThread); + this.updateThread(thread); + }; + + private teardownThread = () => { + if (this.state.thread) { + this.state.thread.removeListener("Thread.update", this.updateThread); + this.state.thread.removeListener("Thread.ready", this.updateThread); + } + }; + + private updateThread = (thread?: Thread) => { + if (thread) { + this.setState({ thread }); + } else { + this.forceUpdate(); + } + }; + + public render(): JSX.Element { + return ( + + { this.state.thread && ( + empty} + alwaysShowTimestamps={true} + layout={Layout.Group} + hideThreadedMessages={false} + /> + ) } + + + ); + } +} diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index f62676a4fcd..0dfb5c414a3 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -47,11 +47,14 @@ import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks"; import Spinner from "../views/elements/Spinner"; import EditorStateTransfer from '../../utils/EditorStateTransfer'; import ErrorDialog from '../views/dialogs/ErrorDialog'; +import { debounce } from 'lodash'; const PAGINATE_SIZE = 20; const INITIAL_SIZE = 20; const READ_RECEIPT_INTERVAL_MS = 500; +const READ_MARKER_DEBOUNCE_MS = 100; + const DEBUG = false; let debuglog = function(...s: any[]) {}; @@ -126,6 +129,8 @@ interface IProps { // callback which is called when we wish to paginate the timeline window. onPaginationRequest?(timelineWindow: TimelineWindow, direction: string, size: number): Promise; + + hideThreadedMessages?: boolean; } interface IState { @@ -214,6 +219,7 @@ class TimelinePanel extends React.Component { timelineCap: Number.MAX_VALUE, className: 'mx_RoomView_messagePanel', sendReadReceiptOnLoad: true, + hideThreadedMessages: true, }; private lastRRSentEventId: string = undefined; @@ -472,22 +478,35 @@ class TimelinePanel extends React.Component { } if (this.props.manageReadMarkers) { - const rmPosition = this.getReadMarkerPosition(); - // we hide the read marker when it first comes onto the screen, but if - // it goes back off the top of the screen (presumably because the user - // clicks on the 'jump to bottom' button), we need to re-enable it. - if (rmPosition < 0) { - this.setState({ readMarkerVisible: true }); - } - - // if read marker position goes between 0 and -1/1, - // (and user is active), switch timeout - const timeout = this.readMarkerTimeout(rmPosition); - // NO-OP when timeout already has set to the given value - this.readMarkerActivityTimer.changeTimeout(timeout); + this.doManageReadMarkers(); } }; + /* + * Debounced function to manage read markers because we don't need to + * do this on every tiny scroll update. It also sets state which causes + * a component update, which can in turn reset the scroll position, so + * it's important we allow the browser to scroll a bit before running this + * (hence trailing edge only and debounce rather than throttle because + * we really only need to update this once the user has finished scrolling, + * not periodically while they scroll). + */ + private doManageReadMarkers = debounce(() => { + const rmPosition = this.getReadMarkerPosition(); + // we hide the read marker when it first comes onto the screen, but if + // it goes back off the top of the screen (presumably because the user + // clicks on the 'jump to bottom' button), we need to re-enable it. + if (rmPosition < 0) { + this.setState({ readMarkerVisible: true }); + } + + // if read marker position goes between 0 and -1/1, + // (and user is active), switch timeout + const timeout = this.readMarkerTimeout(rmPosition); + // NO-OP when timeout already has set to the given value + this.readMarkerActivityTimer.changeTimeout(timeout); + }, READ_MARKER_DEBOUNCE_MS, { leading: false, trailing: true }); + private onAction = (payload: ActionPayload): void => { switch (payload.action) { case "ignore_state_changed": @@ -1511,6 +1530,7 @@ class TimelinePanel extends React.Component { showReactions={this.props.showReactions} layout={this.props.layout} enableFlair={SettingsStore.getValue(UIFeature.Flair)} + hideThreadedMessages={this.props.hideThreadedMessages} /> ); } diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index f978a6cded0..24b47bfa032 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -31,6 +31,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm'; import { IValidationResult } from "../../views/elements/Validation"; +import InlineSpinner from '../../views/elements/InlineSpinner'; enum Phase { // Show the forgot password inputs @@ -66,13 +67,14 @@ interface IState { serverDeadError: string; passwordFieldValid: boolean; + currentHttpRequest?: Promise; } @replaceableComponent("structures.auth.ForgotPassword") export default class ForgotPassword extends React.Component { private reset: PasswordReset; - state = { + state: IState = { phase: Phase.Forgot, email: "", password: "", @@ -148,8 +150,10 @@ export default class ForgotPassword extends React.Component { console.error("onVerify called before submitPasswordReset!"); return; } + if (this.state.currentHttpRequest) return; + try { - await this.reset.checkEmailLinkClicked(); + await this.handleHttpRequest(this.reset.checkEmailLinkClicked()); this.setState({ phase: Phase.Done }); } catch (err) { this.showErrorDialog(err.message); @@ -158,9 +162,10 @@ export default class ForgotPassword extends React.Component { private onSubmitForm = async (ev: React.FormEvent): Promise => { ev.preventDefault(); + if (this.state.currentHttpRequest) return; // refresh the server errors, just in case the server came back online - await this.checkServerLiveliness(this.props.serverConfig); + await this.handleHttpRequest(this.checkServerLiveliness(this.props.serverConfig)); await this['password_field'].validate({ allowEmpty: false }); @@ -221,6 +226,17 @@ export default class ForgotPassword extends React.Component { }); } + private handleHttpRequest(request: Promise): Promise { + this.setState({ + currentHttpRequest: request, + }); + return request.finally(() => { + this.setState({ + currentHttpRequest: undefined, + }); + }); + } + renderForgot() { const Field = sdk.getComponent('elements.Field'); @@ -320,6 +336,9 @@ export default class ForgotPassword extends React.Component { type="button" onClick={this.onVerify} value={_t('I have verified my email address')} /> + { this.state.currentHttpRequest && ( +
) + } ; } @@ -357,6 +376,8 @@ export default class ForgotPassword extends React.Component { case Phase.Done: resetPasswordJsx = this.renderDone(); break; + default: + resetPasswordJsx =
; } return ( diff --git a/src/components/views/audio_messages/Clock.tsx b/src/components/views/audio_messages/Clock.tsx index cb1a179f2eb..69244cc5adc 100644 --- a/src/components/views/audio_messages/Clock.tsx +++ b/src/components/views/audio_messages/Clock.tsx @@ -15,34 +15,30 @@ limitations under the License. */ import React from "react"; +import { formatSeconds } from "../../../DateUtils"; import { replaceableComponent } from "../../../utils/replaceableComponent"; export interface IProps { seconds: number; } -interface IState { -} - /** * Simply converts seconds into minutes and seconds. Note that hours will not be * displayed, making it possible to see "82:29". */ @replaceableComponent("views.audio_messages.Clock") -export default class Clock extends React.Component { +export default class Clock extends React.Component { public constructor(props) { super(props); } - shouldComponentUpdate(nextProps: Readonly, nextState: Readonly, nextContext: any): boolean { + shouldComponentUpdate(nextProps: Readonly): boolean { const currentFloor = Math.floor(this.props.seconds); const nextFloor = Math.floor(nextProps.seconds); return currentFloor !== nextFloor; } public render() { - const minutes = Math.floor(this.props.seconds / 60).toFixed(0).padStart(2, '0'); - const seconds = Math.floor(this.props.seconds % 60).toFixed(0).padStart(2, '0'); // hide millis - return { minutes }:{ seconds }; + return { formatSeconds(this.props.seconds) }; } } diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx index 11c24a59817..3c734705b77 100644 --- a/src/components/views/avatars/MemberAvatar.tsx +++ b/src/components/views/avatars/MemberAvatar.tsx @@ -36,6 +36,7 @@ interface IProps extends Omit, "name" | // Whether the onClick of the avatar should be overriden to dispatch `Action.ViewUser` viewUserOnClick?: boolean; title?: string; + style?: any; } interface IState { diff --git a/src/components/views/dialogs/CreateSubspaceDialog.tsx b/src/components/views/dialogs/CreateSubspaceDialog.tsx index 03927c7d62c..d80245918fb 100644 --- a/src/components/views/dialogs/CreateSubspaceDialog.tsx +++ b/src/components/views/dialogs/CreateSubspaceDialog.tsx @@ -79,7 +79,7 @@ const CreateSubspaceDialog: React.FC = ({ space, onAddExistingSpaceClick } try { - await createSpace(name, joinRule === JoinRule.Public, alias, topic, avatar, {}, { parentSpace }); + await createSpace(name, joinRule === JoinRule.Public, alias, topic, avatar, {}, { parentSpace, joinRule }); onFinished(true); } catch (e) { diff --git a/src/components/views/dialogs/LeaveSpaceDialog.tsx b/src/components/views/dialogs/LeaveSpaceDialog.tsx index 6e1e798e9df..3a8cd53945d 100644 --- a/src/components/views/dialogs/LeaveSpaceDialog.tsx +++ b/src/components/views/dialogs/LeaveSpaceDialog.tsx @@ -80,7 +80,7 @@ const SpaceChildPicker = ({ filterPlaceholder, rooms, selected, onChange }) => { const LeaveRoomsPicker = ({ space, spaceChildren, roomsToLeave, setRoomsToLeave }) => { const selected = useMemo(() => new Set(roomsToLeave), [roomsToLeave]); - const [state, setState] = useState(RoomsToLeave.All); + const [state, setState] = useState(RoomsToLeave.None); useEffect(() => { if (state === RoomsToLeave.All) { @@ -97,11 +97,11 @@ const LeaveRoomsPicker = ({ space, spaceChildren, roomsToLeave, setRoomsToLeave onChange={setState} definitions={[ { - value: RoomsToLeave.All, - label: _t("Leave all rooms and spaces"), - }, { value: RoomsToLeave.None, label: _t("Don't leave any"), + }, { + value: RoomsToLeave.All, + label: _t("Leave all rooms and spaces"), }, { value: RoomsToLeave.Specific, label: _t("Leave specific rooms and spaces"), diff --git a/src/components/views/dialogs/ShareDialog.tsx b/src/components/views/dialogs/ShareDialog.tsx index 85e9c6f1921..80c0543c4e2 100644 --- a/src/components/views/dialogs/ShareDialog.tsx +++ b/src/components/views/dialogs/ShareDialog.tsx @@ -158,7 +158,7 @@ export default class ShareDialog extends React.PureComponent { if (this.state.linkSpecificEvent) { matrixToUrl = this.props.permalinkCreator.forEvent(this.props.target.getId()); } else { - matrixToUrl = this.props.permalinkCreator.forRoom(); + matrixToUrl = this.props.permalinkCreator.forShareableRoom(); } } return matrixToUrl; diff --git a/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx b/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx index ebeab191b18..366adb887c1 100644 --- a/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx +++ b/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx @@ -1,5 +1,5 @@ /* -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2020 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import { _t } from "../../../languageHandler"; import { IDialogProps } from "./IDialogProps"; import { Capability, + isTimelineCapability, Widget, WidgetEventCapability, WidgetKind, @@ -30,14 +31,7 @@ import DialogButtons from "../elements/DialogButtons"; import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import { CapabilityText } from "../../../widgets/CapabilityText"; import { replaceableComponent } from "../../../utils/replaceableComponent"; - -export function getRememberedCapabilitiesForWidget(widget: Widget): Capability[] { - return JSON.parse(localStorage.getItem(`widget_${widget.id}_approved_caps`) || "[]"); -} - -function setRememberedCapabilitiesForWidget(widget: Widget, caps: Capability[]) { - localStorage.setItem(`widget_${widget.id}_approved_caps`, JSON.stringify(caps)); -} +import { lexicographicCompare } from "matrix-js-sdk/src/utils"; interface IProps extends IDialogProps { requestedCapabilities: Set; @@ -95,14 +89,24 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent< }; private closeAndTryRemember(approved: Capability[]) { - if (this.state.rememberSelection) { - setRememberedCapabilitiesForWidget(this.props.widget, approved); - } - this.props.onFinished({ approved }); + this.props.onFinished({ approved, remember: this.state.rememberSelection }); } public render() { - const checkboxRows = Object.entries(this.state.booleanStates).map(([cap, isChecked], i) => { + // We specifically order the timeline capabilities down to the bottom. The capability text + // generation cares strongly about this. + const orderedCapabilities = Object.entries(this.state.booleanStates).sort(([capA], [capB]) => { + const isTimelineA = isTimelineCapability(capA); + const isTimelineB = isTimelineCapability(capB); + + if (!isTimelineA && !isTimelineB) return lexicographicCompare(capA, capB); + if (isTimelineA && !isTimelineB) return 1; + if (!isTimelineA && isTimelineB) return -1; + if (isTimelineA && isTimelineB) return lexicographicCompare(capA, capB); + + return 0; + }); + const checkboxRows = orderedCapabilities.map(([cap, isChecked], i) => { const text = CapabilityText.for(cap, this.props.widgetKind); const byline = text.byline ? { text.byline } diff --git a/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.js b/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx similarity index 71% rename from src/components/views/dialogs/WidgetOpenIDPermissionsDialog.js rename to src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx index 1bc6444ac19..7993f9c4183 100644 --- a/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.js +++ b/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx @@ -1,5 +1,6 @@ /* Copyright 2019 Travis Ralston +Copyright 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,42 +16,46 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import { _t } from "../../../languageHandler"; -import * as sdk from "../../../index"; import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; -import { Widget } from "matrix-widget-api"; +import { Widget, WidgetKind } from "matrix-widget-api"; import { OIDCState, WidgetPermissionStore } from "../../../stores/widgets/WidgetPermissionStore"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { IDialogProps } from "./IDialogProps"; +import BaseDialog from "./BaseDialog"; +import DialogButtons from "../elements/DialogButtons"; -@replaceableComponent("views.dialogs.WidgetOpenIDPermissionsDialog") -export default class WidgetOpenIDPermissionsDialog extends React.Component { - static propTypes = { - onFinished: PropTypes.func.isRequired, - widget: PropTypes.objectOf(Widget).isRequired, - widgetKind: PropTypes.string.isRequired, // WidgetKind from widget-api - inRoomId: PropTypes.string, - }; +interface IProps extends IDialogProps { + widget: Widget; + widgetKind: WidgetKind; + inRoomId?: string; +} - constructor() { - super(); +interface IState { + rememberSelection: boolean; +} + +@replaceableComponent("views.dialogs.WidgetOpenIDPermissionsDialog") +export default class WidgetOpenIDPermissionsDialog extends React.PureComponent { + constructor(props: IProps) { + super(props); this.state = { rememberSelection: false, }; } - _onAllow = () => { - this._onPermissionSelection(true); + private onAllow = () => { + this.onPermissionSelection(true); }; - _onDeny = () => { - this._onPermissionSelection(false); + private onDeny = () => { + this.onPermissionSelection(false); }; - _onPermissionSelection(allowed) { + private onPermissionSelection(allowed: boolean) { if (this.state.rememberSelection) { - console.log(`Remembering ${this.props.widgetId} as allowed=${allowed} for OpenID`); + console.log(`Remembering ${this.props.widget.id} as allowed=${allowed} for OpenID`); WidgetPermissionStore.instance.setOIDCState( this.props.widget, this.props.widgetKind, this.props.inRoomId, @@ -61,14 +66,11 @@ export default class WidgetOpenIDPermissionsDialog extends React.Component { this.props.onFinished(allowed); } - _onRememberSelectionChange = (newVal) => { + private onRememberSelectionChange = (newVal: boolean) => { this.setState({ rememberSelection: newVal }); }; - render() { - const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - + public render() { return ( } /> diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx index 0ce9a3a030c..75b68901120 100644 --- a/src/components/views/elements/AccessibleButton.tsx +++ b/src/components/views/elements/AccessibleButton.tsx @@ -19,7 +19,7 @@ import React, { ReactHTML } from 'react'; import { Key } from '../../../Keyboard'; import classnames from 'classnames'; -export type ButtonEvent = React.MouseEvent | React.KeyboardEvent; +export type ButtonEvent = React.MouseEvent | React.KeyboardEvent | React.FormEvent; /** * children: React's magic prop. Represents all children given to the element. @@ -39,7 +39,7 @@ interface IProps extends React.InputHTMLAttributes { tabIndex?: number; disabled?: boolean; className?: string; - onClick(e?: ButtonEvent): void; + onClick(e?: ButtonEvent): void | Promise; } interface IAccessibleButtonProps extends React.InputHTMLAttributes { diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx index 68a70133e68..a7ebf40c3a8 100644 --- a/src/components/views/elements/EventTilePreview.tsx +++ b/src/components/views/elements/EventTilePreview.tsx @@ -25,6 +25,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import { Layout } from "../../../settings/Layout"; import { UIFeature } from "../../../settings/UIFeature"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import Spinner from './Spinner'; interface IProps { /** @@ -45,7 +46,7 @@ interface IProps { /** * The ID of the displayed user */ - userId: string; + userId?: string; /** * The display name of the displayed user @@ -118,13 +119,16 @@ export default class EventTilePreview extends React.Component { } public render() { - const event = this.fakeEvent(this.state); - const className = classnames(this.props.className, { "mx_IRCLayout": this.props.layout == Layout.IRC, "mx_GroupLayout": this.props.layout == Layout.Group, + "mx_EventTilePreview_loader": !this.props.userId, }); + if (!this.props.userId) return
; + + const event = this.fakeEvent(this.state); + return
{ const avatar = ( { }); return
-
{ this.props.reason }
+
{ this.props.htmlReason ? sanitizedHtmlNode(this.props.htmlReason) : this.props.reason }
diff --git a/src/components/views/elements/ReplyThread.tsx b/src/components/views/elements/ReplyThread.tsx index 0eb795e2579..d061d52f461 100644 --- a/src/components/views/elements/ReplyThread.tsx +++ b/src/components/views/elements/ReplyThread.tsx @@ -19,6 +19,7 @@ import React from 'react'; import { _t } from '../../../languageHandler'; import dis from '../../../dispatcher/dispatcher'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; +import { UNSTABLE_ELEMENT_REPLY_IN_THREAD } from "matrix-js-sdk/src/@types/event"; import { makeUserPermalink, RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import SettingsStore from "../../../settings/SettingsStore"; import { Layout } from "../../../settings/Layout"; @@ -206,15 +207,28 @@ export default class ReplyThread extends React.Component { return { body, html }; } - public static makeReplyMixIn(ev: MatrixEvent) { + public static makeReplyMixIn(ev: MatrixEvent, replyInThread: boolean) { if (!ev) return {}; - return { + + const replyMixin = { 'm.relates_to': { 'm.in_reply_to': { 'event_id': ev.getId(), }, }, }; + + /** + * @experimental + * Rendering hint for threads, only attached if true to make + * sure that Element does not start sending that property for all events + */ + if (replyInThread) { + const inReplyTo = replyMixin['m.relates_to']['m.in_reply_to']; + inReplyTo[UNSTABLE_ELEMENT_REPLY_IN_THREAD.name] = replyInThread; + } + + return replyMixin; } public static makeThread( diff --git a/src/components/views/elements/RoomAliasField.tsx b/src/components/views/elements/RoomAliasField.tsx index a7676e42148..9c383989b11 100644 --- a/src/components/views/elements/RoomAliasField.tsx +++ b/src/components/views/elements/RoomAliasField.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef } from "react"; +import React, { createRef, KeyboardEventHandler } from "react"; import { _t } from '../../../languageHandler'; import withValidation from './Validation'; @@ -28,6 +28,7 @@ interface IProps { label?: string; placeholder?: string; disabled?: boolean; + onKeyDown?: KeyboardEventHandler; onChange?(value: string): void; } @@ -70,6 +71,8 @@ export default class RoomAliasField extends React.PureComponent value={this.props.value.substring(1, this.props.value.length - this.props.domain.length - 1)} maxLength={maxlength} disabled={this.props.disabled} + autoComplete="off" + onKeyDown={this.props.onKeyDown} /> ); } diff --git a/src/components/views/emojipicker/EmojiPicker.tsx b/src/components/views/emojipicker/EmojiPicker.tsx index 0884db71014..9cc995a1401 100644 --- a/src/components/views/emojipicker/EmojiPicker.tsx +++ b/src/components/views/emojipicker/EmojiPicker.tsx @@ -173,16 +173,16 @@ class EmojiPicker extends React.Component { }; private onChangeFilter = (filter: string) => { - filter = filter.toLowerCase(); // filter is case insensitive stored lower-case + const lcFilter = filter.toLowerCase().trim(); // filter is case insensitive for (const cat of this.categories) { let emojis; // If the new filter string includes the old filter string, we don't have to re-filter the whole dataset. - if (filter.includes(this.state.filter)) { + if (lcFilter.includes(this.state.filter)) { emojis = this.memoizedDataByCategory[cat.id]; } else { emojis = cat.id === "recent" ? this.recentlyUsed : DATA_BY_CATEGORY[cat.id]; } - emojis = emojis.filter(emoji => this.emojiMatchesFilter(emoji, filter)); + emojis = emojis.filter(emoji => this.emojiMatchesFilter(emoji, lcFilter)); this.memoizedDataByCategory[cat.id] = emojis; cat.enabled = emojis.length > 0; // The setState below doesn't re-render the header and we already have the refs for updateVisibility, so... @@ -194,9 +194,12 @@ class EmojiPicker extends React.Component { setTimeout(this.updateVisibility, 0); }; - private emojiMatchesFilter = (emoji: IEmoji, filter: string): boolean => - [emoji.annotation, ...emoji.shortcodes, emoji.emoticon, ...emoji.unicode.split(ZERO_WIDTH_JOINER)] - .some(x => x?.includes(filter)); + private emojiMatchesFilter = (emoji: IEmoji, filter: string): boolean => { + return emoji.annotation.toLowerCase().includes(filter) || + emoji.emoticon?.toLowerCase().includes(filter) || + emoji.shortcodes.some(x => x.toLowerCase().includes(filter)) || + emoji.unicode.split(ZERO_WIDTH_JOINER).includes(filter); + }; private onEnterFilter = () => { const btn = this.bodyRef.current.querySelector(".mx_EmojiPicker_item"); diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index 8c9a3da060f..5f514b8390d 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -17,7 +17,7 @@ limitations under the License. import React, { createRef } from 'react'; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { _t, _td } from '../../../languageHandler'; +import { _t } from '../../../languageHandler'; import MemberAvatar from '../avatars/MemberAvatar'; import CallEventGrouper, { CallEventGrouperEvent, CustomCallState } from '../../structures/CallEventGrouper'; import AccessibleButton from '../elements/AccessibleButton'; @@ -26,6 +26,7 @@ import InfoTooltip, { InfoTooltipKind } from '../elements/InfoTooltip'; import classNames from 'classnames'; import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; import { formatCallTime } from "../../../DateUtils"; +import Clock from "../audio_messages/Clock"; const MAX_NON_NARROW_WIDTH = 450 / 70 * 100; @@ -38,13 +39,9 @@ interface IState { callState: CallState | CustomCallState; silenced: boolean; narrow: boolean; + length: number; } -const TEXTUAL_STATES: Map = new Map([ - [CallState.Connected, _td("Connected")], - [CallState.Connecting, _td("Connecting")], -]); - export default class CallEvent extends React.PureComponent { private wrapperElement = createRef(); private resizeObserver: ResizeObserver; @@ -56,12 +53,14 @@ export default class CallEvent extends React.PureComponent { callState: this.props.callEventGrouper.state, silenced: false, narrow: false, + length: 0, }; } componentDidMount() { this.props.callEventGrouper.addListener(CallEventGrouperEvent.StateChanged, this.onStateChanged); this.props.callEventGrouper.addListener(CallEventGrouperEvent.SilencedChanged, this.onSilencedChanged); + this.props.callEventGrouper.addListener(CallEventGrouperEvent.LengthChanged, this.onLengthChanged); this.resizeObserver = new ResizeObserver(this.resizeObserverCallback); this.resizeObserver.observe(this.wrapperElement.current); @@ -70,10 +69,15 @@ export default class CallEvent extends React.PureComponent { componentWillUnmount() { this.props.callEventGrouper.removeListener(CallEventGrouperEvent.StateChanged, this.onStateChanged); this.props.callEventGrouper.removeListener(CallEventGrouperEvent.SilencedChanged, this.onSilencedChanged); + this.props.callEventGrouper.removeListener(CallEventGrouperEvent.LengthChanged, this.onLengthChanged); this.resizeObserver.disconnect(); } + private onLengthChanged = (length: number): void => { + this.setState({ length }); + }; + private resizeObserverCallback = (entries: ResizeObserverEntry[]): void => { const wrapperElementEntry = entries.find((entry) => entry.target === this.wrapperElement.current); if (!wrapperElementEntry) return; @@ -214,10 +218,17 @@ export default class CallEvent extends React.PureComponent {
); } - if (Array.from(TEXTUAL_STATES.keys()).includes(state)) { + if (state === CallState.Connected) { + return ( +
+ +
+ ); + } + if (state === CallState.Connecting) { return (
- { TEXTUAL_STATES.get(state) } + { _t("Connecting") }
); } diff --git a/src/components/views/messages/MAudioBody.tsx b/src/components/views/messages/MAudioBody.tsx index 288ad16d888..1975fe8d42c 100644 --- a/src/components/views/messages/MAudioBody.tsx +++ b/src/components/views/messages/MAudioBody.tsx @@ -24,6 +24,8 @@ import { IMediaEventContent } from "../../../customisations/models/IMediaEventCo import MFileBody from "./MFileBody"; import { IBodyProps } from "./IBodyProps"; import { PlaybackManager } from "../../../audio/PlaybackManager"; +import { isVoiceMessage } from "../../../utils/EventUtils"; +import { PlaybackQueue } from "../../../audio/PlaybackQueue"; interface IState { error?: Error; @@ -67,6 +69,10 @@ export default class MAudioBody extends React.PureComponent playback.clockInfo.populatePlaceholdersFrom(this.props.mxEvent); this.setState({ playback }); + if (isVoiceMessage(this.props.mxEvent)) { + PlaybackQueue.forRoom(this.props.mxEvent.getRoomId()).unsortedEnqueue(this.props.mxEvent, playback); + } + // Note: the components later on will handle preparing the Playback class for us. } diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index 95fa3ee8a9b..72e50619214 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -55,6 +55,7 @@ export default class MImageBody extends React.Component { static contextType = MatrixClientContext; private unmounted = true; private image = createRef(); + private timeout?: number; constructor(props: IBodyProps) { super(props); @@ -128,7 +129,7 @@ export default class MImageBody extends React.Component { private onImageEnter = (e: React.MouseEvent): void => { this.setState({ hover: true }); - if (!this.state.showImage || !this.isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) { + if (!this.state.showImage || !this.isGif() || SettingsStore.getValue("autoplayGifs")) { return; } const imgElement = e.currentTarget; @@ -138,7 +139,7 @@ export default class MImageBody extends React.Component { private onImageLeave = (e: React.MouseEvent): void => { this.setState({ hover: false }); - if (!this.state.showImage || !this.isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) { + if (!this.state.showImage || !this.isGif() || SettingsStore.getValue("autoplayGifs")) { return; } const imgElement = e.currentTarget; @@ -146,12 +147,14 @@ export default class MImageBody extends React.Component { }; private onImageError = (): void => { + this.clearBlurhashTimeout(); this.setState({ imgError: true, }); }; private onImageLoad = (): void => { + this.clearBlurhashTimeout(); this.props.onHeightChanged(); let loadedImageDimensions; @@ -267,6 +270,13 @@ export default class MImageBody extends React.Component { } } + private clearBlurhashTimeout() { + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = undefined; + } + } + componentDidMount() { this.unmounted = false; this.context.on('sync', this.onClientSync); @@ -281,8 +291,9 @@ export default class MImageBody extends React.Component { } // else don't download anything because we don't want to display anything. // Add a 150ms timer for blurhash to first appear. - if (this.media.isEncrypted) { - setTimeout(() => { + if (this.props.mxEvent.getContent().info?.[BLURHASH_FIELD]) { + this.clearBlurhashTimeout(); + this.timeout = setTimeout(() => { if (!this.state.imgLoaded || !this.state.imgError) { this.setState({ placeholder: 'blurhash', @@ -295,6 +306,7 @@ export default class MImageBody extends React.Component { componentWillUnmount() { this.unmounted = true; this.context.removeListener('sync', this.onClientSync); + this.clearBlurhashTimeout(); } protected messageContent( @@ -387,7 +399,7 @@ export default class MImageBody extends React.Component { showPlaceholder = false; // because we're hiding the image, so don't show the placeholder. } - if (this.isGif() && !SettingsStore.getValue("autoplayGifsAndVideos") && !this.state.hover) { + if (this.isGif() && !SettingsStore.getValue("autoplayGifs") && !this.state.hover) { gifLabel =

GIF

; } @@ -487,7 +499,7 @@ export default class MImageBody extends React.Component { const contentUrl = this.getContentUrl(); let thumbUrl; - if (this.isGif() && SettingsStore.getValue("autoplayGifsAndVideos")) { + if (this.isGif() && SettingsStore.getValue("autoplayGifs")) { thumbUrl = contentUrl; } else { thumbUrl = this.getThumbUrl(); diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index 77c7ebacdad..de1915299c6 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -145,7 +145,7 @@ export default class MVideoBody extends React.PureComponent } async componentDidMount() { - const autoplay = SettingsStore.getValue("autoplayGifsAndVideos") as boolean; + const autoplay = SettingsStore.getValue("autoplayVideo") as boolean; this.loadBlurhash(); if (this.props.mediaEventHelper.media.isEncrypted && this.state.decryptedUrl === null) { @@ -209,7 +209,7 @@ export default class MVideoBody extends React.PureComponent render() { const content = this.props.mxEvent.getContent(); - const autoplay = SettingsStore.getValue("autoplayGifsAndVideos"); + const autoplay = SettingsStore.getValue("autoplayVideo"); if (this.state.error !== null) { return ( diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.tsx similarity index 65% rename from src/components/views/messages/MessageActionBar.js rename to src/components/views/messages/MessageActionBar.tsx index 7fe0eca6972..f76fa32ddca 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.tsx @@ -17,12 +17,13 @@ limitations under the License. */ import React, { useEffect } from 'react'; -import PropTypes from 'prop-types'; -import { EventStatus } from 'matrix-js-sdk/src/models/event'; +import { MatrixEvent, EventStatus } from 'matrix-js-sdk/src/models/event'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; +import { Action } from '../../../dispatcher/actions'; +import { RightPanelPhases } from '../../../stores/RightPanelStorePhases'; import { aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu } from '../../structures/ContextMenu'; import { isContentActionable, canEditContent } from '../../../utils/EventUtils'; import RoomContext from "../../../contexts/RoomContext"; @@ -34,48 +35,66 @@ import Resend from "../../../Resend"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MediaEventHelper } from "../../../utils/MediaEventHelper"; import DownloadActionButton from "./DownloadActionButton"; +import SettingsStore from '../../../settings/SettingsStore'; +import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; +import ReplyThread from '../elements/ReplyThread'; + +interface IOptionsButtonProps { + mxEvent: MatrixEvent; + getTile: () => any; // TODO: FIXME, haven't figured out what the return type is here + getReplyThread: () => ReplyThread; + permalinkCreator: RoomPermalinkCreator; + onFocusChange: (menuDisplayed: boolean) => void; +} -const OptionsButton = ({ mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange }) => { - const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); - const [onFocus, isActive, ref] = useRovingTabIndex(button); - useEffect(() => { - onFocusChange(menuDisplayed); - }, [onFocusChange, menuDisplayed]); - - let contextMenu; - if (menuDisplayed) { - const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu'); - - const tile = getTile && getTile(); - const replyThread = getReplyThread && getReplyThread(); - - const buttonRect = button.current.getBoundingClientRect(); - contextMenu = ; - } +const OptionsButton: React.FC = + ({ mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange }) => { + const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); + const [onFocus, isActive, ref] = useRovingTabIndex(button); + useEffect(() => { + onFocusChange(menuDisplayed); + }, [onFocusChange, menuDisplayed]); + + let contextMenu; + if (menuDisplayed) { + const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu'); + + const tile = getTile && getTile(); + const replyThread = getReplyThread && getReplyThread(); + + const buttonRect = button.current.getBoundingClientRect(); + contextMenu = ; + } - return - + return + + + { contextMenu } + ; + }; - { contextMenu } - ; -}; +interface IReactButtonProps { + mxEvent: MatrixEvent; + reactions: any; // TODO: types + onFocusChange: (menuDisplayed: boolean) => void; +} -const ReactButton = ({ mxEvent, reactions, onFocusChange }) => { +const ReactButton: React.FC = ({ mxEvent, reactions, onFocusChange }) => { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); const [onFocus, isActive, ref] = useRovingTabIndex(button); useEffect(() => { @@ -106,21 +125,21 @@ const ReactButton = ({ mxEvent, reactions, onFocusChange }) => { ; }; -@replaceableComponent("views.messages.MessageActionBar") -export default class MessageActionBar extends React.PureComponent { - static propTypes = { - mxEvent: PropTypes.object.isRequired, - // The Relations model from the JS SDK for reactions to `mxEvent` - reactions: PropTypes.object, - permalinkCreator: PropTypes.object, - getTile: PropTypes.func, - getReplyThread: PropTypes.func, - onFocusChange: PropTypes.func, - }; +interface IMessageActionBarProps { + mxEvent: MatrixEvent; + // The Relations model from the JS SDK for reactions to `mxEvent` + reactions?: any; // TODO: types + permalinkCreator?: RoomPermalinkCreator; + getTile: () => any; // TODO: FIXME, haven't figured out what the return type is here + getReplyThread?: () => ReplyThread; + onFocusChange?: (menuDisplayed: boolean) => void; +} - static contextType = RoomContext; +@replaceableComponent("views.messages.MessageActionBar") +export default class MessageActionBar extends React.PureComponent { + public static contextType = RoomContext; - componentDidMount() { + public componentDidMount(): void { if (this.props.mxEvent.status && this.props.mxEvent.status !== EventStatus.SENT) { this.props.mxEvent.on("Event.status", this.onSent); } @@ -134,43 +153,54 @@ export default class MessageActionBar extends React.PureComponent { this.props.mxEvent.on("Event.beforeRedaction", this.onBeforeRedaction); } - componentWillUnmount() { + public componentWillUnmount(): void { this.props.mxEvent.off("Event.status", this.onSent); this.props.mxEvent.off("Event.decrypted", this.onDecrypted); this.props.mxEvent.off("Event.beforeRedaction", this.onBeforeRedaction); } - onDecrypted = () => { + private onDecrypted = (): void => { // When an event decrypts, it is likely to change the set of available // actions, so we force an update to check again. this.forceUpdate(); }; - onBeforeRedaction = () => { + private onBeforeRedaction = (): void => { // When an event is redacted, we can't edit it so update the available actions. this.forceUpdate(); }; - onSent = () => { + private onSent = (): void => { // When an event is sent and echoed the possible actions change. this.forceUpdate(); }; - onFocusChange = (focused) => { + private onFocusChange = (focused: boolean): void => { if (!this.props.onFocusChange) { return; } this.props.onFocusChange(focused); }; - onReplyClick = (ev) => { + private onReplyClick = (ev: React.MouseEvent): void => { dis.dispatch({ action: 'reply_to_event', event: this.props.mxEvent, }); }; - onEditClick = (ev) => { + private onThreadClick = (): void => { + dis.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.ThreadView, + allowClose: false, + refireParams: { + event: this.props.mxEvent, + }, + }); + }; + + private onEditClick = (ev: React.MouseEvent): void => { dis.dispatch({ action: 'edit_event', event: this.props.mxEvent, @@ -186,7 +216,7 @@ export default class MessageActionBar extends React.PureComponent { * @param {Function} fn The execution function. * @param {Function} checkFn The test function. */ - runActionOnFailedEv(fn, checkFn) { + private runActionOnFailedEv(fn: (ev: MatrixEvent) => void, checkFn?: (ev: MatrixEvent) => boolean): void { if (!checkFn) checkFn = () => true; const mxEvent = this.props.mxEvent; @@ -201,18 +231,18 @@ export default class MessageActionBar extends React.PureComponent { } } - onResendClick = (ev) => { + private onResendClick = (ev: React.MouseEvent): void => { this.runActionOnFailedEv((tarEv) => Resend.resend(tarEv)); }; - onCancelClick = (ev) => { + private onCancelClick = (ev: React.MouseEvent): void => { this.runActionOnFailedEv( (tarEv) => Resend.removeFromQueue(tarEv), (testEv) => canCancel(testEv.status), ); }; - render() { + public render(): JSX.Element { const toolbarOpts = []; if (canEditContent(this.props.mxEvent)) { toolbarOpts.push(); + toolbarOpts.splice(0, 0, <> + + { SettingsStore.getValue("feature_thread") && ( + + ) } + ); } if (this.context.canReact) { toolbarOpts.splice(0, 0, { }); }; +const onRoomThreadsClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.ThreadPanel, + }); +}; + const onRoomSettingsClick = () => { defaultDispatcher.dispatch({ action: "open_room_settings" }); }; @@ -273,6 +280,11 @@ const RoomSummaryCard: React.FC = ({ room, onClose }) => { + { SettingsStore.getValue("feature_thread") && ( + + ) } diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index 138f5bf9fe8..d15f349d62d 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -428,7 +428,7 @@ const UserOptionsSection: React.FC<{ let directMessageButton; if (!isMe) { directMessageButton = ( - openDMForUser(cli, member.userId)} className="mx_UserInfo_field"> + { openDMForUser(cli, member.userId); }} className="mx_UserInfo_field"> { _t('Direct message') } ); diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index b7e067ee93f..7a3767deb78 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -43,11 +43,6 @@ import QuestionDialog from "../dialogs/QuestionDialog"; import { ActionPayload } from "../../../dispatcher/payloads"; import AccessibleButton from '../elements/AccessibleButton'; -function eventIsReply(mxEvent: MatrixEvent): boolean { - const relatesTo = mxEvent.getContent()["m.relates_to"]; - return !!(relatesTo && relatesTo["m.in_reply_to"]); -} - function getHtmlReplyFallback(mxEvent: MatrixEvent): string { const html = mxEvent.getContent().formatted_body; if (!html) { @@ -72,7 +67,7 @@ function createEditContent(model: EditorModel, editedEvent: MatrixEvent): IConte if (isEmote) { model = stripEmoteCommand(model); } - const isReply = eventIsReply(editedEvent); + const isReply = !!editedEvent.replyEventId; let plainPrefix = ""; let htmlPrefix = ""; diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index dd954e46ce6..315241c0740 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -21,6 +21,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event"; import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { Relations } from "matrix-js-sdk/src/models/relations"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; +import { Thread } from 'matrix-js-sdk/src/models/thread'; import ReplyThread from "../elements/ReplyThread"; import { _t } from '../../../languageHandler'; @@ -55,6 +56,8 @@ import ReadReceiptMarker from "./ReadReceiptMarker"; import MessageActionBar from "../messages/MessageActionBar"; import ReactionsRow from '../messages/ReactionsRow'; import { getEventDisplayInfo } from '../../../utils/EventUtils'; +import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; +import SettingsStore from "../../../settings/SettingsStore"; const eventTileTypes = { [EventType.RoomMessage]: 'messages.MessageEvent', @@ -240,6 +243,7 @@ interface IProps { // opaque readreceipt info for each userId; used by ReadReceiptMarker // to manage its animations. Should be an empty object when the room // first loads + // TODO: Proper typing for RR info readReceiptMap?: any; // A function which is used to check if the parent panel is being @@ -299,6 +303,9 @@ interface IProps { // whether or not to display the sender hideSender?: boolean; + + // whether or not to display thread info + showThreadInfo?: boolean; } interface IState { @@ -315,6 +322,8 @@ interface IState { reactions: Relations; hover: boolean; + + thread?: Thread; } @replaceableComponent("views.rooms.EventTile") @@ -351,6 +360,8 @@ export default class EventTile extends React.Component { reactions: this.getReactions(), hover: false, + + thread: this.props.mxEvent?.getThread(), }; // don't do RR animations until we are mounted @@ -451,8 +462,20 @@ export default class EventTile extends React.Component { client.on("Room.receipt", this.onRoomReceipt); this.isListeningForReceipts = true; } + + if (SettingsStore.getValue("feature_thread")) { + this.props.mxEvent.once("Thread.ready", this.updateThread); + this.props.mxEvent.on("Thread.update", this.updateThread); + } } + private updateThread = (thread) => { + this.setState({ + thread, + }); + this.forceUpdate(); + }; + // TODO: [REACT-WARNING] Replace with appropriate lifecycle event // eslint-disable-next-line UNSAFE_componentWillReceiveProps(nextProps) { @@ -463,7 +486,7 @@ export default class EventTile extends React.Component { } } - shouldComponentUpdate(nextProps, nextState) { + shouldComponentUpdate(nextProps, nextState, nextContext) { if (objectHasDiff(this.state, nextState)) { return true; } @@ -491,6 +514,43 @@ export default class EventTile extends React.Component { } } + private renderThreadInfo(): React.ReactNode { + if (!SettingsStore.getValue("feature_thread")) { + return null; + } + + const thread = this.state.thread; + const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); + if (!thread || this.props.showThreadInfo === false) { + return null; + } + + const avatars = Array.from(thread.participants).map((mxId: string) => { + const member = room.getMember(mxId); + return ; + }); + + return ( +
{ + dis.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.ThreadView, + refireParams: { + event: this.props.mxEvent, + }, + }); + }} + > + + { avatars } + + { thread.length - 1 } { thread.length === 2 ? 'reply' : 'replies' } +
+ ); + } + private onRoomReceipt = (ev, room) => { // ignore events for other rooms const tileRoom = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); @@ -1180,6 +1240,7 @@ export default class EventTile extends React.Component { { keyRequestInfo } { actionBar } { this.props.layout === Layout.IRC && (reactionsRow) } + { this.renderThreadInfo() }
{ this.props.layout !== Layout.IRC && (reactionsRow) } { msgOption } diff --git a/src/components/views/rooms/JumpToBottomButton.js b/src/components/views/rooms/JumpToBottomButton.tsx similarity index 83% rename from src/components/views/rooms/JumpToBottomButton.js rename to src/components/views/rooms/JumpToBottomButton.tsx index d2e2a391a61..0b680d093d9 100644 --- a/src/components/views/rooms/JumpToBottomButton.js +++ b/src/components/views/rooms/JumpToBottomButton.tsx @@ -14,11 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ +import React from "react"; import { _t } from '../../../languageHandler'; import AccessibleButton from '../elements/AccessibleButton'; import classNames from 'classnames'; -export default (props) => { +interface IProps { + numUnreadMessages: number; + highlight: boolean; + onScrollToBottomClick: (e: React.MouseEvent) => void; +} + +const JumpToBottomButton: React.FC = (props) => { const className = classNames({ 'mx_JumpToBottomButton': true, 'mx_JumpToBottomButton_highlight': props.highlight, @@ -36,3 +43,5 @@ export default (props) => { { badge }
); }; + +export default JumpToBottomButton; diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index 8455e9aa11c..466675ac648 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -183,7 +183,10 @@ interface IProps { resizeNotifier: ResizeNotifier; permalinkCreator: RoomPermalinkCreator; replyToEvent?: MatrixEvent; + replyInThread?: boolean; + showReplyPreview?: boolean; e2eStatus?: E2EStatus; + compact?: boolean; } interface IState { @@ -201,6 +204,12 @@ export default class MessageComposer extends React.Component { private messageComposerInput: SendMessageComposer; private voiceRecordingButton: VoiceRecordComposerTile; + static defaultProps = { + replyInThread: false, + showReplyPreview: true, + compact: false, + }; + constructor(props) { super(props); VoiceRecordingStore.instance.on(UPDATE_EVENT, this.onVoiceStoreUpdate); @@ -362,7 +371,7 @@ export default class MessageComposer extends React.Component { render() { const controls = [ - this.state.me ? : null, + this.state.me && !this.props.compact ? : null, this.props.e2eStatus ? : null, @@ -376,6 +385,7 @@ export default class MessageComposer extends React.Component { room={this.props.room} placeholder={this.renderPlaceholderText()} permalinkCreator={this.props.permalinkCreator} + replyInThread={this.props.replyInThread} replyToEvent={this.props.replyToEvent} onChange={this.onChange} disabled={this.state.haveRecording} @@ -450,11 +460,19 @@ export default class MessageComposer extends React.Component { />; } + const classes = classNames({ + "mx_MessageComposer": true, + "mx_GroupLayout": true, + "mx_MessageComposer--compact": this.props.compact, + }); + return ( -
+
{ recordingTooltip }
- + { this.props.showReplyPreview && ( + + ) }
{ controls }
diff --git a/src/components/views/rooms/ReadReceiptMarker.js b/src/components/views/rooms/ReadReceiptMarker.tsx similarity index 71% rename from src/components/views/rooms/ReadReceiptMarker.js rename to src/components/views/rooms/ReadReceiptMarker.tsx index c9688b4d294..cfc535b23d5 100644 --- a/src/components/views/rooms/ReadReceiptMarker.js +++ b/src/components/views/rooms/ReadReceiptMarker.tsx @@ -15,62 +15,75 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef } from 'react'; -import PropTypes from 'prop-types'; +import React, { createRef, RefObject } from 'react'; +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; + import { _t } from '../../../languageHandler'; import { formatDate } from '../../../DateUtils'; import NodeAnimator from "../../../NodeAnimator"; -import * as sdk from "../../../index"; import { toPx } from "../../../utils/units"; import { replaceableComponent } from "../../../utils/replaceableComponent"; -@replaceableComponent("views.rooms.ReadReceiptMarker") -export default class ReadReceiptMarker extends React.PureComponent { - static propTypes = { - // the RoomMember to show the RR for - member: PropTypes.object, - // userId to fallback the avatar to - // if the member hasn't been loaded yet - fallbackUserId: PropTypes.string.isRequired, +import MemberAvatar from '../avatars/MemberAvatar'; - // number of pixels to offset the avatar from the right of its parent; - // typically a negative value. - leftOffset: PropTypes.number, +interface IProps { + // the RoomMember to show the RR for + member?: RoomMember; + // userId to fallback the avatar to + // if the member hasn't been loaded yet + fallbackUserId: string; - // true to hide the avatar (it will still be animated) - hidden: PropTypes.bool, + // number of pixels to offset the avatar from the right of its parent; + // typically a negative value. + leftOffset?: number; - // don't animate this RR into position - suppressAnimation: PropTypes.bool, + // true to hide the avatar (it will still be animated) + hidden?: boolean; - // an opaque object for storing information about this user's RR in - // this room - readReceiptInfo: PropTypes.object, + // don't animate this RR into position + suppressAnimation?: boolean; - // A function which is used to check if the parent panel is being - // unmounted, to avoid unnecessary work. Should return true if we - // are being unmounted. - checkUnmounting: PropTypes.func, + // an opaque object for storing information about this user's RR in + // this room + // TODO: proper typing for RR info + readReceiptInfo: any; - // callback for clicks on this RR - onClick: PropTypes.func, + // A function which is used to check if the parent panel is being + // unmounted, to avoid unnecessary work. Should return true if we + // are being unmounted. + checkUnmounting?: () => boolean; - // Timestamp when the receipt was read - timestamp: PropTypes.number, + // callback for clicks on this RR + onClick?: (e: React.MouseEvent) => void; - // True to show twelve hour format, false otherwise - showTwelveHour: PropTypes.bool, - }; + // Timestamp when the receipt was read + timestamp?: number; + + // True to show twelve hour format, false otherwise + showTwelveHour?: boolean; +} + +interface IState { + suppressDisplay: boolean; + startStyles?: IReadReceiptMarkerStyle[]; +} + +interface IReadReceiptMarkerStyle { + top: number; + left: number; +} + +@replaceableComponent("views.rooms.ReadReceiptMarker") +export default class ReadReceiptMarker extends React.PureComponent { + private avatar: React.RefObject = createRef(); static defaultProps = { leftOffset: 0, }; - constructor(props) { + constructor(props: IProps) { super(props); - this._avatar = createRef(); - this.state = { // if we are going to animate the RR, we don't show it on first render, // and instead just add a placeholder to the DOM; once we've been @@ -80,7 +93,7 @@ export default class ReadReceiptMarker extends React.PureComponent { }; } - componentWillUnmount() { + public componentWillUnmount(): void { // before we remove the rr, store its location in the map, so that if // it reappears, it can be animated from the right place. const rrInfo = this.props.readReceiptInfo; @@ -95,29 +108,29 @@ export default class ReadReceiptMarker extends React.PureComponent { return; } - const avatarNode = this._avatar.current; + const avatarNode = this.avatar.current; rrInfo.top = avatarNode.offsetTop; rrInfo.left = avatarNode.offsetLeft; rrInfo.parent = avatarNode.offsetParent; } - componentDidMount() { + public componentDidMount(): void { if (!this.state.suppressDisplay) { // we've already done our display - nothing more to do. return; } - this._animateMarker(); + this.animateMarker(); } - componentDidUpdate(prevProps) { + public componentDidUpdate(prevProps: IProps): void { const differentLeftOffset = prevProps.leftOffset !== this.props.leftOffset; const visibilityChanged = prevProps.hidden !== this.props.hidden; if (differentLeftOffset || visibilityChanged) { - this._animateMarker(); + this.animateMarker(); } } - _animateMarker() { + private animateMarker(): void { // treat new RRs as though they were off the top of the screen let oldTop = -15; @@ -126,7 +139,7 @@ export default class ReadReceiptMarker extends React.PureComponent { oldTop = oldInfo.top + oldInfo.parent.getBoundingClientRect().top; } - const newElement = this._avatar.current; + const newElement = this.avatar.current; let startTopOffset; if (!newElement.offsetParent) { // this seems to happen sometimes for reasons I don't understand @@ -156,10 +169,9 @@ export default class ReadReceiptMarker extends React.PureComponent { }); } - render() { - const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); + public render(): JSX.Element { if (this.state.suppressDisplay) { - return
; + return
} />; } const style = { @@ -198,7 +210,7 @@ export default class ReadReceiptMarker extends React.PureComponent { style={style} title={title} onClick={this.props.onClick} - inputRef={this._avatar} + inputRef={this.avatar as RefObject} /> ); diff --git a/src/components/views/rooms/RoomDetailList.js b/src/components/views/rooms/RoomDetailList.tsx similarity index 76% rename from src/components/views/rooms/RoomDetailList.js rename to src/components/views/rooms/RoomDetailList.tsx index bf2f5418c90..869ab9e8f3d 100644 --- a/src/components/views/rooms/RoomDetailList.js +++ b/src/components/views/rooms/RoomDetailList.tsx @@ -14,41 +14,38 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as sdk from '../../../index'; -import dis from '../../../dispatcher/dispatcher'; import React from 'react'; -import { _t } from '../../../languageHandler'; -import PropTypes from 'prop-types'; +import { Room } from 'matrix-js-sdk/src'; import classNames from 'classnames'; +import dis from '../../../dispatcher/dispatcher'; +import { _t } from '../../../languageHandler'; -import { roomShape } from './RoomDetailRow'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import RoomDetailRow from "./RoomDetailRow"; -@replaceableComponent("views.rooms.RoomDetailList") -export default class RoomDetailList extends React.Component { - static propTypes = { - rooms: PropTypes.arrayOf(roomShape), - className: PropTypes.string, - }; +interface IProps { + rooms?: Room[]; + className?: string; +} - getRows() { +@replaceableComponent("views.rooms.RoomDetailList") +export default class RoomDetailList extends React.Component { + private getRows(): JSX.Element[] { if (!this.props.rooms) return []; - - const RoomDetailRow = sdk.getComponent('rooms.RoomDetailRow'); return this.props.rooms.map((room, index) => { return ; }); } - onDetailsClick = (ev, room) => { + private onDetailsClick = (ev: React.MouseEvent, room: Room): void => { dis.dispatch({ action: 'view_room', room_id: room.roomId, - room_alias: room.canonicalAlias || (room.aliases || [])[0], + room_alias: room.getCanonicalAlias() || (room.getAltAliases() || [])[0], }); }; - render() { + public render(): JSX.Element { const rows = this.getRows(); let rooms; if (rows.length === 0) { diff --git a/src/components/views/rooms/RoomHeader.tsx b/src/components/views/rooms/RoomHeader.tsx index 15b25ed64b5..d0e438bcdac 100644 --- a/src/components/views/rooms/RoomHeader.tsx +++ b/src/components/views/rooms/RoomHeader.tsx @@ -195,7 +195,7 @@ export default class RoomHeader extends React.Component { videoCallButton = ev.shiftKey ? + onClick={(ev: React.MouseEvent) => ev.shiftKey ? this.displayInfoDialogAboutScreensharing() : this.props.onCallPlaced(PlaceCallType.Video)} title={_t("Video call")} />; } diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 010780565b1..4988ea6691e 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -38,7 +38,6 @@ import { StaticNotificationState } from "../../../stores/notifications/StaticNot import { Action } from "../../../dispatcher/actions"; import { ViewRoomDeltaPayload } from "../../../dispatcher/payloads/ViewRoomDeltaPayload"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; -import SettingsStore from "../../../settings/SettingsStore"; import CustomRoomTagStore from "../../../stores/CustomRoomTagStore"; import { arrayFastClone, arrayHasDiff } from "../../../utils/arrays"; import { objectShallowClone, objectWithOnly } from "../../../utils/objects"; @@ -320,11 +319,6 @@ export default class RoomList extends React.PureComponent { private updateLists = () => { const newLists = RoomListStore.instance.orderedLists; - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log("new lists", newLists); - } - const previousListIds = Object.keys(this.state.sublists); const newListIds = Object.keys(newLists).filter(t => { if (!isCustomTag(t)) return true; // always include non-custom tags diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js index b8a4315e2d1..89b493595fa 100644 --- a/src/components/views/rooms/RoomPreviewBar.js +++ b/src/components/views/rooms/RoomPreviewBar.js @@ -28,6 +28,8 @@ import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import InviteReason from "../elements/InviteReason"; +const MemberEventHtmlReasonField = "io.element.html_reason"; + const MessageCase = Object.freeze({ NotLoggedIn: "NotLoggedIn", Joining: "Joining", @@ -492,9 +494,13 @@ export default class RoomPreviewBar extends React.Component { } const myUserId = MatrixClientPeg.get().getUserId(); - const reason = this.props.room.currentState.getMember(myUserId).events.member.event.content.reason; - if (reason) { - reasonElement = ; + const memberEventContent = this.props.room.currentState.getMember(myUserId).events.member.getContent(); + + if (memberEventContent.reason) { + reasonElement = ; } primaryActionHandler = this.props.onJoinClick; diff --git a/src/components/views/rooms/RoomUpgradeWarningBar.js b/src/components/views/rooms/RoomUpgradeWarningBar.tsx similarity index 83% rename from src/components/views/rooms/RoomUpgradeWarningBar.js rename to src/components/views/rooms/RoomUpgradeWarningBar.tsx index 384845cdf9a..eb334ab825c 100644 --- a/src/components/views/rooms/RoomUpgradeWarningBar.js +++ b/src/components/views/rooms/RoomUpgradeWarningBar.tsx @@ -1,5 +1,5 @@ /* -Copyright 2018-2020 New Vector Ltd +Copyright 2018-2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,41 +15,43 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; -import * as sdk from '../../../index'; +import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; +import { Room } from 'matrix-js-sdk/src/models/room'; +import { RoomState } from 'matrix-js-sdk/src/models/room-state'; + import Modal from '../../../Modal'; import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import RoomUpgradeDialog from '../dialogs/RoomUpgradeDialog'; +import AccessibleButton from '../elements/AccessibleButton'; -@replaceableComponent("views.rooms.RoomUpgradeWarningBar") -export default class RoomUpgradeWarningBar extends React.PureComponent { - static propTypes = { - room: PropTypes.object.isRequired, - recommendation: PropTypes.object.isRequired, - }; +interface IProps { + room: Room; +} - constructor(props) { - super(props); - this.state = {}; - } +interface IState { + upgraded?: boolean; +} - componentDidMount() { +@replaceableComponent("views.rooms.RoomUpgradeWarningBar") +export default class RoomUpgradeWarningBar extends React.PureComponent { + public componentDidMount(): void { const tombstone = this.props.room.currentState.getStateEvents("m.room.tombstone", ""); this.setState({ upgraded: tombstone && tombstone.getContent().replacement_room }); - MatrixClientPeg.get().on("RoomState.events", this._onStateEvents); + MatrixClientPeg.get().on("RoomState.events", this.onStateEvents); } - componentWillUnmount() { + public componentWillUnmount(): void { const cli = MatrixClientPeg.get(); if (cli) { - cli.removeListener("RoomState.events", this._onStateEvents); + cli.removeListener("RoomState.events", this.onStateEvents); } } - _onStateEvents = (event, state) => { + private onStateEvents = (event: MatrixEvent, state: RoomState): void => { if (!this.props.room || event.getRoomId() !== this.props.room.roomId) { return; } @@ -60,14 +62,11 @@ export default class RoomUpgradeWarningBar extends React.PureComponent { this.setState({ upgraded: tombstone && tombstone.getContent().replacement_room }); }; - onUpgradeClick = () => { - const RoomUpgradeDialog = sdk.getComponent('dialogs.RoomUpgradeDialog'); + private onUpgradeClick = (): void => { Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, { room: this.props.room }); }; - render() { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - + public render(): JSX.Element { let doUpgradeWarnings = (
diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 205320fb686..bb5d5378956 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -54,18 +54,20 @@ import { Room } from 'matrix-js-sdk/src/models/room'; import ErrorDialog from "../dialogs/ErrorDialog"; import QuestionDialog from "../dialogs/QuestionDialog"; import { ActionPayload } from "../../../dispatcher/payloads"; +import { decorateStartSendingTime, sendRoundTripMetric } from "../../../sendTimePerformanceMetrics"; function addReplyToMessageContent( content: IContent, - repliedToEvent: MatrixEvent, + replyToEvent: MatrixEvent, + replyInThread: boolean, permalinkCreator: RoomPermalinkCreator, ): void { - const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent); + const replyContent = ReplyThread.makeReplyMixIn(replyToEvent, replyInThread); Object.assign(content, replyContent); // Part of Replies fallback support - prepend the text we're sending // with the text we're replying to - const nestedReply = ReplyThread.getNestedReplyText(repliedToEvent, permalinkCreator); + const nestedReply = ReplyThread.getNestedReplyText(replyToEvent, permalinkCreator); if (nestedReply) { if (content.formatted_body) { content.formatted_body = nestedReply.html + content.formatted_body; @@ -77,8 +79,9 @@ function addReplyToMessageContent( // exported for tests export function createMessageContent( model: EditorModel, - permalinkCreator: RoomPermalinkCreator, replyToEvent: MatrixEvent, + replyInThread: boolean, + permalinkCreator: RoomPermalinkCreator, ): IContent { const isEmote = containsEmote(model); if (isEmote) { @@ -101,7 +104,7 @@ export function createMessageContent( } if (replyToEvent) { - addReplyToMessageContent(content, replyToEvent, permalinkCreator); + addReplyToMessageContent(content, replyToEvent, replyInThread, permalinkCreator); } return content; @@ -129,6 +132,7 @@ interface IProps { room: Room; placeholder?: string; permalinkCreator: RoomPermalinkCreator; + replyInThread?: boolean; replyToEvent?: MatrixEvent; disabled?: boolean; onChange?(model: EditorModel): void; @@ -357,7 +361,12 @@ export default class SendMessageComposer extends React.Component { if (cmd.category === CommandCategories.messages) { content = await this.runSlashCommand(cmd, args); if (replyToEvent) { - addReplyToMessageContent(content, replyToEvent, this.props.permalinkCreator); + addReplyToMessageContent( + content, + replyToEvent, + this.props.replyInThread, + this.props.permalinkCreator, + ); } } else { this.runSlashCommand(cmd, args); @@ -400,11 +409,20 @@ export default class SendMessageComposer extends React.Component { const startTime = CountlyAnalytics.getTimestamp(); const { roomId } = this.props.room; if (!content) { - content = createMessageContent(this.model, this.props.permalinkCreator, replyToEvent); + content = createMessageContent( + this.model, + replyToEvent, + this.props.replyInThread, + this.props.permalinkCreator, + ); } // don't bother sending an empty message if (!content.body.trim()) return; + if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) { + decorateStartSendingTime(content); + } + const prom = this.context.sendMessage(roomId, content); if (replyToEvent) { // Clear reply_to_event as we put the message into the queue @@ -420,6 +438,11 @@ export default class SendMessageComposer extends React.Component { dis.dispatch({ action: `effects.${effect.command}` }); } }); + if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) { + prom.then(resp => { + sendRoundTripMetric(this.context, roomId, resp.event_id); + }); + } CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, !!replyToEvent, content); } diff --git a/src/components/views/rooms/SimpleRoomHeader.js b/src/components/views/rooms/SimpleRoomHeader.tsx similarity index 82% rename from src/components/views/rooms/SimpleRoomHeader.js rename to src/components/views/rooms/SimpleRoomHeader.tsx index a2b5566e393..d6effaceb43 100644 --- a/src/components/views/rooms/SimpleRoomHeader.js +++ b/src/components/views/rooms/SimpleRoomHeader.tsx @@ -1,5 +1,5 @@ /* -Copyright 2016 OpenMarket Ltd +Copyright 2016-2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,23 +15,21 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +interface IProps { + title?: string; + // `src` to an image. Optional. + icon?: string; +} + /* * A stripped-down room header used for things like the user settings * and room directory. */ @replaceableComponent("views.rooms.SimpleRoomHeader") -export default class SimpleRoomHeader extends React.Component { - static propTypes = { - title: PropTypes.string, - - // `src` to an image. Optional. - icon: PropTypes.string, - }; - - render() { +export default class SimpleRoomHeader extends React.PureComponent { + public render(): JSX.Element { let icon; if (this.props.icon) { icon = { static currentWidget; - constructor(props) { - super(props); - this._onShowStickersClick = this._onShowStickersClick.bind(this); - this._onHideStickersClick = this._onHideStickersClick.bind(this); - this._launchManageIntegrations = this._launchManageIntegrations.bind(this); - this._removeStickerpickerWidgets = this._removeStickerpickerWidgets.bind(this); - this._updateWidget = this._updateWidget.bind(this); - this._onWidgetAction = this._onWidgetAction.bind(this); - this._onResize = this._onResize.bind(this); - this._onFinished = this._onFinished.bind(this); + private dispatcherRef: string; - this.popoverWidth = 300; - this.popoverHeight = 300; + private prevSentVisibility: boolean; - // This is loaded by _acquireScalarClient on an as-needed basis. - this.scalarClient = null; + private popoverWidth = 300; + private popoverHeight = 300; + // This is loaded by _acquireScalarClient on an as-needed basis. + private scalarClient: ScalarAuthClient = null; + constructor(props: IProps) { + super(props); this.state = { showStickers: false, imError: null, @@ -70,7 +81,7 @@ export default class Stickerpicker extends React.PureComponent { }; } - _acquireScalarClient() { + private acquireScalarClient(): Promise { if (this.scalarClient) return Promise.resolve(this.scalarClient); // TODO: Pick the right manager for the widget if (IntegrationManagers.sharedInstance().hasManager()) { @@ -79,15 +90,15 @@ export default class Stickerpicker extends React.PureComponent { this.forceUpdate(); return this.scalarClient; }).catch((e) => { - this._imError(_td("Failed to connect to integration manager"), e); + this.imError(_td("Failed to connect to integration manager"), e); }); } else { IntegrationManagers.sharedInstance().openNoManagerDialog(); } } - async _removeStickerpickerWidgets() { - const scalarClient = await this._acquireScalarClient(); + private removeStickerpickerWidgets = async (): Promise => { + const scalarClient = await this.acquireScalarClient(); console.log('Removing Stickerpicker widgets'); if (this.state.widgetId) { if (scalarClient) { @@ -109,36 +120,36 @@ export default class Stickerpicker extends React.PureComponent { }).catch((e) => { console.error('Failed to remove sticker picker widget', e); }); - } + }; - componentDidMount() { + public componentDidMount(): void { // Close the sticker picker when the window resizes - window.addEventListener('resize', this._onResize); + window.addEventListener('resize', this.onResize); - this.dispatcherRef = dis.register(this._onWidgetAction); + this.dispatcherRef = dis.register(this.onWidgetAction); // Track updates to widget state in account data - MatrixClientPeg.get().on('accountData', this._updateWidget); + MatrixClientPeg.get().on('accountData', this.updateWidget); // Initialise widget state from current account data - this._updateWidget(); + this.updateWidget(); } - componentWillUnmount() { + public componentWillUnmount(): void { const client = MatrixClientPeg.get(); - if (client) client.removeListener('accountData', this._updateWidget); + if (client) client.removeListener('accountData', this.updateWidget); - window.removeEventListener('resize', this._onResize); + window.removeEventListener('resize', this.onResize); if (this.dispatcherRef) { dis.unregister(this.dispatcherRef); } } - componentDidUpdate(prevProps, prevState) { - this._sendVisibilityToWidget(this.state.showStickers); + public componentDidUpdate(prevProps: IProps, prevState: IState): void { + this.sendVisibilityToWidget(this.state.showStickers); } - _imError(errorMsg, e) { + private imError(errorMsg: string, e: Error): void { console.error(errorMsg, e); this.setState({ showStickers: false, @@ -146,7 +157,7 @@ export default class Stickerpicker extends React.PureComponent { }); } - _updateWidget() { + private updateWidget = (): void => { const stickerpickerWidget = WidgetUtils.getStickerpickerWidgets()[0]; if (!stickerpickerWidget) { Stickerpicker.currentWidget = null; @@ -175,9 +186,9 @@ export default class Stickerpicker extends React.PureComponent { stickerpickerWidget, widgetId: stickerpickerWidget ? stickerpickerWidget.id : null, }); - } + }; - _onWidgetAction(payload) { + private onWidgetAction = (payload: ActionPayload): void => { switch (payload.action) { case "user_widget_updated": this.forceUpdate(); @@ -191,11 +202,11 @@ export default class Stickerpicker extends React.PureComponent { this.setState({ showStickers: false }); break; } - } + }; - _defaultStickerpickerContent() { + private defaultStickerpickerContent(): JSX.Element { return ( -

{ _t("You don't currently have any stickerpacks enabled") }

{ _t("Add some now") }

@@ -204,29 +215,29 @@ export default class Stickerpicker extends React.PureComponent { ); } - _errorStickerpickerContent() { + private errorStickerpickerContent(): JSX.Element { return ( -
+

{ this.state.imError }

); } - _sendVisibilityToWidget(visible) { + private sendVisibilityToWidget(visible: boolean): void { if (!this.state.stickerpickerWidget) return; const messaging = WidgetMessagingStore.instance.getMessagingForId(this.state.stickerpickerWidget.id); - if (messaging && visible !== this._prevSentVisibility) { + if (messaging && visible !== this.prevSentVisibility) { messaging.updateVisibility(visible).catch(err => { console.error("Error updating widget visibility: ", err); }); - this._prevSentVisibility = visible; + this.prevSentVisibility = visible; } } - _getStickerpickerContent() { + public getStickerpickerContent(): JSX.Element { // Handle integration manager errors - if (this.state._imError) { - return this._errorStickerpickerContent(); + if (this.state.imError) { + return this.errorStickerpickerContent(); } // Stickers @@ -239,12 +250,11 @@ export default class Stickerpicker extends React.PureComponent { // Use a separate ReactDOM tree to render the AppTile separately so that it persists and does // not unmount when we (a) close the sticker picker (b) switch rooms. It's properties are still // updated. - const PersistedElement = sdk.getComponent("elements.PersistedElement"); // Load stickerpack content if (stickerpickerWidget && stickerpickerWidget.content && stickerpickerWidget.content.url) { // Set default name - stickerpickerWidget.content.name = stickerpickerWidget.name || _t("Stickerpack"); + stickerpickerWidget.content.name = stickerpickerWidget.content.name || _t("Stickerpack"); // FIXME: could this use the same code as other apps? const stickerApp = { @@ -275,12 +285,12 @@ export default class Stickerpicker extends React.PureComponent { creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId} waitForIframeLoad={true} showMenubar={true} - onEditClick={this._launchManageIntegrations} - onDeleteClick={this._removeStickerpickerWidgets} + onEditClick={this.launchManageIntegrations} + onDeleteClick={this.removeStickerpickerWidgets} showTitle={false} showCancel={false} showPopout={false} - onMinimiseClick={this._onHideStickersClick} + onMinimiseClick={this.onHideStickersClick} handleMinimisePointerEvents={true} userWidget={true} /> @@ -290,7 +300,7 @@ export default class Stickerpicker extends React.PureComponent { ); } else { // Default content to show if stickerpicker widget not added - stickersContent = this._defaultStickerpickerContent(); + stickersContent = this.defaultStickerpickerContent(); } return stickersContent; } @@ -300,7 +310,7 @@ export default class Stickerpicker extends React.PureComponent { * Show the sticker picker overlay * If no stickerpacks have been added, show a link to the integration manager add sticker packs page. */ - _onShowStickersClick(e) { + private onShowStickersClick = (e: React.MouseEvent): void => { if (!SettingsStore.getValue("integrationProvisioning")) { // Intercept this case and spawn a warning. return IntegrationManagers.sharedInstance().showDisabledDialog(); @@ -308,7 +318,7 @@ export default class Stickerpicker extends React.PureComponent { // XXX: Simplify by using a context menu that is positioned relative to the sticker picker button - const buttonRect = e.target.getBoundingClientRect(); + const buttonRect = e.currentTarget.getBoundingClientRect(); // The window X and Y offsets are to adjust position when zoomed in to page let x = buttonRect.right + window.pageXOffset - 41; @@ -324,50 +334,50 @@ export default class Stickerpicker extends React.PureComponent { // Offset the chevron location, which is relative to the left of the context menu // (10 = offset when context menu would not be displayed off viewport) // (2 = context menu borders) - const stickerPickerChevronOffset = Math.max(10, 2 + window.pageXOffset + buttonRect.left - x); + const stickerpickerChevronOffset = Math.max(10, 2 + window.pageXOffset + buttonRect.left - x); const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19; this.setState({ showStickers: true, - stickerPickerX: x, - stickerPickerY: y, - stickerPickerChevronOffset, + stickerpickerX: x, + stickerpickerY: y, + stickerpickerChevronOffset, }); - } + }; /** * Trigger hiding of the sticker picker overlay * @param {Event} ev Event that triggered the function call */ - _onHideStickersClick(ev) { + private onHideStickersClick = (ev: React.MouseEvent): void => { if (this.state.showStickers) { this.setState({ showStickers: false }); } - } + }; /** * Called when the window is resized */ - _onResize() { + private onResize = (): void => { if (this.state.showStickers) { this.setState({ showStickers: false }); } - } + }; /** * The stickers picker was hidden */ - _onFinished() { + private onFinished = (): void => { if (this.state.showStickers) { this.setState({ showStickers: false }); } - } + }; /** * Launch the integration manager on the stickers integration page */ - _launchManageIntegrations = () => { + private launchManageIntegrations = (): void => { // TODO: Open the right integration manager for the widget if (SettingsStore.getValue("feature_many_integration_managers")) { IntegrationManagers.sharedInstance().openAll( @@ -384,7 +394,7 @@ export default class Stickerpicker extends React.PureComponent { } }; - render() { + public render(): JSX.Element { let stickerPicker; let stickersButton; const className = classNames( @@ -400,26 +410,24 @@ export default class Stickerpicker extends React.PureComponent { id='stickersButton' key="controls_hide_stickers" className={className} - onClick={this._onHideStickersClick} - active={this.state.showStickers.toString()} + onClick={this.onHideStickersClick} title={_t("Hide Stickers")} />; - const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu'); stickerPicker = - + ; } else { // Show show-stickers button @@ -428,7 +436,7 @@ export default class Stickerpicker extends React.PureComponent { id='stickersButton' key="controls_show_stickers" className="mx_MessageComposer_button mx_MessageComposer_stickers" - onClick={this._onShowStickersClick} + onClick={this.onShowStickersClick} title={_t("Show Stickers")} />; } diff --git a/src/components/views/rooms/TopUnreadMessagesBar.js b/src/components/views/rooms/TopUnreadMessagesBar.tsx similarity index 80% rename from src/components/views/rooms/TopUnreadMessagesBar.js rename to src/components/views/rooms/TopUnreadMessagesBar.tsx index d2a3e3a3030..14f9a27f2d0 100644 --- a/src/components/views/rooms/TopUnreadMessagesBar.js +++ b/src/components/views/rooms/TopUnreadMessagesBar.tsx @@ -1,7 +1,5 @@ /* -Copyright 2016 OpenMarket Ltd -Copyright 2017 Vector Creations Ltd -Copyright 2019 New Vector Ltd +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,19 +15,18 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import AccessibleButton from '../elements/AccessibleButton'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -@replaceableComponent("views.rooms.TopUnreadMessagesBar") -export default class TopUnreadMessagesBar extends React.Component { - static propTypes = { - onScrollUpClick: PropTypes.func, - onCloseClick: PropTypes.func, - }; +interface IProps { + onScrollUpClick?: (e: React.MouseEvent) => void; + onCloseClick?: (e: React.MouseEvent) => void; +} - render() { +@replaceableComponent("views.rooms.TopUnreadMessagesBar") +export default class TopUnreadMessagesBar extends React.PureComponent { + public render(): JSX.Element { return (
{ +interface IProps { + avatarUrl?: string; + avatarName: string; // name of user/room the avatar belongs to + uploadAvatar?: (e: React.MouseEvent) => void; + removeAvatar?: (e: React.MouseEvent) => void; + avatarAltText: string; +} + +const AvatarSetting: React.FC = ({ avatarUrl, avatarAltText, avatarName, uploadAvatar, removeAvatar }) => { const [isHovering, setIsHovering] = useState(false); const hoveringProps = { onMouseEnter: () => setIsHovering(true), @@ -78,12 +85,4 @@ const AvatarSetting = ({ avatarUrl, avatarAltText, avatarName, uploadAvatar, rem
; }; -AvatarSetting.propTypes = { - avatarUrl: PropTypes.string, - avatarName: PropTypes.string.isRequired, // name of user/room the avatar belongs to - uploadAvatar: PropTypes.func, - removeAvatar: PropTypes.func, - avatarAltText: PropTypes.string.isRequired, -}; - export default AvatarSetting; diff --git a/src/components/views/settings/ChangeAvatar.js b/src/components/views/settings/ChangeAvatar.tsx similarity index 71% rename from src/components/views/settings/ChangeAvatar.js rename to src/components/views/settings/ChangeAvatar.tsx index c3a1544cdc6..36178540f7e 100644 --- a/src/components/views/settings/ChangeAvatar.js +++ b/src/components/views/settings/ChangeAvatar.tsx @@ -1,5 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd +Copyright 2015-2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,54 +15,65 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; +import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; +import { Room } from 'matrix-js-sdk/src/models/room'; import { MatrixClientPeg } from "../../../MatrixClientPeg"; -import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import Spinner from '../elements/Spinner'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media"; +import RoomAvatar from '../avatars/RoomAvatar'; +import BaseAvatar from '../avatars/BaseAvatar'; + +interface IProps { + initialAvatarUrl?: string; + room?: Room; + // if false, you need to call changeAvatar.onFileSelected yourself. + showUploadSection?: boolean; + width?: number; + height?: number; + className?: string; +} -@replaceableComponent("views.settings.ChangeAvatar") -export default class ChangeAvatar extends React.Component { - static propTypes = { - initialAvatarUrl: PropTypes.string, - room: PropTypes.object, - // if false, you need to call changeAvatar.onFileSelected yourself. - showUploadSection: PropTypes.bool, - width: PropTypes.number, - height: PropTypes.number, - className: PropTypes.string, - }; +interface IState { + avatarUrl?: string; + errorText?: string; + phase?: Phases; +} - static Phases = { - Display: "display", - Uploading: "uploading", - Error: "error", - }; +enum Phases { + Display = "display", + Uploading = "uploading", + Error = "error", +} - static defaultProps = { +@replaceableComponent("views.settings.ChangeAvatar") +export default class ChangeAvatar extends React.Component { + public static defaultProps = { showUploadSection: true, className: "", width: 80, height: 80, }; - constructor(props) { + private avatarSet = false; + + constructor(props: IProps) { super(props); this.state = { avatarUrl: this.props.initialAvatarUrl, - phase: ChangeAvatar.Phases.Display, + phase: Phases.Display, }; } - componentDidMount() { + public componentDidMount(): void { MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents); } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line camelcase + // eslint-disable-next-line + public UNSAFE_componentWillReceiveProps(newProps: IProps): void { if (this.avatarSet) { // don't clobber what the user has just set return; @@ -72,13 +83,13 @@ export default class ChangeAvatar extends React.Component { }); } - componentWillUnmount() { + public componentWillUnmount(): void { if (MatrixClientPeg.get()) { MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents); } } - onRoomStateEvents = (ev) => { + private onRoomStateEvents = (ev: MatrixEvent) => { if (!this.props.room) { return; } @@ -94,18 +105,17 @@ export default class ChangeAvatar extends React.Component { } }; - setAvatarFromFile(file) { + private setAvatarFromFile(file: File): Promise<{}> { let newUrl = null; this.setState({ - phase: ChangeAvatar.Phases.Uploading, + phase: Phases.Uploading, }); - const self = this; - const httpPromise = MatrixClientPeg.get().uploadContent(file).then(function(url) { + const httpPromise = MatrixClientPeg.get().uploadContent(file).then((url) => { newUrl = url; - if (self.props.room) { + if (this.props.room) { return MatrixClientPeg.get().sendStateEvent( - self.props.room.roomId, + this.props.room.roomId, 'm.room.avatar', { url: url }, '', @@ -115,38 +125,37 @@ export default class ChangeAvatar extends React.Component { } }); - httpPromise.then(function() { - self.setState({ - phase: ChangeAvatar.Phases.Display, + httpPromise.then(() => { + this.setState({ + phase: Phases.Display, avatarUrl: mediaFromMxc(newUrl).srcHttp, }); - }, function(error) { - self.setState({ - phase: ChangeAvatar.Phases.Error, + }, () => { + this.setState({ + phase: Phases.Error, }); - self.onError(error); + this.onError(); }); return httpPromise; } - onFileSelected = (ev) => { + private onFileSelected = (ev: React.ChangeEvent) => { this.avatarSet = true; return this.setAvatarFromFile(ev.target.files[0]); }; - onError = (error) => { + private onError = (): void => { this.setState({ errorText: _t("Failed to upload profile picture!"), }); }; - render() { + public render(): JSX.Element { let avatarImg; // Having just set an avatar we just display that since it will take a little // time to propagate through to the RoomAvatar. if (this.props.room && !this.avatarSet) { - const RoomAvatar = sdk.getComponent('avatars.RoomAvatar'); avatarImg = ; } else { - const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); // XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ? avatarImg =
@@ -188,7 +196,7 @@ export default class ChangeAvatar extends React.Component { { uploadSection }
); - case ChangeAvatar.Phases.Uploading: + case Phases.Uploading: return ( ); diff --git a/src/components/views/settings/ChangeDisplayName.js b/src/components/views/settings/ChangeDisplayName.tsx similarity index 73% rename from src/components/views/settings/ChangeDisplayName.js rename to src/components/views/settings/ChangeDisplayName.tsx index 2f336e18c68..9f0f813ec61 100644 --- a/src/components/views/settings/ChangeDisplayName.js +++ b/src/components/views/settings/ChangeDisplayName.tsx @@ -1,7 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2018 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,14 +15,14 @@ limitations under the License. */ import React from 'react'; -import * as sdk from '../../../index'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import EditableTextContainer from "../elements/EditableTextContainer"; @replaceableComponent("views.settings.ChangeDisplayName") export default class ChangeDisplayName extends React.Component { - _getDisplayName = async () => { + private getDisplayName = async (): Promise => { const cli = MatrixClientPeg.get(); try { const res = await cli.getProfileInfo(cli.getUserId()); @@ -34,21 +32,20 @@ export default class ChangeDisplayName extends React.Component { } }; - _changeDisplayName = (newDisplayname) => { + private changeDisplayName = (newDisplayname: string): Promise<{}> => { const cli = MatrixClientPeg.get(); - return cli.setDisplayName(newDisplayname).catch(function(e) { - throw new Error("Failed to set display name", e); + return cli.setDisplayName(newDisplayname).catch(function() { + throw new Error("Failed to set display name"); }); }; - render() { - const EditableTextContainer = sdk.getComponent('elements.EditableTextContainer'); + public render(): JSX.Element { return ( + onSubmit={this.changeDisplayName} /> ); } } diff --git a/src/components/views/settings/DevicesPanel.js b/src/components/views/settings/DevicesPanel.tsx similarity index 77% rename from src/components/views/settings/DevicesPanel.js rename to src/components/views/settings/DevicesPanel.tsx index 0f052332ee7..9a1321619e0 100644 --- a/src/components/views/settings/DevicesPanel.js +++ b/src/components/views/settings/DevicesPanel.tsx @@ -1,6 +1,5 @@ /* -Copyright 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,52 +15,58 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; +import { IMyDevice } from "matrix-js-sdk/src/client"; -import * as sdk from '../../../index'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import InteractiveAuthDialog from "../dialogs/InteractiveAuthDialog"; +import DevicesPanelEntry from "./DevicesPanelEntry"; +import Spinner from "../elements/Spinner"; +import AccessibleButton from "../elements/AccessibleButton"; + +interface IProps { + className?: string; +} + +interface IState { + devices: IMyDevice[]; + deviceLoadError?: string; + selectedDevices: string[]; + deleting?: boolean; +} @replaceableComponent("views.settings.DevicesPanel") -export default class DevicesPanel extends React.Component { - constructor(props) { - super(props); +export default class DevicesPanel extends React.Component { + private unmounted = false; + constructor(props: IProps) { + super(props); this.state = { - devices: undefined, - deviceLoadError: undefined, - + devices: [], selectedDevices: [], - deleting: false, }; - - this._unmounted = false; - - this._renderDevice = this._renderDevice.bind(this); - this._onDeviceSelectionToggled = this._onDeviceSelectionToggled.bind(this); - this._onDeleteClick = this._onDeleteClick.bind(this); } - componentDidMount() { - this._loadDevices(); + public componentDidMount(): void { + this.loadDevices(); } - componentWillUnmount() { - this._unmounted = true; + public componentWillUnmount(): void { + this.unmounted = true; } - _loadDevices() { + private loadDevices(): void { MatrixClientPeg.get().getDevices().then( (resp) => { - if (this._unmounted) { return; } + if (this.unmounted) { return; } this.setState({ devices: resp.devices || [] }); }, (error) => { - if (this._unmounted) { return; } + if (this.unmounted) { return; } let errtxt; if (error.httpStatus == 404) { // 404 probably means the HS doesn't yet support the API. @@ -79,7 +84,7 @@ export default class DevicesPanel extends React.Component { * compare two devices, sorting from most-recently-seen to least-recently-seen * (and then, for stability, by device id) */ - _deviceCompare(a, b) { + private deviceCompare(a: IMyDevice, b: IMyDevice): number { // return < 0 if a comes before b, > 0 if a comes after b. const lastSeenDelta = (b.last_seen_ts || 0) - (a.last_seen_ts || 0); @@ -91,8 +96,8 @@ export default class DevicesPanel extends React.Component { return (idA < idB) ? -1 : (idA > idB) ? 1 : 0; } - _onDeviceSelectionToggled(device) { - if (this._unmounted) { return; } + private onDeviceSelectionToggled = (device: IMyDevice): void => { + if (this.unmounted) { return; } const deviceId = device.device_id; this.setState((state, props) => { @@ -108,22 +113,21 @@ export default class DevicesPanel extends React.Component { return { selectedDevices }; }); - } + }; - _onDeleteClick() { + private onDeleteClick = (): void => { this.setState({ deleting: true, }); - this._makeDeleteRequest(null).catch((error) => { - if (this._unmounted) { return; } + this.makeDeleteRequest(null).catch((error) => { + if (this.unmounted) { return; } if (error.httpStatus !== 401 || !error.data || !error.data.flows) { // doesn't look like an interactive-auth failure throw error; } // pop up an interactive auth dialog - const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog"); const numDevices = this.state.selectedDevices.length; const dialogAesthetics = { @@ -148,7 +152,7 @@ export default class DevicesPanel extends React.Component { title: _t("Authentication"), matrixClient: MatrixClientPeg.get(), authData: error.data, - makeRequest: this._makeDeleteRequest.bind(this), + makeRequest: this.makeDeleteRequest.bind(this), aestheticsForStagePhases: { [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, @@ -156,15 +160,16 @@ export default class DevicesPanel extends React.Component { }); }).catch((e) => { console.error("Error deleting sessions", e); - if (this._unmounted) { return; } + if (this.unmounted) { return; } }).finally(() => { this.setState({ deleting: false, }); }); - } + }; - _makeDeleteRequest(auth) { + // TODO: proper typing for auth + private makeDeleteRequest(auth?: any): Promise { return MatrixClientPeg.get().deleteMultipleDevices(this.state.selectedDevices, auth).then( () => { // Remove the deleted devices from `devices`, reset selection to [] @@ -178,20 +183,16 @@ export default class DevicesPanel extends React.Component { ); } - _renderDevice(device) { - const DevicesPanelEntry = sdk.getComponent('settings.DevicesPanelEntry'); + private renderDevice = (device: IMyDevice): JSX.Element => { return ; - } - - render() { - const Spinner = sdk.getComponent("elements.Spinner"); - const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); + }; + public render(): JSX.Element { if (this.state.deviceLoadError !== undefined) { const classes = classNames(this.props.className, "error"); return ( @@ -204,15 +205,14 @@ export default class DevicesPanel extends React.Component { const devices = this.state.devices; if (devices === undefined) { // still loading - const classes = this.props.className; - return ; + return ; } - devices.sort(this._deviceCompare); + devices.sort(this.deviceCompare); const deleteButton = this.state.deleting ? : - + { _t("Delete %(count)s sessions", { count: this.state.selectedDevices.length }) } ; @@ -227,12 +227,8 @@ export default class DevicesPanel extends React.Component { { this.state.selectedDevices.length > 0 ? deleteButton : null }
- { devices.map(this._renderDevice) } + { devices.map(this.renderDevice) }
); } } - -DevicesPanel.propTypes = { - className: PropTypes.string, -}; diff --git a/src/components/views/settings/DevicesPanelEntry.js b/src/components/views/settings/DevicesPanelEntry.tsx similarity index 74% rename from src/components/views/settings/DevicesPanelEntry.js rename to src/components/views/settings/DevicesPanelEntry.tsx index a5b674b8f63..d033bc41a9d 100644 --- a/src/components/views/settings/DevicesPanelEntry.js +++ b/src/components/views/settings/DevicesPanelEntry.tsx @@ -1,5 +1,5 @@ /* -Copyright 2016 OpenMarket Ltd +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,30 +15,28 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; +import { IMyDevice } from 'matrix-js-sdk/src/client'; -import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { formatDate } from '../../../DateUtils'; import StyledCheckbox from '../elements/StyledCheckbox'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import EditableTextContainer from "../elements/EditableTextContainer"; -@replaceableComponent("views.settings.DevicesPanelEntry") -export default class DevicesPanelEntry extends React.Component { - constructor(props) { - super(props); - - this._unmounted = false; - this.onDeviceToggled = this.onDeviceToggled.bind(this); - this._onDisplayNameChanged = this._onDisplayNameChanged.bind(this); - } +interface IProps { + device?: IMyDevice; + onDeviceToggled?: (device: IMyDevice) => void; + selected?: boolean; +} - componentWillUnmount() { - this._unmounted = true; - } +@replaceableComponent("views.settings.DevicesPanelEntry") +export default class DevicesPanelEntry extends React.Component { + public static defaultProps = { + onDeviceToggled: () => {}, + }; - _onDisplayNameChanged(value) { + private onDisplayNameChanged = (value: string): Promise<{}> => { const device = this.props.device; return MatrixClientPeg.get().setDeviceDetails(device.device_id, { display_name: value, @@ -46,15 +44,13 @@ export default class DevicesPanelEntry extends React.Component { console.error("Error setting session display name", e); throw new Error(_t("Failed to set display name")); }); - } + }; - onDeviceToggled() { + private onDeviceToggled = (): void => { this.props.onDeviceToggled(this.props.device); - } - - render() { - const EditableTextContainer = sdk.getComponent('elements.EditableTextContainer'); + }; + public render(): JSX.Element { const device = this.props.device; let lastSeen = ""; @@ -76,7 +72,7 @@ export default class DevicesPanelEntry extends React.Component {
@@ -90,12 +86,3 @@ export default class DevicesPanelEntry extends React.Component { ); } } - -DevicesPanelEntry.propTypes = { - device: PropTypes.object.isRequired, - onDeviceToggled: PropTypes.func, -}; - -DevicesPanelEntry.defaultProps = { - onDeviceToggled: function() {}, -}; diff --git a/src/components/views/settings/IntegrationManager.js b/src/components/views/settings/IntegrationManager.tsx similarity index 68% rename from src/components/views/settings/IntegrationManager.js rename to src/components/views/settings/IntegrationManager.tsx index 9f2985df14c..0b880c019f9 100644 --- a/src/components/views/settings/IntegrationManager.js +++ b/src/components/views/settings/IntegrationManager.tsx @@ -1,6 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,53 +15,55 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; -import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import dis from '../../../dispatcher/dispatcher'; import { Key } from "../../../Keyboard"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { ActionPayload } from '../../../dispatcher/payloads'; +import Spinner from "../elements/Spinner"; -@replaceableComponent("views.settings.IntegrationManager") -export default class IntegrationManager extends React.Component { - static propTypes = { - // false to display an error saying that we couldn't connect to the integration manager - connected: PropTypes.bool.isRequired, +interface IProps { + // false to display an error saying that we couldn't connect to the integration manager + connected: boolean; - // true to display a loading spinner - loading: PropTypes.bool.isRequired, + // true to display a loading spinner + loading: boolean; - // The source URL to load - url: PropTypes.string, + // The source URL to load + url?: string; - // callback when the manager is dismissed - onFinished: PropTypes.func.isRequired, - }; + // callback when the manager is dismissed + onFinished: () => void; +} + +interface IState { + errored: boolean; +} - static defaultProps = { +@replaceableComponent("views.settings.IntegrationManager") +export default class IntegrationManager extends React.Component { + private dispatcherRef: string; + + public static defaultProps = { connected: true, loading: false, }; - constructor(props) { - super(props); - - this.state = { - errored: false, - }; - } + public state = { + errored: false, + }; - componentDidMount() { + public componentDidMount(): void { this.dispatcherRef = dis.register(this.onAction); document.addEventListener("keydown", this.onKeyDown); } - componentWillUnmount() { + public componentWillUnmount(): void { dis.unregister(this.dispatcherRef); document.removeEventListener("keydown", this.onKeyDown); } - onKeyDown = (ev) => { + private onKeyDown = (ev: KeyboardEvent): void => { if (ev.key === Key.ESCAPE) { ev.stopPropagation(); ev.preventDefault(); @@ -70,19 +71,18 @@ export default class IntegrationManager extends React.Component { } }; - onAction = (payload) => { + private onAction = (payload: ActionPayload): void => { if (payload.action === 'close_scalar') { this.props.onFinished(); } }; - onError = () => { + private onError = (): void => { this.setState({ errored: true }); }; - render() { + public render(): JSX.Element { if (this.props.loading) { - const Spinner = sdk.getComponent("elements.Spinner"); return (

{ _t("Connecting to integration manager...") }

diff --git a/src/components/views/settings/LayoutSwitcher.tsx b/src/components/views/settings/LayoutSwitcher.tsx index dd7accf9a8a..ad8abd00337 100644 --- a/src/components/views/settings/LayoutSwitcher.tsx +++ b/src/components/views/settings/LayoutSwitcher.tsx @@ -26,7 +26,7 @@ import { Layout } from "../../../settings/Layout"; import { SettingLevel } from "../../../settings/SettingLevel"; interface IProps { - userId: string; + userId?: string; displayName: string; avatarUrl: string; messagePreviewText: string; diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.tsx similarity index 81% rename from src/components/views/settings/ProfileSettings.js rename to src/components/views/settings/ProfileSettings.tsx index d05fca983c1..43364639599 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.tsx @@ -1,5 +1,5 @@ /* -Copyright 2019 New Vector Ltd +Copyright 2019 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,17 +19,30 @@ import { _t } from "../../../languageHandler"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import Field from "../elements/Field"; import { getHostingLink } from '../../../utils/HostingLink'; -import * as sdk from "../../../index"; import { OwnProfileStore } from "../../../stores/OwnProfileStore"; import Modal from "../../../Modal"; import ErrorDialog from "../dialogs/ErrorDialog"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media"; +import AccessibleButton from '../elements/AccessibleButton'; +import AvatarSetting from './AvatarSetting'; + +interface IState { + userId?: string; + originalDisplayName?: string; + displayName?: string; + originalAvatarUrl?: string; + avatarUrl?: string | ArrayBuffer; + avatarFile?: File; + enableProfileSave?: boolean; +} @replaceableComponent("views.settings.ProfileSettings") -export default class ProfileSettings extends React.Component { - constructor() { - super(); +export default class ProfileSettings extends React.Component<{}, IState> { + private avatarUpload: React.RefObject = createRef(); + + constructor(props: {}) { + super(props); const client = MatrixClientPeg.get(); let avatarUrl = OwnProfileStore.instance.avatarMxc; @@ -43,17 +56,15 @@ export default class ProfileSettings extends React.Component { avatarFile: null, enableProfileSave: false, }; - - this._avatarUpload = createRef(); } - _uploadAvatar = () => { - this._avatarUpload.current.click(); + private uploadAvatar = (): void => { + this.avatarUpload.current.click(); }; - _removeAvatar = () => { + private removeAvatar = (): void => { // clear file upload field so same file can be selected - this._avatarUpload.current.value = ""; + this.avatarUpload.current.value = ""; this.setState({ avatarUrl: null, avatarFile: null, @@ -61,7 +72,7 @@ export default class ProfileSettings extends React.Component { }); }; - _cancelProfileChanges = async (e) => { + private cancelProfileChanges = async (e: React.MouseEvent): Promise => { e.stopPropagation(); e.preventDefault(); @@ -74,7 +85,7 @@ export default class ProfileSettings extends React.Component { }); }; - _saveProfile = async (e) => { + private saveProfile = async (e: React.FormEvent): Promise => { e.stopPropagation(); e.preventDefault(); @@ -82,7 +93,7 @@ export default class ProfileSettings extends React.Component { this.setState({ enableProfileSave: false }); const client = MatrixClientPeg.get(); - const newState = {}; + const newState: IState = {}; const displayName = this.state.displayName.trim(); try { @@ -115,14 +126,14 @@ export default class ProfileSettings extends React.Component { this.setState(newState); }; - _onDisplayNameChanged = (e) => { + private onDisplayNameChanged = (e: React.ChangeEvent): void => { this.setState({ displayName: e.target.value, enableProfileSave: true, }); }; - _onAvatarChanged = (e) => { + private onAvatarChanged = (e: React.ChangeEvent): void => { if (!e.target.files || !e.target.files.length) { this.setState({ avatarUrl: this.state.originalAvatarUrl, @@ -144,7 +155,7 @@ export default class ProfileSettings extends React.Component { reader.readAsDataURL(file); }; - render() { + public render(): JSX.Element { const hostingSignupLink = getHostingLink('user-settings'); let hostingSignup = null; if (hostingSignupLink) { @@ -161,20 +172,18 @@ export default class ProfileSettings extends React.Component { ; } - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - const AvatarSetting = sdk.getComponent('settings.AvatarSetting'); return (
@@ -185,7 +194,7 @@ export default class ProfileSettings extends React.Component { type="text" value={this.state.displayName} autoComplete="off" - onChange={this._onDisplayNameChanged} + onChange={this.onDisplayNameChanged} />

{ this.state.userId } @@ -193,22 +202,22 @@ export default class ProfileSettings extends React.Component {

+ uploadAvatar={this.uploadAvatar} + removeAvatar={this.removeAvatar} />
{ _t("Cancel") } diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx index d9e97d570b0..081b1a86989 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx @@ -149,10 +149,12 @@ export default class SecurityRoomSettingsTab extends React.Componentnew encrypted room for " + "the conversation you plan to have.", null, - { "a": (sub) => { - dialog.close(); - this.createNewRoom(false, true); - }}> { sub } }, + { "a": (sub) => { + dialog.close(); + this.createNewRoom(false, true); + }}> { sub } }, ) }

, @@ -248,10 +250,12 @@ export default class SecurityRoomSettingsTab extends React.Component { - dialog.close(); - this.createNewRoom(true, false); - }}> { sub } , + "a": (sub) => { + dialog.close(); + this.createNewRoom(true, false); + }}> { sub } , }, ) }

, diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index cbf0b7916c4..bc54a8155c1 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -67,7 +67,7 @@ interface IState extends IThemeState { showAdvanced: boolean; layout: Layout; // User profile data for the message preview - userId: string; + userId?: string; displayName: string; avatarUrl: string; } @@ -92,8 +92,8 @@ export default class AppearanceUserSettingsTab extends React.Component private getVersionInfo(): { appVersion: string, olmVersion: string } { const brand = SdkConfig.get().brand; const appVersion = this.state.appVersion || 'unknown'; - let olmVersion = MatrixClientPeg.get().olmVersion; - olmVersion = olmVersion ? `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}` : ''; + const olmVersionTuple = MatrixClientPeg.get().olmVersion; + const olmVersion = olmVersionTuple + ? `${olmVersionTuple[0]}.${olmVersionTuple[1]}.${olmVersionTuple[2]}` + : ''; return { appVersion: `${_t("%(brand)s version:", { brand })} ${appVersion}`, diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js index 8cd991134f7..943eb874edd 100644 --- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js @@ -88,7 +88,6 @@ export default class LabsUserSettingsTab extends React.Component { - { hiddenReadReceipts }
; } diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index 21c3ab24ecc..22095379671 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -172,7 +172,8 @@ export default class PreferencesUserSettingsTab extends React.Component = ({ const cli = useContext(MatrixClientContext); const domain = cli.getDomain(); + const onKeyDown = (ev: KeyboardEvent) => { + if (ev.key === Key.ENTER) { + onSubmit(ev); + } + }; + return @@ -174,9 +181,11 @@ export const SpaceCreateForm: React.FC = ({ } setName(newName); }} + onKeyDown={onKeyDown} ref={nameFieldRef} onValidate={spaceNameValidator} disabled={busy} + autoComplete="off" /> { showAliasField @@ -188,6 +197,7 @@ export const SpaceCreateForm: React.FC = ({ placeholder={name ? nameToAlias(name, domain) : _t("e.g. my-space")} label={_t("Address")} disabled={busy} + onKeyDown={onKeyDown} /> : null } diff --git a/src/components/views/spaces/SpacePublicShare.tsx b/src/components/views/spaces/SpacePublicShare.tsx index 39e5115e553..f8860ddd54b 100644 --- a/src/components/views/spaces/SpacePublicShare.tsx +++ b/src/components/views/spaces/SpacePublicShare.tsx @@ -39,7 +39,7 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => { onClick={async () => { const permalinkCreator = new RoomPermalinkCreator(space); permalinkCreator.load(); - const success = await copyPlaintext(permalinkCreator.forRoom()); + const success = await copyPlaintext(permalinkCreator.forShareableRoom()); const text = success ? _t("Copied!") : _t("Failed to copy"); setCopiedText(text); await sleep(5000); @@ -54,8 +54,8 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => { { space.canInvite(MatrixClientPeg.get()?.getUserId()) ? { - showRoomInviteDialog(space.roomId); if (onFinished) onFinished(); + showRoomInviteDialog(space.roomId); }} >

{ _t("Invite people") }

diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx index 4607b750eba..13461c35918 100644 --- a/src/components/views/voip/VideoFeed.tsx +++ b/src/components/views/voip/VideoFeed.tsx @@ -45,6 +45,7 @@ interface IProps { interface IState { audioMuted: boolean; videoMuted: boolean; + speaking: boolean; } @replaceableComponent("views.voip.VideoFeed") @@ -57,6 +58,7 @@ export default class VideoFeed extends React.PureComponent { this.state = { audioMuted: this.props.feed.isAudioMuted(), videoMuted: this.props.feed.isVideoMuted(), + speaking: false, }; } @@ -103,11 +105,19 @@ export default class VideoFeed extends React.PureComponent { if (oldFeed) { this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream); this.props.feed.removeListener(CallFeedEvent.MuteStateChanged, this.onMuteStateChanged); + if (this.props.feed.purpose === SDPStreamMetadataPurpose.Usermedia) { + this.props.feed.removeListener(CallFeedEvent.Speaking, this.onSpeaking); + this.props.feed.measureVolumeActivity(false); + } this.stopMedia(); } if (newFeed) { this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream); this.props.feed.addListener(CallFeedEvent.MuteStateChanged, this.onMuteStateChanged); + if (this.props.feed.purpose === SDPStreamMetadataPurpose.Usermedia) { + this.props.feed.addListener(CallFeedEvent.Speaking, this.onSpeaking); + this.props.feed.measureVolumeActivity(true); + } this.playMedia(); } } @@ -162,6 +172,10 @@ export default class VideoFeed extends React.PureComponent { }); }; + private onSpeaking = (speaking: boolean): void => { + this.setState({ speaking }); + }; + private onResize = (e) => { if (this.props.onResize && !this.props.feed.isLocal()) { this.props.onResize(e); @@ -173,6 +187,7 @@ export default class VideoFeed extends React.PureComponent { const wrapperClasses = classnames("mx_VideoFeed", { mx_VideoFeed_voice: this.state.videoMuted, + mx_VideoFeed_speaking: this.state.speaking, }); const micIconClasses = classnames("mx_VideoFeed_mic", { mx_VideoFeed_mic_muted: this.state.audioMuted, diff --git a/src/createRoom.ts b/src/createRoom.ts index 31774bf56fc..25e7257289d 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -62,6 +62,8 @@ export interface IOpts { roomType?: RoomType | string; historyVisibility?: HistoryVisibility; parentSpace?: Room; + // contextually only makes sense if parentSpace is specified, if true then will be added to parentSpace as suggested + suggested?: boolean; joinRule?: JoinRule; } @@ -228,7 +230,7 @@ export default async function createRoom(opts: IOpts): Promise { } }).then(() => { if (opts.parentSpace) { - return SpaceStore.instance.addRoomToSpace(opts.parentSpace, roomId, [client.getDomain()], true); + return SpaceStore.instance.addRoomToSpace(opts.parentSpace, roomId, [client.getDomain()], opts.suggested); } if (opts.associatedWithCommunity) { return GroupStore.addRoomToGroup(opts.associatedWithCommunity, roomId, false); diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 082d688dc90..65a1fc4040d 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -3368,7 +3368,7 @@ "Failed to update the guest access of this space": "Nepodařilo se aktualizovat přístup hosta do tohoto prostoru", "Failed to update the visibility of this space": "Nepodařilo se aktualizovat viditelnost tohoto prostoru", "e.g. my-space": "např. můj-prostor", - "Silence call": "Tiché volání", + "Silence call": "Ztlumit zvonění", "Sound on": "Zvuk zapnutý", "Show notification badges for People in Spaces": "Zobrazit odznaky oznámení v Lidi v prostorech", "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.": "Pokud je zakázáno, můžete stále přidávat přímé zprávy do osobních prostorů. Pokud je povoleno, automaticky se zobrazí všichni, kteří jsou členy daného prostoru.", @@ -3594,5 +3594,31 @@ "You can change this later.": "Toto můžete změnit později.", "Unknown failure: %(reason)s": "Neznámá chyba: %(reason)s", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Ladící protokoly obsahují údaje o používání aplikace včetně vašeho uživatelského jména, ID nebo aliasů místností nebo skupin, které jste navštívili, s kterými prvky uživatelského rozhraní jste naposledy interagovali a uživatelská jména ostatních uživatelů. Neobsahují zprávy.", - "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Pokud jste odeslali chybu prostřednictvím GitHubu, ladící protokoly nám mohou pomoci problém vysledovat. Ladicí protokoly obsahují údaje o používání aplikace včetně vašeho uživatelského jména, ID nebo aliasů místností nebo skupin, které jste navštívili, s jakými prvky uživatelského rozhraní jste naposledy interagovali a uživatelská jména ostatních uživatelů. Neobsahují zprávy." + "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Pokud jste odeslali chybu prostřednictvím GitHubu, ladící protokoly nám mohou pomoci problém vysledovat. Ladicí protokoly obsahují údaje o používání aplikace včetně vašeho uživatelského jména, ID nebo aliasů místností nebo skupin, které jste navštívili, s jakými prvky uživatelského rozhraní jste naposledy interagovali a uživatelská jména ostatních uživatelů. Neobsahují zprávy.", + "Rooms and spaces": "Místnosti a prostory", + "Results": "Výsledky", + "Enable encryption in settings.": "Povolte šifrování v nastavení.", + "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Vaše soukromé zprávy jsou obvykle šifrované, ale tato místnost není. Obvykle je to způsobeno nepodporovaným zařízením nebo použitou metodou, například e-mailovými pozvánkami.", + "To avoid these issues, create a new public room for the conversation you plan to have.": "Abyste se těmto problémům vyhnuli, vytvořte pro plánovanou konverzaci novou veřejnou místnost.", + "It's not recommended to make encrypted rooms public. It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "Nedoporučujeme šifrované místnosti zveřejňovat. Znamená to, že místnost může kdokoli najít a připojit se k ní, takže si kdokoli může přečíst zprávy. Nezískáte tak žádnou z výhod šifrování. Šifrování zpráv ve veřejné místnosti zpomalí příjem a odesílání zpráv.", + "Are you sure you want to make this encrypted room public?": "Jste si jisti, že chcete tuto šifrovanou místnost zveřejnit?", + "To avoid these issues, create a new encrypted room for the conversation you plan to have.": "Chcete-li se těmto problémům vyhnout, vytvořte pro plánovanou konverzaci novou šifrovanou místnost.", + "It's not recommended to add encryption to public rooms.Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "Nedoporučuje se šifrovat veřejné místnosti.Veřejné místnosti může najít a připojit se k nim kdokoli, takže si v nich může číst zprávy kdokoli. Nezískáte tak žádnou z výhod šifrování a nebudete ho moci později vypnout. Šifrování zpráv ve veřejné místnosti zpomalí příjem a odesílání zpráv.", + "Are you sure you want to add encryption to this public room?": "Opravdu chcete šifrovat tuto veřejnou místnost?", + "Cross-signing is ready but keys are not backed up.": "Křížové podepisování je připraveno, ale klíče nejsou zálohovány.", + "Low bandwidth mode (requires compatible homeserver)": "Režim malé šířky pásma (vyžaduje kompatibilní homeserver)", + "Multiple integration managers (requires manual setup)": "Více správců integrace (vyžaduje ruční nastavení)", + "Threaded messaging": "Zprávy ve vláknech", + "Thread": "Vlákno", + "Show threads": "Zobrazit vlákna", + "The above, but in as well": "Výše uvedené, ale také v ", + "The above, but in any room you are joined or invited to as well": "Výše uvedené, ale také v jakékoli místnosti, ke které jste připojeni nebo do které jste pozváni", + "Autoplay videos": "Automatické přehrávání videí", + "Autoplay GIFs": "Automatické přehrávání GIFů", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s odepnul zprávu z této místnosti. Zobrazit všechny připnuté zprávy.", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s odepnul zprávu z této místnosti. Zobrazit všechny připnuté zprávy.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s připnul zprávu k této místnosti. Zobrazit všechny připnuté zprávy.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s připnul zprávu k této místnosti. Zobrazit všechny připnuté zprávy.", + "Currently, %(count)s spaces have access|one": "V současné době má prostor přístup", + "& %(count)s more|one": "a %(count)s další" } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b9a6b5e04c4..7d754a618ab 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -52,12 +52,12 @@ "A microphone and webcam are plugged in and set up correctly": "A microphone and webcam are plugged in and set up correctly", "Permission is granted to use the webcam": "Permission is granted to use the webcam", "No other application is using the webcam": "No other application is using the webcam", + "Already in call": "Already in call", + "You're already in a call with this person.": "You're already in a call with this person.", "VoIP is unsupported": "VoIP is unsupported", "You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.", "Too Many Calls": "Too Many Calls", "You've reached the maximum number of simultaneous calls.": "You've reached the maximum number of simultaneous calls.", - "Already in call": "Already in call", - "You're already in a call with this person.": "You're already in a call with this person.", "You cannot place a call with yourself.": "You cannot place a call with yourself.", "Unable to look up phone number": "Unable to look up phone number", "There was an error looking up the phone number": "There was an error looking up the phone number", @@ -426,9 +426,6 @@ "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message", "Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown", "Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown", - "Searches DuckDuckGo for results": "Searches DuckDuckGo for results", - "/ddg is not a command": "/ddg is not a command", - "To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.", "Upgrades a room to a new version": "Upgrades a room to a new version", "You do not have the required permissions to use this command.": "You do not have the required permissions to use this command.", "Changes your display nickname": "Changes your display nickname", @@ -547,6 +544,10 @@ "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s made future room history visible to unknown (%(visibility)s).", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s changed the power level of %(powerLevelDiffText)s.", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s pinned a message to this room. See all pinned messages.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s pinned a message to this room. See all pinned messages.", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s unpinned a message from this room. See all pinned messages.", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s unpinned a message from this room. See all pinned messages.", "%(senderName)s changed the pinned messages for the room.": "%(senderName)s changed the pinned messages for the room.", "%(senderName)s changed the pinned messages for the room.": "%(senderName)s changed the pinned messages for the room.", "%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s", @@ -604,6 +605,8 @@ "See when anyone posts a sticker to your active room": "See when anyone posts a sticker to your active room", "with an empty state key": "with an empty state key", "with state key %(stateKey)s": "with state key %(stateKey)s", + "The above, but in any room you are joined or invited to as well": "The above, but in any room you are joined or invited to as well", + "The above, but in as well": "The above, but in as well", "Send %(eventType)s events as you in this room": "Send %(eventType)s events as you in this room", "See %(eventType)s events posted to this room": "See %(eventType)s events posted to this room", "Send %(eventType)s events as you in your active room": "Send %(eventType)s events as you in your active room", @@ -809,17 +812,17 @@ "Render LaTeX maths in messages": "Render LaTeX maths in messages", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.", "Message Pinning": "Message Pinning", + "Threaded messaging": "Threaded messaging", "Custom user status messages": "Custom user status messages", "Group & filter rooms by custom tags (refresh to apply changes)": "Group & filter rooms by custom tags (refresh to apply changes)", "Render simple counters in room header": "Render simple counters in room header", - "Multiple integration managers": "Multiple integration managers", + "Multiple integration managers (requires manual setup)": "Multiple integration managers (requires manual setup)", "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", "Support adding custom themes": "Support adding custom themes", "Show message previews for reactions in DMs": "Show message previews for reactions in DMs", "Show message previews for reactions in all rooms": "Show message previews for reactions in all rooms", "Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices", "Send pseudonymous analytics data": "Send pseudonymous analytics data", - "Enable advanced debugging for the room list": "Enable advanced debugging for the room list", "Show info about bridges in room settings": "Show info about bridges in room settings", "New layout switcher (with message bubbles)": "New layout switcher (with message bubbles)", "Don't send read receipts": "Don't send read receipts", @@ -835,7 +838,8 @@ "Show read receipts sent by other users": "Show read receipts sent by other users", "Show timestamps in 12 hour format (e.g. 2:30pm)": "Show timestamps in 12 hour format (e.g. 2:30pm)", "Always show message timestamps": "Always show message timestamps", - "Autoplay GIFs and videos": "Autoplay GIFs and videos", + "Autoplay GIFs": "Autoplay GIFs", + "Autoplay videos": "Autoplay videos", "Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting", "Expand code blocks by default": "Expand code blocks by default", "Show line numbers in code blocks": "Show line numbers in code blocks", @@ -869,7 +873,7 @@ "Show rooms with unread notifications first": "Show rooms with unread notifications first", "Show shortcuts to recently viewed rooms above the room list": "Show shortcuts to recently viewed rooms above the room list", "Show hidden events in timeline": "Show hidden events in timeline", - "Low bandwidth mode": "Low bandwidth mode", + "Low bandwidth mode (requires compatible homeserver)": "Low bandwidth mode (requires compatible homeserver)", "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)", "Show previews/thumbnails for images": "Show previews/thumbnails for images", "Enable message search in encrypted rooms": "Enable message search in encrypted rooms", @@ -1465,7 +1469,9 @@ "Anyone can find and join.": "Anyone can find and join.", "Upgrade required": "Upgrade required", "& %(count)s more|other": "& %(count)s more", + "& %(count)s more|one": "& %(count)s more", "Currently, %(count)s spaces have access|other": "Currently, %(count)s spaces have access", + "Currently, %(count)s spaces have access|one": "Currently, a space has access", "Anyone in a space can find and join. Edit which spaces can access here.": "Anyone in a space can find and join. Edit which spaces can access here.", "Spaces with access": "Spaces with access", "Anyone in %(spaceName)s can find and join. You can select other spaces too.": "Anyone in %(spaceName)s can find and join. You can select other spaces too.", @@ -1807,6 +1813,7 @@ "%(count)s people|other": "%(count)s people", "%(count)s people|one": "%(count)s person", "Show files": "Show files", + "Show threads": "Show threads", "Share room": "Share room", "Room settings": "Room settings", "Trusted": "Trusted", @@ -1892,7 +1899,6 @@ "You cancelled verification.": "You cancelled verification.", "Verification cancelled": "Verification cancelled", "Compare emoji": "Compare emoji", - "Connected": "Connected", "Call declined": "Call declined", "Call back": "Call back", "No answer": "No answer", @@ -1926,6 +1932,7 @@ "React": "React", "Edit": "Edit", "Reply": "Reply", + "Thread": "Thread", "Message Actions": "Message Actions", "Download %(text)s": "Download %(text)s", "Error decrypting attachment": "Error decrypting attachment", @@ -2412,8 +2419,8 @@ "Clear cache and resync": "Clear cache and resync", "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!", "Updating %(brand)s": "Updating %(brand)s", - "Leave all rooms and spaces": "Leave all rooms and spaces", "Don't leave any": "Don't leave any", + "Leave all rooms and spaces": "Leave all rooms and spaces", "Leave specific rooms and spaces": "Leave specific rooms and spaces", "Search %(spaceName)s": "Search %(spaceName)s", "You won't be able to rejoin unless you are re-invited.": "You won't be able to rejoin unless you are re-invited.", @@ -3003,8 +3010,6 @@ "Commands": "Commands", "Command Autocomplete": "Command Autocomplete", "Community Autocomplete": "Community Autocomplete", - "Results from DuckDuckGo": "Results from DuckDuckGo", - "DuckDuckGo Results": "DuckDuckGo Results", "Emoji": "Emoji", "Emoji Autocomplete": "Emoji Autocomplete", "Notify the whole room": "Notify the whole room", diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index 86f660f45e5..69110f4aea1 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -18,7 +18,7 @@ "Are you sure?": "¿Estás seguro?", "Are you sure you want to reject the invitation?": "¿Estás seguro que quieres rechazar la invitación?", "Attachment": "Adjunto", - "Autoplay GIFs and videos": "Reproducir automáticamente GIFs y videos", + "Autoplay GIFs and videos": "Reproducir automáticamente GIFs y vídeos", "%(senderName)s banned %(targetName)s.": "%(senderName)s vetó a %(targetName)s.", "Ban": "Vetar", "Banned users": "Usuarios vetados", @@ -360,7 +360,7 @@ "Your language of choice": "Idioma elegido", "Your homeserver's URL": "La URL de tu servidor base", "The information being sent to us to help make %(brand)s better includes:": "La información que se nos envía para ayudarnos a mejorar %(brand)s incluye:", - "Whether or not you're using the Richtext mode of the Rich Text Editor": "Estés utilizando o no el modo de texto enriquecido del editor de texto enriquecido", + "Whether or not you're using the Richtext mode of the Rich Text Editor": "Si estás o no usando el editor de texto enriquecido", "Who would you like to add to this community?": "¿A quién te gustaría añadir a esta comunidad?", "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Advertencia: cualquier persona que añadas a una comunidad será públicamente visible a cualquiera que conozca la ID de la comunidad", "Invite new community members": "Invita nuevos miembros a la comunidad", @@ -527,7 +527,7 @@ "Stops ignoring a user, showing their messages going forward": "Deja de ignorar a un usuario, mostrando sus mensajes a partir de ahora", "Unignored user": "Usuario no ignorado", "You are no longer ignoring %(userId)s": "Ya no ignoras a %(userId)s", - "Opens the Developer Tools dialog": "Abre el diálogo de Herramientas de Desarrollador", + "Opens the Developer Tools dialog": "Abre el diálogo de herramientas de desarrollo", "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s cambió su nombre público a %(displayName)s.", "%(senderName)s changed the pinned messages for the room.": "%(senderName)s cambió los mensajes anclados de la sala.", "%(widgetName)s widget modified by %(senderName)s": "el widget %(widgetName)s fue modificado por %(senderName)s", @@ -610,7 +610,7 @@ "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "Cuando alguien incluye una URL en su mensaje, se mostrará una vista previa para ofrecer información sobre el enlace, que incluirá el título, descripción, y una imagen del sitio web.", "Error decrypting audio": "Error al descifrar el sonido", "Error decrypting image": "Error al descifrar imagen", - "Error decrypting video": "Error al descifrar video", + "Error decrypting video": "Error al descifrar el vídeo", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s cambió el avatar para %(roomName)s", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s eliminó el avatar de la sala.", "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s cambió el avatar de la sala a ", @@ -855,8 +855,8 @@ "Please contact your homeserver administrator.": "Por favor, contacta con la administración de tu servidor base.", "This room has been replaced and is no longer active.": "Esta sala ha sido reemplazada y ya no está activa.", "The conversation continues here.": "La conversación continúa aquí.", - "This room is a continuation of another conversation.": "Esta sala es una continuación de otra conversación.", - "Click here to see older messages.": "Haz clic aquí para ver mensajes más antiguos.", + "This room is a continuation of another conversation.": "Esta sala es una continuación de otra.", + "Click here to see older messages.": "Haz clic aquí para ver mensajes anteriores.", "Failed to upgrade room": "No se pudo actualizar la sala", "The room upgrade could not be completed": "La actualización de la sala no pudo ser completada", "Upgrade this room to version %(version)s": "Actualiza esta sala a la versión %(version)s", @@ -867,7 +867,7 @@ "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s ahora utiliza de 3 a 5 veces menos memoria, porque solo carga información sobre otros usuarios cuando es necesario. Por favor, ¡aguarda mientras volvemos a sincronizar con el servidor!", "Updating %(brand)s": "Actualizando %(brand)s", "Room version:": "Versión de la sala:", - "Developer options": "Opciones de desarrollador", + "Developer options": "Opciones de desarrollo", "Room version": "Versión de la sala", "Room information": "Información de la sala", "Room Topic": "Asunto de la sala", @@ -938,7 +938,7 @@ "Send typing notifications": "Enviar notificaciones de tecleo", "Allow Peer-to-Peer for 1:1 calls": "Permitir conexiones «peer-to-peer en llamadas individuales", "Prompt before sending invites to potentially invalid matrix IDs": "Pedir confirmación antes de enviar invitaciones a IDs de matrix que parezcan inválidas", - "Show developer tools": "Mostrar herramientas de desarrollador", + "Show developer tools": "Mostrar herramientas de desarrollo", "Messages containing my username": "Mensajes que contengan mi nombre", "Messages containing @room": "Mensajes que contengan @room", "Encrypted messages in one-to-one chats": "Mensajes cifrados en salas uno a uno", @@ -1091,8 +1091,8 @@ "Enable Community Filter Panel": "Activar el panel de filtro de comunidad", "Verify this user by confirming the following emoji appear on their screen.": "Verifica este usuario confirmando que los siguientes emojis aparecen en su pantalla.", "Your %(brand)s is misconfigured": "Tu %(brand)s tiene un error de configuración", - "Whether or not you're logged in (we don't record your username)": "Hayas o no iniciado sesión (no guardamos tu nombre de usuario)", - "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Uses o no las «migas de pan» (iconos sobre la lista de salas)", + "Whether or not you're logged in (we don't record your username)": "Si has iniciado sesión o no (no guardamos tu nombre de usuario)", + "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Si estás usando o no las «migas de pan» (iconos sobre la lista de salas)", "Replying With Files": "Respondiendo con archivos", "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "En este momento no es posible responder con un archivo. ¿Te gustaría subir el archivo sin responder?", "The file '%(fileName)s' failed to upload.": "La subida del archivo «%(fileName)s ha fallado.", @@ -1260,8 +1260,8 @@ "Upgrade private room": "Actualizar sala privada", "Upgrade public room": "Actualizar sala pública", "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Actualizar una sala es una acción avanzada y es normalmente recomendada cuando una sala es inestable debido a fallos, funcionalidades no disponibles y vulnerabilidades.", - "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.": "Esto solo afecta a como la sala es procesada en el servidor. Si estás teniendo problemas con tu %(brand)s, por favorreporta un fallo.", - "You'll upgrade this room from to .": "Actualizarás esta sala de a .", + "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.": "Esto solo afecta a cómo procesa la sala el servidor. Si estás teniendo problemas con %(brand)s, por favor, avísanos del fallo.", + "You'll upgrade this room from to .": "Actualizarás esta sala de la versión a la .", "Sign out and remove encryption keys?": "¿Salir y borrar las claves de cifrado?", "A username can only contain lower case letters, numbers and '=_-./'": "Un nombre de usuario solo puede contener letras minúsculas, números y '=_-./'", "Checking...": "Comprobando...", @@ -1422,7 +1422,7 @@ "Disconnect from the identity server ?": "¿Desconectarse del servidor de identidad ?", "Disconnect": "Desconectarse", "You should:": "Deberías:", - "Use Single Sign On to continue": "Continuar con SSO", + "Use Single Sign On to continue": "Continuar con registro único (SSO)", "Confirm adding this email address by using Single Sign On to prove your identity.": "Confirma la nueva dirección de correo usando SSO para probar tu identidad.", "Single Sign On": "Single Sign On", "Confirm adding email": "Confirmar un nuevo correo electrónico", @@ -1441,7 +1441,7 @@ "Use your account or create a new one to continue.": "Usa tu cuenta existente o crea una nueva para continuar.", "Create Account": "Crear cuenta", "Sign In": "Iniciar sesión", - "Sends a message as html, without interpreting it as markdown": "Envía un mensaje como html, sin interpretarlo en markdown", + "Sends a message as html, without interpreting it as markdown": "Envía un mensaje como HTML, sin interpretarlo en Markdown", "Failed to set topic": "No se ha podido cambiar el tema", "Command failed": "El comando falló", "Could not find user in room": "No se ha encontrado el usuario en la sala", @@ -1481,7 +1481,7 @@ "Verify the new login accessing your account: %(name)s": "Verifica el nuevo inicio de sesión que está accediendo a tu cuenta: %(name)s", "From %(deviceName)s (%(deviceId)s)": "De %(deviceName)s (%(deviceId)s)", "This bridge was provisioned by .": "Este puente fue aportado por .", - "This bridge is managed by .": "Este puente es administrado por .", + "This bridge is managed by .": "Este puente lo gestiona .", "Your homeserver does not support cross-signing.": "Tu servidor base no soporta las firmas cruzadas.", "Cross-signing and secret storage are enabled.": "La firma cruzada y el almacenamiento secreto están activados.", "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Su cuenta tiene una identidad de firma cruzada en un almacenamiento secreto, pero aún no es confiada en esta sesión.", @@ -1693,7 +1693,7 @@ "Try to join anyway": "Intentar unirse de todas formas", "You can still join it because this is a public room.": "Todavía puedes unirte, ya que es una sala pública.", "Join the discussion": "Unirme a la Sala", - "Do you want to chat with %(user)s?": "¿Quieres chatear con %(user)s?", + "Do you want to chat with %(user)s?": "¿Quieres empezar una conversación con %(user)s?", "Do you want to join %(roomName)s?": "¿Quieres unirte a %(roomName)s?", " invited you": " te ha invitado", "You're previewing %(roomName)s. Want to join it?": "Esto es una vista previa de %(roomName)s. ¿Te quieres unir?", @@ -1738,8 +1738,8 @@ "This invite to %(roomName)s was sent to %(email)s": "Esta invitación a %(roomName)s fue enviada a %(email)s", "Use an identity server in Settings to receive invites directly in %(brand)s.": "Utilice un servidor de identidad en Configuración para recibir invitaciones directamente en %(brand)s.", "Share this email in Settings to receive invites directly in %(brand)s.": "Comparte este correo electrónico en Configuración para recibir invitaciones directamente en %(brand)s.", - " wants to chat": " quiere chatear", - "Start chatting": "Empieza una conversación", + " wants to chat": " quiere mandarte mensajes", + "Start chatting": "Empezar una conversación", "Reject & Ignore user": "Rechazar e ignorar usuario", "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Actualizar esta sala cerrará la instancia actual de la sala y creará una sala actualizada con el mismo nombre.", "This room has already been upgraded.": "Esta sala ya ha sido actualizada.", @@ -2081,7 +2081,7 @@ "Joins room with given address": "Entrar a la sala con la dirección especificada", "Unrecognised room address:": "No se encuentra la dirección de la sala:", "Opens chat with the given user": "Abrir una conversación con el usuario especificado", - "Sends a message to the given user": "Enviar un mensaje al usuario especificado", + "Sends a message to the given user": "Enviar un mensaje al usuario seleccionado", "Light": "Claro", "Dark": "Oscuro", "Unexpected server error trying to leave the room": "Error inesperado del servidor al abandonar esta sala", @@ -2423,7 +2423,7 @@ "Navigate composer history": "Navegar por el historial del editor", "Cancel replying to a message": "Cancelar responder al mensaje", "Toggle microphone mute": "Alternar silencio del micrófono", - "Toggle video on/off": "Activar/desactivar video", + "Toggle video on/off": "Activar/desactivar vídeo", "Scroll up/down in the timeline": "Desplazarse hacia arriba o hacia abajo en la línea de tiempo", "Dismiss read marker and jump to bottom": "Descartar el marcador de lectura y saltar al final", "Jump to oldest unread message": "Ir al mensaje no leído más antiguo", @@ -2452,7 +2452,7 @@ "This version of %(brand)s does not support searching encrypted messages": "Esta versión de %(brand)s no puede buscar mensajes cifrados", "Video conference ended by %(senderName)s": "Videoconferencia terminada por %(senderName)s", "Join the conference from the room information card on the right": "Únete a la conferencia desde el panel de información de la sala de la derecha", - "Join the conference at the top of this room": "Unirse a la conferencia en la parte de arriba de la sala", + "Join the conference at the top of this room": "Únete a la conferencia en la parte de arriba de la sala", "Ignored attempt to disable encryption": "Se ha ignorado un intento de desactivar el cifrado", "Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.": "Los mensajes en esta sala están cifrados de extremo a extremo. Cuando alguien se una podrás verificarle en su perfil, tan solo pulsa en su imagen.", "Add widgets, bridges & bots": "Añadir widgets, puentes y bots", @@ -3248,7 +3248,7 @@ "%(seconds)ss left": "%(seconds)ss restantes", "Failed to send": "No se ha podido mandar", "Change server ACLs": "Cambiar los ACLs del servidor", - "Show options to enable 'Do not disturb' mode": "Mostrar opciones para activar el modo «no molestar»", + "Show options to enable 'Do not disturb' mode": "Ver opciones para activar el modo «no molestar»", "Stop the recording": "Parar grabación", "Delete recording": "Borrar grabación", "Enter your Security Phrase a second time to confirm it.": "Escribe tu frase de seguridad de nuevo para confirmarla.", @@ -3311,7 +3311,7 @@ "sends space invaders": "enviar space invaders", "Sends the given message with a space themed effect": "Envía un mensaje con efectos espaciales", "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Si sales, %(brand)s volverá a cargarse con los espacios desactivados. Las comunidades y las etiquetas personalizadas serán visibles de nuevo.", - "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Permitir conexión directa (peer-to-peer) en las llamadas individuales (si lo activas, la otra parte podría ver tu dirección IP)", + "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Permitir conexión directa (peer-to-peer) en las llamadas individuales (si lo activas, la otra persona podría ver tu dirección IP)", "See when people join, leave, or are invited to your active room": "Ver cuando alguien se una, salga o se le invite a tu sala activa", "Kick, ban, or invite people to this room, and make you leave": "Expulsar, vetar o invitar personas a esta sala, y hacerte salir de ella", "Kick, ban, or invite people to your active room, and make you leave": "Expulsar, vetar o invitar a gente a tu sala activa, o hacerte salir", @@ -3347,7 +3347,7 @@ "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s ha echado a %(targetName)s: %(reason)s", "Disagree": "No estoy de acuerdo", "[number]": "[número]", - "To view %(spaceName)s, you need an invite": "Para ver %(spaceName)s, necesitas que te inviten.", + "To view %(spaceName)s, you need an invite": "Para ver %(spaceName)s, necesitas que te inviten", "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Haz clic sobre una imagen en el panel de filtro para ver solo las salas y personas asociadas con una comunidad.", "Move down": "Bajar", "Move up": "Subir", @@ -3374,7 +3374,7 @@ "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)s ha cambiado los permisos del servidor %(count)s veces", "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)s ha cambiado los permisos del servidor", "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)s ha cambiado los permisos del servidor %(count)s veces", - "Message search initialisation failed, check your settings for more information": "Ha fallado el sistema de búsqueda de mensajes. Comprueba tus ajustes para más información.", + "Message search initialisation failed, check your settings for more information": "Ha fallado el sistema de búsqueda de mensajes. Comprueba tus ajustes para más información", "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Elige una dirección para este espacio y los usuarios de tu servidor base (%(localDomain)s) podrán encontrarlo a través del buscador", "To publish an address, it needs to be set as a local address first.": "Para publicar una dirección, primero debe ser añadida como dirección local.", "Published addresses can be used by anyone on any server to join your room.": "Las direcciones publicadas pueden usarse por cualquiera para unirse a tu sala, independientemente de su servidor base.", @@ -3404,7 +3404,7 @@ "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.": "Si lo desactivas, todavía podrás añadir mensajes directos a tus espacios personales. Si lo activas, aparecerá todo el mundo que pertenezca al espacio.", "Show people in spaces": "Mostrar gente en los espacios", "Show all rooms in Home": "Mostrar todas las salas en la pantalla de inicio", - "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Prototipo de reportes a los moderadores. En las salas que lo permitan, verás el botón «reportar», que te permitirá avisar de mensajes abusivos a los moderadores de la sala.", + "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Prototipo de reportes a los moderadores. En las salas que lo permitan, verás el botón «reportar», que te permitirá avisar de mensajes abusivos a los moderadores de la sala", "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s ha anulado la invitación a %(targetName)s", "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s ha anulado la invitación a %(targetName)s: %(reason)s", "%(targetName)s left the room": "%(targetName)s ha salido de la sala", @@ -3422,7 +3422,7 @@ "We sent the others, but the below people couldn't be invited to ": "Hemos enviado el resto, pero no hemos podido invitar las siguientes personas a la sala ", "Some invites couldn't be sent": "No se han podido enviar algunas invitaciones", "Integration manager": "Gestor de integración", - "Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "%(brand)s no utilizar un \"gestor de integración\" para hacer esto. Por favor, contacta con un administrador.", + "Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "Tu aplicación %(brand)s no te permite usar un gestor de integración para hacer esto. Por favor, contacta con un administrador.", "Using this widget may share data with %(widgetDomain)s & your integration manager.": "Al usar este widget puede que se compartan datos con %(widgetDomain)s y tu gestor de integraciones.", "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Los gestores de integraciones reciben datos de configuración, y pueden modificar widgets, enviar invitaciones de sala, y establecer niveles de poder en tu nombre.", "Use an integration manager to manage bots, widgets, and sticker packs.": "Usa un gestor de integraciones para bots, widgets y paquetes de pegatinas.", @@ -3445,7 +3445,7 @@ "To view all keyboard shortcuts, click here.": "Para ver todos los atajos de teclado, haz clic aquí.", "Keyboard shortcuts": "Atajos de teclado", "Identity server is": "El servidor de identidad es", - "There was an error loading your notification settings.": "Ha ocurrido un error al cargar tus ajustes de notificaciones", + "There was an error loading your notification settings.": "Ha ocurrido un error al cargar tus ajustes de notificaciones.", "Mentions & keywords": "Menciones y palabras clave", "Global": "Global", "New keyword": "Nueva palabra clave", @@ -3465,7 +3465,7 @@ "Image": "Imagen", "Sticker": "Pegatina", "Downloading": "Descargando", - "The call is in an unknown state!": "La llamada está en un estado desconocido", + "The call is in an unknown state!": "¡La llamada está en un estado desconocido!", "Call back": "Devolver", "You missed this call": "No has cogido esta llamada", "This call has failed": "Esta llamada ha fallado", @@ -3479,10 +3479,10 @@ "IRC": "IRC", "Use Ctrl + F to search timeline": "Usa Control + F para buscar dentro de la conversación", "Please note upgrading will make a new version of the room. All current messages will stay in this archived room.": "Ten en cuenta que actualizar crea una nueva versión de la sala. Todos los mensajes hasta ahora quedarán archivados aquí, en esta sala.", - "Automatically invite members from this room to the new one": "Invitar a la nueva sala automáticamente miembros de esta", + "Automatically invite members from this room to the new one": "Invitar a la nueva sala automáticamente a los miembros que tiene ahora", "These are likely ones other room admins are a part of.": "Otros administradores de la sala estarán dentro.", "Other spaces or rooms you might not know": "Otros espacios o salas que puede que no conozcas", - "Decide which spaces can access this room. If a space is selected, its members can find and join .": "Decide qué espacios pueden acceder a esta sala. Si seleccionas un espacio, sus miembros podrán encontrar y unirse a .", + "Decide which spaces can access this room. If a space is selected, its members can find and join .": "Decide qué espacios tienen acceso a esta sala. Si seleccionas un espacio, sus miembros podrán encontrar y unirse a .", "You're removing all spaces. Access will default to invite only": "Al quitar todos los espacios, el acceso por defecto pasará a ser «solo por invitación»", "Only people invited will be able to find and join this space.": "Solo las personas invitadas podrán encontrar y unirse a este espacio.", "Anyone will be able to find and join this space, not just members of .": "Cualquiera podrá encontrar y unirse a este espacio, incluso si no forman parte de .", @@ -3498,14 +3498,14 @@ "Anyone in a space can find and join. You can select multiple spaces.": "Cualquiera en un espacio puede encontrar y unirse. Puedes seleccionar varios espacios.", "Anyone in %(spaceName)s can find and join. You can select other spaces too.": "Cualquiera en %(spaceName)s puede encontrar y unirse. También puedes seleccionar otros espacios.", "Spaces with access": "Espacios con acceso", - "Anyone in a space can find and join. Edit which spaces can access here.": "Cualquiera en un espacio puede encontrar y unirse. Ajusta qué espacios pueden acceder desde aquí.", + "Anyone in a space can find and join. Edit which spaces can access here.": "Cualquiera en un espacio puede encontrar y unirse. Ajusta qué espacios pueden acceder desde aquí.", "Currently, %(count)s spaces have access|other": "Ahora mismo, %(count)s espacios tienen acceso", "& %(count)s more|other": "y %(count)s más", "Upgrade required": "Actualización necesaria", "Anyone can find and join.": "Cualquiera puede encontrar y unirse.", "Only invited people can join.": "Solo las personas invitadas pueden unirse.", "Private (invite only)": "Privado (solo por invitación)", - "This upgrade will allow members of selected spaces access to this room without an invite.": "Esta actualización permitirá a los miembros de los espacios que elijas acceder a esta sala sin que tengas que invitarles.", + "This upgrade will allow members of selected spaces access to this room without an invite.": "Si actualizas, podrás configurar la sala para que los miembros de los espacios que elijas puedan unirse sin que tengas que invitarles.", "Message bubbles": "Burbujas de mensaje", "Show all rooms": "Ver todas las salas", "Give feedback.": "Danos tu opinión.", @@ -3597,5 +3597,46 @@ "You can also create a Space from a community.": "También puedes crear un espacio a partir de una comunidad.", "You can change this later.": "Puedes cambiarlo más tarde.", "What kind of Space do you want to create?": "¿Qué tipo de espacio quieres crear?", - "Don't send read receipts": "No enviar confirmaciones de lectura" + "Don't send read receipts": "No enviar confirmaciones de lectura", + "To view Spaces, hide communities in Preferences": "Para ver espacios, oculta las comunidades en ajustes", + "It's not recommended to make encrypted rooms public. It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "No está recomendado activar el cifrado en salas públicas. Cualquiera puede encontrar la sala y unirse, por lo que cualquiera puede leer los mensajes. No disfrutarás de los beneficios del cifrado. Además, activarlo en una sala pública hará que recibir y enviar mensajes tarde más.", + "Communities have been archived to make way for Spaces but you can convert your communities into Spaces below. Converting will ensure your conversations get the latest features.": "Las comunidades han sido archivadas para dar paso a los espacios, pero puedes convertir tus comunidades a espacios debajo. Al convertirlas, te aseguras de que tus conversaciones tienen acceso a las últimas funcionalidades.", + "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Si nos has avisado de un fallo a través de Github, los registros de depuración nos pueden ayudar a encontrar más fácil el problema. Los registros incluyen datos de uso de la aplicación incluyendo tu nombre de usuario, las IDs o nombres de las salas o grupos que has visitado, los elementos de la interfaz con los que hayas interactuado recientemente, y los nombres de usuario de otras personas. No contienen mensajes.", + "Delete avatar": "Borrar avatar", + "Flair won't be available in Spaces for the foreseeable future.": "Por ahora no está previsto que las insignias estén disponibles en los espacios.", + "Ask the admins of this community to make it into a Space and keep a look out for the invite.": "Pídele a los admins que conviertan la comunidad en un espacio y espera a que te inviten.", + "You can create a Space from this community here.": "Puedes crear un espacio a partir de esta comunidad desde aquí.", + "To create a Space from another community, just pick the community in Preferences.": "Para crear un espacio a partir de otra comunidad, escoge la comunidad en ajustes.", + "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Los registros de depuración contienen datos de uso de la aplicación como tu nombre de usuario, las IDs o los nombres de las salas o grupos que has visitado, con qué elementos de la interfaz has interactuado recientemente, y nombres de usuario de otras personas. No incluyen mensajes.", + "To avoid these issues, create a new public room for the conversation you plan to have.": "Para evitar estos problemas, crea una nueva sala pública para la conversación que planees tener.", + "%(severalUsers)schanged the pinned messages for the room %(count)s times.|other": "%(severalUsers)s han cambiado los mensajes anclados de la sala %(count)s veces.", + "%(oneUser)schanged the pinned messages for the room %(count)s times.|other": "%(oneUser)s han cambiado los mensajes anclados de la sala %(count)s veces.", + "Cross-signing is ready but keys are not backed up.": "La firma cruzada está lista, pero no hay copia de seguridad de las claves.", + "Rooms and spaces": "Salas y espacios", + "Results": "Resultados", + "Communities won't receive further updates.": "Las comunidades no van a recibir actualizaciones.", + "Spaces are a new way to make a community, with new features coming.": "Los espacios son una nueva manera de formar una comunidad, con nuevas funcionalidades próximamente.", + "Communities can now be made into Spaces": "Ahora puedes convertir comunidades en espacios", + "This description will be shown to people when they view your space": "Esta descripción aparecerá cuando alguien vea tu espacio", + "All rooms will be added and all community members will be invited.": "Todas las salas se añadirán, e invitaremos a todos los miembros de la comunidad.", + "A link to the Space will be put in your community description.": "Pondremos un enlace al espacio en la descripción de tu comunidad.", + "Create Space from community": "Crear espacio a partir de una comunidad", + "Failed to migrate community": "Fallo al migrar la comunidad", + " has been made and everyone who was a part of the community has been invited to it.": " ha sido creado, y cualquier persona que formara parte ha sido invitada.", + "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Tus mensajes privados están cifrados normalmente, pero esta sala no lo está. A menudo, esto pasa porque has iniciado sesión con un dispositivo o método no compatible, como las invitaciones por correo.", + "Enable encryption in settings.": "Activa el cifrado en los ajustes.", + "Are you sure you want to make this encrypted room public?": "¿Seguro que quieres activar el cifrado en esta sala pública?", + "To avoid these issues, create a new encrypted room for the conversation you plan to have.": "Para evitar estos problemas, crea una nueva sala cifrada para la conversación que quieras tener.", + "It's not recommended to add encryption to public rooms.Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "No recomendamos activar el cifrado para salas públicas.Cualquiera puede encontrar y unirse a una sala pública, por lo también puede leer los mensajes. No aprovecharás ningún beneficio del cifrado, y no podrás desactivarlo en el futuro. Cifrar mensajes en una sala pública hará que tarden más en enviarse y recibirse.", + "If a community isn't shown you may not have permission to convert it.": "Si echas en falta alguna comunidad, es posible que no tengas permisos suficientes para convertirla.", + "Are you sure you want to add encryption to this public room?": "¿Seguro que quieres activar el cifrado en esta sala pública?", + "Low bandwidth mode (requires compatible homeserver)": "Modo de bajo consumo de datos (el servidor base debe ser compatible)", + "Multiple integration managers (requires manual setup)": "Varios gestores de integración (hay que configurarlos manualmente)", + "Threaded messaging": "Mensajes en hilos", + "Show threads": "Ver hilos", + "Thread": "Hilo", + "The above, but in any room you are joined or invited to as well": "Lo de arriba, pero en cualquier sala en la que estés o te inviten", + "The above, but in as well": "Lo de arriba, pero también en ", + "Autoplay videos": "Reproducir automáticamente los vídeos", + "Autoplay GIFs": "Reproducir automáticamente los GIFs" } diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index eb28ead0b64..817ff8d3128 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2232,7 +2232,7 @@ "For help with using %(brand)s, click here.": "Kui otsid lisateavet %(brand)s'i kasutamise kohta, palun vaata siia.", "Add users and servers you want to ignore here. Use asterisks to have %(brand)s match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Lisa siia kasutajad ja serverid, mida sa soovid eirata. Kui soovid, et %(brand)s kasutaks üldist asendamist, siis kasuta tärni. Näiteks @bot:* eirab kõikide serverite kasutajat 'bot'.", "Use default": "Kasuta vaikimisi väärtusi", - "Mentions & Keywords": "Mainimised ja võtmesõnad", + "Mentions & Keywords": "Mainimised ja märksõnad", "Notification options": "Teavituste eelistused", "Room options": "Jututoa eelistused", "This room is public": "See jututuba on avalik", @@ -3543,7 +3543,7 @@ "Spaces feedback": "Tagasiside kogukonnakeskuste kohta", "Give feedback.": "Jaga tagasisidet.", "We're working on this, but just want to let you know.": "Me küll alles arendame seda võimalust, kuid soovisime, et tead, mis tulemas on.", - "All rooms you're in will appear in Home.": "Kõik sinu jututoad on nähtavad kodulehel.", + "All rooms you're in will appear in Home.": "Kõik sinu jututoad on nähtavad avalehel.", "Show all rooms": "Näita kõiki jututubasid", "Leave all rooms and spaces": "Lahku kõikidest jututubadest ja kogukondadest", "Don't leave any": "Ära lahku ühestki", @@ -3614,12 +3614,65 @@ "Flair won't be available in Spaces for the foreseeable future.": "Me ei plaani lähitulevikus pakkuda kogukondades rinnamärkide funktsionaalsust.", "This description will be shown to people when they view your space": "Seda kirjeldust kuvame kõigile, kes vaatavad sinu kogukonda", "Unknown failure: %(reason)s": "Tundmatu viga: %(reason)s", - "Help space members find private rooms": "Aita", + "Help space members find private rooms": "Aita kogukonnakeskuse liitmetel leida privaatseid jututube", "New in the Spaces beta": "Mida", "We sent the others, but the below people couldn't be invited to ": "Teised kasutajad said kutse, kuid allpool toodud kasutajatele ei õnnestunud saata kutset jututuppa", "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Kui sa oled teatanud meile GitHub'i vahendusel veast, siis silumislogid aitavad meil seda viga kergemini parandada. Vigadega seotud logid sisaldavad rakenduse teavet, sealhulgas sinu kasutajanime, külastatud jututubade kasutajatunnuseid või aliasi, viimatikasutatud liidese funktsionaalsusi ning teiste kasutajate kasutajanimesid. Logides ei ole saadetud sõnumite sisu.", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Vigadega seotud logid sisaldavad rakenduse teavet, sealhulgas sinu kasutajanime, külastatud jututubade kasutajatunnuseid või aliasi, viimatikasutatud liidese funktsionaalsusi ning teiste kasutajate kasutajanimesid. Logides ei ole saadetud sõnumite sisu.", "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Sinu isiklikud sõnumid on tavaliselt läbivalt krüptitud, aga see jututuba ei ole. Tavaliselt on põhjuseks, et kasutusel on mõni seade või meetod nagu e-posti põhised kutsed, mis krüptimist veel ei toeta.", "Enable encryption in settings.": "Võta seadistustes krüptimine kasutusele.", - "Cross-signing is ready but keys are not backed up.": "Risttunnustamine on töövalmis, aga krüptovõtmed on varundamata." + "Cross-signing is ready but keys are not backed up.": "Risttunnustamine on töövalmis, aga krüptovõtmed on varundamata.", + "New layout switcher (with message bubbles)": "Uue kujunduse valik (koos sõnumimullidega)", + "This makes it easy for rooms to stay private to a space, while letting people in the space find and join them. All new rooms in a space will have this option available.": "See muudab lihtsaks, et jututoad jääksid kogukonnakeskuse piires privaatseks, kuid lasevad kogukonnakeskuses viibivatel inimestel need üles leida ja nendega liituda. Kõik kogukonnakeskuse uued jututoad on selle võimalusega.", + "To help space members find and join a private room, go to that room's Security & Privacy settings.": "Selleks, et aidata kogukonnakeskuse liikmetel leida privaatne jututuba ja sellega liituda, minge selle toa turvalisuse ja privaatsuse seadistustesse.", + "Help people in spaces to find and join private rooms": "Aita kogukonnakeskuse liikmetel leida privaatseid jututube ning nendega liituda", + "See when people join, leave, or are invited to your active room": "Näita, millal teised sinu aktiivse toaga liituvad, sealt lahkuvad või sellesse tuppa kutsutakse", + "Kick, ban, or invite people to your active room, and make you leave": "Aktiivsest toast inimeste väljalükkamine, keelamine või tuppa kutsumine", + "See when people join, leave, or are invited to this room": "Näita, millal inimesed toaga liituvad, lahkuvad või siia tuppa kutsutakse", + "Kick, ban, or invite people to this room, and make you leave": "Sellest toast inimeste väljalükkamine, keelamine või tuppa kutsumine", + "Rooms and spaces": "Jututoad ja kogukonnad", + "Results": "Tulemused", + "Error downloading audio": "Helifaili allalaadimine ei õnnestunud", + "These are likely ones other room admins are a part of.": "Ilmselt on tegemist nendega, mille liikmed on teiste jututubade haldajad.", + "& %(count)s more|other": "ja veel %(count)s", + "Add existing space": "Lisa olemasolev kogukonnakeskus", + "Image": "Pilt", + "Sticker": "Kleeps", + "An unknown error occurred": "Tekkis teadmata viga", + "Their device couldn't start the camera or microphone": "Teise osapoole seadmes ei õnnestunud sisse lülitada kaamerat või mikrofoni", + "Connection failed": "Ühendus ebaõnnestus", + "Could not connect media": "Meediaseadme ühendamine ei õnnestunud", + "Connected": "Ühendatud", + "Copy Room Link": "Kopeeri jututoa link", + "Anyone in a space can find and join. Edit which spaces can access here.": "Kõik kogukonnakeskuse liikmed saavad jututuba leida ja sellega liituda. Muuda lubatud kogukonnakeskuste loendit.", + "Currently, %(count)s spaces have access|other": "Hetkel on ligipääs %(count)s'l kogukonnakeskusel", + "Upgrade required": "Vajalik on uuendus", + "Anyone can find and join.": "Kõik saavad jututuba leida ja sellega liituda.", + "Only invited people can join.": "Liitumine toimub vaid kutse alusel.", + "To avoid these issues, create a new encrypted room for the conversation you plan to have.": "Võimalike probleemide vältimiseks loo oma suhtluse jaoks uus krüptitud jututuba.", + "Private (invite only)": "Privaatne jututuba (eeldab kutset)", + "To avoid these issues, create a new public room for the conversation you plan to have.": "Võimalike probleemide vältimiseks loo oma suhtluse jaoks uus avalik jututuba.", + "It's not recommended to make encrypted rooms public. It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "Me ei soovita krüptitud jututoa muutmist avalikuks. See tähendaks, et kõik huvilised saavad vabalt seda jututuba leida ning temaga liituda ning seega ka kõiki selles leiduvaid sõnumeid lugeda. Olemuselt puuduvad sellises olukorras krüptimise eelised. Avalike jututubade sõnumite krüptimine teeb ka sõnumite saatmise ja vastuvõtmise aeglasemaks.", + "Are you sure you want to make this encrypted room public?": "Kas sa oled kindel, et soovid seda krüptitud jututuba muuta avalikuks?", + "This upgrade will allow members of selected spaces access to this room without an invite.": "Antud uuendusega on valitud kogukonnakeskuste liikmetel võimalik selle jututoaga ilma kutseta liituda.", + "It's not recommended to add encryption to public rooms.Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "Me ei soovita avalikes jututubades krüptimise kasutamist. Kuna kõik huvilised saavad vabalt leida avalikke jututube ning nendega liituda, siis saavad nad niikuinii ka neis leiduvaid sõnumeid lugeda. Olemuselt puuduvad sellises olukorras krüptimise eelised ning sa ei saa hiljem krüptimist välja lülitada. Avalike jututubade sõnumite krüptimine teeb ka sõnumite saatmise ja vastuvõtmise aeglasemaks.", + "Are you sure you want to add encryption to this public room?": "Kas sa oled kindel, et soovid selles avalikus jututoas kasutada krüptimist?", + "Message bubbles": "Jutumullid", + "IRC": "IRC", + "Low bandwidth mode (requires compatible homeserver)": "Režiim kehva internetiühenduse jaoks (eeldab koduserveripoolset tuge)", + "Surround selected text when typing special characters": "Erimärkide sisestamisel märgista valitud tekst", + "Multiple integration managers (requires manual setup)": "Mitmed lõiminguhaldurid (eeldab käsitsi seadistamist)", + "Thread": "Jutulõng", + "Show threads": "Näita jutulõnga", + "Threaded messaging": "Sõnumid jutulõngana", + "Autoplay videos": "Esita automaatselt videosid", + "Autoplay GIFs": "Esita automaatselt liikuvaid pilte", + "The above, but in any room you are joined or invited to as well": "Ülaltoodu, aga samuti igas jututoas, millega oled liitunud või kuhu oled kutsutud", + "The above, but in as well": "Ülaltoodu, aga samuti jututoas", + "Currently, %(count)s spaces have access|one": "Hetkel sellel kogukonnal on ligipääs", + "& %(count)s more|one": "ja veel %(count)s", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s eemaldas siin jututoas klammerduse ühelt sõnumilt. Vaata kõiki klammerdatud sõnumeid.", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s eemaldas siin jututoas klammerduse ühelt sõnumilt. Vaata kõiki klammerdatud sõnumeid.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s klammerdas siin jututoas ühe sõnumi. Vaata kõiki klammerdatud sõnumeid.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s klammerdas siin jututoas ühe sõnumi. Vaata kõiki klammerdatud sõnumeid." } diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 873cbc9f9fc..862cefe06dd 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -3436,7 +3436,7 @@ "Show all rooms in Home": "Afficher tous les salons dans Accueil", "%(senderName)s changed the pinned messages for the room.": "%(senderName)s a changé les messages épinglés du salon.", "%(senderName)s kicked %(targetName)s": "%(senderName)s a expulsé %(targetName)s", - "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s a explusé %(targetName)s : %(reason)s", + "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s a expulsé %(targetName)s : %(reason)s", "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s a annulé l’invitation de %(targetName)s", "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s a annulé l’invitation de %(targetName)s : %(reason)s", "%(senderName)s unbanned %(targetName)s": "%(senderName)s a révoqué le bannissement de %(targetName)s", @@ -3650,5 +3650,33 @@ "You can change this later.": "Vous pourrez changer ceci plus tard.", "What kind of Space do you want to create?": "Quel type d’espace voulez-vous créer ?", "Delete avatar": "Supprimer l’avatar", - "Don't send read receipts": "Ne pas envoyer les accusés de réception" + "Don't send read receipts": "Ne pas envoyer les accusés de réception", + "Rooms and spaces": "Salons et espaces", + "Results": "Résultats", + "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Les journaux de débogage contiennent les données d’utilisation de l’application incluant votre nom d’utilisateur, les identifiants ou les alias des salons ou groupes que vous avez visités, les derniers élément de l’interface avec lesquels vous avez interagis, et les noms d’utilisateurs des autres utilisateurs. Ils ne contiennent pas de messages.", + "Thread": "Discussion", + "Show threads": "Afficher les fils de discussion", + "Enable encryption in settings.": "Activer le chiffrement dans les paramètres.", + "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Vos messages privés sont normalement chiffrés, mais ce salon ne l’est pas. Cela est généralement dû à un périphérique non supporté, ou à un moyen de communication non supporté comme les invitations par e-mail.", + "To avoid these issues, create a new public room for the conversation you plan to have.": "Pour éviter ces problèmes, créez un nouveau salon public pour la conversation que vous souhaitez avoir.", + "It's not recommended to make encrypted rooms public. It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "Il n’est pas recommandé de rendre public les salons chiffrés. Cela veut dire que quiconque pourra trouver et rejoindre le salon, donc tout le monde pourra lire les messages. Vous n’aurez plus aucun avantage lié au chiffrement. Chiffrer les messages dans un salon public ralentira la réception et l’envoi de messages.", + "Are you sure you want to make this encrypted room public?": "Êtes-vous sûr de vouloir rendre public ce salon chiffré ?", + "To avoid these issues, create a new encrypted room for the conversation you plan to have.": "Pour éviter ces problèmes, créez un nouveau salon chiffré pour la conversation que vous souhaitez avoir.", + "It's not recommended to add encryption to public rooms.Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "Il n'est pas recommandé d’ajouter le chiffrement aux salons publics. Tout le monde peut trouver et rejoindre les salons publics, donc tout le monde peut lire les messages qui s’y trouvent. Vous n’aurez aucun des avantages du chiffrement, et vous ne pourrez pas le désactiver plus tard. Chiffrer les messages dans un salon public ralentira la réception et l’envoi de messages.", + "Are you sure you want to add encryption to this public room?": "Êtes-vous sûr de vouloir ajouter le chiffrement dans ce salon public ?", + "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Si vous avez soumis un rapport d’anomalie via GitHub, les journaux de débogage nous aiderons à localiser le problème. Les journaux de débogage contiennent les données d’utilisation de l’application incluant votre nom d’utilisateur, les identifiants ou les alias des salons ou groupes que vous avez visités, les derniers élément de l’interface avec lesquels vous avez interagis, et les noms d’utilisateurs des autres utilisateurs. Ils ne contiennent pas de messages.", + "Cross-signing is ready but keys are not backed up.": "La signature croisée est prête mais les clés ne sont pas sauvegardées.", + "Low bandwidth mode (requires compatible homeserver)": "Mode faible bande passante (nécessite un serveur d’accueil compatible)", + "Autoplay videos": "Jouer automatiquement les vidéos", + "Autoplay GIFs": "Jouer automatiquement les GIFs", + "Multiple integration managers (requires manual setup)": "Multiple gestionnaires d’intégration (nécessite un réglage manuel)", + "Threaded messaging": "Fils de discussion", + "The above, but in as well": "Comme ci-dessus, mais également dans ", + "The above, but in any room you are joined or invited to as well": "Comme ci-dessus, mais également dans tous les salons dans lesquels vous avez été invité ou que vous avez rejoint", + "Currently, %(count)s spaces have access|one": "Actuellement, un espace a accès", + "& %(count)s more|one": "& %(count)s autres", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s a désépinglé un message de ce salon. Voir tous les messages épinglés.", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s a désépinglé un message de ce salon. Voir tous les messages épinglés.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s a épinglé un message dans ce salon. Voir tous les messages épinglés.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s a épinglé un message dans ce salon. Voir tous les messages épinglés." } diff --git a/src/i18n/strings/ga.json b/src/i18n/strings/ga.json index c98107b767e..bf8ca8c157a 100644 --- a/src/i18n/strings/ga.json +++ b/src/i18n/strings/ga.json @@ -504,14 +504,14 @@ "Invite": "Tabhair cuireadh", "Mention": "Luaigh", "Demote": "Bain ceadanna", - "Ban": "Coisc", + "Ban": "Toirmisc", "Kick": "Caith amach", "Disinvite": "Tarraing siar cuireadh", "Encrypted": "Criptithe", "Encryption": "Criptiúchán", "Anyone": "Aon duine", "Permissions": "Ceadanna", - "Unban": "Bain an coisc", + "Unban": "Bain an cosc", "Browse": "Brabhsáil", "Reset": "Athshocraigh", "Sounds": "Fuaimeanna", @@ -671,5 +671,157 @@ "Signed Out": "Sínithe Amach", "Unable to query for supported registration methods.": "Ní féidir iarratas a dhéanamh faoi modhanna cláraithe tacaithe.", "Host account on": "Óstáil cuntas ar", - "Create account": "Déan cuntas a chruthú" + "Create account": "Déan cuntas a chruthú", + "Deactivate Account": "Cuir cuntas as feidhm", + "Account management": "Bainistíocht cuntais", + "Phone numbers": "Uimhreacha guthán", + "Email addresses": "Seoltaí r-phost", + "Display Name": "Ainm Taispeána", + "Profile picture": "Pictiúr próifíle", + "Phone Number": "Uimhir Fóin", + "Verification code": "Cód fíoraithe", + "Notification targets": "Spriocanna fógraí", + "Results": "Torthaí", + "Hangup": "Cuir síos", + "Dialpad": "Eochaircheap", + "More": "Níos mó", + "Decrypting": "Ag Díchriptiú", + "Adding...": "Ag Cur...", + "Access": "Rochtain", + "Image": "Íomhá", + "Sticker": "Greamán", + "Connected": "Ceangailte", + "IRC": "IRC", + "Modern": "Comhaimseartha", + "Global": "Uilíoch", + "Keyword": "Eochairfhocal", + "[number]": "[uimhir]", + "Report": "Tuairiscigh", + "Forward": "Seol ar aghaidh", + "Disagree": "Easaontaigh", + "Collapse": "Cumaisc", + "Expand": "Leath", + "Visibility": "Léargas", + "Address": "Seoladh", + "Sent": "Seolta", + "Beta": "Béite", + "Connecting": "Ag Ceangal", + "Play": "Cas", + "Pause": "Cuir ar sos", + "Sending": "Ag Seoladh", + "%(count)s members including %(commaSeparatedMembers)s|one": "%(commaSeparatedMembers)s", + "Avatar": "Abhatár", + "Removing...": "Ag Baint...", + "Suggested": "Moltaí", + "The file '%(fileName)s' failed to upload.": "Níor éirigh leis an gcomhad '%(fileName)s' a uaslódáil.", + "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "Ag an am seo ní féidir freagra a thabhairt le comhad. Ar mhaith leat an comhad seo a uaslódáil gan freagra a thabhairt?", + "Replying With Files": "Ag Freagairt le Comhaid", + "You do not have permission to start a conference call in this room": "Níl cead agat glao comhdhála a thosú sa seomra seo", + "You cannot place a call with yourself.": "Ní féidir leat glaoch ort féin.", + "The user you called is busy.": "Tá an t-úsáideoir ar a ghlaoigh tú gnóthach.", + "User Busy": "Úsáideoir Gnóthach", + "Share your public space": "Roinn do spás poiblí", + "Invite to %(spaceName)s": "Tabhair cuireadh chun %(spaceName)s", + "Unnamed room": "Seomra gan ainm", + "Command error": "Earráid ordaithe", + "Server error": "Earráid freastalaí", + "Upload file": "Uaslódáil comhad", + "Video call": "Físghlao", + "Voice call": "Glao gutha", + "Admin Tools": "Uirlisí Riaracháin", + "Demote yourself?": "Tabhair ísliú céime duit féin?", + "Enable encryption?": "Cumasaigh criptiú?", + "Banned users": "Úsáideoirí toirmiscthe", + "Muted Users": "Úsáideoirí Fuaim", + "Privileged Users": "Úsáideoirí Pribhléideacha", + "Notify everyone": "Tabhair fógraí do gach duine", + "Ban users": "Toirmisc úsáideoirí", + "Kick users": "Caith úsáideoirí amach", + "Change settings": "Athraigh socruithe", + "Invite users": "Tabhair cuirí d'úsáideoirí", + "Send messages": "Seol teachtaireachtaí", + "Default role": "Gnáth-ról", + "Modify widgets": "Mionathraigh giuirléidí", + "Change topic": "Athraigh ábhar", + "Change permissions": "Athraigh ceadanna", + "Notification sound": "Fuaim fógra", + "Uploaded sound": "Fuaim uaslódáilte", + "URL Previews": "Réamhamhairc URL", + "Room Addresses": "Seoltaí Seomra", + "Open Devtools": "Oscail Devtools", + "Developer options": "Roghanna forbróra", + "Room version:": "Leagan seomra:", + "Room version": "Leagan seomra", + "Too Many Calls": "Barraíocht Glaonna", + "You cannot place VoIP calls in this browser.": "Ní féidir leat glaonna VoIP a chur sa brabhsálaí seo.", + "VoIP is unsupported": "Tá VoIP gan tacaíocht", + "No other application is using the webcam": "Níl aon fheidhmchlár eile ag úsáid an cheamara gréasáin", + "Permission is granted to use the webcam": "Tugtar cead an ceamara gréasáin a úsáid", + "A microphone and webcam are plugged in and set up correctly": "Tá micreafón agus ceamara gréasáin plugáilte isteach agus curtha ar bun i gceart", + "Call failed because webcam or microphone could not be accessed. Check that:": "Níor glaodh toisc nach raibh rochtain ar ceamara gréasáin nó mhicreafón. Seiceáil go:", + "Unable to access webcam / microphone": "Ní féidir rochtain a fháil ar ceamara gréasáin / mhicreafón", + "Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "Níor glaodh toisc nach raibh rochtain ar mhicreafón. Seiceáil go bhfuil micreafón plugáilte isteach agus curtha ar bun i gceart.", + "Which rooms would you like to add to this community?": "Cé na seomraí ar mhaith leat a chur leis an bpobal seo?", + "Invite to Community": "Tabhair cuireadh chun an pobal", + "Name or Matrix ID": "Ainm nó ID Matrix", + "Invite new community members": "Tabhair cuireadh do baill nua chun an phobail", + "Who would you like to add to this community?": "Cé ba mhaith leat a chur leis an bpobal seo?", + "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", + "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", + "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s", + "Failure to create room": "Níorbh fhéidir an seomra a chruthú", + "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Sáraíonn an comhad '%(fileName)s' teorainn méide an freastalaí baile seo le haghaidh uaslódálacha", + "The server does not support the room version specified.": "Ní thacaíonn an freastalaí leis an leagan seomra a shonraítear.", + "Server may be unavailable, overloaded, or you hit a bug.": "D’fhéadfadh nach mbeadh an freastalaí ar fáil, ró-ualaithe, nó fuair tú fabht.", + "Rooms and spaces": "Seomraí agus spásanna", + "Collapse reply thread": "Cuir na freagraí i bhfolach", + "Threaded messaging": "Teachtaireachtaí i snáitheanna", + "Thread": "Snáithe", + "Show threads": "Taispeáin snáitheanna", + "Low priority": "Tosaíocht íseal", + "Start chat": "Tosaigh comhrá", + "Share room": "Roinn seomra", + "Forget room": "Déan dearmad ar an seomra", + "Join Room": "Téigh isteach an seomra", + "(~%(count)s results)|one": "(~%(count)s toradh)", + "(~%(count)s results)|other": "(~%(count)s torthaí)", + "World readable": "Inléite ag gach duine", + "Communities can now be made into Spaces": "Is féidir Pobail a dhéanamh ina Spásanna anois", + "Spaces are a new way to make a community, with new features coming.": "Is bealach nua iad spásanna chun pobal a dhéanamh, le gnéithe nua ag teacht.", + "Communities won't receive further updates.": "Ní bhfaighidh pobail nuashonruithe breise.", + "Created from ": "Cruthaithe ó ", + "No answer": "Gan freagair", + "Unknown failure: %(reason)s": "Teip anaithnid: %(reason)s", + "Low bandwidth mode (requires compatible homeserver)": "Modh bandaleithid íseal (teastaíonn freastalaí comhoiriúnach)", + "Enable encryption in settings.": "Tosaigh criptiú sna socruithe.", + "Cross-signing is ready but keys are not backed up.": "Tá tras-sínigh réidh ach ní dhéantar cóip chúltaca d'eochracha.", + "Ask the admins of this community to make it into a Space and keep a look out for the invite.": "", + "You can create a Space from this community here.": "", + "Bans user with given id": "Toirmisc úsáideoir leis an ID áirithe", + "Failed to reject invitation": "Níorbh fhéidir an cuireadh a dhiúltú", + "Failed to reject invite": "Níorbh fhéidir an cuireadh a dhiúltú", + "Failed to mute user": "Níor ciúnaíodh an úsáideoir", + "Failed to load timeline position": "Níor lódáladh áit amlíne", + "Failed to kick": "Níor caitheadh amach é", + "Failed to forget room %(errCode)s": "Níor dhearnadh dearmad ar an seomra %(errCode)s", + "Failed to join room": "Níor éiríodh le dul isteach an seomra", + "Failed to change power level": "Níor éiríodh leis an leibhéal cumhachta a hathrú", + "Failed to change password. Is your password correct?": "Níor éiríodh leis do phasfhocal a hathrú. An bhfuil do phasfhocal ceart?", + "Failed to ban user": "Níor éiríodh leis an úsáideoir a thoirmeasc", + "Export E2E room keys": "Easpórtáil eochracha an tseomra le criptiú ó dheireadh go deireadh", + "Error decrypting attachment": "Earráid le ceangaltán a dhíchriptiú", + "Enter passphrase": "Iontráil pasfrása", + "Email address": "Seoladh ríomhphoist", + "Download %(text)s": "Íoslódáil %(text)s", + "Deops user with given id": "Bain an cumhacht oibritheora ó úsáideoir leis an ID áirithe", + "Decrypt %(text)s": "Díchriptigh %(text)s", + "Custom level": "Leibhéal saincheaptha", + "Create Room": "Déan Seomra", + "Changes your display nickname": "Athraíonn sé d'ainm taispeána", + "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "D'athraigh %(senderDisplayName)s an ábhar go \"%(topic)s\".", + "%(senderDisplayName)s removed the room name.": "Bhain %(senderDisplayName)s ainm an tseomra.", + "%(senderDisplayName)s changed the room name to %(roomName)s.": "D'athraigh %(senderDisplayName)s ainm an tseomra go %(roomName)s.", + "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "D'athraigh %(senderName)s an leibhéal cumhachta %(powerLevelDiffText)s.", + "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Ní féidir ceangal leis an bhfreastalaí baile trí HTTP nuair a bhíonn URL HTTPS i mbarra do bhrabhsálaí. Bain úsáid as HTTPS nó scripteanna neamhshábháilte a chumasú .", + "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Ní féidir ceangal leis an bhfreastalaí baile - seiceáil do nascacht le do thoil, déan cinnte go bhfuil muinín i dteastas SSL do fhreastalaí baile, agus nach bhfuil síneadh brabhsálaí ag cur bac ar iarratais." } diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 382620f36eb..6d08bcb2665 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -3687,5 +3687,20 @@ "To avoid these issues, create a new encrypted room for the conversation you plan to have.": "Para evitar estos problemas, crea unha nova sala cifrada para a conversa que pretendes manter.", "It's not recommended to add encryption to public rooms.Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "Non se recomenda engadir cifrado a salas públicas. Calquera pode atopar e unirse a salas públicas, polo que tamén ler as mensaxes. Non vas ter ningún dos beneficios do cifrado, e máis tarde non poderás desactivalo. Cifrar as mensaxes nunha sala pública tamén fará máis lenta a entrega e recepción das mensaxes.", "Are you sure you want to add encryption to this public room?": "Tes a certeza de querer engadir cifrado a esta sala pública?", - "Cross-signing is ready but keys are not backed up.": "A sinatura-cruzada está preparada pero non hai copia das chaves." + "Cross-signing is ready but keys are not backed up.": "A sinatura-cruzada está preparada pero non hai copia das chaves.", + "Low bandwidth mode (requires compatible homeserver)": "Modo de ancho de banda limitado (require servidor de inicio compatible)", + "Multiple integration managers (requires manual setup)": "Varios xestores de integración (require configuración manual)", + "Thread": "Tema", + "Show threads": "Mostrar temas", + "Currently, %(count)s spaces have access|one": "Actualmente, un espazo ten acceso", + "& %(count)s more|one": "e %(count)s máis", + "Autoplay GIFs": "Reprod. automática GIFs", + "Autoplay videos": "Reprod. automática vídeo", + "Threaded messaging": "Mensaxes fiadas", + "The above, but in as well": "O de arriba, pero tamén en ", + "The above, but in any room you are joined or invited to as well": "O de enriba, pero en calquera sala á que te uniches ou foches convidada", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s desafixou unha mensaxe desta sala. Mira tódalas mensaxes fixadas.", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s deafixou unha mensaxe desta sala. Mira tódalas mensaxes fixadas.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s fixou unha mensaxe nesta sala. Mira tódalas mensaxes fixadas.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s fixou unha mensaxe nesta sala. Mira tódalas mensaxes fixadas." } diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index b0343072e25..df48f631cf6 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -3444,7 +3444,7 @@ "Move down": "Mozgatás le", "Move up": "Mozgatás fel", "Report": "Jelentés", - "Collapse reply thread": "Beszélgetés szál becsukása", + "Collapse reply thread": "Üzenetszál becsukása", "Show preview": "Előnézet megjelenítése", "View source": "Forrás megtekintése", "Forward": "Továbbítás", @@ -3670,5 +3670,31 @@ "What kind of Space do you want to create?": "Milyen típusú teret szeretne készíteni?", "Delete avatar": "Profilkép törlése", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "A hibakereső napló alkalmazás használati adatokat tartalmaz beleértve a felhasználói nevedet, az általad meglátogatott szobák és csoportok azonosítóit alternatív neveit, az utolsó felhasználói felület elemét amit használt és más felhasználói neveket. Csevegés üzenetek szövegét nem tartalmazza.", - "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Ha a GitHubon keresztül küldted be a hibát, a hibakeresési napló segíthet nekünk a javításban. A napló felhasználási adatokat tartalmaz mint a felhasználói neved, az általad meglátogatott szobák vagy csoportok azonosítóját vagy alternatív nevét, az utolsó felhasználói felület elemét amit használt és mások felhasználói nevét. De nem tartalmazzák az üzeneteket." + "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Ha a GitHubon keresztül küldted be a hibát, a hibakeresési napló segíthet nekünk a javításban. A napló felhasználási adatokat tartalmaz mint a felhasználói neved, az általad meglátogatott szobák vagy csoportok azonosítóját vagy alternatív nevét, az utolsó felhasználói felület elemét amit használt és mások felhasználói nevét. De nem tartalmazzák az üzeneteket.", + "Are you sure you want to add encryption to this public room?": "Biztos, hogy titkosítást állít be ehhez a nyilvános szobához?", + "Cross-signing is ready but keys are not backed up.": "Eszközök közötti hitelesítés megvan de a kulcsokhoz nincs biztonsági mentés.", + "Rooms and spaces": "Szobák és terek", + "Results": "Eredmények", + "Enable encryption in settings.": "Titkosítás bekapcsolása a beállításokban.", + "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "A privát üzenetek általában titkosítottak de ez a szoba nem az. Általában ez a titkosítást nem támogató eszköz vagy metódus használata miatt lehet, mint az e-mail meghívók.", + "To avoid these issues, create a new public room for the conversation you plan to have.": "Az ehhez hasonló problémák elkerüléséhez készítsen új nyilvános szobát a tervezett beszélgetésekhez.", + "It's not recommended to make encrypted rooms public. It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "Titkosított szobát nem célszerű nyilvánossá tenni. Bárki megtalálhatja és csatlakozhat nyilvános szobákhoz, így bárki elolvashatja az üzeneteket bennük. A titkosítás előnyeit így nem jelentkeznek és később ezt nem lehet kikapcsolni. Nyilvános szobákban a titkosított üzenetek az üzenetküldést és fogadást csak lassítják.", + "Are you sure you want to make this encrypted room public?": "Biztos, hogy nyilvánossá teszi ezt a titkosított szobát?", + "To avoid these issues, create a new encrypted room for the conversation you plan to have.": "Az ehhez hasonló problémák elkerüléséhez készítsen új titkosított szobát a tervezett beszélgetésekhez.", + "It's not recommended to add encryption to public rooms.Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "Nyilvános szobához nem javasolt a titkosítás beállítása.Bárki megtalálhatja és csatlakozhat nyilvános szobákhoz, így bárki elolvashatja az üzeneteket bennük. A titkosítás előnyeit így nem jelentkeznek és később ezt nem lehet kikapcsolni. Nyilvános szobákban a titkosított üzenetek az üzenetküldést és fogadást csak lassítják.", + "Low bandwidth mode (requires compatible homeserver)": "Alacsony sávszélesség mód (kompatibilis matrix szervert igényel)", + "Multiple integration managers (requires manual setup)": "Több integrációs menedzser (kézi beállítást igényel)", + "Autoplay videos": "Videók automatikus lejátszása", + "Autoplay GIFs": "GIF-ek automatikus lejátszása", + "The above, but in as well": "A felül lévő, de ebben a szobában is: ", + "Show threads": "Üzenetszálak megjelenítése", + "Threaded messaging": "Üzenetszálas beszélgetés", + "The above, but in any room you are joined or invited to as well": "Ami felette van, de minden szobában amibe belépett vagy meghívták", + "Thread": "Üzenetszál", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s levett egy kitűzött üzenetet ebből a szobában. Minden kitűzött üzenet megjelenítése.", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s levett egy kitűzött üzenetet ebből a szobában. Minden kitűzött üzenet megjelenítése.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s kitűzött egy üzenetet ebben a szobában. Minden kitűzött üzenet megjelenítése.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s kitűzött egy üzenetet ebben a szobában. Minden kitűzött üzenet megjelenítése.", + "Currently, %(count)s spaces have access|one": "Jelenleg a Térnek hozzáférése van", + "& %(count)s more|one": "és még %(count)s" } diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 637714b3ef9..1ea18cd5ac5 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -3678,5 +3678,20 @@ "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Se hai segnalato un errore via Github, i log di debug possono aiutarci a identificare il problema. I log di debug contengono dati di utilizzo dell'applicazione inclusi il nome utente, gli ID o alias delle stanze o gruppi visitati, gli ultimi elementi dell'interfaccia con cui hai interagito e i nomi degli altri utenti. Non contengono messaggi.", "Enable encryption in settings.": "Attiva la crittografia nelle impostazioni.", "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "I tuoi messaggi privati normalmente sono cifrati, ma questa stanza non lo è. Di solito ciò è dovuto ad un dispositivo non supportato o dal metodo usato, come gli inviti per email.", - "Cross-signing is ready but keys are not backed up.": "La firma incrociata è pronta ma c'è un backup delle chiavi." + "Cross-signing is ready but keys are not backed up.": "La firma incrociata è pronta ma c'è un backup delle chiavi.", + "Rooms and spaces": "Stanze e spazi", + "Results": "Risultati", + "To avoid these issues, create a new public room for the conversation you plan to have.": "Per evitare questi problemi, crea una nuova stanza pubblica per la conversazione che vuoi avere.", + "It's not recommended to make encrypted rooms public. It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "Non è consigliabile rendere pubbliche le stanze cifrate. Se lo fai, chiunque potrà trovare ed entrare nella stanza, quindi chiunque potrà leggere i messaggi. Non avrai alcun beneficio dalla crittografia. Cifrare i messaggi in una stanza pubblica renderà più lenti l'invio e la ricezione dei messaggi.", + "Are you sure you want to make this encrypted room public?": "Vuoi veramente rendere pubblica questa stanza cifrata?", + "To avoid these issues, create a new encrypted room for the conversation you plan to have.": "Per evitare questi problemi, crea una nuova stanza cifrata per la conversazione che vuoi avere.", + "It's not recommended to add encryption to public rooms.Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "Non è consigliabile aggiungere la crittografia alle stanze pubbliche.Chiunque può trovare ed entrare in stanze pubbliche, quindi chiunque può leggere i messaggi. Non avrai alcun beneficio dalla crittografia e non potrai disattivarla in seguito. Cifrare i messaggi in una stanza pubblica renderà più lenti l'invio e la ricezione dei messaggi.", + "Are you sure you want to add encryption to this public room?": "Vuoi veramente aggiungere la crittografia a questa stanza pubblica?", + "Low bandwidth mode (requires compatible homeserver)": "Modalità a connessione lenta (richiede un homeserver compatibile)", + "Multiple integration managers (requires manual setup)": "Gestori di integrazione multipli (richiede configurazione manuale)", + "Thread": "Argomento", + "Show threads": "Mostra argomenti", + "Threaded messaging": "Messaggi raggruppati", + "The above, but in any room you are joined or invited to as well": "Quanto sopra, ma anche in qualsiasi stanza tu sia entrato o invitato", + "The above, but in as well": "Quanto sopra, ma anche in " } diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json index 14295b05323..3ce4a121d2c 100644 --- a/src/i18n/strings/ja.json +++ b/src/i18n/strings/ja.json @@ -23,9 +23,9 @@ "New Password": "新しいパスワード", "Failed to change password. Is your password correct?": "パスワード変更に失敗しました。パスワードは正しいですか?", "Only people who have been invited": "この部屋に招待された人のみ参加可能", - "Always show message timestamps": "発言時刻を常に表示", + "Always show message timestamps": "発言時刻を常に表示する", "Filter room members": "部屋メンバーを検索", - "Show timestamps in 12 hour format (e.g. 2:30pm)": "発言時刻を12時間形式で表示 (例 2:30PM)", + "Show timestamps in 12 hour format (e.g. 2:30pm)": "発言時刻を 12 時間形式で表示する (例: 2:30午後)", "Upload avatar": "アイコン画像を変更", "Upload file": "ファイルのアップロード", "%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)sはアプリケーションを改善するために匿名の分析情報を収集しています。", @@ -320,11 +320,11 @@ "(no answer)": "(応答なし)", "(unknown failure: %(reason)s)": "(不明なエラー: %(reason)s)", "%(senderName)s ended the call.": "%(senderName)s が通話を終了しました。", - "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s は部屋に加わるよう %(targetDisplayName)s に招待状を送りました。", - "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s が、部屋のメンバー全員に招待された時点からの部屋履歴を参照できるようにしました。", - "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s が、部屋のメンバー全員に参加した時点からの部屋履歴を参照できるようにしました。", - "%(senderName)s made future room history visible to all room members.": "%(senderName)s が、部屋のメンバー全員に部屋履歴を参照できるようにしました。", - "%(senderName)s made future room history visible to anyone.": "%(senderName)s が、部屋履歴を誰でも参照できるようにしました。", + "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s が %(targetDisplayName)s をこの部屋に招待しました。", + "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s がこの部屋で今後送信されるメッセージの履歴を「メンバーのみ (招待された時点以降)」閲覧できるようにしました。", + "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s がこの部屋で今後送信されるメッセージの履歴を「メンバーのみ (参加した時点以降)」閲覧できるようにしました。", + "%(senderName)s made future room history visible to all room members.": "%(senderName)s がこの部屋で今後送信されるメッセージの履歴を「メンバーのみ」閲覧できるようにしました。", + "%(senderName)s made future room history visible to anyone.": "%(senderName)s がこの部屋で今後送信されるメッセージの履歴を「誰でも」閲覧できるようにしました。", "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s が、見知らぬ (%(visibility)s) に部屋履歴を参照できるようにしました。", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s は %(fromPowerLevel)s から %(toPowerLevel)s", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s が %(powerLevelDiffText)s の権限レベルを変更しました。", @@ -479,8 +479,8 @@ "Publish this room to the public in %(domain)s's room directory?": "%(domain)s のルームディレクトリにこの部屋を公開しますか?", "Who can read history?": "誰が履歴を読むことができますか?", "Members only (since the point in time of selecting this option)": "メンバーのみ (このオプションを選択した時点以降)", - "Members only (since they were invited)": "メンバーのみ (招待されて以来)", - "Members only (since they joined)": "メンバーのみ (参加して以来)", + "Members only (since they were invited)": "メンバーのみ (招待された時点以降)", + "Members only (since they joined)": "メンバーのみ (参加した時点以降)", "Permissions": "アクセス許可", "Advanced": "詳細", "Add a topic": "トピックを追加", @@ -1304,8 +1304,8 @@ "Prompt before sending invites to potentially invalid matrix IDs": "不正な可能性のある Matrix ID に招待を送るまえに確認を表示", "Order rooms by name": "名前順で部屋を整列", "Show rooms with unread notifications first": "未読通知のある部屋をトップに表示", - "Show shortcuts to recently viewed rooms above the room list": "最近表示した部屋のショートカットを部屋リストの上に表示", - "Show previews/thumbnails for images": "画像のプレビュー/サムネイルを表示", + "Show shortcuts to recently viewed rooms above the room list": "最近表示した部屋のショートカットを部屋リストの上に表示する", + "Show previews/thumbnails for images": "画像のプレビュー/サムネイルを表示する", "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "あなたのアカウントではクロス署名の認証情報がシークレットストレージに保存されていますが、このセッションでは信頼されていません。", "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "このセッションではキーをバックアップしていません。ですが、あなたは復元に使用したり今後キーを追加したりできるバックアップを持っています。", "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "このセッションのみにあるキーを失なわないために、セッションをキーバックアップに接続しましょう。", @@ -1820,7 +1820,7 @@ "Send general files as you in this room": "あなたとしてファイルを部屋に送る", "See videos posted to this room": "部屋に送られた動画を見る", "See videos posted to your active room": "アクティブな部屋に送られた動画を見る", - "Send videos as you in your active room": "あなたとしてアクティブな部屋に画像を送る", + "Send videos as you in your active room": "あなたとしてアクティブな部屋に動画を送る", "Send videos as you in this room": "あなたとして部屋に動画を送る", "See images posted to your active room": "アクティブな部屋に送られた画像を見る", "See images posted to this room": "部屋に送られた画像を見る", @@ -1887,7 +1887,7 @@ "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s は部屋の禁止ルール %(glob)s を削除しました", "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s はユーザー禁止ルール %(glob)s を削除しました", "%(senderName)s has updated the widget layout": "%(senderName)s はウィジェットのレイアウトを更新しました", - "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s は部屋 %(targetDisplayName)s への招待を取り消しました。", + "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s が %(targetDisplayName)s への招待を取り消しました。", "%(senderName)s declined the call.": "%(senderName)s は通話を拒否しました。", "(an error occurred)": "(エラーが発生しました)", "(their device couldn't start the camera / microphone)": "(彼らのデバイスはカメラ/マイクを使用できませんでした)", @@ -2518,5 +2518,29 @@ "Saving...": "保存しています…", "Failed to save space settings.": "スペースの設定を保存できませんでした。", "Transfer Failed": "転送に失敗しました", - "Unable to transfer call": "通話が転送できませんでした" + "Unable to transfer call": "通話が転送できませんでした", + "All rooms you're in will appear in Home.": "ホームに、あなたが参加しているすべての部屋が表示されます。", + "Show all rooms in Home": "ホームにすべての部屋を表示する", + "Images, GIFs and videos": "画像・GIF・動画", + "Displaying time": "表示時刻", + "Use Command + F to search timeline": "Command + F でタイムラインを検索する", + "Use Ctrl + F to search timeline": "Ctrl + F でタイムラインを検索する", + "To view all keyboard shortcuts, click here.": "ここをクリックすると、すべてのキーボードショートカットを確認できます。", + "Keyboard shortcuts": "キーボードショートカット", + "Messages containing keywords": "指定のキーワードを含むメッセージ", + "Mentions & keywords": "メンションとキーワード", + "Global": "グローバル", + "New keyword": "新しいキーワード", + "Keyword": "キーワード", + "Enable for this account": "このアカウントで有効にする", + "%(targetName)s joined the room": "%(targetName)s がこの部屋に参加しました", + "Anyone can find and join.": "誰でも検索・参加できます。", + "Anyone in %(spaceName)s can find and join. You can select other spaces too.": "%(spaceName)s のメンバーが検索・参加できます。他のスペースも選択可能です。", + "Anyone in a space can find and join. You can select multiple spaces.": "スペースのメンバーが検索・参加できます。複数のスペースも選択可能です。", + "Space members": "スペースのメンバー", + "Upgrade required": "アップグレードが必要", + "Only invited people can join.": "招待された人のみ参加できます。", + "Private (invite only)": "プライベート (招待者のみ)", + "Decide who can join %(roomName)s.": "%(roomName)s に参加できる人を設定します。", + "%(senderName)s invited %(targetName)s": "%(senderName)s が %(targetName)s を招待しました" } diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index 920add953f1..573ed6922a2 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -3564,5 +3564,31 @@ "This community has been upgraded into a Space": "Deze gemeenschap is geupgrade naar een Space", "Unknown failure: %(reason)s": "Onbekende fout: %(reason)s", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Debug logs bevatten applicatie gebruiksgegevens inclusief uw inlognaam, de ID's of aliassen van de kamers of groepen die u hebt bezocht, welke UI elementen u het laatst hebt gebruikt, en de inlognamen van andere personen. Ze bevatten geen berichten.", - "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Als u een bug hebt ingediend via GitHub, kunnen debug logs ons helpen het probleem op te sporen. Debug logs bevatten applicatie gebruiksgegevens inclusief uw inlognaam, de ID's of aliassen van de kamers of groepen die u hebt bezocht, welke UI elementen u het laatst hebt gebruikt, en de inlognamen van andere personen. Ze bevatten geen berichten." + "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Als u een bug hebt ingediend via GitHub, kunnen debug logs ons helpen het probleem op te sporen. Debug logs bevatten applicatie gebruiksgegevens inclusief uw inlognaam, de ID's of aliassen van de kamers of groepen die u hebt bezocht, welke UI elementen u het laatst hebt gebruikt, en de inlognamen van andere personen. Ze bevatten geen berichten.", + "Rooms and spaces": "Kamers en spaces", + "Results": "Resultaten", + "Enable encryption in settings.": "Versleuteling inschakelen in instellingen.", + "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Uw privéberichten zijn versleuteld, maar deze kamer niet. Dit komt vaak doordat u een niet ondersteund apparaat of methode, zoals e-mailuitnodigingen.", + "To avoid these issues, create a new public room for the conversation you plan to have.": "Om problemen te voorkomen, maak eennieuwe publieke kamer voor de gesprekken die u wilt voeren.", + "It's not recommended to make encrypted rooms public. It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "Het wordt afgeraden om publieke kamers te versleutelen. Het betekend dat iedereen u kan vinden en aan deelnemen, dus iedereen kan al de berichten lezen. U krijgt dus geen voordelen bij versleuteling. Versleutelde berichten in een publieke kamer maakt het ontvangen en versturen van berichten langzamer.", + "Are you sure you want to make this encrypted room public?": "Weet u zeker dat u deze publieke kamer wilt versleutelen?", + "To avoid these issues, create a new encrypted room for the conversation you plan to have.": "Om deze problemen te voorkomen, maak een nieuwe versleutelde kamer voor de gesprekken die u wilt voeren.", + "It's not recommended to add encryption to public rooms.Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "Het wordt afgeraden om versleuteling in te schakelen voor publieke kamers.Iedereen kan publieke kamers vinden en aan deelnemen, dus iedereen kan de berichten lezen. U krijgt geen voordelen van de versleuteling en u kunt het later niet uitschakelen. Berichten versleutelen in een publieke kamer maakt het ontvangen en versturen van berichten langzamer.", + "Are you sure you want to add encryption to this public room?": "Weet u zeker dat u versleuteling wil inschakelen voor deze publieke kamer?", + "Cross-signing is ready but keys are not backed up.": "Kruiselings ondertekenen is klaar, maar de sleutels zijn nog niet geback-upt.", + "Low bandwidth mode (requires compatible homeserver)": "Lage bandbreedte modus (compatibele homeserver vereist)", + "Multiple integration managers (requires manual setup)": "Meerdere integratiemanagers (vereist handmatige instelling)", + "Thread": "Draad", + "Show threads": "Draad weergeven", + "Threaded messaging": "Draad berichten", + "The above, but in as well": "Het bovenstaande, maar ook in ", + "The above, but in any room you are joined or invited to as well": "Het bovenstaande, maar in elke kamer waar u aan deelneemt en voor uitgenodigd bent", + "Autoplay videos": "Videos automatisch afspelen", + "Autoplay GIFs": "GIF's automatisch afspelen", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s maakte een vastgeprikt bericht los van deze kamer. Bekijk alle vastgeprikte berichten.", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s maakte een vastgeprikt bericht los van deze kamer. Bekijk alle vastgeprikte berichten.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s prikte een bericht vast aan deze kamer. Bekijk alle vastgeprikte berichten.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s prikte een bericht aan deze kamer. Bekijk alle vastgeprikte berichten.", + "Currently, %(count)s spaces have access|one": "Momenteel heeft één ruimte toegang", + "& %(count)s more|one": "& %(count)s meer" } diff --git a/src/i18n/strings/pl.json b/src/i18n/strings/pl.json index 524d73eecac..cccbed79a7a 100644 --- a/src/i18n/strings/pl.json +++ b/src/i18n/strings/pl.json @@ -2378,5 +2378,9 @@ "Identity server (%(server)s)": "Serwer tożsamości (%(server)s)", "Could not connect to identity server": "Nie można połączyć z serwerem tożsamości", "Not a valid identity server (status code %(code)s)": "Nieprawidłowy serwer tożsamości (kod statusu %(code)s)", - "Identity server URL must be HTTPS": "URL serwera tożsamości musi być HTTPS" + "Identity server URL must be HTTPS": "URL serwera tożsamości musi być HTTPS", + "Your homeserver rejected your log in attempt. This could be due to things just taking too long. Please try again. If this continues, please contact your homeserver administrator.": "Twój serwer domowy odrzucił twoją próbę zalogowania się. Może być to spowodowane zbyt długim czasem oczekiwania. Prosimy spróbować ponownie. Jeśli problem się powtórzy, prosimy o kontakt z administratorem twojego serwera domowego.", + "Failed to transfer call": "Nie udało się przekazać połączenia", + "Transfer Failed": "Transfer nie powiódł się", + "Unable to transfer call": "Nie udało się przekazać połączenia" } diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index 4368bbc33de..937461dbc7c 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -3667,10 +3667,28 @@ "Enable encryption in settings.": "Aktivizoni fshehtëzimin te rregullimet.", "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Mesazhet tuaja private normalisht fshehtëzohen, por kjo dhomë s’fshehtëzohet. Zakonisht kjo vjen për shkak të përdorimit të një pajisjeje ose metode të pambuluar, bie fjala, ftesa me email.", "Cross-signing is ready but keys are not backed up.": "“Cross-signing” është gati, por kyçet s’janë koperuajtur.", - "Rooms and spaces": "", - "Results": "", - "Are you sure you want to add encryption to this public room?": "A jeni të sigurtë që dëshironi të shtoni enkriptim në këtë dhomën publike?", - "Thumbs up": "Gishtin lart", + "Rooms and spaces": "Dhoma dhe hapësira", + "Results": "Përfundime", + "Are you sure you want to add encryption to this public room?": "A jeni i sigurt se doni të shtohet fshehtëzim në këtë dhomë publike?", + "Thumbs up": "", "Remain on your screen while running": "Rrini në ekran për deri sa është hapur", - "Remain on your screen when viewing another room, when running": "Rrini në ekran për deri sa jeni duke shikuar një dhomë tjetër" + "Remain on your screen when viewing another room, when running": "Rrini në ekran për deri sa jeni duke shikuar një dhomë tjetër", + "It's not recommended to make encrypted rooms public. It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "Nuk rekomandohet të bëhen publike dhoma të fshehtëzuara. Kjo do të thoshte se cilido mund të gjejë dhe hyjë te dhoma, pra cilido mund të lexojë mesazhet. S’do të përfitoni asnjë nga të mirat e fshehtëzimit. Fshehtëzimi i mesazheve në një dhomë publike do ta ngadalësojë marrjen dhe dërgimin e tyre.", + "Are you sure you want to make this encrypted room public?": "Jeni i sigurt se doni ta bëni publike këtë dhomë të fshehtëzuar?", + "To avoid these issues, create a new encrypted room for the conversation you plan to have.": "Për të shmangur këto probleme, krijoni një dhomë të re të fshehtëzuar për bisedën që keni në plan të bëni.", + "It's not recommended to add encryption to public rooms.Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "Nuk rekomandohet të shtohet fshehtëzim në dhoma publike.Dhomat publike mund t’i gjejë dhe hyjë në to kushdo, që cilido të mund të lexojë mesazhet në to. S’do të përfitoni asnjë nga të mirat e fshehtëzimit, dhe s’do të jeni në gjendje ta çaktivizoni më vonë. Fshehtëzimi i mesazheve në një dhomë publike do të ngadalësojë marrjen dhe dërgimin e mesazheve.", + "Thread": "Rrjedhë", + "Show threads": "Shfaq rrjedha", + "To avoid these issues, create a new public room for the conversation you plan to have.": "Për të shmangur këto probleme, krijoni për bisedën që keni në plan një dhomë të re publike.", + "Low bandwidth mode (requires compatible homeserver)": "Mënyra trafik me shpejtësi të ulët (lyp shërbyes Home të përputhshëm)", + "Multiple integration managers (requires manual setup)": "Përgjegjës të shumtë integrimi (lyp ujdisje dorazi)", + "Threaded messaging": "Mesazhe me rrjedha", + "The above, but in as well": "Atë më sipër, por edhe te ", + "The above, but in any room you are joined or invited to as well": "Atë më sipër, por edhe në çfarëdo dhome ku keni hyrë ose jeni ftuar", + "Autoplay videos": "Vetëluaji videot", + "Autoplay GIFs": "Vetëluaji GIF-et", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s hoqi fiksimin e një mesazhi nga kjo dhomë. Shihni krejt mesazhet e fiksuar.", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s hoqi fiksimin e një mesazhi nga kjo dhomë. Shihni krejt mesazhet e fiksuar.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s fiksoi një mesazh te kjo dhomë. Shihni krejt mesazhet e fiksuar.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s fiksoi një mesazh te kjo dhomë. Shini krejt mesazhet e fiksuar." } diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 477e1f3acfd..fdd5ab36bab 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -3606,5 +3606,18 @@ "Are you sure you want to make this encrypted room public?": "Är du säker på att du vill göra det här krypterade rummet offentligt?", "To avoid these issues, create a new encrypted room for the conversation you plan to have.": "För att undvika dessa problem, skapa ett nytt krypterat rum för konversationen du planerar att ha.", "Are you sure you want to add encryption to this public room?": "Är du säker på att du vill lägga till kryptering till det här offentliga rummet?", - "Cross-signing is ready but keys are not backed up.": "Korssignering är klart, men nycklarna är inte säkerhetskopierade än." + "Cross-signing is ready but keys are not backed up.": "Korssignering är klart, men nycklarna är inte säkerhetskopierade än.", + "Low bandwidth mode (requires compatible homeserver)": "Lågbandbreddsläge (kräver kompatibel hemserver)", + "Multiple integration managers (requires manual setup)": "Flera integrationshanterare (kräver manuell inställning)", + "Thread": "Trådar", + "Show threads": "Visa trådar", + "Autoplay videos": "Autospela videor", + "Autoplay GIFs": "Autospela GIF:ar", + "Threaded messaging": "Trådat meddelande", + "The above, but in as well": "Det ovanstående, men i också", + "The above, but in any room you are joined or invited to as well": "Det ovanstående, men i vilket som helst rum du är med i eller inbjuden till också", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s avfäste ett meddelande i det här rummet. Se alla fästa meddelanden.", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s avfäste ett meddelande i det här rummet. Se alla fästa meddelanden.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s fäste ett meddelande i det här rummet. Se alla fästa meddelanden.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s fäste ett meddelande i det här rummet. Se alla fästa meddelanden." } diff --git a/src/i18n/strings/uk.json b/src/i18n/strings/uk.json index 4aa92ee8634..f559f78d7dc 100644 --- a/src/i18n/strings/uk.json +++ b/src/i18n/strings/uk.json @@ -61,7 +61,7 @@ "%(senderName)s banned %(targetName)s.": "%(senderName)s заблокував/ла %(targetName)s.", "Ban": "Заблокувати", "Banned users": "Заблоковані користувачі", - "Bans user with given id": "Блокує користувача з вказаним ідентифікатором", + "Bans user with given id": "Блокує користувача зі вказаним ID", "Call Timeout": "Час очікування виклика", "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Не вдається підключитись до домашнього серверу - перевірте підключення, переконайтесь, що ваш SSL-сертифікат домашнього сервера є довіреним і що розширення браузера не блокує запити.", "Cannot add any more widgets": "Неможливо додати більше віджетів", @@ -82,7 +82,7 @@ "This email address is already in use": "Ця е-пошта вже використовується", "This phone number is already in use": "Цей телефонний номер вже використовується", "Fetching third party location failed": "Не вдалось отримати стороннє місцеперебування", - "Messages in one-to-one chats": "Повідомлення у балачках віч-на-віч", + "Messages in one-to-one chats": "Повідомлення у бесідах віч-на-віч", "Send Account Data": "Надіслати дані облікового запису", "Advanced notification settings": "Додаткові налаштування сповіщень", "Uploading report": "Завантаження звіту", @@ -90,7 +90,7 @@ "Guests can join": "Гості можуть приєднуватися", "Failed to add tag %(tagName)s to room": "Не вдалось додати до кімнати мітку %(tagName)s", "Notification targets": "Цілі сповіщень", - "Failed to set direct chat tag": "Не вдалося встановити мітку прямого чату", + "Failed to set direct chat tag": "Не вдалося встановити мітку особистої бесіди", "Today": "Сьогодні", "You are not receiving desktop notifications": "Ви не отримуєте системні сповіщення", "Friday": "П'ятниця", @@ -100,9 +100,9 @@ "Changelog": "Журнал змін", "Waiting for response from server": "Очікується відповідь від сервера", "Leave": "Вийти", - "Send Custom Event": "Відправити приватний захід", + "Send Custom Event": "Надіслати не стандартну подію", "All notifications are currently disabled for all targets.": "Сповіщення для усіх цілей наразі момент вимкнені.", - "Failed to send logs: ": "Не вдалося відправити журнали: ", + "Failed to send logs: ": "Не вдалося надіслати журнали: ", "Forget": "Забути", "World readable": "Відкрито для світу", "You cannot delete this image. (%(code)s)": "Ви не можете видалити це зображення. (%(code)s)", @@ -142,7 +142,7 @@ "Remove %(name)s from the directory?": "Прибрати %(name)s з каталогу?", "%(brand)s uses many advanced browser features, some of which are not available or experimental in your current browser.": "%(brand)s використовує багато новітніх функцій, деякі з яких не доступні або є експериментальними у вашому оглядачі.", "Developer Tools": "Інструменти розробника", - "Preparing to send logs": "Підготовка до відправки журланлу", + "Preparing to send logs": "Приготування до надсилання журланла", "Unnamed room": "Неназвана кімната", "Explore Account Data": "Переглянути дані облікового запису", "All messages (noisy)": "Усі повідомлення (гучно)", @@ -170,10 +170,10 @@ "Call invitation": "Запрошення до виклику", "Downloading update...": "Завантаженя оновлення…", "State Key": "Ключ стану", - "Failed to send custom event.": "Не вдалося відправити приватний захід.", + "Failed to send custom event.": "Не вдалося надіслати не стандартну подію.", "What's new?": "Що нового?", "Notify me for anything else": "Сповіщати мене про будь-що інше", - "View Source": "Переглянути джерело", + "View Source": "Переглянути код", "Can't update user notification settings": "Неможливо оновити налаштування користувацьких сповіщень", "Notify for all other messages/rooms": "Сповіщати щодо всіх повідомлень/кімнат", "Unable to look up room ID from server": "Неможливо знайти ID кімнати на сервері", @@ -188,7 +188,7 @@ "Unable to join network": "Неможливо приєднатись до мережі", "Sorry, your browser is not able to run %(brand)s.": "Вибачте, ваш оглядач не спроможний запустити %(brand)s.", "Uploaded on %(date)s by %(user)s": "Завантажено %(date)s користувачем %(user)s", - "Messages in group chats": "Повідомлення у групових балачках", + "Messages in group chats": "Повідомлення у групових бесідах", "Yesterday": "Вчора", "Error encountered (%(errorDetail)s).": "Трапилась помилка (%(errorDetail)s).", "Low Priority": "Неважливі", @@ -205,7 +205,7 @@ "Download this file": "Звантажити цей файл", "Pin Message": "Прикріпити повідомлення", "Failed to change settings": "Не вдалось змінити налаштування", - "Event sent!": "Захід відправлено!", + "Event sent!": "Подію надіслано!", "Unhide Preview": "Відкрити попередній перегляд", "Event Content": "Зміст заходу", "Thank you!": "Дякуємо!", @@ -272,7 +272,7 @@ "A call is already in progress!": "Вже здійснюється дзвінок!", "Permission Required": "Потрібен дозвіл", "You do not have permission to start a conference call in this room": "У вас немає дозволу, щоб розпочати дзвінок-конференцію в цій кімнаті", - "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Зверніть увагу: будь-яка людина, яку ви додаєте до спільноти, буде публічно видимою тим, хто знає ідентифікатор спільноти", + "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Зверніть увагу: будь-яка людина, яку ви додаєте до спільноти, буде видима усім, хто знає ID спільноти", "Invite new community members": "Запросити до спільноти", "Invite to Community": "Запросити до спільноти", "Which rooms would you like to add to this community?": "Які кімнати ви хочете додати до цієї спільноти?", @@ -287,7 +287,7 @@ "%(brand)s was not given permission to send notifications - please try again": "%(brand)s не має дозволу надсилати сповіщення — будь ласка, спробуйте ще раз", "Unable to enable Notifications": "Не вдалося увімкнути сповіщення", "This email address was not found": "Не знайдено адресу електронної пошти", - "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Схоже, ваша адреса електронної пошти не пов'язана з жодним ідентифікатор Matrix на цьому домашньому сервері.", + "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Схоже, ваша адреса е-пошти не пов'язана з жодним Matrix ID на цьому домашньому сервері.", "Restricted": "Обмежено", "Moderator": "Модератор", "Failed to invite": "Не вдалося запросити", @@ -295,7 +295,7 @@ "You need to be logged in.": "Вам потрібно увійти.", "You need to be able to invite users to do that.": "Щоб це зробити, вам необхідно мати можливість запрошувати людей.", "Unable to create widget.": "Неможливо створити віджет.", - "Missing roomId.": "Бракує ідентифікатора кімнати.", + "Missing roomId.": "Бракує ID кімнати.", "Failed to send request.": "Не вдалося надіслати запит.", "This room is not recognised.": "Кімнату не знайдено.", "Power level must be positive integer.": "Рівень повноважень мусить бути додатним цілим числом.", @@ -309,9 +309,9 @@ "/ddg is not a command": "/ddg не є командою", "To use it, just wait for autocomplete results to load and tab through them.": "Щоб цим скористатися, просто почекайте на підказки автодоповнення й перемикайтеся між ними клавішею TAB.", "Changes your display nickname": "Змінює ваш нік", - "Invites user with given id to current room": "Запрошує користувача з вказаним ідентифікатором до кімнати", + "Invites user with given id to current room": "Запрошує користувача зі вказаним ID до кімнати", "Leave room": "Залишити кімнату", - "Kicks user with given id": "Викидає з кімнати користувача з вказаним ідентифікатором", + "Kicks user with given id": "Викидає з кімнати користувача зі вказаним ID", "Ignores a user, hiding their messages from you": "Ігнорує користувача, приховуючи його повідомлення від вас", "Ignored user": "Зігнорований користувач", "You are now ignoring %(userId)s": "Ви ігноруєте %(userId)s", @@ -319,7 +319,7 @@ "Unignored user": "Припинено ігнорування користувача", "You are no longer ignoring %(userId)s": "Ви більше не ігноруєте %(userId)s", "Define the power level of a user": "Вказати рівень повноважень користувача", - "Deops user with given id": "Знімає права оператора з користувача з вказаним ідентифікатором", + "Deops user with given id": "Знімає права оператора з користувача зі вказаним ID", "Opens the Developer Tools dialog": "Відкриває вікно інструментів розробника", "Verified key": "Звірений ключ", "Displays action": "Показ дій", @@ -340,8 +340,8 @@ "%(senderName)s kicked %(targetName)s.": "%(senderName)s викинув/ла %(targetName)s.", "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s відкликав/ла запрошення %(targetName)s.", "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s надіслав(-ла) зображення.", - "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s призначив(-ла) основну адресу цієї кімнати: %(address)s.", - "%(senderName)s removed the main address for this room.": "%(senderName)s прибрав(-ла) основу адресу цієї кімнати.", + "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s встановлює основною адресою цієї кімнати %(address)s.", + "%(senderName)s removed the main address for this room.": "%(senderName)s вилучає основу адресу цієї кімнати.", "Someone": "Хтось", "(not supported by this browser)": "(не підтримується цією веб-переглядачкою)", "(could not connect media)": "(не можливо під'єднати медіа)", @@ -357,7 +357,7 @@ "%(senderName)s made future room history visible to anyone.": "%(senderName)s зробив(-ла) майбутню історію кімнати видимою для всіх.", "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s зробив(-ла) майбутню історію видимою для невідомого значення (%(visibility)s).", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s з %(fromPowerLevel)s до %(toPowerLevel)s", - "%(senderName)s changed the pinned messages for the room.": "%(senderName)s змінює приколоті повідомлення у кімнаті.", + "%(senderName)s changed the pinned messages for the room.": "%(senderName)s змінює прикріплені повідомлення у кімнаті.", "%(widgetName)s widget modified by %(senderName)s": "%(senderName)s змінює знадіб %(widgetName)s", "%(widgetName)s widget added by %(senderName)s": "%(senderName)s додав(-ла) знадіб %(widgetName)s", "%(widgetName)s widget removed by %(senderName)s": "%(senderName)s вилучив(-ла) знадіб %(widgetName)s", @@ -432,7 +432,7 @@ "Demote": "Знизити рівень прав", "Failed to mute user": "Не вдалося заглушити користувача", "Failed to change power level": "Не вдалося змінити рівень повноважень", - "Chat with %(brand)s Bot": "Балачка з %(brand)s-ботом", + "Chat with %(brand)s Bot": "Бесіда з %(brand)s-ботом", "Whether or not you're logged in (we don't record your username)": "Незалежно від того, увійшли ви чи ні (ми не записуємо ваше ім'я користувача)", "The file '%(fileName)s' failed to upload.": "Файл '%(fileName)s' не вийшло відвантажити.", "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Файл '%(fileName)s' перевищує ліміт розміру для відвантажень домашнього сервера", @@ -470,7 +470,7 @@ "Sets the room name": "Встановлює назву кімнати", "Use an identity server": "Використовувати сервер ідентифікації", "Use an identity server to invite by email. Manage in Settings.": "Використовувати сервер ідентифікації для запрошень через е-пошту. Керуйте у налаштуваннях.", - "Unbans user with given ID": "Розблоковує користувача з вказаним ідентифікатором", + "Unbans user with given ID": "Розблоковує користувача зі вказаним ID", "Adds a custom widget by URL to the room": "Додає власний віджет до кімнати за посиланням", "Please supply a https:// or http:// widget URL": "Вкажіть посилання на віджет — https:// або http://", "You cannot modify widgets in this room.": "Ви не можете змінювати віджети у цій кімнаті.", @@ -514,7 +514,7 @@ "Liberate your communication": "Вивільни своє спілкування", "Send a Direct Message": "Надіслати особисте повідомлення", "Explore Public Rooms": "Дослідити прилюдні кімнати", - "Create a Group Chat": "Створити групову балачку", + "Create a Group Chat": "Створити групову бесіду", "Explore": "Дослідити", "Filter": "Фільтрувати", "Filter rooms…": "Фільтрувати кімнати…", @@ -562,7 +562,7 @@ "Legal": "Правова інформація", "Credits": "Подяки", "For help with using %(brand)s, click here.": "Якщо необхідна допомога у користуванні %(brand)s'ом, клацніть тут.", - "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "Якщо необхідна допомога у користуванні %(brand)s'ом, клацніть тут або розпочніть балачку з нашим ботом, клацнувши на кнопці нижче.", + "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "Якщо необхідна допомога у користуванні %(brand)s, клацніть тут або розпочніть бесіду з нашим ботом, клацнувши на кнопку внизу.", "Join the conversation with an account": "Приєднатись до бесіди з обліковим записом", "Unable to restore session": "Не вдалося відновити сеанс", "We encountered an error trying to restore your previous session.": "Ми натрапили на помилку, намагаючись відновити ваш попередній сеанс.", @@ -578,9 +578,9 @@ "Forget this room": "Забути цю кімнату", "Re-join": "Перепід'єднатись", "This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "Це запрошення до %(roomName)s було надіслане на %(email)s, яка не пов'язана з вашим обліковим записом", - "Link this email with your account in Settings to receive invites directly in %(brand)s.": "Зв'яжіть цю е-пошту з вашим обліковим записом у Налаштуваннях щоб отримувати сповіщення прямо у %(brand)s.", + "Link this email with your account in Settings to receive invites directly in %(brand)s.": "Пов'яжіть цю е-пошту з вашим обліковим записом у Налаштуваннях, щоб отримувати запрошення безпосередньо в %(brand)s.", "This invite to %(roomName)s was sent to %(email)s": "Це запрошення до %(roomName)s було надіслане на %(email)s", - "Use an identity server in Settings to receive invites directly in %(brand)s.": "Використовувати сервер ідентифікації у Налаштуваннях щоб отримувати запрошення прямо у %(brand)s.", + "Use an identity server in Settings to receive invites directly in %(brand)s.": "Використовувати сервер ідентифікації у Налаштуваннях, щоб отримувати запрошення безпосередньо в %(brand)s.", "Are you sure you want to deactivate your account? This is irreversible.": "Ви впевнені у тому, що бажаєте знедіяти ваш обліковий запис? Ця дія безповоротна.", "Confirm account deactivation": "Підтвердьте знедіювання облікового запису", "To continue, please enter your password:": "Щоб продовжити, введіть, будь ласка, ваш пароль:", @@ -627,17 +627,17 @@ "Sends a message as html, without interpreting it as markdown": "Надсилає повідомлення у вигляді HTML, не інтерпретуючи його як розмітку", "Failed to set topic": "Не вдалося встановити тему", "Once enabled, encryption cannot be disabled.": "Після увімкнення шифрування не можна буде вимкнути.", - "Please enter verification code sent via text.": "Будь ласка, введіть звірювальний код, відправлений у текстовому повідомленні.", - "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "Текстове повідомлення було відправлено на номер +%(msisdn)s. Будь ласка, введіть звірювальний код, який воно містить.", + "Please enter verification code sent via text.": "Введіть код перевірки, надісланий у текстовому повідомленні.", + "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "Текстове повідомлення надіслано на номер +%(msisdn)s. Введіть код перевірки з нього.", "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Повідомлення у цій кімнаті захищені наскрізним шифруванням. Тільки ви та одержувачі мають ключі для прочитання цих повідомлень.", "Messages in this room are end-to-end encrypted.": "Повідомлення у цій кімнаті наскрізно зашифровані.", "Messages in this room are not end-to-end encrypted.": "Повідомлення у цій кімнаті не захищено наскрізним шифруванням.", "Encryption enabled": "Шифрування увімкнено", "Messages in this room are end-to-end encrypted. Learn more & verify this user in their user profile.": "Повідомлення у цій кімнаті наскрізно зашифровані. Дізнайтеся більше та звіртеся з цим користувачем через його профіль.", - "You sent a verification request": "Ви відправили звірювальний запит", + "You sent a verification request": "Ви надіслали запит перевірки", "Direct Messages": "Особисті повідомлення", "Room Settings - %(roomName)s": "Налаштування кімнати - %(roomName)s", - "A verification email will be sent to your inbox to confirm setting your new password.": "Ми відправимо перевіряльний електронний лист до вас для підтвердження зміни пароля.", + "A verification email will be sent to your inbox to confirm setting your new password.": "Ми надішлемо вам електронний лист перевірки для підтвердження зміни пароля.", "To return to your account in future you need to set a password": "Щоб повернутися до своєї обліківки в майбутньому, вам потрібно встановити пароль", "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Використовувати сервер ідентифікації, щоб запрошувати через е-пошту. Натисніть \"Продовжити\", щоб використовувати типовий сервер ідентифікації (%(defaultIdentityServerName)s) або змініть його у налаштуваннях.", "Joins room with given address": "Приєднатися до кімнати зі вказаною адресою", @@ -655,7 +655,7 @@ "Displays list of commands with usages and descriptions": "Відбиває перелік команд із прикладами вжитку та описом", "Displays information about a user": "Показує відомості про користувача", "Send a bug report with logs": "Надіслати звіт про ваду разом з журналами", - "Opens chat with the given user": "Відкриває балачку з вказаним користувачем", + "Opens chat with the given user": "Відкриває бесіду з вказаним користувачем", "Sends a message to the given user": "Надсилає повідомлення вказаному користувачеві", "%(senderName)s made no change.": "%(senderName)s не запровадив(-ла) жодних змін.", "%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.": "%(senderDisplayName)s змінює назву кімнати з %(oldRoomName)s на %(newRoomName)s.", @@ -668,10 +668,10 @@ "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s змінює гостьовий доступ на \"%(rule)s\"", "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s увімкнув(-ла) значок для %(groups)s у цій кімнаті.", "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s вимкнув(-ла) значок для %(groups)s в цій кімнаті.", - "%(senderName)s added the alternative addresses %(addresses)s for this room.|other": "%(senderName)s додав(-ла) альтернативні адреси %(addresses)s для цієї кімнати.", - "%(senderName)s added the alternative addresses %(addresses)s for this room.|one": "%(senderName)s додав(-ла) альтернативні адреси %(addresses)s для цієї кімнати.", - "%(senderName)s removed the alternative addresses %(addresses)s for this room.|other": "%(senderName)s прибрав(-ла) альтернативні адреси %(addresses)s для цієї кімнати.", - "%(senderName)s removed the alternative addresses %(addresses)s for this room.|one": "%(senderName)s прибрав(-ла) альтернативні адреси %(addresses)s для цієї кімнати.", + "%(senderName)s added the alternative addresses %(addresses)s for this room.|other": "%(senderName)s додає альтернативні адреси %(addresses)s для цієї кімнати.", + "%(senderName)s added the alternative addresses %(addresses)s for this room.|one": "%(senderName)s додає альтернативні адреси %(addresses)s для цієї кімнати.", + "%(senderName)s removed the alternative addresses %(addresses)s for this room.|other": "%(senderName)s вилучає альтернативні адреси %(addresses)s для цієї кімнати.", + "%(senderName)s removed the alternative addresses %(addresses)s for this room.|one": "%(senderName)s вилучає альтернативні адреси %(addresses)s для цієї кімнати.", "%(senderName)s changed the alternative addresses for this room.": "%(senderName)s змінює альтернативні адреси для цієї кімнати.", "%(senderName)s changed the main and alternative addresses for this room.": "%(senderName)s змінює головні та альтернативні адреси для цієї кімнати.", "%(senderName)s changed the addresses for this room.": "%(senderName)s змінює адреси для цієї кімнати.", @@ -680,18 +680,18 @@ "%(senderName)s placed a video call.": "%(senderName)s розпочинає відеовиклик.", "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s розпочинає відеовиклик. (не підтримується цим переглядачем)", "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s відкликав(-ла) запрошення %(targetDisplayName)s приєднання до кімнати.", - "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s видалив(-ла) правило блокування користувачів зі збігом з %(glob)s", - "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s видалив(-ла) правило блокування кімнат зі збігом з %(glob)s", - "%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s видалив(-ла) правило блокування серверів зі збігом з %(glob)s", - "%(senderName)s removed a ban rule matching %(glob)s": "%(senderName)s видалив(-ла) правило блокування зі збігом з %(glob)s", - "%(senderName)s updated an invalid ban rule": "%(senderName)s оновив(-ла) хибне правило блокування", - "%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s": "%(senderName)s оновив(-ла) правило блокування користувачів зі збігом з %(glob)s через %(reason)s", - "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s оновив(-ла) правило блокування кімнат зі збігом з %(glob)s через %(reason)s", - "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s оновив(-ла) правило блокування серверів зі збігом з %(glob)s через %(reason)s", - "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s": "%(senderName)s оновив(-ла) правило блокування зі збігом з %(glob)s через %(reason)s", - "%(senderName)s created a rule banning users matching %(glob)s for %(reason)s": "%(senderName)s створив(-ла) правило блокування користувачів зі збігом з %(glob)s через %(reason)s", - "%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s створив(-ла) правило блокування кімнат зі збігом з %(glob)s через %(reason)s", - "%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s створив(-ла) правило блокування серверів зі збігом з %(glob)s через %(reason)s", + "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s вилучає правило заборони користувачів зі збігом з %(glob)s", + "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s вилучає правило блокування кімнат зі збігом з %(glob)s", + "%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s вилучає правило блокування серверів зі збігом з %(glob)s", + "%(senderName)s removed a ban rule matching %(glob)s": "%(senderName)s вилучає правило блокування зі збігом з %(glob)s", + "%(senderName)s updated an invalid ban rule": "%(senderName)s оновлює хибне правило блокування", + "%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s": "%(senderName)s оновлює правило блокування користувачів зі збігом з %(glob)s через %(reason)s", + "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s оновлює правило блокування кімнат зі збігом з %(glob)s через %(reason)s", + "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s оновлює правило блокування серверів зі збігом з %(glob)s через %(reason)s", + "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s": "%(senderName)s оновлює правило блокування зі збігом з %(glob)s через %(reason)s", + "%(senderName)s created a rule banning users matching %(glob)s for %(reason)s": "%(senderName)s створює правило блокування користувачів зі збігом з %(glob)s через %(reason)s", + "%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s створює правило блокування кімнат зі збігом з %(glob)s через %(reason)s", + "%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s створює правило блокування серверів зі збігом з %(glob)s через %(reason)s", "Light": "Світла", "Dark": "Темна", "You signed in to a new session without verifying it:": "Ви увійшли в новий сеанс, не підтвердивши його:", @@ -731,7 +731,7 @@ "You do not have permission to invite people to this room.": "У вас немає прав запрошувати людей у цю кімнату.", "User %(userId)s is already in the room": "Користувач %(userId)s вже перебуває в кімнаті", "User %(user_id)s does not exist": "Користувача %(user_id)s не існує", - "The user must be unbanned before they can be invited.": "Користувач має бути розблокованим(ою), перед тим як може бути запрошений(ая).", + "The user must be unbanned before they can be invited.": "Потрібно розблокувати користувача перед тим як їх можна буде запросити.", "The user's homeserver does not support the version of the room.": "Домашній сервер користувача не підтримує версію кімнати.", "Unknown server error": "Невідома помилка з боку сервера", "Use a few words, avoid common phrases": "Використовуйте декілька слів, уникайте звичайних фраз", @@ -824,7 +824,7 @@ "Error adding ignored user/server": "Помилка при додаванні ігнорованого користувача/сервера", "Something went wrong. Please try again or view your console for hints.": "Щось пішло не так. Спробуйте знову, або пошукайте підказки в консолі.", "Error subscribing to list": "Помилка при підписці на список", - "Please verify the room ID or address and try again.": "Перевірте ID кімнати, або адресу та попробуйте знову.", + "Please verify the room ID or address and try again.": "Перевірте ID кімнати, або адресу та повторіть спробу.", "Error removing ignored user/server": "Помилка при видаленні ігнорованого користувача/сервера", "Error unsubscribing from list": "Не вдалося відписатися від списку", "Please try again or view your console for hints.": "Спробуйте знову, або подивіться повідомлення в консолі.", @@ -838,16 +838,16 @@ "View rules": "Подивитись правила", "You are currently subscribed to:": "Ви підписані на:", "⚠ These settings are meant for advanced users.": "⚠ Ці налаштування розраховані на досвідчених користувачів.", - "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Ігнорування людей реалізовано через списки правил блокування. Підписка на список блокування призведе до приховування від вас користувачів і серверів, які в ньому перераховані.", - "Personal ban list": "Особистий бан-ліст", - "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Ваш особистий список блокування містить всіх користувачів і сервери, повідомлення яких ви не хочете бачити. Після внесення туди першого користувача / сервера в списку кімнат з'явиться нова кімната 'Мій список блокування' - не покидає цю кімнату, щоб список блокування працював.", + "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Нехтування людей реалізовано через списки правил блокування. Підписка на список блокування призведе до приховування від вас перелічених у ньому користувачів і серверів.", + "Personal ban list": "Особистий список блокування", + "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Ваш особистий список блокування містить усіх користувачів і сервери, повідомлення яких ви не хочете бачити. Після внесення туди першого користувача/сервера в списку кімнат з'явиться нова кімната «Мій список блокування» — не залишайте цю кімнату, щоб список блокування працював.", "Server or user ID to ignore": "Сервер або ID користувача для ігнорування", "eg: @bot:* or example.org": "наприклад: @bot:* або example.org", "Ignore": "Ігнорувати", "Subscribed lists": "Підписані списки", - "Subscribing to a ban list will cause you to join it!": "При підписці на список блокування ви приєднаєтесь до нього!", + "Subscribing to a ban list will cause you to join it!": "Підписавшись на список блокування ви приєднаєтесь до нього!", "If this isn't what you want, please use a different tool to ignore users.": "Якщо вас це не влаштовує, спробуйте інший інструмент для ігнорування користувачів.", - "Room ID or address of ban list": "Ідентифікатор номера або адреса бан-лісту", + "Room ID or address of ban list": "ID кімнати або адреса списку блокування", "Subscribe": "Підписатись", "Start automatically after system login": "Автозапуск при вході в систему", "Always show the window menu bar": "Завжди показувати рядок меню", @@ -855,7 +855,7 @@ "Preferences": "Параметри", "Room list": "Перелік кімнат", "Composer": "Редактор", - "Security & Privacy": "Безпека та конфіденційність", + "Security & Privacy": "Безпека й приватність", "Where you’re logged in": "Де ви ввійшли", "Skip": "Пропустити", "Notification settings": "Налаштування сповіщень", @@ -869,7 +869,7 @@ "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (повноваження %(powerLevelNumber)s)", "Send a message…": "Надіслати повідомлення…", "People": "Люди", - "Share this email in Settings to receive invites directly in %(brand)s.": "Поширте цю адресу е-пошти у налаштуваннях щоб отримувати запрошення прямо у %(brand)s.", + "Share this email in Settings to receive invites directly in %(brand)s.": "Поширте цю адресу е-пошти у налаштуваннях, щоб отримувати запрошення безпосередньо в %(brand)s.", "Room options": "Параметри кімнати", "Send as message": "Надіслати як повідомлення", "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Ви не зможете скасувати цю зміну через те, що ви підвищуєте рівень повноважень користувача до свого рівня.", @@ -895,11 +895,11 @@ "Cat": "Кіт", "Lion": "Лев", "Horse": "Кінь", - "Pig": "Свиня", + "Pig": "Порося", "Elephant": "Слон", "Rabbit": "Кріль", "Panda": "Панда", - "Rooster": "Когут", + "Rooster": "Півень", "Penguin": "Пінгвін", "Turtle": "Черепаха", "Fish": "Риба", @@ -935,11 +935,11 @@ "Bicycle": "Велоcипед", "Aeroplane": "Літак", "Rocket": "Ракета", - "Trophy": "Приз", + "Trophy": "Кубок", "Ball": "М'яч", "Guitar": "Гітара", "Trumpet": "Труба", - "Bell": "Дзвін", + "Bell": "Дзвінок", "Anchor": "Якір", "Headphones": "Навушники", "Folder": "Тека", @@ -1047,24 +1047,24 @@ "Revoke invite": "Відкликати запрошення", "Security": "Безпека", "Report bugs & give feedback": "Відзвітувати про вади та залишити відгук", - "Report Content to Your Homeserver Administrator": "Поскаржитись на зміст адміністратору вашого домашнього сервера", + "Report Content to Your Homeserver Administrator": "Поскаржитися на вміст адміністратору вашого домашнього сервера", "Failed to upgrade room": "Не вдалось поліпшити кімнату", "The room upgrade could not be completed": "Поліпшення кімнати не може бути завершене", "Upgrade this room to version %(version)s": "Поліпшити цю кімнату до версії %(version)s", "Upgrade Room Version": "Поліпшити версію кімнати", "Upgrade private room": "Поліпшити закриту кімнату", "You'll upgrade this room from to .": "Ви поліпшите цю кімнату з до версії.", - "Share Room Message": "Поширити повідомлення кімнати", - "Report Content": "Поскаржитись на зміст", + "Share Room Message": "Поділитися повідомленням кімнати", + "Report Content": "Поскаржитись на вміст", "Feedback": "Зворотній зв'язок", "General failure": "Загальний збій", "Enter your account password to confirm the upgrade:": "Введіть пароль вашого облікового запису щоб підтвердити поліпшення:", - "Security & privacy": "Безпека та конфіденційність", + "Security & privacy": "Безпека й приватність", "Secret storage public key:": "Таємне сховище відкритого ключа:", "Key backup": "Резервне копіювання ключів", "Message search": "Пошук повідомлень", "Cross-signing": "Перехресне підписування", - "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Адміністратором вашого сервера було вимкнено початкове наскрізне шифрування у закритих кімнатах та особистих повідомленнях.", + "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Адміністратором вашого сервера було вимкнено автоматичне наскрізне шифрування у приватних кімнатах і особистих повідомленнях.", "Something went wrong!": "Щось пішло не так!", "expand": "розгорнути", "Wrong Recovery Key": "Неправильний відновлювальний ключ", @@ -1102,7 +1102,7 @@ "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Поліпште цей сеанс щоб уможливити звіряння інших сеансів, надаючи їм доступ до зашифрованих повідомлень та позначуючи їх довіреними для інших користувачів.", "Upgrade your encryption": "Поліпшити ваше шифрування", "Show a placeholder for removed messages": "Показувати замісну позначку замість видалених повідомлень", - "Show join/leave messages (invites/kicks/bans unaffected)": "Показувати повідомлення про приєднання/залишення (не впливає на запрошення/викидання/заборону)", + "Show join/leave messages (invites/kicks/bans unaffected)": "Показувати повідомлення про приєднання/залишення (не впливає на запрошення/викидання/блокування)", "Show avatar changes": "Показувати зміни личини", "Show display name changes": "Показувати зміни видимого імені", "Show read receipts sent by other users": "Показувати мітки прочитання, надіслані іншими користувачами", @@ -1112,8 +1112,8 @@ "Never send encrypted messages to unverified sessions in this room from this session": "Ніколи не надсилати зашифровані повідомлення до незвірених сеансів у цій кімнаті з цього сеансу", "Enable message search in encrypted rooms": "Увімкнути шукання повідомлень у зашифрованих кімнатах", "IRC display name width": "Ширина видимого імені IRC", - "Encrypted messages in one-to-one chats": "Зашифровані повідомлення у балачках віч-на-віч", - "Encrypted messages in group chats": "Зашифровані повідомлення у групових балачках", + "Encrypted messages in one-to-one chats": "Зашифровані повідомлення у бесідах віч-на-віч", + "Encrypted messages in group chats": "Зашифровані повідомлення у групових бесідах", "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Захищені повідомлення з цим користувачем є наскрізно зашифрованими та непрочитними для сторонніх осіб.", "Securely cache encrypted messages locally for them to appear in search results.": "Безпечно локально кешувати зашифровані повідомлення щоб вони з'являлись у результатах пошуку.", "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with search components added.": "%(brand)s'ові бракує деяких складників, необхідних для безпечного локального кешування зашифрованих повідомлень. Якщо ви хочете поекспериментувати з цією властивістю, зберіть спеціальну збірку %(brand)s Desktop із доданням пошукових складників.", @@ -1152,12 +1152,12 @@ "I don't want my encrypted messages": "Мені не потрібні мої зашифровані повідомлення", "You'll lose access to your encrypted messages": "Ви втратите доступ до ваших зашифрованих повідомлень", "Use this session to verify your new one, granting it access to encrypted messages:": "Використати цей сеанс для звірення вашого нового сеансу, надаючи йому доступ до зашифрованих повідомлень:", - "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Скарження на це повідомлення надішле його унікальний 'ідентифікатор події (event ID)' адміністраторові вашого домашнього сервера. Якщо повідомлення у цій кімнаті зашифровані, то адміністратор не зможе бачити ані тексту повідомлень, ані жодних файлів чи зображень.", + "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Зі скаргою на це повідомлення буде надіслано його унікальний «ID події» адміністраторові вашого домашнього сервера. Якщо повідомлення у цій кімнаті зашифровані, то адміністратор не зможе побачити ані тексту повідомлень, ані жодних файлів чи зображень.", "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Бракує деяких даних сеансу, включно з ключами зашифрованих повідомлень. Вийдіть та зайдіть знову щоб виправити цю проблему, відновлюючи ключі з дубля.", "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Було виявлено дані зі старої версії %(brand)s. Це призведе до збоїння наскрізного шифрування у старій версії. Наскрізно зашифровані повідомлення, що обмінювані нещодавно, під час використання старої версії, можуть бути недешифровними у цій версії. Це може призвести до збоїв повідомлень, обмінюваних також і з цією версією. У разі виникнення проблем вийдіть з програми та зайдіть знову. Задля збереження історії повідомлень експортуйте та переімпортуйте ваші ключі.", "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Змінення паролю скине всі ключі наскрізного шифрування в усіх ваших сеансах, роблячи зашифровану історію листувань нечитабельною. Налагодьте дублювання ключів або експортуйте ключі кімнат з іншого сеансу перед скиданням пароля.", "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Підтвердьте вашу особу шляхом звіряння цього входу з одного з інших ваших сеансів, надаючи йому доступ до зашифрованих повідомлень.", - "Enable big emoji in chat": "Увімкнути великі емодзі у балачках", + "Enable big emoji in chat": "Увімкнути великі емоджі у бесідах", "Show typing notifications": "Сповіщати про друкування", "Show rooms with unread notifications first": "Спочатку показувати кімнати з непрочитаними сповіщеннями", "Show shortcuts to recently viewed rooms above the room list": "Показувати нещодавно бачені кімнати вгорі понад переліком кімнат", @@ -1181,11 +1181,11 @@ "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Ваш новий сеанс тепер є звірений. Він має доступ до ваших зашифрованих повідомлень, а інші користувачі бачитимуть його як довірений.", "Emoji": "Емодзі", "Emoji Autocomplete": "Самодоповнення емодзі", - "%(senderName)s created a ban rule matching %(glob)s for %(reason)s": "%(senderName)s створив(-ла) правило блокування зі збігом з %(glob)s через %(reason)s", + "%(senderName)s created a ban rule matching %(glob)s for %(reason)s": "%(senderName)s створює правило блокування зі збігом з %(glob)s через %(reason)s", "%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s змінює правило блокування користувачів зі збігу з %(oldGlob)s на збіг з %(newGlob)s через %(reason)s", "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s змінює правило блокування кімнат зі збігу з %(oldGlob)s на збіг з %(newGlob)s через %(reason)s", "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s змінює правило блокування серверів зі збігу з %(oldGlob)s на збіг з %(newGlob)s через %(reason)s", - "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s змінєю правило блокування зі збігу з %(oldGlob)s на збіг з %(newGlob)s через %(reason)s", + "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s змінює правило блокування зі збігу з %(oldGlob)s на збіг з %(newGlob)s через %(reason)s", "Enable Community Filter Panel": "Увімкнути фільтр панелі спільнот", "Messages containing my username": "Повідомлення, що містять моє користувацьке ім'я", "Messages containing @room": "Повідомлення, що містять @room", @@ -1237,9 +1237,9 @@ "%(name)s (%(userId)s)": "%(name)s (%(userId)s)", "Unexpected server error trying to leave the room": "Виникла неочікувана помилка серверу під час спроби залишити кімнату", "Unknown App": "Невідомий додаток", - "Send anonymous usage data which helps us improve %(brand)s. This will use a cookie.": "Відправляти анонімну статистику користування, що дозволяє нам покращувати %(brand)s. Це використовує кукі.", + "Send anonymous usage data which helps us improve %(brand)s. This will use a cookie.": "Надсилати анонімну статистику користування, що дозволяє нам вдосконалювати %(brand)s. Це використовує кукі.", "Set up Secure Backup": "Налаштувати захищене резервне копіювання", - "Safeguard against losing access to encrypted messages & data": "Захист від втрати доступу до зашифрованих повідомлень та даних", + "Safeguard against losing access to encrypted messages & data": "Захистіться від втрати доступу до зашифрованих повідомлень і даних", "The person who invited you already left the room.": "Особа, що вас запросила, вже залишила кімнату.", "The person who invited you already left the room, or their server is offline.": "Особа, що вас запросила вже залишила кімнату, або її сервер відімкнено.", "Change notification settings": "Змінити налаштування сповіщень", @@ -1633,10 +1633,10 @@ "Sends the given message as a spoiler": "Надсилає вказане повідомлення згорненим", "Integration manager": "Менеджер інтеграцій", "Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "Ваш %(brand)s не дозволяє вам користуватись для цього менеджером інтеграцій. Зверніться до адміністратора.", - "Using this widget may share data with %(widgetDomain)s & your integration manager.": "Користування цим знадобом може призвести до поширення ваших даних з %(widgetDomain)s та вашим менеджером інтеграцій.", + "Using this widget may share data with %(widgetDomain)s & your integration manager.": "Користування цим віджетом може призвести до поширення ваших даних через %(widgetDomain)s і ваш менеджер інтеграцій.", "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Менеджери інтеграцій отримують дані конфігурації та можуть змінювати знадоби, надсилати запрошення у кімнати й встановлювати рівні повноважень від вашого імені.", - "Use an integration manager to manage bots, widgets, and sticker packs.": "Використовувати менеджер інтеграцій для керування ботами, знадобами та паками наліпок.", - "Use an integration manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Використовувати менеджер інтеграцій %(serverName)s для керування ботами, знадобами та паками наліпок.", + "Use an integration manager to manage bots, widgets, and sticker packs.": "Використовувати менеджер інтеграцій для керування ботами, віджетами й пакунками наліпок.", + "Use an integration manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Використовувати менеджер інтеграцій %(serverName)s для керування ботами, віджетами й пакунками наліпок.", "Identity server": "Сервер ідентифікації", "Identity server (%(server)s)": "Сервер ідентифікації (%(server)s)", "Could not connect to identity server": "Не вдалося під'єднатись до сервера ідентифікації", @@ -1710,5 +1710,143 @@ "edited": "змінено", "Edited at %(date)s. Click to view edits.": "Змінено %(date)s. Натисніть, щоб переглянути зміни.", "Edited at %(date)s": "Змінено %(date)s", - "%(senderName)s changed their profile picture": "%(senderName)s змінює зображення профілю" + "%(senderName)s changed their profile picture": "%(senderName)s змінює зображення профілю", + "Phone Number": "Телефонний номер", + "%(oneUser)sleft %(count)s times|one": "%(oneUser)sвиходить", + "%(oneUser)sleft %(count)s times|other": "%(oneUser)sвийшли %(count)s разів", + "%(severalUsers)sleft %(count)s times|one": "%(severalUsers)sвийшли", + "%(severalUsers)sleft %(count)s times|other": "%(severalUsers)sвийшли %(count)s разів", + "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", + "Language Dropdown": "Спадне меню мов", + "Information": "Відомості", + "Rotate Right": "Обернути праворуч", + "Rotate Left": "Обернути ліворуч", + "Zoom in": "Збільшити", + "Zoom out": "Зменшити", + "collapse": "згорнути", + "No results": "Немає результатів", + "Application window": "Вікно застосунку", + "Error - Mixed content": "Помилка — змішаний вміст", + "Widget ID": "ID віджета", + "%(brand)s URL": "URL-адреса %(brand)s", + "Your theme": "Ваша тема", + "Your user ID": "Ваш ID користувача", + "Your avatar URL": "URL-адреса вашого аватара", + "Unknown Address": "Невідома адреса", + "Cancel search": "Скасувати пошук", + "Quick Reactions": "Швидкі реакції", + "Categories": "Категорії", + "Flags": "Прапори", + "Symbols": "Символи", + "Objects": "Об'єкти", + "Travel & Places": "Подорожі та місця", + "Food & Drink": "Їжа та напої", + "Animals & Nature": "Тварини та природа", + "Smileys & People": "Емоджі та люди", + "Frequently Used": "Часто використовувані", + "Activities": "Діяльність", + "Failed to unban": "Не вдалося розблокувати", + "Banned by %(displayName)s": "Блокує %(displayName)s", + "Ban users": "Блокування користувачів", + "You were banned from %(roomName)s by %(memberName)s": "%(memberName)s блокує вас у %(roomName)s", + "were banned %(count)s times|other": "заблоковані %(count)s разів", + "were banned %(count)s times|one": "заблоковані", + "was banned %(count)s times|other": "заблоковано %(count)s разів", + "was banned %(count)s times|one": "заблоковано", + "were unbanned %(count)s times|other": "розблоковані %(count)s разів", + "were unbanned %(count)s times|one": "розблоковані", + "was unbanned %(count)s times|other": "розблоковано %(count)s разів", + "was unbanned %(count)s times|one": "розблоковано", + "This is the beginning of your direct message history with .": "Це початок історії вашого особистого спілкування з .", + "Publish this room to the public in %(domain)s's room directory?": "Опублікувати цю кімнату для всіх у каталозі кімнат %(domain)s?", + "Direct message": "Особисте повідомлення", + "Recently Direct Messaged": "Недавно надіслані особисті повідомлення", + "User Directory": "Каталог користувачів", + "Room version:": "Версія кімнати:", + "Change topic": "Змінити тему", + "Change the topic of this room": "Змінити тему цієї кімнати", + "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)sзмінює серверні права доступу", + "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)sзмінює серверні права доступу %(count)s разів", + "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)sзмінює серверні права доступу", + "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)sзмінює серверні права доступу %(count)s разів", + "Change server ACLs": "Змінити серверні права доступу", + "Change permissions": "Змінити дозволи", + "Change room name": "Змінити назву кімнати", + "Change the name of this room": "Змінити назву цієї кімнати", + "Change history visibility": "Змінити видимість історії", + "Change main address for the room": "Змінити основну адресу кімнати", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s змінює аватар кімнати на ", + "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s змінює аватар %(roomName)s", + "Change room avatar": "Змінити аватар кімнати", + "Change the avatar of this room": "Змінює аватар цієї кімнати", + "Modify widgets": "Змінити віджети", + "Notify everyone": "Сповістити всіх", + "Remove messages sent by others": "Вилучити повідомлення надіслані іншими", + "Kick users": "Викинути користувачів", + "Invite users": "Запросити користувачів", + "Select the roles required to change various parts of the room": "Виберіть ролі, необхідні для зміни різних частин кімнати", + "Default role": "Типова роль", + "Privileged Users": "Привілейовані користувачі", + "Roles & Permissions": "Ролі й дозволи", + "Main address": "Основна адреса", + "Error updating main address": "Помилка оновлення основної адреси", + "No other published addresses yet, add one below": "Поки немає загальнодоступних адрес, додайте їх унизу", + "Other published addresses:": "Інші загальнодоступні адреси:", + "Published addresses can be used by anyone on any server to join your room.": "Загальнодоступні адреси можуть бути використані будь-ким на будь-якому сервері для приєднання до вашої кімнати.", + "Published addresses can be used by anyone on any server to join your space.": "Загальнодоступні адреси можуть бути використані будь-ким на будь-якому сервері для приєднання до вашого простору.", + "Published Addresses": "Загальнодоступні адреси", + "Room Addresses": "Адреси кімнати", + "Your Security Key is in your Downloads folder.": "Ваш ключ безпеки перебуває у теці Завантажень.", + "Error downloading audio": "Помилка завантаження аудіо", + "Download logs": "Завантажити журнали", + "Preparing to download logs": "Приготування до завантаження журналів", + "Download %(text)s": "Завантажити %(text)s", + "Download": "Завантажити", + "Error downloading theme information.": "Помилка завантаження відомостей теми.", + "Matrix Room ID": "Matrix ID кімнати", + "Room ID": "ID кімнати", + "Failed to remove '%(roomName)s' from %(groupId)s": "Не вдалося вилучити «%(roomName)s» з %(groupId)s", + "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Ви впевнені, що хочете вилучити «%(roomName)s» з %(groupId)s?", + "Decide who can join %(roomName)s.": "Вкажіть, хто може приєднуватися до %(roomName)s.", + "Internal room ID:": "Внутрішній ID кімнати:", + "User %(userId)s is already invited to the room": "Користувача %(userId)s вже запрошено до кімнати", + "Original event source": "Оригінальний початковий код", + "View source": "Переглянути код", + "Report": "Поскаржитися", + "Send report": "Надіслати звіт", + "Report the entire room": "Поскаржитися на всю кімнату", + "What this user is writing is wrong.\nThis will be reported to the room moderators.": "Те, що пише цей користувач, неправильно.\nПро це буде повідомлено модераторам кімнати.", + "Please fill why you're reporting.": "Будь ласка, вкажіть, чому ви скаржитеся.", + "Report a bug": "Повідомити про ваду", + "Share %(name)s": "Поділитися %(name)s", + "Share Community": "Поділитися спільнотою", + "Share User": "Поділитися користувачем", + "Share content": "Поділитися вмістом", + "Share entire screen": "Поділитися всім екраном", + "Any of the following data may be shared:": "Можна поділитися будь-якими з цих даних:", + "Unable to share phone number": "Не вдалося надіслати телефонний номер", + "Share": "Поділитись", + "Unable to share email address": "Не вдалося надіслати адресу е-пошти", + "Share invite link": "Надіслати запрошувальне посилання", + "%(sharerName)s is presenting": "%(sharerName)s показує", + "Yes": "Так", + "Invite to %(spaceName)s": "Запросити до %(spaceName)s", + "Share your public space": "Поділитися своїм загальнодоступним простором", + "Forward": "Переслати", + "Forward message": "Переслати повідомлення", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Бета-версія доступна для переглядачів інтернету, настільних ПК та Android. Деякі функції можуть бути недоступні на вашому домашньому сервері.", + "Join the beta": "Долучитися до beta", + "To join %(spaceName)s, turn on the Spaces beta": "Щоб приєднатися до %(spaceName)s, увімкніть Простори beta", + "Spaces are a new way to group rooms and people.": "Простори — це новий спосіб згуртувати кімнати та людей.", + "Communities are changing to Spaces": "Спільноти змінюються на Простори", + "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Створіть спільноту, щоб об’єднати користувачів та кімнати! Створіть власну домашню сторінку, щоб позначити своє місце у всесвіті Matrix.", + "Some suggestions may be hidden for privacy.": "Деякі пропозиції можуть бути сховані для приватності.", + "Privacy Policy": "Політика приватності", + "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Приватність важлива для нас, тому ми не збираємо жодних особистих або ідентифікаційних даних для нашої аналітики.", + "Privacy": "Приватність", + "To help space members find and join a private room, go to that room's Security & Privacy settings.": "Щоб допомогти учасникам простору знайти та приєднатися до приватної кімнати, перейдіть у налаштування безпеки й приватності цієї кімнати.", + "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Захистіться від втрати доступу до зашифрованих повідомлень і даних створенням резервної копії ключів шифрування на своєму сервері.", + "Secure Backup": "Безпечне резервне копіювання", + "Give feedback.": "Надіслати відгук.", + "You may contact me if you have any follow up questions": "Можете зв’язатися зі мною, якщо у вас виникнуть додаткові запитання" } diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json index bbb4c198cea..62749185ac8 100644 --- a/src/i18n/strings/zh_Hans.json +++ b/src/i18n/strings/zh_Hans.json @@ -71,7 +71,7 @@ "%(senderName)s set a profile picture.": "%(senderName)s 设置了头像。", "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s 将昵称改为了 %(displayName)s。", "Settings": "设置", - "Show timestamps in 12 hour format (e.g. 2:30pm)": "用12小时制显示时间戳 (如:下午 2:30)", + "Show timestamps in 12 hour format (e.g. 2:30pm)": "使用 12 小时制显示时间戳 (下午 2:30)", "Signed Out": "已退出登录", "Sign in": "登录", "Sign out": "注销", @@ -344,7 +344,7 @@ "(~%(count)s results)|one": "(~%(count)s 个结果)", "(~%(count)s results)|other": "(~%(count)s 个结果)", "Please select the destination room for this message": "请选择此消息的目标聊天室", - "Start automatically after system login": "在系统登录后自动启动", + "Start automatically after system login": "开机自启", "Analytics": "统计分析服务", "Reject all %(invitedRooms)s invites": "拒绝所有 %(invitedRooms)s 的邀请", "Sun": "周日", @@ -513,7 +513,7 @@ "Stickerpack": "贴纸包", "You don't currently have any stickerpacks enabled": "你目前未启用任何贴纸包", "Key request sent.": "已发送密钥共享请求。", - "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "如果你是房间中最后一位有权限的用户,在你降低自己的权限等级后将无法撤回此修改,因为你将无法重新获得权限。", + "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "如果你是聊天室中最后一位拥有权限的用户,在你降低自己的权限等级后将无法撤销此修改,你将无法重新获得权限。", "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "你将无法撤回此修改,因为此用户的等级将与你相同。", "Unmute": "取消静音", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s(特权等级 %(powerLevelNumber)s)", @@ -538,7 +538,7 @@ "Failed to remove user from community": "移除用户失败", "Filter community members": "过滤社群成员", "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "你确定要从 %(groupId)s 中移除 %(roomName)s 吗?", - "Removing a room from the community will also remove it from the community page.": "从社群中移除房间时,同时也会将其从社群页面中移除。", + "Removing a room from the community will also remove it from the community page.": "从社群中移除聊天室时,同时也会将其从社群页面中移除。", "Failed to remove room from community": "从社群中移除聊天室失败", "Failed to remove '%(roomName)s' from %(groupId)s": "从 %(groupId)s 中移除 “%(roomName)s” 失败", "Only visible to community members": "仅对社群成员可见", @@ -658,8 +658,8 @@ "This Room": "此聊天室", "Noisy": "响铃", "Error saving email notification preferences": "保存电子邮件通知选项时出错", - "Messages containing my display name": "消息中含有我的显示名称", - "Messages in one-to-one chats": "一对一聊天中的消息", + "Messages containing my display name": "当消息包含我的昵称时", + "Messages in one-to-one chats": "私聊中的消息", "Unavailable": "无法获得", "View Decrypted Source": "查看解密的来源", "Failed to update keywords": "无法更新关键词", @@ -712,7 +712,7 @@ "Quote": "引述", "Send logs": "发送日志", "All messages": "全部消息", - "Call invitation": "语音邀请", + "Call invitation": "当受到通话邀请时", "Downloading update...": "正在下载更新…", "State Key": "状态键(State Key)", "Failed to send custom event.": "自定义事件发送失败。", @@ -735,7 +735,7 @@ "Unable to join network": "无法加入网络", "Sorry, your browser is not able to run %(brand)s.": "抱歉,您的浏览器 无法 运行 %(brand)s.", "Uploaded on %(date)s by %(user)s": "由 %(user)s 在 %(date)s 上传", - "Messages in group chats": "群组聊天中的消息", + "Messages in group chats": "群聊中的消息", "Yesterday": "昨天", "Error encountered (%(errorDetail)s).": "遇到错误 (%(errorDetail)s)。", "Low Priority": "低优先级", @@ -866,7 +866,7 @@ "There was an error joining the room": "加入聊天室时发生错误", "Custom user status messages": "自定义用户状态信息", "Show developer tools": "显示开发者工具", - "Messages containing @room": "含有 @room 的消息", + "Messages containing @room": "当消息包含 @room 时", "Delete Backup": "删除备份", "Backup version: ": "备份版本: ", "Algorithm: ": "算法: ", @@ -944,7 +944,7 @@ "Render simple counters in room header": "在聊天室标题中显示简单计数", "Enable Emoji suggestions while typing": "启用实时表情符号建议", "Show a placeholder for removed messages": "已移除的消息显示为一个占位符", - "Show join/leave messages (invites/kicks/bans unaffected)": "显示 加入/离开 消息(邀请/移除/封禁 不受影响)", + "Show join/leave messages (invites/kicks/bans unaffected)": "显示加入/离开提示(邀请/移除/封禁提示不受此影响)", "Show avatar changes": "显示头像更改", "Show display name changes": "显示昵称更改", "Show read receipts sent by other users": "显示其他用户发送的已读回执", @@ -955,8 +955,8 @@ "Enable Community Filter Panel": "启用社群筛选器面板", "Allow Peer-to-Peer for 1:1 calls": "允许一对一通话使用 P2P", "Prompt before sending invites to potentially invalid matrix IDs": "在发送邀请之前提示可能无效的 Matrix ID", - "Messages containing my username": "包含我的用户名的消息", - "Encrypted messages in one-to-one chats": "一对一聊天中的加密消息", + "Messages containing my username": "当消息包含我的用户名时", + "Encrypted messages in one-to-one chats": "私聊中的加密消息", "Encrypted messages in group chats": "群聊中的加密消息", "The other party cancelled the verification.": "另一方取消了验证。", "Verified!": "已验证!", @@ -1050,14 +1050,14 @@ "Set a new account password...": "设置一个新的账号密码...", "Email addresses": "电子邮箱地址", "Phone numbers": "电话号码", - "Language and region": "语言与区域", + "Language and region": "语言与地区", "Theme": "主题", "Account management": "账号管理", "Deactivating your account is a permanent action - be careful!": "停用你的账号是一项永久性操作 - 请小心!", "General": "通用", "Credits": "感谢", - "For help with using %(brand)s, click here.": "对使用 %(brand)s 的说明,请点击 这里。", - "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "对使用 %(brand)s 的说明,请点击 这里 或者使用下面的按钮开始与我们的机器人聊聊。", + "For help with using %(brand)s, click here.": "关于 %(brand)s 的使用说明。", + "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "关于 %(brand)s 的使用说明,请点击这里或者通过下方按钮同我们的机器人聊聊。", "Chat with %(brand)s Bot": "与 %(brand)s 机器人聊天", "Help & About": "帮助及关于", "Bug reporting": "错误上报", @@ -1108,7 +1108,7 @@ "Invite anyway": "还是邀请", "Before submitting logs, you must create a GitHub issue to describe your problem.": "在提交日志之前,你必须创建一个GitHub issue 来描述你的问题。", "Unable to load commit detail: %(msg)s": "无法加载提交详情:%(msg)s", - "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this": "为避免丢失聊天记录,你必须在登出前导出房间密钥。 你需要回到较新版本的 %(brand)s 才能执行此操作", + "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this": "为避免丢失聊天记录,你必须在登出前导出聊天室密钥。你需要切换至新版 %(brand)s 方可继续执行此操作", "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "验证此用户并将其标记为已信任。在收发端到端加密消息时,信任用户可让你更加放心。", "Waiting for partner to confirm...": "等待对方确认中...", "Incoming Verification Request": "收到验证请求", @@ -1205,7 +1205,7 @@ "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "在纯文本消息开头添加 ¯\\_(ツ)_/¯", "User %(userId)s is already in the room": "用户 %(userId)s 已在聊天室中", "The user must be unbanned before they can be invited.": "用户必须先解封才能被邀请。", - "Upgrade to your own domain": "升级 到你自己的域名", + "Upgrade to your own domain": "切换至自有域名", "Accept all %(invitedRooms)s invites": "接受所有 %(invitedRooms)s 邀请", "Change room avatar": "更改聊天室头像", "Change room name": "更改聊天室名称", @@ -1253,7 +1253,7 @@ "You have %(count)s unread notifications in a prior version of this room.|one": "你在此聊天室的先前版本中有 %(count)s 条未读通知。", "Add Email Address": "添加 Email 地址", "Add Phone Number": "添加电话号码", - "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "是否使用“面包屑”功能(最近访问的房间的图标在房间列表上方显示)", + "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "是否使用「面包屑」功能(显示在聊天室列表的头像)", "Call failed due to misconfigured server": "因为服务器配置错误通话失败", "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.": "请联系你主服务器(%(homeserverDomain)s)的管理员设置 TURN 服务器来确保通话运作稳定。", "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "你也可以尝试使用turn.matrix.org公共服务器,但通话质量稍差,并且其将会得知你的 IP。你可以在设置中更改此选项。", @@ -1368,8 +1368,8 @@ "Ensure you have a stable internet connection, or get in touch with the server admin": "确保你的网络连接稳定,或与服务器管理员联系", "Ask your %(brand)s admin to check your config for incorrect or duplicate entries.": "跟你的%(brand)s管理员确认你的配置不正确或重复的条目。", "Cannot reach identity server": "不可连接到身份服务器", - "Room name or address": "房间名称或地址", - "Joins room with given address": "使用给定地址加入房间", + "Room name or address": "聊天室名称或地址", + "Joins room with given address": "使用指定地址加入聊天室", "Verify this login": "验证此登录名", "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "通过从其他会话之一验证此登录名并授予其访问加密信息的权限来确认您的身份。", "Which officially provided instance you are using, if any": "如果有的话,你正在使用官方所提供的哪个实例", @@ -1451,12 +1451,12 @@ "Use a system font": "使用系统字体", "System font name": "系统字体名称", "Never send encrypted messages to unverified sessions from this session": "永不从本会话向未验证的会话发送加密信息", - "Never send encrypted messages to unverified sessions in this room from this session": "永不从本会话在本房间中向未验证的会话发送加密信息", - "Order rooms by name": "按名称排列房间", - "Show rooms with unread notifications first": "优先显示有未读通知的房间", + "Never send encrypted messages to unverified sessions in this room from this session": "永不从此会话向此聊天室中未验证的会话发送加密信息", + "Order rooms by name": "按名称排列聊天室顺序", + "Show rooms with unread notifications first": "优先显示有未读通知的聊天室", "Show previews/thumbnails for images": "显示图片的预览图", - "Enable message search in encrypted rooms": "在加密房间中启用消息搜索", - "Enable experimental, compact IRC style layout": "启用实验性的、紧凑的IRC式布局", + "Enable message search in encrypted rooms": "在加密聊天室中启用消息搜索", + "Enable experimental, compact IRC style layout": "启用实验性、紧凑的 IRC 式布局", "Verify this session by completing one of the following:": "完成以下之一以验证这一会话:", "or": "或者", "Start": "开始", @@ -1587,7 +1587,7 @@ "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them": "你的密码已成功更改。在你重新登录别的会话之前,你将不会在那里收到推送通知", "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "同意身份服务器(%(serverName)s)的服务协议以允许自己被通过邮件地址或电话号码发现。", "Discovery": "发现", - "Clear cache and reload": "清除缓存重新加载", + "Clear cache and reload": "清理缓存并重载", "Keyboard Shortcuts": "键盘快捷键", "Customise your experience with experimental labs features. Learn more.": "通过实验功能自定义您的体验。了解更多。", "Ignored/Blocked": "已忽略/已屏蔽", @@ -1643,7 +1643,7 @@ "To link to this room, please add an address.": "要链接至此聊天室,请添加一个地址。", "Unable to share email address": "无法共享邮件地址", "Your email address hasn't been verified yet": "你的邮件地址尚未被验证", - "Click the link in the email you received to verify and then click continue again.": "请点击你收到的邮件中的链接后再点击继续。", + "Click the link in the email you received to verify and then click continue again.": "点击你所收到的电子邮件中的链接进行验证,然后再次点击继续。", "Verify the link in your inbox": "验证你的收件箱中的链接", "Complete": "完成", "Share": "共享", @@ -1758,7 +1758,7 @@ "%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s 不能被预览。你想加入吗?", "This room doesn't exist. Are you sure you're at the right place?": "此聊天室不存在。你确定你在正确的地方吗?", "Try again later, or ask a room admin to check if you have access.": "请稍后重试,或询问聊天室管理员以检查你是否有权限。", - "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "尝试访问该房间是返回了 %(errcode)s。如果你认为你看到此消息是个错误,请提交一个错误报告。", + "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "尝试访问此聊天室时返回错误 %(errcode)s。如果你认为这是个错误,请提交错误报告。", "Appearance": "外观", "Show rooms with unread messages first": "优先显示有未读消息的聊天室", "Show previews of messages": "显示消息预览", @@ -1772,7 +1772,7 @@ "Show %(count)s more|other": "多显示 %(count)s 个", "Show %(count)s more|one": "多显示 %(count)s 个", "Use default": "使用默认", - "Mentions & Keywords": "提及和关键词", + "Mentions & Keywords": "提及&关键词", "Notification options": "通知选项", "Leave Room": "离开聊天室", "Forget Room": "忘记聊天室", @@ -1962,7 +1962,7 @@ "Reminder: Your browser is unsupported, so your experience may be unpredictable.": "提醒:你的浏览器不被支持,所以你的体验可能不可预料。", "GitHub issue": "GitHub 上的 issue", "Notes": "提示", - "If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.": "如果有额外的上下文可以帮助我们分析问题,比如你当时在做什么、房间 ID、用户 ID 等等,请将其列于此处。", + "If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.": "如果有额外的上下文可以帮助我们分析问题,比如你当时在做什么、聊天室 ID、用户 ID 等等,请将其列于此处。", "Removing…": "正在移除…", "Destroy cross-signing keys?": "销毁交叉签名密钥?", "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.": "删除交叉签名密钥是永久的。所有你验证过的人都会看到安全警报。除非你丢失了所有可以交叉签名的设备,否则几乎可以确定你不想这么做。", @@ -2311,7 +2311,7 @@ "Space": "空格", "How fast should messages be downloaded.": "消息下载速度。", "IRC display name width": "IRC 显示名称宽度", - "When rooms are upgraded": "聊天室升级时间", + "When rooms are upgraded": "当聊天室升级时", "Unexpected server error trying to leave the room": "试图离开聊天室时发生意外服务器错误", "Error leaving room": "离开聊天室时出错", "New spinner design": "新的下拉列表设计", @@ -2824,7 +2824,7 @@ "Value in this room": "此聊天室中的值", "Settings Explorer": "设置浏览器", "with state key %(stateKey)s": "附带有状态键(state key)%(stateKey)s", - "Your server requires encryption to be enabled in private rooms.": "你的服务器要求私人房间启用加密。", + "Your server requires encryption to be enabled in private rooms.": "你的服务器要求私有聊天室得启用加密。", "An image will help people identify your community.": "图片可以让人们辨识你的社群。", "What's the name of your community or team?": "你的社群或者团队的名称是什么?", "You can change this later if needed.": "如果需要,你可以稍后更改。", @@ -3115,8 +3115,8 @@ "What do you want to organise?": "你想要组织什么?", "Skip for now": "暂时跳过", "Failed to create initial space rooms": "创建初始空间聊天室失败", - "To join %(spaceName)s, turn on the Spaces beta": "加入 %(spaceName)s 前,请开启空间测试版", - "To view %(spaceName)s, turn on the Spaces beta": "查看 %(spaceName)s 前,请开启空间测试版", + "To join %(spaceName)s, turn on the Spaces beta": "加入 %(spaceName)s 前,需加入空间测试", + "To view %(spaceName)s, turn on the Spaces beta": "查看 %(spaceName)s 前,需加入空间测试", "Private space": "私有空间", "Public space": "公开空间", "Spaces are a beta feature.": "空间为测试版功能。", @@ -3126,9 +3126,9 @@ "Search names and descriptions": "搜索名称和描述", "You may want to try a different search or check for typos.": "你可能要尝试其他搜索或检查是否有错别字。", "You may contact me if you have any follow up questions": "如果你有任何后续问题,可以联系我", - "To leave the beta, visit your settings.": "要退出测试版,请访问你的设置。", + "To leave the beta, visit your settings.": "要退出测试,请访问你的设置。", "Your platform and username will be noted to help us use your feedback as much as we can.": "我们将会记录你的平台及用户名,以帮助我们尽我们所能地使用你的反馈。", - "%(featureName)s beta feedback": "%(featureName)s 测试版反馈", + "%(featureName)s beta feedback": "%(featureName)s 测试反馈", "Thank you for your feedback, we really appreciate it.": "感谢你的反馈,我们衷心地感谢。", "Beta feedback": "测试版反馈", "Want to add a new room instead?": "想要添加一个新的聊天室吗?", @@ -3231,9 +3231,9 @@ "Open the link in the email to continue registration.": "打开电子邮件中的链接以继续注册。", "A confirmation email has been sent to %(emailAddress)s": "确认电子邮件以发送至 %(emailAddress)s", "Avatar": "头像", - "Join the beta": "加入测试版", - "Leave the beta": "退出测试版", - "Beta": "测试版", + "Join the beta": "加入测试", + "Leave the beta": "退出测试", + "Beta": "测试", "Tap for more info": "点击以获取更多信息", "Spaces is a beta feature": "空间为测试功能", "Start audio stream": "开始音频流", @@ -3273,7 +3273,7 @@ "See when anyone posts a sticker to your active room": "查看何时有人发送贴纸到你所活跃的聊天室", "Send stickers to your active room as you": "发送贴纸到你所活跃的聊天室", "See when people join, leave, or are invited to your active room": "查看人们何时加入、离开或被邀请到你所活跃的聊天室", - "See when people join, leave, or are invited to this room": "查看人们何时加入、离开或被邀请到这个房间", + "See when people join, leave, or are invited to this room": "查看人们加入、离开或被邀请到此聊天室的时间", "Kick, ban, or invite people to this room, and make you leave": "移除、封禁或邀请用户到此聊天室,并让你离开", "Currently joining %(count)s rooms|one": "目前正在加入 %(count)s 个聊天室", "Currently joining %(count)s rooms|other": "目前正在加入 %(count)s 个聊天室", @@ -3413,7 +3413,7 @@ "Open Space": "打开空间", "Olm version:": "Olm 版本:", "There was an error loading your notification settings.": "加载你的通知设置时出错。", - "Mentions & keywords": "提及和关键词", + "Mentions & keywords": "提及&关键词", "Global": "全局", "New keyword": "新的关键词", "Keyword": "关键词", @@ -3421,7 +3421,7 @@ "Enable for this account": "为此帐号启用", "An error occurred whilst saving your notification preferences.": "保存你的通知首选项时出错。", "Error saving notification preferences": "保存通知设置时出错", - "Messages containing keywords": "包含关键字的消息", + "Messages containing keywords": "当消息包含关键词时", "Message bubbles": "消息气泡", "IRC": "IRC", "Show all rooms": "显示所有聊天室", @@ -3461,7 +3461,7 @@ "Unable to transfer call": "无法转移通话", "Anyone can find and join.": "任何人都可以找到并加入。", "We're working on this, but just want to let you know.": "我们正在为此努力,但只是想让你知道。", - "Search for rooms or spaces": "搜索房间或聊天室", + "Search for rooms or spaces": "搜索聊天室或空间", "Created from ": "从 创建", "Unable to copy a link to the room to the clipboard.": "无法将聊天室的链接复制到剪贴板。", "Unable to copy room link": "无法复制聊天室链接", @@ -3521,7 +3521,7 @@ "Anyone will be able to find and join this room, not just members of .": "任何人都可以找到并加入这个聊天室,而不仅仅是 的成员。", "Everyone in will be able to find and join this room.": " 中的每个人都可以找到并加入这个聊天室。", "You can change this at any time from room settings.": "你可以随时从聊天室设置中更改此设置。", - "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "调试日志包含应用程序使用数据,包括你的用户名、你访问过的房间或群组的 ID 或别名、你上次与之交互的 UI 元素,以及其他用户的用户名。它们不包含消息。", + "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "调试日志包含应用程序使用数据,包括你的用户名、你访问过的聊天室或群组的 ID 或别名、你上次与之交互的 UI 元素,以及其他用户的用户名。它们不包含消息。", "Adding spaces has moved.": "新增空间已移动。", "Search for rooms": "搜索聊天室", "Search for spaces": "搜索空间", @@ -3563,10 +3563,36 @@ "Spaces with access": "可访问的空间", "Anyone in a space can find and join. Edit which spaces can access here.": "空间中的任何人都可以找到并加入。在此处编辑哪些空间可以访问。", "Currently, %(count)s spaces have access|other": "目前,%(count)s 个空间可以访问", - "& %(count)s more|other": "以及更多 %(count)s", + "& %(count)s more|other": "以及另 %(count)s", "Upgrade required": "需要升级", "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "如果你通过 GitHub 提交了错误,调试日志可以帮助我们追踪问题。 调试日志包含应用程序使用数据、你的用户名、你访问过的聊天室或群组的 ID 或别名、你上次与之交互的 UI 元素,以及其他用户的用户名。 它们不包含消息。", "%(sharerName)s is presenting": "%(sharerName)s 正在展示", "You are presenting": "你正在展示", - "Surround selected text when typing special characters": "输入特殊字符时圈出选定的文本" + "Surround selected text when typing special characters": "输入特殊字符时圈出选定的文本", + "Rooms and spaces": "聊天室与空间", + "Results": "结果", + "Enable encryption in settings.": "在设置中启用加密。", + "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "你的私人信息通常是加密的,但此聊天室不是。这通常是因为使用了不受支持的设备或方法,例如电子邮件邀请。", + "To avoid these issues, create a new public room for the conversation you plan to have.": "为避免这些问题,请为计划中的对话创建一个新的加密聊天室。", + "It's not recommended to make encrypted rooms public. It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "不建议公开加密聊天室。这意味着任何人都可以找到并加入聊天室,因此任何人都可以阅读消息。你您将无法享受加密带来的任何好处。 在公共聊天室加密消息会导致接收和发送消息的速度变慢。", + "Are you sure you want to make this encrypted room public?": "你确定要公开此加密聊天室吗?", + "To avoid these issues, create a new encrypted room for the conversation you plan to have.": "为避免这些问题,请为计划中的对话创建一个新的加密聊天室。", + "It's not recommended to add encryption to public rooms.Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "不建议为公开聊天室开启加密。任何人都可以找到并加入公开聊天室,因此任何人都可以阅读其中的消息。 你将无法从中体验加密的任何好处,且以后也无法将其关闭。 在公开聊天室中加密消息会导致接收和发送消息的速度变慢。", + "Are you sure you want to add encryption to this public room?": "你确定要为此公开聊天室开启加密吗?", + "Cross-signing is ready but keys are not backed up.": "交叉签名已就绪,但尚未备份密钥。", + "Low bandwidth mode (requires compatible homeserver)": "低带宽模式(需要主服务器兼容)", + "Multiple integration managers (requires manual setup)": "多个集成管理器(需要手动设置)", + "Show threads": "显示主题帖", + "Thread": "主题帖", + "Threaded messaging": "按主题排列的消息", + "The above, but in as well": "以上,但也包括 ", + "The above, but in any room you are joined or invited to as well": "以上,但也包括您加入或被邀请加入的任何房间中", + "Autoplay videos": "自动播放视频", + "Autoplay GIFs": "自动播放 GIF", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s从此聊天室中取消固定了一条消息。查看所有固定消息。", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s 从此聊天室中取消固定了一条消息。查看所有固定消息。", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s将一条消息固定到此聊天室。查看所有固定信息。", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s 将一条消息固定到此聊天室。查看所有固定消息。", + "Currently, %(count)s spaces have access|one": "目前,一个空间有访问权限", + "& %(count)s more|one": "& 另外 %(count)s" } diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index ccb47a4d309..869a9f6e751 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -3690,5 +3690,20 @@ "To avoid these issues, create a new encrypted room for the conversation you plan to have.": "為了避免這些問題,請為您計畫中的對話建立新的加密聊天室。", "It's not recommended to add encryption to public rooms.Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "不建議加密公開聊天室。任何人都可以尋找並加入公開聊天室,所以任何人都可以閱讀其中的訊息。您不會得到任何加密的好處,而您也無法在稍後關閉加密功能。在公開聊天室中加密訊息會讓接收與傳送訊息更慢。", "Are you sure you want to add encryption to this public room?": "您確定您要在此公開聊天室新增加密?", - "Cross-signing is ready but keys are not backed up.": "已準備好交叉簽署但金鑰未備份。" + "Cross-signing is ready but keys are not backed up.": "已準備好交叉簽署但金鑰未備份。", + "Low bandwidth mode (requires compatible homeserver)": "低頻寬模式(需要相容的家伺服器)", + "Multiple integration managers (requires manual setup)": "多個整合管理程式(需要手動設定)", + "Thread": "討論串", + "Show threads": "顯示討論串", + "Threaded messaging": "討論串訊息", + "The above, but in as well": "以上,但也在 中", + "The above, but in any room you are joined or invited to as well": "以上,但在任何您已加入或被邀請的聊天室中", + "Autoplay videos": "自動播放影片", + "Autoplay GIFs": "自動播放 GIF", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s 從此聊天室取消釘選了訊息。檢視所有釘選的訊息。", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s 從此聊天室取消釘選了訊息。檢視所有釘選的訊息。", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s 釘選了訊息到此聊天室。檢視所有已釘選的訊息。", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s 釘選了訊息到此聊天室。檢視所有釘選的訊息。", + "Currently, %(count)s spaces have access|one": "目前,1 個空間可存取", + "& %(count)s more|one": "與其他 %(count)s 個" } diff --git a/src/sendTimePerformanceMetrics.ts b/src/sendTimePerformanceMetrics.ts new file mode 100644 index 00000000000..ee5caa05a90 --- /dev/null +++ b/src/sendTimePerformanceMetrics.ts @@ -0,0 +1,46 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixClient } from "matrix-js-sdk/src"; + +/** + * Decorates the given event content object with the "send start time". The + * object will be modified in-place. + * @param {object} content The event content. + */ +export function decorateStartSendingTime(content: object) { + content['io.element.performance_metrics'] = { + sendStartTs: Date.now(), + }; +} + +/** + * Called when an event decorated with `decorateStartSendingTime()` has been sent + * by the server (the client now knows the event ID). + * @param {MatrixClient} client The client to send as. + * @param {string} inRoomId The room ID where the original event was sent. + * @param {string} forEventId The event ID for the decorated event. + */ +export function sendRoundTripMetric(client: MatrixClient, inRoomId: string, forEventId: string) { + // noinspection JSIgnoredPromiseFromCall + client.sendEvent(inRoomId, 'io.element.performance_metric', { + "io.element.performance_metrics": { + forEventId: forEventId, + responseTs: Date.now(), + kind: 'send_time', + }, + }); +} diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index d170f8d3579..6dbefd4b8e6 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -211,6 +211,15 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_FEATURE, default: false, }, + "feature_thread": { + isFeature: true, + // Requires a reload as we change an option flag on the `js-sdk` + // And the entire sync history needs to be parsed again + controller: new ReloadOnChangeController(), + displayName: _td("Threaded messaging"), + supportedLevels: LEVELS_FEATURE, + default: false, + }, "feature_custom_status": { isFeature: true, displayName: _td("Custom user status messages"), @@ -233,7 +242,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "feature_many_integration_managers": { isFeature: true, - displayName: _td("Multiple integration managers"), + displayName: _td("Multiple integration managers (requires manual setup)"), supportedLevels: LEVELS_FEATURE, default: false, }, @@ -276,12 +285,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: false, controller: new PseudonymousAnalyticsController(), }, - "advancedRoomListLogging": { - // TODO: Remove flag before launch: https://github.com/vector-im/element-web/issues/14231 - displayName: _td("Enable advanced debugging for the room list"), - supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, - default: false, - }, "doNotDisturb": { supportedLevels: [SettingLevel.DEVICE], default: false, @@ -390,9 +393,14 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td('Always show message timestamps'), default: false, }, - "autoplayGifsAndVideos": { + "autoplayGifs": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td('Autoplay GIFs'), + default: false, + }, + "autoplayVideo": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, - displayName: _td('Autoplay GIFs and videos'), + displayName: _td('Autoplay videos'), default: false, }, "enableSyntaxHighlightLanguageDetection": { @@ -668,7 +676,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "lowBandwidth": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, - displayName: _td('Low bandwidth mode'), + displayName: _td('Low bandwidth mode (requires compatible homeserver)'), default: false, controller: new ReloadOnChangeController(), }, @@ -751,6 +759,10 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: true, controller: new ReducedMotionController(), }, + "Performance.addSendMessageTimingMetadata": { + supportedLevels: [SettingLevel.CONFIG], + default: false, + }, "Widgets.pinned": { // deprecated supportedLevels: LEVELS_ROOM_OR_ACCOUNT, default: {}, diff --git a/src/settings/handlers/AccountSettingsHandler.ts b/src/settings/handlers/AccountSettingsHandler.ts index 9c937ebd882..5afe50e4e9f 100644 --- a/src/settings/handlers/AccountSettingsHandler.ts +++ b/src/settings/handlers/AccountSettingsHandler.ts @@ -110,6 +110,21 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa return content ? content['enabled'] : null; } + // Special case for autoplaying videos and GIFs + if (["autoplayGifs", "autoplayVideo"].includes(settingName)) { + const settings = this.getSettings() || {}; + const value = settings[settingName]; + // Fallback to old combined setting + if (value === null || value === undefined) { + const oldCombinedValue = settings["autoplayGifsAndVideos"]; + // Write, so that we can remove this in the future + this.setValue("autoplayGifs", roomId, oldCombinedValue); + this.setValue("autoplayVideo", roomId, oldCombinedValue); + return oldCombinedValue; + } + return value; + } + const settings = this.getSettings() || {}; let preferredValue = settings[settingName]; diff --git a/src/settings/handlers/DeviceSettingsHandler.ts b/src/settings/handlers/DeviceSettingsHandler.ts index e4ad4cb51ee..e57862a8243 100644 --- a/src/settings/handlers/DeviceSettingsHandler.ts +++ b/src/settings/handlers/DeviceSettingsHandler.ts @@ -71,7 +71,13 @@ export default class DeviceSettingsHandler extends SettingsHandler { // Special case for old useIRCLayout setting if (settingName === "layout") { const settings = this.getSettings() || {}; - if (settings["useIRCLayout"]) return Layout.IRC; + if (settings["useIRCLayout"]) { + // Set the new layout setting and delete the old one so that we + // can delete this block of code after some time + settings["layout"] = Layout.IRC; + delete settings["useIRCLayout"]; + localStorage.setItem("mx_local_settings", JSON.stringify(settings)); + } return settings[settingName]; } diff --git a/src/stores/RightPanelStorePhases.ts b/src/stores/RightPanelStorePhases.ts index d62f6c61103..96a585b6761 100644 --- a/src/stores/RightPanelStorePhases.ts +++ b/src/stores/RightPanelStorePhases.ts @@ -37,6 +37,10 @@ export enum RightPanelPhases { SpaceMemberList = "SpaceMemberList", SpaceMemberInfo = "SpaceMemberInfo", Space3pidMemberInfo = "Space3pidMemberInfo", + + // Thread stuff + ThreadView = "ThreadView", + ThreadPanel = "ThreadPanel", } // These are the phases that are safe to persist (the ones that don't require additional diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx index c08c66714ba..ff99b38fe31 100644 --- a/src/stores/SpaceStore.tsx +++ b/src/stores/SpaceStore.tsx @@ -145,9 +145,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return this._allRoomsInHome; } - public async setActiveRoomInSpace(space: Room | null): Promise { + public setActiveRoomInSpace(space: Room | null): void { if (space && !space.isSpaceRoom()) return; - if (space !== this.activeSpace) await this.setActiveSpace(space); + if (space !== this.activeSpace) this.setActiveSpace(space); if (space) { const roomId = this.getNotificationState(space.roomId).getFirstRoomWithNotifications(); @@ -190,7 +190,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { * @param contextSwitch whether to switch the user's context, * should not be done when the space switch is done implicitly due to another event like switching room. */ - public async setActiveSpace(space: Room | null, contextSwitch = true) { + public setActiveSpace(space: Room | null, contextSwitch = true) { if (space === this.activeSpace || (space && !space.isSpaceRoom())) return; this._activeSpace = space; @@ -293,11 +293,15 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } if (space) { - const suggestedRooms = await this.fetchSuggestedRooms(space); - if (this._activeSpace === space) { - this._suggestedRooms = suggestedRooms; - this.emit(SUGGESTED_ROOMS, this._suggestedRooms); - } + this.loadSuggestedRooms(space); + } + } + + private async loadSuggestedRooms(space) { + const suggestedRooms = await this.fetchSuggestedRooms(space); + if (this._activeSpace === space) { + this._suggestedRooms = suggestedRooms; + this.emit(SUGGESTED_ROOMS, this._suggestedRooms); } } @@ -666,6 +670,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.onSpaceUpdate(); this.emit(room.roomId); } + + if (room === this.activeSpace && // current space + this.matrixClient.getRoom(ev.getStateKey())?.getMyMembership() !== "join" && // target not joined + ev.getPrevContent().suggested !== ev.getContent().suggested // suggested flag changed + ) { + this.loadSuggestedRooms(room); + } + break; case EventType.SpaceParent: @@ -678,12 +690,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } this.emit(room.roomId); break; + } + }; - case EventType.RoomMember: - if (room.isSpaceRoom()) { - this.onSpaceMembersChange(ev); - } - break; + // listening for m.room.member events in onRoomState above doesn't work as the Member object isn't updated by then + private onRoomStateMembers = (ev: MatrixEvent) => { + const room = this.matrixClient.getRoom(ev.getRoomId()); + if (room?.isSpaceRoom()) { + this.onSpaceMembersChange(ev); } }; @@ -743,6 +757,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.matrixClient.removeListener("Room.myMembership", this.onRoom); this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData); this.matrixClient.removeListener("RoomState.events", this.onRoomState); + this.matrixClient.removeListener("RoomState.members", this.onRoomStateMembers); this.matrixClient.removeListener("accountData", this.onAccountData); } await this.reset(); @@ -754,6 +769,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.matrixClient.on("Room.myMembership", this.onRoom); this.matrixClient.on("Room.accountData", this.onRoomAccountData); this.matrixClient.on("RoomState.events", this.onRoomState); + this.matrixClient.on("RoomState.members", this.onRoomStateMembers); this.matrixClient.on("accountData", this.onAccountData); this.matrixClient.getCapabilities().then(capabilities => { @@ -766,7 +782,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // restore selected state from last session if any and still valid const lastSpaceId = window.localStorage.getItem(ACTIVE_SPACE_LS_KEY); if (lastSpaceId) { - this.setActiveSpace(this.matrixClient.getRoom(lastSpaceId)); + // don't context switch here as it may break permalinks + this.setActiveSpace(this.matrixClient.getRoom(lastSpaceId), false); } } diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts index 1a5ef0484e7..df36ac124cc 100644 --- a/src/stores/room-list/RoomListStore.ts +++ b/src/stores/room-list/RoomListStore.ts @@ -71,7 +71,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { private readonly watchedSettings = [ 'feature_custom_tags', - 'advancedRoomListLogging', // TODO: Remove watch: https://github.com/vector-im/element-web/issues/14602 ]; constructor() { @@ -122,8 +121,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { this.readyStore.useUnitTestClient(forcedClient); } - this.checkLoggingEnabled(); - for (const settingName of this.watchedSettings) SettingsStore.monitorSetting(settingName, null); RoomViewStore.addListener(() => this.handleRVSUpdate({})); this.algorithm.on(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); @@ -141,12 +138,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { this.updateFn.trigger(); } - private checkLoggingEnabled() { - if (SettingsStore.getValue("advancedRoomListLogging")) { - console.warn("Advanced room list logging is enabled"); - } - } - private async readAndCacheSettingsFromStore() { const tagsEnabled = SettingsStore.getValue("feature_custom_tags"); await this.updateState({ @@ -172,10 +163,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { console.warn(`${activeRoomId} is current in RVS but missing from client - clearing sticky room`); this.algorithm.setStickyRoom(null); } else if (activeRoom !== this.algorithm.stickyRoom) { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`Changing sticky room to ${activeRoomId}`); - } this.algorithm.setStickyRoom(activeRoom); } } @@ -218,14 +205,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { if (payload.action === Action.SettingUpdated) { const settingUpdatedPayload = payload as SettingUpdatedPayload; if (this.watchedSettings.includes(settingUpdatedPayload.settingName)) { - // TODO: Remove with https://github.com/vector-im/element-web/issues/14602 - if (settingUpdatedPayload.settingName === "advancedRoomListLogging") { - // Log when the setting changes so we know when it was turned on in the rageshake - const enabled = SettingsStore.getValue("advancedRoomListLogging"); - console.warn("Advanced room list logging is enabled? " + enabled); - return; - } - console.log("Regenerating room lists: Settings changed"); await this.readAndCacheSettingsFromStore(); @@ -248,20 +227,12 @@ export class RoomListStoreClass extends AsyncStoreWithClient { console.warn(`Own read receipt was in unknown room ${room.roomId}`); return; } - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Got own read receipt in ${room.roomId}`); - } await this.handleRoomUpdate(room, RoomUpdateCause.ReadReceipt); this.updateFn.trigger(); return; } } else if (payload.action === 'MatrixActions.Room.tags') { const roomPayload = (payload); // TODO: Type out the dispatcher types - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Got tag change in ${roomPayload.room.roomId}`); - } await this.handleRoomUpdate(roomPayload.room, RoomUpdateCause.PossibleTagChange); this.updateFn.trigger(); } else if (payload.action === 'MatrixActions.Room.timeline') { @@ -273,16 +244,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient { const roomId = eventPayload.event.getRoomId(); const room = this.matrixClient.getRoom(roomId); const tryUpdate = async (updatedRoom: Room) => { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Live timeline event ${eventPayload.event.getId()}` + - ` in ${updatedRoom.roomId}`); - } if (eventPayload.event.getType() === 'm.room.tombstone' && eventPayload.event.getStateKey() === '') { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Got tombstone event - trying to remove now-dead room`); - } const newRoom = this.matrixClient.getRoom(eventPayload.event.getContent()['replacement_room']); if (newRoom) { // If we have the new room, then the new room check will have seen the predecessor @@ -315,18 +277,10 @@ export class RoomListStoreClass extends AsyncStoreWithClient { console.warn(`Event ${eventPayload.event.getId()} was decrypted in an unknown room ${roomId}`); return; } - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Decrypted timeline event ${eventPayload.event.getId()} in ${roomId}`); - } await this.handleRoomUpdate(room, RoomUpdateCause.Timeline); this.updateFn.trigger(); } else if (payload.action === 'MatrixActions.accountData' && payload.event_type === 'm.direct') { const eventPayload = (payload); // TODO: Type out the dispatcher types - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Received updated DM map`); - } const dmMap = eventPayload.event.getContent(); for (const userId of Object.keys(dmMap)) { const roomIds = dmMap[userId]; @@ -350,54 +304,29 @@ export class RoomListStoreClass extends AsyncStoreWithClient { const oldMembership = getEffectiveMembership(membershipPayload.oldMembership); const newMembership = getEffectiveMembership(membershipPayload.membership); if (oldMembership !== EffectiveMembership.Join && newMembership === EffectiveMembership.Join) { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Handling new room ${membershipPayload.room.roomId}`); - } - // If we're joining an upgraded room, we'll want to make sure we don't proliferate // the dead room in the list. const createEvent = membershipPayload.room.currentState.getStateEvents("m.room.create", ""); if (createEvent && createEvent.getContent()['predecessor']) { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Room has a predecessor`); - } const prevRoom = this.matrixClient.getRoom(createEvent.getContent()['predecessor']['room_id']); if (prevRoom) { const isSticky = this.algorithm.stickyRoom === prevRoom; if (isSticky) { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Clearing sticky room due to room upgrade`); - } this.algorithm.setStickyRoom(null); } // Note: we hit the algorithm instead of our handleRoomUpdate() function to // avoid redundant updates. - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Removing previous room from room list`); - } this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved); } } - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Adding new room to room list`); - } await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom); this.updateFn.trigger(); return; } if (oldMembership !== EffectiveMembership.Invite && newMembership === EffectiveMembership.Invite) { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Handling invite to ${membershipPayload.room.roomId}`); - } await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom); this.updateFn.trigger(); return; @@ -405,10 +334,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { // If it's not a join, it's transitioning into a different list (possibly historical) if (oldMembership !== newMembership) { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Handling membership change in ${membershipPayload.room.roomId}`); - } await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.PossibleTagChange); this.updateFn.trigger(); return; @@ -438,10 +363,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { const shouldUpdate = this.algorithm.handleRoomUpdate(room, cause); if (shouldUpdate) { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[DEBUG] Room "${room.name}" (${room.roomId}) triggered by ${cause} requires list update`); - } this.updateFn.mark(); } } @@ -450,11 +371,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { if (!this.algorithm) return; if (!this.algorithm.hasTagSortingMap) return; // we're still loading - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log("Calculating new prefiltered room list"); - } - // Inhibit updates because we're about to lie heavily to the algorithm this.algorithm.updatesInhibited = true; @@ -588,10 +504,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { } private onAlgorithmListUpdated = () => { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log("Underlying algorithm has triggered a list update - marking"); - } this.updateFn.mark(); }; @@ -673,10 +585,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { * @param {IFilterCondition} filter The filter condition to add. */ public async addFilter(filter: IFilterCondition): Promise { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log("Adding filter condition:", filter); - } let promise = Promise.resolve(); if (filter.kind === FilterKind.Prefilter) { filter.on(FILTER_CHANGED, this.onPrefilterUpdated); @@ -705,10 +613,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { * @param {IFilterCondition} filter The filter condition to remove. */ public removeFilter(filter: IFilterCondition): void { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log("Removing filter condition:", filter); - } let promise = Promise.resolve(); let idx = this.filterConditions.indexOf(filter); let removed = false; diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index eb6ffe6dcf3..1e2606686d0 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -33,7 +33,6 @@ import { FILTER_CHANGED, IFilterCondition } from "../filters/IFilterCondition"; import { EffectiveMembership, getEffectiveMembership, splitRoomsByMembership } from "../../../utils/membership"; import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm"; import { getListAlgorithmInstance } from "./list-ordering"; -import SettingsStore from "../../../settings/SettingsStore"; import { VisibilityProvider } from "../filters/VisibilityProvider"; import SpaceStore from "../../SpaceStore"; @@ -343,11 +342,6 @@ export class Algorithm extends EventEmitter { } } newMap[tagId] = allowedRoomsInThisTag; - - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[DEBUG] ${newMap[tagId].length}/${rooms.length} rooms filtered into ${tagId}`); - } } const allowedRooms = Object.values(newMap).reduce((rv, v) => { rv.push(...v); return rv; }, []); @@ -360,10 +354,6 @@ export class Algorithm extends EventEmitter { protected recalculateFilteredRoomsForTag(tagId: TagID): void { if (!this.hasFilters) return; // don't bother doing work if there's nothing to do - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`Recalculating filtered rooms for ${tagId}`); - } delete this.filteredRooms[tagId]; const rooms = this.cachedRooms[tagId].map(r => r); // cheap clone this.tryInsertStickyRoomToFilterSet(rooms, tagId); @@ -371,11 +361,6 @@ export class Algorithm extends EventEmitter { if (filteredRooms.length > 0) { this.filteredRooms[tagId] = filteredRooms; } - - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[DEBUG] ${filteredRooms.length}/${rooms.length} rooms filtered into ${tagId}`); - } } protected tryInsertStickyRoomToFilterSet(rooms: Room[], tagId: TagID) { @@ -415,10 +400,6 @@ export class Algorithm extends EventEmitter { } if (!this._cachedStickyRooms || !updatedTag) { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`Generating clone of cached rooms for sticky room handling`); - } const stickiedTagMap: ITagMap = {}; for (const tagId of Object.keys(this.cachedRooms)) { stickiedTagMap[tagId] = this.cachedRooms[tagId].map(r => r); // shallow clone @@ -429,10 +410,6 @@ export class Algorithm extends EventEmitter { if (updatedTag) { // Update the tag indicated by the caller, if possible. This is mostly to ensure // our cache is up to date. - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`Replacing cached sticky rooms for ${updatedTag}`); - } this._cachedStickyRooms[updatedTag] = this.cachedRooms[updatedTag].map(r => r); // shallow clone } @@ -441,12 +418,6 @@ export class Algorithm extends EventEmitter { // we might have updated from the cache is also our sticky room. const sticky = this._stickyRoom; if (!updatedTag || updatedTag === sticky.tag) { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log( - `Inserting sticky room ${sticky.room.roomId} at position ${sticky.position} in ${sticky.tag}`, - ); - } this._cachedStickyRooms[sticky.tag].splice(sticky.position, 0, sticky.room); } @@ -673,10 +644,6 @@ export class Algorithm extends EventEmitter { * should be called after processing. */ public handleRoomUpdate(room: Room, cause: RoomUpdateCause): boolean { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`Handle room update for ${room.roomId} called with cause ${cause}`); - } if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from"); // Note: check the isSticky against the room ID just in case the reference is wrong @@ -733,10 +700,6 @@ export class Algorithm extends EventEmitter { const diff = arrayDiff(oldTags, newTags); if (diff.removed.length > 0 || diff.added.length > 0) { for (const rmTag of diff.removed) { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`Removing ${room.roomId} from ${rmTag}`); - } const algorithm: OrderingAlgorithm = this.algorithms[rmTag]; if (!algorithm) throw new Error(`No algorithm for ${rmTag}`); algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved); @@ -745,10 +708,6 @@ export class Algorithm extends EventEmitter { this.recalculateStickyRoom(rmTag); // update sticky room to make sure it moves if needed } for (const addTag of diff.added) { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`Adding ${room.roomId} to ${addTag}`); - } const algorithm: OrderingAlgorithm = this.algorithms[addTag]; if (!algorithm) throw new Error(`No algorithm for ${addTag}`); algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom); @@ -758,17 +717,9 @@ export class Algorithm extends EventEmitter { // Update the tag map so we don't regen it in a moment this.roomIdsToTags[room.roomId] = newTags; - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`Changing update cause for ${room.roomId} to Timeline to sort rooms`); - } cause = RoomUpdateCause.Timeline; didTagChange = true; } else { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`Received no-op update for ${room.roomId} - changing to Timeline update`); - } cause = RoomUpdateCause.Timeline; } @@ -794,28 +745,15 @@ export class Algorithm extends EventEmitter { // as the sticky room relies on this. if (cause !== RoomUpdateCause.NewRoom && cause !== RoomUpdateCause.RoomRemoved) { if (this.stickyRoom === room) { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.warn(`[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`); - } return false; } } if (!this.roomIdsToTags[room.roomId]) { if (CAUSES_REQUIRING_ROOM.includes(cause)) { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.warn(`Skipping tag update for ${room.roomId} because we don't know about the room`); - } return false; } - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Updating tags for room ${room.roomId} (${room.name})`); - } - // Get the tags for the room and populate the cache const roomTags = this.getTagsForRoom(room).filter(t => !isNullOrUndefined(this.cachedRooms[t])); @@ -824,16 +762,6 @@ export class Algorithm extends EventEmitter { if (!roomTags.length) throw new Error(`Tags cannot be determined for ${room.roomId}`); this.roomIdsToTags[room.roomId] = roomTags; - - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags); - } - } - - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Reached algorithmic handling for ${room.roomId} and cause ${cause}`); } const tags = this.roomIdsToTags[room.roomId]; @@ -856,10 +784,6 @@ export class Algorithm extends EventEmitter { changed = true; } - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Finished handling ${room.roomId} with cause ${cause} (changed=${changed})`); - } return changed; } } diff --git a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts index f47458d1b1b..0da2c69eb8f 100644 --- a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts +++ b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts @@ -20,6 +20,27 @@ import { IAlgorithm } from "./IAlgorithm"; import { MatrixClientPeg } from "../../../../MatrixClientPeg"; import * as Unread from "../../../../Unread"; import { EffectiveMembership, getEffectiveMembership } from "../../../../utils/membership"; +import { EventType } from "matrix-js-sdk/src/@types/event"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + +export function shouldCauseReorder(event: MatrixEvent): boolean { + const type = event.getType(); + const content = event.getContent(); + const prevContent = event.getPrevContent(); + + // Never ignore membership changes + if (type === EventType.RoomMember && prevContent.membership !== content.membership) return true; + + // Ignore status changes + // XXX: This should be an enum + if (type === "im.vector.user_status") return false; + // Ignore display name changes + if (type === EventType.RoomMember && prevContent.displayname !== content.displayname) return false; + // Ignore avatar changes + if (type === EventType.RoomMember && prevContent.avatar_url !== content.avatar_url) return false; + + return true; +} export const sortRooms = (rooms: Room[]): Room[] => { // We cache the timestamp lookup to avoid iterating forever on the timeline @@ -68,7 +89,10 @@ export const sortRooms = (rooms: Room[]): Room[] => { const ev = r.timeline[i]; if (!ev.getTs()) continue; // skip events that don't have timestamps (tests only?) - if (ev.getSender() === myUserId || Unread.eventTriggersUnreadCount(ev)) { + if ( + (ev.getSender() === myUserId && shouldCauseReorder(ev)) || + Unread.eventTriggersUnreadCount(ev) + ) { return ev.getTs(); } } diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index daa1e0e7873..750034c573d 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020, 2021 The Matrix.org Foundation C.I.C. + * Copyright 2020 - 2021 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,6 +55,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { ELEMENT_CLIENT_ID } from "../../identifiers"; import { getUserLanguage } from "../../languageHandler"; import { WidgetVariableCustomisations } from "../../customisations/WidgetVariables"; +import { arrayFastClone } from "../../utils/arrays"; // TODO: Destroy all of this code @@ -146,6 +147,7 @@ export class StopGapWidget extends EventEmitter { private scalarToken: string; private roomId?: string; private kind: WidgetKind; + private readUpToMap: {[roomId: string]: string} = {}; // room ID to event ID constructor(private appTileProps: IAppTileProps) { super(); @@ -294,6 +296,17 @@ export class StopGapWidget extends EventEmitter { this.messaging.transport.reply(ev.detail, {}); }); + // Populate the map of "read up to" events for this widget with the current event in every room. + // This is a bit inefficient, but should be okay. We do this for all rooms in case the widget + // requests timeline capabilities in other rooms down the road. It's just easier to manage here. + for (const room of MatrixClientPeg.get().getRooms()) { + // Timelines are most recent last + const events = room.getLiveTimeline()?.getEvents() || []; + const roomEvent = events[events.length - 1]; + if (!roomEvent) continue; // force later code to think the room is fresh + this.readUpToMap[room.roomId] = roomEvent.getId(); + } + // Attach listeners for feeding events - the underlying widget classes handle permissions for us MatrixClientPeg.get().on('event', this.onEvent); MatrixClientPeg.get().on('Event.decrypted', this.onEventDecrypted); @@ -408,21 +421,56 @@ export class StopGapWidget extends EventEmitter { private onEvent = (ev: MatrixEvent) => { MatrixClientPeg.get().decryptEventIfNeeded(ev); if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; - if (ev.getRoomId() !== this.eventListenerRoomId) return; this.feedEvent(ev); }; private onEventDecrypted = (ev: MatrixEvent) => { if (ev.isDecryptionFailure()) return; - if (ev.getRoomId() !== this.eventListenerRoomId) return; this.feedEvent(ev); }; private feedEvent(ev: MatrixEvent) { if (!this.messaging) return; + // Check to see if this event would be before or after our "read up to" marker. If it's + // before, or we can't decide, then we assume the widget will have already seen the event. + // If the event is after, or we don't have a marker for the room, then we'll send it through. + // + // This approach of "read up to" prevents widgets receiving decryption spam from startup or + // receiving out-of-order events from backfill and such. + const upToEventId = this.readUpToMap[ev.getRoomId()]; + if (upToEventId) { + // Small optimization for exact match (prevent search) + if (upToEventId === ev.getId()) { + return; + } + + let isBeforeMark = true; + + // Timelines are most recent last, so reverse the order and limit ourselves to 100 events + // to avoid overusing the CPU. + const timeline = MatrixClientPeg.get().getRoom(ev.getRoomId()).getLiveTimeline(); + const events = arrayFastClone(timeline.getEvents()).reverse().slice(0, 100); + + for (const timelineEvent of events) { + if (timelineEvent.getId() === upToEventId) { + break; + } else if (timelineEvent.getId() === ev.getId()) { + isBeforeMark = false; + break; + } + } + + if (isBeforeMark) { + // Ignore the event: it is before our interest. + return; + } + } + + this.readUpToMap[ev.getRoomId()] = ev.getId(); + const raw = ev.getEffectiveEvent(); - this.messaging.feedEvent(raw).catch(e => { + this.messaging.feedEvent(raw, this.eventListenerRoomId).catch(e => { console.error("Error sending event to widget: ", e); }); } diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index 13cd260ef00..058a605380d 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright 2020 - 2021 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import { MatrixCapabilities, OpenIDRequestState, SimpleObservable, + Symbols, Widget, WidgetDriver, WidgetEventCapability, @@ -33,9 +34,7 @@ import { MatrixClientPeg } from "../../MatrixClientPeg"; import ActiveRoomObserver from "../../ActiveRoomObserver"; import Modal from "../../Modal"; import WidgetOpenIDPermissionsDialog from "../../components/views/dialogs/WidgetOpenIDPermissionsDialog"; -import WidgetCapabilitiesPromptDialog, { - getRememberedCapabilitiesForWidget, -} from "../../components/views/dialogs/WidgetCapabilitiesPromptDialog"; +import WidgetCapabilitiesPromptDialog from "../../components/views/dialogs/WidgetCapabilitiesPromptDialog"; import { WidgetPermissionCustomisations } from "../../customisations/WidgetPermissions"; import { OIDCState, WidgetPermissionStore } from "./WidgetPermissionStore"; import { WidgetType } from "../../widgets/WidgetType"; @@ -44,10 +43,19 @@ import { CHAT_EFFECTS } from "../../effects"; import { containsEmoji } from "../../effects/utils"; import dis from "../../dispatcher/dispatcher"; import { tryTransformPermalinkToLocalHref } from "../../utils/permalinks/Permalinks"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { IEvent, MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { Room } from "matrix-js-sdk/src/models/room"; // TODO: Purge this from the universe +function getRememberedCapabilitiesForWidget(widget: Widget): Capability[] { + return JSON.parse(localStorage.getItem(`widget_${widget.id}_approved_caps`) || "[]"); +} + +function setRememberedCapabilitiesForWidget(widget: Widget, caps: Capability[]) { + localStorage.setItem(`widget_${widget.id}_approved_caps`, JSON.stringify(caps)); +} + export class StopGapWidgetDriver extends WidgetDriver { private allowedCapabilities: Set; @@ -100,6 +108,7 @@ export class StopGapWidgetDriver extends WidgetDriver { } } // TODO: Do something when the widget requests new capabilities not yet asked for + let rememberApproved = false; if (missing.size > 0) { try { const [result] = await Modal.createTrackedDialog( @@ -111,17 +120,29 @@ export class StopGapWidgetDriver extends WidgetDriver { widgetKind: this.forWidgetKind, }).finished; (result.approved || []).forEach(cap => allowedSoFar.add(cap)); + rememberApproved = result.remember; } catch (e) { console.error("Non-fatal error getting capabilities: ", e); } } - return new Set(iterableUnion(allowedSoFar, requested)); + const allAllowed = new Set(iterableUnion(allowedSoFar, requested)); + + if (rememberApproved) { + setRememberedCapabilitiesForWidget(this.forWidget, Array.from(allAllowed)); + } + + return allAllowed; } - public async sendEvent(eventType: string, content: any, stateKey: string = null): Promise { + public async sendEvent( + eventType: string, + content: any, + stateKey: string = null, + targetRoomId: string = null, + ): Promise { const client = MatrixClientPeg.get(); - const roomId = ActiveRoomObserver.activeRoomId; + const roomId = targetRoomId || ActiveRoomObserver.activeRoomId; if (!client || !roomId) throw new Error("Not in a room or not attached to a client"); @@ -129,6 +150,9 @@ export class StopGapWidgetDriver extends WidgetDriver { if (stateKey !== null) { // state event r = await client.sendStateEvent(roomId, eventType, content, stateKey); + } else if (eventType === EventType.RoomRedaction) { + // special case: extract the `redacts` property and call redact + r = await client.redactEvent(roomId, content['redacts']); } else { // message event r = await client.sendEvent(roomId, eventType, content); @@ -145,48 +169,68 @@ export class StopGapWidgetDriver extends WidgetDriver { return { roomId, eventId: r.event_id }; } - public async readRoomEvents(eventType: string, msgtype: string | undefined, limit: number): Promise { - limit = limit > 0 ? Math.min(limit, 25) : 25; // arbitrary choice - + private pickRooms(roomIds: (string | Symbols.AnyRoom)[] = null): Room[] { const client = MatrixClientPeg.get(); - const roomId = ActiveRoomObserver.activeRoomId; - const room = client.getRoom(roomId); - if (!client || !roomId || !room) throw new Error("Not in a room or not attached to a client"); - - const results: MatrixEvent[] = []; - const events = room.getLiveTimeline().getEvents(); // timelines are most recent last - for (let i = events.length - 1; i > 0; i--) { - if (results.length >= limit) break; - - const ev = events[i]; - if (ev.getType() !== eventType || ev.isState()) continue; - if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()['msgtype']) continue; - results.push(ev); - } + if (!client) throw new Error("Not attached to a client"); - return results.map(e => e.getEffectiveEvent()); + const targetRooms = roomIds + ? (roomIds.includes(Symbols.AnyRoom) ? client.getVisibleRooms() : roomIds.map(r => client.getRoom(r))) + : [client.getRoom(ActiveRoomObserver.activeRoomId)]; + return targetRooms.filter(r => !!r); } - public async readStateEvents(eventType: string, stateKey: string | undefined, limit: number): Promise { - limit = limit > 0 ? Math.min(limit, 100) : 100; // arbitrary choice + public async readRoomEvents( + eventType: string, + msgtype: string | undefined, + limitPerRoom: number, + roomIds: (string | Symbols.AnyRoom)[] = null, + ): Promise { + limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary - const client = MatrixClientPeg.get(); - const roomId = ActiveRoomObserver.activeRoomId; - const room = client.getRoom(roomId); - if (!client || !roomId || !room) throw new Error("Not in a room or not attached to a client"); - - const results: MatrixEvent[] = []; - const state: Map = room.currentState.events.get(eventType); - if (state) { - if (stateKey === "" || !!stateKey) { - const forKey = state.get(stateKey); - if (forKey) results.push(forKey); - } else { - results.push(...Array.from(state.values())); + const rooms = this.pickRooms(roomIds); + const allResults: IEvent[] = []; + for (const room of rooms) { + const results: MatrixEvent[] = []; + const events = room.getLiveTimeline().getEvents(); // timelines are most recent last + for (let i = events.length - 1; i > 0; i--) { + if (results.length >= limitPerRoom) break; + + const ev = events[i]; + if (ev.getType() !== eventType || ev.isState()) continue; + if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()['msgtype']) continue; + results.push(ev); } + + results.forEach(e => allResults.push(e.getEffectiveEvent())); } + return allResults; + } + + public async readStateEvents( + eventType: string, + stateKey: string | undefined, + limitPerRoom: number, + roomIds: (string | Symbols.AnyRoom)[] = null, + ): Promise { + limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary - return results.slice(0, limit).map(e => e.event); + const rooms = this.pickRooms(roomIds); + const allResults: IEvent[] = []; + for (const room of rooms) { + const results: MatrixEvent[] = []; + const state: Map = room.currentState.events.get(eventType); + if (state) { + if (stateKey === "" || !!stateKey) { + const forKey = state.get(stateKey); + if (forKey) results.push(forKey); + } else { + results.push(...Array.from(state.values())); + } + } + + results.slice(0, limitPerRoom).forEach(e => allResults.push(e.getEffectiveEvent())); + } + return allResults; } public async askOpenID(observer: SimpleObservable) { diff --git a/src/utils/membership.ts b/src/utils/membership.ts index f980d1dcd69..1a120e08b3d 100644 --- a/src/utils/membership.ts +++ b/src/utils/membership.ts @@ -15,6 +15,8 @@ limitations under the License. */ import { Room } from "matrix-js-sdk/src/models/room"; +import { sleep } from "matrix-js-sdk/src/utils"; + import { MatrixClientPeg } from "../MatrixClientPeg"; import { _t } from "../languageHandler"; import Modal from "../Modal"; @@ -83,9 +85,10 @@ export function isJoinedOrNearlyJoined(membership: string): boolean { return effective === EffectiveMembership.Join || effective === EffectiveMembership.Invite; } -export async function leaveRoomBehaviour(roomId: string) { +export async function leaveRoomBehaviour(roomId: string, retry = true) { + const cli = MatrixClientPeg.get(); let leavingAllVersions = true; - const history = await MatrixClientPeg.get().getRoomUpgradeHistory(roomId); + const history = cli.getRoomUpgradeHistory(roomId); if (history && history.length > 0) { const currentRoom = history[history.length - 1]; if (currentRoom.roomId !== roomId) { @@ -95,20 +98,28 @@ export async function leaveRoomBehaviour(roomId: string) { } } - let results: { [roomId: string]: Error & { errcode: string, message: string } } = {}; + let results: { [roomId: string]: Error & { errcode?: string, message: string, data?: Record } } = {}; if (!leavingAllVersions) { try { - await MatrixClientPeg.get().leave(roomId); + await cli.leave(roomId); } catch (e) { - if (e && e.data && e.data.errcode) { + if (e?.data?.errcode) { const message = e.data.error || _t("Unexpected server error trying to leave the room"); - results[roomId] = Object.assign(new Error(message), { errcode: e.data.errcode }); + results[roomId] = Object.assign(new Error(message), { errcode: e.data.errcode, data: e.data }); } else { results[roomId] = e || new Error("Failed to leave room for unknown causes"); } } } else { - results = await MatrixClientPeg.get().leaveRoomChain(roomId); + results = await cli.leaveRoomChain(roomId, retry); + } + + if (retry) { + const limitExceededError = Object.values(results).find(e => e?.errcode === "M_LIMIT_EXCEEDED"); + if (limitExceededError) { + await sleep(limitExceededError.data.retry_after_ms ?? 100); + return leaveRoomBehaviour(roomId, false); + } } const errors = Object.entries(results).filter(r => !!r[1]); diff --git a/src/utils/space.tsx b/src/utils/space.tsx index c1d8dbfbea2..5bbae369e74 100644 --- a/src/utils/space.tsx +++ b/src/utils/space.tsx @@ -162,7 +162,9 @@ export const leaveSpace = (space: Room) => { if (!leave) return; const modal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner"); try { - await Promise.all(rooms.map(r => leaveRoomBehaviour(r.roomId))); + for (const room of rooms) { + await leaveRoomBehaviour(room.roomId); + } await leaveRoomBehaviour(space.roomId); } finally { modal.close(); diff --git a/src/widgets/CapabilityText.tsx b/src/widgets/CapabilityText.tsx index 63e34eea7ad..8c13a4b2fc5 100644 --- a/src/widgets/CapabilityText.tsx +++ b/src/widgets/CapabilityText.tsx @@ -1,5 +1,5 @@ /* -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2020 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,11 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Capability, EventDirection, MatrixCapabilities, WidgetEventCapability, WidgetKind } from "matrix-widget-api"; +import { + Capability, + EventDirection, + getTimelineRoomIDFromCapability, + isTimelineCapability, + isTimelineCapabilityFor, + MatrixCapabilities, Symbols, + WidgetEventCapability, + WidgetKind, +} from "matrix-widget-api"; import { _t, _td, TranslatedString } from "../languageHandler"; import { EventType, MsgType } from "matrix-js-sdk/src/@types/event"; import { ElementWidgetCapabilities } from "../stores/widgets/ElementWidgetCapabilities"; import React from "react"; +import { MatrixClientPeg } from "../MatrixClientPeg"; +import TextWithTooltip from "../components/views/elements/TextWithTooltip"; type GENERIC_WIDGET_KIND = "generic"; // eslint-disable-line @typescript-eslint/naming-convention const GENERIC_WIDGET_KIND: GENERIC_WIDGET_KIND = "generic"; @@ -138,8 +149,31 @@ export class CapabilityText { if (textForKind[GENERIC_WIDGET_KIND]) return { primary: _t(textForKind[GENERIC_WIDGET_KIND]) }; // ... we'll fall through to the generic capability processing at the end of this - // function if we fail to locate a simple string and the capability isn't for an - // event. + // function if we fail to generate a string for the capability. + } + + // Try to handle timeline capabilities. The text here implies that the caller has sorted + // the timeline caps to the end for UI purposes. + if (isTimelineCapability(capability)) { + if (isTimelineCapabilityFor(capability, Symbols.AnyRoom)) { + return { primary: _t("The above, but in any room you are joined or invited to as well") }; + } else { + const roomId = getTimelineRoomIDFromCapability(capability); + const room = MatrixClientPeg.get().getRoom(roomId); + return { + primary: _t("The above, but in as well", {}, { + Room: () => { + if (room) { + return + { room.name } + ; + } else { + return { roomId }; + } + }, + }), + }; + } } // We didn't have a super simple line of text, so try processing the capability as the diff --git a/test/TextForEvent-test.ts b/test/TextForEvent-test.ts new file mode 100644 index 00000000000..b8a459af67a --- /dev/null +++ b/test/TextForEvent-test.ts @@ -0,0 +1,129 @@ +import './skinned-sdk'; + +import { textForEvent } from "../src/TextForEvent"; +import { MatrixEvent } from "matrix-js-sdk"; +import SettingsStore from "../src/settings/SettingsStore"; +import { SettingLevel } from "../src/settings/SettingLevel"; +import renderer from 'react-test-renderer'; + +function mockPinnedEvent( + pinnedMessageIds?: string[], + prevPinnedMessageIds?: string[], +): MatrixEvent { + return new MatrixEvent({ + type: "m.room.pinned_events", + state_key: "", + sender: "@foo:example.com", + content: { + pinned: pinnedMessageIds, + }, + prev_content: { + pinned: prevPinnedMessageIds, + }, + }); +} + +// Helper function that renders a component to a plain text string. +// Once snapshots are introduced in tests, this function will no longer be necessary, +// and should be replaced with snapshots. +function renderComponent(component): string { + const serializeObject = (object): string => { + if (typeof object === 'string') { + return object === ' ' ? '' : object; + } + + if (Array.isArray(object) && object.length === 1 && typeof object[0] === 'string') { + return object[0]; + } + + if (object['type'] !== undefined && typeof object['children'] !== undefined) { + return serializeObject(object.children); + } + + if (!Array.isArray(object)) { + return ''; + } + + return object.map(child => { + return serializeObject(child); + }).join(''); + }; + + return serializeObject(component.toJSON()); +} + +describe('TextForEvent', () => { + describe("TextForPinnedEvent", () => { + SettingsStore.setValue("feature_pinning", null, SettingLevel.DEVICE, true); + + it("mentions message when a single message was pinned, with no previously pinned messages", () => { + const event = mockPinnedEvent(['message-1']); + const plainText = textForEvent(event); + const component = renderer.create(textForEvent(event, true)); + + const expectedText = "@foo:example.com pinned a message to this room. See all pinned messages."; + expect(plainText).toBe(expectedText); + expect(renderComponent(component)).toBe(expectedText); + }); + + it("mentions message when a single message was pinned, with multiple previously pinned messages", () => { + const event = mockPinnedEvent(['message-1', 'message-2', 'message-3'], ['message-1', 'message-2']); + const plainText = textForEvent(event); + const component = renderer.create(textForEvent(event, true)); + + const expectedText = "@foo:example.com pinned a message to this room. See all pinned messages."; + expect(plainText).toBe(expectedText); + expect(renderComponent(component)).toBe(expectedText); + }); + + it("mentions message when a single message was unpinned, with a single message previously pinned", () => { + const event = mockPinnedEvent([], ['message-1']); + const plainText = textForEvent(event); + const component = renderer.create(textForEvent(event, true)); + + const expectedText = "@foo:example.com unpinned a message from this room. See all pinned messages."; + expect(plainText).toBe(expectedText); + expect(renderComponent(component)).toBe(expectedText); + }); + + it("mentions message when a single message was unpinned, with multiple previously pinned messages", () => { + const event = mockPinnedEvent(['message-2'], ['message-1', 'message-2']); + const plainText = textForEvent(event); + const component = renderer.create(textForEvent(event, true)); + + const expectedText = "@foo:example.com unpinned a message from this room. See all pinned messages."; + expect(plainText).toBe(expectedText); + expect(renderComponent(component)).toBe(expectedText); + }); + + it("shows generic text when multiple messages were pinned", () => { + const event = mockPinnedEvent(['message-1', 'message-2', 'message-3'], ['message-1']); + const plainText = textForEvent(event); + const component = renderer.create(textForEvent(event, true)); + + const expectedText = "@foo:example.com changed the pinned messages for the room."; + expect(plainText).toBe(expectedText); + expect(renderComponent(component)).toBe(expectedText); + }); + + it("shows generic text when multiple messages were unpinned", () => { + const event = mockPinnedEvent(['message-3'], ['message-1', 'message-2', 'message-3']); + const plainText = textForEvent(event); + const component = renderer.create(textForEvent(event, true)); + + const expectedText = "@foo:example.com changed the pinned messages for the room."; + expect(plainText).toBe(expectedText); + expect(renderComponent(component)).toBe(expectedText); + }); + + it("shows generic text when one message was pinned, and another unpinned", () => { + const event = mockPinnedEvent(['message-2'], ['message-1']); + const plainText = textForEvent(event); + const component = renderer.create(textForEvent(event, true)); + + const expectedText = "@foo:example.com changed the pinned messages for the room."; + expect(plainText).toBe(expectedText); + expect(renderComponent(component)).toBe(expectedText); + }); + }); +}); diff --git a/test/components/views/rooms/SendMessageComposer-test.js b/test/components/views/rooms/SendMessageComposer-test.js index 0c4bde76a8a..db5b55df903 100644 --- a/test/components/views/rooms/SendMessageComposer-test.js +++ b/test/components/views/rooms/SendMessageComposer-test.js @@ -46,7 +46,7 @@ describe('', () => { const model = new EditorModel([], createPartCreator(), createRenderer()); model.update("hello world", "insertText", { offset: 11, atNodeEnd: true }); - const content = createMessageContent(model, permalinkCreator); + const content = createMessageContent(model, null, false, permalinkCreator); expect(content).toEqual({ body: "hello world", @@ -58,7 +58,7 @@ describe('', () => { const model = new EditorModel([], createPartCreator(), createRenderer()); model.update("hello *world*", "insertText", { offset: 13, atNodeEnd: true }); - const content = createMessageContent(model, permalinkCreator); + const content = createMessageContent(model, null, false, permalinkCreator); expect(content).toEqual({ body: "hello *world*", @@ -72,7 +72,7 @@ describe('', () => { const model = new EditorModel([], createPartCreator(), createRenderer()); model.update("/me blinks __quickly__", "insertText", { offset: 22, atNodeEnd: true }); - const content = createMessageContent(model, permalinkCreator); + const content = createMessageContent(model, null, false, permalinkCreator); expect(content).toEqual({ body: "blinks __quickly__", @@ -86,7 +86,7 @@ describe('', () => { const model = new EditorModel([], createPartCreator(), createRenderer()); model.update("//dev/null is my favourite place", "insertText", { offset: 32, atNodeEnd: true }); - const content = createMessageContent(model, permalinkCreator); + const content = createMessageContent(model, null, false, permalinkCreator); expect(content).toEqual({ body: "/dev/null is my favourite place", diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index 2e823aa72bb..7cfd97b2345 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -562,7 +562,7 @@ describe("SpaceStore", () => { ]); mkSpace(space3).getMyMembership.mockReturnValue("invite"); await run(); - await store.setActiveSpace(null); + store.setActiveSpace(null); expect(store.activeSpace).toBe(null); }); afterEach(() => { @@ -570,31 +570,31 @@ describe("SpaceStore", () => { }); it("switch to home space", async () => { - await store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(client.getRoom(space1)); fn.mockClear(); - await store.setActiveSpace(null); + store.setActiveSpace(null); expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, null); expect(store.activeSpace).toBe(null); }); it("switch to invited space", async () => { const space = client.getRoom(space3); - await store.setActiveSpace(space); + store.setActiveSpace(space); expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); expect(store.activeSpace).toBe(space); }); it("switch to top level space", async () => { const space = client.getRoom(space1); - await store.setActiveSpace(space); + store.setActiveSpace(space); expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); expect(store.activeSpace).toBe(space); }); it("switch to subspace", async () => { const space = client.getRoom(space2); - await store.setActiveSpace(space); + store.setActiveSpace(space); expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); expect(store.activeSpace).toBe(space); }); @@ -602,7 +602,7 @@ describe("SpaceStore", () => { it("switch to unknown space is a nop", async () => { expect(store.activeSpace).toBe(null); const space = client.getRoom(room1); // not a space - await store.setActiveSpace(space); + store.setActiveSpace(space); expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); expect(store.activeSpace).toBe(null); }); @@ -635,59 +635,59 @@ describe("SpaceStore", () => { }; it("last viewed room in target space is the current viewed and in both spaces", async () => { - await store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(client.getRoom(space1)); viewRoom(room2); - await store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(client.getRoom(space2)); viewRoom(room2); - await store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(client.getRoom(space1)); expect(getCurrentRoom()).toBe(room2); }); it("last viewed room in target space is in the current space", async () => { - await store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(client.getRoom(space1)); viewRoom(room2); - await store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(client.getRoom(space2)); expect(getCurrentRoom()).toBe(space2); - await store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(client.getRoom(space1)); expect(getCurrentRoom()).toBe(room2); }); it("last viewed room in target space is not in the current space", async () => { - await store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(client.getRoom(space1)); viewRoom(room1); - await store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(client.getRoom(space2)); viewRoom(room2); - await store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(client.getRoom(space1)); expect(getCurrentRoom()).toBe(room1); }); it("last viewed room is target space is not known", async () => { - await store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(client.getRoom(space1)); viewRoom(room1); localStorage.setItem(`mx_space_context_${space2}`, orphan2); - await store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(client.getRoom(space2)); expect(getCurrentRoom()).toBe(space2); }); it("last viewed room is target space is no longer in that space", async () => { - await store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(client.getRoom(space1)); viewRoom(room1); localStorage.setItem(`mx_space_context_${space2}`, room1); - await store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(client.getRoom(space2)); expect(getCurrentRoom()).toBe(space2); // Space home instead of room1 }); it("no last viewed room in target space", async () => { - await store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(client.getRoom(space1)); viewRoom(room1); - await store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(client.getRoom(space2)); expect(getCurrentRoom()).toBe(space2); }); it("no last viewed room in home space", async () => { - await store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(client.getRoom(space1)); viewRoom(room1); - await store.setActiveSpace(null); + store.setActiveSpace(null); expect(getCurrentRoom()).toBeNull(); // Home }); }); @@ -715,28 +715,28 @@ describe("SpaceStore", () => { it("no switch required, room is in current space", async () => { viewRoom(room1); - await store.setActiveSpace(client.getRoom(space1), false); + store.setActiveSpace(client.getRoom(space1), false); viewRoom(room2); expect(store.activeSpace).toBe(client.getRoom(space1)); }); it("switch to canonical parent space for room", async () => { viewRoom(room1); - await store.setActiveSpace(client.getRoom(space2), false); + store.setActiveSpace(client.getRoom(space2), false); viewRoom(room2); expect(store.activeSpace).toBe(client.getRoom(space2)); }); it("switch to first containing space for room", async () => { viewRoom(room2); - await store.setActiveSpace(client.getRoom(space2), false); + store.setActiveSpace(client.getRoom(space2), false); viewRoom(room3); expect(store.activeSpace).toBe(client.getRoom(space1)); }); it("switch to home for orphaned room", async () => { viewRoom(room1); - await store.setActiveSpace(client.getRoom(space1), false); + store.setActiveSpace(client.getRoom(space1), false); viewRoom(orphan1); expect(store.activeSpace).toBeNull(); }); @@ -744,7 +744,7 @@ describe("SpaceStore", () => { it("when switching rooms in the all rooms home space don't switch to related space", async () => { await setShowAllRooms(true); viewRoom(room2); - await store.setActiveSpace(null, false); + store.setActiveSpace(null, false); viewRoom(room1); expect(store.activeSpace).toBeNull(); }); diff --git a/test/stores/room-list/SpaceWatcher-test.ts b/test/stores/room-list/SpaceWatcher-test.ts index 85f79c75b6a..474c279fddb 100644 --- a/test/stores/room-list/SpaceWatcher-test.ts +++ b/test/stores/room-list/SpaceWatcher-test.ts @@ -57,7 +57,7 @@ describe("SpaceWatcher", () => { beforeEach(async () => { filter = null; store.removeAllListeners(); - await store.setActiveSpace(null); + store.setActiveSpace(null); client.getVisibleRooms.mockReturnValue(rooms = []); space1 = mkSpace(space1Id); @@ -95,7 +95,7 @@ describe("SpaceWatcher", () => { await setShowAllRooms(true); new SpaceWatcher(mockRoomListStore); - await SpaceStore.instance.setActiveSpace(space1); + SpaceStore.instance.setActiveSpace(space1); expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter["space"]).toBe(space1); @@ -114,7 +114,7 @@ describe("SpaceWatcher", () => { await setShowAllRooms(false); new SpaceWatcher(mockRoomListStore); - await SpaceStore.instance.setActiveSpace(space1); + SpaceStore.instance.setActiveSpace(space1); expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter["space"]).toBe(space1); @@ -124,22 +124,22 @@ describe("SpaceWatcher", () => { await setShowAllRooms(true); new SpaceWatcher(mockRoomListStore); - await SpaceStore.instance.setActiveSpace(space1); + SpaceStore.instance.setActiveSpace(space1); expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter["space"]).toBe(space1); - await SpaceStore.instance.setActiveSpace(null); + SpaceStore.instance.setActiveSpace(null); expect(filter).toBeNull(); }); it("updates filter correctly for space -> home transition", async () => { await setShowAllRooms(false); - await SpaceStore.instance.setActiveSpace(space1); + SpaceStore.instance.setActiveSpace(space1); new SpaceWatcher(mockRoomListStore); expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter["space"]).toBe(space1); - await SpaceStore.instance.setActiveSpace(null); + SpaceStore.instance.setActiveSpace(null); expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter["space"]).toBe(null); @@ -147,12 +147,12 @@ describe("SpaceWatcher", () => { it("updates filter correctly for space -> space transition", async () => { await setShowAllRooms(false); - await SpaceStore.instance.setActiveSpace(space1); + SpaceStore.instance.setActiveSpace(space1); new SpaceWatcher(mockRoomListStore); expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter["space"]).toBe(space1); - await SpaceStore.instance.setActiveSpace(space2); + SpaceStore.instance.setActiveSpace(space2); expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter["space"]).toBe(space2); @@ -160,7 +160,7 @@ describe("SpaceWatcher", () => { it("doesn't change filter when changing showAllRooms mode to true", async () => { await setShowAllRooms(false); - await SpaceStore.instance.setActiveSpace(space1); + SpaceStore.instance.setActiveSpace(space1); new SpaceWatcher(mockRoomListStore); expect(filter).toBeInstanceOf(SpaceFilterCondition); @@ -173,7 +173,7 @@ describe("SpaceWatcher", () => { it("doesn't change filter when changing showAllRooms mode to false", async () => { await setShowAllRooms(true); - await SpaceStore.instance.setActiveSpace(space1); + SpaceStore.instance.setActiveSpace(space1); new SpaceWatcher(mockRoomListStore); expect(filter).toBeInstanceOf(SpaceFilterCondition); diff --git a/test/utils/DateUtils-test.ts b/test/utils/DateUtils-test.ts new file mode 100644 index 00000000000..ad0530c87e1 --- /dev/null +++ b/test/utils/DateUtils-test.ts @@ -0,0 +1,31 @@ +/* +Copyright 2021 Šimon Brandner + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { formatSeconds } from "../../src/DateUtils"; + +describe("formatSeconds", () => { + it("correctly formats time with hours", () => { + expect(formatSeconds((60 * 60 * 3) + (60 * 31) + (55))).toBe("03:31:55"); + expect(formatSeconds((60 * 60 * 3) + (60 * 0) + (55))).toBe("03:00:55"); + expect(formatSeconds((60 * 60 * 3) + (60 * 31) + (0))).toBe("03:31:00"); + }); + + it("correctly formats time without hours", () => { + expect(formatSeconds((60 * 60 * 0) + (60 * 31) + (55))).toBe("31:55"); + expect(formatSeconds((60 * 60 * 0) + (60 * 0) + (55))).toBe("00:55"); + expect(formatSeconds((60 * 60 * 0) + (60 * 31) + (0))).toBe("31:00"); + }); +}); diff --git a/yarn.lock b/yarn.lock index b157a70403a..4f2a6b72422 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,9 +3,9 @@ "@actions/core@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.4.0.tgz#cf2e6ee317e314b03886adfeb20e448d50d6e524" - integrity sha512-CGx2ilGq5i7zSLgiiGUtBCxhRRxibJYU6Fim0Q1Wg2aQL2LTnF27zbqZOrxfvFQ55eSBW0L8uVStgtKMpa0Qlg== + version "1.5.0" + resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.5.0.tgz#885b864700001a1b9a6fba247833a036e75ad9d3" + integrity sha512-eDOLH1Nq9zh+PJlYLqEMkS/jLQxhksPNmUGNBHfa4G+tQmnIhzpctxmchETtVGyBOvXgOVVpYuE40+eS4cUnwQ== "@actions/github@^5.0.0": version "5.0.0" @@ -47,25 +47,25 @@ dependencies: "@babel/highlight" "^7.14.5" -"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.14.5", "@babel/compat-data@^7.14.7": - version "7.14.7" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.7.tgz#7b047d7a3a89a67d2258dc61f604f098f1bc7e08" - integrity sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw== +"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.14.7", "@babel/compat-data@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.15.0.tgz#2dbaf8b85334796cafbb0f5793a90a2fc010b176" + integrity sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA== "@babel/core@>=7.9.0", "@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.7.5": - version "7.14.8" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.8.tgz#20cdf7c84b5d86d83fac8710a8bc605a7ba3f010" - integrity sha512-/AtaeEhT6ErpDhInbXmjHcUQXH0L0TEgscfcxk1qbOvLuKCa5aZT0SOOtDKFY96/CLROwbLSKyFor6idgNaU4Q== + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.0.tgz#749e57c68778b73ad8082775561f67f5196aafa8" + integrity sha512-tXtmTminrze5HEUPn/a0JtOzzfp0nk+UEXQ/tqIJo3WDGypl/2OFQEMll/zSFU8f/lfmfLXvTaORHF3cfXIQMw== dependencies: "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.14.8" - "@babel/helper-compilation-targets" "^7.14.5" - "@babel/helper-module-transforms" "^7.14.8" + "@babel/generator" "^7.15.0" + "@babel/helper-compilation-targets" "^7.15.0" + "@babel/helper-module-transforms" "^7.15.0" "@babel/helpers" "^7.14.8" - "@babel/parser" "^7.14.8" + "@babel/parser" "^7.15.0" "@babel/template" "^7.14.5" - "@babel/traverse" "^7.14.8" - "@babel/types" "^7.14.8" + "@babel/traverse" "^7.15.0" + "@babel/types" "^7.15.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -74,9 +74,9 @@ source-map "^0.5.0" "@babel/eslint-parser@^7.12.10": - version "7.14.7" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.14.7.tgz#91be59a4f7dd60d02a3ef772d156976465596bda" - integrity sha512-6WPwZqO5priAGIwV6msJcdc9TsEPzYeYdS/Xuoap+/ihkgN6dzHp2bcAAwyWZ5bLzk0vvjDmKvRwkqNaiJ8BiQ== + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.15.0.tgz#b54f06e04d0e93aebcba99f89251e3bf0ee39f21" + integrity sha512-+gSPtjSBxOZz4Uh8Ggqu7HbfpB8cT1LwW0DnVVLZEJvzXauiD0Di3zszcBkRmfGGrLdYeHUwcflG7i3tr9kQlw== dependencies: eslint-scope "^5.1.1" eslint-visitor-keys "^2.1.0" @@ -89,12 +89,12 @@ dependencies: eslint-rule-composer "^0.3.0" -"@babel/generator@^7.14.8": - version "7.14.8" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.8.tgz#bf86fd6af96cf3b74395a8ca409515f89423e070" - integrity sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg== +"@babel/generator@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.0.tgz#a7d0c172e0d814974bad5aa77ace543b97917f15" + integrity sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ== dependencies: - "@babel/types" "^7.14.8" + "@babel/types" "^7.15.0" jsesc "^2.5.1" source-map "^0.5.0" @@ -113,26 +113,26 @@ "@babel/helper-explode-assignable-expression" "^7.14.5" "@babel/types" "^7.14.5" -"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz#7a99c5d0967911e972fe2c3411f7d5b498498ecf" - integrity sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw== +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.14.5", "@babel/helper-compilation-targets@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.0.tgz#973df8cbd025515f3ff25db0c05efc704fa79818" + integrity sha512-h+/9t0ncd4jfZ8wsdAsoIxSa61qhBYlycXiHWqJaQBCXAhDCMbPRSMTGnZIkkmt1u4ag+UQmuqcILwqKzZ4N2A== dependencies: - "@babel/compat-data" "^7.14.5" + "@babel/compat-data" "^7.15.0" "@babel/helper-validator-option" "^7.14.5" browserslist "^4.16.6" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.14.5", "@babel/helper-create-class-features-plugin@^7.14.6": - version "7.14.8" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.8.tgz#a6f8c3de208b1e5629424a9a63567f56501955fc" - integrity sha512-bpYvH8zJBWzeqi1o+co8qOrw+EXzQ/0c74gVmY205AWXy9nifHrOg77y+1zwxX5lXE7Icq4sPlSQ4O2kWBrteQ== +"@babel/helper-create-class-features-plugin@^7.14.5", "@babel/helper-create-class-features-plugin@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.15.0.tgz#c9a137a4d137b2d0e2c649acf536d7ba1a76c0f7" + integrity sha512-MdmDXgvTIi4heDVX/e9EFfeGpugqm9fobBVg/iioE8kueXrOHdRDe36FAY7SnE9xXLVeYCoJR/gdrBEIHRC83Q== dependencies: "@babel/helper-annotate-as-pure" "^7.14.5" "@babel/helper-function-name" "^7.14.5" - "@babel/helper-member-expression-to-functions" "^7.14.7" + "@babel/helper-member-expression-to-functions" "^7.15.0" "@babel/helper-optimise-call-expression" "^7.14.5" - "@babel/helper-replace-supers" "^7.14.5" + "@babel/helper-replace-supers" "^7.15.0" "@babel/helper-split-export-declaration" "^7.14.5" "@babel/helper-create-regexp-features-plugin@^7.14.5": @@ -187,12 +187,12 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-member-expression-to-functions@^7.14.5", "@babel/helper-member-expression-to-functions@^7.14.7": - version "7.14.7" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz#97e56244beb94211fe277bd818e3a329c66f7970" - integrity sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA== +"@babel/helper-member-expression-to-functions@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz#0ddaf5299c8179f27f37327936553e9bba60990b" + integrity sha512-Jq8H8U2kYiafuj2xMTPQwkTBnEEdGKpT35lJEQsRRjnG0LW3neucsaMWLgKcwu3OHKNeYugfw+Z20BXBSEs2Lg== dependencies: - "@babel/types" "^7.14.5" + "@babel/types" "^7.15.0" "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.14.5": version "7.14.5" @@ -201,19 +201,19 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-module-transforms@^7.14.5", "@babel/helper-module-transforms@^7.14.8": - version "7.14.8" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz#d4279f7e3fd5f4d5d342d833af36d4dd87d7dc49" - integrity sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA== +"@babel/helper-module-transforms@^7.14.5", "@babel/helper-module-transforms@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.15.0.tgz#679275581ea056373eddbe360e1419ef23783b08" + integrity sha512-RkGiW5Rer7fpXv9m1B3iHIFDZdItnO2/BLfWVW/9q7+KqQSDY5kUfQEbzdXM1MVhJGcugKV7kRrNVzNxmk7NBg== dependencies: "@babel/helper-module-imports" "^7.14.5" - "@babel/helper-replace-supers" "^7.14.5" + "@babel/helper-replace-supers" "^7.15.0" "@babel/helper-simple-access" "^7.14.8" "@babel/helper-split-export-declaration" "^7.14.5" - "@babel/helper-validator-identifier" "^7.14.8" + "@babel/helper-validator-identifier" "^7.14.9" "@babel/template" "^7.14.5" - "@babel/traverse" "^7.14.8" - "@babel/types" "^7.14.8" + "@babel/traverse" "^7.15.0" + "@babel/types" "^7.15.0" "@babel/helper-optimise-call-expression@^7.14.5": version "7.14.5" @@ -236,17 +236,17 @@ "@babel/helper-wrap-function" "^7.14.5" "@babel/types" "^7.14.5" -"@babel/helper-replace-supers@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz#0ecc0b03c41cd567b4024ea016134c28414abb94" - integrity sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow== +"@babel/helper-replace-supers@^7.14.5", "@babel/helper-replace-supers@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.15.0.tgz#ace07708f5bf746bf2e6ba99572cce79b5d4e7f4" + integrity sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA== dependencies: - "@babel/helper-member-expression-to-functions" "^7.14.5" + "@babel/helper-member-expression-to-functions" "^7.15.0" "@babel/helper-optimise-call-expression" "^7.14.5" - "@babel/traverse" "^7.14.5" - "@babel/types" "^7.14.5" + "@babel/traverse" "^7.15.0" + "@babel/types" "^7.15.0" -"@babel/helper-simple-access@^7.14.5", "@babel/helper-simple-access@^7.14.8": +"@babel/helper-simple-access@^7.14.8": version "7.14.8" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz#82e1fec0644a7e775c74d305f212c39f8fe73924" integrity sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg== @@ -267,10 +267,10 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.8": - version "7.14.8" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.8.tgz#32be33a756f29e278a0d644fa08a2c9e0f88a34c" - integrity sha512-ZGy6/XQjllhYQrNw/3zfWRwZCTVSiBLZ9DHVZxn9n2gip/7ab8mv2TWlKPIBk26RwedCBoWdjLmn+t9na2Gcow== +"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" + integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== "@babel/helper-validator-option@^7.14.5": version "7.14.5" @@ -288,13 +288,13 @@ "@babel/types" "^7.14.5" "@babel/helpers@^7.14.8": - version "7.14.8" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.8.tgz#839f88f463025886cff7f85a35297007e2da1b77" - integrity sha512-ZRDmI56pnV+p1dH6d+UN6GINGz7Krps3+270qqI9UJ4wxYThfAIcI5i7j5vXC4FJ3Wap+S9qcebxeYiqn87DZw== + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.15.3.tgz#c96838b752b95dcd525b4e741ed40bb1dc2a1357" + integrity sha512-HwJiz52XaS96lX+28Tnbu31VeFSQJGOeKHJeaEPQlTl7PnlhFElWPj8tUXtqFIzeN86XxXoBr+WFAyK2PPVz6g== dependencies: "@babel/template" "^7.14.5" - "@babel/traverse" "^7.14.8" - "@babel/types" "^7.14.8" + "@babel/traverse" "^7.15.0" + "@babel/types" "^7.15.0" "@babel/highlight@^7.14.5": version "7.14.5" @@ -305,10 +305,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.13.16", "@babel/parser@^7.14.5", "@babel/parser@^7.14.8": - version "7.14.8" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.8.tgz#66fd41666b2d7b840bd5ace7f7416d5ac60208d4" - integrity sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA== +"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.13.16", "@babel/parser@^7.14.5", "@babel/parser@^7.15.0": + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.3.tgz#3416d9bea748052cfcb63dbcc27368105b1ed862" + integrity sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA== "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.14.5": version "7.14.5" @@ -319,10 +319,10 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.14.5" "@babel/plugin-proposal-optional-chaining" "^7.14.5" -"@babel/plugin-proposal-async-generator-functions@^7.14.7": - version "7.14.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.7.tgz#784a48c3d8ed073f65adcf30b57bcbf6c8119ace" - integrity sha512-RK8Wj7lXLY3bqei69/cc25gwS5puEc3dknoFPFbqfy3XxYQBQFvu4ioWpafMBAB+L9NyptQK4nMOa5Xz16og8Q== +"@babel/plugin-proposal-async-generator-functions@^7.14.9": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.9.tgz#7028dc4fa21dc199bbacf98b39bab1267d0eaf9a" + integrity sha512-d1lnh+ZnKrFKwtTYdw320+sQWCTwgkB9fmUhNXRADA4akR6wLjaruSGnIEUjpt9HCOwTr4ynFTKu19b7rFRpmw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/helper-remap-async-to-generator" "^7.14.5" @@ -628,16 +628,16 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-block-scoping@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.5.tgz#8cc63e61e50f42e078e6f09be775a75f23ef9939" - integrity sha512-LBYm4ZocNgoCqyxMLoOnwpsmQ18HWTQvql64t3GvMUzLQrNoV1BDG0lNftC8QKYERkZgCCT/7J5xWGObGAyHDw== + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.15.3.tgz#94c81a6e2fc230bcce6ef537ac96a1e4d2b3afaf" + integrity sha512-nBAzfZwZb4DkaGtOes1Up1nOAp9TDRRFw4XBzBBSG9QK7KVFmYzgj9o9sbPv7TX5ofL4Auq4wZnxCoPnI/lz2Q== dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-classes@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.5.tgz#0e98e82097b38550b03b483f9b51a78de0acb2cf" - integrity sha512-J4VxKAMykM06K/64z9rwiL6xnBHgB1+FVspqvlgCdwD1KUbQNfszeKVVOMh59w3sztHYIZDgnhOC4WbdEfHFDA== +"@babel/plugin-transform-classes@^7.14.9": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.9.tgz#2a391ffb1e5292710b00f2e2c210e1435e7d449f" + integrity sha512-NfZpTcxU3foGWbl4wxmZ35mTsYJy8oQocbeIMoDAGGFarAmSQlL+LWMkDx/tj6pNotpbX3rltIA4dprgAPOq5A== dependencies: "@babel/helper-annotate-as-pure" "^7.14.5" "@babel/helper-function-name" "^7.14.5" @@ -722,14 +722,14 @@ "@babel/helper-plugin-utils" "^7.14.5" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.5.tgz#7aaee0ea98283de94da98b28f8c35701429dad97" - integrity sha512-en8GfBtgnydoao2PS+87mKyw62k02k7kJ9ltbKe0fXTHrQmG6QZZflYuGI1VVG7sVpx4E1n7KBpNlPb8m78J+A== +"@babel/plugin-transform-modules-commonjs@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.15.0.tgz#3305896e5835f953b5cdb363acd9e8c2219a5281" + integrity sha512-3H/R9s8cXcOGE8kgMlmjYYC9nqr5ELiPkJn4q0mypBrjhYQoc+5/Maq69vV4xRPWnkzZuwJPf5rArxpB/35Cig== dependencies: - "@babel/helper-module-transforms" "^7.14.5" + "@babel/helper-module-transforms" "^7.15.0" "@babel/helper-plugin-utils" "^7.14.5" - "@babel/helper-simple-access" "^7.14.5" + "@babel/helper-simple-access" "^7.14.8" babel-plugin-dynamic-import-node "^2.3.3" "@babel/plugin-transform-modules-systemjs@^7.14.5": @@ -751,10 +751,10 @@ "@babel/helper-module-transforms" "^7.14.5" "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-named-capturing-groups-regex@^7.14.7": - version "7.14.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.7.tgz#60c06892acf9df231e256c24464bfecb0908fd4e" - integrity sha512-DTNOTaS7TkW97xsDMrp7nycUVh6sn/eq22VaxWfEdzuEbRsiaOU0pqU7DlyUGHVsbQbSghvjKRpEl+nUCKGQSg== +"@babel/plugin-transform-named-capturing-groups-regex@^7.14.9": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.9.tgz#c68f5c5d12d2ebaba3762e57c2c4f6347a46e7b2" + integrity sha512-l666wCVYO75mlAtGFfyFwnWmIXQm3kSH0C3IRnJqWcZbWkoihyAdDhFm2ZWaxWTqvBvhVFfJjMRQ0ez4oN1yYA== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.14.5" @@ -788,9 +788,9 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-react-display-name@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.14.5.tgz#baa92d15c4570411301a85a74c13534873885b65" - integrity sha512-07aqY1ChoPgIxsuDviptRpVkWCSbXWmzQqcgy65C6YSFOfPFvb/DX3bBRHh7pCd/PMEEYHYWUTSVkCbkVainYQ== + version "7.15.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.15.1.tgz#6aaac6099f1fcf6589d35ae6be1b6e10c8c602b9" + integrity sha512-yQZ/i/pUCJAHI/LbtZr413S3VT26qNrEm0M5RRxQJA947/YNYwbZbBaXGDrq6CG5QsZycI1VIP6d7pQaBfP+8Q== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -802,15 +802,15 @@ "@babel/plugin-transform-react-jsx" "^7.14.5" "@babel/plugin-transform-react-jsx@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.14.5.tgz#39749f0ee1efd8a1bd729152cf5f78f1d247a44a" - integrity sha512-7RylxNeDnxc1OleDm0F5Q/BSL+whYRbOAR+bwgCxIr0L32v7UFh/pz1DLMZideAUxKT6eMoS2zQH6fyODLEi8Q== + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.14.9.tgz#3314b2163033abac5200a869c4de242cd50a914c" + integrity sha512-30PeETvS+AeD1f58i1OVyoDlVYQhap/K20ZrMjLmmzmC2AYR/G43D4sdJAaDAqCD3MYpSWbmrz3kES158QSLjw== dependencies: "@babel/helper-annotate-as-pure" "^7.14.5" "@babel/helper-module-imports" "^7.14.5" "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-jsx" "^7.14.5" - "@babel/types" "^7.14.5" + "@babel/types" "^7.14.9" "@babel/plugin-transform-react-pure-annotations@^7.14.5": version "7.14.5" @@ -835,9 +835,9 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-runtime@^7.12.10": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.14.5.tgz#30491dad49c6059f8f8fa5ee8896a0089e987523" - integrity sha512-fPMBhh1AV8ZyneiCIA+wYYUH1arzlXR1UMcApjvchDhfKxhy2r2lReJv8uHEyihi4IFIGlr1Pdx7S5fkESDQsg== + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.15.0.tgz#d3aa650d11678ca76ce294071fda53d7804183b3" + integrity sha512-sfHYkLGjhzWTq6xsuQ01oEsUYjkHRux9fW1iUA68dC7Qd8BS1Unq4aZ8itmQp95zUzIcyR2EbNMTzAicFj+guw== dependencies: "@babel/helper-module-imports" "^7.14.5" "@babel/helper-plugin-utils" "^7.14.5" @@ -882,12 +882,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-typescript@^7.14.5": - version "7.14.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.14.6.tgz#6e9c2d98da2507ebe0a883b100cde3c7279df36c" - integrity sha512-XlTdBq7Awr4FYIzqhmYY80WN0V0azF74DMPyFqVHBvf81ZUgc4X7ZOpx6O8eLDK6iM5cCQzeyJw0ynTaefixRA== +"@babel/plugin-transform-typescript@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.15.0.tgz#553f230b9d5385018716586fc48db10dd228eb7e" + integrity sha512-WIIEazmngMEEHDaPTx0IZY48SaAmjVWe3TRSX7cmJXn0bEv9midFzAjxiruOWYIVf5iQ10vFx7ASDpgEO08L5w== dependencies: - "@babel/helper-create-class-features-plugin" "^7.14.6" + "@babel/helper-create-class-features-plugin" "^7.15.0" "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript" "^7.14.5" @@ -907,16 +907,16 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/preset-env@^7.12.11": - version "7.14.8" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.14.8.tgz#254942f5ca80ccabcfbb2a9f524c74bca574005b" - integrity sha512-a9aOppDU93oArQ51H+B8M1vH+tayZbuBqzjOhntGetZVa+4tTu5jp+XTwqHGG2lxslqomPYVSjIxQkFwXzgnxg== + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.15.0.tgz#e2165bf16594c9c05e52517a194bf6187d6fe464" + integrity sha512-FhEpCNFCcWW3iZLg0L2NPE9UerdtsCR6ZcsGHUX6Om6kbCQeL5QZDqFDmeNHC6/fy6UH3jEge7K4qG5uC9In0Q== dependencies: - "@babel/compat-data" "^7.14.7" - "@babel/helper-compilation-targets" "^7.14.5" + "@babel/compat-data" "^7.15.0" + "@babel/helper-compilation-targets" "^7.15.0" "@babel/helper-plugin-utils" "^7.14.5" "@babel/helper-validator-option" "^7.14.5" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.14.5" - "@babel/plugin-proposal-async-generator-functions" "^7.14.7" + "@babel/plugin-proposal-async-generator-functions" "^7.14.9" "@babel/plugin-proposal-class-properties" "^7.14.5" "@babel/plugin-proposal-class-static-block" "^7.14.5" "@babel/plugin-proposal-dynamic-import" "^7.14.5" @@ -949,7 +949,7 @@ "@babel/plugin-transform-async-to-generator" "^7.14.5" "@babel/plugin-transform-block-scoped-functions" "^7.14.5" "@babel/plugin-transform-block-scoping" "^7.14.5" - "@babel/plugin-transform-classes" "^7.14.5" + "@babel/plugin-transform-classes" "^7.14.9" "@babel/plugin-transform-computed-properties" "^7.14.5" "@babel/plugin-transform-destructuring" "^7.14.7" "@babel/plugin-transform-dotall-regex" "^7.14.5" @@ -960,10 +960,10 @@ "@babel/plugin-transform-literals" "^7.14.5" "@babel/plugin-transform-member-expression-literals" "^7.14.5" "@babel/plugin-transform-modules-amd" "^7.14.5" - "@babel/plugin-transform-modules-commonjs" "^7.14.5" + "@babel/plugin-transform-modules-commonjs" "^7.15.0" "@babel/plugin-transform-modules-systemjs" "^7.14.5" "@babel/plugin-transform-modules-umd" "^7.14.5" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.14.7" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.14.9" "@babel/plugin-transform-new-target" "^7.14.5" "@babel/plugin-transform-object-super" "^7.14.5" "@babel/plugin-transform-parameters" "^7.14.5" @@ -978,11 +978,11 @@ "@babel/plugin-transform-unicode-escapes" "^7.14.5" "@babel/plugin-transform-unicode-regex" "^7.14.5" "@babel/preset-modules" "^0.1.4" - "@babel/types" "^7.14.8" + "@babel/types" "^7.15.0" babel-plugin-polyfill-corejs2 "^0.2.2" babel-plugin-polyfill-corejs3 "^0.2.2" babel-plugin-polyfill-regenerator "^0.2.2" - core-js-compat "^3.15.0" + core-js-compat "^3.16.0" semver "^6.3.0" "@babel/preset-modules@^0.1.4": @@ -1009,18 +1009,18 @@ "@babel/plugin-transform-react-pure-annotations" "^7.14.5" "@babel/preset-typescript@^7.12.7": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.14.5.tgz#aa98de119cf9852b79511f19e7f44a2d379bcce0" - integrity sha512-u4zO6CdbRKbS9TypMqrlGH7sd2TAJppZwn3c/ZRLeO/wGsbddxgbPDUZVNrie3JWYLQ9vpineKlsrWFvO6Pwkw== + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.15.0.tgz#e8fca638a1a0f64f14e1119f7fe4500277840945" + integrity sha512-lt0Y/8V3y06Wq/8H/u0WakrqciZ7Fz7mwPDHWUJAXlABL5hiUG42BNlRXiELNjeWjO5rWmnNKlx+yzJvxezHow== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/helper-validator-option" "^7.14.5" - "@babel/plugin-transform-typescript" "^7.14.5" + "@babel/plugin-transform-typescript" "^7.15.0" "@babel/register@^7.12.10": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.14.5.tgz#d0eac615065d9c2f1995842f85d6e56c345f3233" - integrity sha512-TjJpGz/aDjFGWsItRBQMOFTrmTI9tr79CHOK+KIvLeCkbxuOAk2M5QHjvruIMGoo9OuccMh5euplPzc5FjAKGg== + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.15.3.tgz#6b40a549e06ec06c885b2ec42c3dd711f55fe752" + integrity sha512-mj4IY1ZJkorClxKTImccn4T81+UKTo4Ux0+OFSV9hME1ooqS9UV+pJ6BjD0qXPK4T3XW/KNa79XByjeEMZz+fw== dependencies: clone-deep "^4.0.1" find-cache-dir "^2.0.0" @@ -1029,9 +1029,9 @@ source-map-support "^0.5.16" "@babel/runtime@^7.0.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.14.8" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.8.tgz#7119a56f421018852694290b9f9148097391b446" - integrity sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg== + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b" + integrity sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA== dependencies: regenerator-runtime "^0.13.4" @@ -1044,27 +1044,27 @@ "@babel/parser" "^7.14.5" "@babel/types" "^7.14.5" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.12.12", "@babel/traverse@^7.13.0", "@babel/traverse@^7.13.17", "@babel/traverse@^7.14.5", "@babel/traverse@^7.14.8": - version "7.14.8" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.8.tgz#c0253f02677c5de1a8ff9df6b0aacbec7da1a8ce" - integrity sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg== +"@babel/traverse@^7.1.0", "@babel/traverse@^7.12.12", "@babel/traverse@^7.13.0", "@babel/traverse@^7.13.17", "@babel/traverse@^7.14.5", "@babel/traverse@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.0.tgz#4cca838fd1b2a03283c1f38e141f639d60b3fc98" + integrity sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw== dependencies: "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.14.8" + "@babel/generator" "^7.15.0" "@babel/helper-function-name" "^7.14.5" "@babel/helper-hoist-variables" "^7.14.5" "@babel/helper-split-export-declaration" "^7.14.5" - "@babel/parser" "^7.14.8" - "@babel/types" "^7.14.8" + "@babel/parser" "^7.15.0" + "@babel/types" "^7.15.0" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.14.5", "@babel/types@^7.14.8", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.14.8" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.8.tgz#38109de8fcadc06415fbd9b74df0065d4d41c728" - integrity sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q== +"@babel/types@^7.0.0", "@babel/types@^7.14.5", "@babel/types@^7.14.8", "@babel/types@^7.14.9", "@babel/types@^7.15.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.0.tgz#61af11f2286c4e9c69ca8deb5f4375a73c72dcbd" + integrity sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ== dependencies: - "@babel/helper-validator-identifier" "^7.14.8" + "@babel/helper-validator-identifier" "^7.14.9" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": @@ -1384,44 +1384,24 @@ "@octokit/types" "^6.0.3" universal-user-agent "^6.0.0" -"@octokit/openapi-types@^9.3.0": - version "9.3.0" - resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-9.3.0.tgz#160347858d727527901c6aae7f7d5c2414cc1f2e" - integrity sha512-oz60hhL+mDsiOWhEwrj5aWXTOMVtQgcvP+sRzX4C3cH7WOK9QSAoEtjWh0HdOf6V3qpdgAmUMxnQPluzDWR7Fw== - "@octokit/openapi-types@^9.5.0": version "9.7.0" resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-9.7.0.tgz#9897cdefd629cd88af67b8dbe2e5fb19c63426b2" integrity sha512-TUJ16DJU8mekne6+KVcMV5g6g/rJlrnIKn7aALG9QrNpnEipFc1xjoarh0PKaAWf2Hf+HwthRKYt+9mCm5RsRg== -"@octokit/plugin-paginate-rest@^2.13.3": +"@octokit/plugin-paginate-rest@^2.13.3", "@octokit/plugin-paginate-rest@^2.6.2": version "2.15.1" resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.15.1.tgz#264189dd3ce881c6c33758824aac05a4002e056a" integrity sha512-47r52KkhQDkmvUKZqXzA1lKvcyJEfYh3TKAIe5+EzMeyDM3d+/s5v11i2gTk8/n6No6DPi3k5Ind6wtDbo/AEg== dependencies: "@octokit/types" "^6.24.0" -"@octokit/plugin-paginate-rest@^2.6.2": - version "2.15.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.15.0.tgz#9c956c3710b2bd786eb3814eaf5a2b17392c150d" - integrity sha512-/vjcb0w6ggVRtsb8OJBcRR9oEm+fpdo8RJk45khaWw/W0c8rlB2TLCLyZt/knmC17NkX7T9XdyQeEY7OHLSV1g== - dependencies: - "@octokit/types" "^6.23.0" - "@octokit/plugin-request-log@^1.0.2": version "1.0.4" resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== -"@octokit/plugin-rest-endpoint-methods@5.6.0": - version "5.6.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.6.0.tgz#c28833b88d0f07bf94093405d02d43d73c7de99b" - integrity sha512-2G7lIPwjG9XnTlNhe/TRnpI8yS9K2l68W4RP/ki3wqw2+sVeTK8hItPxkqEI30VeH0UwnzpuksMU/yHxiVVctw== - dependencies: - "@octokit/types" "^6.23.0" - deprecation "^2.3.1" - -"@octokit/plugin-rest-endpoint-methods@^5.1.1": +"@octokit/plugin-rest-endpoint-methods@5.8.0", "@octokit/plugin-rest-endpoint-methods@^5.1.1": version "5.8.0" resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.8.0.tgz#33b342fe41f2603fdf8b958e6652103bb3ea3f3b" integrity sha512-qeLZZLotNkoq+it6F+xahydkkbnvSK0iDjlXFo3jNTB+Ss0qIbYQb9V/soKLMkgGw8Q2sHjY5YEXiA47IVPp4A== @@ -1439,9 +1419,9 @@ once "^1.4.0" "@octokit/request@^5.6.0": - version "5.6.0" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.0.tgz#6084861b6e4fa21dc40c8e2a739ec5eff597e672" - integrity sha512-4cPp/N+NqmaGQwbh3vUsYqokQIzt7VjsgTYVXiwpUP2pxd5YiZB2XuTedbb0SPtv9XS7nzAKjAuQxmY8/aZkiA== + version "5.6.1" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.1.tgz#f97aff075c37ab1d427c49082fefeef0dba2d8ce" + integrity sha512-Ls2cfs1OfXaOKzkcxnqw5MR6drMA/zWX/LIS/p8Yjdz7QKTPQLMsB3R+OvoxE6XnXeXEE2X7xe4G4l4X0gRiKQ== dependencies: "@octokit/endpoint" "^6.0.1" "@octokit/request-error" "^2.1.0" @@ -1451,23 +1431,16 @@ universal-user-agent "^6.0.0" "@octokit/rest@^18.6.7": - version "18.8.0" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.8.0.tgz#ba24f7ba554f015a7ae2b7cc2aecef5386ddfea5" - integrity sha512-lsuNRhgzGnLMn/NmQTNCit/6jplFWiTUlPXhqN0zCMLwf2/9pseHzsnTW+Cjlp4bLMEJJNPa5JOzSLbSCOahKw== + version "18.9.1" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.9.1.tgz#db1d7ac1d7b10e908f7d4b78fe35a392554ccb26" + integrity sha512-idZ3e5PqXVWOhtZYUa546IDHTHjkGZbj3tcJsN0uhCy984KD865e8GB2WbYDc2ZxFuJRiyd0AftpL2uPNhF+UA== dependencies: "@octokit/core" "^3.5.0" "@octokit/plugin-paginate-rest" "^2.6.2" "@octokit/plugin-request-log" "^1.0.2" - "@octokit/plugin-rest-endpoint-methods" "5.6.0" + "@octokit/plugin-rest-endpoint-methods" "5.8.0" -"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.23.0": - version "6.23.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.23.0.tgz#b39f242b20036e89fa8f34f7962b4e9b7ff8f65b" - integrity sha512-eG3clC31GSS7K3oBK6C6o7wyXPrkP+mu++eus8CSZdpRytJ5PNszYxudOQ0spWZQ3S9KAtoTG6v1WK5prJcJrA== - dependencies: - "@octokit/openapi-types" "^9.3.0" - -"@octokit/types@^6.24.0", "@octokit/types@^6.25.0": +"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.24.0", "@octokit/types@^6.25.0": version "6.25.0" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.25.0.tgz#c8e37e69dbe7ce55ed98ee63f75054e7e808bf1a" integrity sha512-bNvyQKfngvAd/08COlYIN54nRgxskmejgywodizQNyiKoXmWRAjKup2/LYwm+T9V0gsKH6tuld1gM0PzmOiB4Q== @@ -1475,13 +1448,13 @@ "@octokit/openapi-types" "^9.5.0" "@peculiar/asn1-schema@^2.0.27", "@peculiar/asn1-schema@^2.0.32": - version "2.0.37" - resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.0.37.tgz#700476512ab903d809f64a3040fb1b2fe6fb6d4b" - integrity sha512-f/dozij2XCZZ7ayOWI88TbHt/1rk3zJ91O/xTtDdc8SttyF6pleu4RYBuFohkobA5HJn+bEcY6Cvq4x9feXokQ== + version "2.0.38" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.0.38.tgz#98b6f12daad275ecd6774dfe31fb62f362900412" + integrity sha512-zZ64UpCTm9me15nuCpPgJghSdbEm8atcDQPCyK+bKXjZAQ1735NCZXCSCfbckbQ4MH36Rm9403n/qMq77LFDzQ== dependencies: "@types/asn1js" "^2.0.2" asn1js "^2.1.1" - pvtsutils "^1.1.7" + pvtsutils "^1.2.0" tslib "^2.3.0" "@peculiar/json-schema@^1.1.12": @@ -1552,16 +1525,11 @@ "@sentry/utils" "6.11.0" tslib "^1.9.3" -"@sentry/types@6.11.0": +"@sentry/types@6.11.0", "@sentry/types@^6.10.0": version "6.11.0" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.11.0.tgz#5122685478d32ddacd3a891cbcf550012df85f7c" integrity sha512-gm5H9eZhL6bsIy/h3T+/Fzzz2vINhHhqd92CjHle3w7uXdTdFV98i2pDpErBGNTSNzbntqOMifYEB5ENtZAvcg== -"@sentry/types@^6.10.0": - version "6.10.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.10.0.tgz#6b1f44e5ed4dbc2710bead24d1b32fb08daf04e1" - integrity sha512-M7s0JFgG7/6/yNVYoPUbxzaXDhnzyIQYRRJJKRaTD77YO4MHvi4Ke8alBWqD5fer0cPIfcSkBqa9BLdqRqcMWw== - "@sentry/utils@6.11.0": version "6.11.0" resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.11.0.tgz#d1dee4faf4d9c42c54bba88d5a66fb96b902a14c" @@ -1745,9 +1713,9 @@ pretty-format "^26.0.0" "@types/json-schema@^7.0.7": - version "7.0.8" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818" - integrity sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg== + version "7.0.9" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" + integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== "@types/linkifyjs@^2.1.3": version "2.1.4" @@ -1757,14 +1725,14 @@ "@types/react" "*" "@types/lodash@^4.14.168": - version "4.14.171" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.171.tgz#f01b3a5fe3499e34b622c362a46a609fdb23573b" - integrity sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg== + version "4.14.172" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a" + integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw== "@types/mdast@^3.0.0": - version "3.0.7" - resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.7.tgz#cba63d0cc11eb1605cea5c0ad76e02684394166b" - integrity sha512-YwR7OK8aPmaBvMMUi+pZXBNoW2unbVbfok4YRqGMJBe1dpDlzpRkJrYEYmvjxgs5JhuQmKfDexrN98u941Zasg== + version "3.0.10" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" + integrity sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA== dependencies: "@types/unist" "*" @@ -1779,14 +1747,14 @@ integrity sha512-jhMOZSS0UGYTS9pqvt6q3wtT3uvOSve5piTEmTMx3zzTuBLvSIMxSIBIc3d5lajVD5h4xc41AMZD2M5orN3PxA== "@types/node@*": - version "16.4.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.4.0.tgz#2c219eaa3b8d1e4d04f4dd6e40bc68c7467d5272" - integrity sha512-HrJuE7Mlqcjj+00JqMWpZ3tY8w7EUd+S0U3L1+PQSWiXZbOgyQDvi+ogoUxaHApPJq5diKxYBQwA3iIlNcPqOg== + version "16.7.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.1.tgz#c6b9198178da504dfca1fd0be9b2e1002f1586f0" + integrity sha512-ncRdc45SoYJ2H4eWU9ReDfp3vtFqDYhjOsKlFFUDEn8V1Bgr2RjYal8YT5byfadWIRluhPFU6JiDOl0H6Sl87A== "@types/node@^14.14.22": - version "14.17.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.5.tgz#b59daf6a7ffa461b5648456ca59050ba8e40ed54" - integrity sha512-bjqH2cX/O33jXT/UmReo2pM7DIJREPMnarixbQ57DOOzzFaI6D2+IcwaJQaJpv0M1E9TIhPCYVxrkcityLjlqA== + version "14.17.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.11.tgz#82d266d657aec5ff01ca59f2ffaff1bb43f7bf0f" + integrity sha512-n2OQ+0Bz6WEsUjrvcHD1xZ8K+Kgo4cn9/w94s1bJS690QMUWfJPW/m7CCb7gPkA1fcYwL2UpjXP/rq/Eo41m6w== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -1915,72 +1883,72 @@ integrity sha512-3NoqvZC2W5gAC5DZbTpCeJ251vGQmgcWIHQJGq2J240HY6ErQ9aWKkwfoKJlHLx+A83WPNTZ9+3cd2ILxbvr1w== "@typescript-eslint/eslint-plugin@^4.17.0": - version "4.28.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.4.tgz#e73c8cabbf3f08dee0e1bda65ed4e622ae8f8921" - integrity sha512-s1oY4RmYDlWMlcV0kKPBaADn46JirZzvvH7c2CtAqxCY96S538JRBAzt83RrfkDheV/+G/vWNK0zek+8TB3Gmw== + version "4.29.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.3.tgz#95cb8029a8bd8bd9c7f4ab95074a7cb2115adefa" + integrity sha512-tBgfA3K/3TsZY46ROGvoRxQr1wBkclbVqRQep97MjVHJzcRBURRY3sNFqLk0/Xr//BY5hM9H2p/kp+6qim85SA== dependencies: - "@typescript-eslint/experimental-utils" "4.28.4" - "@typescript-eslint/scope-manager" "4.28.4" + "@typescript-eslint/experimental-utils" "4.29.3" + "@typescript-eslint/scope-manager" "4.29.3" debug "^4.3.1" functional-red-black-tree "^1.0.1" regexpp "^3.1.0" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@4.28.4": - version "4.28.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.4.tgz#9c70c35ebed087a5c70fb0ecd90979547b7fec96" - integrity sha512-OglKWOQRWTCoqMSy6pm/kpinEIgdcXYceIcH3EKWUl4S8xhFtN34GQRaAvTIZB9DD94rW7d/U7tUg3SYeDFNHA== +"@typescript-eslint/experimental-utils@4.29.3": + version "4.29.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.3.tgz#52e437a689ccdef73e83c5106b34240a706f15e1" + integrity sha512-ffIvbytTVWz+3keg+Sy94FG1QeOvmV9dP2YSdLFHw/ieLXWCa3U1TYu8IRCOpMv2/SPS8XqhM1+ou1YHsdzKrg== dependencies: "@types/json-schema" "^7.0.7" - "@typescript-eslint/scope-manager" "4.28.4" - "@typescript-eslint/types" "4.28.4" - "@typescript-eslint/typescript-estree" "4.28.4" + "@typescript-eslint/scope-manager" "4.29.3" + "@typescript-eslint/types" "4.29.3" + "@typescript-eslint/typescript-estree" "4.29.3" eslint-scope "^5.1.1" eslint-utils "^3.0.0" "@typescript-eslint/parser@^4.17.0": - version "4.28.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.28.4.tgz#bc462dc2779afeefdcf49082516afdc3e7b96fab" - integrity sha512-4i0jq3C6n+og7/uCHiE6q5ssw87zVdpUj1k6VlVYMonE3ILdFApEzTWgppSRG4kVNB/5jxnH+gTeKLMNfUelQA== + version "4.29.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.29.3.tgz#2ac25535f34c0e98f50c0e6b28c679c2357d45f2" + integrity sha512-jrHOV5g2u8ROghmspKoW7pN8T/qUzk0+DITun0MELptvngtMrwUJ1tv5zMI04CYVEUsSrN4jV7AKSv+I0y0EfQ== dependencies: - "@typescript-eslint/scope-manager" "4.28.4" - "@typescript-eslint/types" "4.28.4" - "@typescript-eslint/typescript-estree" "4.28.4" + "@typescript-eslint/scope-manager" "4.29.3" + "@typescript-eslint/types" "4.29.3" + "@typescript-eslint/typescript-estree" "4.29.3" debug "^4.3.1" -"@typescript-eslint/scope-manager@4.28.4": - version "4.28.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.28.4.tgz#bdbce9b6a644e34f767bd68bc17bb14353b9fe7f" - integrity sha512-ZJBNs4usViOmlyFMt9X9l+X0WAFcDH7EdSArGqpldXu7aeZxDAuAzHiMAeI+JpSefY2INHrXeqnha39FVqXb8w== +"@typescript-eslint/scope-manager@4.29.3": + version "4.29.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.29.3.tgz#497dec66f3a22e459f6e306cf14021e40ec86e19" + integrity sha512-x+w8BLXO7iWPkG5mEy9bA1iFRnk36p/goVlYobVWHyDw69YmaH9q6eA+Fgl7kYHmFvWlebUTUfhtIg4zbbl8PA== dependencies: - "@typescript-eslint/types" "4.28.4" - "@typescript-eslint/visitor-keys" "4.28.4" + "@typescript-eslint/types" "4.29.3" + "@typescript-eslint/visitor-keys" "4.29.3" -"@typescript-eslint/types@4.28.4": - version "4.28.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.28.4.tgz#41acbd79b5816b7c0dd7530a43d97d020d3aeb42" - integrity sha512-3eap4QWxGqkYuEmVebUGULMskR6Cuoc/Wii0oSOddleP4EGx1tjLnZQ0ZP33YRoMDCs5O3j56RBV4g14T4jvww== +"@typescript-eslint/types@4.29.3": + version "4.29.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.29.3.tgz#d7980c49aef643d0af8954c9f14f656b7fd16017" + integrity sha512-s1eV1lKNgoIYLAl1JUba8NhULmf+jOmmeFO1G5MN/RBCyyzg4TIOfIOICVNC06lor+Xmy4FypIIhFiJXOknhIg== -"@typescript-eslint/typescript-estree@4.28.4": - version "4.28.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.4.tgz#252e6863278dc0727244be9e371eb35241c46d00" - integrity sha512-z7d8HK8XvCRyN2SNp+OXC2iZaF+O2BTquGhEYLKLx5k6p0r05ureUtgEfo5f6anLkhCxdHtCf6rPM1p4efHYDQ== +"@typescript-eslint/typescript-estree@4.29.3": + version "4.29.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.3.tgz#1bafad610015c4ded35c85a70b6222faad598b40" + integrity sha512-45oQJA0bxna4O5TMwz55/TpgjX1YrAPOI/rb6kPgmdnemRZx/dB0rsx+Ku8jpDvqTxcE1C/qEbVHbS3h0hflag== dependencies: - "@typescript-eslint/types" "4.28.4" - "@typescript-eslint/visitor-keys" "4.28.4" + "@typescript-eslint/types" "4.29.3" + "@typescript-eslint/visitor-keys" "4.29.3" debug "^4.3.1" globby "^11.0.3" is-glob "^4.0.1" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@4.28.4": - version "4.28.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.4.tgz#92dacfefccd6751cbb0a964f06683bfd72d0c4d3" - integrity sha512-NIAXAdbz1XdOuzqkJHjNKXKj8QQ4cv5cxR/g0uQhCYf/6//XrmfpaYsM7PnBcNbfvTDLUkqQ5TPNm1sozDdTWg== +"@typescript-eslint/visitor-keys@4.29.3": + version "4.29.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.3.tgz#c691760a00bd86bf8320d2a90a93d86d322f1abf" + integrity sha512-MGGfJvXT4asUTeVs0Q2m+sY63UsfnA+C/FDgBKV3itLBmM9H0u+URcneePtkd0at1YELmZK6HSolCqM4Fzs6yA== dependencies: - "@typescript-eslint/types" "4.28.4" + "@typescript-eslint/types" "4.29.3" eslint-visitor-keys "^2.0.0" "@wojtekmaj/enzyme-adapter-react-17@^0.6.1": @@ -2068,10 +2036,10 @@ ajv@^8.0.1: require-from-string "^2.0.2" uri-js "^4.2.2" -allchange@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/allchange/-/allchange-1.0.0.tgz#f5177b7d97f8e97a2d059a1524db9a72d94dc6d2" - integrity sha512-O0VIaMIORxOaReyYEijDfKdpudJhbzzVYLdJR1aROyUgOLBEp9e5V/TDXQpjX23W90IFCSRZxsDb3exLRD05HA== +allchange@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/allchange/-/allchange-1.0.3.tgz#f8814ddfbcfe39a01bf4570778ee7e6d9ff0ebb3" + integrity sha512-UZkfz5SkNEMFQFLr8vZcXHaph2EbJxmkVNF5Nt6D9RIa5pmAar7oAMfNdda714jg7IQijvaFty5PYazXLgd5WA== dependencies: "@actions/core" "^1.4.0" "@actions/github" "^5.0.0" @@ -2289,7 +2257,7 @@ autoprefixer@^9.8.6: postcss "^7.0.32" postcss-value-parser "^4.1.0" -available-typed-arrays@^1.0.2: +available-typed-arrays@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz#9e0ae84ecff20caae6a94a1c3bc39b955649b7a9" integrity sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA== @@ -2361,9 +2329,9 @@ babel-plugin-polyfill-corejs2@^0.2.2: semver "^6.1.1" babel-plugin-polyfill-corejs3@^0.2.2: - version "0.2.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.3.tgz#72add68cf08a8bf139ba6e6dfc0b1d504098e57b" - integrity sha512-rCOFzEIJpJEAU14XCcV/erIf/wZQMmMT5l5vXOpL5uoznyOGfDIjPj6FVytMvtzaKSTSVKouOCTPJ5OMUZH30g== + version "0.2.4" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.4.tgz#68cb81316b0e8d9d721a92e0009ec6ecd4cd2ca9" + integrity sha512-z3HnJE5TY/j4EFEa/qpQMSbcUJZ5JQi+3UFjXzn6pQCmIKc5Ug5j98SuYyH+m4xQnvKlMDIW4plLfgyVnd0IcQ== dependencies: "@babel/helper-define-polyfill-provider" "^0.2.2" core-js-compat "^3.14.0" @@ -2469,9 +2437,9 @@ bluebird@^3.5.0: integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== blurhash@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-1.1.3.tgz#dc325af7da836d07a0861d830bdd63694382483e" - integrity sha512-yUhPJvXexbqbyijCIE/T2NCXcj9iNPhWmOKbPTuR/cm7Q5snXYIfnVnz6m7MWOXxODMz/Cr3UcVkRdHiuDVRDw== + version "1.1.4" + resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-1.1.4.tgz#a7010ceb3019cd2c9809b17c910ebf6175d29244" + integrity sha512-MXIPz6zwYUKayju+Uidf83KhH0vodZfeRl6Ich8Gu+KGl0JgKiFq9LsfqV7cVU5fKD/AotmduZqvOfrGKOfTaA== boolbase@^1.0.0: version "1.0.0" @@ -2524,16 +2492,16 @@ browser-request@^0.3.3: resolved "https://registry.yarnpkg.com/browser-request/-/browser-request-0.3.3.tgz#9ece5b5aca89a29932242e18bf933def9876cc17" integrity sha1-ns5bWsqJopkyJC4Yv5M975h2zBc= -browserslist@^4.12.0, browserslist@^4.16.6: - version "4.16.6" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" - integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== +browserslist@^4.12.0, browserslist@^4.16.6, browserslist@^4.16.8: + version "4.16.8" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.8.tgz#cb868b0b554f137ba6e33de0ecff2eda403c4fb0" + integrity sha512-sc2m9ohR/49sWEbPj14ZSSZqp+kbi16aLao42Hmn3Z8FpjuMaq2xCA2l4zl9ITfyzvnvyE0hcg62YkIGKxgaNQ== dependencies: - caniuse-lite "^1.0.30001219" - colorette "^1.2.2" - electron-to-chromium "^1.3.723" + caniuse-lite "^1.0.30001251" + colorette "^1.3.0" + electron-to-chromium "^1.3.811" escalade "^3.1.1" - node-releases "^1.1.71" + node-releases "^1.1.75" bs58@^4.0.1: version "4.0.1" @@ -2568,9 +2536,9 @@ buffer-fill@^1.0.0: integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= buffer-from@^1.0.0, buffer-from@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== buffer@^5.4.3: version "5.7.1" @@ -2627,10 +2595,10 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219: - version "1.0.30001246" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001246.tgz#fe17d9919f87124d6bb416ef7b325356d69dc76c" - integrity sha512-Tc+ff0Co/nFNbLOrziBXmMVtpt9S2c2Y+Z9Nk9Khj09J+0zR9ejvIW5qkZAErCbOrVODCx/MN+GpB5FNBs5GFA== +caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001251: + version "1.0.30001251" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001251.tgz#6853a606ec50893115db660f82c094d18f096d85" + integrity sha512-HOe1r+9VkU4TFmnU70z+r7OLmtR+/chB1rdcJUeQlAinjEeb0cKL20tlAtOagNZhbrtLnCvV19B4FmF1rgzl6A== capture-exit@^2.0.0: version "2.0.0" @@ -2662,9 +2630,9 @@ chalk@^3.0.0: supports-color "^7.1.0" chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" - integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" @@ -2850,10 +2818,10 @@ color-name@^1.1.4, color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colorette@^1.2.1, colorette@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" - integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== +colorette@^1.2.1, colorette@^1.2.2, colorette@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.3.0.tgz#ff45d2f0edb244069d3b772adeb04fed38d0a0af" + integrity sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w== combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" @@ -2929,12 +2897,12 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -core-js-compat@^3.14.0, core-js-compat@^3.15.0: - version "3.15.2" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.15.2.tgz#47272fbb479880de14b4e6081f71f3492f5bd3cb" - integrity sha512-Wp+BJVvwopjI+A1EFqm2dwUmWYXrvucmtIB2LgXn/Rb+gWPKYxtmb4GKHGKG/KGF1eK9jfjzT38DITbTOCX/SQ== +core-js-compat@^3.14.0, core-js-compat@^3.16.0: + version "3.16.3" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.16.3.tgz#ae12a6e20505a1d79fbd16b6689dfc77fc989114" + integrity sha512-A/OtSfSJQKLAFRVd4V0m6Sep9lPdjD8bpN8v3tCCGwE0Tmh0hOiVDm9tw6mXmWOKOSZIyr3EkywPo84cJjGvIQ== dependencies: - browserslist "^4.16.6" + browserslist "^4.16.8" semver "7.0.0" core-js@^1.0.0: @@ -2948,9 +2916,9 @@ core-util-is@1.0.2, core-util-is@~1.0.0: integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= cosmiconfig@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" - integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== dependencies: "@types/parse-json" "^4.0.0" import-fresh "^3.2.1" @@ -3081,9 +3049,9 @@ data-urls@^2.0.0: whatwg-url "^8.0.0" date-fns@^2.0.1: - version "2.22.1" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.22.1.tgz#1e5af959831ebb1d82992bf67b765052d8f0efc4" - integrity sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg== + version "2.23.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.23.0.tgz#4e886c941659af0cf7b30fafdd1eaa37e88788a9" + integrity sha512-5ycpauovVyAk0kXNZz6ZoB9AYMZB4DObse7P3BPWmyEjXNORTI8EJ6X0uaSAq4sCHzM1uajzrkr6HnsLQpxGXA== date-names@^0.1.11: version "0.1.13" @@ -3313,10 +3281,10 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -electron-to-chromium@^1.3.723: - version "1.3.782" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.782.tgz#522740fe6b4b5255ca754c68d9c406a17b0998e2" - integrity sha512-6AI2se1NqWA1SBf/tlD6tQD/6ZOt+yAhqmrTlh4XZw4/g0Mt3p6JhTQPZxRPxPZiOg0o7ss1EBP/CpYejfnoIA== +electron-to-chromium@^1.3.811: + version "1.3.817" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.817.tgz#911b4775b5d9fa0c4729d4694adc81de85d8d8f6" + integrity sha512-Vw0Faepf2Id9Kf2e97M/c99qf168xg86JLKDxivvlpBQ9KDtjSeX0v+TiuSE25PqeQfTz+NJs375b64ca3XOIQ== emittery@^0.7.1: version "0.7.2" @@ -3422,10 +3390,10 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.18.0, es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2: - version "1.18.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.3.tgz#25c4c3380a27aa203c44b2b685bba94da31b63e0" - integrity sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw== +es-abstract@^1.18.0, es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2, es-abstract@^1.18.5: + version "1.18.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.5.tgz#9b10de7d4c206a3581fd5b2124233e04db49ae19" + integrity sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" @@ -3433,11 +3401,12 @@ es-abstract@^1.18.0, es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2, es- get-intrinsic "^1.1.1" has "^1.0.3" has-symbols "^1.0.2" + internal-slot "^1.0.3" is-callable "^1.2.3" is-negative-zero "^2.0.1" is-regex "^1.1.3" is-string "^1.0.6" - object-inspect "^1.10.3" + object-inspect "^1.11.0" object-keys "^1.1.1" object.assign "^4.1.2" string.prototype.trimend "^1.0.4" @@ -3804,11 +3773,11 @@ expect@^26.6.2: jest-regex-util "^26.0.0" ext@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" - integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== + version "1.5.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.5.0.tgz#e93b97ae0cb23f8370380f6107d2d2b7887687ad" + integrity sha512-+ONcYoWj/SoQwUofMr94aGu05Ou4FepKi7N7b+O8T4jVfyIsZQV1/xeS8jpaBzF0csAk0KLXoHCxU7cKYZjo1Q== dependencies: - type "^2.0.0" + type "^2.5.0" extend-shallow@^2.0.1: version "2.0.1" @@ -3891,9 +3860,9 @@ fastest-levenshtein@^1.0.12: integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== fastq@^1.6.0: - version "1.11.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.1.tgz#5d8175aae17db61947f8b162cfc7f63264d22807" - integrity sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw== + version "1.12.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.12.0.tgz#ed7b6ab5d62393fb2cc591c853652a5c318bf794" + integrity sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg== dependencies: reusify "^1.0.4" @@ -4005,9 +3974,9 @@ flat-cache@^3.0.4: rimraf "^3.0.2" flatted@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.1.tgz#bbef080d95fca6709362c73044a1634f7c6e7d05" - integrity sha512-OMQjaErSFHmHqZe+PSidH5n8j3O0F2DdnVh8JB4j4eUQ2k6KvB0qGfrKIhapvez5JerBbmWkaLYUYWISaESoXg== + version "3.2.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" + integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== flux@2.1.1: version "2.1.1" @@ -4252,9 +4221,9 @@ gonzales-pe@^4.3.0: minimist "^1.2.5" graceful-fs@^4.1.11, graceful-fs@^4.2.4: - version "4.2.6" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" - integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + version "4.2.8" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== growly@^1.3.0: version "1.3.0" @@ -4299,6 +4268,13 @@ has-symbols@^1.0.1, has-symbols@^1.0.2: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -4574,11 +4550,12 @@ is-alphanumerical@^1.0.0: is-decimal "^1.0.0" is-arguments@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9" - integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg== + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== dependencies: - call-bind "^1.0.0" + call-bind "^1.0.2" + has-tostringtag "^1.0.0" is-arrayish@^0.2.1: version "0.2.1" @@ -4597,10 +4574,12 @@ is-async-fn@^1.1.0: resolved "https://registry.yarnpkg.com/is-async-fn/-/is-async-fn-1.1.0.tgz#a1a15b11d4a1155cc23b11e91b301b45a3caad16" integrity sha1-oaFbEdShFVzCOxHpGzAbRaPKrRY= -is-bigint@^1.0.1, is-bigint@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" - integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== +is-bigint@^1.0.1, is-bigint@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" is-binary-path@^1.0.0: version "1.0.1" @@ -4616,12 +4595,13 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.0.1, is-boolean-object@^1.1.0, is-boolean-object@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8" - integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng== +is-boolean-object@^1.0.1, is-boolean-object@^1.1.0, is-boolean-object@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== dependencies: call-bind "^1.0.2" + has-tostringtag "^1.0.0" is-buffer@^1.1.5: version "1.1.6" @@ -4633,10 +4613,10 @@ is-buffer@^2.0.0: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== -is-callable@^1.0.4, is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" - integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== +is-callable@^1.0.4, is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.3, is-callable@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== is-ci@^2.0.0: version "2.0.0" @@ -4645,10 +4625,10 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-core-module@^2.2.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" - integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== +is-core-module@^2.2.0, is-core-module@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.6.0.tgz#d7553b2526fe59b92ba3e40c8df757ec8a709e19" + integrity sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ== dependencies: has "^1.0.3" @@ -4666,10 +4646,12 @@ is-data-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-date-object@^1.0.1, is-date-object@^1.0.2, is-date-object@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5" - integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A== +is-date-object@^1.0.1, is-date-object@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" is-decimal@^1.0.0: version "1.0.4" @@ -4700,26 +4682,28 @@ is-docker@^2.0.0: integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== is-equal@^1.5.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/is-equal/-/is-equal-1.6.2.tgz#9de9a33fee63c310372e401d2a6ef86dc2668615" - integrity sha512-paNlhukQqphbdiILWvU4Sl3Q1wvJNDqpZUQdnFKbL6XEvSIUemV8BVCOVuElqnYeWN+fG3nLDlv3Mgh3/ehTMA== + version "1.6.3" + resolved "https://registry.yarnpkg.com/is-equal/-/is-equal-1.6.3.tgz#7f5578799a644cfb6dc82285ce9168f151e23259" + integrity sha512-LTIjMaisYvuz8FhWSCc/Lux7MSE6Ucv7G+C2lixnn2vW+pOMgyTWGq3JPeyqFOfcv0Jb1fMpvQ121rjbfF0Z+A== dependencies: es-get-iterator "^1.1.2" functions-have-names "^1.2.2" has "^1.0.3" + has-bigints "^1.0.1" + has-symbols "^1.0.2" is-arrow-function "^2.0.3" - is-bigint "^1.0.2" - is-boolean-object "^1.1.1" - is-callable "^1.2.3" - is-date-object "^1.0.4" - is-generator-function "^1.0.9" - is-number-object "^1.0.5" - is-regex "^1.1.3" - is-string "^1.0.6" - is-symbol "^1.0.3" + is-bigint "^1.0.3" + is-boolean-object "^1.1.2" + is-callable "^1.2.4" + is-date-object "^1.0.5" + is-generator-function "^1.0.10" + is-number-object "^1.0.6" + is-regex "^1.1.4" + is-string "^1.0.7" + is-symbol "^1.0.4" isarray "^2.0.5" - object-inspect "^1.10.3" - object.entries "^1.1.3" + object-inspect "^1.11.0" + object.entries "^1.1.4" object.getprototypeof "^1.0.1" which-boxed-primitive "^1.0.2" which-collection "^1.0.1" @@ -4761,10 +4745,12 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-generator-function@^1.0.8, is-generator-function@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.9.tgz#e5f82c2323673e7fcad3d12858c83c4039f6399c" - integrity sha512-ZJ34p1uvIfptHCN7sFTjGibB9/oBg17sHqzDLfuwhvmN/qLVvIQXRQ8licZQ35WJ8KuEQt/etnnzQFI9C9Ue/A== +is-generator-function@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" @@ -4795,10 +4781,12 @@ is-negative-zero@^2.0.1: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== -is-number-object@^1.0.4, is-number-object@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" - integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== +is-number-object@^1.0.4, is-number-object@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" is-number@^3.0.0: version "3.0.0" @@ -4844,13 +4832,13 @@ is-promise@^2.2.2: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-regex@^1.0.3, is-regex@^1.0.5, is-regex@^1.1.2, is-regex@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" - integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ== +is-regex@^1.0.3, is-regex@^1.0.5, is-regex@^1.1.3, is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: call-bind "^1.0.2" - has-symbols "^1.0.2" + has-tostringtag "^1.0.0" is-regexp@^2.0.0: version "2.1.0" @@ -4868,37 +4856,39 @@ is-stream@^1.0.1, is-stream@^1.1.0: integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-string@^1.0.5, is-string@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f" - integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w== +is-string@^1.0.5, is-string@^1.0.6, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" is-subset@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= -is-symbol@^1.0.2, is-symbol@^1.0.3: +is-symbol@^1.0.2, is-symbol@^1.0.3, is-symbol@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.3: - version "1.1.5" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.5.tgz#f32e6e096455e329eb7b423862456aa213f0eb4e" - integrity sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug== +is-typed-array@^1.1.6: + version "1.1.7" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.7.tgz#881ddc660b13cb8423b2090fa88c0fe37a83eb2f" + integrity sha512-VxlpTBGknhQ3o7YiVjIhdLU6+oD8dPz/79vvvH4F+S/c8608UCVa9fgDpa1kZgFoUST2DCgacc70UszKgzKuvA== dependencies: - available-typed-arrays "^1.0.2" + available-typed-arrays "^1.0.4" call-bind "^1.0.2" - es-abstract "^1.18.0-next.2" + es-abstract "^1.18.5" foreach "^2.0.5" - has-symbols "^1.0.1" + has-tostringtag "^1.0.0" is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" @@ -5477,9 +5467,9 @@ jsbn@~0.1.0: integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= jsdom@^16.2.1, jsdom@^16.4.0: - version "16.6.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.6.0.tgz#f79b3786682065492a3da6a60a4695da983805ac" - integrity sha512-Ty1vmF4NHJkolaEmdjtxTfSfkdb8Ywarwf63f+F8/mDD1uLSSWDxDuMiZxiPhwunLrn9LOSVItWj4bLYsLN3Dg== + version "16.7.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" + integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== dependencies: abab "^2.0.5" acorn "^8.2.4" @@ -5506,7 +5496,7 @@ jsdom@^16.2.1, jsdom@^16.4.0: whatwg-encoding "^1.0.5" whatwg-mimetype "^2.3.0" whatwg-url "^8.5.0" - ws "^7.4.5" + ws "^7.4.6" xml-name-validator "^3.0.0" jsesc@^2.5.1: @@ -5801,10 +5791,10 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@12.4.1: - version "12.4.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-12.4.1.tgz#966f506b4146e4fafffa5bbe80f5c53515e1bc78" - integrity sha512-C9aSGX9e4GoCm0Rli+iGBXmcnRxnwETw7MvgNcSBfPaLHOMZi/wz4YOV7HEZK8R+OXuDrDYyglncWSJkkoDpAQ== +matrix-js-sdk@12.5.0: + version "12.5.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-12.5.0.tgz#3899f9d323c457d15a1fe436a2dfa07ae131cce2" + integrity sha512-HnEXoEhqpNp9/W9Ep7ZNZAubFlUssFyVpjgKfMOxxg+dYbBk5NWToHmAPQxlRUgrZ/rIMLVyMJROSCIthDbo2A== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" @@ -5838,10 +5828,10 @@ matrix-react-test-utils@^0.2.3: "@babel/traverse" "^7.13.17" walk "^2.3.14" -matrix-widget-api@^0.1.0-beta.15: - version "0.1.0-beta.15" - resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-0.1.0-beta.15.tgz#b02511f93fe1a3634868b6e246d736107f182745" - integrity sha512-sWmtb8ZarSbHVbk5ni7IHBR9jOh7m1+5R4soky0fEO9VKl+MN7skT0+qNux3J9WuUAu2D80dZW9xPUT9cxfxbg== +matrix-widget-api@^0.1.0-beta.16: + version "0.1.0-beta.16" + resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-0.1.0-beta.16.tgz#32655f05cab48239b97fe4111a1d0858f2aad61a" + integrity sha512-9zqaNLaM14YDHfFb7WGSUOivGOjYw+w5Su84ZfOl6A4IUy1xT9QPp0nsSA8wNfz0LpxOIPn3nuoF8Tn/40F5tg== dependencies: "@types/events" "^3.0.0" events "^3.2.0" @@ -5961,17 +5951,17 @@ micromatch@^4.0.2, micromatch@^4.0.4: braces "^3.0.1" picomatch "^2.2.3" -mime-db@1.48.0: - version "1.48.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" - integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== +mime-db@1.49.0: + version "1.49.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" + integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.31" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b" - integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg== + version "2.1.32" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" + integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== dependencies: - mime-db "1.48.0" + mime-db "1.49.0" mimic-fn@^2.1.0: version "2.1.0" @@ -6035,9 +6025,9 @@ ms@2.1.2: integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== nanoid@^3.1.23: - version "3.1.23" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" - integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== + version "3.1.25" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" + integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q== nanomatch@^1.2.9: version "1.2.13" @@ -6121,10 +6111,10 @@ node-notifier@^8.0.0: uuid "^8.3.0" which "^2.0.2" -node-releases@^1.1.71: - version "1.1.73" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20" - integrity sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg== +node-releases@^1.1.75: + version "1.1.75" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.75.tgz#6dd8c876b9897a1b8e5a02de26afa79bb54ebbfe" + integrity sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw== normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" @@ -6137,12 +6127,12 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: validate-npm-package-license "^3.0.1" normalize-package-data@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.2.tgz#cae5c410ae2434f9a6c1baa65d5bc3b9366c8699" - integrity sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg== + version "3.0.3" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" + integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== dependencies: hosted-git-info "^4.0.1" - resolve "^1.20.0" + is-core-module "^2.5.0" semver "^7.3.4" validate-npm-package-license "^3.0.1" @@ -6218,7 +6208,7 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@^1.1.0, object-inspect@^1.10.3, object-inspect@^1.7.0, object-inspect@^1.9.0: +object-inspect@^1.1.0, object-inspect@^1.11.0, object-inspect@^1.7.0, object-inspect@^1.9.0: version "1.11.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== @@ -6253,7 +6243,7 @@ object.assign@^4.1.0, object.assign@^4.1.2: has-symbols "^1.0.1" object-keys "^1.1.1" -object.entries@^1.1.1, object.entries@^1.1.3, object.entries@^1.1.4: +object.entries@^1.1.1, object.entries@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.4.tgz#43ccf9a50bc5fd5b649d45ab1a579f24e088cafd" integrity sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA== @@ -6337,9 +6327,9 @@ optionator@^0.9.1: word-wrap "^1.2.3" opus-recorder@^8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/opus-recorder/-/opus-recorder-8.0.3.tgz#f7b44f8f68500c9b96a15042a69f915fd9c1716d" - integrity sha512-8vXGiRwlJAavT9D3yYzukNVXQ8vEcKHcsQL/zXO24DQtJ0PLXvoPHNQPJrbMCdB4ypJgWDExvHF4JitQDL7dng== + version "8.0.4" + resolved "https://registry.yarnpkg.com/opus-recorder/-/opus-recorder-8.0.4.tgz#c4cdbb8bb94d17aa406934b58dcf9caab6c79b09" + integrity sha512-nWwLH5BySgNDHdpkOMV+igl9iyS99g60ap/0LycIgbSXykZvUpweuWCgAl3mTKSL0773yvKohlO5dOv5RQqG/Q== p-each-series@^2.1.0: version "2.2.0" @@ -6386,9 +6376,9 @@ p-try@^2.0.0: integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== pako@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.3.tgz#cdf475e31b678565251406de9e759196a0ea7a43" - integrity sha512-WjR1hOeg+kki3ZIOjaf4b5WVcay1jaliKSYiEaB1XzwhMQZJxRdQRv0V31EKBYlxb4T7SK3hjfc/jxyU64BoSw== + version "2.0.4" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.4.tgz#6cebc4bbb0b6c73b0d5b8d7e8476e2b2fbea576d" + integrity sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg== parent-module@^1.0.0: version "1.0.1" @@ -6621,9 +6611,9 @@ postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0. supports-color "^6.1.0" postcss@^8.0.2: - version "8.3.5" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.5.tgz#982216b113412bc20a86289e91eb994952a5b709" - integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA== + version "8.3.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea" + integrity sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A== dependencies: colorette "^1.2.2" nanoid "^3.1.23" @@ -6718,10 +6708,10 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -pvtsutils@^1.1.2, pvtsutils@^1.1.6, pvtsutils@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.1.7.tgz#39a65ccb3b7448c974f6a6141ce2aad037b3f13c" - integrity sha512-faOiD/XpB/cIebRzYwzYjCmYgiDd53YEBni+Mt1+8/HlrARHYBpsU2OHOt3EZ1ZhfRNxPL0dH3K/vKaMgNWVGA== +pvtsutils@^1.1.2, pvtsutils@^1.1.6, pvtsutils@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.2.0.tgz#619e4767093d23cd600482600c16f4c36d3025bb" + integrity sha512-IDefMJEQl7HX0FP2hIKJFnAR11klP1js2ixCrOaMhe3kXFK6RQ2ABUCuwWaaD4ib0hSbh2fGTICvWJJhDfNecA== dependencies: tslib "^2.2.0" @@ -6981,9 +6971,9 @@ redent@^3.0.0: strip-indent "^3.0.0" redux@^4.0.0, redux@^4.0.4: - version "4.1.0" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4" - integrity sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g== + version "4.1.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.1.tgz#76f1c439bb42043f985fbd9bf21990e60bd67f47" + integrity sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw== dependencies: "@babel/runtime" "^7.9.2" @@ -7011,9 +7001,9 @@ regenerate@^1.4.0: integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== regenerator-runtime@^0.13.4: - version "0.13.7" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" - integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== regenerator-transform@^0.14.2: version "0.14.5" @@ -7173,7 +7163,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.10.0, resolve@^1.14.2, resolve@^1.18.1, resolve@^1.20.0: +resolve@^1.10.0, resolve@^1.14.2, resolve@^1.18.1: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== @@ -7530,9 +7520,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.9" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz#8a595135def9592bda69709474f1cbeea7c2467f" - integrity sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ== + version "3.0.10" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz#0d9becccde7003d6c658d487dd48a32f0bf3014b" + integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA== specificity@^0.4.1: version "0.4.1" @@ -7995,9 +7985,9 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" - integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== tsutils@^3.21.0: version "3.21.0" @@ -8067,7 +8057,7 @@ type@^1.0.1: resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== -type@^2.0.0: +type@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== @@ -8395,21 +8385,22 @@ which-boxed-primitive@^1.0.2: is-symbol "^1.0.3" which-builtin-type@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.0.tgz#0295cbda3fa75837bf4ab6cc56c4b33af1e99454" - integrity sha512-KOX/VAdpOLOahMo64rn+tPK8IHc8TY12r2iCM/Lvlgk6JMzShmxz751C5HEHP55zBAQe2eJeltmfyGbe3ggw4Q== + version "1.1.1" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.1.tgz#1d14bb1b69b5680ebdddd7244689574678a1d83c" + integrity sha512-zY3bUNzl/unBfSDS6ePT+/dwu6hZ7RMVMqHFvYxZEhisGEwCV/pYnXQ70nd3Hn2X6l8BNOWge5sHk3wAR3L42w== dependencies: function.prototype.name "^1.1.4" + has-tostringtag "^1.0.0" is-async-fn "^1.1.0" - is-date-object "^1.0.2" + is-date-object "^1.0.5" is-finalizationregistry "^1.0.1" - is-generator-function "^1.0.8" - is-regex "^1.1.2" + is-generator-function "^1.0.10" + is-regex "^1.1.4" is-weakref "^1.0.1" isarray "^2.0.5" which-boxed-primitive "^1.0.2" which-collection "^1.0.1" - which-typed-array "^1.1.4" + which-typed-array "^1.1.5" which-collection@^1.0.1: version "1.0.1" @@ -8426,18 +8417,17 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which-typed-array@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.4.tgz#8fcb7d3ee5adf2d771066fba7cf37e32fe8711ff" - integrity sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA== +which-typed-array@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.6.tgz#f3713d801da0720a7f26f50c596980a9f5c8b383" + integrity sha512-DdY984dGD5sQ7Tf+x1CkXzdg85b9uEel6nr4UkFg1LoE9OXv3uRuZhe5CoWdawhGACeFpEZXH8fFLQnDhbpm/Q== dependencies: - available-typed-arrays "^1.0.2" - call-bind "^1.0.0" - es-abstract "^1.18.0-next.1" + available-typed-arrays "^1.0.4" + call-bind "^1.0.2" + es-abstract "^1.18.5" foreach "^2.0.5" - function-bind "^1.1.1" - has-symbols "^1.0.1" - is-typed-array "^1.1.3" + has-tostringtag "^1.0.0" + is-typed-array "^1.1.6" which@^1.2.9, which@^1.3.1: version "1.3.1" @@ -8500,7 +8490,7 @@ write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@^7.4.5: +ws@^7.4.6: version "7.5.3" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== @@ -8590,9 +8580,9 @@ yargs@^15.4.1: yargs-parser "^18.1.2" yargs@^17.0.1: - version "17.0.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.0.1.tgz#6a1ced4ed5ee0b388010ba9fd67af83b9362e0bb" - integrity sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ== + version "17.1.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.1.1.tgz#c2a8091564bdb196f7c0a67c1d12e5b85b8067ba" + integrity sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ== dependencies: cliui "^7.0.2" escalade "^3.1.1"