Merge remote-tracking branch 'upstream/develop' into task/remove-message_send_failed
						commit
						6972cb248f
					
				
							
								
								
									
										67
									
								
								CHANGELOG.md
								
								
								
								
							
							
						
						
									
										67
									
								
								CHANGELOG.md
								
								
								
								
							| 
						 | 
				
			
			@ -1,3 +1,70 @@
 | 
			
		|||
Changes in [3.30.0](https://github.com/vector-im/element-desktop/releases/tag/v3.30.0) (2021-09-14)
 | 
			
		||||
===================================================================================================
 | 
			
		||||
 | 
			
		||||
## ✨ 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.29.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.29.1) (2021-09-13)
 | 
			
		||||
===================================================================================================
 | 
			
		||||
 | 
			
		||||
## 🔒 SECURITY FIXES
 | 
			
		||||
 * Fix a security issue with message key sharing. See https://matrix.org/blog/2021/09/13/vulnerability-disclosure-key-sharing
 | 
			
		||||
   for details.
 | 
			
		||||
 | 
			
		||||
Changes in [3.29.0](https://github.com/vector-im/element-desktop/releases/tag/v3.29.0) (2021-08-31)
 | 
			
		||||
===================================================================================================
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "matrix-react-sdk",
 | 
			
		||||
  "version": "3.29.0",
 | 
			
		||||
  "version": "3.30.0",
 | 
			
		||||
  "description": "SDK for matrix.org using React",
 | 
			
		||||
  "author": "matrix.org",
 | 
			
		||||
  "repository": {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -139,7 +139,6 @@ $activeBorderColor: $secondary-content;
 | 
			
		|||
        &:not(.mx_SpaceButton_narrow) {
 | 
			
		||||
            .mx_SpaceButton_selectionWrapper {
 | 
			
		||||
                width: 100%;
 | 
			
		||||
                padding-right: 16px;
 | 
			
		||||
                overflow: hidden;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -151,7 +150,6 @@ $activeBorderColor: $secondary-content;
 | 
			
		|||
            display: block;
 | 
			
		||||
            text-overflow: ellipsis;
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
            padding-right: 8px;
 | 
			
		||||
            font-size: $font-14px;
 | 
			
		||||
            line-height: $font-18px;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -225,8 +223,7 @@ $activeBorderColor: $secondary-content;
 | 
			
		|||
            margin-top: auto;
 | 
			
		||||
            margin-bottom: auto;
 | 
			
		||||
            display: none;
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            right: 4px;
 | 
			
		||||
            position: relative;
 | 
			
		||||
 | 
			
		||||
            &::before {
 | 
			
		||||
                top: 2px;
 | 
			
		||||
| 
						 | 
				
			
			@ -245,8 +242,6 @@ $activeBorderColor: $secondary-content;
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    .mx_SpacePanel_badgeContainer {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
 | 
			
		||||
        // Create a flexbox to make aligning dot badges easier
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
| 
						 | 
				
			
			@ -264,6 +259,7 @@ $activeBorderColor: $secondary-content;
 | 
			
		|||
    &.collapsed {
 | 
			
		||||
        .mx_SpaceButton {
 | 
			
		||||
            .mx_SpacePanel_badgeContainer {
 | 
			
		||||
                position: absolute;
 | 
			
		||||
                right: 0;
 | 
			
		||||
                top: 0;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -293,19 +289,12 @@ $activeBorderColor: $secondary-content;
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    &:not(.collapsed) {
 | 
			
		||||
        .mx_SpacePanel_badgeContainer {
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            right: 4px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .mx_SpaceButton:hover,
 | 
			
		||||
        .mx_SpaceButton:focus-within,
 | 
			
		||||
        .mx_SpaceButton_hasMenuOpen {
 | 
			
		||||
            &:not(.mx_SpaceButton_invite) {
 | 
			
		||||
                // Hide the badge container on hover because it'll be a menu button
 | 
			
		||||
                .mx_SpacePanel_badgeContainer {
 | 
			
		||||
                    width: 0;
 | 
			
		||||
                    height: 0;
 | 
			
		||||
                    display: none;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -98,14 +98,14 @@ limitations under the License.
 | 
			
		|||
    transition:
 | 
			
		||||
        font-size 0.25s ease-out 0.1s,
 | 
			
		||||
        color 0.25s ease-out 0.1s,
 | 
			
		||||
        top 0.25s ease-out 0.1s,
 | 
			
		||||
        transform 0.25s ease-out 0.1s,
 | 
			
		||||
        background-color 0.25s ease-out 0.1s;
 | 
			
		||||
    color: $primary-content;
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
    font-size: $font-14px;
 | 
			
		||||
    transform: translateY(0);
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: 0px;
 | 
			
		||||
    top: 0px;
 | 
			
		||||
    margin: 7px 8px;
 | 
			
		||||
    padding: 2px;
 | 
			
		||||
    pointer-events: none; // Allow clicks to fall through to the input
 | 
			
		||||
| 
						 | 
				
			
			@ -124,10 +124,10 @@ limitations under the License.
 | 
			
		|||
    transition:
 | 
			
		||||
        font-size 0.25s ease-out 0s,
 | 
			
		||||
        color 0.25s ease-out 0s,
 | 
			
		||||
        top 0.25s ease-out 0s,
 | 
			
		||||
        transform 0.25s ease-out 0s,
 | 
			
		||||
        background-color 0.25s ease-out 0s;
 | 
			
		||||
    font-size: $font-10px;
 | 
			
		||||
    top: -13px;
 | 
			
		||||
    transform: translateY(-13px);
 | 
			
		||||
    padding: 0 2px;
 | 
			
		||||
    background-color: $field-focused-label-bg-color;
 | 
			
		||||
    pointer-events: initial;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,6 @@
 | 
			
		|||
    background: $background;
 | 
			
		||||
    border-bottom: none;
 | 
			
		||||
    border-radius: 8px 8px 0 0;
 | 
			
		||||
    max-height: 35vh;
 | 
			
		||||
    overflow: clip;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
| 
						 | 
				
			
			@ -64,6 +63,7 @@
 | 
			
		|||
    margin: 12px;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    overflow-y: scroll;
 | 
			
		||||
    max-height: 35vh;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_Autocomplete_Completion_container_truncate {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -184,6 +184,9 @@ $visual-bell-bg-color: #800;
 | 
			
		|||
 | 
			
		||||
$room-warning-bg-color: $header-panel-bg-color;
 | 
			
		||||
 | 
			
		||||
$authpage-body-bg-color: $background;
 | 
			
		||||
$authpage-primary-color: $primary-content;
 | 
			
		||||
 | 
			
		||||
$dark-panel-bg-color: $header-panel-bg-color;
 | 
			
		||||
$panel-gradient: rgba(34, 38, 46, 0), rgba(34, 38, 46, 1);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -82,6 +82,8 @@ $tab-label-fg-color: var(--timeline-text-color);
 | 
			
		|||
// was #4e5054
 | 
			
		||||
$authpage-lang-color: var(--timeline-text-color);
 | 
			
		||||
$roomheader-color: var(--timeline-text-color);
 | 
			
		||||
// was #232f32
 | 
			
		||||
$authpage-primary-color: var(--timeline-text-color);
 | 
			
		||||
// --roomlist-text-secondary-color
 | 
			
		||||
$roomtile-preview-color: var(--roomlist-text-secondary-color);
 | 
			
		||||
$roomlist-header-color: var(--roomlist-text-secondary-color);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -322,10 +322,16 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
 | 
			
		|||
 | 
			
		||||
        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,
 | 
			
		||||
| 
						 | 
				
			
			@ -404,17 +410,27 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type ToRightOf = {
 | 
			
		||||
    left: number;
 | 
			
		||||
    top: number;
 | 
			
		||||
    chevronOffset: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Placement method for <ContextMenu /> to position context menu to right of elementRect with chevronOffset
 | 
			
		||||
export const toRightOf = (elementRect: Pick<DOMRect, "right" | "top" | "height">, chevronOffset = 12) => {
 | 
			
		||||
export const toRightOf = (elementRect: Pick<DOMRect, "right" | "top" | "height">, chevronOffset = 12): ToRightOf => {
 | 
			
		||||
    const left = elementRect.right + window.pageXOffset + 3;
 | 
			
		||||
    let top = elementRect.top + (elementRect.height / 2) + window.pageYOffset;
 | 
			
		||||
    top -= chevronOffset + 8; // where 8 is half the height of the chevron
 | 
			
		||||
    return { left, top, chevronOffset };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type AboveLeftOf = IPosition & {
 | 
			
		||||
    chevronFace: ChevronFace;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the left of elementRect,
 | 
			
		||||
// and either above or below: wherever there is more space (maybe this should be aboveOrBelowLeftOf?)
 | 
			
		||||
export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None, vPadding = 0) => {
 | 
			
		||||
export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None, vPadding = 0): AboveLeftOf => {
 | 
			
		||||
    const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
 | 
			
		||||
 | 
			
		||||
    const buttonRight = elementRect.right + window.pageXOffset;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -143,7 +143,7 @@ export enum Views {
 | 
			
		|||
    SOFT_LOGOUT,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const AUTH_SCREENS = ["register", "login", "forgot_password", "start_sso", "start_cas"];
 | 
			
		||||
const AUTH_SCREENS = ["register", "login", "forgot_password", "start_sso", "start_cas", "welcome"];
 | 
			
		||||
 | 
			
		||||
// Actions that are redirected through the onboarding process prior to being
 | 
			
		||||
// re-dispatched. NOTE: some actions are non-trivial and would require
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,6 +57,7 @@ import { Key } from "../../Keyboard";
 | 
			
		|||
import { IState, RovingTabIndexProvider, useRovingTabIndex } from "../../accessibility/RovingTabIndex";
 | 
			
		||||
import { getDisplayAliasForRoom } from "./RoomDirectory";
 | 
			
		||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
 | 
			
		||||
import { useEventEmitterState } from "../../hooks/useEventEmitter";
 | 
			
		||||
 | 
			
		||||
interface IProps {
 | 
			
		||||
    space: Room;
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +88,8 @@ const Tile: React.FC<ITileProps> = ({
 | 
			
		|||
}) => {
 | 
			
		||||
    const cli = useContext(MatrixClientContext);
 | 
			
		||||
    const joinedRoom = cli.getRoom(room.room_id)?.getMyMembership() === "join" ? cli.getRoom(room.room_id) : null;
 | 
			
		||||
    const name = joinedRoom?.name || room.name || room.canonical_alias || room.aliases?.[0]
 | 
			
		||||
    const joinedRoomName = useEventEmitterState(joinedRoom, "Room.name", room => room?.name);
 | 
			
		||||
    const name = joinedRoomName || room.name || room.canonical_alias || room.aliases?.[0]
 | 
			
		||||
        || (room.room_type === RoomType.Space ? _t("Unnamed Space") : _t("Unnamed Room"));
 | 
			
		||||
 | 
			
		||||
    const [showChildren, toggleShowChildren] = useStateToggle(true);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -78,6 +78,7 @@ import { CreateEventField, IGroupSummary } from "../views/dialogs/CreateSpaceFro
 | 
			
		|||
import { useAsyncMemo } from "../../hooks/useAsyncMemo";
 | 
			
		||||
import Spinner from "../views/elements/Spinner";
 | 
			
		||||
import GroupAvatar from "../views/avatars/GroupAvatar";
 | 
			
		||||
import { useDispatcher } from "../../hooks/useDispatcher";
 | 
			
		||||
 | 
			
		||||
interface IProps {
 | 
			
		||||
    space: Room;
 | 
			
		||||
| 
						 | 
				
			
			@ -191,6 +192,11 @@ interface ISpacePreviewProps {
 | 
			
		|||
const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }: ISpacePreviewProps) => {
 | 
			
		||||
    const cli = useContext(MatrixClientContext);
 | 
			
		||||
    const myMembership = useMyRoomMembership(space);
 | 
			
		||||
    useDispatcher(defaultDispatcher, payload => {
 | 
			
		||||
        if (payload.action === Action.JoinRoomError && payload.roomId === space.roomId) {
 | 
			
		||||
            setBusy(false); // stop the spinner, join failed
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const [busy, setBusy] = useState(false);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -826,7 +826,7 @@ const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({
 | 
			
		|||
    if (canAffectUser && me.powerLevel >= banPowerLevel) {
 | 
			
		||||
        banButton = <BanToggleButton member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} />;
 | 
			
		||||
    }
 | 
			
		||||
    if (canAffectUser && me.powerLevel >= editPowerLevel) {
 | 
			
		||||
    if (canAffectUser && me.powerLevel >= editPowerLevel && !room.isSpaceRoom()) {
 | 
			
		||||
        muteButton = (
 | 
			
		||||
            <MuteToggleButton
 | 
			
		||||
                member={member}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1192,14 +1192,19 @@ export default class EventTile extends React.Component<IProps, IState> {
 | 
			
		|||
            }
 | 
			
		||||
 | 
			
		||||
            default: {
 | 
			
		||||
                const thread = ReplyThread.makeThread(
 | 
			
		||||
                    this.props.mxEvent,
 | 
			
		||||
                    this.props.onHeightChanged,
 | 
			
		||||
                    this.props.permalinkCreator,
 | 
			
		||||
                    this.replyThread,
 | 
			
		||||
                    this.props.layout,
 | 
			
		||||
                    this.props.alwaysShowTimestamps || this.state.hover,
 | 
			
		||||
                );
 | 
			
		||||
                let thread;
 | 
			
		||||
                // When the "showHiddenEventsInTimeline" lab is enabled,
 | 
			
		||||
                // avoid showing replies for hidden events (events without tiles)
 | 
			
		||||
                if (haveTileForEvent(this.props.mxEvent)) {
 | 
			
		||||
                    thread = ReplyThread.makeThread(
 | 
			
		||||
                        this.props.mxEvent,
 | 
			
		||||
                        this.props.onHeightChanged,
 | 
			
		||||
                        this.props.permalinkCreator,
 | 
			
		||||
                        this.replyThread,
 | 
			
		||||
                        this.props.layout,
 | 
			
		||||
                        this.props.alwaysShowTimestamps || this.state.hover,
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const isOwnEvent = this.props.mxEvent?.sender?.userId === MatrixClientPeg.get().getUserId();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,6 +32,7 @@ import {
 | 
			
		|||
    ContextMenu,
 | 
			
		||||
    useContextMenu,
 | 
			
		||||
    MenuItem,
 | 
			
		||||
    AboveLeftOf,
 | 
			
		||||
} from "../../structures/ContextMenu";
 | 
			
		||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
 | 
			
		||||
import ReplyPreview from "./ReplyPreview";
 | 
			
		||||
| 
						 | 
				
			
			@ -511,7 +512,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
 | 
			
		|||
                null,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let menuPosition;
 | 
			
		||||
        let menuPosition: AboveLeftOf | undefined;
 | 
			
		||||
        if (this.ref.current) {
 | 
			
		||||
            const contentRect = this.ref.current.getBoundingClientRect();
 | 
			
		||||
            menuPosition = aboveLeftOf(contentRect);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 | 
			
		|||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
import React, { MouseEvent } from "react";
 | 
			
		||||
import classNames from "classnames";
 | 
			
		||||
import { formatCount } from "../../../utils/FormattingUtils";
 | 
			
		||||
import SettingsStore from "../../../settings/SettingsStore";
 | 
			
		||||
| 
						 | 
				
			
			@ -22,6 +22,9 @@ import AccessibleButton from "../elements/AccessibleButton";
 | 
			
		|||
import { XOR } from "../../../@types/common";
 | 
			
		||||
import { NOTIFICATION_STATE_UPDATE, NotificationState } from "../../../stores/notifications/NotificationState";
 | 
			
		||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
 | 
			
		||||
import Tooltip from "../elements/Tooltip";
 | 
			
		||||
import { _t } from "../../../languageHandler";
 | 
			
		||||
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
 | 
			
		||||
 | 
			
		||||
interface IProps {
 | 
			
		||||
    notification: NotificationState;
 | 
			
		||||
| 
						 | 
				
			
			@ -39,6 +42,7 @@ interface IProps {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
interface IClickableProps extends IProps, React.InputHTMLAttributes<Element> {
 | 
			
		||||
    showUnsentTooltip?: boolean;
 | 
			
		||||
    /**
 | 
			
		||||
     * If specified will return an AccessibleButton instead of a div.
 | 
			
		||||
     */
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +51,7 @@ interface IClickableProps extends IProps, React.InputHTMLAttributes<Element> {
 | 
			
		|||
 | 
			
		||||
interface IState {
 | 
			
		||||
    showCounts: boolean; // whether or not to show counts. Independent of props.forceCount
 | 
			
		||||
    showTooltip: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@replaceableComponent("views.rooms.NotificationBadge")
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +64,7 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
 | 
			
		|||
 | 
			
		||||
        this.state = {
 | 
			
		||||
            showCounts: SettingsStore.getValue("Notifications.alwaysShowBadgeCounts", this.roomId),
 | 
			
		||||
            showTooltip: false,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.countWatcherRef = SettingsStore.watchSetting(
 | 
			
		||||
| 
						 | 
				
			
			@ -93,9 +99,22 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
 | 
			
		|||
        this.forceUpdate(); // notification state changed - update
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private onMouseOver = (e: MouseEvent) => {
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        this.setState({
 | 
			
		||||
            showTooltip: true,
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private onMouseLeave = () => {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            showTooltip: false,
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    public render(): React.ReactElement {
 | 
			
		||||
        /* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
 | 
			
		||||
        const { notification, forceCount, roomId, onClick, ...props } = this.props;
 | 
			
		||||
        const { notification, showUnsentTooltip, forceCount, roomId, onClick, ...props } = this.props;
 | 
			
		||||
 | 
			
		||||
        // Don't show a badge if we don't need to
 | 
			
		||||
        if (notification.isIdle) return null;
 | 
			
		||||
| 
						 | 
				
			
			@ -124,9 +143,24 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
 | 
			
		|||
        });
 | 
			
		||||
 | 
			
		||||
        if (onClick) {
 | 
			
		||||
            let label: string;
 | 
			
		||||
            let tooltip: JSX.Element;
 | 
			
		||||
            if (showUnsentTooltip && this.state.showTooltip && notification.color === NotificationColor.Unsent) {
 | 
			
		||||
                label = _t("Message didn't send. Click for info.");
 | 
			
		||||
                tooltip = <Tooltip className="mx_RoleButton_tooltip" label={label} />;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return (
 | 
			
		||||
                <AccessibleButton {...props} className={classes} onClick={onClick}>
 | 
			
		||||
                <AccessibleButton
 | 
			
		||||
                    aria-label={label}
 | 
			
		||||
                    {...props}
 | 
			
		||||
                    className={classes}
 | 
			
		||||
                    onClick={onClick}
 | 
			
		||||
                    onMouseOver={this.onMouseOver}
 | 
			
		||||
                    onMouseLeave={this.onMouseLeave}
 | 
			
		||||
                >
 | 
			
		||||
                    <span className="mx_NotificationBadge_count">{ symbol }</span>
 | 
			
		||||
                    { tooltip }
 | 
			
		||||
                </AccessibleButton>
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -670,6 +670,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
 | 
			
		|||
                            onClick={this.onBadgeClick}
 | 
			
		||||
                            tabIndex={tabIndex}
 | 
			
		||||
                            aria-label={ariaLabel}
 | 
			
		||||
                            showUnsentTooltip={true}
 | 
			
		||||
                        />
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,6 @@ limitations under the License.
 | 
			
		|||
 | 
			
		||||
import React, { createRef } from "react";
 | 
			
		||||
import { Room } from "matrix-js-sdk/src/models/room";
 | 
			
		||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
 | 
			
		||||
import classNames from "classnames";
 | 
			
		||||
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
 | 
			
		||||
import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
 | 
			
		||||
| 
						 | 
				
			
			@ -51,8 +50,6 @@ import IconizedContextMenu, {
 | 
			
		|||
} from "../context_menus/IconizedContextMenu";
 | 
			
		||||
import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/CommunityPrototypeStore";
 | 
			
		||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
 | 
			
		||||
import { getUnsentMessages } from "../../structures/RoomStatusBar";
 | 
			
		||||
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
 | 
			
		||||
 | 
			
		||||
interface IProps {
 | 
			
		||||
    room: Room;
 | 
			
		||||
| 
						 | 
				
			
			@ -68,7 +65,6 @@ interface IState {
 | 
			
		|||
    notificationsMenuPosition: PartialDOMRect;
 | 
			
		||||
    generalMenuPosition: PartialDOMRect;
 | 
			
		||||
    messagePreview?: string;
 | 
			
		||||
    hasUnsentEvents: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const messagePreviewId = (roomId: string) => `mx_RoomTile_messagePreview_${roomId}`;
 | 
			
		||||
| 
						 | 
				
			
			@ -95,7 +91,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
 | 
			
		|||
            selected: ActiveRoomObserver.activeRoomId === this.props.room.roomId,
 | 
			
		||||
            notificationsMenuPosition: null,
 | 
			
		||||
            generalMenuPosition: null,
 | 
			
		||||
            hasUnsentEvents: this.countUnsentEvents() > 0,
 | 
			
		||||
 | 
			
		||||
            // generatePreview() will return nothing if the user has previews disabled
 | 
			
		||||
            messagePreview: "",
 | 
			
		||||
| 
						 | 
				
			
			@ -106,11 +101,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
 | 
			
		|||
        this.roomProps = EchoChamber.forRoom(this.props.room);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private countUnsentEvents(): number {
 | 
			
		||||
        return getUnsentMessages(this.props.room).length;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private onRoomNameUpdate = (room) => {
 | 
			
		||||
    private onRoomNameUpdate = (room: Room) => {
 | 
			
		||||
        this.forceUpdate();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -118,11 +109,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
 | 
			
		|||
        this.forceUpdate(); // notification state changed - update
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private onLocalEchoUpdated = (ev: MatrixEvent, room: Room) => {
 | 
			
		||||
        if (room?.roomId !== this.props.room.roomId) return;
 | 
			
		||||
        this.setState({ hasUnsentEvents: this.countUnsentEvents() > 0 });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private onRoomPropertyUpdate = (property: CachedRoomKey) => {
 | 
			
		||||
        if (property === CachedRoomKey.NotificationVolume) this.onNotificationUpdate();
 | 
			
		||||
        // else ignore - not important for this tile
 | 
			
		||||
| 
						 | 
				
			
			@ -178,12 +164,11 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
 | 
			
		|||
        );
 | 
			
		||||
        this.notificationState.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
 | 
			
		||||
        this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
 | 
			
		||||
        this.roomProps.on("Room.name", this.onRoomNameUpdate);
 | 
			
		||||
        this.props.room?.on("Room.name", this.onRoomNameUpdate);
 | 
			
		||||
        CommunityPrototypeStore.instance.on(
 | 
			
		||||
            CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId),
 | 
			
		||||
            this.onCommunityUpdate,
 | 
			
		||||
        );
 | 
			
		||||
        MatrixClientPeg.get().on("Room.localEchoUpdated", this.onLocalEchoUpdated);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public componentWillUnmount() {
 | 
			
		||||
| 
						 | 
				
			
			@ -208,7 +193,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
 | 
			
		|||
            CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId),
 | 
			
		||||
            this.onCommunityUpdate,
 | 
			
		||||
        );
 | 
			
		||||
        MatrixClientPeg.get()?.removeListener("Room.localEchoUpdated", this.onLocalEchoUpdated);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private onAction = (payload: ActionPayload) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -587,30 +571,17 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
 | 
			
		|||
        />;
 | 
			
		||||
 | 
			
		||||
        let badge: React.ReactNode;
 | 
			
		||||
        if (!this.props.isMinimized) {
 | 
			
		||||
        if (!this.props.isMinimized && this.notificationState) {
 | 
			
		||||
            // aria-hidden because we summarise the unread count/highlight status in a manual aria-label below
 | 
			
		||||
            if (this.state.hasUnsentEvents) {
 | 
			
		||||
                // hardcode the badge to a danger state when there's unsent messages
 | 
			
		||||
                badge = (
 | 
			
		||||
                    <div className="mx_RoomTile_badgeContainer" aria-hidden="true">
 | 
			
		||||
                        <NotificationBadge
 | 
			
		||||
                            notification={StaticNotificationState.RED_EXCLAMATION}
 | 
			
		||||
                            forceCount={false}
 | 
			
		||||
                            roomId={this.props.room.roomId}
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                );
 | 
			
		||||
            } else if (this.notificationState) {
 | 
			
		||||
                badge = (
 | 
			
		||||
                    <div className="mx_RoomTile_badgeContainer" aria-hidden="true">
 | 
			
		||||
                        <NotificationBadge
 | 
			
		||||
                            notification={this.notificationState}
 | 
			
		||||
                            forceCount={false}
 | 
			
		||||
                            roomId={this.props.room.roomId}
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            badge = (
 | 
			
		||||
                <div className="mx_RoomTile_badgeContainer" aria-hidden="true">
 | 
			
		||||
                    <NotificationBadge
 | 
			
		||||
                        notification={this.notificationState}
 | 
			
		||||
                        forceCount={false}
 | 
			
		||||
                        roomId={this.props.room.roomId}
 | 
			
		||||
                    />
 | 
			
		||||
                </div>
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let messagePreview = null;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -97,9 +97,8 @@ const spaceNameValidator = withValidation({
 | 
			
		|||
    ],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const nameToAlias = (name: string, domain: string): string => {
 | 
			
		||||
    const localpart = name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9_-]+/gi, "");
 | 
			
		||||
    return `#${localpart}:${domain}`;
 | 
			
		||||
const nameToLocalpart = (name: string): string => {
 | 
			
		||||
    return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9_-]+/gi, "");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// XXX: Temporary for the Spaces release only
 | 
			
		||||
| 
						 | 
				
			
			@ -176,8 +175,9 @@ export const SpaceCreateForm: React.FC<ISpaceCreateFormProps> = ({
 | 
			
		|||
            value={name}
 | 
			
		||||
            onChange={ev => {
 | 
			
		||||
                const newName = ev.target.value;
 | 
			
		||||
                if (!alias || alias === nameToAlias(name, domain)) {
 | 
			
		||||
                    setAlias(nameToAlias(newName, domain));
 | 
			
		||||
                if (!alias || alias === `#${nameToLocalpart(name)}:${domain}`) {
 | 
			
		||||
                    setAlias(`#${nameToLocalpart(newName)}:${domain}`);
 | 
			
		||||
                    aliasFieldRef.current?.validate({ allowEmpty: true });
 | 
			
		||||
                }
 | 
			
		||||
                setName(newName);
 | 
			
		||||
            }}
 | 
			
		||||
| 
						 | 
				
			
			@ -194,7 +194,7 @@ export const SpaceCreateForm: React.FC<ISpaceCreateFormProps> = ({
 | 
			
		|||
                onChange={setAlias}
 | 
			
		||||
                domain={domain}
 | 
			
		||||
                value={alias}
 | 
			
		||||
                placeholder={name ? nameToAlias(name, domain) : _t("e.g. my-space")}
 | 
			
		||||
                placeholder={name ? nameToLocalpart(name) : _t("e.g. my-space")}
 | 
			
		||||
                label={_t("Address")}
 | 
			
		||||
                disabled={busy}
 | 
			
		||||
                onKeyDown={onKeyDown}
 | 
			
		||||
| 
						 | 
				
			
			@ -217,6 +217,7 @@ export const SpaceCreateForm: React.FC<ISpaceCreateFormProps> = ({
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
const SpaceCreateMenu = ({ onFinished }) => {
 | 
			
		||||
    const cli = useContext(MatrixClientContext);
 | 
			
		||||
    const [visibility, setVisibility] = useState<Visibility>(null);
 | 
			
		||||
    const [busy, setBusy] = useState<boolean>(false);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -233,14 +234,18 @@ const SpaceCreateMenu = ({ onFinished }) => {
 | 
			
		|||
 | 
			
		||||
        setBusy(true);
 | 
			
		||||
        // require & validate the space name field
 | 
			
		||||
        if (!await spaceNameField.current.validate({ allowEmpty: false })) {
 | 
			
		||||
        if (!(await spaceNameField.current.validate({ allowEmpty: false }))) {
 | 
			
		||||
            spaceNameField.current.focus();
 | 
			
		||||
            spaceNameField.current.validate({ allowEmpty: false, focused: true });
 | 
			
		||||
            setBusy(false);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // validate the space name alias field but do not require it
 | 
			
		||||
        if (visibility === Visibility.Public && !await spaceAliasField.current.validate({ allowEmpty: true })) {
 | 
			
		||||
 | 
			
		||||
        // validate the space alias field but do not require it
 | 
			
		||||
        const aliasLocalpart = alias.substring(1, alias.length - cli.getDomain().length - 1);
 | 
			
		||||
        if (visibility === Visibility.Public && aliasLocalpart &&
 | 
			
		||||
            (await spaceAliasField.current.validate({ allowEmpty: true })) === false
 | 
			
		||||
        ) {
 | 
			
		||||
            spaceAliasField.current.focus();
 | 
			
		||||
            spaceAliasField.current.validate({ allowEmpty: true, focused: true });
 | 
			
		||||
            setBusy(false);
 | 
			
		||||
| 
						 | 
				
			
			@ -248,7 +253,13 @@ const SpaceCreateMenu = ({ onFinished }) => {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await createSpace(name, visibility === Visibility.Public, alias, topic, avatar);
 | 
			
		||||
            await createSpace(
 | 
			
		||||
                name,
 | 
			
		||||
                visibility === Visibility.Public,
 | 
			
		||||
                aliasLocalpart ? alias : undefined,
 | 
			
		||||
                topic,
 | 
			
		||||
                avatar,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            onFinished();
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -93,6 +93,7 @@ export const SpaceButton: React.FC<IButtonProps> = ({
 | 
			
		|||
                notification={notificationState}
 | 
			
		||||
                aria-label={ariaLabel}
 | 
			
		||||
                tabIndex={tabIndex}
 | 
			
		||||
                showUnsentTooltip={true}
 | 
			
		||||
            />
 | 
			
		||||
        </div>;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,11 @@ import type { EventEmitter } from "events";
 | 
			
		|||
type Handler = (...args: any[]) => void;
 | 
			
		||||
 | 
			
		||||
// Hook to wrap event emitter on and removeListener in hook lifecycle
 | 
			
		||||
export const useEventEmitter = (emitter: EventEmitter, eventName: string | symbol, handler: Handler) => {
 | 
			
		||||
export const useEventEmitter = (
 | 
			
		||||
    emitter: EventEmitter | undefined,
 | 
			
		||||
    eventName: string | symbol,
 | 
			
		||||
    handler: Handler,
 | 
			
		||||
) => {
 | 
			
		||||
    // Create a ref that stores handler
 | 
			
		||||
    const savedHandler = useRef(handler);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +55,11 @@ export const useEventEmitter = (emitter: EventEmitter, eventName: string | symbo
 | 
			
		|||
 | 
			
		||||
type Mapper<T> = (...args: any[]) => T;
 | 
			
		||||
 | 
			
		||||
export const useEventEmitterState = <T>(emitter: EventEmitter, eventName: string | symbol, fn: Mapper<T>): T => {
 | 
			
		||||
export const useEventEmitterState = <T>(
 | 
			
		||||
    emitter: EventEmitter | undefined,
 | 
			
		||||
    eventName: string | symbol,
 | 
			
		||||
    fn: Mapper<T>,
 | 
			
		||||
): T => {
 | 
			
		||||
    const [value, setValue] = useState<T>(fn());
 | 
			
		||||
    const handler = useCallback((...args: any[]) => {
 | 
			
		||||
        setValue(fn(...args));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@ const defaultMapper: Mapper<RoomState> = (roomState: RoomState) => roomState;
 | 
			
		|||
 | 
			
		||||
// Hook to simplify watching Matrix Room state
 | 
			
		||||
export const useRoomState = <T extends any = RoomState>(
 | 
			
		||||
    room: Room,
 | 
			
		||||
    room?: Room,
 | 
			
		||||
    mapper: Mapper<T> = defaultMapper as Mapper<T>,
 | 
			
		||||
): T => {
 | 
			
		||||
    const [value, setValue] = useState<T>(room ? mapper(room.currentState) : undefined);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1598,6 +1598,7 @@
 | 
			
		|||
    "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.": "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.",
 | 
			
		||||
    "Enable encryption in settings.": "Enable encryption in settings.",
 | 
			
		||||
    "End-to-end encryption isn't enabled": "End-to-end encryption isn't enabled",
 | 
			
		||||
    "Message didn't send. Click for info.": "Message didn't send. Click for info.",
 | 
			
		||||
    "Unpin": "Unpin",
 | 
			
		||||
    "View message": "View message",
 | 
			
		||||
    "%(duration)ss": "%(duration)ss",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -629,11 +629,18 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
 | 
			
		|||
    };
 | 
			
		||||
 | 
			
		||||
    private onRoom = (room: Room, newMembership?: string, oldMembership?: string) => {
 | 
			
		||||
        const membership = newMembership || room.getMyMembership();
 | 
			
		||||
        const roomMembership = room.getMyMembership();
 | 
			
		||||
        if (!roomMembership) {
 | 
			
		||||
            // room is still being baked in the js-sdk, we'll process it at Room.myMembership instead
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const membership = newMembership || roomMembership;
 | 
			
		||||
 | 
			
		||||
        if (!room.isSpaceRoom()) {
 | 
			
		||||
            // this.onRoomUpdate(room);
 | 
			
		||||
            this.onRoomsUpdate();
 | 
			
		||||
            // this.onRoomsUpdate();
 | 
			
		||||
            // ideally we only need onRoomsUpdate here but it doesn't rebuild parentMap so always adds new rooms to Home
 | 
			
		||||
            this.rebuild();
 | 
			
		||||
 | 
			
		||||
            if (membership === "join") {
 | 
			
		||||
                // the user just joined a room, remove it from the suggested list if it was there
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,7 +32,7 @@ export class ListNotificationState extends NotificationState {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    public get symbol(): string {
 | 
			
		||||
        return null; // This notification state doesn't support symbols
 | 
			
		||||
        return this._color === NotificationColor.Unsent ? "!" : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public setRooms(rooms: Room[]) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,4 +21,5 @@ export enum NotificationColor {
 | 
			
		|||
    Bold, // no badge, show as unread
 | 
			
		||||
    Grey, // unread notified messages
 | 
			
		||||
    Red,  // unread pings
 | 
			
		||||
    Unsent, // some messages failed to send
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
 | 
			
		|||
import * as RoomNotifs from '../../RoomNotifs';
 | 
			
		||||
import * as Unread from '../../Unread';
 | 
			
		||||
import { NotificationState } from "./NotificationState";
 | 
			
		||||
import { getUnsentMessages } from "../../components/structures/RoomStatusBar";
 | 
			
		||||
 | 
			
		||||
export class RoomNotificationState extends NotificationState implements IDestroyable {
 | 
			
		||||
    constructor(public readonly room: Room) {
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +33,7 @@ export class RoomNotificationState extends NotificationState implements IDestroy
 | 
			
		|||
        this.room.on("Room.timeline", this.handleRoomEventUpdate);
 | 
			
		||||
        this.room.on("Room.redaction", this.handleRoomEventUpdate);
 | 
			
		||||
        this.room.on("Room.myMembership", this.handleMembershipUpdate);
 | 
			
		||||
        this.room.on("Room.localEchoUpdated", this.handleLocalEchoUpdated);
 | 
			
		||||
        MatrixClientPeg.get().on("Event.decrypted", this.handleRoomEventUpdate);
 | 
			
		||||
        MatrixClientPeg.get().on("accountData", this.handleAccountDataUpdate);
 | 
			
		||||
        this.updateNotificationState();
 | 
			
		||||
| 
						 | 
				
			
			@ -47,12 +49,17 @@ export class RoomNotificationState extends NotificationState implements IDestroy
 | 
			
		|||
        this.room.removeListener("Room.timeline", this.handleRoomEventUpdate);
 | 
			
		||||
        this.room.removeListener("Room.redaction", this.handleRoomEventUpdate);
 | 
			
		||||
        this.room.removeListener("Room.myMembership", this.handleMembershipUpdate);
 | 
			
		||||
        this.room.removeListener("Room.localEchoUpdated", this.handleLocalEchoUpdated);
 | 
			
		||||
        if (MatrixClientPeg.get()) {
 | 
			
		||||
            MatrixClientPeg.get().removeListener("Event.decrypted", this.handleRoomEventUpdate);
 | 
			
		||||
            MatrixClientPeg.get().removeListener("accountData", this.handleAccountDataUpdate);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private handleLocalEchoUpdated = () => {
 | 
			
		||||
        this.updateNotificationState();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private handleReadReceipt = (event: MatrixEvent, room: Room) => {
 | 
			
		||||
        if (!readReceiptChangeIsFor(event, MatrixClientPeg.get())) return; // not our own - ignore
 | 
			
		||||
        if (room.roomId !== this.room.roomId) return; // not for us - ignore
 | 
			
		||||
| 
						 | 
				
			
			@ -79,7 +86,12 @@ export class RoomNotificationState extends NotificationState implements IDestroy
 | 
			
		|||
    private updateNotificationState() {
 | 
			
		||||
        const snapshot = this.snapshot();
 | 
			
		||||
 | 
			
		||||
        if (RoomNotifs.getRoomNotifsState(this.room.roomId) === RoomNotifs.MUTE) {
 | 
			
		||||
        if (getUnsentMessages(this.room).length > 0) {
 | 
			
		||||
            // When there are unsent messages we show a red `!`
 | 
			
		||||
            this._color = NotificationColor.Unsent;
 | 
			
		||||
            this._symbol = "!";
 | 
			
		||||
            this._count = 1; // not used, technically
 | 
			
		||||
        } else if (RoomNotifs.getRoomNotifsState(this.room.roomId) === RoomNotifs.MUTE) {
 | 
			
		||||
            // When muted we suppress all notification states, even if we have context on them.
 | 
			
		||||
            this._color = NotificationColor.None;
 | 
			
		||||
            this._symbol = null;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,7 +31,7 @@ export class SpaceNotificationState extends NotificationState {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    public get symbol(): string {
 | 
			
		||||
        return null; // This notification state doesn't support symbols
 | 
			
		||||
        return this._color === NotificationColor.Unsent ? "!" : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public setRooms(rooms: Room[]) {
 | 
			
		||||
| 
						 | 
				
			
			@ -54,7 +54,7 @@ export class SpaceNotificationState extends NotificationState {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    public getFirstRoomWithNotifications() {
 | 
			
		||||
        return this.rooms.find((room) => room.getUnreadNotificationCount() > 0).roomId;
 | 
			
		||||
        return Object.values(this.states).find(state => state.color >= this.color)?.room.roomId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public destroy() {
 | 
			
		||||
| 
						 | 
				
			
			@ -83,4 +83,3 @@ export class SpaceNotificationState extends NotificationState {
 | 
			
		|||
        this.emitIfUpdated(snapshot);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5791,9 +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@github:matrix-org/matrix-js-sdk#develop":
 | 
			
		||||
  version "12.4.0"
 | 
			
		||||
  resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/2783d162b77d6629c574f35e88bea9ae29765c34"
 | 
			
		||||
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"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue