From 40b684d7de997f667d858ad13a1c8bb9d373c576 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 23 Feb 2021 19:41:23 -0500 Subject: [PATCH 001/174] Vectorize spinners This replaces most of the GIF spinners used throughout the app with an SVG that respects the user's theme. However, spinner.gif is still retained for the room timeline avatar uploader component, since it is more difficult to replace. Signed-off-by: Robin Townsend --- res/css/views/elements/_InlineSpinner.scss | 19 +- res/css/views/elements/_Spinner.scss | 16 ++ res/img/logo-spinner.svg | 141 +++++++++++ res/img/spinner.svg | 235 +++++++----------- .../views/elements/InlineSpinner.js | 26 +- src/components/views/elements/Spinner.js | 28 ++- 6 files changed, 304 insertions(+), 161 deletions(-) create mode 100644 res/img/logo-spinner.svg diff --git a/res/css/views/elements/_InlineSpinner.scss b/res/css/views/elements/_InlineSpinner.scss index 6b91e45923..c850191b93 100644 --- a/res/css/views/elements/_InlineSpinner.scss +++ b/res/css/views/elements/_InlineSpinner.scss @@ -18,7 +18,24 @@ limitations under the License. display: inline; } -.mx_InlineSpinner_spin img { +.mx_InlineSpinner img, .mx_InlineSpinner_icon { margin: 0px 6px; vertical-align: -3px; } + +@keyframes spin { + from { + transform: rotateZ(0deg); + } + to { + transform: rotateZ(360deg); + } +} + +.mx_InlineSpinner_icon { + display: inline-block; + background-color: $primary-fg-color; + mask: url('$(res)/img/spinner.svg'); + mask-size: contain; + animation: 1.1s steps(12, end) infinite spin; +} diff --git a/res/css/views/elements/_Spinner.scss b/res/css/views/elements/_Spinner.scss index 01b4f23c2c..93d5e2d96c 100644 --- a/res/css/views/elements/_Spinner.scss +++ b/res/css/views/elements/_Spinner.scss @@ -26,3 +26,19 @@ limitations under the License. .mx_MatrixChat_middlePanel .mx_Spinner { height: auto; } + +@keyframes spin { + from { + transform: rotateZ(0deg); + } + to { + transform: rotateZ(360deg); + } +} + +.mx_Spinner_icon { + background-color: $primary-fg-color; + mask: url('$(res)/img/spinner.svg'); + mask-size: contain; + animation: 1.1s steps(12, end) infinite spin; +} diff --git a/res/img/logo-spinner.svg b/res/img/logo-spinner.svg new file mode 100644 index 0000000000..08965e982e --- /dev/null +++ b/res/img/logo-spinner.svg @@ -0,0 +1,141 @@ + + + start + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/img/spinner.svg b/res/img/spinner.svg index 08965e982e..c3680f19d2 100644 --- a/res/img/spinner.svg +++ b/res/img/spinner.svg @@ -1,141 +1,96 @@ - - - start - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/src/components/views/elements/InlineSpinner.js b/src/components/views/elements/InlineSpinner.js index 73316157f4..2dad9ebe1e 100644 --- a/src/components/views/elements/InlineSpinner.js +++ b/src/components/views/elements/InlineSpinner.js @@ -24,23 +24,29 @@ export default class InlineSpinner extends React.Component { const h = this.props.h || 16; const imgClass = this.props.imgClassName || ""; - let imageSource; + let icon; if (SettingsStore.getValue('feature_new_spinner')) { - imageSource = require("../../../../res/img/spinner.svg"); - } else { - imageSource = require("../../../../res/img/spinner.gif"); - } - - return ( -
+ icon = ( -
+ ); + } else { + icon = ( +
+ ); + } + + return ( +
{ icon }
); } } diff --git a/src/components/views/elements/Spinner.js b/src/components/views/elements/Spinner.js index 4d2dcea90a..43030d01d5 100644 --- a/src/components/views/elements/Spinner.js +++ b/src/components/views/elements/Spinner.js @@ -21,23 +21,31 @@ import {_t} from "../../../languageHandler"; import SettingsStore from "../../../settings/SettingsStore"; const Spinner = ({w = 32, h = 32, imgClassName, message}) => { - let imageSource; + let icon; if (SettingsStore.getValue('feature_new_spinner')) { - imageSource = require("../../../../res/img/spinner.svg"); - } else { - imageSource = require("../../../../res/img/spinner.gif"); - } - - return ( -
- { message &&
{ message}
 
} + icon = ( + ); + } else { + icon = ( +
+ ); + } + + return ( +
+ { message &&
{ message }
 
} + { icon }
); }; From d3db5fe77f8cc17af9b53c040d4f6bd4592a16e0 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 26 Apr 2021 14:10:09 -0400 Subject: [PATCH 002/174] Vectorize mini avatar uploader spinner Signed-off-by: Robin Townsend --- .../views/elements/_MiniAvatarUploader.scss | 28 ++++++++----------- res/css/views/rooms/_NewRoomIntro.scss | 4 +-- .../views/elements/MiniAvatarUploader.tsx | 7 +++++ 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/res/css/views/elements/_MiniAvatarUploader.scss b/res/css/views/elements/_MiniAvatarUploader.scss index 698184a095..df4676ab56 100644 --- a/res/css/views/elements/_MiniAvatarUploader.scss +++ b/res/css/views/elements/_MiniAvatarUploader.scss @@ -28,8 +28,7 @@ limitations under the License. top: 0; } - &::before, &::after { - content: ''; + .mx_MiniAvatarUploader_indicator { position: absolute; height: 26px; @@ -37,27 +36,22 @@ limitations under the License. right: -6px; bottom: -6px; - } - &::before { background-color: $primary-bg-color; border-radius: 50%; z-index: 1; - } - &::after { - background-color: $secondary-fg-color; - mask-position: center; - mask-repeat: no-repeat; - mask-image: url('$(res)/img/element-icons/camera.svg'); - mask-size: 16px; - z-index: 2; - } + .mx_MiniAvatarUploader_cameraIcon { + height: 100%; + width: 100%; - &.mx_MiniAvatarUploader_busy::after { - background: url("$(res)/img/spinner.gif") no-repeat center; - background-size: 80%; - mask: unset; + background-color: $secondary-fg-color; + mask-position: center; + mask-repeat: no-repeat; + mask-image: url('$(res)/img/element-icons/camera.svg'); + mask-size: 16px; + z-index: 2; + } } } diff --git a/res/css/views/rooms/_NewRoomIntro.scss b/res/css/views/rooms/_NewRoomIntro.scss index 4322ba341c..b75e950361 100644 --- a/res/css/views/rooms/_NewRoomIntro.scss +++ b/res/css/views/rooms/_NewRoomIntro.scss @@ -18,8 +18,8 @@ limitations under the License. margin: 40px 0 48px 64px; .mx_MiniAvatarUploader_hasAvatar:not(.mx_MiniAvatarUploader_busy):not(:hover) { - &::before, &::after { - content: unset; + .mx_MiniAvatarUploader_indicator { + display: none; } } diff --git a/src/components/views/elements/MiniAvatarUploader.tsx b/src/components/views/elements/MiniAvatarUploader.tsx index b2609027d4..32ef0d4da2 100644 --- a/src/components/views/elements/MiniAvatarUploader.tsx +++ b/src/components/views/elements/MiniAvatarUploader.tsx @@ -19,6 +19,7 @@ import {EventType} from 'matrix-js-sdk/src/@types/event'; import classNames from 'classnames'; import AccessibleButton from "./AccessibleButton"; +import Spinner from "./Spinner"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {useTimeout} from "../../../hooks/useTimeout"; import Analytics from "../../../Analytics"; @@ -88,6 +89,12 @@ const MiniAvatarUploader: React.FC = ({ hasAvatar, hasAvatarLabel, noAva > { children } +
+ { busy ? + : +
} +
+
Date: Mon, 26 Apr 2021 14:10:30 -0400 Subject: [PATCH 003/174] Remove spinner.gif All spinners throughout the app have been vectorized, and thus this GIF is no longer necessary. Signed-off-by: Robin Townsend --- res/img/spinner.gif | Bin 34411 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 res/img/spinner.gif diff --git a/res/img/spinner.gif b/res/img/spinner.gif deleted file mode 100644 index ab4871214b14361e4491cd549bb05a7e49a190ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34411 zcmeF(S6Gwng8%y`m68M!dezW7gx)b!>Ai~@I#M(sUDSjo-O!65y%$4AL``Ud(gi_8 zjf#jJ6cropto6=%XZFne=bE#<_jT=@CMTSbPk#6JevVpM=;->A020s$08lVAH92*B zbll9utf8)9^Z90gUx1{zWKluU^3rl&Pv73Bz38ZDS7%oZH4PCVk({iYnY%L`Z5{94 zybBKvudJxtSl<{K9{Kg-S5jhFFH2b)!kJ?OGD(4NKRhP%-l>zSI4^#@4`=qS6;2$c(O5aYvk9# zucQk}J|}z(j0|k;ZU6lF1N`4#Vt)|!=B_TL#*XG{$~Xv!`L5V^C>tA~2PppIlmGZL z0r&yFifE}Qba|>_$x~LExIX2i!6y2=cqGCM^CAP4n(n3Jyu!NSFvv>N16IVR> zK)S+kG0iG(gqDN^4_rc(&HHl8$u@Tx1r83;2?=tqhteQ?cu=*`ql{MI#U?ztF{w4< z&NqL3XqeUOb~)Wk=*}I5sVNx4Rp#aV@bJwQTB|4jM-iU4O{6D0YzwF5T+570ie28M zDjBO12$$k^O$2bLsV-_j$D~%YtBjIj-L>Ju!&-->^~<|+voW-F#Ym3j`nw_I%eo-6Flo6&LK znYt6?&xaIRi7F>=VPu+8-BRM&Qj0*3b#-LC*^ul$yam~y7oYBTnN|{xyu7Rpa_x=Q@<0RcTyzT$glnS>hCED6-&O??x(V%_Tg0@ABKuHC zW4M+esKtf{;L=4dpkE6hrf6P{91Kb;muw$ey62!HmI#CP^mw&aoGKw~qM`9-bhbwo z-FQUsG+_dvP@~Yq^2&%b#>-_p+;C&A%c~Wtsoaf6Uhl(oiSTV;9lc%47J!~Zq?u7| zcb=UOHx}pGwqW16j3F?74QB{gbgv(ZL}vI<>lGcl);n{YXhms+M4*@n>{L&APY1`*V`Nt0B`SGY(eZqLnf$LDGcX$6iRHlZ?_$fDN_iDfW!F}eFhg7-G7h)r4&dCD)d-x?7Wm?SURQT);XO{tv*Jy6Vz<> z&2nKc#bi8T#xeOuM%dhN0sp+o*uiGstDWyy41Oo3;wWcC3PWXr7P@)r2sODhx5jaQ zgOL3=UIw8dzXS2@SxCNCM8o5sM+e&voCMb>Bp{!v`x|$RG{0N&m&+0CsfWl$IHIuI zbK$^Y1I|3wSA4dk?J8a*u*RU#BI_Aj#eLu_RXkY|h6NBl_mGp(kU{boR4KzN(SiiI zBE<^9?A-$^4%Tv2?4-)t*0Os{jq>q73(M{?L6gI43RhGsS-dt{!yzO}ldJ|t3(N};@53#h)iYiS@s=H9f zYNzmEhEXs3_W(=1R_JJOm?G_7T%F^`k*Y-qn z30`@jwXup4ICtkPbV`18Zs2PdmLh*u{E^$L-Z$uCtFixQijVjRPq*94&;XUh1Mss$ zzV$v*N}+0?$-bTcHnb%zb~O&c-pMjA+0 zAZ?ZKQqx-R_~zO`esI*bNE(u1J=TO9ZLWUfYY5mnx3$pHv%-p2@{uy8%qM5AQ&6^%mrTTZTYp;>&d9MJc`(dv}D2! z3p6O}sC5dj44FuCI};c2;>D8RH+BK++r;ZL9`ZSEw>jKtLyou4ehMy)Cg;CJ zJQ-i#XT6}@1>R##`O@{hPEi5^XD1{J5)v@&ol(K2il>fe@0BWN$i>_In9p_o+EOxG zT76&P{>djr!binE-<;P9-y32Hs-)NY>F z5yFEQd$#uX^4Zaj)(c-R9iMq=`f~z||J`kQMdZ}Ev;Ez6xdZLI_|-atM~lr>FNaXK z-dwrveN)iTx9eN>yZrPr)&!F{$H6dJ95^yPoetTD=;(4Z9Rd=uSHO1swA#Ol~LT^SQUKALmDw+;H!1UDkhN zU9{0LnzzICCila!x7bA>FMLc_>`UIg{>FPx1|C*-E;SddKCLPv-?a)gE^XG{sz`kM zz@Pm~Zj#})%=RbV=NzB&>%G>lz}~DYJd2>e5kaKi{9!(OHR8?svHM|UI^(9$X$9P^ z_DX&*ibwfj#j)p0nEB+V^A(ArH2iCi6;AfDbIYGXpY5@%nt*r?l4Z^l;q7Of-uKRi zm#IAcO4b9h`h_h>+A`Wf9#XYcXG5z}j^TfcW4YFETB8n4d&;jd2A+I@F8 z^~p)4SNr#>CkqEJ^70YDXc7(Db%)LPU-Ucj@AOOiKlzr2i{~%mGG+U>a+$$gl2LVen6}Ko37$%;NHo9(KT2#!LGmTpLAuvHbL+_Mtn7tboz0> zZTL01aNW;87h&hZPM&loWn_AvI+qxBPAaRQ(B@RU^yN@kQDv1$NqlZ?{?*2#B47an zSp^M$_Hz-rsSRE_Y;c4UQoqR?C2KGW5)~87KAD>!eZ@-;!L7E)t)zI%Q+rHw)IhAu zGbgkc-tR8Bw7sZ?S#^gQfHuZ=p!{(wG)t;MZMwzBah9(9MKaN#|3cNW=pf3`)+DLih34SXj#7O ziX9{nT$Vsg0c!+_EhITbn$j~VqWk6%O4MLKJ`{~XhlCi>#_}#Y0WS-k>hK^DFCvBH z_qj;Z3kGJTfH-(vNv81gJ>OF}5PG;tq(sf@6ogUDDf&6r@g3!{+-49cT zKgB0r^yPeBIc??ik1mL`f5%!aSz}z#^kC2p?}WWvjm3sHTn0 zgI31xh%I z!!iH&9W3JQI3m2v15e3kE4NRzwT42iiouif{6)Al*j?C(^rt_)3qade)({Lf%3@$$=+N6n z0-ThydFXRISU7qS4$CkW5oEh#e|NVqH2}}4UYM-x9#xRIT#x!B0G6)9M$u5PqbNQ-Pb^BMKini}$MB>?! z-GF+b=~tp~q-D^!X zfYfvsNO{j7fla1KHGGp$y9jw!y_&(WcK}PbfrMzWcNP~3+wNsZHg$3)qt>Ddoe(yt z1#1s}o_QX4QkeMtc;}Orq%IN+&!PB+rS;Hfs-LDLOVudoFqDsqX2qX;MM^o#t=4UW*ESWv`(Z}SQ;0DuY2P-oqanjRkZIb=X>5!&t&0~3%9W19y(k~ zXCFQ0Z2(FMxeC}=3K-tO$tH!vlTq&;iCF?yALZ7uO5X#S<#}SZ14$g?f)LK|wO-93 z6VLK1-kNV1qo)#{vj^?5Y(6gmaan*t%2eOOKF^9BeBNKE@=ArzbW?POx;)j;4yIj% z9uoqH6N`R|@F8b18g|tV=fE(_YuBBn!3Z9rZJZjT-jzm%Pd-Xf2%uG8;h|r=OgL%4 z?aX3(0?eX##quRwfQ^gTK~yEeIED)9^swQWSi;OpJ3>Xe|fo_+R@tmk!Cj*Jf!*?wsZmIeOZEY#&ho{2(7#-Da^sD)Jh)6>X^wiv^ z9J$0mqetu281BuMD?{V}mQQdFJSf7Z*2b&A*WjL;W8h-cG4o2na5?8dPO_Ev=%RGtkB2r}FU9JU1jr!u!E0bRQVjDX_`&=^yQQDsejXz%)$a4tYDN<+LUW%=c zI@3CO`hNEfyir!!vzxEjSIa1mV)L*!ACL1oh;3X$ex5An+44Nl7B(x|k~}*f;^g%9 z+v73#Tl$qUBD?tYyMe3dpr?B8I^wve3*eTS^Qb$%fAGtoqxFxSPM?y|6E(7EmRDqR zh2^OH+Hyx5-_xHamEI5haOA1p+2S`}a_;C$GJeU39)HXsg1ecS=W*lZm#}XR9aVM6 zj2m)bhflnuky{9s;MW&{?-Rmmd%B?bvn~!DBaafl#{E$pNqCRA)_(yGJ#zQinfEy* zXO^Ccmw) zDw#I}V5zJ=1>>@@)E++E?70y6dlKMcAq5f6BfedPd%|>sWfL9@kX#tVgCYxa7&(KH zaGw7HpQV>u`Z-|%78C_6js?pB=ij5D|F(7-{g32j?zzlW_iyt4?cKjt-M`8EU%mS` zcK?TAm$~XPx81+V`?q)h+IIgY?|=2~-`M?6^Dc9({hPd&PRAVu|0XZjLPAV!(Lc%C zdPbYdMw#0g+Nem0xq0Pd;M~V=h|mbVa6N?!mooeXQWD}*^v+}!6k4A;5gr$j&m?bl zs$N7cPgSF}kg6I#x45&nTxv~RgEve|j~An(+vKmn&F|o#1-n+7&@Ft$L!4K7ejX!q z(+e@Ws@3I@U7Hhn&kZ)mBE7k(wd^4|DocOQFYiTta=Et8LvKNvck}1B*EW98v%xRc zQHt3@QulW+XJwpcnJHF~0oRPl$gQhDuC5du}yA4ZWHVy`uA>wpT~;!Q12^K7W;ja-XWWzVcuOg+^+3R;%oQSErk zI;uq|U1Nn<9uakte+utNQ{xQ<^>1V2RG zp6X)Jkpy?0d@qN5;LZ^=<|y^xN-2-*s!H^2c8$uz9^yEOm-mB<14@eTd_@cV2y*#2 zd{dHq_sOH1c2R||eWhY{-Cyz3{Lk$Qf(8}|dy5beE}?MzgYe&kox{9{Uv3MhOGQCk z_FpAyM}t=n@_X;3xXjc%I`tk+<;Ro=cplM?8Ow^~UkDAS-oCtT70N!yALN zb=I5FT$C?BwoNr%dzmU0EeRGGBs=8ClHKgYQD-q4$B!&| zd6b7Gu8a0k{O2`jsd%jzk`8^{5&%w#PXw&eONkwY%$3-qXwx&we4f!*vyzff_)6S?)xKh zQl|a{wrSowU1V$mPo7i6O`e=wPz@k;*hRx)uGSi}ZOOf{bvtu1&p>67@$)#5{JIE+%fvJ z3sD9dIJj#JwI|g;)?>2W6j`6oUar}!m!%6> zS})b)7TQRP54QB-RF|aWG8z=c>iq zz11>znvlskN>^-_o3vVAf|+DJlYYs1^z&o15)@xAd#QZV53ZK0A)(Y^x`=IJmU!8KsB}v-X5t`h6Pf4XTpkUpN@~N-=SPcH?Gb(ucaesKD zi!oeH&qz3l(Rtxs-UroU4Z}Qva944&y!q$hEIvz&1!yYCzuRZKqxx15YX&gU!bq z!pt3AQNOZpXF8G)%%e>Rk0#78KJ3)@3Rls+_I_IhQI4x`QuYNAkTyFo#1qfph-+h4 zBZ52x6`y8tV142mg@+B^Lf|}$p4wA5i~u^BKYC~ky}!jSj$L1T@D-{&1n%C7SN*I8 z8Ff(qBw*(jt&wnQKJu^QMT%SgB88|x2Ir}+a3B-mBkt!j5MIE2)3@+dLw z$d_19>XHeEvehPRCJ?4A3$SQXlR35ot)SS?h5ER3jKSAyGDJY4JkC;0E}5w5Ln=?h zgNm{cwk>Z-HD@Z74&q@ztD+{TG#8g5+(3f19XC$B(v*BdX|O}U^f*%M@iT6oVsma* z5D0!OOY0Dy?Q2n!#+yh($#+x}v24%WK)m<068WZlJXq$w5cFxd!1a3Y1A3hbgM~^r`9h zcEha2(vMp|y#Ynl(~{fLH6D83ght;`-2jPwLs-o6pUlo2L9h;BZguXQQlUdP-M>Q5 zPDhB;oLPI}>^)@?@FFZbN&l=xt2#gBddQy-k6rPg>xNi57?~F&?>WdNe_m0+-UD(N z8+Y!}i>D91X64?yrWN*bC*fQ$q*N_93FPCNb+@!<5O&rYy zg`E)s4cgy9?c5~wDIaf{f2ngR-!Nt=wd#5%te1E5z^5?f8+0mDmZR3v@s79j+%+5{T zZJ7sy!a0FML?cn5ArOZv2?7&2$@1>~Q{t&*e$3HQ$ zsyT5-&hO8kNs}+}frEoD1~6jXx9%N|Ju~q6x9_Fs+aIerk^d&xa{%UFf;Idf1^XYP zg#WbIGUfSS7u)}Ku4O`(IjS=E*1yw)|2W$IT_^mf(e@wF!$0eU|2W$Iw~OuHbpms= zz51`w){@&U2=gz&ss{FYI`TcTvr)b>zmm-V>x=UOQ?P9k)Q=?(ljdK!(BdN$&!?sZ zgvx5g9hcQezf7^wJ`q;TmzQ5&VF)9g%!(+xQg0!|4@T&+rFa)+Mik`+NFi`yd>Fk3 zZ$({%I^wp%8LvVr>3E$78%SS&R$oi#hNn1PVHBaz;ZYTqRny}Mo0}KjN?dl+pg(*s z?)Bta{9QNM`Ij$2%#$qs59;nvA4k89X7iK#N>&sV8vN3n-`G+ zNg*sl|5b+<0OFPc@$84u1r$DUqz@MuKQ#@ORB3Rn16?`Lk63MTi=4=CHc`;1d5?Wf z6@=F6=ezbzMG=y7MSag3nAB|A70ij7z07f;NYTgyjo=x2`~tfH!LrFcKUZ zi#f!5=4qevBDwDM*jY*KhM{(|ODZ`=WX!KeK~HAnFtkKXE7C4k>^ayEWXYH^mw(CX z5br1!YLxNvP}f%xNImr?BHH>#^vu0?u&olI(^|PJvX)Id&H+CA z;-DoRF70GxJ5X8@jWYUH>G{qkas5Q~SB*1I2pdgUhpH}%J?oGNeo=HF*K%JG(pk|tQEEDeqT_m0B8 z`R-nhqFX*Vj(G}ep}-=`&6eCoY#qsL;T2dEpO8VUyS(#>T2uJX*P1yPWCH~ZeAw)=Szy;_|92xBA$zgr(hH7C1xtD4;KUa@M zN+GC|cTR@`V9j7B6F9#A+~R%2I1uS7MI-br9$07fHDuA5WL2DHb8PZ>V1q)04&3c{ z1l;j4h6#FxsnD^PX9M*G8}ytwC|r-jXAZ=VgFr50#-<$aOsZai%L@yV7qJ?K4N^ zTTyeSCUpOVt1|*@#ea?&1MThgW3X)D%c7956OTzJ{ZCOyUHcep0*qV4VYbltnTN>_ z+2{x#Hr))-SVc6c__!tH>%GN~uH!Vg-VPa^^H0Ibg^=JWcd>Bo7CJ{1I)yEV9j1&Q z{bz4r@T|6LwT^Fq2+tdvM+RU)LhICYosn9Ovz}u@9q+&ekriudQfAW~SaW|2jiYqR zRC=AN^Nu7m*QC?zrFsSwtZomkTpL$bb^`IVMcYT>-&tPm3DG1$C*NI`QlK7J^WG&XMkVN%gclnH`b& zWy`gw2(-CzNhC$l$)3-!cfz(}DP1idf|fW%b$3>^RLA?d7RBFndEUaJ_^UyAel1U+ zE*Rb)Ue9T>Ys@KhHf1V+CM#%~!c%~Q&5r;Y&2qD$)@RFkXPUGIr)N(me!jwG#z+$& zPTc?;Ns|vr?6*=6!D~!}i#NSf z0BZ-%g|kovwAfmM4jS^Qw-)#4$<3)rs$7Uw+CI$}Fv{j+xb_KE(c%Ml@l zAIqyl*rFmqst>uM(W^StLf?>x@DPb(Zu~pgxSJK?55G$EyeRHyKfi!yJwd!DDfzO^ zE3n-v`!3{T8omQ_%(53X1t4m)ai7hj~;0So=N~f1wi{fZpV}h@X&m|HR8RL!KJI zH`optvs>>xIRsb@L&OR&Z*pE)zrD^P$J$E~`~@7a^p!aDml-?8(ZzdygNF=jq8mRZ zy*{LYpCWBVv-V|nl5nF?n(c)ofE!XyS8u{9 zMGf%Z_gdr*Og$Z}=CUx@v#SIJkFP)ccmc#AR8|@;8w`73F*;Rh z2opu(Uv$HxBNy-Ej!(vH$2MI@LrI^GJa1lms7#5IZt~-+?x?AE(fKyhx$yDgpJAu{ zQ)LtrZjyut^;z1X1f$w}hL>^zbom>k06UN231kZfoFw|PJuZSc)d;jF;aiif%#iBz9RUkG? zfmnM2ddi{e&ada$I5O_O2Z*yd9e$do>jpi^dTri>J(Q8mIfZX#@C<*b=gi!6sYXAz zZFE?ilyEQXBtj^j)FZlc-qaKSFjeJ`hzS5TNWn7RXq$n(aU{$;+Rxvt;D6g&@&B&X z^uH1VWmKhf?w^wFS%j|0Z(khdx%$AmkRAyzs+*_GN?O&5CQ>)CPmKhf? zhgW7%%j|0Znp~M$Wfrwe?=pv1W>L%RYX1rim}4t5t!4HF%=wj>)-nUzzuC(CP5J-D z7s~+&pPZ7KmVPNCGb^1FVF*!B$u2A^E-5W5Pt#XIT}3I$RMa;#HWl+<)kJAlDRDM; z_w<$=sk%DYZrC?+^CoA9QhR6h;ahj6>Sd~H+D56gwK3_IrdHI&ZsDpoe}3aT|YmerEG&zD&+l2?-- zH-^qU57f&~5gX&Vf%P8V&0K572w(By{;KGbcA3p3x=2QC5grfI&m7KBmzXsd=98Zh zi++|>QRH4UzSVlk$mU*UXUDGkVm6m4ccN`K@VY@%r}OtC`u&D^8wtM)k`Z=? z%0&{B(5rD4urDfVQ~m8)zHpiS>ZPwS3VMs27iXdp*00OTKPf3hpMKd+HGgj<*{t^7 zNjv|>N{7r%#=U{aya#t+vr{_#^mMDz;=xgl0DN%2oMW)Zo{3ttBOj)9Zi@XUYF>^1 z?#yD6VcnIoOwBRz`QIh8;@Pi{ikM#h&M$aASSm^|wL+@L)O{?_(Kv)dyeB7K5rv;U za#u8Ciqo_6rQjJ+d2vi?HPzG7b-^uu3MqJj$2mLO0I1Ux-P5*ol^P&I#P3fWm{mIJ zj^FVYe?;H5N=cjXD3CdN$nVgF&{mK85dCl>iBi({O<0vz)ER63LNuz{yGP0uC@&tt zjRD*N<6p6RwC6OZ zD372mK5c|L#-VMnGm7B!A+}NU$D686G;`EUp~Y|py#WSoVrO+&<6eeRQ9t$cb}x7TsrmYOM{#2qdRYLwNUbuN#Wqw6$}> zd`#+2*?zyC(sKzY6qfbB>nAge(7gT**l`|i?_ZfDS|Pqy1i9-q960xohV+Zt*{X4Z zp^?bz!(p8x$e)x^Deh?f{JY#bnU+3d!?yQOnv&3pJ;b1obzs`L@MoUql80M81OVO3Zh3p zbR|a5_jW(H|FcPF>_01A@^v}~2T}ge2kIsK zuUho^c%XE`!P>Hcvis{kh+Ah73McyU)XGlCJMU&WAwdyZ*p8vrE-ZluA*;My&E~nA z8Dd*6v={;)GBPB~Uz9x9Y{`OWoG}z8*1_u5EFgQkm(M{P4u6w1M{E%EgTB}CkFDv8 z+mN{Sga3HdUdi@bA%FU7&I=f9M^i!RlJZQ&NYr{v~q}4_0N2JN?c!JdOk3! zw*bycTQb6rC9f}}<7|sHFGHFj;fzZLIkILl@=k~N?xzXNwpeK2UCc}DOT}(3LIT*z zk*9I`7%CYgx(7s)p4UC5kf8zS&oWdGuD@s?wc1p*LZXLJpTt7NJJIsGTBMbpAFgm!omZ-8S^ieraU1e? z5~|1By-B_Xj9VG!FwvYd|`$Wx$ z6C089)_RT)e{13)1;vUkE{rvEPnF)UGpvi2dU-59ntiFoWOgB%w>k85$exq^J-uk) z@HpQ9-xQym_Yb!xs`V_Pyt7c`&avLFeNABcy9Xsveb+i&g?xC&Swd^TYs$Rs@;jbV zzgFBQdQwA#Y(T6c(z%BvNgxNiaTFKhF};n}lwC&h^k%EVuzrEFGQXV7fHlu>JPWdh z4AvWj9Plq|x9i3WoQ5nzeo2o)!F)KV3HE`F0pli;RTcHdE%T57gRa(3zKK@vuJ4Y8 z3+Ab^J1;yt^peCP9%P)MOYoH}#)D?7T0wno6)SA@A*BrCoKhr&zo-PQ%?e^MsU(Qr zC&4sW0cmA4=%Y0aQaUs}o?+Z!+eerVX)-=R-T||1?$3&Kg-W^OR_S-~aM7XECR`y$ zQV=^!$cd;VSR@I|Lx3ZOXl#5qkm<2bnAkv1Ui%D5EUXTqm(hU;y|?u#66ILWYFy*r zk!%_bz;tK;yX$NyPvtV8k)hGZJwvJv^wYBr426awUy61xpb3Tm)Iy1XJ{akObhyW~ zIpvZIPt)$YUhQX%x65FO#B+UC}IAMiq721JCPAr)cE4fo+IJNIXOq6G>M8`3BztbPne2K-&+7QguUgIOSDi5>$jg z9$6rD)Z4u@KC<&-gX8-ksyciG;tTef z*mJQu=n#RaaxMw&i;^A}lc|7W%{kAl2&E{nBsBm_2zw@Tdg4wfRF+k##$O>AybQ;v zP+|Z)SlW@0g}v~?BeI?fx-|iY6V(qFvo@@TPI!=&j+U{3ETd$!Q>Go`!9$^H87U<= zh~&`=vbb|bI_yXA5Y6ULF2~eqM=-7>Jnv3w9|hPQ2$ggMI}4x`&|vu*2!;_%!La`S zqW^y#`v0F1Jwj9oqW_;n52qchmV zS*3cf=wV--@KNor8QD{!ujUHjg{bwVuXTd@yxQ@@tdZ-5*I=`jvNK!kFA7gM-po9|Uh_9V(P_L47tH0-$8Ek3?U=N7!6`*L6jSpLOb z9o(YIyNrT5n;ed-E^^PXul#;#D$yt6)rr(>gB>4lqUhvR1@xi1$4(echG<46_UJR2 zRn4YS6FH$CTJFlq_CKKxBw9J~KQ+tMJa}oXkp3*_Vfc_`$ux_?4AmsscX7}fA#9nz zW-FNZNleUw9%qx6if=%xk9&qpvq)@G=#_Hs(x+Jt`pWXI6#b$W`9b@+9K}@LQO8i6 zBRiuIeu1nL<)kI8yX>$4N^;}bpoQep?2fo{OHUy%)^Y}Ed6&f|av~_Qw9=8@Sn9{F zaJG0qB{#oH5#JylIybPye#8CKD0nDcLt2V+k8+A#cbq*8j`FD^#MF}uE=$p%YonB& zB-(VFrTZs3GHO&_rm5tv*QnH1XXvVoA<}vpte{CXr8bNJu9kfkZs4ldM)wq(czx8! z$9z^E(V!~g;v1}}`vu`Lu?O~1rl=m}8{xfW4=kqRJ9pREsZRaGlWIqRiD0pKFmi|1 zy;N?>QOlL=X#3JP&Bh_?mt(JD$e~YT;Fq^Lx@m%&G<9k18i`|3s$X{0U0Hs;4IdWt zKQGq~j#;pcVSl?pLt-47FvD|ENOc--Wzrk=l~z5s^FGhtVk#~CtG+erD$-8Lsfhe4 zQ&*3r_8e8<>5P1!Y??HKs_jhlvviR;3#^w(%!Z?ke}3vL;K>WG=n;p#&$jv1kFk6|2~54oAXi4-;PB z-07**mHc?0MgPE>@#)~z7H;%ulp+4`mwiK$-0JJ9g_O(tUmWSrCeITFeN-r%FAjcv z`^dZrjI04zEHgQ=Z%CZ@8Zh@14fbSSLtBT=#(|@=JMSd(&0yh+{v(133n`j4)sYsA zl)psp%dSrCL&=(HaU}bm$1m!Fm9T&jD~W~JVtVKU?$VKWV(1vpQ6PHUN=G>F>gfq- z{x2T^qa6}kqo;|yMP!zzVjUZV2-ze^r4tY@7(zR==}PP+Q`>sxXJTsa zioQIEvN+J19Y&h)0rZz^? zfG+ds;*nQdNpU~-7m|-kH^5gE+pMuuhB9QVuNL(6UeYvXzZ#s$W%hM|!`v9oXWSUM z*DfN8QzYshTb-YC$8Zzmqd)+dg&@bGj_Nw))d&fWx)Xq1bw zqd3@5@IBI`GfDnA5P?^vP!RqN_B8W|Fna&4eu*)%tc3ja>dN@B0W;vq-ITZTbr4)^ zRN-d@a1NI>i)UW%rMLQ-5Gdo`t;UwqXEv6qp4Y%D_5p<7On_iLVjAKOV6zySRe4WsxR~AY)1FtZax{tsOA~gJY~vg8r4=PDOt5#p36|EUGp7;PsAa@iFLVS+h7}Eo^#AZCm zpm&-=T4lJ}&sRc`sSnX}=>vT$cEQ+j#`;hRo@4oXvZk>2iYx)}UmyP9(My8=dGG|r z1AOGTS99le5vXky>5rEfUFkV7*mtTbgPo^xj4J~Gg<($zmL7v=F$Ql-aO}iFp24Jt z`suq|G!`6b1L2@~@9;q=31aX-nq?HK{KQu{NzYI<<9eGaA;nzh+;hq5uj1gED2_pZ zbVoY+j@(Rm{rrzDYDw%I+3|f-Sk0PpH!yR@-j_Njvo85ZdxAedx#`}ci`*pLv`9k8 zp^a;u-50bj!gc{(pMzhFrzQ7T%xbnqkDTX14}QNbb1TNUkMxSma`(VO;f2N>Pgt=| z;**G5amK%Ba64_4oJ07ONPR(w4(_bjPFH-Thac!PKQ6-r_bp21%7-!wgLU7|{YlQ` z*9MpQ$jYQMt`23;BR1cjfBtaJxRN#|zlv|YZk!M)eDO&MQR^fB$xk*#gRe8R@;L3Q z-?8;FUZpU!9~`gd(K)&C46mYbD-w8SgEx3zdB?0#<;0)8@yC0uO`vjYlqE~p>`~Sl zmNRPT_(QeCms)X>3km6z`29Oz79CavLf~psBqj=EKum;E5s-{n*idZQ0%T#(U%4iN zV;9VZ1qsj~%Dz}~r*s&vCG*z~t1ub@$3m1zmODIA5~XoSBH37n{f!6NB0jaeB*ttg z)wYFKd;g3zUs}Ts1dLC;)&y}cLuxvn-M9mOwwg9Vfhdxq4S`Ryf{(s%-|KqbQrYx7CP0Rmw zK`=7iJDy)Q|DUxq_s6i^H+2I4xFFacbMsqFF0+;v43`ayI4Ss#T3Y-hAvBqonO|Ui zDk3EgK`FdaDIQl=Qc+odRFDsh&}4J==1w8!)Oq081Z2VLS`Gekn&Ele-r1+gwcfDf zVJE=pH#`r=Q)N3migUWXaAj1bx!#khCl7m#O(b9Tx>b^J=7}30bqnFQvb);jlD+y2 z_Q6$}G0?aaXkM-B@9s<(;_1qTU33W=@7)WtxJmrh1%&e~APh6C)Tlo{wF(wTy+u1) z)mj`9MoJrtKiuA4N`}JT`?iB~Jr9m%s}*PT-QhUQ<)Ar#6PGwHG^Ve~3b%8v!AuZe zw77C@r=7wGYi2}*eU6mpqON|5HxR@%dWdsfyZX6crGra=WpE`UbDVdz%O$-4xi?S%xi~xCO26cNvMN+O%pb}QW4~2)nPy|%Ei7enSzU?j z9xlGHZ1U%WLEE`ju7(C*^NA{zk9we@3lm?nN__DG zV`#{N}0f#Iaouw_PNj7GW08Jwjsf}3SCx^{qH0s2u) zY>lOViH`SdrN~1e;Et@Izz9abL`cN$YI+r>wnkc93n=o(2x5c$j%IDOLDhDPCglZw z%lf92oUMsZMIG=ZEj^$L+uwj9Tc*AQ>u6FC1*}>eKV&3^O2m?j5W{;Bh;{e`Ejo8Q zGpIR|=J~LUdu6ir+Km88aL-fgv)dwaGu?AgG~NF;bK&pnwRLB!F~9dJ&9t6qODNIuk%TQk9NTKoLH;u|u0wB?p z=gDzB^JBH(#7EVOs&?!jZy8CNv1>KAiD00n`u@&(U6V}LYh(_Yv?d|?4rqP4_+@PV@+`dDRqNt(&kXO=5Qdk2@4Q^rgnZ43j(}g8l+%_|b z_8B=A`_}fzj_8a9^bKFE&NlE>#?5@=)j^(4&(fbehr0N?UcscLy+4~c0Sz1tycsb>=kb1EuKL%Jq#ZS%#*Ls9Nxgoi>jFF+JXHEg|EG{856B7>v z5O1W`QTr9NA(9E3xyEjlx>k$E$T zGp3H2P7w2U`9YwN+GAcjUtn+5L3f&WDRe}D9(>Q^z1ZypDIpyDW@=82e$$UWqWmi; z9C6x?n`@<&K*!6)tNBAXw2MmHGOkH7a}fP^}vkeoXAb|bxe?ULAh*zFLXOc)IT>kd0+{Ex+eRn@a+tW zw5OtE+V4W7>p>DR5sHH1ASimwwZ}yc#K-iLtsI;LaBLL9KSbpm4w5Fx;=!*`AtpF? zerw8YJc)$f8Jram$HBfGrU5~PN1Jyda$IJp3Q3ZXTcRg9mG3P~0}G zdL%a!On%Y`DSb-EcmXdvCcd%ojCVYCz93%faj+5rV}TpxFh+|T zKMVOWW=xR%0-=*x7qV*Vj*)%^(F`Z?ob6nec^`$4S-YEwVJ|+hq>71$uZ8xFfhA(t zP^~3FEE|6u@qk)lLZx17TkzfE`v6!{JRA1-E`7+S4Ju9r@@yg?@I$Uo#yhq1-)?~U z1R33(AHvQ52%xQ5ZnJP>d3han0em`?j%|^~^Q!EHHF09AQ{E?(U`PUK^kLt!6)o&@ z=gVEA{a9{y@NThO2%yL=81uY;&RTD5@fSU&3GeTPk|%h+o0tJLXo7jji^~WVebAFt z%~A;Y#DWvC_42jQkg$3l+j5WRhl$ioPxB9bnrFaLHJinIsPX!Iucq?9Z3;5cuPHyH zo3xY9lJ)rC?R&uP9Kjk^RTHUJx8}PqEjGRO;1BYgoPj<8e{Xs_NEJ`N@JCT4U>)xh z&$~Up*k4F*(po$LS}APX*~1DHYNJa0G<~wSrtAJH=7Zu44)A#rQ$LVeEKH>|I$f=Y z%)}+6i0$n8snMAECrXCL&ZYPA)?c;xgdh5_Nln6cKr~eh1VT#Jcd>F1HKIw2R-%-| z#or_NoZDK@&Ngv>vV11?Sgz0}n3|R?P%qq!|Do)q5r3WyD4r8ITB|f@O?~ zBvz$=z0$gZE*ow5a_d&&jW2VYseK91KL1k-|#wwxZ8)G!@y<}q>;9@k23Rt{Rbh3tIZL%D^SndmEbfp)}OF5bB z+G?eM*(y&~9rd&WFS#vC`Sg8#(pjEo#)E?y~~Dd3m#cu2>O1|51qlNrB<$r^T(3499j zJdTfX*8w5EK10#rR%gL@oqfB2VzrzsuKJWK$Ro}Mi#FU)MMF{u6Tm+KIx;VGt(@yHVFHQ9&CEsWq;UCx*yqB*-;G_-uk8G@Ce8j<)eFHfr^ z%Ld%}@@?V#r{CXq*-2Wv+gHbQyTNa(ukY@xw~bz&A{g9R`^7gFcIEf>{B>_WX)Yg+ z+J&(H1Y0vfl4R=XWtqZLJxqy)GyK{r3_w|hg~IYfS*e*vmslj|h0OSwwE;9XfP~88 z(2bbHW36z-s)uAft(C#U-V_i}#JQ@7rl9OI&2F&|8eqXbmoyu}pzTjrO#iaMC4&_W z#;N(m!<$}LGt{ts&KPkJpJuX^bSZz5JsRzqVBH&^aR(|?^GfjH_ETwaeeAZtxZ(W* zHT;th=-!YrjAOr*vWb0?N`FbHhq#rlXe0hbDh?T1&<#=2K0WUV)826F!yBwR$FG5S zXLf7)4q8OeF=U+ey5qqPo@H7hLH4JMas1Xp!Al?FePg}D+dz@!r|Y zXQHbJNr8{w&=lk5-_y{7KpEDFm`RzXa&J8Vb}LiLu%C$1VQVj&;drL}0E`=1=NB3W zHSQ+`MbjSt#nH9+*GwmF!0AE zX(kNpPcrHr00m_2Ek-^+W%9j{CbN(&%9l*DdTKloQRGx+P(K1!qmm<)rYh%$S?K1Z zbV&rI?2B|QvDO_tX-B**aX{Jfc_B^EZdgorjA-?)&@uK}4iwKbgfyDlbtrq%T#3o7 zR6WY3-8{NgaYCe;$AeIqS}9cHq!{E3=Zv@bQ6Waq_YR7KG=GV=5cu=c*n~&+87}&8 zJR?xagiG6J=F}c}uBv~WVt+kAp+@UNu(K|iRukj{F<}P!{4q$6xWhR2ZWd&FPt9rK za9*)_2*`_X-o0cn7bH*gl)p@I51e|zzt4^7t$$~1;-~?VN{ ze$Kq$_n8h#pfX`f;Xt!Ta(d`i9p?z?n1%rP=~)AwP$(U)Y3GYs!SyBry-COokc^a3 zPCjRv{yp$G&Q%_M)%=}}M);JC9nej#YTyYWzG7Q3nV>c8yf|rUFZMN9-Mqm=7nrl^ zBtnb)spyrc6h3j{px#k&xibXd;s)a~e1=pK(MaY0IWr1AM zc%YEeqZcf5R&~n;MDr&OTqWAWYe1lj>}JMDYR5!fajo0ySboP{sQ zzr^NboZQ1nd(nDduQ?2}1Q#~y@%qNBeh?@rM%)=kly2SY&IlC3g#u<4=aZW@D4)4+ z2C-f*MxJ?2#-&{SKB>oB)OTz-!Ud3TaMkv8QVMbRWvx@V@(S&-?7tzut&ax1Vh-hDY6%>Rn0$27X@p>>;%P!1({3heYEu?L2=L@K3fR*m`l9_kTTfl)608UlIu`rFp2&&tNUB4aHP$qr- z0wd^~>V1FQzSws855skDtz>t;J~a+J+xrNw`XQ$FAuD-0``|Z4T;TYD@VgpU#*)1> zIS15al4|bpOXG)l*~fo2zrGNPxFpwpP}uF9a+hK6$B^gp56)Xz$h>#+GhO|9VDHk6 zP{y{oZ{MW@2W1@Z8YlHTi0AeeUdhhkh{G)!42CZy{~80C@feJW#%G6f+(0QaqOQ6} zLkYPDF^K-#^15bMEhDbxBuOP+?WB;e67JU|W3T@RXIxN_*Xec>Z=5J`#tZNAlgQqKcLGM!ML5 z{b8wKtiKP#(m7v`BaXS`(YTOd8X7YI(Yye4ypj`6eDk1YHx-1{q#7gmf>`7bRV{U@ z8$r!svnx}=q$yC5Ri;)`>z_p?)+IFu2%(TGZi8^Ci-V;PQyIm9RfMQ;B(f6a@u^ju z!w67RvgWZwY?NjxU#)u)EqB$_xG7B$hUn11R>9I}pw3;Tvk>quf~>98env^n_nhX6 zT?XH8oS>8Eh}X|BCis#n%T8` zB4;gM#NKKRrzzpB$8?^k32Bg;caLY{yQ1<8Sfja9OrReB;9^v*DErqO>olZaZ2tC3T6)wZ>L46eNTDr-b80trsKfl zzEv0Xu~5Fm*^Wq2V|qh{FfnY7pD$Exq|~0b#S-=C(lErr&r@qoJeg^1EE|L3`}o}X z(J0o(XN*=oi7d|PU168;IQ#bMvmB>7x-dmsM$y1Jw`d<1@AVq)1+LyFGK^Kc* zruity^LDBnGVvL|<}S>(o6tR=x?`_qzj6(gQ*%Iaq9IeKc^P7Cl^<63IXy@rOJ9j# zttqp4&9iP(wZ(@YAXm*^nt(J7bhf@s zA~SoxX*d_{W&l?_pyH+C^KmknU0=%Q-DXPpb2d62|DW@xp;a zj^D+KR^`hY-IFj)QP37;+qjD`D|C*8xQdYJUDuW&Y)GmKCd;VKrwk6`-0?At8%H3X zn}+`s)(|??=AbZDcEPz0Q}(@7sNL%R!+txf8y?9ZLe%Y>i*>N@f@Im7xnj4>6He?= z9e6&Y*q%K@J>{4-FKrYFh_XX~ z^=vG$O09;L1RtmW+34Q;c7!Qsz>YucxihhTkCuC=^DVkQPw>K>qp6}A2q6&)V5HQ8 z38{Z0X*&CSZ$&d7m<{q6eKfKDm`XFwSnWL%2*=Nmj-P(@Qg3%ue1xjhg9#G`e_qPRB7c@H{-XZs^aj=K9%DAYRlix592yihH>miGlI}Tj#^4xEVCYQrJ@fEj z9ZhP_#if?qD_$(*ukQnPReR*0d`D5=p%14WJQX29m-U`GOXp=I6)%xwdjN|k9LR~{{ZhoH^56~HtAW_%)~c^z`^aZ5 zImXSbY?FX2EZg-@Ll*x0qt1vUDI)69FO6sP42>1YO0v5h1K<-q(JD0U3^;CFZ&u!1>eRGMV(BR{F=pqKyjT3yARp zNr4vP7&tCWqAjAW#JpWIi2zS!t-%Ba~EhUML{Cg?oAFbYho20qJCjZ(?;dVU#HtYT6kk(Y^)-eCK zk%Zd`{oh6sZXxvVy_A2ndjCjCxZTqKmz0Ft@%*2-2p1IjM@o|1Xe3?N^L?C?i^>Aa4oJ-{{3cMYiV5vGc)rM{*L-*FGb|-YF zt{U&NL8gshgBIU(y+q&bYwp$0I$rVOC!Gl0e45>AZfc7YhaZiOZH+m+v6ZRUdjI=3 zpU1C8y4?{o1u4Dca>HkC7K%I*ED!ilZ|?cqSQ%17{ZiLHzHBGXp&Ktvg!B+yO^x(Y z^XqCZiFRQ5Jxn)@)2U{8lGpCz2wv%DR$Ywvfh@qD;ck<7@R0Xcy?*IbzaFa)%`+C( z4Uun!_kd67IclFd2)%PSa>LqH(4|9FTq;o7fl{}~ACT{qy*--9RE1)W%yR0~_lvA< zs%cZ5@dw1i3)O5WZRHM6H|@M~*T8;R%rb5p0p;|RIBX;Dwih|pmDQfRrHOk#UZ{J! zCPi{p`n`_e*pk(?xQF<@sRip()_OmPR|VPdsGIa}*&9tc{rUX?H(NfWk7i|1Vo*`N z3max+vRBg70XXcBZXr3Z`A51vYt?SzFAPOj_KwL3bZho}em$=zJ{(SmaWY7jp6lpI z3F;=2g^p^St!4$f?}m^86jZMOiaBaF&H@a=W1ez*Xoqfq4l$#L1myS;B3f!OSD(V9 zHH70+9qTe_kUeH6E6~k;4VJvZfg-%#2NuG+kl=)H2uyH=uPZMQ0u%>B-I&;n^-q z>K?r)(V)MzMue{7i$2XCKXWA=Bkg#`7wxltB)e$m`(()p8z=T^e*~U=~;0 zH~v&4e=*+$oVA^jfNhYuD1N~>hz7zkC*BiUkFkl`u_AVF1@0#$X4aburQ3ydhgh({ z`32hKy&W$;--JBNmLH?{>tqM5Veh?ZAyrN}_co#K^pGqea+7vM3enmBAZa(Kx#l-kpAhQ(_6~_jCjgLcedVJH zSYJr?Sn1KbRM4GdVG64_)Px#c6Y_TXTmGvYiSD7S1O~A`#G}IPdgG~r{ZE){MCLAb zwzVGePOinO#5Un=H-IQN2`8O_05pVvq`;*hh_s6$+U1I$2?UxU(?Ed<643eo$prVxsOt2t_ z3bVt3kt$W3#nfZ`>Ff+|LmremnI_)J2ER;{);hTs?}L;>Xj}2AGH~%KQw%UIZ%=jq zx&-0kZM3g=_u@US+I09fN_p&YCjpm0<(218+7^=10HBIvw*|$L&144$laHmBnX_R= zNQHSk+Q<|1%;BxjSlo2-3)JtjCcT z{{zt@RSQ_`?f2PZTu^+W`9=oW%+4r5kZCf}K>Kk4C)?eAC~k!qw~;N2nrj<4>ch+~ z?E!w1o4|j5{ghKx!TNKmZ_wq@W^R3YrTX(Wnx4f<+^aMYufoAj+f&tm0D%fROc@F! zhKWrK%_0&8hM_B0bvjICoFf?^UP5-EzKW6(Lpe|N+cgmt4pYDHM_wl}JK<@Bw}Kj# z8u6%!r(CZu(Xy|4JTx1u7+!7h;7~56COzR&j`a2>KV5R|3j;>=vsMurGf$~BdYKM^ zfvMz#JX~3Rc(Fjr%p>obFKE4X;sH!1O)zJxLa_$aV7cpqkB_=@8ZYE2ZBS3WDBoPs z)%=biR)4@w9mD9+9u9>KX2YC>tGkDZN43Ha-61fPC+W4~S6pC%RCbNYNG%A=QO^k^ zrsKcNfxW7r_-jtpN=Irfuh-M6VDhDw%mM~Qr8ntW?>(D#lFMR>LslMxM|gXUUxA-` z3~33^Vb$;(@I$WV9J-@9uEckgY+qdU96&JfTnQaWG=>Ut&2p~Ls?!4!gddQA*m3*_llGDFJUU8^O- z>BKg!Cx2_F?cs?3M`ZG&gg%@4V|z^!RI ziUD$ZWbAq`6@(!Z!OPBI2$;J3<(Z3v*%KhMMS6vI>{Aj&Z5V7|1>?KThS?wO0S5&8 zqT$3~D-|IB7$sf+U#NwYM>kTuOJLJ!1OE%*YCJCjFHn=~)5xtl95bfmr#Oi&DwVn$S)!z5tiQ z$@iP`S3g^kM5>>r4v?sj5??+eN2Gb{m)p9J6C_hwWH8T0J|%~)u3l>!i#!??pjDwQ zeIQ>=I5{#GQvYHvCGMFQR)bH{wmxZ7LLb^6el<6Zsv%08-DlB!O01)y&m>mT#E8U* zFq>bLpC4O$(fH^>_+99it|vXZ56(`$2R%RXYN__9p-4qjV+|AnfHiyole1~W!Gmt0kL8nsFyFN+n*Sd1nC)*5H#Bp2Se81CF0 z(&W)%<(NnxwCxPmhyeT1))Auyi;0<#q*3dTu%fdI zOK|^Ym;cYEXYTmOzsCExdGs$C^FQMz-2IV%P55ymDOV2i-;tENKl1m4-H-vVY%87u5*MNNx8xFAK5cE$a1wKT;~Wkm2$J`KdF?PhX3|Gb5rTx z+7YgEglk3lTlxG?F#VsV=Ylv4?DoXJBkB2$7%io%|B9qiTI-Dm7lr>~msewEwm&6v z*=3R75W~=5k-ymGz%xN7RMfcavaQsa(3mrSvC9@|bjJ0Pa%&A9Ni44b)K~p@UWQna zJ4zD9Z;q6%@X+VQGG(xPi;Uv#~MB zDHokt8O;yVqVBoufktsY~O10y!Zq-N6RJflMz)$V=Di{wwwi>gn z%Hjcm8qW!I4QkuP>v>3g7W6=uAND}k{}`oBpaU$USYN*MU%NAUW}~Ypf#ym9BEY6p zm`6FB{(I`B%EnU63V#bvDNl+p_RIQ}mW#Fk<(4b%~cQ*}De=WCj zO_H;xThY{sK4G@fvjo4L?^L7OzCzf4I7Ww_Ym}1ym{9f9B}-n%>ZB}LDzaw|?%b?; zRcl;rwgNI{9XUNzk-+li%dOlyOZJc_?#Yu#c^a1xPkWsB6hD5l!z@i($n+IRJ z$g9D#j|DzUd8nipK;h6DGBw;?kHbO;aoPdQtK+n8ijoPZi_9+9dUaA$zzS%k2LPbfk6rtMpl*qQG|oseG2kwj$(8RSF2dT>2oP8@2|7LKN2Tx$DP=&BWaGviJKNyXoad0Biew6o3&J&Ec zSl2qvyB4!joT`Z2%oGIMQ)J%_`Jv~00J(c%Jut92|L6tP7MBFZ`%d#sai|Dp3m8KV z?KB+qcqKYctZZow4sLcdaJ7QzdYOL8)8%KZ3kbJ!DiowM=UoM*ht)K0O*D2W-%_`; zL)Kuc>sT;b4s}sMu=ysbBmd)tT2_IJYPE>*Oy*{{`5dF(USuQl+(_a5;=m5Uf{;W& z^4A83M_^;a6(T=n3_D7w^OaT^`&cUf;BF-Lk5YSDr^D!bMNiom zb1fYT%F^``r~sSR`(`q8WaOCUqCuDZ6b{Mz0{hJOx|r0e_Os({KlPP0D1_E2(O*YU z@Plb}F%#;3xJR;z7YWz?>6ZeJx)QFSlC&Hs1?Y3z+Ox1fnazqKB9lXv`~$? z{S6HVi0aCtpW!H#dtK{OejM<1UEH5$w~h=c)3Mby$`B6!4wLp>CxZX{4yRA+&tF^9 zVSoPKGR)i$GTwN(@6TOc901?`!+xNeYXu~M#cG%^9S#j!2g-Be&>?PPbQ2~6#>e%r zovDvENp{~kOXv_R3Wpo{u%Nm#EMD%o389l+g-dA9tR4E7C+|>is&ov8M}kKlPOE{) zOWMN^SiKgAhu=mKKBXAOmkF1PAMl96#;Z%q3-0Sbz!#f&DI}1I?JL+Xt(kF54MF7h zoUzoR*Qbf~$hhqGSU**rYe>ZnlTp8jza4VAqT12G3JQUG{D zx}Vs~wO&Jd#I^IoetWL>0wODvG|#EmDCKuuSdSwn`t2tt_I zPUKnMe07EYyVa8)jrq)#6}0LaOMOBoO?|jbj1OmtxIyr{pHQWjdkVjWOusVo>(-x* z10$gyN2#w#6dpP2XNVUJ*r)DW|LzBJa$fGNEI-+*#lh5tMt>9--4x_!Lu3&w#oBQ& z2NNj_#Q;PI2ORUXDt6t6^>wBTf=mDyvN0?;n4i91-YIzaocA}u`Y|yT;quR)d98wX zU5Jj}pyF%-fqS&9}7z42sa0Ri(DkI3vF!r#wQG}m|Z!wD}d{S4B3f9S8Cs(7!5KNYe zS-&UiV6SV1Dzp5;EQxFwG;zB(svN{GFgI1zR(C(+RCroE*oWX)Ht1X>%&zvW?{#=H ztA6+59Laq&k(OxLQh)Nf3e0EvSUK%m%B2#A?SP1JMyh^`eFo9_IukN-uibm*yB_l40GtcL z@KWPohmQopXYSGv!v!v$NAf+{$IqNBFQVpakN|OO5;cM|ZDEyi*m3iL;9*L0-^a+~ zswH$weaYwZFPy=4=9U=n@GC%wQWg>L;^%xko#a*HUXHXwNQyy^XYWCI( zH>MFkh>|u2?!7ilnZHA?UbnP1Cueo@P;6Cn`da*(NmZD;{FGZ%MvP4wZ?W3#hYIv$Aj>EhrbVA|6y=R8pkYy zcD~Wi7n@h)(bUR54B0n%4EC^98_Tn>w)f`z`3(_P)p26);JKEg!5OY7 zwA;wO^X&p^xDnmbizo$jYh^UPBz(U+8?-b-Z_>EuvHL)(HRGmW)vRDx(6s5}Pf9<= zXL`T5?ECq6&)Woq@Z(eZvKLTRCzdK>KE~HUJsn^9HMJ{W?$2t=`1tHiMX3Sd++{1n zm7r6`BepSL@YXiV`FYRg#P;~3gfAlo4NCIvaV8Tz53l9DC$x~R^C&x%e6&;ntyK4T zw6_0RT|b^XVQ?*0uD!MYkSrF%uwc(=U5?JUH8mIXHMFmp3O;e--23XxAFew~a46IF z_MhGH?v+%qe9RL*&f_+Am^)X3P6OnnMkjuLJav-m$y3KRs#c!(9TBtDn_qQl+fgpp zNXotWLl{&h=E=)nNeC!l7dPPta)ar9@vS&dJyVJLYraIS{z~UR^Cka*C;o4RC*X1> zl1Q!LD%f~l;!#Xq(ujt15ikOoDCkhe6r+)nSUFVaPd!kpD zDzvr3%A`-}!tCT?M7mCfiM`t%e0T=im+HN?W=e)VwBZ_^dZHf{*;GbK`A}GVk!9E$ zki#4@@}_@8p}?hjWJiIYMeYr2u(*4Kwd!KnGixyD z%@a~tqeoT1=OvEbC4~)9cI3nkf0@1{PXArTiwdu%nx<@5&BdhE5nV5zBUxEec2ym^ z;GTN$kRf^OImV2u=g+<9J5I&=;xK* zaCJ@N&%iA!XigasV=mE!+4jYZV<^F{rc6)Z1n1;j6);xk>8GAQ`y z8LbYxK!oX$SuipguWrfCwqw&L4qYkP>XuM>{}Ltdx1@0`ZC#!--{?zYXfwl({UE^yzw1Mc0O~Pg(x;h3`smCX5R~D%L-eBu(wLA3jUym= zs)iIbTwRKii#E#7=k}*A2}}T(Yr2(u3j{60{#%84{XKs#2bKv}t0apFzfXlT%S4;{ zpK8dA8)(HaF+JA3+C>fN0cO5}m5fDs&xy<+eK8tS+@H9z4 zn)^?_rDS7fGIuyo=w19^;X5AgyMQ-{f565iD=Vv~LQ!|ueZcJF3J|7H!DmJobjaGw zsj^d4gAg9eEmE#)jQOg6F&ctRZRTC5@Z)_g)^}hh>qeSFrC`&SL0ie}?EZxc#n8e& zkqSm;g;S-Tz0Hw;9b(M>SU@*Wb?D65FU5T~nYy=s44u`<$^mfz1OF|$wTxq7QTl1+ z??!Y~h0wLrmj{{jP_tBM)Y0M(a&tmu&1~p6OmU4^ZTn38$9s zY*iD#1~#;>IbJbQN)NItcX)1eu-xHuEjiL}+oO#4?zwo^H|sg_VrxnPiz#;I zSVmKQ43RIZ*!`)JYl9;Dq#+g9DTJ--+9vnXC-CQn(mfONsX zczEFOS@`$IJ+Q;<#YN4wwo~^)5@ZPU-LbO#c5__jzM!#%r$MdEPxq)1q36Ie4z7i} z_f`?s_+(6{ShwgYb+2=r`Tf6uj{X=;xqe%>SBLZR1gmiJn2f zaP7txJ-i!1ICsl(CP$8JBDrd5@6{S-x;~vmrY`J?tg^ zCE5w7NTcXz7}hV@Uc|K*ga8$&2s(CSx|9D2PwsN@oU}TzKcRi&h$r{es`VNoRimAA zvW1=oHp#Zyu*rQ@91qHDWicu%)IWQqim6lo7^(&-`dFdQdauidos{A+IaPjuf+;oHKM_B?;b8y!y7mb6cwY86#bHW?6= zwh=O2YpIM+N3zbmcv8;vB_6vIRMZ)yljmW@( zhR{Bj>m|R{%}%GNI$UJBnfB`#%9}3k6PvXimTp&QxO*b}Y~xe%MQTlk>d?Z0w&yo` z&M@^=$%3ch!-gNYUHYImZe4DRS3WAZ-ERLtk2byjVE6gx2-+93K2e;5ZcUFViLmu` z7tn)mO$tTOH?z$09$kJ7b6fIr7J9v6q4l6g>Q``yS=}G}U3~pam>a+O z9}$D9q!s>ShHE$zYW!PdtW|r*^K^LE$~%oO@}eDYYNz8y&Pn{tHgvz9Bs=kPRLVnd z|M3sGH%$}|lE1B&mdJZnDNcvFpT6Nz`YkV@+7vzqw))~%CDOL`IrKwh`-#_|J`FFg zUTFO3|9WXBo?vv}aCAv@O1!zxFzJzM*ad2XcUIQ}tI_Ebkhu9bFl9xbLY)1PQ#K8U z5Hbg^NbTKA5cABAMrLls##Tfog}c9>2E5@1>!Z9iGW~Mc7vufOaBBpdeNLYMj+*9^ z<$mwRz~=S9-u^Hphv$cclM#Fbh=vgWoN`XV*~`s@O7+E0j79HlJEX6YPys}XiX>Qz x`_}gyx-p%QO^M{q4|JH})rB6#5H7nKp{%NG(qANcN2?3fgYDNaT-%1}{{u;U#;E`R From c9988d59a155fa37b7459d2f72fb7a59ff65bb3b Mon Sep 17 00:00:00 2001 From: libexus Date: Wed, 12 May 2021 20:26:30 +0000 Subject: [PATCH 004/174] Translated using Weblate (German) Currently translated at 99.3% (2939 of 2959 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index a3d6027fba..23f6071efc 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -3293,5 +3293,34 @@ "Stop the recording": "Aufnahme stoppen", "%(count)s results in all spaces|one": "%(count)s Ergebnis in allen Bereichen", "%(count)s results in all spaces|other": "%(count)s Ergebnisse in allen Bereichen", - "You have no ignored users.": "Du ignorierst keine Benutzer." + "You have no ignored users.": "Du ignorierst keine Benutzer.", + "Error processing voice message": "Fehler beim Verarbeiten der Sprachnachricht", + "To join %(spaceName)s, turn on the Spaces beta": "Um %(spaceName)s beizutreten, aktiviere die Spaces Betaversion", + "To view %(spaceName)s, turn on the Spaces beta": "Um %(spaceName)s zu betreten, aktiviere die Spaces Betaversion", + "Select a room below first": "Wähle zuerst einen Raum aus", + "Communities are changing to Spaces": "Spaces ersetzen Communities", + "Join the beta": "Beta beitreten", + "Leave the beta": "Beta verlassen", + "Beta": "Beta", + "Tap for more info": "Klicke für mehr Infos", + "Spaces is a beta feature": "Spaces sind noch in der Entwicklung und möglicherweise instabil", + "Want to add a new room instead?": "Willst du einen neuen Raum hinzufügen?", + "Adding rooms... (%(progress)s out of %(count)s)|one": "Raum wird hinzugefügt...", + "Adding rooms... (%(progress)s out of %(count)s)|other": "Räume werden hinzugefügt... (%(progress)s von %(count)s)", + "You can add existing spaces to a space.": "Du kannst existierende Spaces zu einem Space hinzfügen.", + "Feeling experimental?": "Lust auf Experimente?", + "You are not allowed to view this server's rooms list": "Du darfst diese Raumliste nicht sehen", + "We didn't find a microphone on your device. Please check your settings and try again.": "Auf deinem Gerät kann kein Mikrofon gefunden werden. Bitte überprüfe deine Einstellungen und versuche es nochmal.", + "No microphone found": "Kein Mikrofon gefunden", + "We were unable to access your microphone. Please check your browser settings and try again.": "Fehler beim Zugriff auf dein Mikrofon. Überprüfe deine Browsereinstellungen und versuche es nochmal.", + "Unable to access your microphone": "Fehler beim Zugriff auf Mikrofon", + "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "Lust auf ein paar Experimente? In den Laboreinstellungen kannst du zukünftige Features vor der Veröffentlichung testen und uns mit Feedback beim Verbessern helfen. Mehr Infos.", + "Please enter a name for the space": "Gib den Namen des Spaces ein", + "Connecting": "Verbinden", + "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Direktverbindung für Direktanrufe aktivieren. Dadurch sieht dein Gegenüber möglicherweise deine IP-Adresse.", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Die Betaversion ist verfügbar für Browser, Desktop und Android verfügbar. Je nach Homeserver sind einige Funktionen möglicherweise nicht verfügbar.", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "Du kannst die Betaversion jederzeit verlassen. Mache dies entweder in den Einstellungen oder klicke auf Beta-Icons wie dieses hier.", + "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s wird mit aktivierten Spaces neuladen. Danach kannst Communities und Custom Tags nicht verwenden.", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "Die Betaversion ist für Browser, Desktop und Android verfügbar. Danke, dass Du die Betaversion testest.", + "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s wird mit deaktivierten Spaces neuladen und du kannst Communities und Custom Tags wieder verwenden können." } From bf9dd05b3c48f64e14d2a31762ea8c94d7ded609 Mon Sep 17 00:00:00 2001 From: iaiz Date: Wed, 12 May 2021 13:12:03 +0000 Subject: [PATCH 005/174] Translated using Weblate (Spanish) Currently translated at 99.8% (2956 of 2959 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/es/ --- src/i18n/strings/es.json | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index 3e4b7b52ce..86749e7b3f 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -3259,5 +3259,37 @@ "%(count)s results in all spaces|other": "%(count)s resultados en todos los espacios", "You have no ignored users.": "No has ignorado a nadie.", "Pause": "Pausar", - "Play": "Reproducir" + "Play": "Reproducir", + "This is an experimental feature. For now, new users receiving an invite will have to open the invite on to actually join.": "Esto es una funcionalidad experimental. Por ahora, los usuarios nuevos que reciban una invitación tendrán que abrirla en para unirse.", + "To view %(spaceName)s, turn on the Spaces beta": "Para ver %(spaceName)s, activa la beta de los espacios", + "To join %(spaceName)s, turn on the Spaces beta": "Para unirte a %(spaceName)s, activa la beta de los espacios", + "Select a room below first": "Selecciona una sala de abajo primero", + "Communities are changing to Spaces": "Las comunidades se van a convertir en espacios", + "Join the beta": "Unirse a la beta", + "Leave the beta": "Salir de la beta", + "Beta": "Beta", + "Tap for more info": "Pulsa para más información", + "Spaces is a beta feature": "Los espacios son una funcionalidad en beta", + "Want to add a new room instead?": "¿Quieres añadir una sala nueva en su lugar?", + "Adding rooms... (%(progress)s out of %(count)s)|other": "Añadiendo salas… (%(progress)s de %(count)s)", + "Adding rooms... (%(progress)s out of %(count)s)|one": "Añadiendo sala…", + "Not all selected were added": "No se han añadido todas las seleccionadas", + "You can add existing spaces to a space.": "Puedes añadir espacios ya existentes dentro de otros espacios.", + "Feeling experimental?": "¿Te animas a probar cosas nuevas?", + "You are not allowed to view this server's rooms list": "No tienes permiso para ver la lista de salas de este servidor", + "Error processing voice message": "Ha ocurrido un error al procesar el mensaje de voz", + "We didn't find a microphone on your device. Please check your settings and try again.": "No hemos encontrado un micrófono en tu dispositivo. Por favor, consulta tus ajustes e inténtalo de nuevo.", + "No microphone found": "No se ha encontrado ningún micrófono", + "We were unable to access your microphone. Please check your browser settings and try again.": "No hemos podido acceder a tu micrófono. Por favor, comprueba los ajustes de tu navegador e inténtalo de nuevo.", + "Unable to access your microphone": "No se ha podido acceder a tu micrófono", + "Your access token gives full access to your account. Do not share it with anyone.": "Tu token de acceso da acceso completo a tu cuenta. No lo compartas con nadie.", + "Access Token": "Token de acceso", + "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Los espacios son una nueva forma de agrupar salas y personas. Para unirte a uno ya existente, necesitarás que te inviten a él.", + "Please enter a name for the space": "Por favor, elige un nombre para el espacio", + "Connecting": "Conectando", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "Puedes salirte de la beta en cualquier momento desde tus ajustes o pulsando sobre la etiqueta de beta, como la que hay arriba.", + "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s se volverá a cargar con los espacios activados. Las comunidades y etiquetas personalizadas se ocultarán.", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "Versión beta disponible para web, escritorio y Android. Gracias por usar la beta.", + "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s volverá a cargarse con los espacios desactivados. Las comunidades y etiquetas personalizadas serán visibles de nuevo.", + "Spaces are a new way to group rooms and people.": "Los espacios son una nueva manera de agrupar salas y gente." } From 93ca9e689186ac5bf14f1e9bff7edb117a38d86f Mon Sep 17 00:00:00 2001 From: Thibault Martin Date: Wed, 12 May 2021 12:36:46 +0000 Subject: [PATCH 006/174] Translated using Weblate (French) Currently translated at 100.0% (2959 of 2959 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 4aee15691b..4b36d5b7ed 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -3296,5 +3296,40 @@ "Stop the recording": "Arrêter l’enregistrement", "%(count)s results in all spaces|one": "%(count)s résultat dans tous les espaces", "%(count)s results in all spaces|other": "%(count)s résultats dans tous les espaces", - "You have no ignored users.": "Vous n’avez ignoré personne." + "You have no ignored users.": "Vous n’avez ignoré personne.", + "Your access token gives full access to your account. Do not share it with anyone.": "Votre jeton d’accès donne un accès intégral à votre compte. Ne le partagez avec personne.", + "This is an experimental feature. For now, new users receiving an invite will have to open the invite on to actually join.": "Ceci est une fonctionnalité expérimentale. Pour l’instant, les nouveaux utilisateurs recevant une invitation devront l’ouvrir sur pour poursuivre.", + "To join %(spaceName)s, turn on the Spaces beta": "Pour rejoindre %(spaceName)s, activez les espaces en bêta", + "To view %(spaceName)s, turn on the Spaces beta": "Pour visualiser %(spaceName)s, activez les espaces en bêta", + "Select a room below first": "Sélectionnez un salon ci-dessous d’abord", + "Communities are changing to Spaces": "Les communautés deviennent des espaces", + "Join the beta": "Rejoindre la bêta", + "Leave the beta": "Quitter la bêta", + "Beta": "Bêta", + "Tap for more info": "Appuyez pour plus d’information", + "Spaces is a beta feature": "Les espaces sont une fonctionnalité en bêta", + "Want to add a new room instead?": "Voulez-vous plutôt ajouter un nouveau salon ?", + "Adding rooms... (%(progress)s out of %(count)s)|one": "Ajout du salon…", + "Adding rooms... (%(progress)s out of %(count)s)|other": "Ajout des salons… (%(progress)s sur %(count)s)", + "Not all selected were added": "Toute la sélection n’a pas été ajoutée", + "You can add existing spaces to a space.": "Vous pouvez ajouter des espaces existants à un espace.", + "Feeling experimental?": "L’esprit aventurier ?", + "You are not allowed to view this server's rooms list": "Vous n’avez pas l’autorisation d’accéder à la liste des salons de ce serveur", + "Error processing voice message": "Erreur lors du traitement du message vocal", + "We didn't find a microphone on your device. Please check your settings and try again.": "Nous n’avons pas détecté de microphone sur votre appareil. Merci de vérifier vos paramètres et de réessayer.", + "No microphone found": "Aucun microphone détecté", + "We were unable to access your microphone. Please check your browser settings and try again.": "Nous n’avons pas pu accéder à votre microphone. Merci de vérifier les paramètres de votre navigateur et de réessayer.", + "Unable to access your microphone": "Impossible d’accéder à votre microphone", + "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "L’esprit aventurier ? Les fonctionnalités expérimentales vous permettent de tester les nouveautés et aider à les polir avant leur lancement. En apprendre plus.", + "Access Token": "Jeton d’accès", + "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Les espaces sont un nouveau moyen de grouper les salons et les personnes. Une invitation est nécessaire pour rejoindre un espace existant.", + "Please enter a name for the space": "Veuillez renseigner un nom pour l’espace", + "Connecting": "Connexion", + "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Autoriser le pair-à-pair (p2p) pour les appels individuels (si activé, votre correspondant pourra voir votre adresse IP)", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Bêta disponible pour l’application web, de bureau et Android. Certains fonctionnalités pourraient ne pas être disponibles sur votre serveur d’accueil.", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "Vous pouvez quitter la bêta n’importe quand à partir des paramètres, ou en appuyant sur le badge bêta comme celui ci-dessus.", + "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s va être redémarré avec les espaces activés. Les communautés et les étiquettes personnalisées seront cachés.", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "Bêta disponible pour l’application web, de bureau et Android. Merci d’essayer la bêta.", + "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s va être redémarré avec les espaces désactivés. Les communautés et les étiquettes personnalisées seront de nouveau visibles.", + "Spaces are a new way to group rooms and people.": "Les espaces sont un nouveau moyen de regrouper les salons et les personnes." } From 61f90cdc45a7d32c9f95a6c5aeea0f893bfefee3 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Tue, 11 May 2021 16:48:05 +0000 Subject: [PATCH 007/174] Translated using Weblate (Hungarian) Currently translated at 100.0% (2959 of 2959 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 6b33ecb81e..0230ecf52a 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -3314,5 +3314,40 @@ "%(count)s results in all spaces|other": "%(count)s találat a terekben", "You have no ignored users.": "Nincs figyelmen kívül hagyott felhasználó.", "Play": "Lejátszás", - "Pause": "Szünet" + "Pause": "Szünet", + "This is an experimental feature. For now, new users receiving an invite will have to open the invite on to actually join.": "Ez egy kísérleti funkció Egyenlőre az a felhasználó aki meghívót kap a meghívóban lévő linkre kattintva tud csatlakozni.", + "To join %(spaceName)s, turn on the Spaces beta": "A csatlakozáshoz ide: %(spaceName)s először kapcsolja be a béta Tereket", + "To view %(spaceName)s, turn on the Spaces beta": "A %(spaceName)s megjelenítéséhez először kapcsolja be a béta Tereket", + "Select a room below first": "Először válasszon ki szobát alulról", + "Communities are changing to Spaces": "A közösségek Terek lesznek", + "Join the beta": "Csatlakozás béta lehetőségekhez", + "Leave the beta": "Béta kikapcsolása", + "Beta": "Béta", + "Tap for more info": "Koppints további információért", + "Spaces is a beta feature": "A terek béta állapotban van", + "Want to add a new room instead?": "Inkább új szobát adna hozzá?", + "Adding rooms... (%(progress)s out of %(count)s)|one": "Szobák hozzáadása…", + "Adding rooms... (%(progress)s out of %(count)s)|other": "Szobák hozzáadása… (%(progress)s ennyiből: %(count)s)", + "Not all selected were added": "Nem az összes kijelölt lett hozzáadva", + "You can add existing spaces to a space.": "Létező tereket adhat a térhez.", + "Feeling experimental?": "Kísérletezni szeretne?", + "You are not allowed to view this server's rooms list": "Nincs joga ennek a szervernek a szobalistáját megnézni", + "Error processing voice message": "Hiba a hangüzenet feldolgozásánál", + "We didn't find a microphone on your device. Please check your settings and try again.": "Nem található mikrofon. Ellenőrizze a beállításokat és próbálja újra.", + "No microphone found": "Nem található mikrofon", + "We were unable to access your microphone. Please check your browser settings and try again.": "Nem lehet a mikrofont használni. Ellenőrizze a böngésző beállításait és próbálja újra.", + "Unable to access your microphone": "A mikrofont nem lehet használni", + "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "Kedve van kísérletezni? Labs az a hely ahol először hozzá lehet jutni az új dolgokhoz, kipróbálni új lehetőségeket és segíteni a fejlődésüket mielőtt mindenkihez eljut. Tudj meg többet.", + "Your access token gives full access to your account. Do not share it with anyone.": "A hozzáférési kulcs teljes elérést biztosít a fiókhoz. Soha ne ossza meg mással.", + "Access Token": "Elérési kulcs", + "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "A terek egy új lehetőség a szobák és emberek csoportosításához. Létező térhez meghívóval lehet csatlakozni.", + "Please enter a name for the space": "Kérem adjon meg egy nevet a térhez", + "Connecting": "Kapcsolás", + "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Közvetlen hívás engedélyezése két fél között (ha ezt engedélyezi a másik fél láthatja az ön IP címét)", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Béta verzió elérhető webre, asztali kliensre és Androidra. Bizonyos funkciók lehet, hogy nem elérhetők a matrix szerverén.", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "Bármikor elhagyhatja a béta változatot a beállításokban vagy a béta kitűzőre koppintva, mint alább.", + "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s a Terekkel lesz újra betöltve. A közösségek és egyedi címkék rejtve maradnak.", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "Béta verzió elérhető webre, asztali kliensre és Androidra. Köszönjük, hogy kipróbálja.", + "Spaces are a new way to group rooms and people.": "Szobák és emberek csoportosításának új lehetősége a Terek használata.", + "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s a Terek nélkül lesz újra betöltve. A közösségek és egyedi címkék újra megjelennek." } From 7be918f52744ceb8dc60353683be3fe2b6099a7a Mon Sep 17 00:00:00 2001 From: jelv Date: Wed, 12 May 2021 12:29:48 +0000 Subject: [PATCH 008/174] Translated using Weblate (Dutch) Currently translated at 100.0% (2959 of 2959 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/nl/ --- src/i18n/strings/nl.json | 215 +++++++++++++++++++++++---------------- 1 file changed, 125 insertions(+), 90 deletions(-) diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index a30c949635..e12780ec10 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -59,7 +59,7 @@ "Close": "Sluiten", "Create new room": "Nieuw gesprek aanmaken", "Custom Server Options": "Aangepaste serverinstellingen", - "Dismiss": "Afwijzen", + "Dismiss": "Sluiten", "Error": "Fout", "Failed to forget room %(errCode)s": "Vergeten van gesprek is mislukt %(errCode)s", "Favourite": "Favoriet", @@ -177,7 +177,7 @@ "Hangup": "Ophangen", "Historical": "Historisch", "Home": "Thuis", - "Homeserver is": "Thuisserver is", + "Homeserver is": "Homeserver is", "Identity Server is": "Identiteitsserver is", "I have verified my email address": "Ik heb mijn e-mailadres geverifieerd", "Import": "Inlezen", @@ -193,7 +193,7 @@ "Invited": "Uitgenodigd", "Invites": "Uitnodigingen", "Invites user with given id to current room": "Nodigt de gebruiker met de gegeven ID uit in het huidige gesprek", - "Sign in with": "Aanmelden met", + "Sign in with": "Inloggen met", "Join as voice or video.": "Deelnemen met spraak of video.", "Join Room": "Gesprek toetreden", "%(targetName)s joined the room.": "%(targetName)s is tot het gesprek toegetreden.", @@ -240,7 +240,7 @@ "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s heeft %(targetDisplayName)s in het gesprek uitgenodigd.", "Server error": "Serverfout", "Server may be unavailable, overloaded, or search timed out :(": "De server is misschien onbereikbaar of overbelast, of het zoeken duurde te lang :(", - "Server may be unavailable, overloaded, or you hit a bug.": "De server is misschien onbereikbaar of overbelast, of je bent een fout tegengekomen.", + "Server may be unavailable, overloaded, or you hit a bug.": "De server is misschien onbereikbaar of overbelast, of je bent een bug tegengekomen.", "Server unavailable, overloaded, or something else went wrong.": "De server is onbereikbaar of overbelast, of er is iets anders foutgegaan.", "Session ID": "Sessie-ID", "%(senderName)s kicked %(targetName)s.": "%(senderName)s heeft %(targetName)s het gesprek uitgestuurd.", @@ -250,8 +250,8 @@ "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s heeft %(displayName)s als weergavenaam aangenomen.", "Show timestamps in 12 hour format (e.g. 2:30pm)": "Tijd in 12-uursformaat tonen (bv. 2:30pm)", "Signed Out": "Afgemeld", - "Sign in": "Aanmelden", - "Sign out": "Afmelden", + "Sign in": "Inloggen", + "Sign out": "Uitloggen", "%(count)s of your messages have not been sent.|other": "Enkele van uw berichten zijn niet verstuurd.", "Someone": "Iemand", "The phone number entered looks invalid": "Het ingevoerde telefoonnummer ziet er ongeldig uit", @@ -266,7 +266,7 @@ "This room": "Dit gesprek", "This room is not accessible by remote Matrix servers": "Dit gesprek is niet toegankelijk vanaf externe Matrix-servers", "To use it, just wait for autocomplete results to load and tab through them.": "Om het te gebruiken, wacht u tot de autoaanvullen resultaten geladen zijn en tabt u erdoorheen.", - "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "U heeft gepoogd een gegeven punt in de tijdslijn van dit gesprek te laden, maar u bent niet bevoegd het desbetreffende bericht te zien.", + "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "U probeert een punt in de tijdlijn van dit gesprek te laden, maar u heeft niet voldoende rechten om het bericht te lezen.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Geprobeerd een gegeven punt in de tijdslijn van dit gesprek te laden, maar kon dit niet vinden.", "Unable to add email address": "Kan e-mailadres niet toevoegen", "Unable to remove contact information": "Kan contactinformatie niet verwijderen", @@ -329,7 +329,7 @@ "Please select the destination room for this message": "Selecteer het bestemmingsgesprek voor dit bericht", "New Password": "Nieuw wachtwoord", "Start automatically after system login": "Automatisch starten na systeemlogin", - "Analytics": "Statistische gegevens", + "Analytics": "Gebruiksgegevens", "Options": "Opties", "%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)s verzamelt anonieme analysegegevens die het mogelijk maken de toepassing te verbeteren.", "Passphrases must match": "Wachtwoorden moeten overeenkomen", @@ -628,10 +628,10 @@ "Room Notification": "Groepsgespreksmelding", "The information being sent to us to help make %(brand)s better includes:": "De informatie die naar ons wordt verstuurd om %(brand)s te verbeteren bevat:", "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Waar deze pagina identificeerbare informatie bevat, zoals een gespreks-, gebruikers- of groeps-ID, zullen deze gegevens verwijderd worden voordat ze naar de server gestuurd worden.", - "The platform you're on": "Het platform dat je gebruikt", + "The platform you're on": "Het platform dat u gebruikt", "The version of %(brand)s": "De versie van %(brand)s", "Your language of choice": "De door jou gekozen taal", - "Which officially provided instance you are using, if any": "Welke officieel aangeboden instantie je eventueel gebruikt", + "Which officially provided instance you are using, if any": "Welke officieel aangeboden instantie u eventueel gebruikt", "Whether or not you're using the Richtext mode of the Rich Text Editor": "Of u de tekstverwerker al dan niet in de modus voor opgemaakte tekst gebruikt", "Your homeserver's URL": "De URL van je homeserver", "In reply to ": "Als antwoord op ", @@ -658,8 +658,8 @@ "Who can join this community?": "Wie kan er tot deze gemeenschap toetreden?", "Everyone": "Iedereen", "Leave this community": "Deze gemeenschap verlaten", - "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 and the usernames of other users. They do not contain messages.": "Voor het oplossen van, via GitHub, gemelde problemen helpen foutopsporingslogboeken ons enorm. Deze bevatten wel gebruiksgegevens (waaronder uw gebruikersnaam, de ID’s of bijnamen van de gesprekken en groepen die u heeft bezocht, en de namen van andere gebruikers), maar geen berichten.", - "Submit debug logs": "Foutopsporingslogboeken indienen", + "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 and the usernames of other users. They do not contain messages.": "Voor het oplossen van, via GitHub, gemelde bugs helpen foutenlogboeken ons enorm. Deze bevatten wel uw gebruiksgegevens, maar geen berichten. Het bevat onder meer uw gebruikersnaam, de ID’s of bijnamen van de gesprekken en groepen die u heeft bezocht en de namen van andere gebruikers.", + "Submit debug logs": "Foutenlogboek versturen", "Opens the Developer Tools dialog": "Opent het dialoogvenster met ontwikkelaarsgereedschap", "Fetching third party location failed": "Het ophalen van de locatie van de derde partij is mislukt", "I understand the risks and wish to continue": "Ik begrijp de risico’s en wil graag verdergaan", @@ -727,11 +727,11 @@ "All messages (noisy)": "Alle berichten (luid)", "Enable them now": "Deze nu inschakelen", "Toolbox": "Gereedschap", - "Collecting logs": "Logboeken worden verzameld", + "Collecting logs": "Logs worden verzameld", "You must specify an event type!": "U dient een gebeurtenistype op te geven!", "(HTTP status %(httpStatus)s)": "(HTTP-status %(httpStatus)s)", "Invite to this room": "Uitnodigen voor dit gesprek", - "Send logs": "Logboeken versturen", + "Send logs": "Logs versturen", "All messages": "Alle berichten", "Call invitation": "Oproep-uitnodiging", "Downloading update...": "Update wordt gedownload…", @@ -778,17 +778,17 @@ "Thank you!": "Bedankt!", "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "Met uw huidige browser kan de toepassing er volledig onjuist uitzien. Tevens is het mogelijk dat niet alle functies naar behoren werken. U kunt doorgaan als u het toch wilt proberen, maar bij problemen bent u volledig op uzelf aangewezen!", "Checking for an update...": "Bezig met controleren op updates…", - "Logs sent": "Logboeken verstuurd", - "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Foutopsporingslogboeken bevatten gebruiksgegevens over de toepassing, inclusief uw gebruikersnaam, de ID’s of bijnamen van de gesprekken die u heeft bezocht, evenals de gebruikersnamen van andere gebruikers. Ze bevatten geen berichten.", - "Failed to send logs: ": "Versturen van logboeken mislukt: ", - "Preparing to send logs": "Logboeken worden voorbereid voor versturen", + "Logs sent": "Logs verstuurd", + "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Foutenlogboeken bevatten gebruiksgegevens van de app inclusief uw gebruikersnaam, de ID’s of bijnamen van de gesprekken die u heeft bezocht, en de gebruikersnamen van andere gebruikers. Ze bevatten geen berichten.", + "Failed to send logs: ": "Versturen van logs mislukt: ", + "Preparing to send logs": "Logs voorbereiden voor versturen", "e.g. %(exampleValue)s": "bv. %(exampleValue)s", - "Every page you use in the app": "Iedere bladzijde die je in de toepassing gebruikt", + "Every page you use in the app": "Iedere bladzijde die u in de app gebruikt", "e.g. ": "bv. ", "Your device resolution": "De resolutie van je apparaat", "Missing roomId.": "roomId ontbreekt.", "Always show encryption icons": "Versleutelingspictogrammen altijd tonen", - "Send analytics data": "Statistische gegevens versturen", + "Send analytics data": "Gebruiksgegevens delen", "Enable widget screenshots on supported widgets": "Widget-schermafbeeldingen inschakelen op ondersteunde widgets", "Muted Users": "Gedempte gebruikers", "Popout widget": "Widget in nieuw venster openen", @@ -798,11 +798,11 @@ "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "De zichtbaarheid van berichten in Matrix is zoals bij e-mails. Het vergeten van uw berichten betekent dat berichten die u heeft verstuurd niet meer gedeeld worden met nieuwe of ongeregistreerde gebruikers, maar geregistreerde gebruikers die al toegang hebben tot deze berichten zullen alsnog toegang hebben tot hun eigen kopie ervan.", "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Vergeet bij het sluiten van mijn account alle door mij verstuurde berichten (Let op: hierdoor zullen personen een onvolledig beeld krijgen van gesprekken)", "To continue, please enter your password:": "Voer uw wachtwoord in om verder te gaan:", - "Clear Storage and Sign Out": "Opslag wissen en afmelden", - "Send Logs": "Logboek versturen", + "Clear Storage and Sign Out": "Opslag wissen en uitloggen", + "Send Logs": "Logs versturen", "Refresh": "Herladen", "We encountered an error trying to restore your previous session.": "Het herstel van uw vorige sessie is mislukt.", - "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Het legen van de opslag van uw browser zal het probleem misschien verhelpen, maar zal u ook afmelden en uw gehele versleutelde gespreksgeschiedenis onleesbaar maken.", + "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Het legen van de opslag van uw browser zal het probleem misschien verhelpen, maar zal u ook uitloggen en uw gehele versleutelde gespreksgeschiedenis onleesbaar maken.", "Collapse Reply Thread": "Reactieketting dichtvouwen", "Can't leave Server Notices room": "Kan servermeldingsgesprek niet verlaten", "This room is used for important messages from the Homeserver, so you cannot leave it.": "Dit gesprek is bedoeld voor belangrijke berichten van de homeserver, dus u kunt het niet verlaten.", @@ -842,7 +842,7 @@ "Bulk options": "Bulkopties", "This homeserver has hit its Monthly Active User limit.": "Deze homeserver heeft zijn limiet voor maandelijks actieve gebruikers bereikt.", "This homeserver has exceeded one of its resource limits.": "Deze homeserver heeft één van zijn systeembronlimieten overschreden.", - "Whether or not you're logged in (we don't record your username)": "Of je al dan niet ingelogd bent (we slaan je gebruikersnaam niet op)", + "Whether or not you're logged in (we don't record your username)": "Of u al dan niet ingelogd bent (we slaan je gebruikersnaam niet op)", "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Het bestand ‘%(fileName)s’ is groter dan de uploadlimiet van de homeserver", "Unable to load! Check your network connectivity and try again.": "Laden mislukt! Controleer je netwerktoegang en probeer het nogmaals.", "Failed to invite users to the room:": "Kon de volgende gebruikers hier niet uitnodigen:", @@ -1023,20 +1023,20 @@ "Profile picture": "Profielfoto", "Upgrade to your own domain": "Upgrade naar uw eigen domein", "Display Name": "Weergavenaam", - "Set a new account password...": "Stel een nieuw accountwachtwoord in…", + "Set a new account password...": "Stel een nieuw wachtwoord in…", "Email addresses": "E-mailadressen", "Phone numbers": "Telefoonnummers", "Language and region": "Taal en regio", "Theme": "Thema", "Account management": "Accountbeheer", - "Deactivating your account is a permanent action - be careful!": "Pas op! Het sluiten van uw account is onherroepelijk!", + "Deactivating your account is a permanent action - be careful!": "Pas op! Het sluiten van uw account kan niet teruggedraaid worden!", "General": "Algemeen", - "Legal": "Wettelijk", + "Legal": "Juridisch", "Credits": "Met dank aan", "For help with using %(brand)s, click here.": "Klik hier voor hulp bij het gebruiken van %(brand)s.", "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "Klik hier voor hulp bij het gebruiken van %(brand)s, of begin een gesprek met onze robot met de knop hieronder.", - "Help & About": "Hulp & Info", - "Bug reporting": "Foutmeldingen", + "Help & About": "Hulp & info", + "Bug reporting": "Bug meldingen", "FAQ": "FAQ", "Versions": "Versies", "Preferences": "Instellingen", @@ -1046,10 +1046,10 @@ "Autocomplete delay (ms)": "Vertraging voor autoaanvullen (ms)", "Accept all %(invitedRooms)s invites": "Alle %(invitedRooms)s de uitnodigingen aannemen", "Key backup": "Sleutelback-up", - "Security & Privacy": "Veiligheid & Privacy", + "Security & Privacy": "Veiligheid & privacy", "Missing media permissions, click the button below to request.": "Mediatoestemmingen ontbreken, klik op de knop hieronder om deze aan te vragen.", "Request media permissions": "Mediatoestemmingen verzoeken", - "Voice & Video": "Spraak & Video", + "Voice & Video": "Spraak & video", "Room information": "Gespreksinformatie", "Internal room ID:": "Interne gespreks-ID:", "Room version": "Gespreksversie", @@ -1108,7 +1108,7 @@ "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Kan geen profielen voor de Matrix-ID’s hieronder vinden - wilt u ze toch uitnodigen?", "Invite anyway and never warn me again": "Alsnog uitnodigen en mij nooit meer waarschuwen", "Invite anyway": "Alsnog uitnodigen", - "Before submitting logs, you must create a GitHub issue to describe your problem.": "Voor u logboeken indient, dient u uw probleem te melden op GitHub.", + "Before submitting logs, you must create a GitHub issue to describe your problem.": "Voordat u logs indient, dient u uw probleem te melden in een GitHub issue.", "Unable to load commit detail: %(msg)s": "Kan commitdetail niet laden: %(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": "Om uw gespreksgeschiedenis niet te verliezen vóór het uitloggen dient u uw veiligheidssleutel te exporteren. Dat moet vanuit de nieuwere versie van %(brand)s", "Incompatible Database": "Incompatibele database", @@ -1125,7 +1125,7 @@ "I don't want my encrypted messages": "Ik wil mijn versleutelde berichten niet", "Manually export keys": "Sleutels handmatig wegschrijven", "You'll lose access to your encrypted messages": "U zult de toegang tot uw versleutelde berichten verliezen", - "Are you sure you want to sign out?": "Weet u zeker dat u zich wilt afmelden?", + "Are you sure you want to sign out?": "Weet u zeker dat u wilt uitloggen?", "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "Als u fouten zou tegenkomen of voorstellen zou hebben, laat het ons dan weten op GitHub.", "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.": "Voorkom dubbele meldingen: doorzoek eerst de bestaande meldingen (en voeg desgewenst een +1 toe). Maak enkel een nieuwe melding aan indien u niets kunt vinden.", "Report bugs & give feedback": "Fouten melden & feedback geven", @@ -1193,7 +1193,7 @@ "Could not load user profile": "Kon gebruikersprofiel niet laden", "Your Matrix account on %(serverName)s": "Uw Matrix-account op %(serverName)s", "A verification email will be sent to your inbox to confirm setting your new password.": "Er is een verificatie-e-mail naar u gestuurd om het instellen van uw nieuwe wachtwoord te bevestigen.", - "Sign in instead": "Aanmelden", + "Sign in instead": "In plaats daarvan inloggen", "Your password has been reset.": "Uw wachtwoord is opnieuw ingesteld.", "Set a new password": "Stel een nieuw wachtwoord in", "Invalid homeserver discovery response": "Ongeldig homeserver-vindbaarheids-antwoord", @@ -1202,13 +1202,13 @@ "This homeserver does not support login using email address.": "Deze homeserver biedt geen ondersteuning voor inloggen met e-mailadres.", "Please contact your service administrator to continue using this service.": "Gelieve contact op te nemen met uw dienstbeheerder om deze dienst te blijven gebruiken.", "Failed to perform homeserver discovery": "Ontdekken van homeserver is mislukt", - "Sign in with single sign-on": "Aanmelden met eenmalige aanmelding", + "Sign in with single sign-on": "Inloggen met eenmalig inloggen", "Create account": "Account aanmaken", "Registration has been disabled on this homeserver.": "Registratie is uitgeschakeld op deze homeserver.", "Unable to query for supported registration methods.": "Kan ondersteunde registratiemethoden niet opvragen.", "Create your account": "Maak uw account aan", "Keep going...": "Doe verder…", - "For maximum security, this should be different from your account password.": "Voor maximale veiligheid zou dit moeten verschillen van uw accountwachtwoord.", + "For maximum security, this should be different from your account password.": "Voor maximale veiligheid moet dit verschillen van uw accountwachtwoord.", "That matches!": "Dat komt overeen!", "That doesn't match.": "Dat komt niet overeen.", "Go back to set it again.": "Ga terug om het opnieuw in te stellen.", @@ -1228,11 +1228,11 @@ "Don't ask again": "Niet opnieuw vragen", "New Recovery Method": "Nieuwe herstelmethode", "A new recovery passphrase and key for Secure Messages have been detected.": "Er zijn een nieuw herstelwachtwoord en een nieuwe herstelsleutel voor beveiligde berichten gedetecteerd.", - "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Als u deze nieuwe herstelmethode niet heeft ingesteld, is het mogelijk dat een aanvaller toegang tot uw account probeert te krijgen. Wijzig onmiddellijk uw accountwachtwoord en stel in het instellingenmenu een nieuwe herstelmethode in.", + "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Als u deze nieuwe herstelmethode niet heeft ingesteld, is het mogelijk dat een aanvaller toegang tot uw account probeert te krijgen. Wijzig onmiddellijk uw wachtwoord en stel bij instellingen een nieuwe herstelmethode in.", "Go to Settings": "Ga naar instellingen", "Set up Secure Messages": "Beveiligde berichten instellen", "Recovery Method Removed": "Herstelmethode verwijderd", - "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Als u de herstelmethode niet heeft verwijderd, is het mogelijk dat er een aanvaller toegang tot uw account probeert te verkrijgen. Wijzig onmiddellijk uw accountwachtwoord en stel in het instellingenmenu een nieuwe herstelmethode in.", + "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Als u de herstelmethode niet heeft verwijderd, is het mogelijk dat er een aanvaller toegang tot uw account probeert te verkrijgen. Wijzig onmiddellijk uw wachtwoord en stel bij instellingen een nieuwe herstelmethode in.", "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Let op: gesprekken bijwerken voegt gespreksleden niet automatisch toe aan de nieuwe versie van het gesprek. Er komt in het oude gesprek een koppeling naar het nieuwe, waarop gespreksleden moeten klikken om aan het nieuwe gesprek deel te nemen.", "Adds a custom widget by URL to the room": "Voegt met een URL een aangepaste widget toe aan het gesprek", "Please supply a https:// or http:// widget URL": "Voer een https://- of http://-widget-URL in", @@ -1264,8 +1264,8 @@ "GitHub issue": "GitHub-melding", "Notes": "Opmerkingen", "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.": "Gelieve alle verdere informatie die zou kunnen helpen het probleem te analyseren (wat u aan het doen was, relevante gespreks-ID’s, gebruikers-ID’s, enz.) bij te voegen.", - "Sign out and remove encryption keys?": "Afmelden en versleutelingssleutels verwijderen?", - "To help us prevent this in future, please send us logs.": "Gelieve ons logboeken te sturen om dit in de toekomst te helpen voorkomen.", + "Sign out and remove encryption keys?": "Uitloggen en versleutelingssleutels verwijderen?", + "To help us prevent this in future, please send us logs.": "Stuur ons uw logs om dit in de toekomst te helpen voorkomen.", "Missing session data": "Sessiegegevens ontbreken", "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Sommige sessiegegevens, waaronder sleutels voor versleutelde berichten, ontbreken. Herstel de sleutels uit uw back-up door u af- en weer aan te melden.", "Your browser likely removed this data when running low on disk space.": "Uw browser heeft deze gegevens wellicht verwijderd toen de beschikbare opslagruimte vol was.", @@ -1297,7 +1297,7 @@ "Rejecting invite …": "Uitnodiging wordt geweigerd…", "Join the conversation with an account": "Neem deel aan het gesprek met een account", "Sign Up": "Registreren", - "Sign In": "Aanmelden", + "Sign In": "Inloggen", "You were kicked from %(roomName)s by %(memberName)s": "U bent uit %(roomName)s gezet door %(memberName)s", "Reason: %(reason)s": "Reden: %(reason)s", "Forget this room": "Dit gesprek vergeten", @@ -1315,7 +1315,7 @@ "%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s kan niet vooraf bekeken worden. Wilt u eraan deelnemen?", "This room doesn't exist. Are you sure you're at the right place?": "Dit gesprek bestaat niet. Weet u zeker dat u zich op de juiste plaats bevindt?", "Try again later, or ask a room admin to check if you have access.": "Probeer het later opnieuw, of vraag een gespreksbeheerder om te controleren of u wel toegang heeft.", - "%(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.": "De foutcode %(errcode)s is weergegeven bij het toetreden van het gesprek. Als u meent dat u dit bericht foutief te zien krijgt, gelieve dan een foutmelding in te dienen.", + "%(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.": "De foutcode %(errcode)s is weergegeven bij het toetreden van het gesprek. Als u meent dat u dit bericht foutief te zien krijgt, gelieve dan een bugmelding indienen.", "This room has already been upgraded.": "Dit gesprek is reeds geüpgraded.", "reacted with %(shortName)s": "heeft gereageerd met %(shortName)s", "edited": "bewerkt", @@ -1386,8 +1386,8 @@ "Resend edit": "Bewerking opnieuw versturen", "Resend %(unsentCount)s reaction(s)": "%(unsentCount)s reactie(s) opnieuw versturen", "Resend removal": "Verwijdering opnieuw versturen", - "Failed to re-authenticate due to a homeserver problem": "Opnieuw aanmelden is mislukt wegens een probleem met de homeserver", - "Failed to re-authenticate": "Opnieuw aanmelden is mislukt", + "Failed to re-authenticate due to a homeserver problem": "Opnieuw inloggen is mislukt wegens een probleem met de homeserver", + "Failed to re-authenticate": "Opnieuw inloggen is mislukt", "Enter your password to sign in and regain access to your account.": "Voer uw wachtwoord in om u aan te melden en toegang tot uw account te herkrijgen.", "Forgotten your password?": "Wachtwoord vergeten?", "You're signed out": "U bent afgemeld", @@ -1401,7 +1401,7 @@ "Service": "Dienst", "Summary": "Samenvatting", "Sign in and regain access to your account.": "Meld u aan en herkrijg toegang tot uw account.", - "You cannot sign in to your account. Please contact your homeserver admin for more information.": "U kunt zich niet aanmelden met uw account. Neem voor meer informatie contact op met de beheerder van uw homeserver.", + "You cannot sign in to your account. Please contact your homeserver admin for more information.": "U kunt niet inloggen met uw account. Neem voor meer informatie contact op met de beheerder van uw homeserver.", "This account has been deactivated.": "Deze account is gesloten.", "Messages": "Berichten", "Actions": "Acties", @@ -1478,11 +1478,11 @@ "No recent messages by %(user)s found": "Geen recente berichten door %(user)s gevonden", "Try scrolling up in the timeline to see if there are any earlier ones.": "Probeer omhoog te scrollen in de tijdslijn om te kijken of er eerdere zijn.", "Remove recent messages by %(user)s": "Recente berichten door %(user)s verwijderen", - "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "U staat op het punt %(count)s berichten door %(user)s te verwijderen. Dit is onherroepelijk. Wilt u doorgaan?", + "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "U staat op het punt %(count)s berichten van %(user)s te verwijderen. Dit kan niet teruggedraaid worden. Wilt u doorgaan?", "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "Bij een groot aantal berichten kan dit even duren. Herlaad uw cliënt niet gedurende deze tijd.", "Remove %(count)s messages|other": "%(count)s berichten verwijderen", "Deactivate user?": "Gebruiker deactiveren?", - "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Deze gebruiker deactiveren zal deze gebruiker uitloggen en verhinderen dat de gebruiker weer inlogt. Bovendien zal de gebruiker alle gesprekken waaraan de gebruiker deelneemt verlaten. Deze actie is onherroepelijk. Weet u zeker dat u deze gebruiker wilt deactiveren?", + "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Deze gebruiker deactiveren zal deze gebruiker uitloggen en verhinderen dat de gebruiker weer inlogt. Bovendien zal de gebruiker alle gesprekken waaraan de gebruiker deelneemt verlaten. Deze actie is niet terug te draaien. Weet u zeker dat u deze gebruiker wilt deactiveren?", "Deactivate user": "Gebruiker deactiveren", "Remove recent messages": "Recente berichten verwijderen", "Bold": "Vet", @@ -1523,7 +1523,7 @@ "%(count)s unread messages.|other": "%(count)s ongelezen berichten.", "Unread mentions.": "Ongelezen vermeldingen.", "Show image": "Afbeelding tonen", - "Please create a new issue on GitHub so that we can investigate this bug.": "Maak een nieuw rapport aan op GitHub opdat we dit probleem kunnen onderzoeken.", + "Please create a new issue on GitHub so that we can investigate this bug.": "Maak een nieuwe issue aan op GitHub zodat we deze bug kunnen onderzoeken.", "e.g. my-room": "bv. mijn-gesprek", "Close dialog": "Dialoog sluiten", "Please enter a name for the room": "Geef een naam voor het gesprek op", @@ -1549,7 +1549,7 @@ "Click the link in the email you received to verify and then click continue again.": "Open de koppeling in de ontvangen verificatie-e-mail, en klik dan op ‘Doorgaan’.", "%(creator)s created and configured the room.": "Gesprek gestart en ingesteld door %(creator)s.", "Setting up keys": "Sleutelconfiguratie", - "Verify this session": "Deze sessie verifiëren", + "Verify this session": "Verifieer deze sessie", "Encryption upgrade available": "Versleutelingsupgrade beschikbaar", "You can use /help to list available commands. Did you mean to send this as a message?": "Typ /help om alle opdrachten te zien. Was het uw bedoeling dit als bericht te sturen?", "Help": "Hulp", @@ -1621,7 +1621,7 @@ "Upgrade": "Upgraden", "Verify": "Verifiëren", "Later": "Later", - "Review": "Controle", + "Review": "Controleer", "Decline (%(counter)s)": "Afwijzen (%(counter)s)", "This bridge was provisioned by .": "Dank aan voor de brug.", "This bridge is managed by .": "Brug onderhouden door .", @@ -1636,14 +1636,14 @@ "Unable to load session list": "Kan sessielijst niet laden", "Delete %(count)s sessions|other": "%(count)s sessies verwijderen", "Delete %(count)s sessions|one": "%(count)s sessie verwijderen", - "Whether you're using %(brand)s on a device where touch is the primary input mechanism": "Of je %(brand)s op een apparaat gebruikt waarop een aanraakscherm de voornaamste invoermethode is", - "Whether you're using %(brand)s as an installed Progressive Web App": "Of je %(brand)s gebruikt als een geïnstalleerde Progressive-Web-App", + "Whether you're using %(brand)s on a device where touch is the primary input mechanism": "Of u %(brand)s op een apparaat gebruikt waarop een aanraakscherm de voornaamste invoermethode is", + "Whether you're using %(brand)s as an installed Progressive Web App": "Of u %(brand)s gebruikt als een geïnstalleerde Progressieve Web-App", "Your user agent": "Jouw gebruikersagent", "If you cancel now, you won't complete verifying the other user.": "Als u nu annuleert zult u de andere gebruiker niet verifiëren.", "If you cancel now, you won't complete verifying your other session.": "Als u nu annuleert zult u uw andere sessie niet verifiëren.", "Cancel entering passphrase?": "Wachtwoord annuleren?", "Show typing notifications": "Typmeldingen weergeven", - "Verify this session by completing one of the following:": "Verifieer deze sessie door een van het volgende te doen:", + "Verify this session by completing one of the following:": "Verifieer deze sessie door een van het volgende handelingen te doen:", "Scan this unique code": "Scan deze unieke code", "or": "of", "Compare unique emoji": "Vergelijk unieke emoji", @@ -1746,7 +1746,7 @@ "Clear notifications": "Meldingen wissen", "You should remove your personal data from identity server before disconnecting. Unfortunately, identity server is currently offline or cannot be reached.": "U moet uw persoonlijke informatie van de identiteitsserver verwijderen voordat u zich ontkoppelt. Helaas kan de identiteitsserver op dit moment niet worden bereikt. Mogelijk is hij offline.", "Your homeserver does not support cross-signing.": "Uw homeserver biedt geen ondersteuning voor kruiselings ondertekenen.", - "Homeserver feature support:": "Homeserver ondersteund deze functies:", + "Homeserver feature support:": "Homeserver functie ondersteuning:", "exists": "aanwezig", "Sign In or Create Account": "Meld u aan of maak een account aan", "Use your account or create a new one to continue.": "Gebruik uw bestaande account of maak een nieuwe aan om verder te gaan.", @@ -1872,10 +1872,10 @@ "More options": "Meer opties", "Language Dropdown": "Taalselectie", "Destroy cross-signing keys?": "Sleutels voor kruiselings ondertekenen verwijderen?", - "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.": "Het verwijderen van sleutels voor kruiselings ondertekenen is onherroepelijk. Iedereen waarmee u geverifieerd heeft zal beveiligingswaarschuwingen te zien krijgen. U wilt dit hoogstwaarschijnlijk niet doen, tenzij u alle apparaten heeft verloren waarmee u kruiselings kon ondertekenen.", + "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.": "Het verwijderen van sleutels voor kruiselings ondertekenen is niet terug te draaien. Iedereen waarmee u geverifieerd heeft zal beveiligingswaarschuwingen te zien krijgen. U wilt dit hoogstwaarschijnlijk niet doen, tenzij u alle apparaten heeft verloren waarmee u kruiselings kon ondertekenen.", "Clear cross-signing keys": "Sleutels voor kruiselings ondertekenen wissen", "Clear all data in this session?": "Alle gegevens in deze sessie verwijderen?", - "Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Het verwijderen van alle gegevens in deze sessie is onherroepelijk. Versleutelde berichten zullen verloren gaan, tenzij u een back-up van de sleutels heeft.", + "Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Het verwijderen van alle gegevens in deze sessie is niet terug te draaien. Versleutelde berichten zullen verloren gaan, tenzij u een back-up van de sleutels heeft.", "Verify session": "Sessie verifiëren", "Session name": "Sessienaam", "Session key": "Sessiesleutel", @@ -1909,7 +1909,7 @@ "Automatically invite users": "Gebruikers automatisch uitnodigen", "Upgrade private room": "Privégesprek upgraden", "Upgrade public room": "Openbaar gesprek upgraden", - "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Het bijwerken van een gesprek is een gevorderde actie en wordt meestal aanbevolen wanneer een gesprek onstabiel is door fouten, ontbrekende functies of problemen met de beveiliging.", + "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Het bijwerken van een gesprek is een gevorderde actie en wordt meestal aanbevolen wanneer een gesprek onstabiel is door bugs, ontbrekende functies of problemen met de beveiliging.", "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.": "Dit heeft meestal enkel een invloed op de manier waarop het gesprek door de server verwerkt wordt. Als u problemen met uw %(brand)s ondervindt, dien dan een foutmelding in.", "You'll upgrade this room from to .": "U upgrade dit gesprek van naar .", "This will allow you to return to your account after signing out, and sign in on other sessions.": "Daardoor kunt u na afmelding terugkeren tot uw account, en u bij andere sessies aanmelden.", @@ -1927,7 +1927,7 @@ "Remove for me": "Verwijderen voor mezelf", "User Status": "Gebruikersstatus", "Country Dropdown": "Landselectie", - "Confirm your identity by entering your account password below.": "Bevestig uw identiteit door hieronder uw accountwachtwoord in te voeren.", + "Confirm your identity by entering your account password below.": "Bevestig uw identiteit door hieronder uw wachtwoord in te voeren.", "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "Er is geen identiteitsserver geconfigureerd, dus u kunt geen e-mailadres toevoegen om in de toekomst een nieuw wachtwoord in te stellen.", "Jump to first unread room.": "Ga naar het eerste ongelezen gesprek.", "Jump to first invite.": "Ga naar de eerste uitnodiging.", @@ -1939,13 +1939,13 @@ "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.": "Door uw wachtwoord te wijzigen stelt u alle eind-tot-eind-versleutelingssleutels op al uw sessies opnieuw in, waardoor uw versleutelde gespreksgeschiedenis onleesbaar wordt. Stel uw sleutelback-up in of sla uw gesprekssleutels van een andere sessie op voor u een nieuw wachtwoord instelt.", "You have been logged out of all sessions and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "U bent uitgelogd bij al uw sessies en zult geen pushberichten meer ontvangen. Meld u op elk apparaat opnieuw aan om meldingen opnieuw in te schakelen.", "Regain access to your account and recover encryption keys stored in this session. Without them, you won’t be able to read all of your secure messages in any session.": "Ontvang toegang tot uw account en herstel de tijdens deze sessie opgeslagen versleutelingssleutels, zonder deze sleutels zijn sommige van uw versleutelde berichten in uw sessies onleesbaar.", - "Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.": "Let op: uw persoonlijke gegevens (waaronder versleutelingssleutels) zijn nog steeds opgeslagen in deze sessie. Wis ze wanneer u klaar bent met deze sessie, of wanneer u zich wilt aanmelden met een andere account.", + "Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.": "Let op: uw persoonlijke gegevens (waaronder versleutelingssleutels) zijn nog steeds opgeslagen in deze sessie. Wis ze wanneer u klaar bent met deze sessie, of wanneer u wilt inloggen met een andere account.", "Command Autocomplete": "Opdrachten autoaanvullen", "DuckDuckGo Results": "DuckDuckGo-resultaten", - "Enter your account password to confirm the upgrade:": "Voer uw accountwachtwoord in om het upgraden te bevestigen:", + "Enter your account password to confirm the upgrade:": "Voer uw wachtwoord in om het upgraden te bevestigen:", "Restore your key backup to upgrade your encryption": "Herstel uw sleutelback-up om uw versleuteling te upgraden", "Restore": "Herstellen", - "You'll need to authenticate with the server to confirm the upgrade.": "U zult zich moeten aanmelden bij de server om het upgraden te bevestigen.", + "You'll need to authenticate with the server to confirm the upgrade.": "U zult moeten inloggen bij de server om het upgraden te bevestigen.", "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 deze sessie om er andere sessies mee te verifiëren, waardoor deze ook de toegang verkrijgen tot uw versleutelde berichten en deze voor andere gebruikers als vertrouwd gemarkeerd worden.", "Set up with a recovery key": "Instellen met een herstelsleutel", "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Bewaar een kopie op een veilige plaats, zoals in een wachtwoordbeheerder of een kluis.", @@ -1970,7 +1970,7 @@ "To report a Matrix-related security issue, please read the Matrix.org Security Disclosure Policy.": "Bekijk eerst het beveiligingsopenbaarmakingsbeleid van Matrix.org als u een probleem met de beveiliging van Matrix wilt melden.", "Not currently indexing messages for any room.": "Er worden momenteel voor geen enkel gesprek berichten geïndexeerd.", "%(doneRooms)s out of %(totalRooms)s": "%(doneRooms)s van %(totalRooms)s", - "Where you’re logged in": "Waar u ingelogd bent", + "Where you’re logged in": "Waar u bent ingelogd", "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Beheer hieronder de namen van uw sessies en meld ze af. Of verifieer ze in uw gebruikersprofiel.", "Use Single Sign On to continue": "Ga verder met eenmalige aanmelding", "Confirm adding this email address by using Single Sign On to prove your identity.": "Bevestig je identiteit met je eenmalige aanmelding om dit e-mailadres toe te voegen.", @@ -1989,7 +1989,7 @@ "Command failed": "Opdracht mislukt", "Could not find user in room": "Kon die deelnemer aan het gesprek niet vinden", "Please supply a widget URL or embed code": "Gelieve een widgetURL of in te bedden code te geven", - "Send a bug report with logs": "Rapporteer een fout, met foutopsporingslogboek bijgesloten", + "Send a bug report with logs": "Stuur een bugrapport met logs", "%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.": "%(senderDisplayName)s heeft het gesprek %(oldRoomName)s hernoemd tot %(newRoomName)s.", "%(senderName)s added the alternative addresses %(addresses)s for this room.|other": "%(senderName)s heeft dit gesprek de nevenadressen %(addresses)s toegekend.", "%(senderName)s added the alternative addresses %(addresses)s for this room.|one": "%(senderName)s heeft dit gesprek het nevenadres %(addresses)s toegekend.", @@ -2315,7 +2315,7 @@ "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.": "Uw homeserver wees uw inlogpoging af. Dit kan zijn doordat het te lang heeft geduurd. Probeer het opnieuw. Als dit probleem zich blijft voordoen, neem contact op met de beheerder van uw homeserver.", "Your homeserver was unreachable and was not able to log you in. Please try again. If this continues, please contact your homeserver administrator.": "Uw homeserver was onbereikbaar en kon u niet inloggen, probeer het opnieuw. Wanneer dit probleem zich blijft voordoen, neem contact op met de beheerder van uw homeserver.", "Try again": "Probeer opnieuw", - "We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.": "De browser is verzocht uw homeserver te onthouden die u gebruikt om zich aan te melden, maar is deze vergeten. Ga naar de aanmeldpagina en probeer het opnieuw.", + "We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.": "De browser is verzocht uw homeserver te onthouden die u gebruikt om in te loggen, maar helaas heeft de browser deze vergeten. Ga naar de inlog-pagina en probeer het opnieuw.", "We couldn't log you in": "We konden u niet inloggen", "Room Info": "Gespreksinfo", "Matrix.org is the biggest public homeserver in the world, so it’s a good place for many.": "Matrix.org is de grootste openbare homeserver van de wereld, dus het is een goede plek voor vele.", @@ -2416,8 +2416,8 @@ "sends snowfall": "Stuur sneeuwvlokken", "sends confetti": "verstuurt confetti", "sends fireworks": "Stuur vuurwerk", - "Downloading logs": "Logboeken downloaden", - "Uploading logs": "Logboeken versturen", + "Downloading logs": "Logs downloaden", + "Uploading logs": "Logs uploaden", "Use Ctrl + Enter to send a message": "Gebruik Ctrl + Enter om een bericht te sturen", "Use Command + Enter to send a message": "Gebruik Command (⌘) + Enter om een bericht te sturen", "Use Ctrl + F to search": "Ctrl + F om te zoeken gebruiken", @@ -2425,7 +2425,7 @@ "Use a more compact ‘Modern’ layout": "Compacte 'Modern'-layout inschakelen", "Use custom size": "Aangepaste lettergrootte gebruiken", "Font size": "Lettergrootte", - "Enable advanced debugging for the room list": "Geavanceerde foutopsporing voor de gesprekkenlijst inschakelen", + "Enable advanced debugging for the room list": "Geavanceerde bugopsporing voor de gesprekkenlijst inschakelen", "Render LaTeX maths in messages": "Weergeef LaTeX-wiskundenotatie in berichten", "Change notification settings": "Meldingsinstellingen wijzigen", "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", @@ -2449,7 +2449,7 @@ "%(senderName)s has updated the widget layout": "%(senderName)s heeft de widget-indeling bijgewerkt", "%(senderName)s declined the call.": "%(senderName)s heeft de oproep afgewezen.", "(an error occurred)": "(een fout is opgetreden)", - "You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "U heeft eerder een nieuwere versie van %(brand)s in deze sessie gebruikt. Om deze versie opnieuw met eind-tot-eind-versleuteling te gebruiken, zult u zich moeten afmelden en opnieuw aanmelden.", + "You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "U heeft eerder een nieuwere versie van %(brand)s in deze sessie gebruikt. Om deze versie opnieuw met eind-tot-eind-versleuteling te gebruiken, zult u moeten uitloggen en opnieuw inloggen.", "Block anyone not part of %(serverName)s from ever joining this room.": "Weiger iedereen die geen deel uitmaakt van %(serverName)s aan dit gesprek deel te nemen.", "Create a room in %(communityName)s": "Een gesprek aanmaken in %(communityName)s", "Enable end-to-end encryption": "Eind-tot-eind-versleuteling inschakelen", @@ -2467,7 +2467,7 @@ "Show": "Toon", "People you know on %(brand)s": "Personen die u kent van %(brand)s", "Add another email": "Nog een e-mailadres toevoegen", - "Download logs": "Download logboeken", + "Download logs": "Logs downloaden", "Add a new server...": "Een nieuwe server toevoegen…", "Server name": "Servernaam", "Add a new server": "Een nieuwe server toevoegen", @@ -2479,8 +2479,8 @@ "Enter a server name": "Geef een servernaam", "Continue with %(provider)s": "Doorgaan met %(provider)s", "Homeserver": "Homeserver", - "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use Element with an existing Matrix account on a different homeserver.": "U kunt de aangepaste serverinstellingen gebruiken om u aan te melden bij andere Matrix-servers, door een andere homeserver-URL in te voeren. Dit laat u toe Element te gebruiken met een bestaande Matrix-account bij een andere homeserver.", - "Server Options": "Serverinstellingen", + "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use Element with an existing Matrix account on a different homeserver.": "U kunt de server opties wijzigen om in te loggen bij andere Matrix-servers, wijzig hiervoor de homeserver-URL. Hiermee kunt u Element gebruiken met een al bestaand Matrix-account van een andere homeserver.", + "Server Options": "Server opties", "This address is already in use": "Dit adres is al in gebruik", "This address is available to use": "Dit adres kan worden gebruikt", "Please provide a room address": "Geef een gespreksadres", @@ -2539,7 +2539,7 @@ "Invite by email": "Via e-mail uitnodigen", "Click the button below to confirm your identity.": "Druk op de knop hieronder om uw identiteit te bevestigen.", "Confirm to continue": "Bevestig om door te gaan", - "Report a bug": "Een fout rapporteren", + "Report a bug": "Een bug rapporteren", "Comment": "Opmerking", "Add comment": "Opmerking toevoegen", "Tell us below how you feel about %(brand)s so far.": "Vertel ons hoe %(brand)s u tot dusver bevalt.", @@ -2640,7 +2640,7 @@ "Use this when referencing your community to others. The community ID cannot be changed.": "Gebruik dit om anderen naar uw gemeenschap te verwijzen. De gemeenschaps-ID kan later niet meer veranderd worden.", "Please go into as much detail as you like, so we can track down the problem.": "Gebruik a.u.b. zoveel mogelijk details, zodat wij uw probleem kunnen vinden.", "There are two ways you can provide feedback and help us improve %(brand)s.": "U kunt op twee manieren feedback geven en ons helpen %(brand)s te verbeteren.", - "Please view existing bugs on Github first. No match? Start a new one.": "Bekijk eerst de bestaande problemen op Github. Maak een nieuwe aan wanneer u uw probleem niet heeft gevonden.", + "Please view existing bugs on Github first. No match? Start a new one.": "Bekijk eerst de bestaande bugs op GitHub. Maak een nieuwe aan wanneer u uw bugs niet heeft gevonden.", "Invite someone using their name, email address, username (like ) or share this room.": "Nodig iemand uit door gebruik te maken van hun naam, e-mailadres, gebruikersnaam (zoals ) of deel dit gesprek.", "Invite someone using their name, username (like ) or share this room.": "Nodig iemand uit door gebruik te maken van hun naam, gebruikersnaam (zoals ) of deel dit gesprek.", "Send feedback": "Feedback versturen", @@ -2738,13 +2738,13 @@ "Use Security Key or Phrase": "Gebruik veiligheidssleutel of -wachtwoord", "Decide where your account is hosted": "Kies waar uw account wordt gehost", "Host account on": "Host uw account op", - "Already have an account? Sign in here": "Heeft u al een account? Aanmelden", + "Already have an account? Sign in here": "Heeft u al een account? Inloggen", "%(ssoButtons)s Or %(usernamePassword)s": "%(ssoButtons)s of %(usernamePassword)s", "Continue with %(ssoButtons)s": "Ga verder met %(ssoButtons)s", "That username already exists, please try another.": "Die gebruikersnaam bestaat al, probeer een andere.", "New? Create account": "Nieuw? Maak een account aan", "If you've joined lots of rooms, this might take a while": "Als u zich bij veel gesprekken heeft aangesloten, kan dit een tijdje duren", - "Signing In...": "Aanmelden...", + "Signing In...": "Inloggen...", "Syncing...": "Synchroniseren...", "There was a problem communicating with the homeserver, please try again later.": "Er was een communicatieprobleem met de homeserver, probeer het later opnieuw.", "Community and user menu": "Gemeenschaps- en gebruikersmenu", @@ -2754,7 +2754,7 @@ "User settings": "Gebruikersinstellingen", "Security & privacy": "Veiligheid & privacy", "New here? Create an account": "Nieuw hier? Maak een account", - "Got an account? Sign in": "Heeft u een account? Aanmelden", + "Got an account? Sign in": "Heeft u een account? Inloggen", "Failed to find the general chat for this community": "De algemene chat voor deze gemeenschap werd niet gevonden", "Filter rooms and people": "Gespreken en personen filteren", "Explore rooms in %(communityName)s": "Ontdek de gesprekken van %(communityName)s", @@ -2774,8 +2774,8 @@ "Create community": "Gemeenschap aanmaken", "Attach files from chat or just drag and drop them anywhere in a room.": "Voeg bestanden toe vanuit het gesprek of sleep ze in een gesprek.", "No files visible in this room": "Geen bestanden zichtbaar in dit gesprek", - "Sign in with SSO": "Aanmelden met SSO", - "Use email to optionally be discoverable by existing contacts.": "Gebruik e-mail om optioneel ontdekt te worden door bestaande contacten.", + "Sign in with SSO": "Inloggen met SSO", + "Use email to optionally be discoverable by existing contacts.": "Gebruik e-mail ook om optioneel ontdekt te worden door bestaande contacten.", "Use email or phone to optionally be discoverable by existing contacts.": "Gebruik e-mail of telefoon om optioneel ontdekt te kunnen worden door bestaande contacten.", "Add an email to be able to reset your password.": "Voeg een e-mail toe om uw wachtwoord te kunnen resetten.", "Forgot password?": "Wachtwoord vergeten?", @@ -2852,7 +2852,7 @@ "Continuing temporarily allows the %(hostSignupBrand)s setup process to access your account to fetch verified email addresses. This data is not stored.": "Door tijdelijk door te gaan, krijgt het installatieproces van %(hostSignupBrand)s toegang tot uw account om geverifieerde e-mailadressen op te halen. Deze gegevens worden niet opgeslagen.", "Failed to connect to your homeserver. Please close this dialog and try again.": "Kan geen verbinding maken met uw homeserver. Sluit dit dialoogvenster en probeer het opnieuw.", "Are you sure you wish to abort creation of the host? The process cannot be continued.": "Weet u zeker dat u het aanmaken van de host wilt afbreken? Het proces kan niet worden voortgezet.", - "PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.": "PRO TIP: Als u een bug start, stuur ons dan debug logs om ons te helpen het probleem op te sporen.", + "PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.": "PRO TIP: Als u een nieuwe bug maakt, stuur ons dan uw foutenlogboek om ons te helpen het probleem op te sporen.", "There was an error updating your community. The server is unable to process your request.": "Er is een fout opgetreden bij het updaten van uw gemeenschap. De server is niet in staat om uw verzoek te verwerken.", "There was an error finding this widget.": "Er is een fout opgetreden bij het vinden van deze widget.", "Server did not return valid authentication information.": "Server heeft geen geldige verificatiegegevens teruggestuurd.", @@ -2889,7 +2889,7 @@ "Submit logs": "Logs versturen", "Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.": "Berichten in dit gesprek zijn eind-tot-eind-versleuteld. Als personen deelnemen, kan u ze verifiëren in hun profiel, tik hiervoor op hun avatar.", "In encrypted rooms, verify all users to ensure it’s secure.": "Controleer alle gebruikers in versleutelde gesprekken om er zeker van te zijn dat het veilig is.", - "Verify all users in a room to ensure it's secure.": "Controleer alle gebruikers in een gesprek om er zeker van te zijn dat hij veilig is.", + "Verify all users in a room to ensure it's secure.": "Controleer alle gebruikers in een gesprek om er zeker van te zijn dat het veilig is.", "%(count)s people|one": "%(count)s persoon", "Add widgets, bridges & bots": "Widgets, bruggen & bots toevoegen", "Edit widgets, bridges & bots": "Widgets, bruggen & bots bewerken", @@ -2921,10 +2921,10 @@ "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.": "%(brand)s kan versleutelde berichten niet veilig lokaal opslaan in een webbrowser. Gebruik %(brand)s Desktop om versleutelde berichten in zoekresultaten te laten verschijnen.", "Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(rooms)s rooms.|one": "Veilig lokaal opslaan van versleutelde berichten zodat ze in de zoekresultaten verschijnen, gebruik %(size)s voor het opslaan van berichten uit %(rooms)s gesprek.", "Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(rooms)s rooms.|other": "Veilig lokaal opslaan van versleutelde berichten zodat ze in de zoekresultaten verschijnen, gebruik %(size)s voor het opslaan van berichten uit %(rooms)s gesprekken.", - "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Elke sessie die door een gebruiker wordt gebruikt, afzonderlijk verifiëren om deze als vertrouwd aan te merken, waarbij geen vertrouwen wordt gesteld in kruiselings ondertekende apparaten.", - "User signing private key:": "Gebruiker ondertekening privésleutel:", - "Master private key:": "Hoofd privésleutel:", - "Self signing private key:": "Zelfondertekenende privésleutel:", + "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Verifieer elke sessie die door een gebruiker wordt gebruikt afzonderlijk om deze te markeren als vertrouwd, niet vertrouwend op kruislings ondertekende apparaten.", + "User signing private key:": "Gebruikerondertekening-privésleutel:", + "Master private key:": "Hoofdprivésleutel:", + "Self signing private key:": "Zelfondertekening-privésleutel:", "Cross-signing is not set up.": "Kruiselings ondertekenen is niet ingesteld.", "Cross-signing is ready for use.": "Kruiselings ondertekenen is klaar voor gebruik.", "Your server isn't responding to some requests.": "Uw server reageert niet op sommige verzoeken.", @@ -2941,13 +2941,13 @@ "Minimize dialog": "Dialoog minimaliseren", "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "Als u nu annuleert, kunt u versleutelde berichten en gegevens verliezen als u geen toegang meer heeft tot uw login.", "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Bevestig uw identiteit door deze login te verifiëren vanuit een van uw andere sessies, waardoor u toegang krijgt tot uw versleutelde berichten.", - "Verify this login": "Controleer deze login", + "Verify this login": "Deze inlog verifiëren", "To continue, use Single Sign On to prove your identity.": "Om verder te gaan, gebruik uw eenmalige aanmelding om uw identiteit te bewijzen.", "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Plakt ( ͡° ͜ʖ ͡°) vóór een bericht zonder opmaak", "Prepends ┬──┬ ノ( ゜-゜ノ) to a plain-text message": "Plakt ┬──┬ ノ( ゜-゜ノ) vóór een bericht zonder opmaak", "Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message": "Plakt (╯°□°)╯︵ ┻━┻ vóór een bericht zonder opmaak", "Liberate your communication": "Bevrijd uw communicatie", - "Create a Group Chat": "Maak een groepsgesprek aan", + "Create a Group Chat": "Maak een groepsgesprek", "Send a Direct Message": "Start een direct gesprek", "Welcome to %(appName)s": "Welkom bij %(appName)s", "Add a topic to help people know what it is about.": "Stel een gespreksonderwerp in zodat de personen weten waar het over gaat.", @@ -3024,7 +3024,7 @@ "Start audio stream": "Audio-stream starten", "Failed to start livestream": "Starten van livestream is mislukt", "Unable to start audio streaming.": "Kan audio-streaming niet starten.", - "Save Changes": "Wijzigingen Opslaan", + "Save Changes": "Wijzigingen opslaan", "Saving...": "Opslaan...", "View dev tools": "Bekijk dev tools", "Leave Space": "Space verlaten", @@ -3136,7 +3136,7 @@ "A new login is accessing your account: %(name)s (%(deviceID)s) at %(ip)s": "Een nieuwe login heeft toegang tot uw account: %(name)s (%(deviceID)s) op %(ip)s", "You have unverified logins": "U heeft ongeverifieerde logins", "Without verifying, you won’t have access to all your messages and may appear as untrusted to others.": "Zonder verifiëren heeft u geen toegang tot al uw berichten en kan u als onvertrouwd aangemerkt staan bij anderen.", - "Verify your identity to access encrypted messages and prove your identity to others.": "Verifeer uw identiteit om toegang te krijgen tot uw versleutelde berichten en uw identiteit te bewijzen voor anderen.", + "Verify your identity to access encrypted messages and prove your identity to others.": "Verifeer uw identiteit om toegang te krijgen tot uw versleutelde berichten en om uw identiteit te bewijzen voor anderen.", "Use another login": "Gebruik andere login", "Please choose a strong password": "Kies een sterk wachtwoord", "You can add more later too, including already existing ones.": "U kunt er later nog meer toevoegen, inclusief al bestaande gesprekken.", @@ -3170,7 +3170,7 @@ "Share decryption keys for room history when inviting users": "Deel ontsleutelsleutels voor de gespreksgeschiedenis wanneer u personen uitnodigd", "Send and receive voice messages (in development)": "Verstuur en ontvang audioberichten (in ontwikkeling)", "%(deviceId)s from %(ip)s": "%(deviceId)s van %(ip)s", - "Review to ensure your account is safe": "Controleer ze voor de zekerheid dat uw account veilig is", + "Review to ensure your account is safe": "Controleer ze zodat uw account veilig is", "Sends the given message as a spoiler": "Verstuurt het bericht als een spoiler", "You are the only person here. If you leave, no one will be able to join in the future, including you.": "U bent de enige persoon hier. Als u weggaat, zal niemand in de toekomst kunnen toetreden, u ook niet.", "If you reset everything, you will restart with no trusted sessions, no trusted users, and might not be able to see past messages.": "Als u alles reset, zult u opnieuw opstarten zonder vertrouwde sessies, zonder vertrouwde gebruikers, en zult u misschien geen vroegere berichten meer kunnen zien.", @@ -3205,5 +3205,40 @@ "%(count)s results in all spaces|other": "%(count)s resultaten in alle spaces", "You have no ignored users.": "U heeft geen gebruiker genegeerd.", "Play": "Afspelen", - "Pause": "Pauze" + "Pause": "Pauze", + "This is an experimental feature. For now, new users receiving an invite will have to open the invite on to actually join.": "Dit is een experimentele functie. Voorlopig moeten nieuwe personen die een uitnodiging krijgen de gebruiken om daadwerkelijk deel te nemen.", + "To join %(spaceName)s, turn on the Spaces beta": "Om aan %(spaceName)s deel te nemen moet u de Spaces beta inschakelen", + "To view %(spaceName)s, turn on the Spaces beta": "Om %(spaceName)s te bekijken moet u de Spaces beta inschakelen", + "Select a room below first": "Start met selecteren van een gesprek hieronder", + "Communities are changing to Spaces": "Gemeenschappen worden vervangen door Spaces", + "Join the beta": "Aan beta deelnemen", + "Leave the beta": "Beta verlaten", + "Beta": "Beta", + "Tap for more info": "Klik voor meer info", + "Spaces is a beta feature": "Spaces zijn in beta", + "Want to add a new room instead?": "Wilt u anders een nieuw gesprek toevoegen?", + "Adding rooms... (%(progress)s out of %(count)s)|one": "Gesprek toevoegen...", + "Adding rooms... (%(progress)s out of %(count)s)|other": "Gesprekken toevoegen... (%(progress)s van %(count)s)", + "Not all selected were added": "Niet alle geselecteerden zijn toegevoegd", + "You can add existing spaces to a space.": "U kunt bestaande spaces toevoegen aan een space.", + "Feeling experimental?": "Zin in een experiment?", + "You are not allowed to view this server's rooms list": "U heeft geen toegang tot deze server zijn gesprekkenlijst", + "Error processing voice message": "Fout bij verwerking spraakbericht", + "We didn't find a microphone on your device. Please check your settings and try again.": "We hebben geen microfoon gevonden op uw apparaat. Controleer uw instellingen en probeer het opnieuw.", + "No microphone found": "Geen microfoon gevonden", + "We were unable to access your microphone. Please check your browser settings and try again.": "We hebben geen toegang tot uw microfoon. Controleer uw browserinstellingen en probeer het opnieuw.", + "Unable to access your microphone": "Geen toegang tot uw microfoon", + "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "Zin in een experiment? Labs is de beste manier om dingen vroeg te krijgen, nieuwe functies uit te testen en ze te helpen vormen voordat ze daadwerkelijk worden gelanceerd. Lees meer.", + "Your access token gives full access to your account. Do not share it with anyone.": "Uw toegangstoken geeft u toegang to uw account. Deel hem niet met anderen.", + "Access Token": "Toegangstoken", + "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Spaces zijn de nieuwe manier om gesprekken en personen te groeperen. Om aan een bestaande space deel te nemen heeft u een uitnodiging nodig.", + "Please enter a name for the space": "Vul een naam in voor deze space", + "Connecting": "Verbinden", + "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Peer-to-peer voor 1op1 oproepen toestaan (als u dit inschakelt kunnen andere personen mogelijk uw ipadres zien)", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Beta beschikbaar voor web, desktop en Android. Sommige functies zijn nog niet beschikbaar op uw homeserver.", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "U kunt de beta elk moment verlaten via instellingen of door op de beta badge hierboven te klikken.", + "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s zal herladen met Spaces ingeschakeld. Gemeenschappen en labels worden verborgen.", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta beschikbaar voor web, desktop en Android. Bedankt dat u de beta wilt proberen.", + "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s zal herladen met Spaces uitgeschakeld. Gemeenschappen en labels zullen weer zichtbaar worden.", + "Spaces are a new way to group rooms and people.": "Spaces zijn de nieuwe manier om gesprekken en personen te groeperen." } From 9e981ae1189b6649fa643abed9cdde6894f9d19b Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 12 May 2021 02:44:24 +0000 Subject: [PATCH 009/174] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2959 of 2959 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 37 ++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 2dff300c18..51ebb36dc0 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -3322,5 +3322,40 @@ "%(count)s results in all spaces|other": "所有空間中有 %(count)s 個結果", "You have no ignored users.": "您沒有忽略的使用者。", "Play": "播放", - "Pause": "暫停" + "Pause": "暫停", + "This is an experimental feature. For now, new users receiving an invite will have to open the invite on to actually join.": "這是實驗性功能。目前,收到邀請的新使用者必須在 上開啟邀請才能真的加入。", + "To join %(spaceName)s, turn on the Spaces beta": "要加入 %(spaceName)s,請開啟空間測試版", + "To view %(spaceName)s, turn on the Spaces beta": "要檢視 %(spaceName)s,開啟空間測試版", + "Select a room below first": "首先選取一個聊天室", + "Communities are changing to Spaces": "社群正在變更為空間", + "Join the beta": "加入測試版", + "Leave the beta": "離開測試版", + "Beta": "測試", + "Tap for more info": "點擊以取得更多資訊", + "Spaces is a beta feature": "空間為測試功能", + "Want to add a new room instead?": "想要新增新聊天室嗎?", + "Adding rooms... (%(progress)s out of %(count)s)|one": "正在新增聊天室……", + "Adding rooms... (%(progress)s out of %(count)s)|other": "正在新增聊天室……(%(count)s 中的第 %(progress)s 個)", + "Not all selected were added": "並非所有選定的都被新增了", + "You can add existing spaces to a space.": "您可以新增既有的空間至空間中。", + "Feeling experimental?": "想要來點實驗嗎?", + "You are not allowed to view this server's rooms list": "您不被允許檢視此伺服器的聊天室清單", + "Error processing voice message": "處理語音訊息時發生錯誤", + "We didn't find a microphone on your device. Please check your settings and try again.": "我們在您的裝置上找不到麥克風。請檢查您的設定並再試一次。", + "No microphone found": "找不到麥克風", + "We were unable to access your microphone. Please check your browser settings and try again.": "我們無法存取您的麥克風。請檢查您的瀏覽器設定並再試一次。", + "Unable to access your microphone": "無法存取您的麥克風", + "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "想要來點實驗嗎?實驗室是儘早取得成果,測試新功能並在實際發佈前協助塑造它們的最佳方式。取得更多資訊。", + "Your access token gives full access to your account. Do not share it with anyone.": "您的存取權杖可給您帳號完整的存取權限。不要將其與任何人分享。", + "Access Token": "存取權杖", + "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "空間是將聊天室與人們分組的一種新方式。要加入既有的空間,您需要邀請。", + "Please enter a name for the space": "請輸入空間名稱", + "Connecting": "正在連線", + "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "允許在 1:1 通話中使用點對點通訊(若您啟用此功能,對方就能看到您的 IP 位置)", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "供網頁、桌面與 Android 使用的測試版。部份功能可能在您的家伺服器上不可用。", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "您可以隨時從設定中退出測試版,或是點擊測試版徽章,例如上面那個。", + "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s 將在啟用空間的情況下重新載入。社群與自訂標籤將會隱藏。", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "測試版可用於網路、桌面與 Android。感謝您試用測試版。", + "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s 將在停用空間的情況下重新載入。社群與自訂標籤將再次可見。", + "Spaces are a new way to group rooms and people.": "空間是將聊天室與人們分組的一種新方式。" } From 3169f75e909ceb4d5d2d7c677efb07b9a1515d5d Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Wed, 12 May 2021 08:21:58 +0000 Subject: [PATCH 010/174] Translated using Weblate (Czech) Currently translated at 100.0% (2959 of 2959 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ --- src/i18n/strings/cs.json | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index c9dd63f637..6d4cb953cc 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -3236,5 +3236,40 @@ "Pause": "Pozastavit", "Enter your Security Phrase a second time to confirm it.": "Zadejte bezpečnostní frázi podruhé a potvrďte ji.", "Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.": "Vyberte místnosti nebo konverzace, které chcete přidat. Toto je prostor pouze pro vás, nikdo nebude informován. Později můžete přidat další.", - "You have no ignored users.": "Nemáte žádné ignorované uživatele." + "You have no ignored users.": "Nemáte žádné ignorované uživatele.", + "This is an experimental feature. For now, new users receiving an invite will have to open the invite on to actually join.": "Jedná se o experimentální funkci. Noví uživatelé, kteří obdrží pozvánku, ji budou muset otevřít na , aby se mohli připojit.", + "To join %(spaceName)s, turn on the Spaces beta": "Pro připojení k %(spaceName)s, zapněte Prostory beta", + "To view %(spaceName)s, turn on the Spaces beta": "Pro zobrazení %(spaceName)s, zapněte Prostory beta", + "Select a room below first": "Nejprve si vyberte místnost níže", + "Communities are changing to Spaces": "Skupiny se mění na Prostory", + "Join the beta": "Připojit se k beta verzi", + "Leave the beta": "Opustit beta verzi", + "Beta": "Beta", + "Tap for more info": "Klepněte pro více informací", + "Spaces is a beta feature": "Prostory jsou beta verze", + "Want to add a new room instead?": "Chcete místo toho přidat novou místnost?", + "Adding rooms... (%(progress)s out of %(count)s)|one": "Přidávání místnosti...", + "Adding rooms... (%(progress)s out of %(count)s)|other": "Přidávání místností... (%(progress)s z %(count)s)", + "Not all selected were added": "Ne všechny vybrané byly přidány", + "You can add existing spaces to a space.": "Do prostoru můžete přidat existující prostory.", + "Feeling experimental?": "Chcete experimentovat?", + "You are not allowed to view this server's rooms list": "Namáte oprávnění zobrazit seznam místností tohoto serveru", + "Error processing voice message": "Chyba při zpracování hlasové zprávy", + "We didn't find a microphone on your device. Please check your settings and try again.": "Ve vašem zařízení nebyl nalezen žádný mikrofon. Zkontrolujte prosím nastavení a zkuste to znovu.", + "No microphone found": "Nebyl nalezen žádný mikrofon", + "We were unable to access your microphone. Please check your browser settings and try again.": "Nepodařilo se získat přístup k vašemu mikrofonu . Zkontrolujte prosím nastavení prohlížeče a zkuste to znovu.", + "Unable to access your microphone": "Nelze získat přístup k mikrofonu", + "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "Chcete experimentovat? Laboratoře jsou nejlepším způsobem, jak získat novinky v raném stádiu, vyzkoušet nové funkce a pomoci je formovat ještě před jejich spuštěním. Zjistěte více.", + "Your access token gives full access to your account. Do not share it with anyone.": "Přístupový token vám umožní plný přístup k účtu. Nikomu ho nesdělujte.", + "Access Token": "Přístupový token", + "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Prostory představují nový způsob seskupování místností a osob. Chcete-li se připojit k existujícímu prostoru, potřebujete pozvánku.", + "Please enter a name for the space": "Zadejte prosím název prostoru", + "Connecting": "Spojování", + "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Povolit Peer-to-Peer pro hovory 1:1 (pokud tuto funkci povolíte, druhá strana může vidět vaši IP adresu)", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Beta verze je k dispozici pro web, desktop a Android. Některé funkce mohou být na vašem domovském serveru nedostupné.", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "Beta verzi můžete kdykoli opustit v nastavení nebo klepnutím na štítek beta verze, jako je ten výše.", + "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s se znovu načte s povolenými Prostory. Skupiny a vlastní značky budou skryty.", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta verze je k dispozici pro web, desktop a Android. Děkujeme vám za vyzkoušení beta verze.", + "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s se znovu načte s vypnutými Prostory. Skupiny a vlastní značky budou opět viditelné.", + "Spaces are a new way to group rooms and people.": "Prostory představují nový způsob seskupování místností a osob." } From 68fcc259d0fbe6c44ef768c8811ee88bc782cb83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 11 May 2021 17:04:44 +0000 Subject: [PATCH 011/174] Translated using Weblate (Estonian) Currently translated at 100.0% (2959 of 2959 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 43 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index fc42ad73dd..88480ceb6e 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -652,7 +652,7 @@ "A session's public name is visible to people you communicate with": "Sessiooni avalik nimi on nähtav neile, kellega sa suhtled", "%(brand)s collects anonymous analytics to allow us to improve the application.": "Võimaldamaks meil rakendust parandada kogub %(brand)s anonüümset teavet rakenduse kasutuse kohta.", "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privaatsus on meile oluline ning seega me ei kogu ei isiklikke ega isikustatavaid andmeid.", - "Learn more about how we use analytics.": "Loe lisaks kuidas me kasutama analüütikat.", + "Learn more about how we use analytics.": "Loe lisaks selles kohta, kuidas me kasutame analüütikat.", "No media permissions": "Meediaõigused puuduvad", "You may need to manually permit %(brand)s to access your microphone/webcam": "Sa võib-olla pead andma %(brand)s'ile loa mikrofoni ja veebikaamera kasutamiseks", "Missing media permissions, click the button below to request.": "Meediaga seotud õigused puuduvad. Nende nõutamiseks klõpsi järgnevat nuppu.", @@ -1202,8 +1202,8 @@ "eg: @bot:* or example.org": "näiteks: @bot:* või example.org", "Subscribed lists": "Tellitud loendid", "Subscribe": "Telli", - "Start automatically after system login": "Käivita automaatselt peale arvutisse sisselogimist", - "Always show the window menu bar": "Näita alati aknas menüüriba", + "Start automatically after system login": "Käivita Element automaatselt peale arvutisse sisselogimist", + "Always show the window menu bar": "Näita aknas alati menüüriba", "Preferences": "Eelistused", "Room list": "Jututubade loend", "Timeline": "Ajajoon", @@ -3297,5 +3297,40 @@ "%(count)s results in all spaces|other": "%(count)s tulemust kõikides kogukonnakeskustes", "You have no ignored users.": "Sa ei ole veel kedagi eiranud.", "Play": "Esita", - "Pause": "Peata" + "Pause": "Peata", + "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "Kas sa tahaksid katsetada? Sa tutvud meie rakenduse uuendustega teistest varem ja võib-olla isegi saad mõjutada arenduse lõpptulemust. Lisateavet liad siit.", + "This is an experimental feature. For now, new users receiving an invite will have to open the invite on to actually join.": "See on katseline funktsionaalsus. Seetõttu uued kutse saanud kasutajad peavad tegelikuks liitumiseks avama kutse siin .", + "To join %(spaceName)s, turn on the Spaces beta": "%(spaceName)s kogukonnakeskusega liitumiseks lülita sisse vastav katseline funktsionaalsus", + "To view %(spaceName)s, turn on the Spaces beta": "%(spaceName)s kogukonnakeskuse vaatamiseks lülita sisse vastav katseline funktsionaalsus", + "Select a room below first": "Esmalt vali alljärgnevast üks jututuba", + "Communities are changing to Spaces": "Seniste kogukondade asemele tulevad kogukonnakeskused", + "Join the beta": "Hakka kasutama beetaversiooni", + "Leave the beta": "Lõpeta beetaversiooni kasutamine", + "Beta": "Beetaversioon", + "Tap for more info": "Lisateabe jaoks klõpsi", + "Spaces is a beta feature": "Kogukonnakeskused on veel katsetamisjärgus funktsionaalsus", + "Want to add a new room instead?": "Kas sa selle asemel soovid lisada jututuba?", + "Adding rooms... (%(progress)s out of %(count)s)|one": "Lisan jututuba...", + "Adding rooms... (%(progress)s out of %(count)s)|other": "Lisan jututubasid... (%(progress)s/%(count)s)", + "Not all selected were added": "Kõiki valituid me ei lisanud", + "You can add existing spaces to a space.": "Sa võid kogukonnakeskusele lisada ka teisi kogukonnakeskuseid.", + "Feeling experimental?": "Kas sa tahaksid natukene katsetada?", + "You are not allowed to view this server's rooms list": "Sul puuduvad õigused selle serveri jututubade loendi vaatamiseks", + "Error processing voice message": "Viga häälsõnumi töötlemisel", + "We didn't find a microphone on your device. Please check your settings and try again.": "Me ei suutnud sinu seadmest leida mikrofoni. Palun kontrolli seadistusi ja proovi siis uuesti.", + "No microphone found": "Mikrofoni ei leidu", + "We were unable to access your microphone. Please check your browser settings and try again.": "Meil puudub ligipääs sinu mikrofonile. Palun kontrolli oma veebibrauseri seadistusi ja proovi uuesti.", + "Unable to access your microphone": "Puudub ligipääs mikrofonile", + "Your access token gives full access to your account. Do not share it with anyone.": "Sinu pääsuluba annab täismahulise ligipääsu sinu kasutajakontole. Palun ära jaga seda teistega.", + "Access Token": "Pääsuluba", + "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Kogukonnakeskused on uus viis inimeste ja jututubade ühendamiseks. Kogukonnakeskusega liitumiseks vajad sa kutset.", + "Please enter a name for the space": "Palun sisesta kogukonnakeskuse nimi", + "Connecting": "Kõne on ühendamisel", + "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Kasuta võrdõigusvõrku 1:1 kõnede jaoks (kui sa P2P-võrgu sisse lülitad, siis teine osapool ilmselt näeb sinu IP-aadressi)", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Rakenduse beetaversioon on saadaval veebirakendusena, töölauarakendusena ja Androidi jaoks. Kõik funtsionaalsused ei pruugi sinu koduserveri poolt olla toetatud.", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "Sa võid beetaversiooni kasutamise lõpetada niipea, kui tahad. Selleks klõpsi beeta-silti, mida näed siin samas ülal.", + "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "Käivitame %(brand)s uuesti nii, et kogukonnakeskused on kasutusel. Vana tüüpi kogukonnad ja kohandatud sildid on siis välja lülitatud.", + "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Käivitame %(brand)s uuesti nii, et kogukonnakeskused ei ole kasutusel. Vana tüüpi kogukonnad ja kohandatud sildid saavad jälle olema kasutusel.", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "Rakenduse beetaversioon on saadaval veebirakendusena, töölauarakendusena ja Androidi jaoks. Tänud, et oled huviline katsetama meie rakendust.", + "Spaces are a new way to group rooms and people.": "Kogukonnakeskused on uus viis jututubade ja inimeste ühendamiseks." } From f595813e21af32fba9331f2cf527e33d77e55deb Mon Sep 17 00:00:00 2001 From: libexus Date: Thu, 13 May 2021 18:00:02 +0000 Subject: [PATCH 012/174] Translated using Weblate (German) Currently translated at 99.1% (2935 of 2959 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 23f6071efc..dbd2df28e8 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -338,7 +338,7 @@ "Uploading %(filename)s and %(count)s others|one": "%(filename)s und %(count)s weitere Dateien werden hochgeladen", "Uploading %(filename)s and %(count)s others|other": "%(filename)s und %(count)s weitere Dateien werden hochgeladen", "You must register to use this functionality": "Du musst dich registrieren, um diese Funktionalität nutzen zu können", - "Create new room": "Neuen Raum erstellen", + "Create new room": "Neuer Raum", "Room directory": "Raum-Verzeichnis", "Start chat": "Chat starten", "New Password": "Neues Passwort", @@ -2597,7 +2597,7 @@ "See videos posted to your active room": "In deinen aktiven Raum gesendete Videos anzeigen", "See videos posted to this room": "In diesen Raum gesendete Videos anzeigen", "Send images as you in this room": "Bilder als du in diesen Raum senden", - "Send images as you in your active room": "Sende Bilder als deine Person in den aktiven Raum.", + "Send images as you in your active room": "Sende Bilder in den aktuellen Raum", "See images posted to this room": "In diesen Raum gesendete Bilder anzeigen", "See images posted to your active room": "In deinen aktiven Raum gesendete Bilder anzeigen", "Send videos as you in this room": "Videos als du in diesen Raum senden", @@ -2945,7 +2945,7 @@ "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use Element with an existing Matrix account on a different homeserver.": "Du kannst in den benutzerdefinierten Serveroptionen eine andere Heimserver-URL angeben, um dich bei anderen Matrixservern anzumelden.", "Server Options": "Servereinstellungen", "No other application is using the webcam": "keine andere Anwendung auf die Webcam zugreift", - "Permission is granted to use the webcam": "Zugriff auf die Webcam ist gestattet.", + "Permission is granted to use the webcam": "Zugriff auf Webcam gestattet", "A microphone and webcam are plugged in and set up correctly": "Mikrofon und Webcam eingesteckt und richtig eingerichtet sind", "Call failed because no microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "Der Anruf ist fehlgeschlagen weil nicht auf das Mikrofon zugegriffen werden konnte. Stelle sicher, dass das Mikrofon richtig eingesteckt und eingerichtet ist.", "Call failed because no webcam or microphone could not be accessed. Check that:": "Der Anruf ist fehlgeschlagen weil nicht auf das Mikrofon oder die Webcam zugegriffen werden konnte. Stelle sicher, dass:", @@ -3084,12 +3084,12 @@ "Accept Invite": "Einladung akzeptieren", "Save changes": "Änderungen speichern", "Undo": "Rückgängig", - "Save Changes": "Änderungen Speichern", + "Save Changes": "Speichern", "View dev tools": "Entwicklerwerkzeuge", "Apply": "Anwenden", "Create a new room": "Neuen Raum erstellen", "Suggested Rooms": "Vorgeschlagene Räume", - "Add existing room": "Existierenden Raum hinzufügen", + "Add existing room": "Existierenden Raum", "Send message": "Nachricht senden", "New room": "Neuer Raum", "Share invite link": "Einladungslink teilen", @@ -3241,7 +3241,7 @@ "Values at explicit levels in this room": "Werte für explizite Stufen in diesem Raum", "Confirm abort of host creation": "Bestätige das Abbrechen der Host-Erstellung", "Are you sure you wish to abort creation of the host? The process cannot be continued.": "Soll die Host-Erstellung wirklich abgebrochen werden? Dieser Prozess kann nicht wieder fortgesetzt werden.", - "Invite to just this room": "Nur für diesen Raum einladen", + "Invite to just this room": "Nur in diesen Raum einladen", "Consult first": "Konsultiere zuerst", "Reset event store?": "Ereignisspeicher zurück setzen?", "You most likely do not want to reset your event index store": "Es ist wahrscheinlich, dass du den Ereignis-Indexspeicher nicht zurück setzen möchtest", @@ -3253,7 +3253,7 @@ "What are some things you want to discuss in %(spaceName)s?": "Welche Themen willst du in %(spaceName)s besprechen?", "Inviting...": "Einladen...", "Failed to create initial space rooms": "Fehler beim Initialisieren des Space", - "You are the only person here. If you leave, no one will be able to join in the future, including you.": "Du bist hier noch alleine. Wenn du den Space verlässt, ist er für immer verloren (eine lange Zeit).", + "You are the only person here. If you leave, no one will be able to join in the future, including you.": "Du bist die einzige Person hier. Wenn du den Space verlässt, ist er für immer verloren (eine lange Zeit).", "Edit settings relating to your space.": "Einstellungen vom Space bearbeiten.", "Please choose a strong password": "Bitte gib ein sicheres Passwort ein", "If you reset everything, you will restart with no trusted sessions, no trusted users, and might not be able to see past messages.": "Wenn du alles zurücksetzt, gehen alle verifizierten Anmeldungen, Benutzer und verschlüsselte Nachrichten verloren.", @@ -3288,11 +3288,11 @@ "What do you want to organise?": "Was willst du organisieren?", "Enter your Security Phrase a second time to confirm it.": "Gib dein Kennwort ein zweites Mal zur Bestätigung ein.", "Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.": "Wähle Räume oder Konversationen die Du hinzufügen möchtest. Dieser Bereich ist nur für Dich, niemand wird informiert. Du kannst später mehr hinzufügen.", - "Filter all spaces": "Alle Bereiche filtern", + "Filter all spaces": "Alle Spaces durchsuchen", "Delete recording": "Aufnahme löschen", "Stop the recording": "Aufnahme stoppen", - "%(count)s results in all spaces|one": "%(count)s Ergebnis in allen Bereichen", - "%(count)s results in all spaces|other": "%(count)s Ergebnisse in allen Bereichen", + "%(count)s results in all spaces|one": "%(count)s Ergebnis", + "%(count)s results in all spaces|other": "%(count)s Ergebnisse", "You have no ignored users.": "Du ignorierst keine Benutzer.", "Error processing voice message": "Fehler beim Verarbeiten der Sprachnachricht", "To join %(spaceName)s, turn on the Spaces beta": "Um %(spaceName)s beizutreten, aktiviere die Spaces Betaversion", From 7248f28278f261d44abf2e9b9a76737d8ba769a6 Mon Sep 17 00:00:00 2001 From: random Date: Thu, 13 May 2021 10:26:49 +0000 Subject: [PATCH 013/174] Translated using Weblate (Italian) Currently translated at 100.0% (2959 of 2959 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index a393a83409..00fce6dcb2 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -3319,5 +3319,40 @@ "%(count)s results in all spaces|other": "%(count)s risultati in tutti gli spazi", "You have no ignored users.": "Non hai utenti ignorati.", "Play": "Riproduci", - "Pause": "Pausa" + "Pause": "Pausa", + "This is an experimental feature. For now, new users receiving an invite will have to open the invite on to actually join.": "Questa è una funzione sperimentale. Per ora, i nuovi utenti che ricevono un invito dovranno aprirlo su per entrare.", + "To join %(spaceName)s, turn on the Spaces beta": "Per entrare in %(spaceName)s, attiva la beta degli spazi", + "To view %(spaceName)s, turn on the Spaces beta": "Per vedere %(spaceName)s, attiva la beta degli spazi", + "Select a room below first": "Prima seleziona una stanza sotto", + "Communities are changing to Spaces": "Le comunità stanno diventando spazi", + "Join the beta": "Unisciti alla beta", + "Leave the beta": "Abbandona la beta", + "Beta": "Beta", + "Tap for more info": "Tocca per maggiori info", + "Spaces is a beta feature": "Gli spazi sono una funzionalità beta", + "Want to add a new room instead?": "Vuoi invece aggiungere una nuova stanza?", + "Adding rooms... (%(progress)s out of %(count)s)|one": "Aggiunta stanza...", + "Adding rooms... (%(progress)s out of %(count)s)|other": "Aggiunta stanze... (%(progress)s di %(count)s)", + "Not all selected were added": "Non tutti i selezionati sono stati aggiunti", + "You can add existing spaces to a space.": "Puoi aggiungere spazi esistenti ad uno spazio.", + "Feeling experimental?": "Ti va di sperimentare?", + "You are not allowed to view this server's rooms list": "Non hai i permessi per vedere l'elenco di stanze del server", + "Error processing voice message": "Errore di elaborazione del vocale", + "We didn't find a microphone on your device. Please check your settings and try again.": "Non abbiamo trovato un microfono nel tuo dispositivo. Controlla le impostazioni e riprova.", + "No microphone found": "Nessun microfono trovato", + "We were unable to access your microphone. Please check your browser settings and try again.": "Non abbiamo potuto accedere al tuo microfono. Controlla le impostazioni del browser e riprova.", + "Unable to access your microphone": "Impossibile accedere al microfono", + "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "Ti va di sperimentare? I laboratori sono il miglior modo di ottenere anteprime, testare nuove funzioni ed aiutare a modellarle prima che vengano pubblicate. Maggiori informazioni.", + "Your access token gives full access to your account. Do not share it with anyone.": "Il tuo token di accesso ti dà l'accesso al tuo account. Non condividerlo con nessuno.", + "Access Token": "Token di accesso", + "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Gli spazi sono un nuovo modo di raggruppare stanze e persone. Per entrare in uno spazio esistente ti serve un invito.", + "Please enter a name for the space": "Inserisci un nome per lo spazio", + "Connecting": "In connessione", + "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Permetti Peer-to-Peer per chiamate 1:1 (se lo attivi, l'altra parte potrebbe essere in grado di vedere il tuo indirizzo IP)", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Beta disponibile per web, desktop e Android. Alcune funzioni potrebbero non essere disponibili nel tuo homeserver.", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "Puoi abbandonare la beta quando vuoi dalle impostazioni o toccando un'etichetta beta, come quella sopra.", + "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s si ricaricherà con gli spazi attivati. Le comunità e le etichette personalizzate saranno nascoste.", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta disponibile per web, desktop e Android. Grazie per la partecipazione alla beta.", + "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s si ricaricherà con gli spazi disattivati. Le comunità e le etichette personalizzate saranno di nuovo visibili.", + "Spaces are a new way to group rooms and people.": "Gli spazi sono un nuovo modo di raggruppare stanze e persone." } From c5093cafad1542c39d4124b573fb60e7af410ca6 Mon Sep 17 00:00:00 2001 From: XoseM Date: Thu, 13 May 2021 12:43:51 +0000 Subject: [PATCH 014/174] Translated using Weblate (Galician) Currently translated at 100.0% (2959 of 2959 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 451be0fb7c..15226882f2 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -3319,5 +3319,40 @@ "%(count)s results in all spaces|other": "%(count)s resultados en tódolos espazos", "You have no ignored users.": "Non tes usuarias ignoradas.", "Play": "Reproducir", - "Pause": "Deter" + "Pause": "Deter", + "This is an experimental feature. For now, new users receiving an invite will have to open the invite on to actually join.": "Esta é unha característica experimental. Por agora as novas usuarias convidadas deberán abrir o convite en para poder unirse.", + "To join %(spaceName)s, turn on the Spaces beta": "Para unirte a %(spaceName)s, activa a beta de Espazos", + "To view %(spaceName)s, turn on the Spaces beta": "Para ver %(spaceName)s, cambia á beta de Espazos", + "Select a room below first": "Primeiro elixe embaixo unha sala", + "Communities are changing to Spaces": "Comunidades cambia a Espazos", + "Join the beta": "Unirse á beta", + "Leave the beta": "Saír da beta", + "Beta": "Beta", + "Tap for more info": "Toca para ter máis información", + "Spaces is a beta feature": "Espazos é unha característica en beta", + "Want to add a new room instead?": "Queres engadir unha nova sala?", + "Adding rooms... (%(progress)s out of %(count)s)|one": "Engadindo sala...", + "Adding rooms... (%(progress)s out of %(count)s)|other": "Engadindo salas... (%(progress)s de %(count)s)", + "Not all selected were added": "Non se engadiron tódolos seleccionados", + "You can add existing spaces to a space.": "Podes engadir espazos existentes a un espazo.", + "Feeling experimental?": "Sínteste aventureira?", + "You are not allowed to view this server's rooms list": "Non tes permiso para ver a lista de salas deste servidor", + "Error processing voice message": "Erro ao procesar a mensaxe de voz", + "We didn't find a microphone on your device. Please check your settings and try again.": "Non atopamos ningún micrófono no teu dispositivo. Comproba os axustes e proba outra vez.", + "No microphone found": "Non atopamos ningún micrófono", + "We were unable to access your microphone. Please check your browser settings and try again.": "Non puidemos acceder ao teu micrófono. Comproba os axustes do navegador e proba outra vez.", + "Unable to access your microphone": "Non se puido acceder ao micrófono", + "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "Gañas de experimentar? Labs é o mellor xeito para un acceso temperá e probar novas funcións e axudar a melloralas antes de ser publicadas. Coñece máis.", + "Your access token gives full access to your account. Do not share it with anyone.": "O teu token de acceso da acceso completo á túa conta. Non o compartas con ninguén.", + "Access Token": "Token de acceso", + "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Espazos é un novo xeito de agrupar salas e persoas. Precisas un convite para unirte a un espazo existente.", + "Please enter a name for the space": "Escribe un nome para o espazo", + "Connecting": "Conectando", + "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Permitir Peer-to-Peer en chamadas 1:1 (se activas isto a outra parte podería coñecer o teu enderezo IP)", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Beta dispoñible para web, escritorio e Android. Algunhas características poderían non estar dispoñibles no teu servidor de inicio.", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "Podes saír da beta desde os axustes cando queiras ou tocando na insignia beta, como a superior.", + "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s cargará con Espazos activado. Comunidades e etiquetas personais estarán agochadas.", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta dispoñible para web, escritorio e Android. Grazas por probar a beta.", + "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s volverá a cargar con Espazos desactivado. Comunidade e etiquetas personalizadas estarán visibles de volta.", + "Spaces are a new way to group rooms and people.": "Espazos é un novo xeito de agrupar salas e persoas." } From 3f9ef015c73e0fd6b299fd545f9f8ca65f52a86b Mon Sep 17 00:00:00 2001 From: Trendyne Date: Thu, 13 May 2021 22:12:00 +0000 Subject: [PATCH 015/174] Translated using Weblate (Icelandic) Currently translated at 13.7% (407 of 2959 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/is/ --- src/i18n/strings/is.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/is.json b/src/i18n/strings/is.json index ddb3bbc66d..90b8bc8d13 100644 --- a/src/i18n/strings/is.json +++ b/src/i18n/strings/is.json @@ -460,5 +460,11 @@ "Create Account": "Stofna Reikning", "Please install Chrome, Firefox, or Safari for the best experience.": "vinsamlegast setja upp Chrome, Firefox, eða Safari fyrir besta reynsluna.", "Explore rooms": "Kanna herbergi", - "Sign In": "Skrá inn" + "Sign In": "Skrá inn", + "The user's homeserver does not support the version of the room.": "Heimaþjónn notandans styður ekki útgáfu herbergis.", + "The user must be unbanned before they can be invited.": "Notandinn þarf að vera afbannaður áður en að hægt er að bjóða þeim.", + "User %(user_id)s may or may not exist": "Notandi %(user_id)s gæti verið til", + "User %(user_id)s does not exist": "Notandi %(user_id)s er ekki til", + "User %(userId)s is already in the room": "Notandi %(userId)s er nú þegar í herberginu", + "You do not have permission to invite people to this room.": "Þú hefur ekki heimild til að bjóða fólk í þessa spjallrás." } From 08d855c8b993cb8fee966c1975f467142b536b64 Mon Sep 17 00:00:00 2001 From: libexus Date: Fri, 14 May 2021 17:13:28 +0000 Subject: [PATCH 016/174] Translated using Weblate (German) Currently translated at 98.8% (2935 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index dbd2df28e8..5742517d6f 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2488,7 +2488,7 @@ "Secret storage:": "Sicherer Speicher:", "ready": "bereit", "not ready": "nicht bereit", - "Secure Backup": "Sichere Aufbewahrungskopie", + "Secure Backup": "Sicheres Backup", "End Call": "Anruf beenden", "Remove the group call from the room?": "Konferenzgespräch aus diesem Raum entfernen?", "You don't have permission to remove the call from the room": "Du hast keine Berechtigung um den Konferenzanruf aus dem Raum zu entfernen", @@ -3318,9 +3318,12 @@ "Please enter a name for the space": "Gib den Namen des Spaces ein", "Connecting": "Verbinden", "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Direktverbindung für Direktanrufe aktivieren. Dadurch sieht dein Gegenüber möglicherweise deine IP-Adresse.", - "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Die Betaversion ist verfügbar für Browser, Desktop und Android verfügbar. Je nach Homeserver sind einige Funktionen möglicherweise nicht verfügbar.", - "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "Du kannst die Betaversion jederzeit verlassen. Mache dies entweder in den Einstellungen oder klicke auf Beta-Icons wie dieses hier.", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Die Betaversion ist verfügbar für Browser, Desktop und Android. Je nach Homeserver sind einige Funktionen möglicherweise nicht verfügbar.", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "Du kannst die Betaversion jederzeit verlassen. Mache dies entweder in den Einstellungen oder klicke auf eines der \"Beta\"-Icons wie das hier oben.", "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s wird mit aktivierten Spaces neuladen. Danach kannst Communities und Custom Tags nicht verwenden.", "Beta available for web, desktop and Android. Thank you for trying the beta.": "Die Betaversion ist für Browser, Desktop und Android verfügbar. Danke, dass Du die Betaversion testest.", - "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s wird mit deaktivierten Spaces neuladen und du kannst Communities und Custom Tags wieder verwenden können." + "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s wird mit deaktivierten Spaces neuladen und du kannst Communities und Custom Tags wieder verwenden können.", + "Spaces are a beta feature.": "Spaces sind in der Beta.", + "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Wir haben Spaces entwickelt, damit ihr eure vielen Räume besser organisieren könnt. Um einen existierenden Space beitreten zu können musst du (noch) von jemandem eingeladen werden.", + "Spaces are a new way to group rooms and people.": "Wir haben Spaces entwickelt, damit ihr eure vielen Räume besser organisieren könnt." } From 0642952d5f235afabe25a2fe2729766bf6f0942b Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 14 May 2021 04:43:51 +0000 Subject: [PATCH 017/174] Translated using Weblate (German) Currently translated at 98.8% (2935 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 5742517d6f..7a16e994cf 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -3325,5 +3325,6 @@ "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s wird mit deaktivierten Spaces neuladen und du kannst Communities und Custom Tags wieder verwenden können.", "Spaces are a beta feature.": "Spaces sind in der Beta.", "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Wir haben Spaces entwickelt, damit ihr eure vielen Räume besser organisieren könnt. Um einen existierenden Space beitreten zu können musst du (noch) von jemandem eingeladen werden.", - "Spaces are a new way to group rooms and people.": "Wir haben Spaces entwickelt, damit ihr eure vielen Räume besser organisieren könnt." + "Spaces are a new way to group rooms and people.": "Wir haben Spaces entwickelt, damit ihr eure vielen Räume besser organisieren könnt.", + "Message search initialisation failed": "Initialisierung der Nachrichtensuche fehlgeschlagen" } From 2d74ee722aad09e3b89ac47d9f016217f01d99be Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 14 May 2021 04:41:12 +0000 Subject: [PATCH 018/174] Translated using Weblate (English (United States)) Currently translated at 19.7% (585 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/en_US/ --- src/i18n/strings/en_US.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json index 4a97b60a2e..a5d7756de8 100644 --- a/src/i18n/strings/en_US.json +++ b/src/i18n/strings/en_US.json @@ -665,5 +665,6 @@ "Unrecognised command: %(commandText)s": "Unrecognized command: %(commandText)s", "Add some details to help people recognise it.": "Add some details to help people recognize it.", "Unrecognised room address:": "Unrecognized room address:", - "A private space to organise your rooms": "A private space to organize your rooms" + "A private space to organise your rooms": "A private space to organize your rooms", + "Message search initialisation failed": "Message search initialization failed" } From 796306fa8fc00b3afd4b6eaba7497f6b344d928b Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 14 May 2021 04:44:38 +0000 Subject: [PATCH 019/174] Translated using Weblate (Spanish) Currently translated at 99.4% (2953 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/es/ --- src/i18n/strings/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index 86749e7b3f..ec5e4c1bfe 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -3291,5 +3291,6 @@ "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s se volverá a cargar con los espacios activados. Las comunidades y etiquetas personalizadas se ocultarán.", "Beta available for web, desktop and Android. Thank you for trying the beta.": "Versión beta disponible para web, escritorio y Android. Gracias por usar la beta.", "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s volverá a cargarse con los espacios desactivados. Las comunidades y etiquetas personalizadas serán visibles de nuevo.", - "Spaces are a new way to group rooms and people.": "Los espacios son una nueva manera de agrupar salas y gente." + "Spaces are a new way to group rooms and people.": "Los espacios son una nueva manera de agrupar salas y gente.", + "Message search initialisation failed": "Ha fallado la inicialización de la búsqueda de mensajes" } From d74ab2a4f9bea3d113626b4625c8d652440ac4d1 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 14 May 2021 04:48:00 +0000 Subject: [PATCH 020/174] Translated using Weblate (French) Currently translated at 99.5% (2956 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 4b36d5b7ed..1d03786f71 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -3331,5 +3331,6 @@ "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s va être redémarré avec les espaces activés. Les communautés et les étiquettes personnalisées seront cachés.", "Beta available for web, desktop and Android. Thank you for trying the beta.": "Bêta disponible pour l’application web, de bureau et Android. Merci d’essayer la bêta.", "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s va être redémarré avec les espaces désactivés. Les communautés et les étiquettes personnalisées seront de nouveau visibles.", - "Spaces are a new way to group rooms and people.": "Les espaces sont un nouveau moyen de regrouper les salons et les personnes." + "Spaces are a new way to group rooms and people.": "Les espaces sont un nouveau moyen de regrouper les salons et les personnes.", + "Message search initialisation failed": "Échec de l’initialisation de la recherche de message" } From 59144ac9352cccf6d86e54235ba5dafd73f70474 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Fri, 14 May 2021 06:10:46 +0000 Subject: [PATCH 021/174] Translated using Weblate (Hungarian) Currently translated at 99.8% (2965 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 0230ecf52a..3daa0b2f16 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -3349,5 +3349,14 @@ "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s a Terekkel lesz újra betöltve. A közösségek és egyedi címkék rejtve maradnak.", "Beta available for web, desktop and Android. Thank you for trying the beta.": "Béta verzió elérhető webre, asztali kliensre és Androidra. Köszönjük, hogy kipróbálja.", "Spaces are a new way to group rooms and people.": "Szobák és emberek csoportosításának új lehetősége a Terek használata.", - "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s a Terek nélkül lesz újra betöltve. A közösségek és egyedi címkék újra megjelennek." + "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s a Terek nélkül lesz újra betöltve. A közösségek és egyedi címkék újra megjelennek.", + "To leave the beta, visit your settings.": "A beállításokban tudja elhagyni a bétát.", + "Your platform and username will be noted to help us use your feedback as much as we can.": "A platform és a felhasználói neve felhasználásra kerül ami segít nekünk a visszajelzést minél jobban felhasználni.", + "%(featureName)s beta feedback": "%(featureName)s béta visszajelzés", + "Thank you for your feedback, we really appreciate it.": "Köszönjük a visszajelzését, ezt nagyra értékeljük.", + "Beta feedback": "Béta visszajelzés", + "Add reaction": "Reakció hozzáadása", + "Send and receive voice messages": "Hangüzenet küldése, fogadása", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "A visszajelzése segítség a terek javításához. Minél részletesebb annál jobb.", + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Távozás után %(brand)s Terek nélkül lesz újra betöltve. A közösségek és egyedi címkék újra megjelennek." } From 6def74a418b1b54c93a1f387b5b088ff9389cc32 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 14 May 2021 04:49:36 +0000 Subject: [PATCH 022/174] Translated using Weblate (Hungarian) Currently translated at 99.8% (2965 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 3daa0b2f16..bc38d20716 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -3358,5 +3358,6 @@ "Add reaction": "Reakció hozzáadása", "Send and receive voice messages": "Hangüzenet küldése, fogadása", "Your feedback will help make spaces better. The more detail you can go into, the better.": "A visszajelzése segítség a terek javításához. Minél részletesebb annál jobb.", - "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Távozás után %(brand)s Terek nélkül lesz újra betöltve. A közösségek és egyedi címkék újra megjelennek." + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Távozás után %(brand)s Terek nélkül lesz újra betöltve. A közösségek és egyedi címkék újra megjelennek.", + "Message search initialisation failed": "Üzenet keresés beállítása sikertelen" } From 57d3c525e2c2fb2540de3a8955134be893c83c22 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 14 May 2021 04:51:02 +0000 Subject: [PATCH 023/174] Translated using Weblate (Dutch) Currently translated at 99.5% (2956 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/nl/ --- src/i18n/strings/nl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index e12780ec10..69a73ab76b 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -3240,5 +3240,6 @@ "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s zal herladen met Spaces ingeschakeld. Gemeenschappen en labels worden verborgen.", "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta beschikbaar voor web, desktop en Android. Bedankt dat u de beta wilt proberen.", "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s zal herladen met Spaces uitgeschakeld. Gemeenschappen en labels zullen weer zichtbaar worden.", - "Spaces are a new way to group rooms and people.": "Spaces zijn de nieuwe manier om gesprekken en personen te groeperen." + "Spaces are a new way to group rooms and people.": "Spaces zijn de nieuwe manier om gesprekken en personen te groeperen.", + "Message search initialisation failed": "Zoeken in berichten opstarten is mislukt" } From 6e47f968fab987061030e4ee92b3af3648cb5b0d Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 14 May 2021 04:51:59 +0000 Subject: [PATCH 024/174] Translated using Weblate (Swedish) Currently translated at 98.4% (2922 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 5824224f3c..2ed4fa03d1 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -3250,5 +3250,6 @@ "%(count)s results in all spaces|other": "%(count)s resultat i alla utrymmen", "You have no ignored users.": "Du har inga ignorerade användare.", "Play": "Spela", - "Pause": "Pausa" + "Pause": "Pausa", + "Message search initialisation failed": "Initialisering av meddelandesökning misslyckades" } From fc3d56cdc34481f76c03990b59a432d3733bb868 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 14 May 2021 04:53:38 +0000 Subject: [PATCH 025/174] Translated using Weblate (Chinese (Traditional)) Currently translated at 99.5% (2956 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 51ebb36dc0..a3854e59dd 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -3357,5 +3357,6 @@ "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s 將在啟用空間的情況下重新載入。社群與自訂標籤將會隱藏。", "Beta available for web, desktop and Android. Thank you for trying the beta.": "測試版可用於網路、桌面與 Android。感謝您試用測試版。", "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s 將在停用空間的情況下重新載入。社群與自訂標籤將再次可見。", - "Spaces are a new way to group rooms and people.": "空間是將聊天室與人們分組的一種新方式。" + "Spaces are a new way to group rooms and people.": "空間是將聊天室與人們分組的一種新方式。", + "Message search initialisation failed": "訊息搜尋初始化失敗" } From 00508faf9d0c046cbd7b23883b8a10c82e9932cd Mon Sep 17 00:00:00 2001 From: random Date: Fri, 14 May 2021 14:53:50 +0000 Subject: [PATCH 026/174] Translated using Weblate (Italian) Currently translated at 100.0% (2968 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 00fce6dcb2..2d6a6ec3e9 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -3354,5 +3354,17 @@ "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s si ricaricherà con gli spazi attivati. Le comunità e le etichette personalizzate saranno nascoste.", "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta disponibile per web, desktop e Android. Grazie per la partecipazione alla beta.", "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s si ricaricherà con gli spazi disattivati. Le comunità e le etichette personalizzate saranno di nuovo visibili.", - "Spaces are a new way to group rooms and people.": "Gli spazi sono un nuovo modo di raggruppare stanze e persone." + "Spaces are a new way to group rooms and people.": "Gli spazi sono un nuovo modo di raggruppare stanze e persone.", + "Spaces are a beta feature.": "Gli spazi sono una funzionalità beta.", + "Search names and descriptions": "Cerca nomi e descrizioni", + "You may contact me if you have any follow up questions": "Potete contattarmi se avete altre domande", + "To leave the beta, visit your settings.": "Per abbandonare la beta, vai nelle impostazioni.", + "Your platform and username will be noted to help us use your feedback as much as we can.": "Verranno annotate la tua piattaforma e il nome utente per aiutarci ad usare la tua opinione al meglio.", + "%(featureName)s beta feedback": "Feedback %(featureName)s beta", + "Thank you for your feedback, we really appreciate it.": "Grazie per la tua opinione, lo appreziamo molto.", + "Beta feedback": "Feedback beta", + "Add reaction": "Aggiungi reazione", + "Send and receive voice messages": "Invia e ricevi messaggi vocali", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "La tua opinione aiuterà a migliorare gli spazi. Più dettagli dai, meglio è.", + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Se esci, %(brand)s si ricaricherà con gli spazi disattivati. Le comunità e le etichette personalizzate saranno di nuovo visibili." } From 1b67e680616f62b94f3bd291a0b4eb0e919b3395 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 14 May 2021 04:50:01 +0000 Subject: [PATCH 027/174] Translated using Weblate (Italian) Currently translated at 100.0% (2968 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 2d6a6ec3e9..d109837bb1 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -3366,5 +3366,6 @@ "Add reaction": "Aggiungi reazione", "Send and receive voice messages": "Invia e ricevi messaggi vocali", "Your feedback will help make spaces better. The more detail you can go into, the better.": "La tua opinione aiuterà a migliorare gli spazi. Più dettagli dai, meglio è.", - "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Se esci, %(brand)s si ricaricherà con gli spazi disattivati. Le comunità e le etichette personalizzate saranno di nuovo visibili." + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Se esci, %(brand)s si ricaricherà con gli spazi disattivati. Le comunità e le etichette personalizzate saranno di nuovo visibili.", + "Message search initialisation failed": "Inizializzazione ricerca messaggi fallita" } From d862df9f05b734fe7878b99c2af3875dded07bc6 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 14 May 2021 04:43:25 +0000 Subject: [PATCH 028/174] Translated using Weblate (Czech) Currently translated at 99.5% (2956 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ --- src/i18n/strings/cs.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 6d4cb953cc..9f3e48dd61 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -3271,5 +3271,6 @@ "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s se znovu načte s povolenými Prostory. Skupiny a vlastní značky budou skryty.", "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta verze je k dispozici pro web, desktop a Android. Děkujeme vám za vyzkoušení beta verze.", "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s se znovu načte s vypnutými Prostory. Skupiny a vlastní značky budou opět viditelné.", - "Spaces are a new way to group rooms and people.": "Prostory představují nový způsob seskupování místností a osob." + "Spaces are a new way to group rooms and people.": "Prostory představují nový způsob seskupování místností a osob.", + "Message search initialisation failed": "Inicializace vyhledávání zpráv se nezdařila" } From 126077a67817366a946be8a676f17d5331d0400d Mon Sep 17 00:00:00 2001 From: XoseM Date: Fri, 14 May 2021 12:42:01 +0000 Subject: [PATCH 029/174] Translated using Weblate (Galician) Currently translated at 100.0% (2968 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 15226882f2..110f95c8b2 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -3354,5 +3354,17 @@ "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s cargará con Espazos activado. Comunidades e etiquetas personais estarán agochadas.", "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta dispoñible para web, escritorio e Android. Grazas por probar a beta.", "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s volverá a cargar con Espazos desactivado. Comunidade e etiquetas personalizadas estarán visibles de volta.", - "Spaces are a new way to group rooms and people.": "Espazos é un novo xeito de agrupar salas e persoas." + "Spaces are a new way to group rooms and people.": "Espazos é un novo xeito de agrupar salas e persoas.", + "Spaces are a beta feature.": "Espazos é unha ferramenta en beta.", + "Search names and descriptions": "Buscar nome e descricións", + "You may contact me if you have any follow up questions": "Podes contactar conmigo se tes algunha outra suxestión", + "To leave the beta, visit your settings.": "Para saír da beta, vai aos axustes.", + "Your platform and username will be noted to help us use your feedback as much as we can.": "A túa plataforma e nome de usuaria serán notificados para axudarnos a utilizar a túa opinión do mellor xeito posible.", + "%(featureName)s beta feedback": "Opinión acerca de %(featureName)s beta", + "Thank you for your feedback, we really appreciate it.": "Grazas pola túa opinión, realmente apreciámola.", + "Beta feedback": "Opinión sobre a beta", + "Add reaction": "Engadir reacción", + "Send and receive voice messages": "Enviar e recibir mensaxes de voz", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "A túa opinión axudaranos a mellorar os espazos. Canto máis detallada sexa moito mellor para nós.", + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Se saes, %(brand)s volverá a cargar con Espazos desactivados. Comunidades e etiquetas personais serán visibles outra vez." } From 9cc24657878ca58371ded2479234c2b048486dd6 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 14 May 2021 04:48:54 +0000 Subject: [PATCH 030/174] Translated using Weblate (Galician) Currently translated at 100.0% (2968 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 110f95c8b2..0de93753f7 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -3366,5 +3366,6 @@ "Add reaction": "Engadir reacción", "Send and receive voice messages": "Enviar e recibir mensaxes de voz", "Your feedback will help make spaces better. The more detail you can go into, the better.": "A túa opinión axudaranos a mellorar os espazos. Canto máis detallada sexa moito mellor para nós.", - "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Se saes, %(brand)s volverá a cargar con Espazos desactivados. Comunidades e etiquetas personais serán visibles outra vez." + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Se saes, %(brand)s volverá a cargar con Espazos desactivados. Comunidades e etiquetas personais serán visibles outra vez.", + "Message search initialisation failed": "Fallo a inicialización da busca de mensaxes" } From 7e1f5625fa39b3af0099cdf095141e03967f1c33 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Fri, 14 May 2021 07:32:02 +0000 Subject: [PATCH 031/174] Translated using Weblate (Albanian) Currently translated at 99.7% (2961 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sq/ --- src/i18n/strings/sq.json | 52 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index ab4d9862c8..505b98bcc8 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -3305,5 +3305,55 @@ "%(count)s results in all spaces|other": "%(count)s përfundime në krejt hapësirat", "You have no ignored users.": "S’keni përdorues të shpërfillur.", "Play": "Luaje", - "Pause": "Ndalesë" + "Pause": "Ndalesë", + "This is an experimental feature. For now, new users receiving an invite will have to open the invite on to actually join.": "Kjo është një veçori eksperimentale. Hëpërhë, përdoruesve të rinj që marrin një ftesë, do t’u duhet ta hapin ftesën në , që të marrin pjesë.", + "To join %(spaceName)s, turn on the Spaces beta": "Për të hyrë në %(spaceName)s, aktivizoni beta-n për Hapësira", + "To view %(spaceName)s, turn on the Spaces beta": "Për të parë %(spaceName)s, aktivizoni beta-n për Hapësira", + "Spaces are a beta feature.": "Hapësirat janë një veçori në version beta.", + "Search names and descriptions": "Kërko te emra dhe përshkrime", + "Select a room below first": "Së pari, përzgjidhni më poshtë një dhomë", + "Communities are changing to Spaces": "Bashkësitë po ndryshojnë në Hapësira", + "Join the beta": "Merrni pjesë te beta", + "Leave the beta": "Braktiseni beta-n", + "Beta": "Beta", + "Tap for more info": "Për më tepër hollësi, prekeni", + "Spaces is a beta feature": "Hapësirat janë një veçori në version beta", + "You may contact me if you have any follow up questions": "Mund të lidheni me mua, nëse keni pyetje të mëtejshme", + "To leave the beta, visit your settings.": "Që të braktisni beta-n, vizitoni rregullimet tuaja.", + "Your platform and username will be noted to help us use your feedback as much as we can.": "Platforma dhe emri juaj i përdoruesit do të mbahen shënim, për të na ndihmuar t’i përdorim përshtypjet tuaja sa më shumë që të mundemi.", + "%(featureName)s beta feedback": "Përshtypje për beta %(featureName)s", + "Thank you for your feedback, we really appreciate it.": "Faleminderit për përshtypjet tuaja, vërtet e çmojmë.", + "Beta feedback": "Përshtypje për versionin Beta", + "Want to add a new room instead?": "Doni të shtohet një dhomë e re, në vend të kësaj?", + "Adding rooms... (%(progress)s out of %(count)s)|one": "Po shtohet dhomë…", + "Adding rooms... (%(progress)s out of %(count)s)|other": "Po shtohen dhoma… (%(progress)s nga %(count)s)", + "Not all selected were added": "S’u shtuan të gjithë të përzgjedhurit", + "You can add existing spaces to a space.": "Mund të shtoni hapësira ekzistuese te një hapësirë.", + "Feeling experimental?": "Ndiheni eksperimentues?", + "You are not allowed to view this server's rooms list": "S’keni leje të shihni listën e dhomave të këtij shërbyesi", + "Zoom in": "Zmadhoje", + "Zoom out": "Zvogëloje", + "Add reaction": "Shtoni reagim", + "Error processing voice message": "Gabim në përpunimin e mesazhit zanor", + "Accept on your other login…": "Pranojeni te hyrja tjetër e juaja…", + "We didn't find a microphone on your device. Please check your settings and try again.": "S’gjetëm mikrofon në pajisjen tuaj. Ju lutemi, kontrolloni rregullimet tuaja dhe riprovoni.", + "No microphone found": "S’u gjet mikrofon", + "We were unable to access your microphone. Please check your browser settings and try again.": "S’qemë në gjendje të përdorim mikrofonin tuaj. Ju lutemi, kontrolloni rregullimet e shfletuesit tuaj dhe riprovoni.", + "Unable to access your microphone": "S’arrihet të përdoret mikrofoni juaj", + "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "Ndiheni eksperimentues? Laboratorët janë rruga më e mirë për t’u marrë herët me gjërat, për të provuar veçori të reja dhe për të ndihmuar t’u jepet formë atyre, përpara se të hidhen faktikisht në qarkullim. Mësoni më tepër.", + "Your access token gives full access to your account. Do not share it with anyone.": "Tokeni-i juaj i hyrjeve jep hyrje të plotë në llogarinë tuaj. Mos ia jepni kujt.", + "Access Token": "Token Hyrjesh", + "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Hapësirat janë rrugë e re për të grupuar dhoma dhe njerëz. Për t’u bërë pjesë e një hapësire ekzistuese, do t’ju duhet një ftesë.", + "Please enter a name for the space": "Ju lutemi, jepni një emër për hapësirën", + "Connecting": "Po lidhet", + "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Lejo Tek-për-Tek për thirrje 1:1 (nëse e aktivizoni këtë, pala tjetër mund të jetë në gjendje të shohë adresën tuaj IP)", + "New spinner design": "Rrotullues i ri", + "Send and receive voice messages": "Dërgoni dhe merrni mesazhe zanorë", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "Përshtypjet tuaja do t’i bëjnë hapësirat më të mira. Sa më shumë hollësi që të jepni, aq më mirë.", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Beta e gatshme për web, desktop dhe Android. Disa veçori mund të mos jenë të përdorshme në shërbyesin tuaj Home.", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "Beta-n mund ta braktisni në çfarëdo kohe, që nga rregullimet, ose duke prekur një stemë beta, si ajo më sipër.", + "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s do të ringarkohet me Hapësirat të aktivizuara. Bashkësitë dhe etiketat vetjake do të jenë të fshehura.", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta e gatshme për web, desktop dhe Android. Faleminderit që provoni beta-n.", + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Nëse ikni, %(brand)s-i do të ringarkohet me Hapësira të çaktivizuara. Bashkësitë dhe etiketat vetjake do të jenë sërish të dukshme.", + "Spaces are a new way to group rooms and people.": "Hapësirat janë një rrugë e re për të grupuar dhoma dhe njerëz." } From bf5e07db88ed30f128c53ec34741aa2f5d3baba9 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 14 May 2021 04:51:36 +0000 Subject: [PATCH 032/174] Translated using Weblate (Albanian) Currently translated at 99.7% (2961 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sq/ --- src/i18n/strings/sq.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index 505b98bcc8..8935b5e348 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -3355,5 +3355,6 @@ "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s do të ringarkohet me Hapësirat të aktivizuara. Bashkësitë dhe etiketat vetjake do të jenë të fshehura.", "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta e gatshme për web, desktop dhe Android. Faleminderit që provoni beta-n.", "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Nëse ikni, %(brand)s-i do të ringarkohet me Hapësira të çaktivizuara. Bashkësitë dhe etiketat vetjake do të jenë sërish të dukshme.", - "Spaces are a new way to group rooms and people.": "Hapësirat janë një rrugë e re për të grupuar dhoma dhe njerëz." + "Spaces are a new way to group rooms and people.": "Hapësirat janë një rrugë e re për të grupuar dhoma dhe njerëz.", + "Message search initialisation failed": "Dështoi gatitje kërkimi mesazhesh" } From 779972ea51a3aec221c06f48a16258fc379f0704 Mon Sep 17 00:00:00 2001 From: Trendyne Date: Fri, 14 May 2021 15:25:39 +0000 Subject: [PATCH 033/174] Translated using Weblate (Icelandic) Currently translated at 16.2% (481 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/is/ --- src/i18n/strings/is.json | 77 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/is.json b/src/i18n/strings/is.json index 90b8bc8d13..ae337d56bf 100644 --- a/src/i18n/strings/is.json +++ b/src/i18n/strings/is.json @@ -466,5 +466,80 @@ "User %(user_id)s may or may not exist": "Notandi %(user_id)s gæti verið til", "User %(user_id)s does not exist": "Notandi %(user_id)s er ekki til", "User %(userId)s is already in the room": "Notandi %(userId)s er nú þegar í herberginu", - "You do not have permission to invite people to this room.": "Þú hefur ekki heimild til að bjóða fólk í þessa spjallrás." + "You do not have permission to invite people to this room.": "Þú hefur ekki heimild til að bjóða fólk í þessa spjallrás.", + "Leave Room": "Fara af Spjallrás", + "Add room": "Bæta við herbergi", + "Use a more compact ‘Modern’ layout": "Nota þéttara ‘nútímalegt’ skipulag", + "Switch to dark mode": "Skiptu yfir í dökkstillingu", + "Switch to light mode": "Skiptu yfir í ljósstillingu", + "Modify widgets": "Breyta viðmótshluta", + "Room Info": "Herbergis upplýsingar", + "Room information": "Upplýsingar um herbergi", + "Room options": "Herbergisvalkostir", + "Invite People": "Bjóða Fólki", + "Invite people": "Bjóða fólki", + "%(count)s people|other": "%(count)s manns", + "%(count)s people|one": "%(count)s manneskja", + "People": "Fólk", + "Finland": "Finnland", + "Norway": "Noreg", + "Denmark": "Danmörk", + "Iceland": "Ísland", + "Mentions & Keywords": "Nefnir og stikkorð", + "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "Ef þú hættir við núna geturðu tapað dulkóðuðum skilaboðum og gögnum ef þú missir aðgang að innskráningum þínum.", + "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Heimaþjónastjórnandi þinn hefur lokað á sjálfkrafa dulkóðun í einkaherbergjum og beinskilaboðum.", + "You can’t disable this later. Bridges & most bots won’t work yet.": "Þú getur ekki gert þetta óvirkt síðar. Brýr og flest vélmenni virka ekki ennþá.", + "Travel & Places": "Ferðalög og staðir", + "Food & Drink": "Mat og drykkur", + "Animals & Nature": "Dýr og náttúra", + "Smileys & People": "Broskarlar og fólk", + "Voice & Video": "Rödd og myndband", + "Roles & Permissions": "Hlutverk og heimildir", + "Help & About": "Hjálp og um", + "Reject & Ignore user": "Hafna og hunsa notanda", + "Security & privacy": "Öryggi og einkalíf", + "Security & Privacy": "Öryggi & Einkalíf", + "Feedback sent": "Endurgjöf sent", + "Send feedback": "Senda endurgjöf", + "Feedback": "Endurgjöf", + "%(featureName)s beta feedback": "%(featureName)s beta endurgjöf", + "Thank you for your feedback, we really appreciate it.": "Þakka þér fyrir athugasemdir þínar.", + "Beta feedback": "Beta endurgjöf", + "All settings": "Allar stillingar", + "Notification settings": "Tilkynningarstillingar", + "Change notification settings": "Breytta tilkynningastillingum", + "You can't send any messages until you review and agree to our terms and conditions.": "Þú getur ekki sent nein skilaboð fyrr en þú hefur farið yfir og samþykkir skilmála okkar.", + "Send a Direct Message": "Senda beinskilaboð", + "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.": "Tilkynning um þessi skilaboð mun senda einstakt 'atburðarauðkenni' til stjórnanda heimaþjónns. Ef skilaboð í þessu herbergi eru dulkóðuð getur stjórnandi heimaþjónns ekki lesið skilaboðatextann eða skoðað skrár eða myndir.", + "Send a message…": "Senda skilaboð…", + "Send message": "Senda skilaboð", + "Sending your message...": "Er að senda skilaboð þitt...", + "Send as message": "Senda sem skilaboð", + "You can use /help to list available commands. Did you mean to send this as a message?": "Þú getur notað /help til að lista tilteknar skipanir. Ætlaðir þú að senda þetta sem skilaboð?", + "Send messages": "Senda skilaboð", + "Sends the given message with snowfall": "Sendir skilaboðið með snjókomu", + "Sends the given message with fireworks": "Sendir skilaboðið með flugeldum", + "Sends the given message with confetti": "Sendir skilaboðið með skrauti", + "Never send encrypted messages to unverified sessions in this room from this session": "Aldrei senda dulrituð skilaboð af þessu tæki til ósannvottaðra tækja í þessu herbergi", + "Never send encrypted messages to unverified sessions from this session": "Aldrei senda dulrituð skilaboð af þessu tæki til ósannvottaðra tækja", + "Use Ctrl + Enter to send a message": "Notaðu Ctrl + Enter til að senda skilaboð", + "Use Command + Enter to send a message": "Notaðu Command + Enter til að senda skilaboð", + "Jump to the bottom of the timeline when you send a message": "Hoppaðu neðst á tímalínunni þegar þú sendir skilaboð", + "Send and receive voice messages": "Senda og taka á móti talskilaboðum", + "%(senderName)s: %(message)s": "%(senderName)s: %(message)s", + "Send %(msgtype)s messages as you in your active room": "Senda %(msgtype)s skilaboð sem þú í virka herbergi þínu", + "Send %(msgtype)s messages as you in this room": "Senda %(msgtype)s skilaboð sem þú í þessu herbergi", + "Send text messages as you in your active room": "Senda texta skilaboð sem þú í virku herbergi þínu", + "Send text messages as you in this room": "Senda texta skilaboð sem þú í þessu herbergi", + "Send messages as you in your active room": "Senda skilaboð sem þú í virku herbergi þínu", + "Send messages as you in this room": "Senda skilaboð sem þú í þessu herbergi", + "%(senderName)s changed the pinned messages for the room.": "%(senderName)s breytti föstum skilaboðum fyrir herbergið.", + "Sends a message to the given user": "Sendir skilaboð til viðkomandi notanda", + "Sends the given message coloured as a rainbow": "Sendir gefið skilaboð litað sem regnbogi", + "Sends a message as html, without interpreting it as markdown": "Sendir skilaboð sem html, án þess að túlka það sem markdown", + "Sends a message as plain text, without interpreting it as markdown": "Sendir skilaboð sem óbreyttur texti án þess að túlka það sem markdown", + "Sends the given message as a spoiler": "Sendir skilaboðið sem spoiler", + "No need for symbols, digits, or uppercase letters": "Engin þörf á táknum, tölustöfum, eða hástöfum", + "Use a few words, avoid common phrases": "Notaðu nokkur orð. Forðastu algengar setningar", + "Unknown server error": "Óþekkt villa á þjóni" } From 159693fc4711f7a8bb969cef9a6b833a1f671418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Fri, 14 May 2021 06:40:16 +0000 Subject: [PATCH 034/174] Translated using Weblate (Estonian) Currently translated at 100.0% (2968 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 88480ceb6e..47d57e908a 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -3332,5 +3332,17 @@ "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "Käivitame %(brand)s uuesti nii, et kogukonnakeskused on kasutusel. Vana tüüpi kogukonnad ja kohandatud sildid on siis välja lülitatud.", "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Käivitame %(brand)s uuesti nii, et kogukonnakeskused ei ole kasutusel. Vana tüüpi kogukonnad ja kohandatud sildid saavad jälle olema kasutusel.", "Beta available for web, desktop and Android. Thank you for trying the beta.": "Rakenduse beetaversioon on saadaval veebirakendusena, töölauarakendusena ja Androidi jaoks. Tänud, et oled huviline katsetama meie rakendust.", - "Spaces are a new way to group rooms and people.": "Kogukonnakeskused on uus viis jututubade ja inimeste ühendamiseks." + "Spaces are a new way to group rooms and people.": "Kogukonnakeskused on uus viis jututubade ja inimeste ühendamiseks.", + "Spaces are a beta feature.": "Kogukonnakeskused on veel katsetamisjärgus funktsionaalsus.", + "Search names and descriptions": "Otsi nimede ja kirjelduste seast", + "You may contact me if you have any follow up questions": "Kui sul on lisaküsimusi, siis vastan neile hea meelega", + "To leave the beta, visit your settings.": "Beetaversiooni saad välja lülitada rakenduse seadistustest.", + "Your platform and username will be noted to help us use your feedback as much as we can.": "Lisame sinu kommentaaridele ka kasutajanime ja operatsioonisüsteemi.", + "%(featureName)s beta feedback": "%(featureName)s testversiooni tagasiside", + "Thank you for your feedback, we really appreciate it.": "Täname sind nende kommentaaride eest.", + "Beta feedback": "Tagasiside testversioonile", + "Add reaction": "Lisa reaktsioon", + "Send and receive voice messages": "Saada ja võta vastu häälsõnumeid", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "Sinu tagasiside aitab teha kogukonnakeskuseid paremaks. Mida detailsemalt sa oma arvamust kirjeldad, seda parem.", + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Kui sa lahkud, siis käivitame %(brand)s uuesti nii, et kogukonnakeskused ei ole kasutusel. Vana tüüpi kogukonnad ja kohandatud sildid saavad jälle olema kasutusel." } From b834ac8a080d6f31a6d2a40b9c78120a2fe5eee3 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 14 May 2021 04:45:22 +0000 Subject: [PATCH 035/174] Translated using Weblate (Estonian) Currently translated at 100.0% (2968 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 47d57e908a..5e8d744cca 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -3344,5 +3344,6 @@ "Add reaction": "Lisa reaktsioon", "Send and receive voice messages": "Saada ja võta vastu häälsõnumeid", "Your feedback will help make spaces better. The more detail you can go into, the better.": "Sinu tagasiside aitab teha kogukonnakeskuseid paremaks. Mida detailsemalt sa oma arvamust kirjeldad, seda parem.", - "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Kui sa lahkud, siis käivitame %(brand)s uuesti nii, et kogukonnakeskused ei ole kasutusel. Vana tüüpi kogukonnad ja kohandatud sildid saavad jälle olema kasutusel." + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Kui sa lahkud, siis käivitame %(brand)s uuesti nii, et kogukonnakeskused ei ole kasutusel. Vana tüüpi kogukonnad ja kohandatud sildid saavad jälle olema kasutusel.", + "Message search initialisation failed": "Sõnumite otsingu alustamine ei õnnestunud" } From 555b2e3266e542e20d02f7c81984d4af33534402 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sun, 16 May 2021 08:00:54 -0400 Subject: [PATCH 036/174] Reuse Spinner styles for InlineSpinner Signed-off-by: Robin Townsend --- res/css/views/elements/_InlineSpinner.scss | 13 ------------- src/components/views/elements/InlineSpinner.js | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/res/css/views/elements/_InlineSpinner.scss b/res/css/views/elements/_InlineSpinner.scss index c850191b93..ca5cb5d3a8 100644 --- a/res/css/views/elements/_InlineSpinner.scss +++ b/res/css/views/elements/_InlineSpinner.scss @@ -23,19 +23,6 @@ limitations under the License. vertical-align: -3px; } -@keyframes spin { - from { - transform: rotateZ(0deg); - } - to { - transform: rotateZ(360deg); - } -} - .mx_InlineSpinner_icon { display: inline-block; - background-color: $primary-fg-color; - mask: url('$(res)/img/spinner.svg'); - mask-size: contain; - animation: 1.1s steps(12, end) infinite spin; } diff --git a/src/components/views/elements/InlineSpinner.js b/src/components/views/elements/InlineSpinner.js index feb0adb7a1..757e809564 100644 --- a/src/components/views/elements/InlineSpinner.js +++ b/src/components/views/elements/InlineSpinner.js @@ -40,7 +40,7 @@ export default class InlineSpinner extends React.Component { } else { icon = (
From fa77bd91bb719ff7f51034281ed566b9a7da2458 Mon Sep 17 00:00:00 2001 From: libexus Date: Sat, 15 May 2021 09:22:28 +0000 Subject: [PATCH 037/174] Translated using Weblate (German) Currently translated at 98.9% (2937 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 7a16e994cf..0113f1d07f 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -451,7 +451,7 @@ "Pinned Messages": "Angeheftete Nachrichten", "%(senderName)s changed the pinned messages for the room.": "%(senderName)s hat die angehefteten Nachrichten für diesen Raum geändert.", "Jump to read receipt": "Zur Lesebestätigung springen", - "Message Pinning": "Nachrichtenanheftung", + "Message Pinning": "Nachrichten anheften", "Long Description (HTML)": "Lange Beschreibung (HTML)", "Jump to message": "Zur Nachricht springen", "No pinned messages.": "Keine angehefteten Nachrichten vorhanden.", @@ -795,7 +795,7 @@ "We encountered an error trying to restore your previous session.": "Wir haben ein Problem beim Wiederherstellen deiner vorherigen Sitzung festgestellt.", "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Den Browser-Speicher zu löschen kann das Problem lösen, wird dich aber abmelden und verschlüsselte Chats unlesbar machen.", "Collapse Reply Thread": "Antwort-Thread zusammenklappen", - "Enable widget screenshots on supported widgets": "Bildschirmfotos bei unterstützten Widgets aktivieren", + "Enable widget screenshots on supported widgets": "Bildschirmfotos für unterstützte Widgets", "Send analytics data": "Analysedaten senden", "e.g. %(exampleValue)s": "z.B. %(exampleValue)s", "Muted Users": "Stummgeschaltete Benutzer", @@ -1336,7 +1336,7 @@ "Sends a message as plain text, without interpreting it as markdown": "Verschickt eine Nachricht in Rohtext, ohne sie als Markdown darzustellen", "Use an identity server to invite by email. Manage in Settings.": "Mit einem Identitätsserver kannst du über E-Mail Einladungen zu verschicken. Verwalte ihn in den Einstellungen.", "%(name)s (%(userId)s)": "%(name)s (%(userId)s)", - "Try out new ways to ignore people (experimental)": "Verwende neue Möglichkeiten, Menschen zu blockieren (experimentell)", + "Try out new ways to ignore people (experimental)": "Verwende neue Möglichkeiten, Menschen zu blockieren", "Send read receipts for messages (requires compatible homeserver to disable)": "Lesebestätigungen für Nachrichten senden (Deaktivieren erfordert einen kompatiblen Heimserver)", "My Ban List": "Meine Bannliste", "This is your list of users/servers you have blocked - don't leave the room!": "Dies ist die Liste von Benutzer und Servern, die du blockiert hast - verlasse diesen Raum nicht!", @@ -1368,7 +1368,7 @@ "%(num)s hours from now": "in %(num)s Stunden", "about a day from now": "in etwa einem Tag", "%(num)s days from now": "in %(num)s Tagen", - "Show info about bridges in room settings": "Information über Brücken in den Raumeinstellungen anzeigen", + "Show info about bridges in room settings": "Information über Brücken in Raumeinstellungen", "Enable message search in encrypted rooms": "Nachrichtensuche in verschlüsselten Räumen aktivieren", "Lock": "Schloss", "Later": "Später", @@ -1651,7 +1651,7 @@ "Not Trusted": "Nicht vertraut", "Manually Verify by Text": "Verifiziere manuell mit einem Text", "Interactively verify by Emoji": "Verifiziere interaktiv mit Emojis", - "Support adding custom themes": "Unterstütze das Hinzufügen von benutzerdefinierten Designs", + "Support adding custom themes": "Benutzerdefinierte Designs", "Ask this user to verify their session, or manually verify it below.": "Bitte diesen Nutzer, seine Sitzung zu verifizieren, oder verifiziere diese unten manuell.", "a few seconds from now": "in ein paar Sekunden", "Manually verify all remote sessions": "Remotesitzungen manuell verifizieren", @@ -1700,7 +1700,7 @@ "This room is end-to-end encrypted": "Dieser Raum ist Ende-zu-Ende verschlüsselt", "You are not subscribed to any lists": "Du hast keine Listen abonniert", "Error adding ignored user/server": "Fehler beim Blockieren eines Nutzers/Servers", - "None": "Keine", + "None": "Nichts", "Ban list rules - %(roomName)s": "Verbotslistenregeln - %(roomName)s", "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.": "Füge hier die Benutzer und Server hinzu, die du blockieren willst. Verwende Sternchen, damit %(brand)s mit beliebigen Zeichen übereinstimmt. Bspw. würde @bot: * alle Benutzer blockieren, die auf einem Server den Namen 'bot' haben.", "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.": "Das Ignorieren von Personen erfolgt über Sperrlisten. Wenn eine Sperrliste abonniert wird, werden die von dieser Liste blockierten Benutzer und Server ausgeblendet.", @@ -1764,7 +1764,7 @@ "Backup has a valid signature from unverified session ": "Die Sicherung hat eine gültige Signatur von einer nicht verifizierten Sitzung ", "Backup has an invalid signature from verified session ": "Die Sicherung hat eine ungültige Signatur von einer verifizierten Sitzung ", "Backup has an invalid signature from unverified session ": "Die Sicherung hat eine ungültige Signatur von einer nicht verifizierten Sitzung ", - "Your keys are not being backed up from this session.": "Deine Schlüssel werden nicht von dieser Sitzung gesichert.", + "Your keys are not being backed up from this session.": "Deine Schlüssel werden von dieser Sitzung nicht gesichert.", "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "Zur Zeit verwendest du , um Kontakte zu finden und von anderen gefunden zu werden. Du kannst deinen Identitätsserver weiter unten ändern.", "Invalid theme schema.": "Ungültiges Designschema.", "Error downloading theme information.": "Fehler beim herunterladen des Themas.", @@ -2321,7 +2321,7 @@ "%(senderName)s invited %(targetName)s": "%(senderName)s hat %(targetName)s eingeladen", "You changed the room topic": "Du hast das Raumthema geändert", "%(senderName)s changed the room topic": "%(senderName)s hat das Raumthema geändert", - "New spinner design": "Neue Warteanimation", + "New spinner design": "Neue Ladeanimation", "Use a more compact ‘Modern’ layout": "Modernes kompaktes Layout", "Message deleted on %(date)s": "Nachricht am %(date)s gelöscht", "Wrong file type": "Falscher Dateityp", @@ -2337,7 +2337,7 @@ "Are you sure you want to cancel entering passphrase?": "Bist du sicher, dass du die Eingabe der Passphrase abbrechen möchtest?", "Use your account to sign in to the latest version": "Melde dich mit deinem Account in der neuesten Version an", "* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s", - "Enable advanced debugging for the room list": "Erweiterte Fehlersuche für die Raumliste aktivieren", + "Enable advanced debugging for the room list": "Erweiterte Fehlersuche für die Raumliste", "Enable experimental, compact IRC style layout": "Kompaktes Layout im IRC-Stil (experimentell)", "User menu": "Benutzermenü", "%(brand)s Web": "%(brand)s Web", @@ -2345,10 +2345,10 @@ "%(brand)s iOS": "%(brand)s iOS", "%(brand)s X for Android": "%(brand)s X für Android", "We’re excited to announce Riot is now Element": "Wir freuen uns zu verkünden, dass Riot jetzt Element ist", - "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.": "%(brand)s kann verschlüsselte Nachrichten nicht sicher während der Ausführung im Browser durchsuchen. Benutze %(brand)s Desktop, um verschlüsselte Nachrichten in den Suchergebnissen angezeigt zu bekommen.", + "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.": "Das Durchsuchen von verschlüsselten Nachrichten wird aus Sicherheitsgründen nur von %(brand)s Desktop unterstützt. Hier geht's zum Download.", "Show rooms with unread messages first": "Räume mit ungelesenen Nachrichten zuerst zeigen", "Show previews of messages": "Nachrichtenvorschau anzeigen", - "Use default": "Standardeinstellungen benutzen", + "Use default": "Standardeinstellungen", "Mentions & Keywords": "Erwähnungen und Schlüsselwörter", "Notification options": "Benachrichtigungsoptionen", "Forget Room": "Raum vergessen", @@ -2916,7 +2916,7 @@ "United Kingdom": "Großbritannien", "We call the places you where you can host your account ‘homeservers’.": "Orte, an denen du dein Benutzerkonto hosten kannst, nennen wir \"Homeserver\".", "Specify a homeserver": "Gib einen Homeserver an", - "Render LaTeX maths in messages": "LaTeX-Matheformeln in Nachrichten anzeigen", + "Render LaTeX maths in messages": "LaTeX-Matheformeln", "Decide where your account is hosted": "Gib an wo dein Benutzerkonto gehostet werden soll", "Already have an account? Sign in here": "Hast du schon ein Benutzerkonto? Melde dich hier an", "%(ssoButtons)s Or %(usernamePassword)s": "%(ssoButtons)s oder %(usernamePassword)s", @@ -3314,7 +3314,7 @@ "No microphone found": "Kein Mikrofon gefunden", "We were unable to access your microphone. Please check your browser settings and try again.": "Fehler beim Zugriff auf dein Mikrofon. Überprüfe deine Browsereinstellungen und versuche es nochmal.", "Unable to access your microphone": "Fehler beim Zugriff auf Mikrofon", - "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "Lust auf ein paar Experimente? In den Laboreinstellungen kannst du zukünftige Features vor der Veröffentlichung testen und uns mit Feedback beim Verbessern helfen. Mehr Infos.", + "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "Hier kannst du zukünftige Features noch vor der Veröffentlichung testen und uns mit Feedback beim Verbessern helfen. Mehr Infos.", "Please enter a name for the space": "Gib den Namen des Spaces ein", "Connecting": "Verbinden", "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Direktverbindung für Direktanrufe aktivieren. Dadurch sieht dein Gegenüber möglicherweise deine IP-Adresse.", @@ -3326,5 +3326,6 @@ "Spaces are a beta feature.": "Spaces sind in der Beta.", "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Wir haben Spaces entwickelt, damit ihr eure vielen Räume besser organisieren könnt. Um einen existierenden Space beitreten zu können musst du (noch) von jemandem eingeladen werden.", "Spaces are a new way to group rooms and people.": "Wir haben Spaces entwickelt, damit ihr eure vielen Räume besser organisieren könnt.", - "Message search initialisation failed": "Initialisierung der Nachrichtensuche fehlgeschlagen" + "Message search initialisation failed": "Initialisierung der Nachrichtensuche fehlgeschlagen", + "Send and receive voice messages": "Sprachnachrichten" } From ba5a769a609822b1bdb5491f80e90626b070dc16 Mon Sep 17 00:00:00 2001 From: iaiz Date: Mon, 17 May 2021 11:13:39 +0000 Subject: [PATCH 038/174] Translated using Weblate (Spanish) Currently translated at 99.9% (2966 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/es/ --- src/i18n/strings/es.json | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index ec5e4c1bfe..0e4d50210a 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -2102,7 +2102,7 @@ "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Prototipo de comunidades v2. Requiere un servidor compatible. Altamente experimental - usar con precuación.", "Font size": "Tamaño del texto", "Use custom size": "Usar un tamaño personalizado", - "Use a more compact ‘Modern’ layout": "Usar un diseño más «moderno y compacto", + "Use a more compact ‘Modern’ layout": "Usar un diseño más «moderno y compacto»", "Use a system font": "Usar una fuente del sistema", "System font name": "Nombre de la fuente", "Enable experimental, compact IRC style layout": "Activar el diseño experimental de IRC compacto", @@ -3292,5 +3292,18 @@ "Beta available for web, desktop and Android. Thank you for trying the beta.": "Versión beta disponible para web, escritorio y Android. Gracias por usar la beta.", "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s volverá a cargarse con los espacios desactivados. Las comunidades y etiquetas personalizadas serán visibles de nuevo.", "Spaces are a new way to group rooms and people.": "Los espacios son una nueva manera de agrupar salas y gente.", - "Message search initialisation failed": "Ha fallado la inicialización de la búsqueda de mensajes" + "Message search initialisation failed": "Ha fallado la inicialización de la búsqueda de mensajes", + "Spaces are a beta feature.": "Los espacios son una funcionalidad en beta.", + "Search names and descriptions": "Buscar por nombre y descripción", + "You may contact me if you have any follow up questions": "Os podéis poner en contacto conmigo si tenéis alguna pregunta", + "To leave the beta, visit your settings.": "Para salir de la beta, ve a tus ajustes.", + "Your platform and username will be noted to help us use your feedback as much as we can.": "Tu nombre de usuario y plataforma serán adjuntados, para que podamos interpretar tus comentarios lo mejor posible.", + "%(featureName)s beta feedback": "Comentarios sobre la funcionalidad beta %(featureName)s", + "Thank you for your feedback, we really appreciate it.": "Muchas gracias por tus comentarios.", + "Beta feedback": "Danos tu opinión sobre la beta", + "Add reaction": "Reaccionar", + "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "¿Te apetece probar cosas nuevas? Los experimentos son la mejor manera de conseguir acceso anticipado a nuevas funcionalidades, probarlas y ayudar a mejorarlas antes de su lanzamiento. Más información.", + "Send and receive voice messages": "Enviar y recibir mensajes de voz", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "Tus comentarios ayudarán a mejorar los espacios. Cuanto más detalle incluyas, mejor.", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Beta disponible para la versión web, de escritorio o Android. Puede que algunas funcionalidades no estén disponibles en tu servidor base." } From 9d538614c4aaa4feca5635666a6ecf727b860e9d Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Mon, 17 May 2021 14:15:40 +0000 Subject: [PATCH 039/174] Translated using Weblate (French) Currently translated at 100.0% (2968 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 1d03786f71..b4bf9de59f 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -3332,5 +3332,10 @@ "Beta available for web, desktop and Android. Thank you for trying the beta.": "Bêta disponible pour l’application web, de bureau et Android. Merci d’essayer la bêta.", "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s va être redémarré avec les espaces désactivés. Les communautés et les étiquettes personnalisées seront de nouveau visibles.", "Spaces are a new way to group rooms and people.": "Les espaces sont un nouveau moyen de regrouper les salons et les personnes.", - "Message search initialisation failed": "Échec de l’initialisation de la recherche de message" + "Message search initialisation failed": "Échec de l’initialisation de la recherche de message", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "Vos commentaires aideront à rendre les espaces meilleurs. N'hésitez pas à entrer dans les détails.", + "%(featureName)s beta feedback": "Commentaires sur la bêta de %(featureName)s", + "Thank you for your feedback, we really appreciate it.": "Merci pour vos commentaires, nous en sommes vraiment reconnaissants.", + "Beta feedback": "Commentaires sur la bêta", + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Si vous quittez, %(brand)s sera rechargé avec les espaces désactivés. Les communautés et les étiquettes personnalisées seront à nouveau visibles." } From b48be43a2fa82d67608fb8539b23849b5216640c Mon Sep 17 00:00:00 2001 From: Thibault Martin Date: Sat, 15 May 2021 07:59:35 +0000 Subject: [PATCH 040/174] Translated using Weblate (French) Currently translated at 100.0% (2968 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index b4bf9de59f..36446ebba3 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -3337,5 +3337,12 @@ "%(featureName)s beta feedback": "Commentaires sur la bêta de %(featureName)s", "Thank you for your feedback, we really appreciate it.": "Merci pour vos commentaires, nous en sommes vraiment reconnaissants.", "Beta feedback": "Commentaires sur la bêta", - "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Si vous quittez, %(brand)s sera rechargé avec les espaces désactivés. Les communautés et les étiquettes personnalisées seront à nouveau visibles." + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Si vous quittez, %(brand)s sera rechargé avec les espaces désactivés. Les communautés et les étiquettes personnalisées seront à nouveau visibles.", + "Spaces are a beta feature.": "Les espaces sont une fonctionnalité en bêta.", + "Search names and descriptions": "Rechercher par nom et description", + "You may contact me if you have any follow up questions": "Vous pouvez me contacter si vous avez des questions par la suite", + "To leave the beta, visit your settings.": "Pour quitter la bêta, consultez les paramètres.", + "Your platform and username will be noted to help us use your feedback as much as we can.": "Votre plateforme et nom d’utilisateur seront consignés pour nous aider à tirer le maximum de vos retours.", + "Add reaction": "Ajouter une réaction", + "Send and receive voice messages": "Envoyer et recevoir des messages vocaux" } From 5e0db72582510616aefbd307ee9498d4bcd1d971 Mon Sep 17 00:00:00 2001 From: jelv Date: Sun, 16 May 2021 06:41:15 +0000 Subject: [PATCH 041/174] Translated using Weblate (Dutch) Currently translated at 100.0% (2968 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/nl/ --- src/i18n/strings/nl.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index 69a73ab76b..94da2fccc1 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -3241,5 +3241,17 @@ "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta beschikbaar voor web, desktop en Android. Bedankt dat u de beta wilt proberen.", "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s zal herladen met Spaces uitgeschakeld. Gemeenschappen en labels zullen weer zichtbaar worden.", "Spaces are a new way to group rooms and people.": "Spaces zijn de nieuwe manier om gesprekken en personen te groeperen.", - "Message search initialisation failed": "Zoeken in berichten opstarten is mislukt" + "Message search initialisation failed": "Zoeken in berichten opstarten is mislukt", + "Spaces are a beta feature.": "Spaces zijn een beta functie.", + "Search names and descriptions": "Namen en beschrijvingen zoeken", + "You may contact me if you have any follow up questions": "U mag contact met mij opnemen als u nog vervolg vragen heeft", + "To leave the beta, visit your settings.": "Om de beta te verlaten, ga naar uw instellingen.", + "Your platform and username will be noted to help us use your feedback as much as we can.": "Uw platform en gebruikersnaam zullen worden opgeslagen om onze te helpen uw feedback zo goed mogelijk te gebruiken.", + "%(featureName)s beta feedback": "%(featureName)s beta feedback", + "Thank you for your feedback, we really appreciate it.": "Bedankt voor uw feedback, we waarderen het enorm.", + "Beta feedback": "Beta feedback", + "Add reaction": "Reactie toevoegen", + "Send and receive voice messages": "Stuur en ontvang spraakberichten", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "Uw feedback maakt spaces beter. Hoe meer details u kan geven, des te beter.", + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Als u de pagina nu verlaat zal %(brand)s herladen met Spaces uitgeschakeld. Gemeenschappen en labels zullen weer zichtbaar worden." } From fc4ee0df4b53cb2ca406557de23d8eec8b86e4e5 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Mon, 17 May 2021 19:49:21 +0000 Subject: [PATCH 042/174] Translated using Weblate (Swedish) Currently translated at 99.9% (2966 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 46 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 2ed4fa03d1..74e7a30032 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -3251,5 +3251,49 @@ "You have no ignored users.": "Du har inga ignorerade användare.", "Play": "Spela", "Pause": "Pausa", - "Message search initialisation failed": "Initialisering av meddelandesökning misslyckades" + "Message search initialisation failed": "Initialisering av meddelandesökning misslyckades", + "To view %(spaceName)s, turn on the Spaces beta": "För att se %(spaceName)s, aktivera utrymmesbetan", + "Spaces are a beta feature.": "Utrymmen är en betafunktion.", + "Search names and descriptions": "Sök namn och beskrivningar", + "Select a room below first": "Välj ett rum nedan först", + "Communities are changing to Spaces": "Gemenskaper byts ut mot utrymmen", + "Join the beta": "Gå med i betan", + "Leave the beta": "Lämna betan", + "Beta": "Beta", + "Tap for more info": "Klicka för mer info", + "Spaces is a beta feature": "Utrymmen är en betafunktion", + "You may contact me if you have any follow up questions": "Ni kan kontakta mig om ni har vidare frågor", + "To leave the beta, visit your settings.": "För att lämna betan, besök dina inställningar.", + "Your platform and username will be noted to help us use your feedback as much as we can.": "Din plattform och ditt användarnamn kommer att noteras för att hjälpa oss att använda din återkoppling så mycket vi kan.", + "%(featureName)s beta feedback": "%(featureName)s betaåterkoppling", + "Thank you for your feedback, we really appreciate it.": "Tack för din återkoppling, vi uppskattar det verkligen.", + "Beta feedback": "Betaåterkoppling", + "Want to add a new room instead?": "Vill du lägga till ett nytt rum istället?", + "Adding rooms... (%(progress)s out of %(count)s)|one": "Lägger till rum…", + "Adding rooms... (%(progress)s out of %(count)s)|other": "Lägger till rum… (%(progress)s av %(count)s)", + "Not all selected were added": "Inte alla valda tillades", + "You can add existing spaces to a space.": "Du kan lägga till existerande utrymmen till ett utrymme.", + "Feeling experimental?": "Känner du dig äventyrlig?", + "You are not allowed to view this server's rooms list": "Du tillåts inte att se den här serverns rumslista", + "Add reaction": "Lägg till reaktion", + "Error processing voice message": "Fel vid hantering av röstmeddelande", + "We didn't find a microphone on your device. Please check your settings and try again.": "Vi kunde inte hitta en mikrofon på din enhet. Vänligen kolla dina inställningar och försök igen.", + "No microphone found": "Ingen mikrofon hittad", + "We were unable to access your microphone. Please check your browser settings and try again.": "Vi kunde inte komma åt din mikrofon. Vänligen kolla dina webbläsarinställningar och försök igen.", + "Unable to access your microphone": "Kan inte komma åt din mikrofon", + "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "Känner du dig äventyrlig? Experiment är det bästa sättet att få saker tidigt, testa nya funktioner och hjälpa till att forma dem innan de egentligen släpps Läs mer.", + "Your access token gives full access to your account. Do not share it with anyone.": "Din åtkomsttoken ger full åtkomst till ditt konto. Dela den inte med någon.", + "Access Token": "Åtkomsttoken", + "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Utrymmen är ett nytt sätt att gruppera rum och personer. För att gå med i existerande utrymme så behöver du en inbjudan.", + "Please enter a name for the space": "Vänligen ange ett namn för utrymmet", + "Connecting": "Ansluter", + "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Tillåt peer-to-peer för 1:1-samtal (om du aktiverar det hör så kan den andra parten kanske se din IP-adress)", + "Send and receive voice messages": "Skicka och ta emot röstmeddelanden", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "Din återkoppling kommer att hjälpa till att göra utrymmen bättre. Ju fler detaljer du kan ge desto bättre.", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Beta tillgänglig för webben, skrivbord och Android. Vissa funktioner kan vara otillgängliga på din hemserver.", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "Du kan lämna betan när som helst från inställningarna eller genom att trycka en betabricka, som den ovan.", + "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s kommer att ladda om med utrymmen aktiverade. Gemenskaper och anpassade taggar kommer att döljas.", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta tillgänglig för webben, skrivbord och Android. Tack för att du provar betan.", + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Om du lämnar så kommer %(brand)s att ladda om med utrymmen inaktiverade. Gemenskaper och anpassade taggar kommer att synas igen.", + "Spaces are a new way to group rooms and people.": "Utrymmen är nya sätt att gruppera rum och personer." } From 0aa666693f0bad9be0b735d4271deeed564ac1ad Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Sun, 16 May 2021 01:02:41 +0000 Subject: [PATCH 043/174] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2968 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index a3854e59dd..1ab5a59911 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -3358,5 +3358,17 @@ "Beta available for web, desktop and Android. Thank you for trying the beta.": "測試版可用於網路、桌面與 Android。感謝您試用測試版。", "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s 將在停用空間的情況下重新載入。社群與自訂標籤將再次可見。", "Spaces are a new way to group rooms and people.": "空間是將聊天室與人們分組的一種新方式。", - "Message search initialisation failed": "訊息搜尋初始化失敗" + "Message search initialisation failed": "訊息搜尋初始化失敗", + "Spaces are a beta feature.": "空間為測試版功能。", + "Search names and descriptions": "搜尋名稱與描述", + "You may contact me if you have any follow up questions": "如果您還有任何後續問題,可以聯絡我", + "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 測試版回饋", + "Thank you for your feedback, we really appreciate it.": "感謝您的回饋,我們衷心感謝。", + "Beta feedback": "測試版回饋", + "Add reaction": "新增反應", + "Send and receive voice messages": "傳送與接收語音訊息", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "您的回饋意見將會讓空間變得更好。您可以輸入愈多細節愈好。", + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "若您離開,%(brand)s 將在停用空間的情況下重新載入。社群與自訂標籤將再次可見。" } From 3c526be5de94d3ec08682b1383f777887181b422 Mon Sep 17 00:00:00 2001 From: Andrejs Date: Sat, 15 May 2021 07:11:43 +0000 Subject: [PATCH 044/174] Translated using Weblate (Latvian) Currently translated at 49.5% (1470 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/lv/ --- src/i18n/strings/lv.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/lv.json b/src/i18n/strings/lv.json index ed7da7dc6b..b56599f26e 100644 --- a/src/i18n/strings/lv.json +++ b/src/i18n/strings/lv.json @@ -1581,5 +1581,6 @@ "Failed to set topic": "Neizdevās iestatīt tematu", "Upload files": "Failu augšupielāde", "These files are too large to upload. The file size limit is %(limit)s.": "Šie faili pārsniedz augšupielādes izmēra limitu %(limit)s.", - "Upload files (%(current)s of %(total)s)": "Failu augšupielāde (%(current)s no %(total)s)" + "Upload files (%(current)s of %(total)s)": "Failu augšupielāde (%(current)s no %(total)s)", + "Check your devices": "Pārskatiet savas ierīces" } From af5bc71602308a6aa4d05ae5cf6838756524bbf1 Mon Sep 17 00:00:00 2001 From: Kaede Date: Mon, 17 May 2021 15:55:19 +0000 Subject: [PATCH 045/174] Translated using Weblate (Japanese) Currently translated at 77.4% (2300 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/ja/ --- src/i18n/strings/ja.json | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json index 83d8961147..4eb49e45e2 100644 --- a/src/i18n/strings/ja.json +++ b/src/i18n/strings/ja.json @@ -629,7 +629,7 @@ "ex. @bob:example.com": "例 @bob:example.com", "Add User": "ユーザーを追加", "Matrix ID": "Matirx ID", - "Matrix Room ID": "Matrix 部屋ID", + "Matrix Room ID": "Matrix 部屋 ID", "email address": "メールアドレス", "You have entered an invalid address.": "無効なアドレスを入力しました。", "Try using one of the following valid address types: %(validTypesList)s.": "次の有効なアドレスタイプのいずれかを使用してください:%(validTypesList)s", @@ -1212,7 +1212,7 @@ "WARNING: Session already verified, but keys do NOT MATCH!": "警告: このセッションは検証済みです、しかし鍵が一致していません!", "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "警告: 鍵の検証に失敗しました!提供された鍵「%(fingerprint)s」は、%(userId)s およびセッション %(deviceId)s の署名鍵「%(fprint)s」と一致しません。これはつまり、あなたの会話が傍受・盗聴されようとしている恐れがあるということです!", "Show typing notifications": "入力中通知を表示する", - "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "あなたのホームサーバーが対応していない場合は (通話中に自己の IP アドレスが相手に共有されるのを防ぐために) 代替通話支援サーバー turn.matrix.org の使用を許可する", + "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "あなたのホームサーバーが対応していない場合は代替通話支援サーバー turn.matrix.org の使用を許可 (あなたの IP アドレスが通話相手に漏洩するのを防ぎます)", "Your homeserver does not support cross-signing.": "あなたのホームサーバーはクロス署名に対応していません。", "Cross-signing and secret storage are enabled.": "クロス署名および機密ストレージは有効です。", "Reset cross-signing and secret storage": "クロス署名および機密ストレージをリセット", @@ -2440,7 +2440,7 @@ "Create a space": "スペースを作成する", "Delete": "削除", "Jump to the bottom of the timeline when you send a message": "メッセージを送信する際にタイムライン最下部に移動します", - "Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features.": "Spacesはプロトタイプです。 コミュニティ、コミュニティv2、カスタムタグとは互換性がありません。 一部の機能には互換性のあるホームサーバーが必要です。", + "Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features.": "スペースはプロトタイプです。 コミュニティ、コミュニティv2、カスタムタグとは互換性がありません。 一部の機能には互換性のあるホームサーバーが必要です。", "This homeserver has been blocked by it's administrator.": "このホームサーバーは管理者によりブロックされています。", "This homeserver has been blocked by its administrator.": "このホームサーバーは管理者によりブロックされています。", "You're already in a call with this person.": "あなたは既にこの人と通話中です。", @@ -2448,5 +2448,34 @@ "Invite People": "ユーザーを招待", "Edit devices": "デバイスを編集", "%(count)s messages deleted.|one": "%(count)s 件のメッセージが削除されました。", - "%(count)s messages deleted.|other": "%(count)s 件のメッセージが削除されました。" + "%(count)s messages deleted.|other": "%(count)s 件のメッセージが削除されました。", + "To view %(spaceName)s, turn on the Spaces beta": "スペース Beta を有効にすると %(spaceName)s を表示できます", + "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "1 対 1 の通話で P2P の使用を許可 (有効にするとあなたの IP アドレスが通話相手に漏洩する可能性があります)", + "You have no ignored users.": "無視しているユーザーはいません。", + "Join the beta": "Beta に参加", + "Leave the beta": "Beta を終了", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta は、ウェブ、デスクトップ、Android で利用可能です。Beta をお試しいただきありがとうございます。", + "Your access token gives full access to your account. Do not share it with anyone.": "アクセストークンを用いるとあなたのアカウントの全てにアクセスできます。外部に公開しないでください。", + "Access Token": "アクセストークン", + "Filter all spaces": "全スペースを検索", + "Save Changes": "変更を保存", + "Edit settings relating to your space.": "スペースの設定を変更します。", + "Space settings": "スペースの設定", + "Spaces are a beta feature.": "スペースは Beta 機能です。", + "Spaces is a beta feature": "スペースは Beta 機能です", + "Spaces are a new way to group rooms and people.": "スペースは、部屋や人をグループ化する新しい方法です。", + "Spaces": "スペース", + "Welcome to ": "ようこそ ", + "Invite to just this room": "この部屋に招待", + "Invite to %(spaceName)s": "%(spaceName)s に招待", + "Quick actions": "クイックアクション", + "A private space for you and your teammates": "", + "Me and my teammates": "自分とチームメイト", + "Just me": "自分専用", + "Make sure the right people have access to %(name)s": "必要な人が %(name)s にアクセスできるようにします", + "Who are you working with?": "誰が使いますか?", + "Beta": "Beta", + "Tap for more info": "タップして詳細を表示", + "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "スペースは、部屋や人をグループ化する新しい方法です。既存のスペースに参加するには、招待が必要です。", + "Check your devices": "デバイスを確認" } From 0e2ddec7a31c2e5fb827eaa0540f5158fd422699 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Mon, 17 May 2021 07:22:08 +0000 Subject: [PATCH 046/174] Translated using Weblate (Czech) Currently translated at 100.0% (2968 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ --- src/i18n/strings/cs.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 9f3e48dd61..9701afa0e5 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -3272,5 +3272,17 @@ "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta verze je k dispozici pro web, desktop a Android. Děkujeme vám za vyzkoušení beta verze.", "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s se znovu načte s vypnutými Prostory. Skupiny a vlastní značky budou opět viditelné.", "Spaces are a new way to group rooms and people.": "Prostory představují nový způsob seskupování místností a osob.", - "Message search initialisation failed": "Inicializace vyhledávání zpráv se nezdařila" + "Message search initialisation failed": "Inicializace vyhledávání zpráv se nezdařila", + "Spaces are a beta feature.": "Prostory jsou funkcí beta verze.", + "Search names and descriptions": "Hledat názvy a popisy", + "You may contact me if you have any follow up questions": "V případě dalších dotazů se na mě můžete obrátit", + "To leave the beta, visit your settings.": "Chcete-li opustit beta verzi, jděte do nastavení.", + "Your platform and username will be noted to help us use your feedback as much as we can.": "Vaše platforma a uživatelské jméno budou zaznamenány, abychom mohli co nejlépe využít vaši zpětnou vazbu.", + "%(featureName)s beta feedback": "%(featureName)s zpětná vazba beta verze", + "Thank you for your feedback, we really appreciate it.": "Děkujeme za vaši zpětnou vazbu, velmi si jí vážíme.", + "Beta feedback": "Zpětná vazba na betaverzi", + "Add reaction": "Přidat reakci", + "Send and receive voice messages": "Odeslat a přijmout hlasové zprávy", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "Vaše zpětná vazba pomůže zlepšit prostory. Čím podrobnější bude, tím lépe.", + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Pokud odejdete, %(brand)s se znovu načte s vypnutými Prostory. Skupiny a vlastní značky budou opět viditelné." } From f3e8d137dea3499fb97b0397b923d78984b44f6d Mon Sep 17 00:00:00 2001 From: XoseM Date: Sat, 15 May 2021 04:03:50 +0000 Subject: [PATCH 047/174] Translated using Weblate (Galician) Currently translated at 100.0% (2968 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 0de93753f7..019929b081 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -3367,5 +3367,5 @@ "Send and receive voice messages": "Enviar e recibir mensaxes de voz", "Your feedback will help make spaces better. The more detail you can go into, the better.": "A túa opinión axudaranos a mellorar os espazos. Canto máis detallada sexa moito mellor para nós.", "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Se saes, %(brand)s volverá a cargar con Espazos desactivados. Comunidades e etiquetas personais serán visibles outra vez.", - "Message search initialisation failed": "Fallo a inicialización da busca de mensaxes" + "Message search initialisation failed": "Fallou a inicialización da busca de mensaxes" } From 34f902229e219d10662fea0dcbbd2592a2ee9d00 Mon Sep 17 00:00:00 2001 From: Trendyne Date: Sat, 15 May 2021 12:16:12 +0000 Subject: [PATCH 048/174] Translated using Weblate (Icelandic) Currently translated at 22.1% (656 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/is/ --- src/i18n/strings/is.json | 181 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 178 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/is.json b/src/i18n/strings/is.json index ae337d56bf..03fa871f29 100644 --- a/src/i18n/strings/is.json +++ b/src/i18n/strings/is.json @@ -136,7 +136,7 @@ "Attachment": "Viðhengi", "Hangup": "Leggja á", "Voice call": "Raddsamtal", - "Video call": "_Myndsímtal", + "Video call": "Myndsímtal", "Upload file": "Hlaða inn skrá", "Send an encrypted message…": "Senda dulrituð skilaboð…", "You do not have permission to post to this room": "Þú hefur ekki heimild til að senda skilaboð á þessa spjallrás", @@ -198,7 +198,7 @@ "Today": "Í dag", "Yesterday": "Í gær", "Error decrypting attachment": "Villa við afkóðun viðhengis", - "Copied!": "Afritað", + "Copied!": "Afritað!", "Custom Server Options": "Sérsniðnir valkostir vefþjóns", "Dismiss": "Hunsa", "Please check your email to continue registration.": "Skoðaðu tölvupóstinn þinn til að geta haldið áfram með skráningu.", @@ -541,5 +541,180 @@ "Sends the given message as a spoiler": "Sendir skilaboðið sem spoiler", "No need for symbols, digits, or uppercase letters": "Engin þörf á táknum, tölustöfum, eða hástöfum", "Use a few words, avoid common phrases": "Notaðu nokkur orð. Forðastu algengar setningar", - "Unknown server error": "Óþekkt villa á þjóni" + "Unknown server error": "Óþekkt villa á þjóni", + "Message deleted by %(name)s": "Skilaboð eytt af %(name)s", + "Message deleted": "Skilaboð eytt", + "Room list": "Herbergislisti", + "Subscribed lists": "Skráðir listar", + "eg: @bot:* or example.org": "t.d.: @vélmenni:* eða dæmi.is", + "Personal ban list": "Persónulegur bann listi", + "⚠ These settings are meant for advanced users.": "⚠ Þessar stillingar eru ætlaðar fyrir háþróaða notendur.", + "Ignored users": "Hunsaðir notendur", + "You are currently subscribed to:": "Þú ert skráður til:", + "View rules": "Skoða reglur", + "You are not subscribed to any lists": "Þú ert ekki skráður fyrir neina lista", + "You are currently ignoring:": "Þú ert að hunsa:", + "You have not ignored anyone.": "Þú hefur ekki hunsað nein.", + "User rules": "Reglur notanda", + "Server rules": "Reglur netþjóns", + "Please try again or view your console for hints.": "Vinsamlegast reyndu aftur eða skoðaðu framkvæmdaraðilaatvikuskrá þína fyrir vísbendingar.", + "Error unsubscribing from list": "Galli við að afskrá frá lista", + "Error removing ignored user/server": "Villa við að fjarlægja hunsaða notanda/netþjón", + "Use the Desktop app to search encrypted messages": "Notaðu tölvuforritið til að sía dulkóðuð skilaboð", + "Use the Desktop app to see all encrypted files": "Notaðu tölvuforritið til að sjá öll dulkóðuð gögn", + "Not encrypted": "Ekki dulkóðað", + "Encrypted by a deleted session": "Dulkóðað af eyddu tæki", + "Encrypted by an unverified session": "Dulkóðað af ósannreynu tæki", + "Enable message search in encrypted rooms": "Virka skilaboðleit í dulkóðuð herbergjum", + "This room is end-to-end encrypted": "Þetta herbergi er enda-til-enda dulkóðað", + "Unencrypted": "Ódulkóðað", + "Messages in this room are end-to-end encrypted.": "Skilaboð í þessu herbergi eru enda-til-enda dulkóðuð.", + "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Hreinsun geymslu vafrans gæti lagað vandamálið en mun skrá þig út og valda því að dulkóðaður spjallferil sé ólæsilegur.", + "Send an encrypted reply…": "Senda dulritað svar…", + "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.": "Þegar hún er gerð virk er ekki hægt að óvirka dulkóðun. Skilaboð í dulkóðuðu herbergi geta ekki verið séð af netþjóni en bara af þátttakendum í herberginu. Virkun dulkóðuns gæti komið í veg fyrir að vélmenni og brúr virki rétt. Lærðu meira um dulkóðun.", + "Once enabled, encryption cannot be disabled.": "Þegar kveikt er á dulkóðun er ekki hægt að slökkva á henni.", + "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "Í dulkóðuðum herbergjum eins og þetta er slökkt á forskoðun vefslóða sjálfgefið til að tryggja að heimaþjónn þinn (þar sem forsýningin myndast) geti ekki safnað upplýsingum um tengla sem þú sérð í þessu herbergi.", + "URL Previews": "Forskoðun Vefslóða", + "URL previews are disabled by default for participants in this room.": "Forskoðun vefslóða er ekki sjálfgefið fyrir þátttakendur í þessu herbergi.", + "URL previews are enabled by default for participants in this room.": "Forskoðun vefslóða er sjálfgefið fyrir þátttakendur í þessu herbergi.", + "You have disabled URL previews by default.": "Þú hefur óvirkt forskoðun vefslóða sjálfgefið.", + "You have enabled URL previews by default.": "Þú hefur virkt forskoðun vefslóða sjálfgefið.", + "Enable URL previews by default for participants in this room": "Virkja forskoðun vefslóða sjálfgefið fyrir þátttakendur í þessu herbergi", + "Enable URL previews for this room (only affects you)": "Virkja forskoðun vefslóða fyrir þetta herbergi (einungis fyrir þig)", + "Room settings": "Herbergisstillingar", + "Room Settings - %(roomName)s": "Herbergisstillingar - %(roomName)s", + "Pinned Messages": "Föst Skilaboð", + "No pinned messages.": "Engin föst skilaboð.", + "This is the beginning of your direct message history with .": "Þetta er upphaf beinna skilaboðasögu með .", + "Recently Direct Messaged": "Nýlega Fékk Bein Skilaboð", + "Direct Messages": "Bein skilaboð", + "Direct message": "Beint skilaboð", + "Frequently Used": "Oft notað", + "Filter all spaces": "Sía öll rými", + "Filter your rooms and spaces": "Sía rými og herbergin þín", + "Filter rooms and people": "Sía fólk og herbergi", + "Filter": "Sía", + "Your Security Key is in your Downloads folder.": "Öryggislykillinn þinn er Niðurhals möppu þinni.", + "Download logs": "Niðurhal atvikaskrá", + "Preparing to download logs": "Undirbý niðurhal atvikaskráa", + "Downloading logs": "Er að niðurhala atvikaskrá", + "Error downloading theme information.": "Villa við að niðurhala þemaupplýsingum.", + "Message downloading sleep time(ms)": "Skilaboða niðurhal svefn tími(ms)", + "How fast should messages be downloaded.": "Hve hratt ætti að hlaða niður skilaboðum.", + "Download %(text)s": "Niðurhala %(text)s", + "Share Link to User": "Deila Hlekk að Notanda", + "You have verified this user. This user has verified all of their sessions.": "Þú hefur sannreynt þennan notanda. Þessi notandi hefur sannreynt öll tæki þeirra.", + "This user has not verified all of their sessions.": "Þessi notandi hefur ekki sannreynt öll tæki þeirra.", + "%(count)s verified sessions|one": "1 sannreynt tæki", + "%(count)s verified sessions|other": "%(count)s sannreyn tæki", + "Hide verified sessions": "Fela sannreyn tæki", + "Remove recent messages": "Fjarlægja nýleg skilaboð", + "Remove recent messages by %(user)s": "Fjarlægja nýleg skilaboð af %(user)s", + "Messages in this room are not end-to-end encrypted.": "Skilaboð í þessu herbergi eru ekki enda-til-enda dulkóðuð.", + "Who would you like to add to this community?": "Hvern viltu bæta við í þetta samfélagi?", + "You cannot place a call with yourself.": "Þú getur ekki byrjað símtal með sjálfum þér.", + "You cannot place VoIP calls in this browser.": "Þú getur ekki byrjað netsímtal (VoIP) köll í þessum vafra.", + "Call Failed": "Símtal Mistókst", + "Every page you use in the app": "Sérhver síða sem þú notar í forritinu", + "Which officially provided instance you are using, if any": "Hvaða opinberlega veittan heimaþjón sem þú notar, ef einhvern", + "Whether or not you're logged in (we don't record your username)": "Hvort sem þú ert skráð(ur) inn (við skráum ekki notendanafnið þitt)", + "Add Phone Number": "Bæta Við Símanúmeri", + "Click the button below to confirm adding this phone number.": "Smelltu á hnappinn hér að neðan til að staðfesta að bæta við þessu símanúmeri.", + "Confirm adding phone number": "Staðfestu að bæta við símanúmeri", + "Add Email Address": "Bæta Við Tölvupóstfangi", + "Click the button below to confirm adding this email address.": "Smelltu á hnappinn hér að neðan til að staðfesta að bæta við þessu netfangi.", + "Confirm adding email": "Staðfestu að bæta við tölvupósti", + "Upgrade": "Uppfæra", + "Verify": "Sannreyna", + "Security": "Öryggi", + "Trusted": "Traustað", + "Subscribe": "Skrá", + "Unsubscribe": "Afskrá", + "None": "Ekkert", + "Ignored/Blocked": "Hunsað/Hindrað", + "Trust": "Treysta", + "Flags": "Fánar", + "Symbols": "Tákn", + "Objects": "Hlutir", + "Activities": "Starfsemi", + "Document": "Skjal", + "Complete": "Búið", + "View": "Skoða", + "Preview": "Forskoðun", + "Strikethrough": "Yfirstrikletrað", + "Italics": "Skáletrað", + "Bold": "Feitletrað", + "ID": "Auðkenni (ID)", + "Disconnect": "Aftengja", + "Share": "Deila", + "Revoke": "Afturkalla", + "Discovery": "Uppgötvun", + "Actions": "Aðgerðir", + "Messages": "Skilaboð", + "Summary": "Yfirlit", + "Service": "Þjónusta", + "Removing…": "Er að fjarlægja…", + "Browse": "Skoða", + "Reset": "Endursetja", + "Sounds": "Hljóð", + "edited": "breytt", + "Re-join": "Taka þátt aftur", + "Banana": "Banani", + "Fire": "Eldur", + "Cloud": "Ský", + "Moon": "Tungl", + "Globe": "Heiminn", + "Mushroom": "Sveppur", + "Cactus": "Kaktus", + "Tree": "Tré", + "Flower": "Blóm", + "Butterfly": "Fiðrildi", + "Octopus": "Kolkrabbi", + "Fish": "Fiskur", + "Turtle": "Skjaldbaka", + "Penguin": "Mörgæs", + "Rooster": "Hani", + "Panda": "Pandabjörn", + "Rabbit": "Kanína", + "Elephant": "Fíll", + "Pig": "Svín", + "Unicorn": "Einhyrningur", + "Horse": "Hestur", + "Lion": "Ljón", + "Cat": "Köttur", + "Dog": "Hundur", + "Guest": "Gestur", + "Other": "Annað", + "Confirm": "Staðfesta", + "Username": "Notandanafn", + "Join": "Taka þátt", + "Encrypted": "Dulkóðað", + "Encryption": "Dulkóðun", + "Timeline": "Tímalína", + "Composer": "Ritari", + "Preferences": "Stillingar", + "Versions": "Útgáfur", + "FAQ": "Algengar spurningar", + "Theme": "Þema", + "General": "Almennt", + "No": "Nei", + "Yes": "Já", + "Verified!": "Sannreynt!", + "Retry": "Reyna aftur", + "Download": "Niðurhal", + "Next": "Næsta", + "Legal": "Löglegt", + "Demote": "Leggja til baka", + "%(oneUser)sleft %(count)s times|one": "%(oneUser)sfór", + "%(severalUsers)sleft %(count)s times|one": "%(severalUsers)sfóru", + "%(oneUser)sjoined %(count)s times|one": "%(oneUser)sskráðist", + "%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)sskráðust", + "Stickerpack": "Límmiða pakki", + "Replying": "Svara", + "%(duration)sd": "%(duration)sd", + "%(duration)sh": "%(duration)sklst", + "%(duration)sm": "%(duration)sm", + "%(duration)ss": "%(duration)ss", + "Emoji picker": "Tjáningartáknmyndvalmynd", + "Show less": "Sýna minna" } From 2732280923aa696c559bd70ba72bc4ac5d83825e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 18 May 2021 11:54:45 +0100 Subject: [PATCH 049/174] Fix space room hierarchy not updating when removing a room --- src/components/structures/SpaceRoomDirectory.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index 5091131d49..c1e9027b1d 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -471,8 +471,12 @@ export const SpaceHierarchy: React.FC = ({ try { for (const [parentId, childId] of selectedRelations) { await cli.sendStateEvent(parentId, EventType.SpaceChild, {}, childId); - parentChildMap.get(parentId).get(childId).content = {}; - parentChildMap.set(parentId, new Map(parentChildMap.get(parentId))); + parentChildMap.get(parentId).delete(childId); + if (parentChildMap.get(parentId).size > 0) { + parentChildMap.set(parentId, new Map(parentChildMap.get(parentId))); + } else { + parentChildMap.delete(parentId); + } } } catch (e) { setError(_t("Failed to remove some rooms. Try again later")); From 655010844adcf45b1427a05a64d27409523e0d8d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 18 May 2021 13:27:34 +0100 Subject: [PATCH 050/174] Switch to using QueryMatcher for add existing to space dialog This helps it support filtering by alias --- .../dialogs/AddExistingToSpaceDialog.tsx | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx index af52fbce6c..faeaa80c15 100644 --- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx +++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx @@ -38,6 +38,7 @@ import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/Recent import ProgressBar from "../elements/ProgressBar"; import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; +import QueryMatcher from "../../../autocomplete/QueryMatcher"; interface IProps extends IDialogProps { matrixClient: MatrixClient; @@ -74,37 +75,47 @@ export const AddExistingToSpace: React.FC = ({ onFinished, }) => { const cli = useContext(MatrixClientContext); - const visibleRooms = useMemo(() => sortRooms(cli.getVisibleRooms()), [cli]); + const visibleRooms = useMemo(() => cli.getVisibleRooms().filter(r => r.getMyMembership() === "join"), [cli]); const [selectedToAdd, setSelectedToAdd] = useState(new Set()); const [progress, setProgress] = useState(null); const [error, setError] = useState(null); const [query, setQuery] = useState(""); - const lcQuery = query.toLowerCase(); + const lcQuery = query.toLowerCase().trim(); - const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId); - const existingSubspacesSet = new Set(existingSubspaces); - const existingRoomsSet = new Set(SpaceStore.instance.getChildRooms(space.roomId)); + const existingSubspacesSet = useMemo(() => new Set(SpaceStore.instance.getChildSpaces(space.roomId)), [space]); + const existingRoomsSet = useMemo(() => new Set(SpaceStore.instance.getChildRooms(space.roomId)), [space]); - const joinRule = space.getJoinRule(); - const [spaces, rooms, dms] = visibleRooms.reduce((arr, room) => { - if (room.getMyMembership() !== "join") return arr; - if (!room.name.toLowerCase().includes(lcQuery)) return arr; + const [spaces, rooms, dms] = useMemo(() => { + let rooms = visibleRooms; - if (room.isSpaceRoom()) { - if (room !== space && !existingSubspacesSet.has(room)) { - arr[0].push(room); - } - } else if (!existingRoomsSet.has(room)) { - if (!DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { - arr[1].push(room); - } else if (joinRule !== "public") { - // Only show DMs for non-public spaces as they make very little sense in spaces other than "Just Me" ones. - arr[2].push(room); - } + if (lcQuery) { + const matcher = new QueryMatcher(visibleRooms, { + keys: ["name"], + funcs: [r => r.getCanonicalAlias() ?? r.getAltAliases()?.[0]], + shouldMatchWordsOnly: false, + }); + + rooms = matcher.match(lcQuery); } - return arr; - }, [[], [], []]); + + const joinRule = space.getJoinRule(); + return sortRooms(rooms).reduce((arr, room) => { + if (room.isSpaceRoom()) { + if (room !== space && !existingSubspacesSet.has(room)) { + arr[0].push(room); + } + } else if (!existingRoomsSet.has(room)) { + if (!DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { + arr[1].push(room); + } else if (joinRule !== "public") { + // Only show DMs for non-public spaces as they make very little sense in spaces other than "Just Me" ones. + arr[2].push(room); + } + } + return arr; + }, [[], [], []]); + }, [visibleRooms, space, lcQuery, existingRoomsSet, existingSubspacesSet]); const addRooms = async () => { setError(null); From edb20267801fa7d20eb2a93ce092627e7c716649 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 18 May 2021 13:31:53 +0100 Subject: [PATCH 051/174] Support any alias not just first --- src/autocomplete/QueryMatcher.ts | 9 +++++++-- .../views/dialogs/AddExistingToSpaceDialog.tsx | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/autocomplete/QueryMatcher.ts b/src/autocomplete/QueryMatcher.ts index ea6e0882fd..73bb37ff0f 100644 --- a/src/autocomplete/QueryMatcher.ts +++ b/src/autocomplete/QueryMatcher.ts @@ -21,7 +21,7 @@ import {removeHiddenChars} from "matrix-js-sdk/src/utils"; interface IOptions { keys: Array; - funcs?: Array<(T) => string>; + funcs?: Array<(T) => string | string[]>; shouldMatchWordsOnly?: boolean; // whether to apply unhomoglyph and strip diacritics to fuzz up the search. Defaults to true fuzzy?: boolean; @@ -69,7 +69,12 @@ export default class QueryMatcher { if (this._options.funcs) { for (const f of this._options.funcs) { - keyValues.push(f(object)); + const v = f(object); + if (Array.isArray(v)) { + keyValues.push(...v); + } else { + keyValues.push(v); + } } } diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx index faeaa80c15..9a7f96e653 100644 --- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx +++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx @@ -92,7 +92,7 @@ export const AddExistingToSpace: React.FC = ({ if (lcQuery) { const matcher = new QueryMatcher(visibleRooms, { keys: ["name"], - funcs: [r => r.getCanonicalAlias() ?? r.getAltAliases()?.[0]], + funcs: [r => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)], shouldMatchWordsOnly: false, }); From 00024c794c65c1d67b5e467ee852f397f746f3d2 Mon Sep 17 00:00:00 2001 From: libexus Date: Tue, 18 May 2021 16:56:45 +0000 Subject: [PATCH 052/174] Translated using Weblate (German) Currently translated at 99.6% (2958 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 0113f1d07f..cfe87ad823 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -1017,7 +1017,7 @@ "Language and region": "Sprache und Region", "Theme": "Design", "Account management": "Benutzerkontenverwaltung", - "For help with using %(brand)s, click here.": "Um Hilfe zur Benutzung von %(brand)s zu erhalten, klicke hier.", + "For help with using %(brand)s, click here.": "Um Hilfe zur Benutzung von %(brand)s zu erhalten, klicke hier.", "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "Um Hilfe zur Benutzung von %(brand)s zu erhalten, klicke hier oder beginne einen Chat mit unserem Bot. Klicke dazu auf den unteren Knopf.", "Chat with %(brand)s Bot": "Chatte mit dem %(brand)s-Bot", "Help & About": "Hilfe und Über", @@ -1236,7 +1236,7 @@ "Name or Matrix ID": "Name oder Matrix-ID", "Your %(brand)s is misconfigured": "Dein %(brand)s ist falsch konfiguriert", "You cannot modify widgets in this room.": "Du darfst in diesem Raum keine Widgets verändern.", - "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Ob du die \"Breadcrumbs\"-Funktion nutzt oder nicht (Avatare oberhalb der Raumliste)", + "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Ob du die Liste der kürzlich besuchten Räume oberhalb der Raumliste nutzt", "The server does not support the room version specified.": "Der Server unterstützt die angegebene Raumversion nicht.", "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Achtung: Ein Raum-Upgrade wird die Mitglieder des Raumes nicht automatisch auf die neue Version migrieren. Wir werden in der alten Raumversion einen Link zum neuen Raum posten - Raummitglieder müssen dann auf diesen Link klicken um dem neuen Raum beizutreten.", "Replying With Files": "Mit Dateien antworten", @@ -2156,7 +2156,7 @@ "Liberate your communication": "Befreie deine Kommunikation", "Message downloading sleep time(ms)": "Wartezeit zwischen dem Herunterladen von Nachrichten (ms)", "Navigate recent messages to edit": "Letzte Nachrichten zur Bearbeitung ansehen", - "Jump to start/end of the composer": "Springe zum Anfang/Ende der Nachrichteneingabe", + "Jump to start/end of the composer": "Zu Anfang/Ende des Textfelds springen", "Navigate composer history": "Verlauf der Nachrichteneingabe durchsuchen", "If you did this accidentally, you can setup Secure Messages on this session which will re-encrypt this session's message history with a new recovery method.": "Wenn du dies versehentlich getan hast, kannst du in dieser Sitzung \"sichere Nachrichten\" einrichten, die den Nachrichtenverlauf dieser Sitzung mit einer neuen Wiederherstellungsmethode erneut verschlüsseln.", "Cancel replying to a message": "Nachricht beantworten abbrechen", @@ -3059,7 +3059,7 @@ "Cookie Policy": "Cookie-Richtlinie", "Learn more in our , and .": "Erfahre mehr in unserer , und .", "Failed to connect to your homeserver. Please close this dialog and try again.": "Verbindung zum Homeserver fehlgeschlagen. Bitte schließe diesen Dialog and versuche es erneut.", - "Abort": "Abbrechen", + "Abort": "Beenden", "Upgrade to %(hostSignupBrand)s": "Zu %(hostSignupBrand)s upgraden", "Edit Values": "Werte bearbeiten", "Value in this room:": "Wert in diesem Raum:", @@ -3239,8 +3239,8 @@ "Values at explicit levels in this room:": "Werte für explizite Stufen in diesem Raum:", "Values at explicit levels:": "Werte für explizite Stufen:", "Values at explicit levels in this room": "Werte für explizite Stufen in diesem Raum", - "Confirm abort of host creation": "Bestätige das Abbrechen der Host-Erstellung", - "Are you sure you wish to abort creation of the host? The process cannot be continued.": "Soll die Host-Erstellung wirklich abgebrochen werden? Dieser Prozess kann nicht wieder fortgesetzt werden.", + "Confirm abort of host creation": "Bestätige das Beenden der Host-Erstellung", + "Are you sure you wish to abort creation of the host? The process cannot be continued.": "Soll die Host-Erstellung wirklich beendet werden? Dieser Prozess kann nicht wieder fortgesetzt werden.", "Invite to just this room": "Nur in diesen Raum einladen", "Consult first": "Konsultiere zuerst", "Reset event store?": "Ereignisspeicher zurück setzen?", @@ -3296,7 +3296,7 @@ "You have no ignored users.": "Du ignorierst keine Benutzer.", "Error processing voice message": "Fehler beim Verarbeiten der Sprachnachricht", "To join %(spaceName)s, turn on the Spaces beta": "Um %(spaceName)s beizutreten, aktiviere die Spaces Betaversion", - "To view %(spaceName)s, turn on the Spaces beta": "Um %(spaceName)s zu betreten, aktiviere die Spaces Betaversion", + "To view %(spaceName)s, turn on the Spaces beta": "Um %(spaceName)s zu betreten, aktiviere die Spaces Beta", "Select a room below first": "Wähle zuerst einen Raum aus", "Communities are changing to Spaces": "Spaces ersetzen Communities", "Join the beta": "Beta beitreten", @@ -3308,9 +3308,9 @@ "Adding rooms... (%(progress)s out of %(count)s)|one": "Raum wird hinzugefügt...", "Adding rooms... (%(progress)s out of %(count)s)|other": "Räume werden hinzugefügt... (%(progress)s von %(count)s)", "You can add existing spaces to a space.": "Du kannst existierende Spaces zu einem Space hinzfügen.", - "Feeling experimental?": "Lust auf Experimente?", + "Feeling experimental?": "Willst du die Entwicklung von Element hautnah miterleben?", "You are not allowed to view this server's rooms list": "Du darfst diese Raumliste nicht sehen", - "We didn't find a microphone on your device. Please check your settings and try again.": "Auf deinem Gerät kann kein Mikrofon gefunden werden. Bitte überprüfe deine Einstellungen und versuche es nochmal.", + "We didn't find a microphone on your device. Please check your settings and try again.": "Es konnte kein Mikrofon gefunden werden. Überprüfe deine Einstellungen und versuche es erneut.", "No microphone found": "Kein Mikrofon gefunden", "We were unable to access your microphone. Please check your browser settings and try again.": "Fehler beim Zugriff auf dein Mikrofon. Überprüfe deine Browsereinstellungen und versuche es nochmal.", "Unable to access your microphone": "Fehler beim Zugriff auf Mikrofon", @@ -3327,5 +3327,19 @@ "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Wir haben Spaces entwickelt, damit ihr eure vielen Räume besser organisieren könnt. Um einen existierenden Space beitreten zu können musst du (noch) von jemandem eingeladen werden.", "Spaces are a new way to group rooms and people.": "Wir haben Spaces entwickelt, damit ihr eure vielen Räume besser organisieren könnt.", "Message search initialisation failed": "Initialisierung der Nachrichtensuche fehlgeschlagen", - "Send and receive voice messages": "Sprachnachrichten" + "Send and receive voice messages": "Sprachnachrichten", + "Search names and descriptions": "Nach Name und Beschreibung filtern", + "Not all selected were added": "Nicht alle Ausgewählten konnten hinzugefügt werden", + "Add reaction": "Reaktion hinzufügen", + "This is an experimental feature. For now, new users receiving an invite will have to open the invite on to actually join.": "Dieses Feature ist experimentell. Falls du eine Einladung erhältst musst du sie momentan noch auf öffnen, um beizutreten.", + "You may contact me if you have any follow up questions": "Kontaktiert mich, falls ihr weitere Fragen zu meinem Feedback habt", + "To leave the beta, visit your settings.": "Du kannst die Beta in den Einstellungen deaktivieren.", + "Your platform and username will be noted to help us use your feedback as much as we can.": "Die Platform von Element und dein Benutzername werden mitgeschickt, damit wir dein Feedback bestmöglich nachvollziehen können.", + "%(featureName)s beta feedback": "%(featureName)s-Beta Feedback", + "Thank you for your feedback, we really appreciate it.": "Uns liegt es am Herzen, Element zu verbessern. Deshalb ein großes Danke für dein Feedback.", + "Beta feedback": "Beta Feedback", + "Your access token gives full access to your account. Do not share it with anyone.": "Dein Zugriffstoken gibt vollen Zugriff auf dein Konto. Teile es niemals mit jemanden anderen.", + "Access Token": "Zugriffstoken", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "Dein Feedback hilfst uns, die Spaces zu verbessern. Je genauer, desto besser.", + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Durchs Verlassen lädt %(brand)s mit deaktivierten Spaces neu. Danach kannst du wieder Communities und Custom Tags verwenden." } From 8eb0a8b3b51d9dae1be5121c0b87128791977486 Mon Sep 17 00:00:00 2001 From: Thibault Martin Date: Tue, 18 May 2021 06:28:27 +0000 Subject: [PATCH 053/174] Translated using Weblate (French) Currently translated at 100.0% (2968 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 36446ebba3..dc8b701e35 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -3333,7 +3333,7 @@ "%(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "%(brand)s va être redémarré avec les espaces désactivés. Les communautés et les étiquettes personnalisées seront de nouveau visibles.", "Spaces are a new way to group rooms and people.": "Les espaces sont un nouveau moyen de regrouper les salons et les personnes.", "Message search initialisation failed": "Échec de l’initialisation de la recherche de message", - "Your feedback will help make spaces better. The more detail you can go into, the better.": "Vos commentaires aideront à rendre les espaces meilleurs. N'hésitez pas à entrer dans les détails.", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "Vos commentaires aideront à améliorer les espaces. N’hésitez pas à entrer dans les détails.", "%(featureName)s beta feedback": "Commentaires sur la bêta de %(featureName)s", "Thank you for your feedback, we really appreciate it.": "Merci pour vos commentaires, nous en sommes vraiment reconnaissants.", "Beta feedback": "Commentaires sur la bêta", From 90c660a74f49addca117b1d2acefa665152d19a5 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Mon, 17 May 2021 19:52:36 +0000 Subject: [PATCH 054/174] Translated using Weblate (Swedish) Currently translated at 100.0% (2968 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 74e7a30032..1337dc47b7 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -3295,5 +3295,7 @@ "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s kommer att ladda om med utrymmen aktiverade. Gemenskaper och anpassade taggar kommer att döljas.", "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta tillgänglig för webben, skrivbord och Android. Tack för att du provar betan.", "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Om du lämnar så kommer %(brand)s att ladda om med utrymmen inaktiverade. Gemenskaper och anpassade taggar kommer att synas igen.", - "Spaces are a new way to group rooms and people.": "Utrymmen är nya sätt att gruppera rum och personer." + "Spaces are a new way to group rooms and people.": "Utrymmen är nya sätt att gruppera rum och personer.", + "This is an experimental feature. For now, new users receiving an invite will have to open the invite on to actually join.": "Det här är en experimentell funktion. För tillfället så behöver nya inbjudna användare öppna inbjudan på för att faktiskt gå med.", + "To join %(spaceName)s, turn on the Spaces beta": "För att gå med i %(spaceName)s, aktivera utrymmesbetan" } From da3e0a4cb46e1ce359aff0a8d02c7c4be10f5784 Mon Sep 17 00:00:00 2001 From: Hivaa Date: Tue, 18 May 2021 04:16:18 +0000 Subject: [PATCH 055/174] Translated using Weblate (Persian) Currently translated at 20.2% (601 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fa/ --- src/i18n/strings/fa.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fa.json b/src/i18n/strings/fa.json index 5948048561..7dcce06ff6 100644 --- a/src/i18n/strings/fa.json +++ b/src/i18n/strings/fa.json @@ -632,5 +632,7 @@ "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "هرگاه این صفحه شامل اطلاعات قابل شناسایی مانند شناسه‌ی اتاق ، کاربر یا گروه باشد ، این داده‌ها قبل از ارسال به سرور حذف می شوند.", "Your user agent": "نماینده کاربری شما", "Whether you're using %(brand)s as an installed Progressive Web App": "این که آیا شما از%(brand)s به عنوان یک PWA استفاده می‌کنید یا نه", - "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "این که آیا از ویژگی 'breadcrumbs' (نمایه‌ی کاربری بالای فهرست اتاق‌ها) استفاده می‌کنید یا خیر" + "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "این که آیا از ویژگی 'breadcrumbs' (نمایه‌ی کاربری بالای فهرست اتاق‌ها) استفاده می‌کنید یا خیر", + "Use an identity server to invite by email. Manage in Settings.": "برای دعوت از یک سرور هویت‌سنجی استفاده نمائید. می‌توانید این مورد را در تنظیمات پیکربندی نمائید.", + "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "برای دعوت با استفاده از ایمیل از یک سرور هویت‌سنجی استفاده نمائید. جهت استفاده از سرور هویت‌سنجی پیش‌فرض (%(defaultIdentityServerName)s) بر روی ادامه کلیک کنید، وگرنه آن را در بخش تنظیمات پیکربندی نمائید." } From ec7c1ab9f0423921d4fbab2c40070fc703dd943b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 18 May 2021 15:40:09 -0600 Subject: [PATCH 056/174] Revert "Try putting room list handling behind a lock" --- src/stores/room-list/algorithms/Algorithm.ts | 325 +++++++++---------- src/utils/MultiLock.ts | 30 -- 2 files changed, 155 insertions(+), 200 deletions(-) delete mode 100644 src/utils/MultiLock.ts diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index c50db43d08..024c484c41 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -34,7 +34,6 @@ import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm"; import { getListAlgorithmInstance } from "./list-ordering"; import SettingsStore from "../../../settings/SettingsStore"; import { VisibilityProvider } from "../filters/VisibilityProvider"; -import { MultiLock } from "../../../utils/MultiLock"; /** * Fired when the Algorithm has determined a list has been updated. @@ -78,7 +77,6 @@ export class Algorithm extends EventEmitter { } = {}; private allowedByFilter: Map = new Map(); private allowedRoomsByFilters: Set = new Set(); - private handlerLock = new MultiLock(); /** * Set to true to suspend emissions of algorithm updates. @@ -681,204 +679,191 @@ export class Algorithm extends EventEmitter { public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise { if (SettingsStore.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`Acquiring lock for ${room.roomId} with cause ${cause}`); + console.log(`Handle room update for ${room.roomId} called with cause ${cause}`); } - const release = await this.handlerLock.acquire(room.roomId); - try { - 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 + const isSticky = this._stickyRoom && this._stickyRoom.room && this._stickyRoom.room.roomId === room.roomId; + if (cause === RoomUpdateCause.NewRoom) { + const isForLastSticky = this._lastStickyRoom && this._lastStickyRoom.room === room; + const roomTags = this.roomIdsToTags[room.roomId]; + const hasTags = roomTags && roomTags.length > 0; + + // Don't change the cause if the last sticky room is being re-added. If we fail to + // pass the cause through as NewRoom, we'll fail to lie to the algorithm and thus + // lose the room. + if (hasTags && !isForLastSticky) { + console.warn(`${room.roomId} is reportedly new but is already known - assuming TagChange instead`); + cause = RoomUpdateCause.PossibleTagChange; } - 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 - const isSticky = this._stickyRoom && this._stickyRoom.room && this._stickyRoom.room.roomId === room.roomId; - if (cause === RoomUpdateCause.NewRoom) { - const isForLastSticky = this._lastStickyRoom && this._lastStickyRoom.room === room; - const roomTags = this.roomIdsToTags[room.roomId]; - const hasTags = roomTags && roomTags.length > 0; - - // Don't change the cause if the last sticky room is being re-added. If we fail to - // pass the cause through as NewRoom, we'll fail to lie to the algorithm and thus - // lose the room. - if (hasTags && !isForLastSticky) { - console.warn(`${room.roomId} is reportedly new but is already known - assuming TagChange instead`); - cause = RoomUpdateCause.PossibleTagChange; - } - - // Check to see if the room is known first - let knownRoomRef = this.rooms.includes(room); - if (hasTags && !knownRoomRef) { - console.warn(`${room.roomId} might be a reference change - attempting to update reference`); - this.rooms = this.rooms.map(r => r.roomId === room.roomId ? room : r); - knownRoomRef = this.rooms.includes(room); - if (!knownRoomRef) { - console.warn(`${room.roomId} is still not referenced. It may be sticky.`); - } - } - - // If we have tags for a room and don't have the room referenced, something went horribly - // wrong - the reference should have been updated above. - if (hasTags && !knownRoomRef && !isSticky) { - throw new Error(`${room.roomId} is missing from room array but is known`); - } - - // Like above, update the reference to the sticky room if we need to - if (hasTags && isSticky) { - // Go directly in and set the sticky room's new reference, being careful not - // to trigger a sticky room update ourselves. - this._stickyRoom.room = room; - } - - // If after all that we're still a NewRoom update, add the room if applicable. - // We don't do this for the sticky room (because it causes duplication issues) - // or if we know about the reference (as it should be replaced). - if (cause === RoomUpdateCause.NewRoom && !isSticky && !knownRoomRef) { - this.rooms.push(room); + // Check to see if the room is known first + let knownRoomRef = this.rooms.includes(room); + if (hasTags && !knownRoomRef) { + console.warn(`${room.roomId} might be a reference change - attempting to update reference`); + this.rooms = this.rooms.map(r => r.roomId === room.roomId ? room : r); + knownRoomRef = this.rooms.includes(room); + if (!knownRoomRef) { + console.warn(`${room.roomId} is still not referenced. It may be sticky.`); } } - let didTagChange = false; - if (cause === RoomUpdateCause.PossibleTagChange) { - const oldTags = this.roomIdsToTags[room.roomId] || []; - const newTags = this.getTagsForRoom(room); - 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}`); - await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved); - this._cachedRooms[rmTag] = algorithm.orderedRooms; - this.recalculateFilteredRoomsForTag(rmTag); // update filter to re-sort the list - 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}`); - await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom); - this._cachedRooms[addTag] = algorithm.orderedRooms; - } + // If we have tags for a room and don't have the room referenced, something went horribly + // wrong - the reference should have been updated above. + if (hasTags && !knownRoomRef && !isSticky) { + throw new Error(`${room.roomId} is missing from room array but is known - trying to find duplicate`); + } - // Update the tag map so we don't regen it in a moment - this.roomIdsToTags[room.roomId] = newTags; + // Like above, update the reference to the sticky room if we need to + if (hasTags && isSticky) { + // Go directly in and set the sticky room's new reference, being careful not + // to trigger a sticky room update ourselves. + this._stickyRoom.room = room; + } + // If after all that we're still a NewRoom update, add the room if applicable. + // We don't do this for the sticky room (because it causes duplication issues) + // or if we know about the reference (as it should be replaced). + if (cause === RoomUpdateCause.NewRoom && !isSticky && !knownRoomRef) { + this.rooms.push(room); + } + } + + let didTagChange = false; + if (cause === RoomUpdateCause.PossibleTagChange) { + const oldTags = this.roomIdsToTags[room.roomId] || []; + const newTags = this.getTagsForRoom(room); + 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(`Changing update cause for ${room.roomId} to Timeline to sort rooms`); + console.log(`Removing ${room.roomId} from ${rmTag}`); } - cause = RoomUpdateCause.Timeline; - didTagChange = true; + const algorithm: OrderingAlgorithm = this.algorithms[rmTag]; + if (!algorithm) throw new Error(`No algorithm for ${rmTag}`); + await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved); + this._cachedRooms[rmTag] = algorithm.orderedRooms; + this.recalculateFilteredRoomsForTag(rmTag); // update filter to re-sort the list + 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}`); + await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom); + this._cachedRooms[addTag] = algorithm.orderedRooms; + } + + // 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; + } + + if (didTagChange && isSticky) { + // Manually update the tag for the sticky room without triggering a sticky room + // update. The update will be handled implicitly by the sticky room handling and + // requires no changes on our part, if we're in the middle of a sticky room change. + if (this._lastStickyRoom) { + this._stickyRoom = { + room, + tag: this.roomIdsToTags[room.roomId][0], + position: 0, // right at the top as it changed tags + }; } 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; - } - - if (didTagChange && isSticky) { - // Manually update the tag for the sticky room without triggering a sticky room - // update. The update will be handled implicitly by the sticky room handling and - // requires no changes on our part, if we're in the middle of a sticky room change. - if (this._lastStickyRoom) { - this._stickyRoom = { - room, - tag: this.roomIdsToTags[room.roomId][0], - position: 0, // right at the top as it changed tags - }; - } else { - // We have to clear the lock as the sticky room change will trigger updates. - await this.setStickyRoom(room); - } + // We have to clear the lock as the sticky room change will trigger updates. + await this.setStickyRoom(room); } } + } - // If the update is for a room change which might be the sticky room, prevent it. We - // need to make sure that the causes (NewRoom and RoomRemoved) are still triggered though - // 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 the update is for a room change which might be the sticky room, prevent it. We + // need to make sure that the causes (NewRoom and RoomRemoved) are still triggered though + // 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.log(`[RoomListDebug] Updating tags for room ${room.roomId} (${room.name})`); + console.warn(`[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`); } + return false; + } + } - // Get the tags for the room and populate the cache - const roomTags = this.getTagsForRoom(room).filter(t => !isNullOrUndefined(this.cachedRooms[t])); - - // "This should never happen" condition - we specify DefaultTagID.Untagged in getTagsForRoom(), - // which means we should *always* have a tag to go off of. - if (!roomTags.length) throw new Error(`Tags cannot be determined for ${room.roomId}`); - - this.roomIdsToTags[room.roomId] = roomTags; - + 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.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags); + console.warn(`Skipping tag update for ${room.roomId} because we don't know about the room`); } - } - - 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]; - if (!tags) { - console.warn(`No tags known for "${room.name}" (${room.roomId})`); return false; } - let changed = didTagChange; - for (const tag of tags) { - const algorithm: OrderingAlgorithm = this.algorithms[tag]; - if (!algorithm) throw new Error(`No algorithm for ${tag}`); - - await algorithm.handleRoomUpdate(room, cause); - this._cachedRooms[tag] = algorithm.orderedRooms; - - // Flag that we've done something - this.recalculateFilteredRoomsForTag(tag); // update filter to re-sort the list - this.recalculateStickyRoom(tag); // update sticky room to make sure it appears if needed - changed = true; + 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])); + + // "This should never happen" condition - we specify DefaultTagID.Untagged in getTagsForRoom(), + // which means we should *always* have a tag to go off of. + 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] Finished handling ${room.roomId} with cause ${cause} (changed=${changed})`, - ); + console.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags); } - return changed; - } finally { - release(); } + + 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]; + if (!tags) { + console.warn(`No tags known for "${room.name}" (${room.roomId})`); + return false; + } + + let changed = didTagChange; + for (const tag of tags) { + const algorithm: OrderingAlgorithm = this.algorithms[tag]; + if (!algorithm) throw new Error(`No algorithm for ${tag}`); + + await algorithm.handleRoomUpdate(room, cause); + this._cachedRooms[tag] = algorithm.orderedRooms; + + // Flag that we've done something + this.recalculateFilteredRoomsForTag(tag); // update filter to re-sort the list + this.recalculateStickyRoom(tag); // update sticky room to make sure it appears if needed + 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/utils/MultiLock.ts b/src/utils/MultiLock.ts deleted file mode 100644 index 507a924dda..0000000000 --- a/src/utils/MultiLock.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* -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 { EnhancedMap } from "./maps"; -import AwaitLock from "await-lock"; - -export type DoneFn = () => void; - -export class MultiLock { - private locks = new EnhancedMap(); - - public async acquire(key: string): Promise { - const lock = this.locks.getOrCreate(key, new AwaitLock()); - await lock.acquireAsync(); - return () => lock.release(); - } -} From 0d8a7eabc7b7cf203d0c3bcdf78ed8583030eddb Mon Sep 17 00:00:00 2001 From: Jaiwanth Date: Wed, 19 May 2021 12:38:39 +0530 Subject: [PATCH 057/174] Update MemberList on invite permission change Signed-off-by: Jaiwanth --- src/components/views/rooms/MemberList.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index fbc0e477a1..bb60c958f7 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -196,6 +196,9 @@ export default class MemberList extends React.Component { event.getType() === "m.room.third_party_invite") { this._updateList(); } + if (event.getContent().invite !== event.getPrevContent().invite) { + this.forceUpdate(); + } }; _updateList = rate_limited_func(() => { From e18120f4122a87e35b10907f9afb65e28758dcc0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 08:54:23 +0100 Subject: [PATCH 058/174] Show DMs in space for invited members too, to match Android impl --- src/stores/SpaceStore.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx index ba2b91aa2c..05fe52aa2b 100644 --- a/src/stores/SpaceStore.tsx +++ b/src/stores/SpaceStore.tsx @@ -361,7 +361,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient { const space = this.matrixClient?.getRoom(spaceId); // Add relevant DMs - space?.getJoinedMembers().forEach(member => { + space?.getMembers().forEach(member => { + if (member.membership !== "join" && member.membership !== "invite") return; DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => { roomIds.add(roomId); }); From cb2ee0451d5d919cd4c45773299f1c2d8f5565de Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 09:06:01 +0100 Subject: [PATCH 059/174] move Settings watchers over to an ES6 Map --- src/settings/SettingsStore.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index c32bbe731d..25bc23f572 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -26,7 +26,7 @@ import { _t } from '../languageHandler'; import dis from '../dispatcher/dispatcher'; import { ISetting, SETTINGS } from "./Settings"; import LocalEchoWrapper from "./handlers/LocalEchoWrapper"; -import { WatchManager } from "./WatchManager"; +import { WatchManager, CallbackFn as WatchCallbackFn } from "./WatchManager"; import { SettingLevel } from "./SettingLevel"; import SettingsHandler from "./handlers/SettingsHandler"; @@ -117,7 +117,7 @@ export default class SettingsStore { // We also maintain a list of monitors which are special watchers: they cause dispatches // when the setting changes. We track which rooms we're monitoring though to ensure we // don't duplicate updates on the bus. - private static watchers = {}; // { callbackRef => { callbackFn } } + private static watchers = new Map(); private static monitors = {}; // { settingName => { roomId => callbackRef } } // Counter used for generation of watcher IDs @@ -163,7 +163,7 @@ export default class SettingsStore { callbackFn(originalSettingName, changedInRoomId, atLevel, newValAtLevel, newValue); }; - SettingsStore.watchers[watcherId] = localizedCallback; + SettingsStore.watchers.set(watcherId, localizedCallback); defaultWatchManager.watchSetting(settingName, roomId, localizedCallback); return watcherId; @@ -176,13 +176,13 @@ export default class SettingsStore { * to cancel. */ public static unwatchSetting(watcherReference: string) { - if (!SettingsStore.watchers[watcherReference]) { + if (!SettingsStore.watchers.has(watcherReference)) { console.warn(`Ending non-existent watcher ID ${watcherReference}`); return; } - defaultWatchManager.unwatchSetting(SettingsStore.watchers[watcherReference]); - delete SettingsStore.watchers[watcherReference]; + defaultWatchManager.unwatchSetting(SettingsStore.watchers.get(watcherReference)); + SettingsStore.watchers.delete(watcherReference); } /** From cf501371fa313885c551b650d0959608be5b1f64 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 09:11:14 +0100 Subject: [PATCH 060/174] move Settings monitors over to an ES6 Map --- src/settings/SettingsStore.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 25bc23f572..e1e300e185 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -118,7 +118,7 @@ export default class SettingsStore { // when the setting changes. We track which rooms we're monitoring though to ensure we // don't duplicate updates on the bus. private static watchers = new Map(); - private static monitors = {}; // { settingName => { roomId => callbackRef } } + private static monitors = new Map>(); // { settingName => { roomId => callbackRef } } // Counter used for generation of watcher IDs private static watcherCount = 1; @@ -196,10 +196,10 @@ export default class SettingsStore { public static monitorSetting(settingName: string, roomId: string) { roomId = roomId || null; // the thing wants null specifically to work, so appease it. - if (!this.monitors[settingName]) this.monitors[settingName] = {}; + if (!this.monitors.has(settingName)) this.monitors.set(settingName, new Map()); const registerWatcher = () => { - this.monitors[settingName][roomId] = SettingsStore.watchSetting( + this.monitors.get(settingName).set(roomId, SettingsStore.watchSetting( settingName, roomId, (settingName, inRoomId, level, newValueAtLevel, newValue) => { dis.dispatch({ action: 'setting_updated', @@ -210,19 +210,20 @@ export default class SettingsStore { newValue, }); }, - ); + )); }; - const hasRoom = Object.keys(this.monitors[settingName]).find((r) => r === roomId || r === null); + const rooms = Array.from(this.monitors.get(settingName).keys()); + const hasRoom = rooms.find((r) => r === roomId || r === null); if (!hasRoom) { registerWatcher(); } else { if (roomId === null) { // Unregister all existing watchers and register the new one - for (const roomId of Object.keys(this.monitors[settingName])) { - SettingsStore.unwatchSetting(this.monitors[settingName][roomId]); - } - this.monitors[settingName] = {}; + rooms.forEach(roomId => { + SettingsStore.unwatchSetting(this.monitors.get(settingName).get(roomId)); + }); + this.monitors.get(settingName).clear(); registerWatcher(); } // else a watcher is already registered for the room, so don't bother registering it again } From 73b24ae2251f144d8bacd93492285085add34577 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 09:24:46 +0100 Subject: [PATCH 061/174] move WatchManager over to an ES6 Map --- src/settings/WatchManager.ts | 44 ++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/settings/WatchManager.ts b/src/settings/WatchManager.ts index ea2f158ef6..56f911f180 100644 --- a/src/settings/WatchManager.ts +++ b/src/settings/WatchManager.ts @@ -18,11 +18,7 @@ import { SettingLevel } from "./SettingLevel"; export type CallbackFn = (changedInRoomId: string, atLevel: SettingLevel, newValAtLevel: any) => void; -const IRRELEVANT_ROOM: string = null; - -interface RoomWatcherMap { - [roomId: string]: CallbackFn[]; -} +const IRRELEVANT_ROOM = Symbol("irrelevant-room"); /** * Generalized management class for dealing with watchers on a per-handler (per-level) @@ -30,25 +26,25 @@ interface RoomWatcherMap { * class, which are then proxied outwards to any applicable watchers. */ export class WatchManager { - private watchers: {[settingName: string]: RoomWatcherMap} = {}; + private watchers = new Map>(); // settingName -> roomId -> CallbackFn[] // Proxy for handlers to delegate changes to this manager public watchSetting(settingName: string, roomId: string | null, cb: CallbackFn) { - if (!this.watchers[settingName]) this.watchers[settingName] = {}; - if (!this.watchers[settingName][roomId]) this.watchers[settingName][roomId] = []; - this.watchers[settingName][roomId].push(cb); + if (!this.watchers.has(settingName)) this.watchers.set(settingName, new Map()); + if (!this.watchers.get(settingName).has(roomId)) this.watchers.get(settingName).set(roomId, []); + this.watchers.get(settingName).get(roomId).push(cb); } // Proxy for handlers to delegate changes to this manager public unwatchSetting(cb: CallbackFn) { - for (const settingName of Object.keys(this.watchers)) { - for (const roomId of Object.keys(this.watchers[settingName])) { + this.watchers.forEach((map) => { + map.forEach((callbacks) => { let idx; - while ((idx = this.watchers[settingName][roomId].indexOf(cb)) !== -1) { - this.watchers[settingName][roomId].splice(idx, 1); + while ((idx = callbacks.indexOf(cb)) !== -1) { + callbacks.splice(idx, 1); } - } - } + }); + }); } public notifyUpdate(settingName: string, inRoomId: string | null, atLevel: SettingLevel, newValueAtLevel: any) { @@ -56,21 +52,21 @@ export class WatchManager { // we also don't have a reliable way to get the old value of a setting. Instead, we'll just // let it fall through regardless and let the receiver dedupe if they want to. - if (!this.watchers[settingName]) return; + if (!this.watchers.has(settingName)) return; - const roomWatchers = this.watchers[settingName]; + const roomWatchers = this.watchers.get(settingName); const callbacks = []; - if (inRoomId !== null && roomWatchers[inRoomId]) { - callbacks.push(...roomWatchers[inRoomId]); + if (inRoomId !== null && roomWatchers.has(inRoomId)) { + callbacks.push(...roomWatchers.get(inRoomId)); } if (!inRoomId) { - // Fire updates to all the individual room watchers too, as they probably - // care about the change higher up. - callbacks.push(...Object.values(roomWatchers).flat(1)); - } else if (roomWatchers[IRRELEVANT_ROOM]) { - callbacks.push(...roomWatchers[IRRELEVANT_ROOM]); + // Fire updates to all the individual room watchers too, as they probably care about the change higher up. + const callbacks = Array.from(roomWatchers.values()).flat(1); + callbacks.push(...callbacks); + } else if (roomWatchers.has(IRRELEVANT_ROOM)) { + callbacks.push(...roomWatchers.get(IRRELEVANT_ROOM)); } for (const callback of callbacks) { From e78206301fbc1951791b1dbd41693e1feae60244 Mon Sep 17 00:00:00 2001 From: Jaiwanth Date: Wed, 19 May 2021 14:31:04 +0530 Subject: [PATCH 062/174] Modify to avoid forceUpdate --- src/components/views/rooms/MemberList.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index bb60c958f7..f991827091 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -136,11 +136,13 @@ export default class MemberList extends React.Component { _getMembersState(members) { // set the state after determining _showPresence to make sure it's // taken into account while rerendering + const cli = MatrixClientPeg.get(); return { loading: false, members: members, filteredJoinedMembers: this._filterMembers(members, 'join'), filteredInvitedMembers: this._filterMembers(members, 'invite'), + canInvite: cli.getRoom(this.props.roomId).canInvite(cli.getUserId()), // ideally we'd size this to the page height, but // in practice I find that a little constraining @@ -196,9 +198,10 @@ export default class MemberList extends React.Component { event.getType() === "m.room.third_party_invite") { this._updateList(); } - if (event.getContent().invite !== event.getPrevContent().invite) { - this.forceUpdate(); - } + + const cli = MatrixClientPeg.get(); + const canInvite = cli.getRoom(this.props.roomId).canInvite(cli.getUserId()); + if (canInvite !== this.state.canInvite) this.setState({canInvite}); }; _updateList = rate_limited_func(() => { @@ -458,8 +461,6 @@ export default class MemberList extends React.Component { let inviteButton; if (room && room.getMyMembership() === 'join') { - const canInvite = room.canInvite(cli.getUserId()); - let inviteButtonText = _t("Invite to this room"); const chat = CommunityPrototypeStore.instance.getSelectedCommunityGeneralChat(); if (chat && chat.roomId === this.props.roomId) { @@ -470,7 +471,7 @@ export default class MemberList extends React.Component { const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); inviteButton = - + { inviteButtonText } ; } From f7d0afcd287d79a8fc673197facb055311c3f840 Mon Sep 17 00:00:00 2001 From: Germain Date: Wed, 19 May 2021 10:07:02 +0100 Subject: [PATCH 063/174] Performance monitoring measurements (#6041) --- src/@types/global.d.ts | 3 + src/components/structures/MatrixChat.tsx | 46 ++---- src/performance/entry-names.ts | 57 ++++++++ src/performance/index.ts | 178 +++++++++++++++++++++++ test/end-to-end-tests/.gitignore | 1 + test/end-to-end-tests/src/session.js | 2 +- test/end-to-end-tests/start.js | 20 ++- 7 files changed, 275 insertions(+), 32 deletions(-) create mode 100644 src/performance/entry-names.ts create mode 100644 src/performance/index.ts diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index f04a2ff237..63966d96fa 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -42,6 +42,7 @@ import {SpaceStoreClass} from "../stores/SpaceStore"; import TypingStore from "../stores/TypingStore"; import { EventIndexPeg } from "../indexing/EventIndexPeg"; import {VoiceRecordingStore} from "../stores/VoiceRecordingStore"; +import PerformanceMonitor from "../performance"; declare global { interface Window { @@ -79,6 +80,8 @@ declare global { mxVoiceRecordingStore: VoiceRecordingStore; mxTypingStore: TypingStore; mxEventIndexPeg: EventIndexPeg; + mxPerformanceMonitor: PerformanceMonitor; + mxPerformanceEntryNames: any; } interface Document { diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 288acc108a..4c7fca2fec 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -86,6 +86,8 @@ import {RoomUpdateCause} from "../../stores/room-list/models"; import defaultDispatcher from "../../dispatcher/dispatcher"; import SecurityCustomisations from "../../customisations/Security"; +import PerformanceMonitor, { PerformanceEntryNames } from "../../performance"; + /** constants for MatrixChat.state.view */ export enum Views { // a special initial state which is only used at startup, while we are @@ -484,42 +486,22 @@ export default class MatrixChat extends React.PureComponent { } startPageChangeTimer() { - // Tor doesn't support performance - if (!performance || !performance.mark) return null; - - // This shouldn't happen because UNSAFE_componentWillUpdate and componentDidUpdate - // are used. - if (this.pageChanging) { - console.warn('MatrixChat.startPageChangeTimer: timer already started'); - return; - } - this.pageChanging = true; - performance.mark('element_MatrixChat_page_change_start'); + PerformanceMonitor.instance.start(PerformanceEntryNames.PAGE_CHANGE); } stopPageChangeTimer() { - // Tor doesn't support performance - if (!performance || !performance.mark) return null; + const perfMonitor = PerformanceMonitor.instance; - if (!this.pageChanging) { - console.warn('MatrixChat.stopPageChangeTimer: timer not started'); - return; - } - this.pageChanging = false; - performance.mark('element_MatrixChat_page_change_stop'); - performance.measure( - 'element_MatrixChat_page_change_delta', - 'element_MatrixChat_page_change_start', - 'element_MatrixChat_page_change_stop', - ); - performance.clearMarks('element_MatrixChat_page_change_start'); - performance.clearMarks('element_MatrixChat_page_change_stop'); - const measurement = performance.getEntriesByName('element_MatrixChat_page_change_delta').pop(); + perfMonitor.stop(PerformanceEntryNames.PAGE_CHANGE); - // In practice, sometimes the entries list is empty, so we get no measurement - if (!measurement) return null; + const entries = perfMonitor.getEntries({ + name: PerformanceEntryNames.PAGE_CHANGE, + }); + const measurement = entries.pop(); - return measurement.duration; + return measurement + ? measurement.duration + : null; } shouldTrackPageChange(prevState: IState, state: IState) { @@ -1632,11 +1614,13 @@ export default class MatrixChat extends React.PureComponent { action: 'start_registration', params: params, }); + PerformanceMonitor.instance.start(PerformanceEntryNames.REGISTER); } else if (screen === 'login') { dis.dispatch({ action: 'start_login', params: params, }); + PerformanceMonitor.instance.start(PerformanceEntryNames.LOGIN); } else if (screen === 'forgot_password') { dis.dispatch({ action: 'start_password_recovery', @@ -1965,6 +1949,8 @@ export default class MatrixChat extends React.PureComponent { // Create and start the client await Lifecycle.setLoggedIn(credentials); await this.postLoginSetup(); + PerformanceMonitor.instance.stop(PerformanceEntryNames.LOGIN); + PerformanceMonitor.instance.stop(PerformanceEntryNames.REGISTER); }; // complete security / e2e setup has finished diff --git a/src/performance/entry-names.ts b/src/performance/entry-names.ts new file mode 100644 index 0000000000..effd9506f6 --- /dev/null +++ b/src/performance/entry-names.ts @@ -0,0 +1,57 @@ +/* +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. +*/ + +export enum PerformanceEntryNames { + + /** + * Application wide + */ + + APP_STARTUP = "mx_AppStartup", + PAGE_CHANGE = "mx_PageChange", + + /** + * Events + */ + + RESEND_EVENT = "mx_ResendEvent", + SEND_E2EE_EVENT = "mx_SendE2EEEvent", + SEND_ATTACHMENT = "mx_SendAttachment", + + /** + * Rooms + */ + + SWITCH_ROOM = "mx_SwithRoom", + JUMP_TO_ROOM = "mx_JumpToRoom", + JOIN_ROOM = "mx_JoinRoom", + CREATE_DM = "mx_CreateDM", + PEEK_ROOM = "mx_PeekRoom", + + /** + * User + */ + + VERIFY_E2EE_USER = "mx_VerifyE2EEUser", + LOGIN = "mx_Login", + REGISTER = "mx_Register", + + /** + * VoIP + */ + + SETUP_VOIP_CALL = "mx_SetupVoIPCall", +} diff --git a/src/performance/index.ts b/src/performance/index.ts new file mode 100644 index 0000000000..bfb5b4a9c7 --- /dev/null +++ b/src/performance/index.ts @@ -0,0 +1,178 @@ +/* +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 { PerformanceEntryNames } from "./entry-names"; + +interface GetEntriesOptions { + name?: string, + type?: string, +} + +type PerformanceCallbackFunction = (entry: PerformanceEntry[]) => void; + +interface PerformanceDataListener { + entryNames?: string[], + callback: PerformanceCallbackFunction +} + +export default class PerformanceMonitor { + static _instance: PerformanceMonitor; + + private START_PREFIX = "start:" + private STOP_PREFIX = "stop:" + + private listeners: PerformanceDataListener[] = [] + private entries: PerformanceEntry[] = [] + + public static get instance(): PerformanceMonitor { + if (!PerformanceMonitor._instance) { + PerformanceMonitor._instance = new PerformanceMonitor(); + } + return PerformanceMonitor._instance; + } + + /** + * Starts a performance recording + * @param name Name of the recording + * @param id Specify an identifier appended to the measurement name + * @returns {void} + */ + start(name: string, id?: string): void { + if (!this.supportsPerformanceApi()) { + return; + } + const key = this.buildKey(name, id); + + if (performance.getEntriesByName(this.START_PREFIX + key).length > 0) { + console.warn(`Recording already started for: ${name}`); + return; + } + + performance.mark(this.START_PREFIX + key); + } + + /** + * Stops a performance recording and stores delta duration + * with the start marker + * @param name Name of the recording + * @param id Specify an identifier appended to the measurement name + * @returns {void} + */ + stop(name: string, id?: string): PerformanceEntry { + if (!this.supportsPerformanceApi()) { + return; + } + const key = this.buildKey(name, id); + if (performance.getEntriesByName(this.START_PREFIX + key).length === 0) { + console.warn(`No recording started for: ${name}`); + return; + } + + performance.mark(this.STOP_PREFIX + key); + performance.measure( + key, + this.START_PREFIX + key, + this.STOP_PREFIX + key, + ); + + this.clear(name, id); + + const measurement = performance.getEntriesByName(key).pop(); + + // Keeping a reference to all PerformanceEntry created + // by this abstraction for historical events collection + // when adding a data callback + this.entries.push(measurement); + + this.listeners.forEach(listener => { + if (this.shouldEmit(listener, measurement)) { + listener.callback([measurement]) + } + }); + + return measurement; + } + + clear(name: string, id?: string): void { + if (!this.supportsPerformanceApi()) { + return; + } + const key = this.buildKey(name, id); + performance.clearMarks(this.START_PREFIX + key); + performance.clearMarks(this.STOP_PREFIX + key); + } + + getEntries({ name, type }: GetEntriesOptions = {}): PerformanceEntry[] { + return this.entries.filter(entry => { + const satisfiesName = !name || entry.name === name; + const satisfiedType = !type || entry.entryType === type; + return satisfiesName && satisfiedType; + }); + } + + addPerformanceDataCallback(listener: PerformanceDataListener, buffer = false) { + this.listeners.push(listener); + if (buffer) { + const toEmit = this.entries.filter(entry => this.shouldEmit(listener, entry)); + if (toEmit.length > 0) { + listener.callback(toEmit); + } + } + } + + removePerformanceDataCallback(callback?: PerformanceCallbackFunction) { + if (!callback) { + this.listeners = []; + } else { + this.listeners.splice( + this.listeners.findIndex(listener => listener.callback === callback), + 1, + ); + } + } + + /** + * Tor browser does not support the Performance API + * @returns {boolean} true if the Performance API is supported + */ + private supportsPerformanceApi(): boolean { + return performance !== undefined && performance.mark !== undefined; + } + + private shouldEmit(listener: PerformanceDataListener, entry: PerformanceEntry): boolean { + return !listener.entryNames || listener.entryNames.includes(entry.name); + } + + /** + * Internal utility to ensure consistent name for the recording + * @param name Name of the recording + * @param id Specify an identifier appended to the measurement name + * @returns {string} a compound of the name and identifier if present + */ + private buildKey(name: string, id?: string): string { + return `${name}${id ? `:${id}` : ''}`; + } +} + + +// Convenience exports +export { + PerformanceEntryNames, +} + +// Exposing those to the window object to bridge them from tests +window.mxPerformanceMonitor = PerformanceMonitor.instance; +window.mxPerformanceEntryNames = PerformanceEntryNames; diff --git a/test/end-to-end-tests/.gitignore b/test/end-to-end-tests/.gitignore index 61f9012393..9180d32e90 100644 --- a/test/end-to-end-tests/.gitignore +++ b/test/end-to-end-tests/.gitignore @@ -1,3 +1,4 @@ node_modules *.png element/env +performance-entries.json diff --git a/test/end-to-end-tests/src/session.js b/test/end-to-end-tests/src/session.js index 4c611ef877..6c68929a0b 100644 --- a/test/end-to-end-tests/src/session.js +++ b/test/end-to-end-tests/src/session.js @@ -208,7 +208,7 @@ module.exports = class ElementSession { this.log.done(); } - close() { + async close() { return this.browser.close(); } diff --git a/test/end-to-end-tests/start.js b/test/end-to-end-tests/start.js index 234d60da9f..f29b485c84 100644 --- a/test/end-to-end-tests/start.js +++ b/test/end-to-end-tests/start.js @@ -79,8 +79,26 @@ async function runTests() { await new Promise((resolve) => setTimeout(resolve, 5 * 60 * 1000)); } - await Promise.all(sessions.map((session) => session.close())); + const performanceEntries = {}; + await Promise.all(sessions.map(async (session) => { + // Collecting all performance monitoring data before closing the session + const measurements = await session.page.evaluate(() => { + let measurements = []; + window.mxPerformanceMonitor.addPerformanceDataCallback({ + entryNames: [ + window.mxPerformanceEntryNames.REGISTER, + ], + callback: (events) => { + measurements = JSON.stringify(events); + }, + }, true); + return measurements; + }); + performanceEntries[session.username] = JSON.parse(measurements); + return session.close(); + })); + fs.writeFileSync(`performance-entries.json`, JSON.stringify(performanceEntries)); if (failure) { process.exit(-1); } else { From 8a3db3b40ac466c17368ba24571f06820ac84db7 Mon Sep 17 00:00:00 2001 From: Nique Woodhouse Date: Wed, 19 May 2021 10:25:54 +0100 Subject: [PATCH 064/174] Update _MessageActionBar.scss Refine UI of message action bar to increase usability and focus on bar content. --- res/css/views/messages/_MessageActionBar.scss | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index d41ac3a4ba..a697c8d0be 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -20,11 +20,12 @@ limitations under the License. visibility: hidden; cursor: pointer; display: flex; - height: 24px; + height: 28px; line-height: $font-24px; - border-radius: 4px; - background: $message-action-bar-bg-color; - top: -26px; + border-radius: 8px; + background: $primary-bg-color; + border: 1px solid $input-border-color; + top: -28px; right: 8px; user-select: none; // Ensure the action bar appears above over things, like the read marker. @@ -51,31 +52,19 @@ limitations under the License. white-space: nowrap; display: inline-block; position: relative; - border: 1px solid $message-action-bar-border-color; - margin-left: -1px; + margin: 2px; &:hover { - border-color: $message-action-bar-hover-border-color; + background: $roomlist-button-bg-color; + border-radius: 6px; z-index: 1; } - - &:first-child { - border-radius: 3px 0 0 3px; - } - - &:last-child { - border-radius: 0 3px 3px 0; - } - - &:only-child { - border-radius: 3px; - } } } - .mx_MessageActionBar_maskButton { - width: 27px; + width: 24px; + height: 24px; } .mx_MessageActionBar_maskButton::after { @@ -88,7 +77,11 @@ limitations under the License. mask-size: 18px; mask-repeat: no-repeat; mask-position: center; - background-color: $message-action-bar-fg-color; + background-color: $secondary-fg-color; +} + +.mx_MessageActionBar_maskButton:hover:after { + background-color: $primary-fg-color; } .mx_MessageActionBar_reactButton::after { From 9f4c75fd4f1e26b9cc916db46ec9200bd35ed3d4 Mon Sep 17 00:00:00 2001 From: jelv Date: Tue, 18 May 2021 19:05:16 +0000 Subject: [PATCH 065/174] Translated using Weblate (Dutch) Currently translated at 100.0% (2968 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/nl/ --- src/i18n/strings/nl.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index 94da2fccc1..867478453f 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -6,7 +6,7 @@ "Admin": "Beheerder", "Advanced": "Geavanceerd", "Always show message timestamps": "Altijd tijdstempels van berichten tonen", - "Authentication": "Authenticatie", + "Authentication": "Login bevestigen", "%(items)s and %(lastItem)s": "%(items)s en %(lastItem)s", "and %(count)s others...|other": "en %(count)s anderen…", "and %(count)s others...|one": "en één andere…", @@ -1689,7 +1689,7 @@ "wait and try again later": "wachten en het later weer proberen", "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Gebruik een integratiebeheerder (%(serverName)s) om robots, widgets en stickerpakketten te beheren.", "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Gebruik een integratiebeheerder om robots, widgets en stickerpakketten te beheren.", - "Manage integrations": "Beheer integraties", + "Manage integrations": "Integratiebeheerder", "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integratiebeheerders ontvangen configuratie-informatie en kunnen widgets aanpassen, gespreksuitnodigingen versturen en machtsniveau’s namens u aanpassen.", "Ban list rules - %(roomName)s": "Banlijstregels - %(roomName)s", "Server rules": "Serverregels", @@ -1884,7 +1884,7 @@ "Verify this device to mark it as trusted. Trusting this device gives you and other users extra peace of mind when using end-to-end encrypted messages.": "Verifieer dit apparaat om het als vertrouwd te markeren. Door dit apparaat te vertrouwen geeft u extra gemoedsrust aan uzelf en andere gebruikers bij het gebruik van eind-tot-eind-versleutelde berichten.", "Verifying this device will mark it as trusted, and users who have verified with you will trust this device.": "Dit apparaat verifiëren zal het als vertrouwd markeren, en gebruikers die met u geverifieerd hebben zullen het vertrouwen.", "Integrations are disabled": "Integraties zijn uitgeschakeld", - "Enable 'Manage Integrations' in Settings to do this.": "Schakel in het Algemene Instellingenmenu ‘Beheer integraties’ in om dit te doen.", + "Enable 'Manage Integrations' in Settings to do this.": "Schakel de ‘Integratiebeheerder’ in in uw Instellingen om dit te doen.", "Integrations not allowed": "Integraties niet toegestaan", "Your %(brand)s doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Uw %(brand)s laat u geen integratiebeheerder gebruiken om dit te doen. Neem contact op met een beheerder.", "Failed to invite the following users to chat: %(csvUsers)s": "Het uitnodigen van volgende gebruikers voor gesprek is mislukt: %(csvUsers)s", @@ -2921,7 +2921,7 @@ "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.": "%(brand)s kan versleutelde berichten niet veilig lokaal opslaan in een webbrowser. Gebruik %(brand)s Desktop om versleutelde berichten in zoekresultaten te laten verschijnen.", "Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(rooms)s rooms.|one": "Veilig lokaal opslaan van versleutelde berichten zodat ze in de zoekresultaten verschijnen, gebruik %(size)s voor het opslaan van berichten uit %(rooms)s gesprek.", "Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(rooms)s rooms.|other": "Veilig lokaal opslaan van versleutelde berichten zodat ze in de zoekresultaten verschijnen, gebruik %(size)s voor het opslaan van berichten uit %(rooms)s gesprekken.", - "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Verifieer elke sessie die door een gebruiker wordt gebruikt afzonderlijk om deze te markeren als vertrouwd, niet vertrouwend op kruislings ondertekende apparaten.", + "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Verifieer elke sessie die door een gebruiker wordt gebruikt afzonderlijk. Dit markeert hem als vertrouwd zonder te vertrouwen op kruislings ondertekende apparaten.", "User signing private key:": "Gebruikerondertekening-privésleutel:", "Master private key:": "Hoofdprivésleutel:", "Self signing private key:": "Zelfondertekening-privésleutel:", From 5ea4f91654c57a16bcfb3c0a1b4746b799f1482f Mon Sep 17 00:00:00 2001 From: Hivaa Date: Wed, 19 May 2021 07:33:47 +0000 Subject: [PATCH 066/174] Translated using Weblate (Persian) Currently translated at 20.2% (602 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fa/ --- src/i18n/strings/fa.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fa.json b/src/i18n/strings/fa.json index 7dcce06ff6..0e6a9116d2 100644 --- a/src/i18n/strings/fa.json +++ b/src/i18n/strings/fa.json @@ -634,5 +634,6 @@ "Whether you're using %(brand)s as an installed Progressive Web App": "این که آیا شما از%(brand)s به عنوان یک PWA استفاده می‌کنید یا نه", "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "این که آیا از ویژگی 'breadcrumbs' (نمایه‌ی کاربری بالای فهرست اتاق‌ها) استفاده می‌کنید یا خیر", "Use an identity server to invite by email. Manage in Settings.": "برای دعوت از یک سرور هویت‌سنجی استفاده نمائید. می‌توانید این مورد را در تنظیمات پیکربندی نمائید.", - "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "برای دعوت با استفاده از ایمیل از یک سرور هویت‌سنجی استفاده نمائید. جهت استفاده از سرور هویت‌سنجی پیش‌فرض (%(defaultIdentityServerName)s) بر روی ادامه کلیک کنید، وگرنه آن را در بخش تنظیمات پیکربندی نمائید." + "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": "به اتاق با آدرس داده‌شده بپیوندید" } From 575e27ea0e2c878235a998a2e79066046d001e97 Mon Sep 17 00:00:00 2001 From: Fake Mail Date: Tue, 18 May 2021 23:26:38 +0000 Subject: [PATCH 067/174] Translated using Weblate (Bulgarian) Currently translated at 87.6% (2601 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/bg/ --- src/i18n/strings/bg.json | 70 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/bg.json b/src/i18n/strings/bg.json index 8c6ea60a8d..294d5a4979 100644 --- a/src/i18n/strings/bg.json +++ b/src/i18n/strings/bg.json @@ -728,7 +728,7 @@ "Enable them now": "Включете ги сега", "Toolbox": "Инструменти", "Collecting logs": "Събиране на логове", - "You must specify an event type!": "Трябва да укажате тип на събитието", + "You must specify an event type!": "Трябва да укажате тип на събитието!", "(HTTP status %(httpStatus)s)": "(HTTP статус %(httpStatus)s)", "All Rooms": "Във всички стаи", "Wednesday": "Сряда", @@ -2831,5 +2831,71 @@ "Åland Islands": "Оландски острови", "Afghanistan": "Афганистан", "United States": "Съединените щати", - "United Kingdom": "Обединеното кралство" + "United Kingdom": "Обединеното кралство", + "Manage & explore rooms": "Управление и откриване на стаи", + "Space options": "Опции на пространството", + "Workspace: ": "Работна област: ", + "Channel: ": "Канал: ", + "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Пространствата са нов начин за групиране на стаи и хора. За да се присъедините към съществуващо пространство, се нуждаете от покана.", + "Open space for anyone, best for communities": "Открийте пространство за всеки, най-добро за общности", + "Add existing room": "Добави съществуваща стая", + "Leave space": "Напусни пространство", + "Invite with email or username": "Покани чрез имейл или потребителско име", + "Invite people": "Покани хора", + "Share invite link": "Сподели връзка с покана", + "Click to copy": "Натиснете за копиране", + "Delete": "Изтрий", + "Please enter a name for the space": "Моля, въведете име на пространството", + "Create a space": "Създаване на пространство", + "Add some details to help people recognise it.": "Добавете някои подробности, за да помогнете на хората да го разпознаят.", + "Invite only, best for yourself or teams": "Само с покана, най-добро за вас самият или отбори", + "Public": "Публично", + "Private": "Лично", + "You can change this later": "Можете да го промените по-късно", + "Your public space": "Вашето публично пространство", + "Your private space": "Вашето лично пространство", + "You can change these anytime.": "Можете да ги промените по всяко време.", + "Creating...": "Създаване...", + "Collapse space panel": "Свий панел с пространства", + "Expand space panel": "Разшири панел с пространства", + "unknown person": "", + "sends snowfall": "изпраща снеговалеж", + "Sends the given message with snowfall": "Изпраща даденото съобщение със снеговалеж", + "Sends the given message with fireworks": "Изпраща даденото съобщение с фойерверки", + "sends fireworks": "изпраща фойерверки", + "sends confetti": "изпраща конфети", + "Sends the given message with confetti": "Изпраща даденото съобщение с конфети", + "Show chat effects (animations when receiving e.g. confetti)": "Покажи чат ефектите (анимации при получаване, като например конфети)", + "Use Ctrl + Enter to send a message": "Използвай Ctrl + Enter за изпращане на съобщение", + "Use Command + Enter to send a message": "Използвай Command + Enter за изпращане на съобщение", + "Use Ctrl + F to search": "Използвай Ctrl + F за търсене", + "Use Command + F to search": "Използвай Command + F за търсене", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "Вашата обратна връзка ще направи пространствата по-добри. Kолкото повече изпаднете в подобробности, толкова по-добре.", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Бета версията е налична за уеб, десктоп и Android. Някои функции може да не са налични на вашия Home сървър.", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "Можете да напуснете бета версията по всяко време от настройките или чрез докосване на симовола за бета версията, като този по-горе.", + "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s ще се презареди с пуснати Пространства. Общностите и собствените етикети ще бъдат скрити.", + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Ако напуснете, %(brand)s ще се презареди със изключени Пространства. Общностите и собствените етикети ще бъдат видими отново.", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "Бета версията е налична за уеб, десктоп и Android. Благодарим ви, че изпробвахте бета версията.", + "Spaces are a new way to group rooms and people.": "Пространствата са нов начин за групиране на стаи и хора.", + "Spaces": "Пространства", + "Check your devices": "Проверете устройствата си", + "%(deviceId)s from %(ip)s": "%(deviceId)s от %(ip)s", + "This homeserver has been blocked by it's administrator.": "Този Home сървър е бил блокиран от администратора му.", + "Use app for a better experience": "Използвайте приложението за по-добра работа", + "Use app": "Използване на приложението", + "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.": "Element Web е експериментален на мобилни устройства. За по-добра работа и най-новите функции, използвайте нашето безплатно приложение.", + "Review to ensure your account is safe": "Прегледайте, за да уверите, че профилът ви е в безопастност", + "You have unverified logins": "Имате неверифицирани сесии", + "Share your public space": "Споделете публичното си място", + "Invite to %(spaceName)s": "Покани в %(spaceName)s", + "%(senderName)s has updated the widget layout": "%(senderName)s обнови оформлението на приспособлението", + "Sends the given message as a spoiler": "Изпраща даденото съобщение като спойлер", + "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.": "Вашият Home сървър отхвърли вашия опит за влизане. Това може да се дължи на неща, които просто отнемат твърде много време. Моля, опитайте отново. Ако това продължи, моля, свържете се със админитратора на вашия Home сървър.", + "Your homeserver was unreachable and was not able to log you in. Please try again. If this continues, please contact your homeserver administrator.": "Вашият Home сървър беше недостижим и не можа да ви впише. Моля, опитайте отново. Ако това продължи, моля, свържете се със админитратора на вашия Home сървър.", + "Try again": "Опитайте отново", + "We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.": "Помолихме браузъра да запомни кой Home сървър използвате за влизане, но за съжаление браузърът ви го е забравил. Отидете на страницата за влизане и опитайте отново.", + "Already in call": "Вече в разговор", + "You're already in a call with this person.": "Вече сте в разговор в този човек.", + "Too Many Calls": "Твърде много повиквания", + "Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "Неуспешно повикване поради неуспешен достъп до микрофон. Проверете дали микрофонът е включен и настроен правилно." } From 78a8c9e10ecdbd6fadb6d9c949c32b9a6b185aab Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 10:46:11 +0100 Subject: [PATCH 068/174] Fix issue when a room without a name or alias is marked as suggested --- src/components/views/rooms/RoomList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index fdf38784cf..b042414c85 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -428,7 +428,7 @@ export default class RoomList extends React.PureComponent { private renderSuggestedRooms(): ReactComponentElement[] { return this.state.suggestedRooms.map(room => { - const name = room.name || room.canonical_alias || room.aliases.pop() || _t("Empty room"); + const name = room.name || room.canonical_alias || room.aliases?.[0] || _t("Empty room"); const avatar = ( Date: Wed, 19 May 2021 15:01:05 +0530 Subject: [PATCH 069/174] Add getMember mock --- test/components/views/rooms/MemberList-test.js | 1 + test/test-utils.js | 1 + 2 files changed, 2 insertions(+) diff --git a/test/components/views/rooms/MemberList-test.js b/test/components/views/rooms/MemberList-test.js index 093e5588d0..50b40dea20 100644 --- a/test/components/views/rooms/MemberList-test.js +++ b/test/components/views/rooms/MemberList-test.js @@ -88,6 +88,7 @@ describe('MemberList', () => { }; memberListRoom.currentState = { members: {}, + getMember: jest.fn(), getStateEvents: (eventType, stateKey) => stateKey === undefined ? [] : null, // ignore 3pid invites }; for (const member of [...adminUsers, ...moderatorUsers, ...defaultUsers]) { diff --git a/test/test-utils.js b/test/test-utils.js index 953693a820..d7c960228c 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -245,6 +245,7 @@ export function mkStubRoom(roomId = null) { maySendMessage: jest.fn().mockReturnValue(true), currentState: { getStateEvents: jest.fn(), + getMember: jest.fn(), mayClientSendStateEvent: jest.fn().mockReturnValue(true), maySendStateEvent: jest.fn().mockReturnValue(true), maySendEvent: jest.fn().mockReturnValue(true), From 5daca41bfb049c12ecdb5edfb326f5c257287578 Mon Sep 17 00:00:00 2001 From: Nique Woodhouse Date: Wed, 19 May 2021 11:17:34 +0100 Subject: [PATCH 070/174] Update _MessageActionBar.scss Increase size of buttons --- res/css/views/messages/_MessageActionBar.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index a697c8d0be..81be6d1917 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -20,12 +20,12 @@ limitations under the License. visibility: hidden; cursor: pointer; display: flex; - height: 28px; + height: 32px; line-height: $font-24px; border-radius: 8px; background: $primary-bg-color; border: 1px solid $input-border-color; - top: -28px; + top: -32px; right: 8px; user-select: none; // Ensure the action bar appears above over things, like the read marker. @@ -63,8 +63,8 @@ limitations under the License. } .mx_MessageActionBar_maskButton { - width: 24px; - height: 24px; + width: 28px; + height: 28px; } .mx_MessageActionBar_maskButton::after { From e6c595a5635cec2eb48c84526c7dd66c0132eb9a Mon Sep 17 00:00:00 2001 From: Germain Date: Wed, 19 May 2021 11:27:32 +0100 Subject: [PATCH 071/174] Delete RoomView dead code --- src/components/structures/RoomView.tsx | 45 -------------------------- 1 file changed, 45 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index c0f3c59457..7c9afacd55 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1598,33 +1598,6 @@ export default class RoomView extends React.Component { this.setState({auxPanelMaxHeight: auxPanelMaxHeight}); }; - private onFullscreenClick = () => { - dis.dispatch({ - action: 'video_fullscreen', - fullscreen: true, - }, true); - }; - - private onMuteAudioClick = () => { - const call = this.getCallForRoom(); - if (!call) { - return; - } - const newState = !call.isMicrophoneMuted(); - call.setMicrophoneMuted(newState); - this.forceUpdate(); // TODO: just update the voip buttons - }; - - private onMuteVideoClick = () => { - const call = this.getCallForRoom(); - if (!call) { - return; - } - const newState = !call.isLocalVideoMuted(); - call.setLocalVideoMuted(newState); - this.forceUpdate(); // TODO: just update the voip buttons - }; - private onStatusBarVisible = () => { if (this.unmounted) return; this.setState({ @@ -1640,24 +1613,6 @@ export default class RoomView extends React.Component { }); }; - /** - * called by the parent component when PageUp/Down/etc is pressed. - * - * We pass it down to the scroll panel. - */ - private handleScrollKey = ev => { - let panel; - if (this.searchResultsPanel.current) { - panel = this.searchResultsPanel.current; - } else if (this.messagePanel) { - panel = this.messagePanel; - } - - if (panel) { - panel.handleScrollKey(ev); - } - }; - /** * get any current call for this room */ From 8d072cedff41f77a89289e58435fe270408a18fe Mon Sep 17 00:00:00 2001 From: Nique Woodhouse Date: Wed, 19 May 2021 12:11:24 +0100 Subject: [PATCH 072/174] Update _MessageActionBar.scss Correct error on css hover for message action button background colour change --- res/css/views/messages/_MessageActionBar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index 81be6d1917..e2fafe6c62 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -80,7 +80,7 @@ limitations under the License. background-color: $secondary-fg-color; } -.mx_MessageActionBar_maskButton:hover:after { +.mx_MessageActionBar_maskButton:hover::after { background-color: $primary-fg-color; } From 88d3706c04fef7e23d997aa9b1548e3d02142549 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 12:34:27 +0100 Subject: [PATCH 073/174] mock getMembers on mkStubRoom --- test/test-utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test-utils.js b/test/test-utils.js index 953693a820..bad3547133 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -234,6 +234,7 @@ export function mkStubRoom(roomId = null) { }), getMembersWithMembership: jest.fn().mockReturnValue([]), getJoinedMembers: jest.fn().mockReturnValue([]), + getMembers: jest.fn().mockReturnValue([]), getPendingEvents: () => [], getLiveTimeline: () => stubTimeline, getUnfilteredTimelineSet: () => null, From 6e25e42e66d6d38eb487b6c8d4f0597528f5d893 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 13:00:46 +0100 Subject: [PATCH 074/174] Show subspace rooms count even if it is 0 for consistency --- src/components/structures/SpaceRoomDirectory.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index c1e9027b1d..2881b6c3ea 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -152,7 +152,7 @@ const Tile: React.FC = ({ } let description = _t("%(count)s members", { count: room.num_joined_members }); - if (numChildRooms) { + if (numChildRooms !== undefined) { description += " · " + _t("%(count)s rooms", { count: numChildRooms }); } if (room.topic) { From d73eb0c70f23b4d54a928db6399d2a855a350654 Mon Sep 17 00:00:00 2001 From: Jaiwanth Date: Wed, 19 May 2021 17:46:10 +0530 Subject: [PATCH 075/174] Update MemberList.js --- src/components/views/rooms/MemberList.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index f991827091..55fe650b7b 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -133,16 +133,21 @@ export default class MemberList extends React.Component { } } + get canInvite() { + const cli = MatrixClientPeg.get(); + const room = cli.getRoom(this.props.roomId); + return room && room.canInvite(cli.getUserId()); + } + _getMembersState(members) { // set the state after determining _showPresence to make sure it's // taken into account while rerendering - const cli = MatrixClientPeg.get(); return { loading: false, members: members, filteredJoinedMembers: this._filterMembers(members, 'join'), filteredInvitedMembers: this._filterMembers(members, 'invite'), - canInvite: cli.getRoom(this.props.roomId).canInvite(cli.getUserId()), + canInvite: this.canInvite, // ideally we'd size this to the page height, but // in practice I find that a little constraining @@ -199,9 +204,7 @@ export default class MemberList extends React.Component { this._updateList(); } - const cli = MatrixClientPeg.get(); - const canInvite = cli.getRoom(this.props.roomId).canInvite(cli.getUserId()); - if (canInvite !== this.state.canInvite) this.setState({canInvite}); + if (this.canInvite !== this.state.canInvite) this.setState({ canInvite: this.canInvite }); }; _updateList = rate_limited_func(() => { From 6170403c103f2fe9f6c4404c6c49f3cececf7299 Mon Sep 17 00:00:00 2001 From: Germain Date: Wed, 19 May 2021 13:25:52 +0100 Subject: [PATCH 076/174] Depile encrypted events to find the most suitable one for preview (#6056) --- src/components/views/rooms/RoomTile.tsx | 16 +++++++++------- src/stores/room-list/MessagePreviewStore.ts | 11 +++++++---- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index edd644fd65..60368ce250 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -100,8 +100,10 @@ export default class RoomTile extends React.PureComponent { hasUnsentEvents: this.countUnsentEvents() > 0, // generatePreview() will return nothing if the user has previews disabled - messagePreview: this.generatePreview(), + messagePreview: "", }; + this.generatePreview(); + this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room); this.roomProps = EchoChamber.forRoom(this.props.room); if (this.props.resizeNotifier) { @@ -123,7 +125,7 @@ export default class RoomTile extends React.PureComponent { private onResize = () => { if (this.showMessagePreview && !this.state.messagePreview) { - this.setState({messagePreview: this.generatePreview()}); + this.generatePreview(); } }; @@ -147,7 +149,7 @@ export default class RoomTile extends React.PureComponent { public componentDidUpdate(prevProps: Readonly, prevState: Readonly) { if (prevProps.showMessagePreview !== this.props.showMessagePreview && this.showMessagePreview) { - this.setState({messagePreview: this.generatePreview()}); + this.generatePreview(); } if (prevProps.room?.roomId !== this.props.room?.roomId) { MessagePreviewStore.instance.off( @@ -236,17 +238,17 @@ export default class RoomTile extends React.PureComponent { private onRoomPreviewChanged = (room: Room) => { if (this.props.room && room.roomId === this.props.room.roomId) { - // generatePreview() will return nothing if the user has previews disabled - this.setState({messagePreview: this.generatePreview()}); + this.generatePreview(); } }; - private generatePreview(): string | null { + private async generatePreview() { if (!this.showMessagePreview) { return null; } - return MessagePreviewStore.instance.getPreviewForRoom(this.props.room, this.props.tag); + const messagePreview = await MessagePreviewStore.instance.getPreviewForRoom(this.props.room, this.props.tag); + this.setState({ messagePreview }); } private scrollIntoView = () => { diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index 1da0e661e8..10e5cf554e 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -94,10 +94,10 @@ export class MessagePreviewStore extends AsyncStoreWithClient { * @param inTagId The tag ID in which the room resides * @returns The preview, or null if none present. */ - public getPreviewForRoom(room: Room, inTagId: TagID): string { + public async getPreviewForRoom(room: Room, inTagId: TagID): Promise { if (!room) return null; // invalid room, just return nothing - if (!this.previews.has(room.roomId)) this.generatePreview(room, inTagId); + if (!this.previews.has(room.roomId)) await this.generatePreview(room, inTagId); const previews = this.previews.get(room.roomId); if (!previews) return null; @@ -108,7 +108,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient { return previews.get(inTagId); } - private generatePreview(room: Room, tagId?: TagID) { + private async generatePreview(room: Room, tagId?: TagID) { const events = room.timeline; if (!events) return; // should only happen in tests @@ -130,6 +130,9 @@ export class MessagePreviewStore extends AsyncStoreWithClient { } const event = events[i]; + + await this.matrixClient.decryptEventIfNeeded(event); + const previewDef = PREVIEWS[event.getType()]; if (!previewDef) continue; if (previewDef.isState && isNullOrUndefined(event.getStateKey())) continue; @@ -174,7 +177,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient { if (payload.action === 'MatrixActions.Room.timeline' || payload.action === 'MatrixActions.Event.decrypted') { const event = payload.event; // TODO: Type out the dispatcher if (!this.previews.has(event.getRoomId())) return; // not important - this.generatePreview(this.matrixClient.getRoom(event.getRoomId()), TAG_ANY); + await this.generatePreview(this.matrixClient.getRoom(event.getRoomId()), TAG_ANY); } } } From 0a018500e223e7f791be50f9e72f468a61bfdbde Mon Sep 17 00:00:00 2001 From: Hivaa Date: Wed, 19 May 2021 10:30:22 +0000 Subject: [PATCH 077/174] Translated using Weblate (Persian) Currently translated at 21.0% (625 of 2968 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fa/ --- src/i18n/strings/fa.json | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fa.json b/src/i18n/strings/fa.json index 0e6a9116d2..e248bc4c0f 100644 --- a/src/i18n/strings/fa.json +++ b/src/i18n/strings/fa.json @@ -635,5 +635,28 @@ "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "این که آیا از ویژگی 'breadcrumbs' (نمایه‌ی کاربری بالای فهرست اتاق‌ها) استفاده می‌کنید یا خیر", "Use an identity server to invite by email. Manage in Settings.": "برای دعوت از یک سرور هویت‌سنجی استفاده نمائید. می‌توانید این مورد را در تنظیمات پیکربندی نمائید.", "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": "به اتاق با آدرس داده‌شده بپیوندید" + "Joins room with given address": "به اتاق با آدرس داده‌شده بپیوندید", + "WARNING: Session already verified, but keys do NOT MATCH!": "هشدار امنیتی: نشست پیش از این تائید شده، اما کلیدها مطابقت ندارد!", + "Session already verified!": "نشست پیش از این تائید شده‌است!", + "Unknown (user, session) pair:": "جفت (کاربر، نشست) ناشناخته:", + "Verifies a user, session, and pubkey tuple": "یک کاربر، نشست و عبارت کلید عمومی را تائید می‌کند", + "You cannot modify widgets in this room.": "شما امکان تغییر ویجت‌ها در این اتاق را ندارید.", + "Please supply a https:// or http:// widget URL": "لطفا نشانی یک ویجت را به پروتکل http:// یا https:// وارد کنید", + "Please supply a widget URL or embed code": "لطفا نشانی (URL) ویجت یا یک کد قابل جاسازی (embeded) وارد کنید", + "Adds a custom widget by URL to the room": "یک ویجت سفارشی را با استفاده از نشانی (URL) به اتاق اضافه می‌کند", + "Opens the Developer Tools dialog": "پنجره‌ی ابزار توسعه را باز می‌کند", + "Could not find user in room": "کاربر در اتاق یافت نشد", + "Command failed": "دستور موفقیت‌آمیز نبود", + "Define the power level of a user": "سطح قدرت یک کاربر را تعریف کنید", + "You are no longer ignoring %(userId)s": "شما دیگر کاربر %(userId)s را نادیده نمی‌گیرید", + "Unignored user": "کاربران نادیده گرفته‌نشده", + "Stops ignoring a user, showing their messages going forward": "توقف نادیده گرفتن یک کاربر، باعث می‌شود پیام‌های او به شما نمایش داده شود", + "You are now ignoring %(userId)s": "شما هم‌اکنون کاربر %(userId)s را نادیده گرفتید", + "Ignored user": "کاربران نادیده گرفته‌شده", + "Ignores a user, hiding their messages from you": "نادیده گرفتن یک کاربر، باعث می‌شود پیام‌های او به شما نمایش داده نشود", + "Unbans user with given ID": "رفع تحریم کاربر با شناسه‌ی مذکور", + "Bans user with given id": "تحریم کاربر با شناسه‌ی مذکور", + "Kicks user with given id": "اخراج کاربر با شناسه‌ی مذکور", + "Unrecognised room address:": "آدرس اتاق قابل تشخیص نیست:", + "Leave room": "ترک اتاق" } From 7d0f3b0d99656361484b601999d2ae8ab35905f7 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 19 May 2021 14:33:21 +0100 Subject: [PATCH 078/174] Upgrade matrix-js-sdk to 11.1.0-rc.1 --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index f8d94e2d21..d7670f55ce 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "11.1.0-rc.1", "matrix-widget-api": "^0.1.0-beta.14", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 19c0646d32..0472555dd9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5669,9 +5669,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 "11.0.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/52a893a8116d60bb76f1b015d3161a15404b3628" +matrix-js-sdk@11.1.0-rc.1: + version "11.1.0-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-11.1.0-rc.1.tgz#98da580fa51bc8c70c57984e79dc63c617a671d1" + integrity sha512-yyZeL1mHttw+EnZcGfMH+wd33s0+ZKB+KyXHA3QiqiVRWLgANjIY9QCNjbJYa9FK8zZRqO2yHiFLtTAAU379Ag== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From cde23ab6e8e3571bfc1b82f64dd20888816db973 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 19 May 2021 14:40:36 +0100 Subject: [PATCH 079/174] Prepare changelog for v3.22.0-rc.1 --- CHANGELOG.md | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2582668ef9..3b4ec8b457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,108 @@ +Changes in [3.22.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.22.0-rc.1) (2021-05-19) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.21.0...v3.22.0-rc.1) + + * Upgrade to JS SDK 11.1.0-rc.1 + * Translations update from Weblate + [\#6068](https://github.com/matrix-org/matrix-react-sdk/pull/6068) + * Show DMs in space for invited members too, to match Android impl + [\#6062](https://github.com/matrix-org/matrix-react-sdk/pull/6062) + * Support filtering by alias in add existing to space dialog + [\#6057](https://github.com/matrix-org/matrix-react-sdk/pull/6057) + * Fix issue when a room without a name or alias is marked as suggested + [\#6064](https://github.com/matrix-org/matrix-react-sdk/pull/6064) + * Fix space room hierarchy not updating when removing a room + [\#6055](https://github.com/matrix-org/matrix-react-sdk/pull/6055) + * Revert "Try putting room list handling behind a lock" + [\#6060](https://github.com/matrix-org/matrix-react-sdk/pull/6060) + * Stop assuming encrypted messages are decrypted ahead of time + [\#6052](https://github.com/matrix-org/matrix-react-sdk/pull/6052) + * Add error detail when languges fail to load + [\#6059](https://github.com/matrix-org/matrix-react-sdk/pull/6059) + * Add space invaders chat effect + [\#6053](https://github.com/matrix-org/matrix-react-sdk/pull/6053) + * Create SpaceProvider and hide Spaces from the RoomProvider autocompleter + [\#6051](https://github.com/matrix-org/matrix-react-sdk/pull/6051) + * Don't mark a room as unread when redacted event is present + [\#6049](https://github.com/matrix-org/matrix-react-sdk/pull/6049) + * Add support for MSC2873: Client information for Widgets + [\#6023](https://github.com/matrix-org/matrix-react-sdk/pull/6023) + * Support UI for MSC2762: Widgets reading events from rooms + [\#5960](https://github.com/matrix-org/matrix-react-sdk/pull/5960) + * Fix crash on opening notification panel + [\#6047](https://github.com/matrix-org/matrix-react-sdk/pull/6047) + * Remove custom LoggedInView::shouldComponentUpdate logic + [\#6046](https://github.com/matrix-org/matrix-react-sdk/pull/6046) + * Fix edge cases with the new add reactions prompt button + [\#6045](https://github.com/matrix-org/matrix-react-sdk/pull/6045) + * Add ids to homeserver and passphrase fields + [\#6043](https://github.com/matrix-org/matrix-react-sdk/pull/6043) + * Update space order field validity requirements to match msc update + [\#6042](https://github.com/matrix-org/matrix-react-sdk/pull/6042) + * Try putting room list handling behind a lock + [\#6024](https://github.com/matrix-org/matrix-react-sdk/pull/6024) + * Improve progress bar progression for smaller voice messages + [\#6035](https://github.com/matrix-org/matrix-react-sdk/pull/6035) + * Fix share space edge case where space is public but not invitable + [\#6039](https://github.com/matrix-org/matrix-react-sdk/pull/6039) + * Add missing 'rel' to image view download button + [\#6033](https://github.com/matrix-org/matrix-react-sdk/pull/6033) + * Improve visible waveform for voice messages + [\#6034](https://github.com/matrix-org/matrix-react-sdk/pull/6034) + * Fix roving tab index intercepting home/end in space create menu + [\#6040](https://github.com/matrix-org/matrix-react-sdk/pull/6040) + * Decorate room avatars with publicity in add existing to space flow + [\#6030](https://github.com/matrix-org/matrix-react-sdk/pull/6030) + * Improve Spaces "Just Me" wizard + [\#6025](https://github.com/matrix-org/matrix-react-sdk/pull/6025) + * Increase hover feedback on room sub list buttons + [\#6037](https://github.com/matrix-org/matrix-react-sdk/pull/6037) + * Show alternative button during space creation wizard if no rooms + [\#6029](https://github.com/matrix-org/matrix-react-sdk/pull/6029) + * Swap rotation buttons in the image viewer + [\#6032](https://github.com/matrix-org/matrix-react-sdk/pull/6032) + * Typo: initilisation -> initialisation + [\#5915](https://github.com/matrix-org/matrix-react-sdk/pull/5915) + * Save edited state of a message when switching rooms + [\#6001](https://github.com/matrix-org/matrix-react-sdk/pull/6001) + * Fix shield icon in Untrusted Device Dialog + [\#6022](https://github.com/matrix-org/matrix-react-sdk/pull/6022) + * Do not eagerly decrypt breadcrumb rooms + [\#6028](https://github.com/matrix-org/matrix-react-sdk/pull/6028) + * Update spaces.png + [\#6031](https://github.com/matrix-org/matrix-react-sdk/pull/6031) + * Encourage more diverse reactions to content + [\#6027](https://github.com/matrix-org/matrix-react-sdk/pull/6027) + * Wrap decodeURIComponent in try-catch to protect against malformed URIs + [\#6026](https://github.com/matrix-org/matrix-react-sdk/pull/6026) + * Iterate beta feedback dialog + [\#6021](https://github.com/matrix-org/matrix-react-sdk/pull/6021) + * Disable space fields whilst their form is busy + [\#6020](https://github.com/matrix-org/matrix-react-sdk/pull/6020) + * Add missing space on beta feedback dialog + [\#6018](https://github.com/matrix-org/matrix-react-sdk/pull/6018) + * Fix colours used for the back button in space create menu + [\#6017](https://github.com/matrix-org/matrix-react-sdk/pull/6017) + * Prioritise and reduce the amount of events decrypted on application startup + [\#5980](https://github.com/matrix-org/matrix-react-sdk/pull/5980) + * Linkify topics in space room directory results + [\#6015](https://github.com/matrix-org/matrix-react-sdk/pull/6015) + * Persistent space collapsed states + [\#5972](https://github.com/matrix-org/matrix-react-sdk/pull/5972) + * Catch another instance of unlabeled avatars. + [\#6010](https://github.com/matrix-org/matrix-react-sdk/pull/6010) + * Rescale and smooth voice message playback waveform to better match + expectation + [\#5996](https://github.com/matrix-org/matrix-react-sdk/pull/5996) + * Scale voice message clock with user's font size + [\#5993](https://github.com/matrix-org/matrix-react-sdk/pull/5993) + * Remove "in development" flag from voice messages + [\#5995](https://github.com/matrix-org/matrix-react-sdk/pull/5995) + * Support voice messages on Safari + [\#5989](https://github.com/matrix-org/matrix-react-sdk/pull/5989) + * Translations update from Weblate + [\#6011](https://github.com/matrix-org/matrix-react-sdk/pull/6011) + Changes in [3.21.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.21.0) (2021-05-17) ===================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.21.0-rc.1...v3.21.0) From 10bc96dc374ec9093705a480a78f0feb06d0fb23 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 19 May 2021 14:40:37 +0100 Subject: [PATCH 080/174] v3.22.0-rc.1 --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index d7670f55ce..8ecebc4eb3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.21.0", + "version": "3.22.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -25,7 +25,7 @@ "bin": { "reskindex": "scripts/reskindex.js" }, - "main": "./src/index.js", + "main": "./lib/index.js", "matrix_src_main": "./src/index.js", "matrix_lib_main": "./lib/index.js", "matrix_lib_typings": "./lib/index.d.ts", @@ -200,5 +200,6 @@ "coverageReporters": [ "text" ] - } + }, + "typings": "./lib/index.d.ts" } From abd290938af0efd358a9e143315a19dd378352d8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 17:12:25 +0100 Subject: [PATCH 081/174] Fix room name not wrapping in summary card --- res/css/views/right_panel/_RoomSummaryCard.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index 36882f4e8b..dc7804d072 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -36,6 +36,7 @@ limitations under the License. -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; + white-space: pre-wrap; } .mx_RoomSummaryCard_avatar { From f52dc9a3eaa515991fd1000fe689f5f70607b63b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 17:12:31 +0100 Subject: [PATCH 082/174] Fix room name not updating whilst summary card is open --- src/components/views/right_panel/RoomSummaryCard.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index bbe61807ae..88928290f4 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -45,6 +45,7 @@ import {ChevronFace, ContextMenuTooltipButton, useContextMenu} from "../../struc import WidgetContextMenu from "../context_menus/WidgetContextMenu"; import {useRoomMemberCount} from "../../../hooks/useRoomMembers"; import { Container, MAX_PINNED, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; +import RoomName from "../elements/RoomName"; interface IProps { room: Room; @@ -249,7 +250,13 @@ const RoomSummaryCard: React.FC = ({ room, onClose }) => { />
-

{ room.name }

+ + { name => ( +

+ { name } +

+ )} +
{ alias }
From d10a45c6a33997146bf825993e90240503f1efe0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 18:40:03 +0100 Subject: [PATCH 083/174] Convert RoomDirectory and NetworkDropdown to Typescript --- .../{RoomDirectory.js => RoomDirectory.tsx} | 351 ++++++++++-------- ...NetworkDropdown.js => NetworkDropdown.tsx} | 91 +++-- 2 files changed, 253 insertions(+), 189 deletions(-) rename src/components/structures/{RoomDirectory.js => RoomDirectory.tsx} (75%) rename src/components/views/directory/{NetworkDropdown.js => NetworkDropdown.tsx} (81%) diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.tsx similarity index 75% rename from src/components/structures/RoomDirectory.js rename to src/components/structures/RoomDirectory.tsx index 3613261da6..2dc9bf2b9f 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.tsx @@ -1,7 +1,6 @@ /* -Copyright 2015, 2016 OpenMarket Ltd Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. +Copyright 2015, 2016, 2019, 2020 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,39 +15,90 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; -import {MatrixClientPeg} from "../../MatrixClientPeg"; -import * as sdk from "../../index"; +import React from "react"; + +import { MatrixClientPeg } from "../../MatrixClientPeg"; import dis from "../../dispatcher/dispatcher"; import Modal from "../../Modal"; import { linkifyAndSanitizeHtml } from '../../HtmlUtils'; -import PropTypes from 'prop-types'; import { _t } from '../../languageHandler'; import SdkConfig from '../../SdkConfig'; import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils'; import Analytics from '../../Analytics'; -import {ALL_ROOMS} from "../views/directory/NetworkDropdown"; +import {ALL_ROOMS, IFieldType, IInstance, IProtocol, Protocols} from "../views/directory/NetworkDropdown"; import SettingsStore from "../../settings/SettingsStore"; import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore"; import GroupStore from "../../stores/GroupStore"; import FlairStore from "../../stores/FlairStore"; import CountlyAnalytics from "../../CountlyAnalytics"; -import {replaceableComponent} from "../../utils/replaceableComponent"; -import {mediaFromMxc} from "../../customisations/Media"; +import { replaceableComponent } from "../../utils/replaceableComponent"; +import { mediaFromMxc } from "../../customisations/Media"; +import { IDialogProps } from "../views/dialogs/IDialogProps"; +import AccessibleButton, {ButtonEvent} from "../views/elements/AccessibleButton"; +import BaseAvatar from "../views/avatars/BaseAvatar"; +import ErrorDialog from "../views/dialogs/ErrorDialog"; +import QuestionDialog from "../views/dialogs/QuestionDialog"; +import BaseDialog from "../views/dialogs/BaseDialog"; +import DirectorySearchBox from "../views/elements/DirectorySearchBox"; +import NetworkDropdown from "../views/directory/NetworkDropdown"; +import ScrollPanel from "./ScrollPanel"; +import Spinner from "../views/elements/Spinner"; +import { ActionPayload } from "../../dispatcher/payloads"; + const MAX_NAME_LENGTH = 80; const MAX_TOPIC_LENGTH = 800; -function track(action) { +function track(action: string) { Analytics.trackEvent('RoomDirectory', action); } +interface IProps extends IDialogProps { + initialText?: string; +} + +interface IState { + publicRooms: IRoom[]; + loading: boolean; + protocolsLoading: boolean; + error?: string; + instanceId: string | symbol; + roomServer: string; + filterString: string; + selectedCommunityId?: string; + communityName?: string; +} + +/* eslint-disable camelcase */ +interface IRoom { + room_id: string; + name?: string; + avatar_url?: string; + topic?: string; + canonical_alias?: string; + aliases?: string[]; + world_readable: boolean; + guest_can_join: boolean; + num_joined_members: number; +} + +interface IPublicRoomsRequest { + limit?: number; + since?: string; + server?: string; + filter?: object; + include_all_networks?: boolean; + third_party_instance_id?: string; +} +/* eslint-enable camelcase */ + @replaceableComponent("structures.RoomDirectory") -export default class RoomDirectory extends React.Component { - static propTypes = { - initialText: PropTypes.string, - onFinished: PropTypes.func.isRequired, - }; +export default class RoomDirectory extends React.Component { + private readonly startTime: number; + private unmounted = false + private nextBatch: string = null; + private filterTimeout: NodeJS.Timeout; + private protocols: Protocols; constructor(props) { super(props); @@ -57,34 +107,12 @@ export default class RoomDirectory extends React.Component { this.startTime = CountlyAnalytics.getTimestamp(); const selectedCommunityId = GroupFilterOrderStore.getSelectedTags()[0]; - this.state = { - publicRooms: [], - loading: true, - protocolsLoading: true, - error: null, - instanceId: undefined, - roomServer: MatrixClientPeg.getHomeserverName(), - filterString: this.props.initialText || "", - selectedCommunityId: SettingsStore.getValue("feature_communities_v2_prototypes") - ? selectedCommunityId - : null, - communityName: null, - }; - this._unmounted = false; - this.nextBatch = null; - this.filterTimeout = null; - this.scrollPanel = null; - this.protocols = null; - - this.state.protocolsLoading = true; + let protocolsLoading = true; if (!MatrixClientPeg.get()) { // We may not have a client yet when invoked from welcome page - this.state.protocolsLoading = false; - return; - } - - if (!this.state.selectedCommunityId) { + protocolsLoading = false; + } else if (!this.state.selectedCommunityId) { MatrixClientPeg.get().getThirdpartyProtocols().then((response) => { this.protocols = response; this.setState({protocolsLoading: false}); @@ -109,13 +137,27 @@ export default class RoomDirectory extends React.Component { }); } else { // We don't use the protocols in the communities v2 prototype experience - this.state.protocolsLoading = false; + protocolsLoading = false; // Grab the profile info async FlairStore.getGroupProfileCached(MatrixClientPeg.get(), this.state.selectedCommunityId).then(profile => { this.setState({communityName: profile.name}); }); } + + this.state = { + publicRooms: [], + loading: true, + error: null, + instanceId: undefined, + roomServer: MatrixClientPeg.getHomeserverName(), + filterString: this.props.initialText || "", + selectedCommunityId: SettingsStore.getValue("feature_communities_v2_prototypes") + ? selectedCommunityId + : null, + communityName: null, + protocolsLoading, + }; } componentDidMount() { @@ -126,10 +168,10 @@ export default class RoomDirectory extends React.Component { if (this.filterTimeout) { clearTimeout(this.filterTimeout); } - this._unmounted = true; + this.unmounted = true; } - refreshRoomList = () => { + private refreshRoomList = () => { if (this.state.selectedCommunityId) { this.setState({ publicRooms: GroupStore.getGroupRooms(this.state.selectedCommunityId).map(r => { @@ -165,7 +207,7 @@ export default class RoomDirectory extends React.Component { this.getMoreRooms(); }; - getMoreRooms() { + private getMoreRooms() { if (this.state.selectedCommunityId) return Promise.resolve(); // no more rooms if (!MatrixClientPeg.get()) return Promise.resolve(); @@ -173,34 +215,34 @@ export default class RoomDirectory extends React.Component { loading: true, }); - const my_filter_string = this.state.filterString; - const my_server = this.state.roomServer; + const filterString = this.state.filterString; + const roomServer = this.state.roomServer; // remember the next batch token when we sent the request // too. If it's changed, appending to the list will corrupt it. - const my_next_batch = this.nextBatch; - const opts = {limit: 20}; - if (my_server != MatrixClientPeg.getHomeserverName()) { - opts.server = my_server; + const nextBatch = this.nextBatch; + const opts: IPublicRoomsRequest = { limit: 20 }; + if (roomServer != MatrixClientPeg.getHomeserverName()) { + opts.server = roomServer; } if (this.state.instanceId === ALL_ROOMS) { opts.include_all_networks = true; } else if (this.state.instanceId) { - opts.third_party_instance_id = this.state.instanceId; + opts.third_party_instance_id = this.state.instanceId as string; } if (this.nextBatch) opts.since = this.nextBatch; - if (my_filter_string) opts.filter = { generic_search_term: my_filter_string }; + if (filterString) opts.filter = { generic_search_term: filterString }; return MatrixClientPeg.get().publicRooms(opts).then((data) => { if ( - my_filter_string != this.state.filterString || - my_server != this.state.roomServer || - my_next_batch != this.nextBatch) { + filterString != this.state.filterString || + roomServer != this.state.roomServer || + nextBatch != this.nextBatch) { // if the filter or server has changed since this request was sent, // throw away the result (don't even clear the busy flag // since we must still have a request in flight) return; } - if (this._unmounted) { + if (this.unmounted) { // if we've been unmounted, we don't care either. return; } @@ -211,23 +253,23 @@ export default class RoomDirectory extends React.Component { } this.nextBatch = data.next_batch; - this.setState((s) => { - s.publicRooms.push(...(data.chunk || [])); - s.loading = false; - return s; - }); + this.setState((s) => ({ + ...s, + publicRooms: [...s.publicRooms, ...(data.chunk || [])], + loading: false, + })); return Boolean(data.next_batch); }, (err) => { if ( - my_filter_string != this.state.filterString || - my_server != this.state.roomServer || - my_next_batch != this.nextBatch) { + filterString != this.state.filterString || + roomServer != this.state.roomServer || + nextBatch != this.nextBatch) { // as above: we don't care about errors for old // requests either return; } - if (this._unmounted) { + if (this.unmounted) { // if we've been unmounted, we don't care either. return; } @@ -252,13 +294,10 @@ export default class RoomDirectory extends React.Component { * HS admins to do this through the RoomSettings interface, but * this needs SPEC-417. */ - removeFromDirectory(room) { - const alias = get_display_alias_for_room(room); + private removeFromDirectory(room: IRoom) { + const alias = getDisplayAliasForRoom(room); const name = room.name || alias || _t('Unnamed room'); - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - let desc; if (alias) { desc = _t('Delete the room address %(alias)s and remove %(name)s from the directory?', {alias, name}); @@ -269,11 +308,10 @@ export default class RoomDirectory extends React.Component { Modal.createTrackedDialog('Remove from Directory', '', QuestionDialog, { title: _t('Remove from Directory'), description: desc, - onFinished: (should_delete) => { - if (!should_delete) return; + onFinished: (shouldDelete: boolean) => { + if (!shouldDelete) return; - const Loader = sdk.getComponent("elements.Spinner"); - const modal = Modal.createDialog(Loader); + const modal = Modal.createDialog(Spinner); let step = _t('remove %(name)s from the directory.', {name: name}); MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => { @@ -289,14 +327,16 @@ export default class RoomDirectory extends React.Component { console.error("Failed to " + step + ": " + err); Modal.createTrackedDialog('Remove from Directory Error', '', ErrorDialog, { title: _t('Error'), - description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded')), + description: (err && err.message) + ? err.message + : _t('The server may be unavailable or overloaded'), }); }); }, }); } - onRoomClicked = (room, ev) => { + private onRoomClicked = (room: IRoom, ev: ButtonEvent) => { if (ev.shiftKey && !this.state.selectedCommunityId) { ev.preventDefault(); this.removeFromDirectory(room); @@ -305,7 +345,7 @@ export default class RoomDirectory extends React.Component { } }; - onOptionChange = (server, instanceId) => { + private onOptionChange = (server: string, instanceId?: string | symbol) => { // clear next batch so we don't try to load more rooms this.nextBatch = null; this.setState({ @@ -325,13 +365,13 @@ export default class RoomDirectory extends React.Component { // Easiest to just blow away the state & re-fetch. }; - onFillRequest = (backwards) => { + private onFillRequest = (backwards: boolean) => { if (backwards || !this.nextBatch) return Promise.resolve(false); return this.getMoreRooms(); }; - onFilterChange = (alias) => { + private onFilterChange = (alias: string) => { this.setState({ filterString: alias || null, }); @@ -349,7 +389,7 @@ export default class RoomDirectory extends React.Component { }, 700); }; - onFilterClear = () => { + private onFilterClear = () => { // update immediately this.setState({ filterString: null, @@ -360,7 +400,7 @@ export default class RoomDirectory extends React.Component { } }; - onJoinFromSearchClick = (alias) => { + private onJoinFromSearchClick = (alias: string) => { // If we don't have a particular instance id selected, just show that rooms alias if (!this.state.instanceId || this.state.instanceId === ALL_ROOMS) { // If the user specified an alias without a domain, add on whichever server is selected @@ -373,9 +413,10 @@ export default class RoomDirectory extends React.Component { // This is a 3rd party protocol. Let's see if we can join it const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId); const instance = instanceForInstanceId(this.protocols, this.state.instanceId); - const fields = protocolName ? this._getFieldsForThirdPartyLocation(alias, this.protocols[protocolName], instance) : null; + const fields = protocolName + ? this.getFieldsForThirdPartyLocation(alias, this.protocols[protocolName], instance) + : null; if (!fields) { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const brand = SdkConfig.get().brand; Modal.createTrackedDialog('Unable to join network', '', ErrorDialog, { title: _t('Unable to join network'), @@ -387,14 +428,12 @@ export default class RoomDirectory extends React.Component { if (resp.length > 0 && resp[0].alias) { this.showRoomAlias(resp[0].alias, true); } else { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Room not found', '', ErrorDialog, { title: _t('Room not found'), description: _t('Couldn\'t find a matching Matrix room'), }); } }, (e) => { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Fetching third party location failed', '', ErrorDialog, { title: _t('Fetching third party location failed'), description: _t('Unable to look up room ID from server'), @@ -403,22 +442,22 @@ export default class RoomDirectory extends React.Component { } }; - onPreviewClick = (ev, room) => { + private onPreviewClick = (ev: ButtonEvent, room: IRoom) => { this.showRoom(room, null, false, true); ev.stopPropagation(); }; - onViewClick = (ev, room) => { + private onViewClick = (ev: ButtonEvent, room: IRoom) => { this.showRoom(room); ev.stopPropagation(); }; - onJoinClick = (ev, room) => { + private onJoinClick = (ev: ButtonEvent, room: IRoom) => { this.showRoom(room, null, true); ev.stopPropagation(); }; - onCreateRoomClick = room => { + private onCreateRoomClick = () => { this.onFinished(); dis.dispatch({ action: 'view_create_room', @@ -426,13 +465,13 @@ export default class RoomDirectory extends React.Component { }); }; - showRoomAlias(alias, autoJoin=false) { + private showRoomAlias(alias: string, autoJoin = false) { this.showRoom(null, alias, autoJoin); } - showRoom(room, room_alias, autoJoin = false, shouldPeek = false) { + private showRoom(room: IRoom, roomAlias?: string, autoJoin = false, shouldPeek = false) { this.onFinished(); - const payload = { + const payload: ActionPayload = { action: 'view_room', auto_join: autoJoin, should_peek: shouldPeek, @@ -449,15 +488,15 @@ export default class RoomDirectory extends React.Component { } } - if (!room_alias) { - room_alias = get_display_alias_for_room(room); + if (!roomAlias) { + roomAlias = getDisplayAliasForRoom(room); } payload.oob_data = { avatarUrl: room.avatar_url, // XXX: This logic is duplicated from the JS SDK which // would normally decide what the name is. - name: room.name || room_alias || _t('Unnamed room'), + name: room.name || roomAlias || _t('Unnamed room'), }; if (this.state.roomServer) { @@ -471,21 +510,19 @@ export default class RoomDirectory extends React.Component { // which servers to start querying. However, there's no other way to join rooms in // this list without aliases at present, so if roomAlias isn't set here we have no // choice but to supply the ID. - if (room_alias) { - payload.room_alias = room_alias; + if (roomAlias) { + payload.room_alias = roomAlias; } else { payload.room_id = room.room_id; } dis.dispatch(payload); } - createRoomCells(room) { + private createRoomCells(room: IRoom) { const client = MatrixClientPeg.get(); const clientRoom = client.getRoom(room.room_id); const hasJoinedRoom = clientRoom && clientRoom.getMyMembership() === "join"; const isGuest = client.isGuest(); - const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); let previewButton; let joinOrViewButton; @@ -495,20 +532,26 @@ export default class RoomDirectory extends React.Component { // it is readable, the preview appears as normal. if (!hasJoinedRoom && (room.world_readable || isGuest)) { previewButton = ( - this.onPreviewClick(ev, room)}>{_t("Preview")} + this.onPreviewClick(ev, room)}> + { _t("Preview") } + ); } if (hasJoinedRoom) { joinOrViewButton = ( - this.onViewClick(ev, room)}>{_t("View")} + this.onViewClick(ev, room)}> + { _t("View") } + ); } else if (!isGuest) { joinOrViewButton = ( - this.onJoinClick(ev, room)}>{_t("Join")} + this.onJoinClick(ev, room)}> + { _t("Join") } + ); } - let name = room.name || get_display_alias_for_room(room) || _t('Unnamed room'); + let name = room.name || getDisplayAliasForRoom(room) || _t('Unnamed room'); if (name.length > MAX_NAME_LENGTH) { name = `${name.substring(0, MAX_NAME_LENGTH)}...`; } @@ -531,9 +574,13 @@ export default class RoomDirectory extends React.Component { onMouseDown={(ev) => {ev.preventDefault();}} className="mx_RoomDirectory_roomAvatar" > -
,
{ ev.stopPropagation(); } } dangerouslySetInnerHTML={{ __html: topic }} /> -
{ get_display_alias_for_room(room) }
+
{ getDisplayAliasForRoom(room) }
,
this.onRoomClicked(room, ev)} @@ -576,20 +623,16 @@ export default class RoomDirectory extends React.Component { ]; } - collectScrollPanel = (element) => { - this.scrollPanel = element; - }; - - _stringLooksLikeId(s, field_type) { + private stringLooksLikeId(s: string, fieldType: IFieldType) { let pat = /^#[^\s]+:[^\s]/; - if (field_type && field_type.regexp) { - pat = new RegExp(field_type.regexp); + if (fieldType && fieldType.regexp) { + pat = new RegExp(fieldType.regexp); } return pat.test(s); } - _getFieldsForThirdPartyLocation(userInput, protocol, instance) { + private getFieldsForThirdPartyLocation(userInput: string, protocol: IProtocol, instance: IInstance) { // make an object with the fields specified by that protocol. We // require that the values of all but the last field come from the // instance. The last is the user input. @@ -605,71 +648,52 @@ export default class RoomDirectory extends React.Component { return fields; } - /** - * called by the parent component when PageUp/Down/etc is pressed. - * - * We pass it down to the scroll panel. - */ - handleScrollKey = ev => { - if (this.scrollPanel) { - this.scrollPanel.handleScrollKey(ev); - } - }; - - onFinished = () => { + private onFinished = () => { CountlyAnalytics.instance.trackRoomDirectory(this.startTime); - this.props.onFinished(); + this.props.onFinished(false); }; render() { - const Loader = sdk.getComponent("elements.Spinner"); - const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - let content; if (this.state.error) { content = this.state.error; } else if (this.state.protocolsLoading) { - content = ; + content = ; } else { const cells = (this.state.publicRooms || []) - .reduce((cells, room) => cells.concat(this.createRoomCells(room)), [],); + .reduce((cells, room) => cells.concat(this.createRoomCells(room)), []); // we still show the scrollpanel, at least for now, because // otherwise we don't fetch more because we don't get a fill // request from the scrollpanel because there isn't one let spinner; if (this.state.loading) { - spinner = ; + spinner = ; } - let scrollpanel_content; + let scrollPanelContent; if (cells.length === 0 && !this.state.loading) { - scrollpanel_content = { _t('No rooms to show') }; + scrollPanelContent = { _t('No rooms to show') }; } else { - scrollpanel_content =
+ scrollPanelContent =
{ cells }
; } - const ScrollPanel = sdk.getComponent("structures.ScrollPanel"); - content = - { scrollpanel_content } + { scrollPanelContent } { spinner } ; } let listHeader; if (!this.state.protocolsLoading) { - const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown'); - const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox'); - const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId); - let instance_expected_field_type; + let instanceExpectedFieldType; if ( protocolName && this.protocols && @@ -677,21 +701,27 @@ export default class RoomDirectory extends React.Component { this.protocols[protocolName].location_fields.length > 0 && this.protocols[protocolName].field_types ) { - const last_field = this.protocols[protocolName].location_fields.slice(-1)[0]; - instance_expected_field_type = this.protocols[protocolName].field_types[last_field]; + const lastField = this.protocols[protocolName].location_fields.slice(-1)[0]; + instanceExpectedFieldType = this.protocols[protocolName].field_types[lastField]; } let placeholder = _t('Find a room…'); if (!this.state.instanceId || this.state.instanceId === ALL_ROOMS) { - placeholder = _t("Find a room… (e.g. %(exampleRoom)s)", {exampleRoom: "#example:" + this.state.roomServer}); - } else if (instance_expected_field_type) { - placeholder = instance_expected_field_type.placeholder; + placeholder = _t("Find a room… (e.g. %(exampleRoom)s)", { + exampleRoom: "#example:" + this.state.roomServer, + }); + } else if (instanceExpectedFieldType) { + placeholder = instanceExpectedFieldType.placeholder; } - let showJoinButton = this._stringLooksLikeId(this.state.filterString, instance_expected_field_type); + let showJoinButton = this.stringLooksLikeId(this.state.filterString, instanceExpectedFieldType); if (protocolName) { const instance = instanceForInstanceId(this.protocols, this.state.instanceId); - if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.protocols[protocolName], instance) === null) { + if (this.getFieldsForThirdPartyLocation( + this.state.filterString, + this.protocols[protocolName], + instance, + ) === null) { showJoinButton = false; } } @@ -723,12 +753,11 @@ export default class RoomDirectory extends React.Component { } const explanation = _t("If you can't find the room you're looking for, ask for an invite or Create a new room.", null, - {a: sub => { - return ({sub}); - }}, + {a: sub => ( + + { sub } + + )}, ); const title = this.state.selectedCommunityId @@ -756,6 +785,6 @@ export default class RoomDirectory extends React.Component { // Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom // but works with the objects we get from the public room list -function get_display_alias_for_room(room) { - return room.canonical_alias || (room.aliases ? room.aliases[0] : ""); +function getDisplayAliasForRoom(room: IRoom) { + return room.canonical_alias || room.aliases?.[0] || ""; } diff --git a/src/components/views/directory/NetworkDropdown.js b/src/components/views/directory/NetworkDropdown.tsx similarity index 81% rename from src/components/views/directory/NetworkDropdown.js rename to src/components/views/directory/NetworkDropdown.tsx index e6ff6d9791..66b7321ce0 100644 --- a/src/components/views/directory/NetworkDropdown.js +++ b/src/components/views/directory/NetworkDropdown.tsx @@ -1,7 +1,6 @@ /* -Copyright 2016 OpenMarket Ltd Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2016, 2020 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,39 +15,42 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {useEffect, useState} from 'react'; -import PropTypes from 'prop-types'; +import React, { useEffect, useState } from "react"; +import { MatrixError } from "matrix-js-sdk/src/http-api"; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; -import {instanceForInstanceId} from '../../../utils/DirectoryUtils'; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; +import { instanceForInstanceId } from '../../../utils/DirectoryUtils'; import { + ChevronFace, ContextMenu, - useContextMenu, ContextMenuButton, - MenuItemRadio, - MenuItem, MenuGroup, + MenuItem, + MenuItemRadio, + useContextMenu, } from "../../structures/ContextMenu"; -import {_t} from "../../../languageHandler"; +import { _t } from "../../../languageHandler"; import SdkConfig from "../../../SdkConfig"; -import {useSettingValue} from "../../../hooks/useSettings"; -import * as sdk from "../../../index"; +import { useSettingValue } from "../../../hooks/useSettings"; import Modal from "../../../Modal"; import SettingsStore from "../../../settings/SettingsStore"; import withValidation from "../elements/Validation"; +import { SettingLevel } from "../../../settings/SettingLevel"; +import TextInputDialog from "../dialogs/TextInputDialog"; +import QuestionDialog from "../dialogs/QuestionDialog"; export const ALL_ROOMS = Symbol("ALL_ROOMS"); const SETTING_NAME = "room_directory_servers"; -const inPlaceOf = (elementRect) => ({ +const inPlaceOf = (elementRect: Pick) => ({ right: window.innerWidth - elementRect.right, top: elementRect.top, chevronOffset: 0, - chevronFace: "none", + chevronFace: ChevronFace.None, }); -const validServer = withValidation({ +const validServer = withValidation({ deriveData: async ({ value }) => { try { // check if we can successfully load this server's room directory @@ -78,15 +80,49 @@ const validServer = withValidation({ ], }); +/* eslint-disable camelcase */ +export interface IFieldType { + regexp: string; + placeholder: string; +} + +export interface IInstance { + desc: string; + icon?: string; + fields: object; + network_id: string; + // XXX: this is undocumented but we rely on it. + // we inject a fake entry with a symbolic instance_id. + instance_id: string | symbol; +} + +export interface IProtocol { + user_fields: string[]; + location_fields: string[]; + icon: string; + field_types: Record; + instances: IInstance[]; +} +/* eslint-enable camelcase */ + +export type Protocols = Record; + +interface IProps { + protocols: Protocols; + selectedServerName: string; + selectedInstanceId: string | symbol; + onOptionChange(server: string, instanceId?: string | symbol): void; +} + // This dropdown sources homeservers from three places: // + your currently connected homeserver // + homeservers in config.json["roomDirectory"] // + homeservers in SettingsStore["room_directory_servers"] // if a server exists in multiple, only keep the top-most entry. -const NetworkDropdown = ({onOptionChange, protocols = {}, selectedServerName, selectedInstanceId}) => { - const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu(); - const _userDefinedServers = useSettingValue(SETTING_NAME); +const NetworkDropdown = ({ onOptionChange, protocols = {}, selectedServerName, selectedInstanceId }: IProps) => { + const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu(); + const _userDefinedServers: string[] = useSettingValue(SETTING_NAME); const [userDefinedServers, _setUserDefinedServers] = useState(_userDefinedServers); const handlerFactory = (server, instanceId) => { @@ -98,7 +134,7 @@ const NetworkDropdown = ({onOptionChange, protocols = {}, selectedServerName, se const setUserDefinedServers = servers => { _setUserDefinedServers(servers); - SettingsStore.setValue(SETTING_NAME, null, "account", servers); + SettingsStore.setValue(SETTING_NAME, null, SettingLevel.ACCOUNT, servers); }; // keep local echo up to date with external changes useEffect(() => { @@ -112,7 +148,7 @@ const NetworkDropdown = ({onOptionChange, protocols = {}, selectedServerName, se const roomDirectory = config.roomDirectory || {}; const hsName = MatrixClientPeg.getHomeserverName(); - const configServers = new Set(roomDirectory.servers); + const configServers = new Set(roomDirectory.servers); // configured servers take preference over user-defined ones, if one occurs in both ignore the latter one. const removableServers = new Set(userDefinedServers.filter(s => !configServers.has(s) && s !== hsName)); @@ -136,9 +172,15 @@ const NetworkDropdown = ({onOptionChange, protocols = {}, selectedServerName, se // add a fake protocol with the ALL_ROOMS symbol protocolsList.push({ instances: [{ + fields: [], + network_id: "", instance_id: ALL_ROOMS, desc: _t("All rooms"), }], + location_fields: [], + user_fields: [], + field_types: {}, + icon: "", }); } @@ -172,7 +214,6 @@ const NetworkDropdown = ({onOptionChange, protocols = {}, selectedServerName, se if (removableServers.has(server)) { const onClick = async () => { closeMenu(); - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const {finished} = Modal.createTrackedDialog("Network Dropdown", "Remove server", QuestionDialog, { title: _t("Are you sure?"), description: _t("Are you sure you want to remove %(serverName)s", { @@ -191,7 +232,7 @@ const NetworkDropdown = ({onOptionChange, protocols = {}, selectedServerName, se setUserDefinedServers(servers.filter(s => s !== server)); // the selected server is being removed, reset to our HS - if (serverSelected === server) { + if (serverSelected) { onOptionChange(hsName, undefined); } }; @@ -223,7 +264,6 @@ const NetworkDropdown = ({onOptionChange, protocols = {}, selectedServerName, se const onClick = async () => { closeMenu(); - const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog"); const { finished } = Modal.createTrackedDialog("Network Dropdown", "Add a new server", TextInputDialog, { title: _t("Add a new server"), description: _t("Enter the name of a new server you want to explore."), @@ -284,9 +324,4 @@ const NetworkDropdown = ({onOptionChange, protocols = {}, selectedServerName, se
; }; -NetworkDropdown.propTypes = { - onOptionChange: PropTypes.func.isRequired, - protocols: PropTypes.object, -}; - export default NetworkDropdown; From b3aade075d12b28bd0ff53e12967398c06d05f34 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 19:18:28 +0100 Subject: [PATCH 084/174] Convert CreateRoomDialog to Typescript --- ...eateRoomDialog.js => CreateRoomDialog.tsx} | 174 ++++++++++-------- src/createRoom.ts | 2 +- 2 files changed, 102 insertions(+), 74 deletions(-) rename src/components/views/dialogs/{CreateRoomDialog.js => CreateRoomDialog.tsx} (61%) diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.tsx similarity index 61% rename from src/components/views/dialogs/CreateRoomDialog.js rename to src/components/views/dialogs/CreateRoomDialog.tsx index e9dc6e2be0..2d433f6b51 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -1,6 +1,6 @@ /* Copyright 2017 Michael Telatynski <7t3chguy@gmail.com> -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. @@ -15,27 +15,45 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; -import PropTypes from 'prop-types'; +import React, {ChangeEvent, createRef, KeyboardEvent, SyntheticEvent} from "react"; import {Room} from "matrix-js-sdk/src/models/room"; -import * as sdk from '../../../index'; import SdkConfig from '../../../SdkConfig'; -import withValidation from '../elements/Validation'; -import { _t } from '../../../languageHandler'; +import withValidation, {IFieldState} from '../elements/Validation'; +import {_t} from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {Key} from "../../../Keyboard"; -import {privateShouldBeEncrypted} from "../../../createRoom"; +import {IOpts, Preset, privateShouldBeEncrypted, Visibility} from "../../../createRoom"; import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore"; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import Field from "../elements/Field"; +import RoomAliasField from "../elements/RoomAliasField"; +import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; +import DialogButtons from "../elements/DialogButtons"; +import BaseDialog from "../dialogs/BaseDialog"; + +interface IProps { + defaultPublic?: boolean; + parentSpace?: Room; + onFinished(proceed: boolean, opts?: IOpts): void; +} + +interface IState { + isPublic: boolean; + isEncrypted: boolean; + name: string; + topic: string; + alias: string; + detailsOpen: boolean; + noFederate: boolean; + nameIsValid: boolean; + canChangeEncryption: boolean; +} @replaceableComponent("views.dialogs.CreateRoomDialog") -export default class CreateRoomDialog extends React.Component { - static propTypes = { - onFinished: PropTypes.func.isRequired, - defaultPublic: PropTypes.bool, - parentSpace: PropTypes.instanceOf(Room), - }; +export default class CreateRoomDialog extends React.Component { + private nameField = createRef(); + private aliasField = createRef(); constructor(props) { super(props); @@ -54,26 +72,25 @@ export default class CreateRoomDialog extends React.Component { }; MatrixClientPeg.get().doesServerForceEncryptionForPreset("private") - .then(isForced => this.setState({canChangeEncryption: !isForced})); + .then(isForced => this.setState({ canChangeEncryption: !isForced })); } - _roomCreateOptions() { - const opts = {}; - const createOpts = opts.createOpts = {}; + private roomCreateOptions() { + const opts: IOpts = {}; + const createOpts: IOpts["createOpts"] = opts.createOpts = {}; createOpts.name = this.state.name; if (this.state.isPublic) { - createOpts.visibility = "public"; - createOpts.preset = "public_chat"; + createOpts.visibility = Visibility.Public; + createOpts.preset = Preset.PublicChat; opts.guestAccess = false; - const {alias} = this.state; - const localPart = alias.substr(1, alias.indexOf(":") - 1); - createOpts['room_alias_name'] = localPart; + const { alias } = this.state; + createOpts.room_alias_name = alias.substr(1, alias.indexOf(":") - 1); } if (this.state.topic) { createOpts.topic = this.state.topic; } if (this.state.noFederate) { - createOpts.creation_content = {'m.federate': false}; + createOpts.creation_content = { 'm.federate': false }; } if (!this.state.isPublic) { @@ -98,16 +115,14 @@ export default class CreateRoomDialog extends React.Component { } componentDidMount() { - this._detailsRef.addEventListener("toggle", this.onDetailsToggled); // move focus to first field when showing dialog - this._nameFieldRef.focus(); + this.nameField.current.focus(); } componentWillUnmount() { - this._detailsRef.removeEventListener("toggle", this.onDetailsToggled); } - _onKeyDown = event => { + private onKeyDown = (event: KeyboardEvent) => { if (event.key === Key.ENTER) { this.onOk(); event.preventDefault(); @@ -115,26 +130,26 @@ export default class CreateRoomDialog extends React.Component { } }; - onOk = async () => { - const activeElement = document.activeElement; + private onOk = async () => { + const activeElement = document.activeElement as HTMLElement; if (activeElement) { activeElement.blur(); } - await this._nameFieldRef.validate({allowEmpty: false}); - if (this._aliasFieldRef) { - await this._aliasFieldRef.validate({allowEmpty: false}); + await this.nameField.current.validate({allowEmpty: false}); + if (this.aliasField.current) { + await this.aliasField.current.validate({allowEmpty: false}); } // Validation and state updates are async, so we need to wait for them to complete // first. Queue a `setState` callback and wait for it to resolve. - await new Promise(resolve => this.setState({}, resolve)); - if (this.state.nameIsValid && (!this._aliasFieldRef || this._aliasFieldRef.isValid)) { - this.props.onFinished(true, this._roomCreateOptions()); + await new Promise(resolve => this.setState({}, resolve)); + if (this.state.nameIsValid && (!this.aliasField.current || this.aliasField.current.isValid)) { + this.props.onFinished(true, this.roomCreateOptions()); } else { let field; if (!this.state.nameIsValid) { - field = this._nameFieldRef; - } else if (this._aliasFieldRef && !this._aliasFieldRef.isValid) { - field = this._aliasFieldRef; + field = this.nameField.current; + } else if (this.aliasField.current && !this.aliasField.current.isValid) { + field = this.aliasField.current; } if (field) { field.focus(); @@ -143,49 +158,45 @@ export default class CreateRoomDialog extends React.Component { } }; - onCancel = () => { + private onCancel = () => { this.props.onFinished(false); }; - onNameChange = ev => { - this.setState({name: ev.target.value}); + private onNameChange = (ev: ChangeEvent) => { + this.setState({ name: ev.target.value }); }; - onTopicChange = ev => { - this.setState({topic: ev.target.value}); + private onTopicChange = (ev: ChangeEvent) => { + this.setState({ topic: ev.target.value }); }; - onPublicChange = isPublic => { - this.setState({isPublic}); + private onPublicChange = (isPublic: boolean) => { + this.setState({ isPublic }); }; - onEncryptedChange = isEncrypted => { - this.setState({isEncrypted}); + private onEncryptedChange = (isEncrypted: boolean) => { + this.setState({ isEncrypted }); }; - onAliasChange = alias => { - this.setState({alias}); + private onAliasChange = (alias: string) => { + this.setState({ alias }); }; - onDetailsToggled = ev => { - this.setState({detailsOpen: ev.target.open}); + private onDetailsToggled = (ev: SyntheticEvent) => { + this.setState({ detailsOpen: (ev.target as HTMLDetailsElement).open }); }; - onNoFederateChange = noFederate => { - this.setState({noFederate}); + private onNoFederateChange = (noFederate: boolean) => { + this.setState({ noFederate }); }; - collectDetailsRef = ref => { - this._detailsRef = ref; - }; - - onNameValidate = async fieldState => { - const result = await CreateRoomDialog._validateRoomName(fieldState); + private onNameValidate = async (fieldState: IFieldState) => { + const result = await CreateRoomDialog.validateRoomName(fieldState); this.setState({nameIsValid: result.valid}); return result; }; - static _validateRoomName = withValidation({ + private static validateRoomName = withValidation({ rules: [ { key: "required", @@ -196,18 +207,17 @@ export default class CreateRoomDialog extends React.Component { }); render() { - const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - const Field = sdk.getComponent('views.elements.Field'); - const LabelledToggleSwitch = sdk.getComponent('views.elements.LabelledToggleSwitch'); - const RoomAliasField = sdk.getComponent('views.elements.RoomAliasField'); - let aliasField; if (this.state.isPublic) { const domain = MatrixClientPeg.get().getDomain(); aliasField = (
- this._aliasFieldRef = ref} onChange={this.onAliasChange} domain={domain} value={this.state.alias} /> +
); } @@ -270,16 +280,34 @@ export default class CreateRoomDialog extends React.Component { -
+
- this._nameFieldRef = ref} label={ _t('Name') } onChange={this.onNameChange} onValidate={this.onNameValidate} value={this.state.name} className="mx_CreateRoomDialog_name" /> - - + + + { publicPrivateLabel } { e2eeSection } { aliasField } -
- { this.state.detailsOpen ? _t('Hide advanced') : _t('Show advanced') } +
+ + { this.state.detailsOpen ? _t('Hide advanced') : _t('Show advanced') } + Date: Wed, 19 May 2021 19:20:58 +0100 Subject: [PATCH 085/174] Pre-populate create room dialog name when going from room directory --- src/components/structures/MatrixChat.tsx | 9 ++++++--- src/components/structures/RoomDirectory.tsx | 1 + src/components/views/dialogs/CreateRoomDialog.tsx | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 4c7fca2fec..49386c5f65 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -665,7 +665,7 @@ export default class MatrixChat extends React.PureComponent { break; } case 'view_create_room': - this.createRoom(payload.public); + this.createRoom(payload.public, payload.defaultName); break; case 'view_create_group': { let CreateGroupDialog = sdk.getComponent("dialogs.CreateGroupDialog") @@ -1011,7 +1011,7 @@ export default class MatrixChat extends React.PureComponent { }); } - private async createRoom(defaultPublic = false) { + private async createRoom(defaultPublic = false, defaultName?: string) { const communityId = CommunityPrototypeStore.instance.getSelectedCommunityId(); if (communityId) { // double check the user will have permission to associate this room with the community @@ -1025,7 +1025,10 @@ export default class MatrixChat extends React.PureComponent { } const CreateRoomDialog = sdk.getComponent('dialogs.CreateRoomDialog'); - const modal = Modal.createTrackedDialog('Create Room', '', CreateRoomDialog, { defaultPublic }); + const modal = Modal.createTrackedDialog('Create Room', '', CreateRoomDialog, { + defaultPublic, + defaultName, + }); const [shouldCreate, opts] = await modal.finished; if (shouldCreate) { diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx index 2dc9bf2b9f..f9021b2f5e 100644 --- a/src/components/structures/RoomDirectory.tsx +++ b/src/components/structures/RoomDirectory.tsx @@ -462,6 +462,7 @@ export default class RoomDirectory extends React.Component { dis.dispatch({ action: 'view_create_room', public: true, + defaultName: this.state.filterString.trim(), }); }; diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index 2d433f6b51..cce6b6c34c 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -34,6 +34,7 @@ import BaseDialog from "../dialogs/BaseDialog"; interface IProps { defaultPublic?: boolean; + defaultName?: string; parentSpace?: Room; onFinished(proceed: boolean, opts?: IOpts): void; } @@ -62,7 +63,7 @@ export default class CreateRoomDialog extends React.Component { this.state = { isPublic: this.props.defaultPublic || false, isEncrypted: privateShouldBeEncrypted(), - name: "", + name: this.props.defaultName || "", topic: "", alias: "", detailsOpen: false, From a61e977b5fa7bf129b1f45e1f6aa205e7ddcce4b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 19:30:56 +0100 Subject: [PATCH 086/174] Fix room directory ts migration --- src/components/structures/RoomDirectory.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx index f9021b2f5e..b5e16b8804 100644 --- a/src/components/structures/RoomDirectory.tsx +++ b/src/components/structures/RoomDirectory.tsx @@ -106,19 +106,21 @@ export default class RoomDirectory extends React.Component { CountlyAnalytics.instance.trackRoomDirectoryBegin(); this.startTime = CountlyAnalytics.getTimestamp(); - const selectedCommunityId = GroupFilterOrderStore.getSelectedTags()[0]; + const selectedCommunityId = SettingsStore.getValue("feature_communities_v2_prototypes") + ? GroupFilterOrderStore.getSelectedTags()[0] + : null; let protocolsLoading = true; if (!MatrixClientPeg.get()) { // We may not have a client yet when invoked from welcome page protocolsLoading = false; - } else if (!this.state.selectedCommunityId) { + } else if (!selectedCommunityId) { MatrixClientPeg.get().getThirdpartyProtocols().then((response) => { this.protocols = response; - this.setState({protocolsLoading: false}); + this.setState({ protocolsLoading: false }); }, (err) => { console.warn(`error loading third party protocols: ${err}`); - this.setState({protocolsLoading: false}); + this.setState({ protocolsLoading: false }); if (MatrixClientPeg.get().isGuest()) { // Guests currently aren't allowed to use this API, so // ignore this as otherwise this error is literally the @@ -131,7 +133,7 @@ export default class RoomDirectory extends React.Component { error: _t( '%(brand)s failed to get the protocol list from the homeserver. ' + 'The homeserver may be too old to support third party networks.', - {brand}, + { brand }, ), }); }); @@ -141,7 +143,7 @@ export default class RoomDirectory extends React.Component { // Grab the profile info async FlairStore.getGroupProfileCached(MatrixClientPeg.get(), this.state.selectedCommunityId).then(profile => { - this.setState({communityName: profile.name}); + this.setState({ communityName: profile.name }); }); } @@ -152,9 +154,7 @@ export default class RoomDirectory extends React.Component { instanceId: undefined, roomServer: MatrixClientPeg.getHomeserverName(), filterString: this.props.initialText || "", - selectedCommunityId: SettingsStore.getValue("feature_communities_v2_prototypes") - ? selectedCommunityId - : null, + selectedCommunityId, communityName: null, protocolsLoading, }; From aa7ccc1420a0db1723cd81d21da9534465fb77b5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 20:07:54 +0100 Subject: [PATCH 087/174] Show prompt to create new room from room directory results --- res/css/structures/_RoomDirectory.scss | 38 ++++++++++++++++++--- src/components/structures/RoomDirectory.tsx | 25 ++++++++++++-- src/i18n/strings/en_EN.json | 2 ++ 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/res/css/structures/_RoomDirectory.scss b/res/css/structures/_RoomDirectory.scss index 89cb21b7a6..ec07500af5 100644 --- a/res/css/structures/_RoomDirectory.scss +++ b/res/css/structures/_RoomDirectory.scss @@ -61,6 +61,39 @@ limitations under the License. .mx_RoomDirectory_tableWrapper { overflow-y: auto; flex: 1 1 0; + + .mx_RoomDirectory_footer { + margin-top: 24px; + text-align: center; + + > h5 { + margin: 0; + font-weight: $font-semi-bold; + font-size: $font-15px; + line-height: $font-18px; + color: $primary-fg-color; + } + + > p { + margin: 40px auto 60px; + font-size: $font-14px; + line-height: $font-20px; + color: $secondary-fg-color; + max-width: 464px; // easier reading + } + + > hr { + margin: 0; + border: none; + height: 1px; + background-color: $header-panel-bg-color; + } + + .mx_RoomDirectory_newRoom { + margin: 24px auto 0; + width: max-content; + } + } } .mx_RoomDirectory_table { @@ -138,11 +171,6 @@ limitations under the License. color: $settings-grey-fg-color; } -.mx_RoomDirectory_table tr { - padding-bottom: 10px; - cursor: pointer; -} - .mx_RoomDirectory .mx_RoomView_MessageList { padding: 0; } diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx index b5e16b8804..04fad7d3fa 100644 --- a/src/components/structures/RoomDirectory.tsx +++ b/src/components/structures/RoomDirectory.tsx @@ -672,22 +672,43 @@ export default class RoomDirectory extends React.Component { spinner = ; } + const createNewButton = <> +
+ + { _t("Create new room") } + + ; + let scrollPanelContent; + let footer; if (cells.length === 0 && !this.state.loading) { - scrollPanelContent = { _t('No rooms to show') }; + footer = <> +
{ _t('No results for "%(query)s"', { query: this.state.filterString.trim() }) }
+

+ { _t("Try different words or check for typos. " + + "Some results may not be visible as they're private and you need an invite to see them.") } +

+ { createNewButton } + ; } else { scrollPanelContent =
{ cells }
; + if (!this.state.loading && !this.nextBatch) { + footer = createNewButton; + } } content = { scrollPanelContent } { spinner } + { footer &&
+ { footer } +
}
; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d1fe791319..7d12a1b827 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2646,6 +2646,8 @@ "Unable to look up room ID from server": "Unable to look up room ID from server", "Preview": "Preview", "View": "View", + "No results for \"%(query)s\"": "No results for \"%(query)s\"", + "Try different words or check for typos. Some results may not be visible as they're private and you need an invite to see them.": "Try different words or check for typos. Some results may not be visible as they're private and you need an invite to see them.", "Find a room…": "Find a room…", "Find a room… (e.g. %(exampleRoom)s)": "Find a room… (e.g. %(exampleRoom)s)", "If you can't find the room you're looking for, ask for an invite or Create a new room.": "If you can't find the room you're looking for, ask for an invite or Create a new room.", From 49c853a304b32ced6aa7bc5e74e07e23aa0d8dca Mon Sep 17 00:00:00 2001 From: Germain Date: Wed, 19 May 2021 11:27:32 +0100 Subject: [PATCH 088/174] Delete RoomView dead code --- src/components/structures/RoomView.tsx | 45 -------------------------- 1 file changed, 45 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index dbfba13297..d822b6a839 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1598,33 +1598,6 @@ export default class RoomView extends React.Component { this.setState({auxPanelMaxHeight: auxPanelMaxHeight}); }; - private onFullscreenClick = () => { - dis.dispatch({ - action: 'video_fullscreen', - fullscreen: true, - }, true); - }; - - private onMuteAudioClick = () => { - const call = this.getCallForRoom(); - if (!call) { - return; - } - const newState = !call.isMicrophoneMuted(); - call.setMicrophoneMuted(newState); - this.forceUpdate(); // TODO: just update the voip buttons - }; - - private onMuteVideoClick = () => { - const call = this.getCallForRoom(); - if (!call) { - return; - } - const newState = !call.isLocalVideoMuted(); - call.setLocalVideoMuted(newState); - this.forceUpdate(); // TODO: just update the voip buttons - }; - private onStatusBarVisible = () => { if (this.unmounted) return; this.setState({ @@ -1640,24 +1613,6 @@ export default class RoomView extends React.Component { }); }; - /** - * called by the parent component when PageUp/Down/etc is pressed. - * - * We pass it down to the scroll panel. - */ - private handleScrollKey = ev => { - let panel; - if (this.searchResultsPanel.current) { - panel = this.searchResultsPanel.current; - } else if (this.messagePanel) { - panel = this.messagePanel; - } - - if (panel) { - panel.handleScrollKey(ev); - } - }; - /** * get any current call for this room */ From 66ffaf945ffc67c6a18f4c111cb07465fca21ca8 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 20 May 2021 10:43:57 +0100 Subject: [PATCH 089/174] Cache normalized room name --- .../room-list/filters/NameFilterCondition.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/stores/room-list/filters/NameFilterCondition.ts b/src/stores/room-list/filters/NameFilterCondition.ts index 8e63c23131..2a652a091f 100644 --- a/src/stores/room-list/filters/NameFilterCondition.ts +++ b/src/stores/room-list/filters/NameFilterCondition.ts @@ -17,7 +17,7 @@ limitations under the License. import { Room } from "matrix-js-sdk/src/models/room"; import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition"; import { EventEmitter } from "events"; -import { removeHiddenChars } from "matrix-js-sdk/src/utils"; +import { normalize } from "matrix-js-sdk/src/utils"; import { throttle } from "lodash"; /** @@ -65,17 +65,7 @@ export class NameFilterCondition extends EventEmitter implements IFilterConditio return this.matches(room.name); } - private normalize(val: string): string { - // Note: we have to match the filter with the removeHiddenChars() room name because the - // function strips spaces and other characters (M becomes RN for example, in lowercase). - return removeHiddenChars(val.toLowerCase()) - // Strip all punctuation - .replace(/[\\'!"#$%&()*+,\-./:;<=>?@[\]^_`{|}~\u2000-\u206f\u2e00-\u2e7f]/g, "") - // We also doubly convert to lowercase to work around oddities of the library. - .toLowerCase(); - } - - public matches(val: string): boolean { - return this.normalize(val).includes(this.normalize(this.search)); + public matches(normalizedName: string): boolean { + return normalizedName.includes(normalize(this.search)); } } From 83e2461155ccf9a197c6a4553d7920184884dab1 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 20 May 2021 10:57:20 +0100 Subject: [PATCH 090/174] call matches with normalized name --- src/stores/room-list/filters/NameFilterCondition.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/room-list/filters/NameFilterCondition.ts b/src/stores/room-list/filters/NameFilterCondition.ts index 2a652a091f..7ec91a3249 100644 --- a/src/stores/room-list/filters/NameFilterCondition.ts +++ b/src/stores/room-list/filters/NameFilterCondition.ts @@ -62,7 +62,7 @@ export class NameFilterCondition extends EventEmitter implements IFilterConditio if (!room.name) return false; // should realistically not happen: the js-sdk always calculates a name - return this.matches(room.name); + return this.matches(room.normalizedName); } public matches(normalizedName: string): boolean { From 422740f13b3da84164739a9700efc48854f49c17 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 20 May 2021 11:04:17 +0100 Subject: [PATCH 091/174] normalize displayName --- src/components/views/rooms/RoomSublist.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx index dd80e9d40a..f9881d33ae 100644 --- a/src/components/views/rooms/RoomSublist.tsx +++ b/src/components/views/rooms/RoomSublist.tsx @@ -18,6 +18,7 @@ limitations under the License. import * as React from "react"; import { createRef, ReactComponentElement } from "react"; +import { normalize } from "matrix-js-sdk/src/utils"; import { Room } from "matrix-js-sdk/src/models/room"; import classNames from 'classnames'; import { RovingAccessibleButton, RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; @@ -259,7 +260,7 @@ export default class RoomSublist extends React.Component { const nameCondition = RoomListStore.instance.getFirstNameFilterCondition(); if (nameCondition) { stateUpdates.filteredExtraTiles = this.props.extraTiles - .filter(t => nameCondition.matches(t.props.displayName || "")); + .filter(t => nameCondition.matches(normalize(t.props.displayName || ""))); } else if (this.state.filteredExtraTiles) { stateUpdates.filteredExtraTiles = null; } From dab75f9b88fbf05edefd1f83efc8ced8596bc628 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 20 May 2021 12:20:53 +0100 Subject: [PATCH 092/174] Fix add reaction prompt showing even when user is not joined to room --- src/components/views/messages/ReactionsRow.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/ReactionsRow.tsx b/src/components/views/messages/ReactionsRow.tsx index 8809302088..f5910473ff 100644 --- a/src/components/views/messages/ReactionsRow.tsx +++ b/src/components/views/messages/ReactionsRow.tsx @@ -198,7 +198,8 @@ export default class ReactionsRow extends React.PureComponent { const cli = this.context; let addReactionButton; - if (cli.getRoom(mxEvent.getRoomId()).currentState.maySendEvent(EventType.Reaction, cli.getUserId())) { + const room = cli.getRoom(mxEvent.getRoomId()); + if (room.getMyMembership() === "join" && room.currentState.maySendEvent(EventType.Reaction, cli.getUserId())) { addReactionButton = ; } From bee1a88f0b1dae37a177461dddfc0f0769836c52 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 20 May 2021 13:34:31 +0100 Subject: [PATCH 093/174] Reduce noise in tests This disables a common log message to cut down the test log size and make it easier to read messages specific to each test. --- src/languageHandler.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 4a3fb30886..26c89afec6 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -344,7 +344,10 @@ export function setLanguage(preferredLangs: string | string[]) { counterpart.registerTranslations(langToUse, langData); counterpart.setLocale(langToUse); SettingsStore.setValue("language", null, SettingLevel.DEVICE, langToUse); - console.log("set language to " + langToUse); + // Adds a lot of noise to test runs, so disable logging there. + if (process.env.NODE_ENV !== "test") { + console.log("set language to " + langToUse); + } // Set 'en' as fallback language: if (langToUse !== "en") { From 018a75ea1e75a791585f5f0b3ce1fe9b26d274de Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 20 May 2021 09:10:21 -0600 Subject: [PATCH 094/174] Upgrade showChatEffects to room-level setting exposure --- src/settings/Settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 577f23fa3a..37f493f427 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -730,7 +730,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: Layout.Group, }, "showChatEffects": { - supportedLevels: LEVELS_ACCOUNT_SETTINGS, + supportedLevels: LEVELS_ROOM_SETTINGS_WITH_ROOM, displayName: _td("Show chat effects (animations when receiving e.g. confetti)"), default: true, controller: new ReducedMotionController(), From 21c1179f8d2726a909c5540f8e25a92fa0070dc3 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 20 May 2021 17:54:42 +0100 Subject: [PATCH 095/174] Update extensions for more files with types This migrates the another bucket of files using some amount of Flow typing to mark them as TypeScript instead. The remaining type errors are fixed in subsequent commits. --- ...eAuthEntryComponents.js => InteractiveAuthEntryComponents.tsx} | 0 .../views/dialogs/{DevtoolsDialog.js => DevtoolsDialog.tsx} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/components/views/auth/{InteractiveAuthEntryComponents.js => InteractiveAuthEntryComponents.tsx} (100%) rename src/components/views/dialogs/{DevtoolsDialog.js => DevtoolsDialog.tsx} (100%) diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.tsx similarity index 100% rename from src/components/views/auth/InteractiveAuthEntryComponents.js rename to src/components/views/auth/InteractiveAuthEntryComponents.tsx diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.tsx similarity index 100% rename from src/components/views/dialogs/DevtoolsDialog.js rename to src/components/views/dialogs/DevtoolsDialog.tsx From 6574ca98fa098e3690391cfc0152ccaca2ea4cfd Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 12 May 2021 14:06:10 +0100 Subject: [PATCH 096/174] Fix basic lint errors --- .../auth/InteractiveAuthEntryComponents.tsx | 4 +- .../views/dialogs/DevtoolsDialog.tsx | 57 +++++++++++++++---- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx index e34349c474..5a492b14ee 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx +++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx @@ -786,7 +786,9 @@ export class FallbackAuthEntry extends React.Component { } return ( ); diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 8a035263cc..1d544af315 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -169,8 +169,16 @@ export class SendCustomEvent extends GenericEditor { { !this.state.message && } { showTglFlip &&
- -
}
; @@ -253,8 +261,17 @@ class SendAccountData extends GenericEditor { { !this.state.message && } { !this.state.message &&
- -
} ; @@ -581,8 +598,16 @@ class AccountDataExplorer extends React.PureComponent {
{ !this.state.message &&
- -
}
; @@ -1062,27 +1087,37 @@ class SettingsExplorer extends React.Component {
{_t("Value:")}  - {this.renderSettingValue(SettingsStore.getValue(this.state.viewSetting))} + {this.renderSettingValue( + SettingsStore.getValue(this.state.viewSetting), + )}
{_t("Value in this room:")}  - {this.renderSettingValue(SettingsStore.getValue(this.state.viewSetting, room.roomId))} + {this.renderSettingValue( + SettingsStore.getValue(this.state.viewSetting, room.roomId), + )}
{_t("Values at explicit levels:")} -
{this.renderExplicitSettingValues(this.state.viewSetting, null)}
+
{this.renderExplicitSettingValues(
+                                this.state.viewSetting, null,
+                            )}
{_t("Values at explicit levels in this room:")} -
{this.renderExplicitSettingValues(this.state.viewSetting, room.roomId)}
+
{this.renderExplicitSettingValues(
+                                this.state.viewSetting, room.roomId,
+                            )}
- +
From df09bdf823e3c3cc018827c93f95ebf73e58a288 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 12 May 2021 19:28:22 +0100 Subject: [PATCH 097/174] Add types to InteractiveAuthEntryComponents --- src/Terms.ts | 14 +- .../auth/InteractiveAuthEntryComponents.tsx | 371 ++++++++++-------- src/languageHandler.tsx | 8 +- 3 files changed, 213 insertions(+), 180 deletions(-) diff --git a/src/Terms.ts b/src/Terms.ts index 1bdff36cbc..1b1c152fdd 100644 --- a/src/Terms.ts +++ b/src/Terms.ts @@ -36,14 +36,18 @@ export class Service { } } -interface Policy { +export interface LocalisedPolicy { + name: string; + url: string; +} + +export interface Policy { // @ts-ignore: No great way to express indexed types together with other keys version: string; - [lang: string]: { - url: string; - }; + [lang: string]: LocalisedPolicy; } -type Policies = { + +export type Policies = { [policy: string]: Policy, }; diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx index 5a492b14ee..066c064cc1 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx +++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx @@ -1,7 +1,5 @@ /* -Copyright 2016 OpenMarket Ltd -Copyright 2017 Vector Creations Ltd -Copyright 2019, 2020 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,9 +14,9 @@ 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 classnames from 'classnames'; +import React, { ChangeEvent, createRef, FormEvent, MouseEvent } from 'react'; +import classNames from 'classnames'; +import { MatrixClient } from "matrix-js-sdk/src/client"; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; @@ -27,6 +25,7 @@ import AccessibleButton from "../elements/AccessibleButton"; import Spinner from "../elements/Spinner"; import CountlyAnalytics from "../../../CountlyAnalytics"; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { LocalisedPolicy, Policies } from '../../../Terms'; /* This file contains a collection of components which are used by the * InteractiveAuth to prompt the user to enter the information needed @@ -74,21 +73,49 @@ import {replaceableComponent} from "../../../utils/replaceableComponent"; * focus: set the input focus appropriately in the form. */ +enum AuthType { + Password = "m.login.password", + Recaptcha = "m.login.recaptcha", + Terms = "m.login.terms", + Email = "m.login.email.identity", + Msisdn = "m.login.msisdn", + Sso = "m.login.sso", + SsoUnstable = "org.matrix.login.sso", +} + +/* eslint-disable camelcase */ +interface IAuthDict { + type?: AuthType; + // TODO: Remove `user` once servers support proper UIA + // See https://github.com/vector-im/element-web/issues/10312 + user?: string; + identifier?: object; + password?: string; + response?: string; + // TODO: Remove `threepid_creds` once servers support proper UIA + // See https://github.com/vector-im/element-web/issues/10312 + // See https://github.com/matrix-org/matrix-doc/issues/2220 + threepid_creds?: object; + threepidCreds?: object; +} +/* eslint-enable camelcase */ + export const DEFAULT_PHASE = 0; -@replaceableComponent("views.auth.PasswordAuthEntry") -export class PasswordAuthEntry extends React.Component { - static LOGIN_TYPE = "m.login.password"; +interface IAuthEntryProps { + matrixClient: MatrixClient; + loginType: string; + authSessionId: string; + submitAuthDict: (auth: IAuthDict) => void; + errorText?: string; + // Is the auth logic currently waiting for something to happen? + busy?: boolean; + onPhaseChange: (phase: number) => void; +} - static propTypes = { - matrixClient: PropTypes.object.isRequired, - submitAuthDict: PropTypes.func.isRequired, - errorText: PropTypes.string, - // is the auth logic currently waiting for something to - // happen? - busy: PropTypes.bool, - onPhaseChange: PropTypes.func.isRequired, - }; +@replaceableComponent("views.auth.PasswordAuthEntry") +export class PasswordAuthEntry extends React.Component { + static LOGIN_TYPE = AuthType.Password; componentDidMount() { this.props.onPhaseChange(DEFAULT_PHASE); @@ -98,12 +125,12 @@ export class PasswordAuthEntry extends React.Component { password: "", }; - _onSubmit = e => { + private onSubmit = (e: FormEvent) => { e.preventDefault(); if (this.props.busy) return; this.props.submitAuthDict({ - type: PasswordAuthEntry.LOGIN_TYPE, + type: AuthType.Password, // TODO: Remove `user` once servers support proper UIA // See https://github.com/vector-im/element-web/issues/10312 user: this.props.matrixClient.credentials.userId, @@ -115,7 +142,7 @@ export class PasswordAuthEntry extends React.Component { }); }; - _onPasswordFieldChange = ev => { + private onPasswordFieldChange = (ev: ChangeEvent) => { // enable the submit button iff the password is non-empty this.setState({ password: ev.target.value, @@ -123,7 +150,7 @@ export class PasswordAuthEntry extends React.Component { }; render() { - const passwordBoxClass = classnames({ + const passwordBoxClass = classNames({ "error": this.props.errorText, }); @@ -155,7 +182,7 @@ export class PasswordAuthEntry extends React.Component { return (

{ _t("Confirm your identity by entering your account password below.") }

- +
{ submitButtonOrSpinner } @@ -175,26 +202,26 @@ export class PasswordAuthEntry extends React.Component { } } -@replaceableComponent("views.auth.RecaptchaAuthEntry") -export class RecaptchaAuthEntry extends React.Component { - static LOGIN_TYPE = "m.login.recaptcha"; - - static propTypes = { - submitAuthDict: PropTypes.func.isRequired, - stageParams: PropTypes.object.isRequired, - errorText: PropTypes.string, - busy: PropTypes.bool, - onPhaseChange: PropTypes.func.isRequired, +/* eslint-disable camelcase */ +interface IRecaptchaAuthEntryProps extends IAuthEntryProps { + stageParams?: { + public_key?: string; }; +} +/* eslint-enable camelcase */ + +@replaceableComponent("views.auth.RecaptchaAuthEntry") +export class RecaptchaAuthEntry extends React.Component { + static LOGIN_TYPE = AuthType.Recaptcha; componentDidMount() { this.props.onPhaseChange(DEFAULT_PHASE); } - _onCaptchaResponse = response => { + private onCaptchaResponse = (response: string) => { CountlyAnalytics.instance.track("onboarding_grecaptcha_submit"); this.props.submitAuthDict({ - type: RecaptchaAuthEntry.LOGIN_TYPE, + type: AuthType.Recaptcha, response: response, }); }; @@ -230,7 +257,7 @@ export class RecaptchaAuthEntry extends React.Component { return (
{ errorSection }
@@ -238,18 +265,28 @@ export class RecaptchaAuthEntry extends React.Component { } } -@replaceableComponent("views.auth.TermsAuthEntry") -export class TermsAuthEntry extends React.Component { - static LOGIN_TYPE = "m.login.terms"; - - static propTypes = { - submitAuthDict: PropTypes.func.isRequired, - stageParams: PropTypes.object.isRequired, - errorText: PropTypes.string, - busy: PropTypes.bool, - showContinue: PropTypes.bool, - onPhaseChange: PropTypes.func.isRequired, +interface ITermsAuthEntryProps extends IAuthEntryProps { + stageParams?: { + policies?: Policies; }; + showContinue: boolean; +} + +interface LocalisedPolicyWithId extends LocalisedPolicy { + id: string; +} + +interface ITermsAuthEntryState { + policies: LocalisedPolicyWithId[]; + toggledPolicies: { + [policy: string]: boolean; + }; + errorText?: string; +} + +@replaceableComponent("views.auth.TermsAuthEntry") +export class TermsAuthEntry extends React.Component { + static LOGIN_TYPE = AuthType.Terms; constructor(props) { super(props); @@ -294,8 +331,11 @@ export class TermsAuthEntry extends React.Component { initToggles[policyId] = false; - langPolicy.id = policyId; - pickedPolicies.push(langPolicy); + pickedPolicies.push({ + id: policyId, + name: langPolicy.name, + url: langPolicy.url, + }); } this.state = { @@ -312,10 +352,10 @@ export class TermsAuthEntry extends React.Component { } tryContinue = () => { - this._trySubmit(); + this.trySubmit(); }; - _togglePolicy(policyId) { + private togglePolicy(policyId: string) { const newToggles = {}; for (const policy of this.state.policies) { let checked = this.state.toggledPolicies[policy.id]; @@ -326,7 +366,7 @@ export class TermsAuthEntry extends React.Component { this.setState({"toggledPolicies": newToggles}); } - _trySubmit = () => { + private trySubmit = () => { let allChecked = true; for (const policy of this.state.policies) { const checked = this.state.toggledPolicies[policy.id]; @@ -334,7 +374,7 @@ export class TermsAuthEntry extends React.Component { } if (allChecked) { - this.props.submitAuthDict({type: TermsAuthEntry.LOGIN_TYPE}); + this.props.submitAuthDict({type: AuthType.Terms}); CountlyAnalytics.instance.track("onboarding_terms_complete"); } else { this.setState({errorText: _t("Please review and accept all of the homeserver's policies")}); @@ -356,7 +396,7 @@ export class TermsAuthEntry extends React.Component { checkboxes.push( // XXX: replace with StyledCheckbox , ); @@ -375,7 +415,7 @@ export class TermsAuthEntry extends React.Component { if (this.props.showContinue !== false) { // XXX: button classes submitButton = ; + onClick={this.trySubmit} disabled={!allChecked}>{_t("Accept")}; } return ( @@ -389,21 +429,18 @@ export class TermsAuthEntry extends React.Component { } } -@replaceableComponent("views.auth.EmailIdentityAuthEntry") -export class EmailIdentityAuthEntry extends React.Component { - static LOGIN_TYPE = "m.login.email.identity"; - - static propTypes = { - matrixClient: PropTypes.object.isRequired, - submitAuthDict: PropTypes.func.isRequired, - authSessionId: PropTypes.string.isRequired, - clientSecret: PropTypes.string.isRequired, - inputs: PropTypes.object.isRequired, - stageState: PropTypes.object.isRequired, - fail: PropTypes.func.isRequired, - setEmailSid: PropTypes.func.isRequired, - onPhaseChange: PropTypes.func.isRequired, +interface IEmailIdentityAuthEntryProps extends IAuthEntryProps { + inputs?: { + emailAddress?: string; }; + stageState?: { + emailSid: string; + }; +} + +@replaceableComponent("views.auth.EmailIdentityAuthEntry") +export class EmailIdentityAuthEntry extends React.Component { + static LOGIN_TYPE = AuthType.Email; componentDidMount() { this.props.onPhaseChange(DEFAULT_PHASE); @@ -427,7 +464,7 @@ export class EmailIdentityAuthEntry extends React.Component { return (

{ _t("A confirmation email has been sent to %(emailAddress)s", - { emailAddress: (sub) => { this.props.inputs.emailAddress } }, + { emailAddress: { this.props.inputs.emailAddress } }, ) }

{ _t("Open the link in the email to continue registration.") }

@@ -437,37 +474,34 @@ export class EmailIdentityAuthEntry extends React.Component { } } -@replaceableComponent("views.auth.MsisdnAuthEntry") -export class MsisdnAuthEntry extends React.Component { - static LOGIN_TYPE = "m.login.msisdn"; - - static propTypes = { - inputs: PropTypes.shape({ - phoneCountry: PropTypes.string, - phoneNumber: PropTypes.string, - }), - fail: PropTypes.func, - clientSecret: PropTypes.func, - submitAuthDict: PropTypes.func.isRequired, - matrixClient: PropTypes.object, - onPhaseChange: PropTypes.func.isRequired, +interface IMsisdnAuthEntryProps extends IAuthEntryProps { + inputs: { + phoneCountry: string; + phoneNumber: string; }; + clientSecret: string; + fail: (error: Error) => void; +} + +@replaceableComponent("views.auth.MsisdnAuthEntry") +export class MsisdnAuthEntry extends React.Component { + static LOGIN_TYPE = AuthType.Msisdn; + + private submitUrl: string; + private sid: string; + private msisdn: string; state = { token: '', requestingToken: false, + errorText: '', }; componentDidMount() { this.props.onPhaseChange(DEFAULT_PHASE); - this._submitUrl = null; - this._sid = null; - this._msisdn = null; - this._tokenBox = null; - this.setState({requestingToken: true}); - this._requestMsisdnToken().catch((e) => { + this.requestMsisdnToken().catch((e) => { this.props.fail(e); }).finally(() => { this.setState({requestingToken: false}); @@ -477,26 +511,26 @@ export class MsisdnAuthEntry extends React.Component { /* * Requests a verification token by SMS. */ - _requestMsisdnToken() { + private requestMsisdnToken(): Promise { return this.props.matrixClient.requestRegisterMsisdnToken( this.props.inputs.phoneCountry, this.props.inputs.phoneNumber, this.props.clientSecret, 1, // TODO: Multiple send attempts? ).then((result) => { - this._submitUrl = result.submit_url; - this._sid = result.sid; - this._msisdn = result.msisdn; + this.submitUrl = result.submit_url; + this.sid = result.sid; + this.msisdn = result.msisdn; }); } - _onTokenChange = e => { + private onTokenChange = (e: ChangeEvent) => { this.setState({ token: e.target.value, }); }; - _onFormSubmit = async e => { + private onFormSubmit = async (e: FormEvent) => { e.preventDefault(); if (this.state.token == '') return; @@ -506,20 +540,20 @@ export class MsisdnAuthEntry extends React.Component { try { let result; - if (this._submitUrl) { + if (this.submitUrl) { result = await this.props.matrixClient.submitMsisdnTokenOtherUrl( - this._submitUrl, this._sid, this.props.clientSecret, this.state.token, + this.submitUrl, this.sid, this.props.clientSecret, this.state.token, ); } else { throw new Error("The registration with MSISDN flow is misconfigured"); } if (result.success) { const creds = { - sid: this._sid, + sid: this.sid, client_secret: this.props.clientSecret, }; this.props.submitAuthDict({ - type: MsisdnAuthEntry.LOGIN_TYPE, + type: AuthType.Msisdn, // TODO: Remove `threepid_creds` once servers support proper UIA // See https://github.com/vector-im/element-web/issues/10312 // See https://github.com/matrix-org/matrix-doc/issues/2220 @@ -543,7 +577,7 @@ export class MsisdnAuthEntry extends React.Component { return ; } else { const enableSubmit = Boolean(this.state.token); - const submitClasses = classnames({ + const submitClasses = classNames({ mx_InteractiveAuthEntryComponents_msisdnSubmit: true, mx_GeneralButton: true, }); @@ -558,16 +592,16 @@ export class MsisdnAuthEntry extends React.Component { return (

{ _t("A text message has been sent to %(msisdn)s", - { msisdn: { this._msisdn } }, + { msisdn: { this.msisdn } }, ) }

{ _t("Please enter the code it contains:") }

- +
@@ -584,40 +618,40 @@ export class MsisdnAuthEntry extends React.Component { } } -@replaceableComponent("views.auth.SSOAuthEntry") -export class SSOAuthEntry extends React.Component { - static propTypes = { - matrixClient: PropTypes.object.isRequired, - authSessionId: PropTypes.string.isRequired, - loginType: PropTypes.string.isRequired, - submitAuthDict: PropTypes.func.isRequired, - errorText: PropTypes.string, - onPhaseChange: PropTypes.func.isRequired, - continueText: PropTypes.string, - continueKind: PropTypes.string, - onCancel: PropTypes.func, - }; +interface ISSOAuthEntryProps extends IAuthEntryProps { + continueText?: string; + continueKind?: string; + onCancel?: () => void; +} - static LOGIN_TYPE = "m.login.sso"; - static UNSTABLE_LOGIN_TYPE = "org.matrix.login.sso"; +interface ISSOAuthEntryState { + phase: number; + attemptFailed: boolean; +} + +@replaceableComponent("views.auth.SSOAuthEntry") +export class SSOAuthEntry extends React.Component { + static LOGIN_TYPE = AuthType.Sso; + static UNSTABLE_LOGIN_TYPE = AuthType.SsoUnstable; static PHASE_PREAUTH = 1; // button to start SSO static PHASE_POSTAUTH = 2; // button to confirm SSO completed - _ssoUrl: string; + private ssoUrl: string; + private popupWindow: Window; constructor(props) { super(props); // We actually send the user through fallback auth so we don't have to // deal with a redirect back to us, losing application context. - this._ssoUrl = props.matrixClient.getFallbackAuthUrl( + this.ssoUrl = props.matrixClient.getFallbackAuthUrl( this.props.loginType, this.props.authSessionId, ); - this._popupWindow = null; - window.addEventListener("message", this._onReceiveMessage); + this.popupWindow = null; + window.addEventListener("message", this.onReceiveMessage); this.state = { phase: SSOAuthEntry.PHASE_PREAUTH, @@ -625,15 +659,15 @@ export class SSOAuthEntry extends React.Component { }; } - componentDidMount(): void { + componentDidMount() { this.props.onPhaseChange(SSOAuthEntry.PHASE_PREAUTH); } componentWillUnmount() { - window.removeEventListener("message", this._onReceiveMessage); - if (this._popupWindow) { - this._popupWindow.close(); - this._popupWindow = null; + window.removeEventListener("message", this.onReceiveMessage); + if (this.popupWindow) { + this.popupWindow.close(); + this.popupWindow = null; } } @@ -643,11 +677,11 @@ export class SSOAuthEntry extends React.Component { }); }; - _onReceiveMessage = event => { + private onReceiveMessage = (event: MessageEvent) => { if (event.data === "authDone" && event.origin === this.props.matrixClient.getHomeserverUrl()) { - if (this._popupWindow) { - this._popupWindow.close(); - this._popupWindow = null; + if (this.popupWindow) { + this.popupWindow.close(); + this.popupWindow = null; } } }; @@ -657,7 +691,7 @@ export class SSOAuthEntry extends React.Component { // certainly will need to open the thing in a new tab to avoid losing application // context. - this._popupWindow = window.open(this._ssoUrl, "_blank"); + this.popupWindow = window.open(this.ssoUrl, "_blank"); this.setState({phase: SSOAuthEntry.PHASE_POSTAUTH}); this.props.onPhaseChange(SSOAuthEntry.PHASE_POSTAUTH); }; @@ -716,46 +750,37 @@ export class SSOAuthEntry extends React.Component { } @replaceableComponent("views.auth.FallbackAuthEntry") -export class FallbackAuthEntry extends React.Component { - static propTypes = { - matrixClient: PropTypes.object.isRequired, - authSessionId: PropTypes.string.isRequired, - loginType: PropTypes.string.isRequired, - submitAuthDict: PropTypes.func.isRequired, - errorText: PropTypes.string, - onPhaseChange: PropTypes.func.isRequired, - }; +export class FallbackAuthEntry extends React.Component { + private popupWindow: Window; + private fallbackButton = createRef(); constructor(props) { super(props); // we have to make the user click a button, as browsers will block // the popup if we open it immediately. - this._popupWindow = null; - window.addEventListener("message", this._onReceiveMessage); - - this._fallbackButton = createRef(); + this.popupWindow = null; + window.addEventListener("message", this.onReceiveMessage); } - componentDidMount() { this.props.onPhaseChange(DEFAULT_PHASE); } componentWillUnmount() { - window.removeEventListener("message", this._onReceiveMessage); - if (this._popupWindow) { - this._popupWindow.close(); + window.removeEventListener("message", this.onReceiveMessage); + if (this.popupWindow) { + this.popupWindow.close(); } } focus = () => { - if (this._fallbackButton.current) { - this._fallbackButton.current.focus(); + if (this.fallbackButton.current) { + this.fallbackButton.current.focus(); } }; - _onShowFallbackClick = e => { + private onShowFallbackClick = (e: MouseEvent) => { e.preventDefault(); e.stopPropagation(); @@ -763,10 +788,10 @@ export class FallbackAuthEntry extends React.Component { this.props.loginType, this.props.authSessionId, ); - this._popupWindow = window.open(url, "_blank"); + this.popupWindow = window.open(url, "_blank"); }; - _onReceiveMessage = event => { + private onReceiveMessage = (event: MessageEvent) => { if ( event.data === "authDone" && event.origin === this.props.matrixClient.getHomeserverUrl() @@ -786,7 +811,7 @@ export class FallbackAuthEntry extends React.Component { } return (
- { + { _t("Start authentication") } {errorSection} @@ -795,20 +820,22 @@ export class FallbackAuthEntry extends React.Component { } } -const AuthEntryComponents = [ - PasswordAuthEntry, - RecaptchaAuthEntry, - EmailIdentityAuthEntry, - MsisdnAuthEntry, - TermsAuthEntry, - SSOAuthEntry, -]; - -export default function getEntryComponentForLoginType(loginType) { - for (const c of AuthEntryComponents) { - if (c.LOGIN_TYPE === loginType || c.UNSTABLE_LOGIN_TYPE === loginType) { - return c; - } +export default function getEntryComponentForLoginType(loginType: AuthType): typeof React.Component { + switch (loginType) { + case AuthType.Password: + return PasswordAuthEntry; + case AuthType.Recaptcha: + return RecaptchaAuthEntry; + case AuthType.Email: + return EmailIdentityAuthEntry; + case AuthType.Msisdn: + return MsisdnAuthEntry; + case AuthType.Terms: + return TermsAuthEntry; + case AuthType.Sso: + case AuthType.SsoUnstable: + return SSOAuthEntry; + default: + return FallbackAuthEntry; } - return FallbackAuthEntry; } diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 26c89afec6..16950dc008 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -105,12 +105,14 @@ function safeCounterpartTranslate(text: string, options?: object) { return translated; } +type SubstitutionValue = number | string | React.ReactNode | ((sub: string) => React.ReactNode); + export interface IVariables { count?: number; - [key: string]: number | string; + [key: string]: SubstitutionValue; } -type Tags = Record React.ReactNode>; +type Tags = Record; export type TranslatedString = string | React.ReactNode; @@ -247,7 +249,7 @@ export function replaceByRegexes(text: string, mapping: IVariables | Tags): stri let replaced; // If substitution is a function, call it if (mapping[regexpString] instanceof Function) { - replaced = (mapping as Tags)[regexpString].apply(null, capturedGroups); + replaced = ((mapping as Tags)[regexpString] as Function)(...capturedGroups); } else { replaced = mapping[regexpString]; } From d9e490926b5bd4f0601f826a6918c954d41791d8 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 18 May 2021 15:20:08 +0100 Subject: [PATCH 098/174] Add types to DevtoolsDialog --- .../views/dialogs/DevtoolsDialog.tsx | 392 ++++++++++-------- 1 file changed, 221 insertions(+), 171 deletions(-) diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 1d544af315..81d3a77327 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -1,5 +1,6 @@ /* Copyright 2017 Michael Telatynski <7t3chguy@gmail.com> +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. @@ -14,8 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {useState, useEffect} from 'react'; -import PropTypes from 'prop-types'; +import React, {useState, useEffect, ChangeEvent, MouseEvent} from 'react'; import * as sdk from '../../../index'; import SyntaxHighlight from '../elements/SyntaxHighlight'; import { _t } from '../../../languageHandler'; @@ -30,8 +30,9 @@ import { PHASE_DONE, PHASE_STARTED, PHASE_CANCELLED, + VerificationRequest, } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; -import WidgetStore from "../../../stores/WidgetStore"; +import WidgetStore, { IApp } from "../../../stores/WidgetStore"; import {UPDATE_EVENT} from "../../../stores/AsyncStore"; import {SETTINGS} from "../../../settings/Settings"; import SettingsStore, {LEVEL_ORDER} from "../../../settings/SettingsStore"; @@ -40,17 +41,22 @@ import ErrorDialog from "./ErrorDialog"; import {replaceableComponent} from "../../../utils/replaceableComponent"; import {Room} from "matrix-js-sdk/src/models/room"; import {MatrixEvent} from "matrix-js-sdk/src/models/event"; +import { SettingLevel } from '../../../settings/SettingLevel'; -class GenericEditor extends React.PureComponent { - // static propTypes = {onBack: PropTypes.func.isRequired}; +interface IGenericEditorProps { + onBack: () => void; +} - constructor(props) { - super(props); - this._onChange = this._onChange.bind(this); - this.onBack = this.onBack.bind(this); - } +interface IGenericEditorState { + message?: string; + [inputId: string]: boolean | string; +} - onBack() { +abstract class GenericEditor< + P extends IGenericEditorProps = IGenericEditorProps, + S extends IGenericEditorState = IGenericEditorState, +> extends React.PureComponent { + protected onBack = () => { if (this.state.message) { this.setState({ message: null }); } else { @@ -58,47 +64,60 @@ class GenericEditor extends React.PureComponent { } } - _onChange(e) { + protected onChange = (e: ChangeEvent) => { + // @ts-ignore: Unsure how to convince TS this is okay when the state + // type can be extended. this.setState({[e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value}); } - _buttons() { - return
+ protected abstract send(); + + protected buttons(): React.ReactNode { + return
- { !this.state.message && } + { !this.state.message && }
; } - textInput(id, label) { + protected textInput(id: string, label: string): React.ReactNode { return ; } } -export class SendCustomEvent extends GenericEditor { - static getLabel() { return _t('Send Custom Event'); } - - static propTypes = { - onBack: PropTypes.func.isRequired, - room: PropTypes.instanceOf(Room).isRequired, - forceStateEvent: PropTypes.bool, - forceGeneralEvent: PropTypes.bool, - inputs: PropTypes.object, +interface ISendCustomEventProps extends IGenericEditorProps { + room: Room; + forceStateEvent?: boolean; + forceGeneralEvent?: boolean; + inputs?: { + eventType?: string; + stateKey?: string; + evContent?: string; }; +} + +interface ISendCustomEventState extends IGenericEditorState { + isStateEvent: boolean; + eventType: string; + stateKey: string; + evContent: string; +} + +export class SendCustomEvent extends GenericEditor { + static getLabel() { return _t('Send Custom Event'); } static contextType = MatrixClientContext; constructor(props) { super(props); - this._send = this._send.bind(this); const {eventType, stateKey, evContent} = Object.assign({ eventType: '', @@ -115,7 +134,7 @@ export class SendCustomEvent extends GenericEditor { }; } - send(content) { + private doSend(content: object): Promise { const cli = this.context; if (this.state.isStateEvent) { return cli.sendStateEvent(this.props.room.roomId, this.state.eventType, content, this.state.stateKey); @@ -124,7 +143,7 @@ export class SendCustomEvent extends GenericEditor { } } - async _send() { + protected send = async () => { if (this.state.eventType === '') { this.setState({ message: _t('You must specify an event type!') }); return; @@ -133,7 +152,7 @@ export class SendCustomEvent extends GenericEditor { let message; try { const content = JSON.parse(this.state.evContent); - await this.send(content); + await this.doSend(content); message = _t('Event sent!'); } catch (e) { message = _t('Failed to send custom event.') + ' (' + e.toString() + ')'; @@ -147,7 +166,7 @@ export class SendCustomEvent extends GenericEditor {
{ this.state.message }
- { this._buttons() } + { this.buttons() }
; } @@ -163,16 +182,16 @@ export class SendCustomEvent extends GenericEditor {
+ autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
-
+
- { !this.state.message && } + { !this.state.message && } { showTglFlip &&
; } @@ -255,17 +282,17 @@ class SendAccountData extends GenericEditor {
+ autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
-
+
- { !this.state.message && } + { !this.state.message && } { !this.state.message &&
-
+
@@ -482,31 +517,29 @@ class RoomStateExplorer extends React.PureComponent {
{ list }
-
+
; } } -class AccountDataExplorer extends React.PureComponent { - static getLabel() { return _t('Explore Account Data'); } +interface IAccountDataExplorerState { + isRoomAccountData: boolean; + event?: MatrixEvent; + editing: boolean; + queryEventType: string; + [inputId: string]: boolean | string; +} - static propTypes = { - onBack: PropTypes.func.isRequired, - room: PropTypes.instanceOf(Room).isRequired, - }; +class AccountDataExplorer extends React.PureComponent { + static getLabel() { return _t('Explore Account Data'); } static contextType = MatrixClientContext; constructor(props) { super(props); - this.onBack = this.onBack.bind(this); - this.editEv = this.editEv.bind(this); - this._onChange = this._onChange.bind(this); - this.onQueryEventType = this.onQueryEventType.bind(this); - this.state = { isRoomAccountData: false, event: null, @@ -516,20 +549,20 @@ class AccountDataExplorer extends React.PureComponent { }; } - getData() { + private getData(): Record { if (this.state.isRoomAccountData) { return this.props.room.accountData; } return this.context.store.accountData; } - onViewSourceClick(event) { + private onViewSourceClick(event: MatrixEvent) { return () => { this.setState({ event }); }; } - onBack() { + private onBack = () => { if (this.state.editing) { this.setState({ editing: false }); } else if (this.state.event) { @@ -539,15 +572,15 @@ class AccountDataExplorer extends React.PureComponent { } } - _onChange(e) { + private onChange = (e: ChangeEvent) => { this.setState({[e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value}); } - editEv() { + private editEv = () => { this.setState({ editing: true }); } - onQueryEventType(queryEventType) { + private onQueryEventType = (queryEventType: string) => { this.setState({ queryEventType }); } @@ -570,7 +603,7 @@ class AccountDataExplorer extends React.PureComponent { { JSON.stringify(this.state.event.event, null, 2) }
-
+
@@ -595,40 +628,41 @@ class AccountDataExplorer extends React.PureComponent { { rows }
-
+
- { !this.state.message &&
+
} +
; } } -class ServersInRoomList extends React.PureComponent { +interface IServersInRoomListState { + query: string; +} + +class ServersInRoomList extends React.PureComponent { static getLabel() { return _t('View Servers in Room'); } - static propTypes = { - onBack: PropTypes.func.isRequired, - room: PropTypes.instanceOf(Room).isRequired, - }; - static contextType = MatrixClientContext; + private servers: React.ReactElement[]; + constructor(props) { super(props); const room = this.props.room; - const servers = new Set(); + const servers: Set = new Set(); room.currentState.getStateEvents("m.room.member").forEach(ev => servers.add(ev.getSender().split(":")[1])); this.servers = Array.from(servers).map(s =>
-
+
; @@ -667,7 +701,10 @@ const PHASE_MAP = { [PHASE_CANCELLED]: "cancelled", }; -function VerificationRequest({txnId, request}) { +const VerificationRequest: React.FC<{ + txnId: string; + request: VerificationRequest; +}> = ({txnId, request}) => { const [, updateState] = useState(); const [timeout, setRequestTimeout] = useState(request.timeout); @@ -704,7 +741,7 @@ function VerificationRequest({txnId, request}) {
); } -class VerificationExplorer extends React.Component { +class VerificationExplorer extends React.PureComponent { static getLabel() { return _t("Verification Requests"); } @@ -712,7 +749,7 @@ class VerificationExplorer extends React.Component { /* Ensure this.context is the cli */ static contextType = MatrixClientContext; - onNewRequest = () => { + private onNewRequest = () => { this.forceUpdate(); } @@ -738,14 +775,19 @@ class VerificationExplorer extends React.Component { , )}
-
+
); } } -class WidgetExplorer extends React.Component { +interface IWidgetExplorerState { + query: string; + editWidget?: IApp; +} + +class WidgetExplorer extends React.Component { static getLabel() { return _t("Active Widgets"); } @@ -759,19 +801,19 @@ class WidgetExplorer extends React.Component { }; } - onWidgetStoreUpdate = () => { + private onWidgetStoreUpdate = () => { this.forceUpdate(); }; - onQueryChange = (query) => { + private onQueryChange = (query: string) => { this.setState({query}); }; - onEditWidget = (widget) => { + private onEditWidget = (widget: IApp) => { this.setState({editWidget: widget}); }; - onBack = () => { + private onBack = () => { const widgets = WidgetStore.instance.getApps(this.props.room.roomId); if (this.state.editWidget && widgets.includes(this.state.editWidget)) { this.setState({editWidget: null}); @@ -794,13 +836,16 @@ class WidgetExplorer extends React.Component { const editWidget = this.state.editWidget; const widgets = WidgetStore.instance.getApps(room.roomId); if (editWidget && widgets.includes(editWidget)) { - const allState = Array.from(Array.from(room.currentState.events.values()).map(e => e.values())) - .reduce((p, c) => {p.push(...c); return p;}, []); + const allState = Array.from( + Array.from(room.currentState.events.values()).map((e: Map) => { + return e.values(); + }), + ).reduce((p, c) => { p.push(...c); return p; }, []); const stateEv = allState.find(ev => ev.getId() === editWidget.eventId); if (!stateEv) { // "should never happen" return
{_t("There was an error finding this widget.")} -
+
; @@ -829,14 +874,22 @@ class WidgetExplorer extends React.Component { })}
-
+
); } } -class SettingsExplorer extends React.Component { +interface ISettingsExplorerState { + query: string; + editSetting?: string; + viewSetting?: string; + explicitValues?: string; + explicitRoomValues?: string; + } + +class SettingsExplorer extends React.PureComponent { static getLabel() { return _t("Settings Explorer"); } @@ -854,19 +907,19 @@ class SettingsExplorer extends React.Component { }; } - onQueryChange = (ev) => { + private onQueryChange = (ev: ChangeEvent) => { this.setState({query: ev.target.value}); }; - onExplValuesEdit = (ev) => { + private onExplValuesEdit = (ev: ChangeEvent) => { this.setState({explicitValues: ev.target.value}); }; - onExplRoomValuesEdit = (ev) => { + private onExplRoomValuesEdit = (ev: ChangeEvent) => { this.setState({explicitRoomValues: ev.target.value}); }; - onBack = () => { + private onBack = () => { if (this.state.editSetting) { this.setState({editSetting: null}); } else if (this.state.viewSetting) { @@ -876,12 +929,12 @@ class SettingsExplorer extends React.Component { } }; - onViewClick = (ev, settingId) => { + private onViewClick = (ev: MouseEvent, settingId: string) => { ev.preventDefault(); this.setState({viewSetting: settingId}); }; - onEditClick = (ev, settingId) => { + private onEditClick = (ev: MouseEvent, settingId: string) => { ev.preventDefault(); this.setState({ editSetting: settingId, @@ -890,7 +943,7 @@ class SettingsExplorer extends React.Component { }); }; - onSaveClick = async () => { + private onSaveClick = async () => { try { const settingId = this.state.editSetting; const parsedExplicit = JSON.parse(this.state.explicitValues); @@ -899,7 +952,7 @@ class SettingsExplorer extends React.Component { console.log(`[Devtools] Setting value of ${settingId} at ${level} from user input`); try { const val = parsedExplicit[level]; - await SettingsStore.setValue(settingId, null, level, val); + await SettingsStore.setValue(settingId, null, level as SettingLevel, val); } catch (e) { console.warn(e); } @@ -909,7 +962,7 @@ class SettingsExplorer extends React.Component { console.log(`[Devtools] Setting value of ${settingId} at ${level} in ${roomId} from user input`); try { const val = parsedExplicitRoom[level]; - await SettingsStore.setValue(settingId, roomId, level, val); + await SettingsStore.setValue(settingId, roomId, level as SettingLevel, val); } catch (e) { console.warn(e); } @@ -926,7 +979,7 @@ class SettingsExplorer extends React.Component { } }; - renderSettingValue(val) { + private renderSettingValue(val: any): string { // Note: we don't .toString() a string because we want JSON.stringify to inject quotes for us const toStringTypes = ['boolean', 'number']; if (toStringTypes.includes(typeof(val))) { @@ -936,7 +989,7 @@ class SettingsExplorer extends React.Component { } } - renderExplicitSettingValues(setting, roomId) { + private renderExplicitSettingValues(setting: string, roomId: string): string { const vals = {}; for (const level of LEVEL_ORDER) { try { @@ -951,7 +1004,7 @@ class SettingsExplorer extends React.Component { return JSON.stringify(vals, null, 4); } - renderCanEditLevel(roomId, level) { + private renderCanEditLevel(roomId: string, level: SettingLevel): React.ReactNode { const canEdit = SettingsStore.canSetValue(this.state.editSetting, roomId, level); const className = canEdit ? 'mx_DevTools_SettingsExplorer_mutable' : 'mx_DevTools_SettingsExplorer_immutable'; return {canEdit.toString()}; @@ -1006,7 +1059,7 @@ class SettingsExplorer extends React.Component {
-
+
@@ -1068,7 +1121,7 @@ class SettingsExplorer extends React.Component {
-
+
@@ -1114,7 +1167,7 @@ class SettingsExplorer extends React.Component {
-
+
@@ -1126,7 +1179,11 @@ class SettingsExplorer extends React.Component { } } -const Entries = [ +type DevtoolsDialogEntry = React.JSXElementConstructor & { + getLabel: () => string; +}; + +const Entries: DevtoolsDialogEntry[] = [ SendCustomEvent, RoomStateExplorer, SendAccountData, @@ -1137,43 +1194,36 @@ const Entries = [ SettingsExplorer, ]; -@replaceableComponent("views.dialogs.DevtoolsDialog") -export default class DevtoolsDialog extends React.PureComponent { - static propTypes = { - roomId: PropTypes.string.isRequired, - onFinished: PropTypes.func.isRequired, - }; +interface IProps { + roomId: string; + onFinished: (finished: boolean) => void; +} +interface IState { + mode?: DevtoolsDialogEntry; +} + +@replaceableComponent("views.dialogs.DevtoolsDialog") +export default class DevtoolsDialog extends React.PureComponent { constructor(props) { super(props); - this.onBack = this.onBack.bind(this); - this.onCancel = this.onCancel.bind(this); this.state = { mode: null, }; } - componentWillUnmount() { - this._unmounted = true; - } - - _setMode(mode) { + private setMode(mode: DevtoolsDialogEntry) { return () => { this.setState({ mode }); }; } - onBack() { - if (this.prevMode) { - this.setState({ mode: this.prevMode }); - this.prevMode = null; - } else { - this.setState({ mode: null }); - } + private onBack = () => { + this.setState({ mode: null }); } - onCancel() { + private onCancel = () => { this.props.onFinished(false); } @@ -1200,12 +1250,12 @@ export default class DevtoolsDialog extends React.PureComponent {
{ Entries.map((Entry) => { const label = Entry.getLabel(); - const onClick = this._setMode(Entry); + const onClick = this.setMode(Entry); return ; }) }
-
+
; From 073127aa3cac6ff6837bcfc710f310c2e19f8a05 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 20 May 2021 18:47:12 +0100 Subject: [PATCH 099/174] Fix handling of via servers for suggested rooms --- .../structures/SpaceRoomDirectory.tsx | 6 ++--- src/components/views/rooms/RoomList.tsx | 9 +++---- src/stores/SpaceStore.tsx | 24 +++++++++++++++---- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index 2881b6c3ea..dde8dd8331 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -76,7 +76,7 @@ export interface ISpaceSummaryEvent { order?: string; suggested?: boolean; auto_join?: boolean; - via?: string; + via?: string[]; }; } /* eslint-enable camelcase */ @@ -356,9 +356,9 @@ export const useSpaceSummary = (cli: MatrixClient, space: Room, refreshToken?: a parentChildRelations.getOrCreate(ev.room_id, new Map()).set(ev.state_key, ev); childParentRelations.getOrCreate(ev.state_key, new Set()).add(ev.room_id); } - if (Array.isArray(ev.content["via"])) { + if (Array.isArray(ev.content.via)) { const set = viaMap.getOrCreate(ev.state_key, new Set()); - ev.content["via"].forEach(via => set.add(via)); + ev.content.via.forEach(via => set.add(via)); } }); diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index b042414c85..7b0dadeca5 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -46,11 +46,10 @@ import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../con import AccessibleButton from "../elements/AccessibleButton"; import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore"; import CallHandler from "../../../CallHandler"; -import SpaceStore, {SUGGESTED_ROOMS} from "../../../stores/SpaceStore"; +import SpaceStore, {ISuggestedRoom, SUGGESTED_ROOMS} from "../../../stores/SpaceStore"; import {showAddExistingRooms, showCreateNewRoom, showSpaceInvite} from "../../../utils/space"; import {replaceableComponent} from "../../../utils/replaceableComponent"; import RoomAvatar from "../avatars/RoomAvatar"; -import { ISpaceSummaryRoom } from "../../structures/SpaceRoomDirectory"; interface IProps { onKeyDown: (ev: React.KeyboardEvent) => void; @@ -66,7 +65,7 @@ interface IState { sublists: ITagMap; isNameFiltering: boolean; currentRoomId?: string; - suggestedRooms: ISpaceSummaryRoom[]; + suggestedRooms: ISuggestedRoom[]; } const TAG_ORDER: TagID[] = [ @@ -363,7 +362,7 @@ export default class RoomList extends React.PureComponent { return room; }; - private updateSuggestedRooms = (suggestedRooms: ISpaceSummaryRoom[]) => { + private updateSuggestedRooms = (suggestedRooms: ISuggestedRoom[]) => { this.setState({ suggestedRooms }); }; @@ -443,7 +442,9 @@ export default class RoomList extends React.PureComponent { const viewRoom = () => { defaultDispatcher.dispatch({ action: "view_room", + room_alias: room.canonical_alias || room.aliases?.[0], room_id: room.room_id, + via_servers: room.viaServers, oobData: { avatarUrl: room.avatar_url, name, diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx index 05fe52aa2b..9488bf3cbd 100644 --- a/src/stores/SpaceStore.tsx +++ b/src/stores/SpaceStore.tsx @@ -45,6 +45,10 @@ export const UPDATE_INVITED_SPACES = Symbol("invited-spaces"); export const UPDATE_SELECTED_SPACE = Symbol("selected-space"); // Space Room ID will be emitted when a Space's children change +export interface ISuggestedRoom extends ISpaceSummaryRoom { + viaServers: string[]; +} + const MAX_SUGGESTED_ROOMS = 20; const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || "ALL_ROOMS"}`; @@ -89,7 +93,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { private spaceFilteredRooms = new Map>(); // The space currently selected in the Space Panel - if null then All Rooms is selected private _activeSpace?: Room = null; - private _suggestedRooms: ISpaceSummaryRoom[] = []; + private _suggestedRooms: ISuggestedRoom[] = []; private _invitedSpaces = new Set(); public get invitedSpaces(): Room[] { @@ -104,7 +108,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return this._activeSpace || null; } - public get suggestedRooms(): ISpaceSummaryRoom[] { + public get suggestedRooms(): ISuggestedRoom[] { return this._suggestedRooms; } @@ -160,10 +164,22 @@ export class SpaceStoreClass extends AsyncStoreWithClient { if (space) { const data = await this.fetchSuggestedRooms(space); if (this._activeSpace === space) { + const viaMap = new EnhancedMap>(); + data.events.forEach(ev => { + if (ev.type === EventType.SpaceChild && ev.content.via?.length) { + ev.content.via.forEach(via => { + viaMap.getOrCreate(ev.state_key, new Set()).add(via); + }); + } + }); + this._suggestedRooms = data.rooms.filter(roomInfo => { return roomInfo.room_type !== RoomType.Space && this.matrixClient.getRoom(roomInfo.room_id)?.getMyMembership() !== "join"; - }); + }).map(roomInfo => ({ + ...roomInfo, + viaServers: Array.from(viaMap.get(roomInfo.room_id) || []), + })); this.emit(SUGGESTED_ROOMS, this._suggestedRooms); } } @@ -222,7 +238,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return room?.currentState.getStateEvents(EventType.SpaceParent) .filter(ev => { const content = ev.getContent(); - if (!content?.via) return false; + if (!content?.via?.length) return false; // TODO apply permissions check to verify that the parent mapping is valid if (canonicalOnly && !content?.canonical) return false; return true; From e984a4f0cdac4b9bfbfb06a68dbc1d9cb07bf766 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 20 May 2021 20:12:28 +0100 Subject: [PATCH 100/174] rejig the code to make types happy --- src/components/structures/SpaceRoomView.tsx | 6 ++- src/stores/SpaceStore.tsx | 44 ++++++++++----------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 56ee9dd62a..ea4c75e25c 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -806,7 +806,7 @@ export default class SpaceRoomView extends React.PureComponent { 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)).rooms; + suggestedRooms = (await SpaceStore.instance.fetchSuggestedRooms(this.props.space, 1)); } if (suggestedRooms.length) { @@ -814,9 +814,11 @@ export default class SpaceRoomView extends React.PureComponent { 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.pop() || _t("Empty room"), + name: room.name || room.canonical_alias || room.aliases?.[0] || _t("Empty room"), }, }); return; diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx index 9488bf3cbd..40997d30a8 100644 --- a/src/stores/SpaceStore.tsx +++ b/src/stores/SpaceStore.tsx @@ -162,43 +162,41 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } if (space) { - const data = await this.fetchSuggestedRooms(space); + const suggestedRooms = await this.fetchSuggestedRooms(space); if (this._activeSpace === space) { - const viaMap = new EnhancedMap>(); - data.events.forEach(ev => { - if (ev.type === EventType.SpaceChild && ev.content.via?.length) { - ev.content.via.forEach(via => { - viaMap.getOrCreate(ev.state_key, new Set()).add(via); - }); - } - }); - - this._suggestedRooms = data.rooms.filter(roomInfo => { - return roomInfo.room_type !== RoomType.Space - && this.matrixClient.getRoom(roomInfo.room_id)?.getMyMembership() !== "join"; - }).map(roomInfo => ({ - ...roomInfo, - viaServers: Array.from(viaMap.get(roomInfo.room_id) || []), - })); + this._suggestedRooms = suggestedRooms; this.emit(SUGGESTED_ROOMS, this._suggestedRooms); } } } - public fetchSuggestedRooms = async (space: Room, limit = MAX_SUGGESTED_ROOMS) => { + public fetchSuggestedRooms = async (space: Room, limit = MAX_SUGGESTED_ROOMS): Promise => { try { const data: { rooms: ISpaceSummaryRoom[]; events: ISpaceSummaryEvent[]; } = await this.matrixClient.getSpaceSummary(space.roomId, 0, true, false, limit); - return data; + + const viaMap = new EnhancedMap>(); + data.events.forEach(ev => { + if (ev.type === EventType.SpaceChild && ev.content.via?.length) { + ev.content.via.forEach(via => { + viaMap.getOrCreate(ev.state_key, new Set()).add(via); + }); + } + }); + + return data.rooms.filter(roomInfo => { + return roomInfo.room_type !== RoomType.Space + && this.matrixClient.getRoom(roomInfo.room_id)?.getMyMembership() !== "join"; + }).map(roomInfo => ({ + ...roomInfo, + viaServers: Array.from(viaMap.get(roomInfo.room_id) || []), + })); } catch (e) { console.error(e); } - return { - rooms: [], - events: [], - }; + return []; }; public addRoomToSpace(space: Room, roomId: string, via: string[], suggested = false, autoJoin = false) { From 332412782ed18b6fcd922df4334b4bbf48e82e47 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Thu, 20 May 2021 17:31:10 -0400 Subject: [PATCH 101/174] Remove logo spinner Removed since design wants to avoid associating slowness with the brand. Signed-off-by: Robin Townsend --- res/css/structures/_ContextualMenu.scss | 5 - res/img/logo-spinner.svg | 141 ------------------ .../views/elements/InlineSpinner.js | 23 +-- src/components/views/elements/Spinner.js | 40 ++--- src/components/views/right_panel/UserInfo.tsx | 2 +- src/i18n/strings/en_EN.json | 1 - src/settings/Settings.tsx | 6 - 7 files changed, 14 insertions(+), 204 deletions(-) delete mode 100644 res/img/logo-spinner.svg diff --git a/res/css/structures/_ContextualMenu.scss b/res/css/structures/_ContextualMenu.scss index 658033339a..4b33427a87 100644 --- a/res/css/structures/_ContextualMenu.scss +++ b/res/css/structures/_ContextualMenu.scss @@ -115,8 +115,3 @@ limitations under the License. border-top: 8px solid $menu-bg-color; border-right: 8px solid transparent; } - -.mx_ContextualMenu_spinner { - display: block; - margin: 0 auto; -} diff --git a/res/img/logo-spinner.svg b/res/img/logo-spinner.svg deleted file mode 100644 index 08965e982e..0000000000 --- a/res/img/logo-spinner.svg +++ /dev/null @@ -1,141 +0,0 @@ - - - start - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/components/views/elements/InlineSpinner.js b/src/components/views/elements/InlineSpinner.js index 757e809564..bbbe60d500 100644 --- a/src/components/views/elements/InlineSpinner.js +++ b/src/components/views/elements/InlineSpinner.js @@ -16,7 +16,6 @@ limitations under the License. import React from "react"; import {_t} from "../../../languageHandler"; -import SettingsStore from "../../../settings/SettingsStore"; import {replaceableComponent} from "../../../utils/replaceableComponent"; @replaceableComponent("views.elements.InlineSpinner") @@ -24,31 +23,15 @@ export default class InlineSpinner extends React.Component { render() { const w = this.props.w || 16; const h = this.props.h || 16; - const imgClass = this.props.imgClassName || ""; - let icon; - if (SettingsStore.getValue('feature_new_spinner')) { - icon = ( - - ); - } else { - icon = ( + return ( +
- ); - } - - return ( -
{ icon }
+
); } } diff --git a/src/components/views/elements/Spinner.js b/src/components/views/elements/Spinner.js index 43030d01d5..3ad8444bd6 100644 --- a/src/components/views/elements/Spinner.js +++ b/src/components/views/elements/Spinner.js @@ -18,41 +18,21 @@ limitations under the License. import React from "react"; import PropTypes from "prop-types"; import {_t} from "../../../languageHandler"; -import SettingsStore from "../../../settings/SettingsStore"; -const Spinner = ({w = 32, h = 32, imgClassName, message}) => { - let icon; - if (SettingsStore.getValue('feature_new_spinner')) { - icon = ( - - ); - } else { - icon = ( -
- ); - } +const Spinner = ({w = 32, h = 32, message}) => ( +
+ { message &&
{ message }
 
} +
+
+); - return ( -
- { message &&
{ message }
 
} - { icon } -
- ); -}; Spinner.propTypes = { w: PropTypes.number, h: PropTypes.number, - imgClassName: PropTypes.string, message: PropTypes.node, }; diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index d5f67623a2..9798b282f6 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -1307,7 +1307,7 @@ const BasicUserInfo: React.FC<{ } if (pendingUpdateCount > 0) { - spinner = ; + spinner = ; } let memberDetails; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d1fe791319..d6eef99dbf 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -793,7 +793,6 @@ "Send and receive voice messages": "Send and receive voice messages", "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.", - "New spinner design": "New spinner design", "Message Pinning": "Message Pinning", "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)", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 37f493f427..6ff14c16b5 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -197,12 +197,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: false, controller: new IncompatibleController("feature_spaces"), }, - "feature_new_spinner": { - isFeature: true, - displayName: _td("New spinner design"), - supportedLevels: LEVELS_FEATURE, - default: false, - }, "feature_pinning": { isFeature: true, displayName: _td("Message Pinning"), From d0da4b2a2578688dc4892ecd68f0f6c0c9317e90 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 21 May 2021 12:37:34 +0100 Subject: [PATCH 102/174] Use separate name for verification request component --- src/components/views/dialogs/DevtoolsDialog.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 81d3a77327..c4be186da1 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -701,7 +701,7 @@ const PHASE_MAP = { [PHASE_CANCELLED]: "cancelled", }; -const VerificationRequest: React.FC<{ +const VerificationRequestExplorer: React.FC<{ txnId: string; request: VerificationRequest; }> = ({txnId, request}) => { @@ -772,7 +772,7 @@ class VerificationExplorer extends React.PureComponent { return (
{Array.from(inRoomRequests.entries()).reverse().map(([txnId, request]) => - , + , )}
From d59b2b357936d4b66595eaea2833996ab81fbd79 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 21 May 2021 12:38:32 +0100 Subject: [PATCH 103/174] Fix unintended buttons class change --- .../views/dialogs/DevtoolsDialog.tsx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index c4be186da1..7df57b030f 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -73,7 +73,7 @@ abstract class GenericEditor< protected abstract send(); protected buttons(): React.ReactNode { - return
+ return
{ !this.state.message && }
; @@ -184,7 +184,7 @@ export class SendCustomEvent extends GenericEditor
-
+
{ !this.state.message && } { showTglFlip &&
@@ -284,7 +284,7 @@ class SendAccountData extends GenericEditor
-
+
{ !this.state.message && } { !this.state.message &&
@@ -472,7 +472,7 @@ class RoomStateExplorer extends React.PureComponent
-
+
@@ -517,7 +517,7 @@ class RoomStateExplorer extends React.PureComponent { list }
-
+
; @@ -603,7 +603,7 @@ class AccountDataExplorer extends React.PureComponent
-
+
@@ -628,7 +628,7 @@ class AccountDataExplorer extends React.PureComponent
-
+
-
+
; @@ -775,7 +775,7 @@ class VerificationExplorer extends React.PureComponent { , )}
-
+
); @@ -845,7 +845,7 @@ class WidgetExplorer extends React.Component {_t("There was an error finding this widget.")} -
+
; @@ -874,7 +874,7 @@ class WidgetExplorer extends React.Component
-
+
); @@ -1059,7 +1059,7 @@ class SettingsExplorer extends React.PureComponent
-
+
@@ -1121,7 +1121,7 @@ class SettingsExplorer extends React.PureComponent
-
+
@@ -1167,7 +1167,7 @@ class SettingsExplorer extends React.PureComponent
-
+
@@ -1255,7 +1255,7 @@ export default class DevtoolsDialog extends React.PureComponent }) }
-
+
; From f8e61a982b6399f5c2133cf31f5f8d0d01ab0611 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 21 May 2021 12:41:59 +0100 Subject: [PATCH 104/174] One less Set --- src/components/views/dialogs/DevtoolsDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 7df57b030f..0ea77cc9e8 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -662,7 +662,7 @@ class ServersInRoomList extends React.PureComponent = new Set(); + const servers = new Set(); room.currentState.getStateEvents("m.room.member").forEach(ev => servers.add(ev.getSender().split(":")[1])); this.servers = Array.from(servers).map(s =>
); } diff --git a/src/components/views/elements/TooltipButton.js b/src/components/views/elements/TooltipButton.tsx similarity index 90% rename from src/components/views/elements/TooltipButton.js rename to src/components/views/elements/TooltipButton.tsx index c5ebb3b1aa..1232f48695 100644 --- a/src/components/views/elements/TooltipButton.js +++ b/src/components/views/elements/TooltipButton.tsx @@ -19,8 +19,16 @@ import React from 'react'; import * as sdk from '../../../index'; import {replaceableComponent} from "../../../utils/replaceableComponent"; +interface IProps { + helpText: string; +} + +interface IState { + hover: boolean; +} + @replaceableComponent("views.elements.TooltipButton") -export default class TooltipButton extends React.Component { +export default class TooltipButton extends React.Component { state = { hover: false, }; From 36d95ff73799a7dcfc1135a626d4e7b4030c13a5 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 24 May 2021 15:02:26 +0100 Subject: [PATCH 116/174] Display spinner in user menu when joining a room --- src/components/structures/UserMenu.tsx | 58 ++++++++++++++++++++++---- src/i18n/strings/en_EN.json | 2 + 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 65861624e6..c05f74a436 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -57,7 +57,8 @@ import { IHostSignupConfig } from "../views/dialogs/HostSignupDialogTypes"; import SpaceStore, { UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore"; import RoomName from "../views/elements/RoomName"; import {replaceableComponent} from "../../utils/replaceableComponent"; - +import InlineSpinner from "../views/elements/InlineSpinner"; +import TooltipButton from "../views/elements/TooltipButton"; interface IProps { isMinimized: boolean; } @@ -68,6 +69,7 @@ interface IState { contextMenuPosition: PartialDOMRect; isDarkTheme: boolean; selectedSpace?: Room; + pendingRoomJoin: string[] } @replaceableComponent("structures.UserMenu") @@ -84,6 +86,7 @@ export default class UserMenu extends React.Component { this.state = { contextMenuPosition: null, isDarkTheme: this.isUserOnDarkTheme(), + pendingRoomJoin: [], }; OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); @@ -147,15 +150,48 @@ export default class UserMenu extends React.Component { }; private onAction = (ev: ActionPayload) => { - if (ev.action !== Action.ToggleUserMenu) return; // not interested - - if (this.state.contextMenuPosition) { - this.setState({contextMenuPosition: null}); - } else { - if (this.buttonRef.current) this.buttonRef.current.click(); + switch (ev.action) { + case Action.ToggleUserMenu: + if (this.state.contextMenuPosition) { + this.setState({contextMenuPosition: null}); + } else { + if (this.buttonRef.current) this.buttonRef.current.click(); + } + break; + case Action.JoinRoom: + this.addPendingJoinRoom(ev.roomId); + break; + case Action.JoinRoomReady: + case Action.JoinRoomError: + this.removePendingJoinRoom(ev.roomId); + break; } }; + private addPendingJoinRoom(roomId) { + this.setState({ + pendingRoomJoin: [ + ...this.state.pendingRoomJoin, + roomId, + ], + }); + } + + private removePendingJoinRoom(roomId) { + const newPendingRoomJoin = this.state.pendingRoomJoin.filter(pendingJoinRoomId => { + return pendingJoinRoomId !== roomId; + }); + if (newPendingRoomJoin.length !== this.state.pendingRoomJoin.length) { + this.setState({ + pendingRoomJoin: newPendingRoomJoin, + }) + } + } + + get hasPendingActions(): boolean { + return this.state.pendingRoomJoin.length > 0; + } + private onOpenMenuClick = (ev: React.MouseEvent) => { ev.preventDefault(); ev.stopPropagation(); @@ -617,6 +653,14 @@ export default class UserMenu extends React.Component { /> {name} + {this.hasPendingActions && ( + + + + )} {dnd} {buttons}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7ceb039822..5aba8d998d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2753,6 +2753,8 @@ "Switch theme": "Switch theme", "User menu": "User menu", "Community and user menu": "Community and user menu", + "Currently joining %(count)s rooms|one": "Currently joining %(count)s room", + "Currently joining %(count)s rooms|other": "Currently joining %(count)s rooms", "Could not load user profile": "Could not load user profile", "Decrypted event source": "Decrypted event source", "Original event source": "Original event source", From f478cd98f7c0faf94e31ef277a09ec0068d7e34c Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 24 May 2021 15:12:17 +0100 Subject: [PATCH 117/174] fix i18n for UserMenu spinner --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5aba8d998d..1b04ae3b89 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2753,8 +2753,8 @@ "Switch theme": "Switch theme", "User menu": "User menu", "Community and user menu": "Community and user menu", - "Currently joining %(count)s rooms|one": "Currently joining %(count)s room", "Currently joining %(count)s rooms|other": "Currently joining %(count)s rooms", + "Currently joining %(count)s rooms|one": "Currently joining %(count)s room", "Could not load user profile": "Could not load user profile", "Decrypted event source": "Decrypted event source", "Original event source": "Original event source", From 671f1694579253afcc4a6ea8e53bb11fb11f768c Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 24 May 2021 16:08:48 +0100 Subject: [PATCH 118/174] Remove unused middlePanelResized event listener --- src/components/views/rooms/RoomTile.tsx | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index 60368ce250..3ed040c173 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -54,6 +54,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; import { getUnsentMessages } from "../../structures/RoomStatusBar"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; import { ResizeNotifier } from "../../../utils/ResizeNotifier"; +import { checkObjectHasNoAdditionalKeys } from "matrix-js-sdk/src/utils"; interface IProps { room: Room; @@ -106,9 +107,6 @@ export default class RoomTile extends React.PureComponent { this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room); this.roomProps = EchoChamber.forRoom(this.props.room); - if (this.props.resizeNotifier) { - this.props.resizeNotifier.on("middlePanelResized", this.onResize); - } } private countUnsentEvents(): number { @@ -123,12 +121,6 @@ export default class RoomTile extends React.PureComponent { this.forceUpdate(); // notification state changed - update }; - private onResize = () => { - if (this.showMessagePreview && !this.state.messagePreview) { - this.generatePreview(); - } - }; - private onLocalEchoUpdated = (ev: MatrixEvent, room: Room) => { if (!room?.roomId === this.props.room.roomId) return; this.setState({hasUnsentEvents: this.countUnsentEvents() > 0}); @@ -148,7 +140,9 @@ export default class RoomTile extends React.PureComponent { } public componentDidUpdate(prevProps: Readonly, prevState: Readonly) { - if (prevProps.showMessagePreview !== this.props.showMessagePreview && this.showMessagePreview) { + const showMessageChanged = prevProps.showMessagePreview !== this.props.showMessagePreview; + const minimizedChanged = prevProps.isMinimized !== this.props.isMinimized; + if (showMessageChanged || minimizedChanged) { this.generatePreview(); } if (prevProps.room?.roomId !== this.props.room?.roomId) { @@ -208,9 +202,6 @@ export default class RoomTile extends React.PureComponent { ); this.props.room.off("Room.name", this.onRoomNameUpdate); } - if (this.props.resizeNotifier) { - this.props.resizeNotifier.off("middlePanelResized", this.onResize); - } ActiveRoomObserver.removeListener(this.props.room.roomId, this.onActiveRoomUpdate); defaultDispatcher.unregister(this.dispatcherRef); this.notificationState.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate); From a0b052d3dea81143a6d766583d7fc28b8a31721b Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Mon, 24 May 2021 11:11:45 -0400 Subject: [PATCH 119/174] bump to olm 3.2.3 --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index aaad80d068..21c1ffb6ea 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,7 @@ "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.2", "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", - "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz", + "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "react-test-renderer": "^16.14.0", "rimraf": "^3.0.2", "stylelint": "^13.9.0", diff --git a/yarn.lock b/yarn.lock index 041bb8b4b9..5b42436039 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1325,9 +1325,9 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz": - version "3.2.2" - resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz#5e3d784461ca3bbeb791ac8f3c175375aeb81318" +"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz": + version "3.2.3" + resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz#cc332fdd25c08ef0e40f4d33fc3f822a0f98b6f4" "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents": version "2.1.8-no-fsevents" From 0bbfb1a6d953344083fb7554a303ee38039adc7d Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 24 May 2021 16:18:55 +0100 Subject: [PATCH 120/174] remove unused variable checkObjectHasNoAdditionalKeys --- src/components/views/rooms/RoomTile.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index 3ed040c173..579a275155 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -54,7 +54,6 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; import { getUnsentMessages } from "../../structures/RoomStatusBar"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; import { ResizeNotifier } from "../../../utils/ResizeNotifier"; -import { checkObjectHasNoAdditionalKeys } from "matrix-js-sdk/src/utils"; interface IProps { room: Room; From 73221e4d08cb5e7fe71b7df59223d232d403bae3 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 21 May 2021 16:09:28 -0400 Subject: [PATCH 121/174] Bump libolm version. --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8ecebc4eb3..ba4c8bc98c 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,7 @@ "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.2", "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", - "olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz", + "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz", "react-test-renderer": "^16.14.0", "rimraf": "^3.0.2", "stylelint": "^13.9.0", diff --git a/yarn.lock b/yarn.lock index 0472555dd9..a32b81af4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1325,6 +1325,10 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz": + version "3.2.2" + resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz#5e3d784461ca3bbeb791ac8f3c175375aeb81318" + "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents": version "2.1.8-no-fsevents" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.tgz#da7c3996b8e6e19ebd14d82eaced2313e7769f9b" @@ -6145,10 +6149,6 @@ object.values@^1.1.1, object.values@^1.1.2: es-abstract "^1.18.0-next.1" has "^1.0.3" -"olm@https://packages.matrix.org/npm/olm/olm-3.2.1.tgz": - version "3.2.1" - resolved "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz#d623d76f99c3518dde68be8c86618d68bc7b004a" - once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" From 140258d3d6f53117957ce903fbcd078729a4d2f7 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Mon, 24 May 2021 11:11:45 -0400 Subject: [PATCH 122/174] bump to olm 3.2.3 --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index ba4c8bc98c..a8d29eca48 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,7 @@ "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.2", "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", - "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz", + "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "react-test-renderer": "^16.14.0", "rimraf": "^3.0.2", "stylelint": "^13.9.0", diff --git a/yarn.lock b/yarn.lock index a32b81af4b..fe106d7e7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1325,9 +1325,9 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz": - version "3.2.2" - resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz#5e3d784461ca3bbeb791ac8f3c175375aeb81318" +"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz": + version "3.2.3" + resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz#cc332fdd25c08ef0e40f4d33fc3f822a0f98b6f4" "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents": version "2.1.8-no-fsevents" From fdc22bfdf747e618dd510c8a257f74ffd8dcca6d Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 24 May 2021 16:40:55 +0100 Subject: [PATCH 123/174] Adhere to TypeScript codestyle better --- src/components/views/elements/TooltipButton.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/views/elements/TooltipButton.tsx b/src/components/views/elements/TooltipButton.tsx index 1232f48695..191018cc19 100644 --- a/src/components/views/elements/TooltipButton.tsx +++ b/src/components/views/elements/TooltipButton.tsx @@ -29,17 +29,20 @@ interface IState { @replaceableComponent("views.elements.TooltipButton") export default class TooltipButton extends React.Component { - state = { - hover: false, - }; + constructor(props) { + super(props); + this.state = { + hover: false, + }; + } - onMouseOver = () => { + private onMouseOver = () => { this.setState({ hover: true, }); }; - onMouseLeave = () => { + private onMouseLeave = () => { this.setState({ hover: false, }); From 3b69c0203c1fba42249b16110db3c7fd976465d2 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 24 May 2021 17:05:59 +0100 Subject: [PATCH 124/174] Remove resize notifier prop from RoomTile --- src/components/views/rooms/RoomTile.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index 579a275155..aae182eca4 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -53,14 +53,12 @@ import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/Community import { replaceableComponent } from "../../../utils/replaceableComponent"; import { getUnsentMessages } from "../../structures/RoomStatusBar"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; -import { ResizeNotifier } from "../../../utils/ResizeNotifier"; interface IProps { room: Room; showMessagePreview: boolean; isMinimized: boolean; tag: TagID; - resizeNotifier: ResizeNotifier; } type PartialDOMRect = Pick; From ad30e89967e4abc4a3fbab53b17c589f8ad97d0b Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 24 May 2021 17:15:16 +0100 Subject: [PATCH 125/174] Upgrade matrix-js-sdk to 11.1.0 --- package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index a8d29eca48..dff6afa609 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "11.1.0-rc.1", + "matrix-js-sdk": "11.1.0", "matrix-widget-api": "^0.1.0-beta.14", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", @@ -121,6 +121,7 @@ "@babel/preset-typescript": "^7.12.7", "@babel/register": "^7.12.10", "@babel/traverse": "^7.12.12", + "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "@peculiar/webcrypto": "^1.1.4", "@sinonjs/fake-timers": "^7.0.2", "@types/classnames": "^2.2.11", @@ -161,7 +162,6 @@ "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.2", "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", - "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "react-test-renderer": "^16.14.0", "rimraf": "^3.0.2", "stylelint": "^13.9.0", diff --git a/yarn.lock b/yarn.lock index fe106d7e7a..ff7611da84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5673,10 +5673,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@11.1.0-rc.1: - version "11.1.0-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-11.1.0-rc.1.tgz#98da580fa51bc8c70c57984e79dc63c617a671d1" - integrity sha512-yyZeL1mHttw+EnZcGfMH+wd33s0+ZKB+KyXHA3QiqiVRWLgANjIY9QCNjbJYa9FK8zZRqO2yHiFLtTAAU379Ag== +matrix-js-sdk@11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-11.1.0.tgz#59119f9fe76adbc38b309947c5532baea8499bf1" + integrity sha512-yBvSGb33MDz9mfbjtVGO7557kgtY/kJcrFyhtN7LwSyi/TDhhYleq5xAqsi7MJrmIb/E0JIF10JIwlF9dAW64Q== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From d0e41c6461738773c261574dd1da2b1b98d0ff3a Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 24 May 2021 17:20:30 +0100 Subject: [PATCH 126/174] Prepare changelog for v3.22.0 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b4ec8b457..f3d9afd51d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +Changes in [3.22.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.22.0) (2021-05-24) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.22.0-rc.1...v3.22.0) + + * Upgrade to JS SDK 11.1.0 + * [Release] Bump libolm version + [\#6087](https://github.com/matrix-org/matrix-react-sdk/pull/6087) + Changes in [3.22.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.22.0-rc.1) (2021-05-19) =============================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.21.0...v3.22.0-rc.1) From fbc01d4930dfd4c9376041ac6b5ef40a038aeb4d Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 24 May 2021 17:20:31 +0100 Subject: [PATCH 127/174] v3.22.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dff6afa609..5f07c0c0a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.22.0-rc.1", + "version": "3.22.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From f8ef2d42f5966c06a0ac3a392023bec17bb6fb1c Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 24 May 2021 17:25:58 +0100 Subject: [PATCH 128/174] Resetting package fields for development --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5f07c0c0a4..e26ce17f42 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "bin": { "reskindex": "scripts/reskindex.js" }, - "main": "./lib/index.js", + "main": "./src/index.js", "matrix_src_main": "./src/index.js", "matrix_lib_main": "./lib/index.js", "matrix_lib_typings": "./lib/index.d.ts", @@ -200,6 +200,5 @@ "coverageReporters": [ "text" ] - }, - "typings": "./lib/index.d.ts" + } } From 7123abc122aa08b51c7847b06d6e3a623b0c5d1e Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 24 May 2021 17:26:24 +0100 Subject: [PATCH 129/174] Reset matrix-js-sdk back to develop branch --- package.json | 2 +- yarn.lock | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e26ce17f42..13047b69cf 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "11.1.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^0.1.0-beta.14", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index ff7611da84..35aac66e26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5673,10 +5673,9 @@ 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@11.1.0: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "11.1.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-11.1.0.tgz#59119f9fe76adbc38b309947c5532baea8499bf1" - integrity sha512-yBvSGb33MDz9mfbjtVGO7557kgtY/kJcrFyhtN7LwSyi/TDhhYleq5xAqsi7MJrmIb/E0JIF10JIwlF9dAW64Q== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/acb9bc8cc5234326a7583514a8e120a4ac42eedc" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From cdecc156df1800179a619e2a8063295ece3d63b0 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 24 May 2021 17:30:37 +0100 Subject: [PATCH 130/174] Remove unused prop --- src/components/views/rooms/RoomSublist.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx index f9881d33ae..8a2059a247 100644 --- a/src/components/views/rooms/RoomSublist.tsx +++ b/src/components/views/rooms/RoomSublist.tsx @@ -530,7 +530,6 @@ export default class RoomSublist extends React.Component { tiles.push( Date: Mon, 24 May 2021 18:57:24 +0100 Subject: [PATCH 131/174] Use local room state to render space hierarchy if the room is known --- .../structures/SpaceRoomDirectory.tsx | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index dde8dd8331..3f1679c97e 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -101,15 +101,13 @@ const Tile: React.FC = ({ numChildRooms, children, }) => { - const name = room.name || room.canonical_alias || room.aliases?.[0] + const cli = MatrixClientPeg.get(); + const joinedRoom = cli.getRoom(room.room_id)?.getMyMembership() === "join" && cli.getRoom(room.room_id); + const name = joinedRoom?.name || room.name || room.canonical_alias || room.aliases?.[0] || (room.room_type === RoomType.Space ? _t("Unnamed Space") : _t("Unnamed Room")); const [showChildren, toggleShowChildren] = useStateToggle(true); - const cli = MatrixClientPeg.get(); - const cliRoom = cli.getRoom(room.room_id); - const myMembership = cliRoom?.getMyMembership(); - const onPreviewClick = (ev: ButtonEvent) => { ev.preventDefault(); ev.stopPropagation(); @@ -122,7 +120,7 @@ const Tile: React.FC = ({ } let button; - if (myMembership === "join") { + if (joinedRoom) { button = { _t("View") } ; @@ -146,17 +144,27 @@ const Tile: React.FC = ({ } } - let url: string; - if (room.avatar_url) { - url = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(20); + let avatar; + if (joinedRoom) { + avatar = ; + } else { + avatar = ; } let description = _t("%(count)s members", { count: room.num_joined_members }); if (numChildRooms !== undefined) { description += " · " + _t("%(count)s rooms", { count: numChildRooms }); } - if (room.topic) { - description += " · " + room.topic; + + const topic = joinedRoom?.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || room.topic; + if (topic) { + description += " · " + topic; } let suggestedSection; @@ -167,7 +175,7 @@ const Tile: React.FC = ({ } const content = - + { avatar }
{ name } { suggestedSection } From 4be8bbeef9360351873f6a23239c3ab6413fa408 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 24 May 2021 21:17:30 +0100 Subject: [PATCH 132/174] Close creation menu when expanding space panel via expand hierarchy --- src/components/views/spaces/SpacePanel.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 411b0f9b5e..163a5cfb0b 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -221,14 +221,20 @@ const SpacePanel = () => { space={s} activeSpaces={activeSpaces} isPanelCollapsed={isPanelCollapsed} - onExpand={() => setPanelCollapsed(false)} + onExpand={() => { + closeMenu(); + setPanelCollapsed(false); + }} />) } { spaces.map(s => setPanelCollapsed(false)} + onExpand={() => { + closeMenu(); + setPanelCollapsed(false); + }} />) }
Date: Tue, 25 May 2021 09:46:49 +0100 Subject: [PATCH 133/174] Prevent reflow when getting screen orientation It is better to access the device orientation using media queries as it will not force a reflow compared to accessing innerWidth/innerHeight --- src/CountlyAnalytics.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CountlyAnalytics.ts b/src/CountlyAnalytics.ts index 974c08df18..61c471e4d4 100644 --- a/src/CountlyAnalytics.ts +++ b/src/CountlyAnalytics.ts @@ -684,7 +684,9 @@ export default class CountlyAnalytics { } private getOrientation = (): Orientation => { - return window.innerWidth > window.innerHeight ? Orientation.Landscape : Orientation.Portrait; + return window.matchMedia("(orientation: landscape)").matches + ? Orientation.Landscape + : Orientation.Portrait }; private reportOrientation = () => { From 73d51a91d6dbd91ac65cd25df050f071f4a01995 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 09:47:45 +0100 Subject: [PATCH 134/174] Prevent unneeded state updates to hide StickerPicker --- src/components/views/rooms/Stickerpicker.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index 82e8cf640c..3d2300b83c 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -40,7 +40,7 @@ const STICKERPICKER_Z_INDEX = 3500; const PERSISTED_ELEMENT_KEY = "stickerPicker"; @replaceableComponent("views.rooms.Stickerpicker") -export default class Stickerpicker extends React.Component { +export default class Stickerpicker extends React.PureComponent { static currentWidget; constructor(props) { @@ -341,21 +341,27 @@ export default class Stickerpicker extends React.Component { * @param {Event} ev Event that triggered the function call */ _onHideStickersClick(ev) { - this.setState({showStickers: false}); + if (this.state.showStickers) { + this.setState({showStickers: false}); + } } /** * Called when the window is resized */ _onResize() { - this.setState({showStickers: false}); + if (this.state.showStickers) { + this.setState({showStickers: false}); + } } /** * The stickers picker was hidden */ _onFinished() { - this.setState({showStickers: false}); + if (this.state.showStickers) { + this.setState({showStickers: false}); + } } /** From 2710062df70892f422da904ee7a1edc226e7b168 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 09:49:15 +0100 Subject: [PATCH 135/174] Create a UIStore to track important data This helper should hold data related to the UI and access save in a smart to avoid performance pitfalls in other parts of the application --- src/stores/UIStore.ts | 69 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/stores/UIStore.ts diff --git a/src/stores/UIStore.ts b/src/stores/UIStore.ts new file mode 100644 index 0000000000..62b73a14f6 --- /dev/null +++ b/src/stores/UIStore.ts @@ -0,0 +1,69 @@ +/* +Copyright 2020 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 EventEmitter from "events"; + +export enum UI_EVENTS { + Resize = "resize" +} + +export type ResizeObserverCallbackFunction = (entries: ResizeObserverEntry[]) => void; + + +export default class UIStore extends EventEmitter { + private static _instance: UIStore = null; + + private resizeObserver: ResizeObserver; + + public windowWith: number; + public windowHeight: number; + + constructor() { + super(); + + this.windowWith = window.innerWidth; + this.windowHeight = window.innerHeight; + + this.resizeObserver = new ResizeObserver(this.resizeObserverCallback); + this.resizeObserver.observe(document.body); + } + + public static get instance(): UIStore { + if (!UIStore._instance) { + UIStore._instance = new UIStore(); + } + return UIStore._instance; + } + + public static destroy(): void { + if (UIStore._instance) { + UIStore._instance.resizeObserver.disconnect(); + UIStore._instance.removeAllListeners(); + UIStore._instance = null; + } + } + + private resizeObserverCallback = (entries: ResizeObserverEntry[]) => { + const { width, height } = entries + .find(entry => entry.target === document.body) + .contentRect; + + this.windowWith = width; + this.windowHeight = height; + + this.emit(UI_EVENTS.Resize, entries); + } +} From ac93cc514f0e0a2be46c300d142496a6325a00f7 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 09:50:09 +0100 Subject: [PATCH 136/174] Prevent layout trashing when resizing the window --- src/components/structures/LeftPanel.tsx | 13 +------------ src/components/structures/LeftPanelWidget.tsx | 10 ++-------- src/components/structures/MatrixChat.tsx | 17 +++++++---------- src/components/views/rooms/RoomList.tsx | 6 +----- src/components/views/rooms/RoomSublist.tsx | 2 -- src/utils/ResizeNotifier.js | 6 ------ 6 files changed, 11 insertions(+), 43 deletions(-) diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 7f9ef7516e..465d4cac49 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -90,10 +90,6 @@ export default class LeftPanel extends React.Component { this.groupFilterPanelWatcherRef = SettingsStore.watchSetting("TagPanel.enableTagPanel", null, () => { this.setState({showGroupFilterPanel: SettingsStore.getValue("TagPanel.enableTagPanel")}); }); - - // We watch the middle panel because we don't actually get resized, the middle panel does. - // We listen to the noisy channel to avoid choppy reaction times. - this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize); } public componentWillUnmount() { @@ -103,7 +99,6 @@ export default class LeftPanel extends React.Component { RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); OwnProfileStore.instance.off(UPDATE_EVENT, this.onBackgroundImageUpdate); SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace); - this.props.resizeNotifier.off("middlePanelResizedNoisy", this.onResize); } private updateActiveSpace = (activeSpace: Room) => { @@ -281,11 +276,6 @@ export default class LeftPanel extends React.Component { this.handleStickyHeaders(list); }; - private onResize = () => { - if (!this.listContainerRef.current) return; // ignore: no headers to sticky - this.handleStickyHeaders(this.listContainerRef.current); - }; - private onFocus = (ev: React.FocusEvent) => { this.focusedElement = ev.target; }; @@ -420,7 +410,6 @@ export default class LeftPanel extends React.Component { onFocus={this.onFocus} onBlur={this.onBlur} isMinimized={this.props.isMinimized} - onResize={this.onResize} activeSpace={this.state.activeSpace} />; @@ -454,7 +443,7 @@ export default class LeftPanel extends React.Component { {roomList}
- { !this.props.isMinimized && } + { !this.props.isMinimized && }
); diff --git a/src/components/structures/LeftPanelWidget.tsx b/src/components/structures/LeftPanelWidget.tsx index e88af282ba..89c0744cf8 100644 --- a/src/components/structures/LeftPanelWidget.tsx +++ b/src/components/structures/LeftPanelWidget.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {useContext, useEffect, useMemo} from "react"; +import React, {useContext, useMemo} from "react"; import {Resizable} from "re-resizable"; import classNames from "classnames"; @@ -28,15 +28,11 @@ import {useAccountData} from "../../hooks/useAccountData"; import AppTile from "../views/elements/AppTile"; import {useSettingValue} from "../../hooks/useSettings"; -interface IProps { - onResize(): void; -} - const MIN_HEIGHT = 100; const MAX_HEIGHT = 500; // or 50% of the window height const INITIAL_HEIGHT = 280; -const LeftPanelWidget: React.FC = ({ onResize }) => { +const LeftPanelWidget: React.FC = () => { const cli = useContext(MatrixClientContext); const mWidgetsEvent = useAccountData>(cli, "m.widgets"); @@ -56,7 +52,6 @@ const LeftPanelWidget: React.FC = ({ onResize }) => { const [height, setHeight] = useLocalStorageState("left-panel-widget-height", INITIAL_HEIGHT); const [expanded, setExpanded] = useLocalStorageState("left-panel-widget-expanded", true); - useEffect(onResize, [expanded, onResize]); const [onFocus, isActive, ref] = useRovingTabIndex(); const tabIndex = isActive ? 0 : -1; @@ -69,7 +64,6 @@ const LeftPanelWidget: React.FC = ({ onResize }) => { size={{height} as any} minHeight={MIN_HEIGHT} maxHeight={Math.min(window.innerHeight / 2, MAX_HEIGHT)} - onResize={onResize} onResizeStop={(e, dir, ref, d) => { setHeight(height + d.height); }} diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 49386c5f65..1d794b05c4 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -87,6 +87,7 @@ import defaultDispatcher from "../../dispatcher/dispatcher"; import SecurityCustomisations from "../../customisations/Security"; import PerformanceMonitor, { PerformanceEntryNames } from "../../performance"; +import UIStore, { UI_EVENTS } from "../../stores/UIStore"; /** constants for MatrixChat.state.view */ export enum Views { @@ -225,7 +226,6 @@ export default class MatrixChat extends React.PureComponent { firstSyncPromise: IDeferred; private screenAfterLogin?: IScreen; - private windowWidth: number; private pageChanging: boolean; private tokenLogin?: boolean; private accountPassword?: string; @@ -277,9 +277,7 @@ export default class MatrixChat extends React.PureComponent { } } - this.windowWidth = 10000; - this.handleResize(); - window.addEventListener('resize', this.handleResize); + UIStore.instance.on(UI_EVENTS.Resize, this.handleResize); this.pageChanging = false; @@ -436,7 +434,7 @@ export default class MatrixChat extends React.PureComponent { dis.unregister(this.dispatcherRef); this.themeWatcher.stop(); this.fontWatcher.stop(); - window.removeEventListener('resize', this.handleResize); + UIStore.destroy(); this.state.resizeNotifier.removeListener("middlePanelResized", this.dispatchTimelineResize); if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer); @@ -1820,18 +1818,17 @@ export default class MatrixChat extends React.PureComponent { } handleResize = () => { - const hideLhsThreshold = 1000; - const showLhsThreshold = 1000; + const LHS_THRESHOLD = 1000; + const width = UIStore.instance.windowWith; - if (this.windowWidth > hideLhsThreshold && window.innerWidth <= hideLhsThreshold) { + if (width <= LHS_THRESHOLD && !this.state.collapseLhs) { dis.dispatch({ action: 'hide_left_panel' }); } - if (this.windowWidth <= showLhsThreshold && window.innerWidth > showLhsThreshold) { + if (width > LHS_THRESHOLD && this.state.collapseLhs) { dis.dispatch({ action: 'show_left_panel' }); } this.state.resizeNotifier.notifyWindowResized(); - this.windowWidth = window.innerWidth; }; private dispatchTimelineResize() { diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 7b0dadeca5..896021f918 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -55,7 +55,6 @@ interface IProps { onKeyDown: (ev: React.KeyboardEvent) => void; onFocus: (ev: React.FocusEvent) => void; onBlur: (ev: React.FocusEvent) => void; - onResize: () => void; resizeNotifier: ResizeNotifier; isMinimized: boolean; activeSpace: Room; @@ -404,9 +403,7 @@ export default class RoomList extends React.PureComponent { const newSublists = objectWithOnly(newLists, newListIds); const sublists = objectShallowClone(newSublists, (k, v) => arrayFastClone(v)); - this.setState({sublists, isNameFiltering}, () => { - this.props.onResize(); - }); + this.setState({sublists, isNameFiltering}); } }; @@ -537,7 +534,6 @@ export default class RoomList extends React.PureComponent { addRoomLabel={aesthetics.addRoomLabel ? _t(aesthetics.addRoomLabel) : aesthetics.addRoomLabel} addRoomContextMenu={aesthetics.addRoomContextMenu} isMinimized={this.props.isMinimized} - onResize={this.props.onResize} showSkeleton={showSkeleton} extraTiles={extraTiles} resizeNotifier={this.props.resizeNotifier} diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx index f9881d33ae..74987b066a 100644 --- a/src/components/views/rooms/RoomSublist.tsx +++ b/src/components/views/rooms/RoomSublist.tsx @@ -74,7 +74,6 @@ interface IProps { addRoomLabel: string; isMinimized: boolean; tagId: TagID; - onResize: () => void; showSkeleton?: boolean; alwaysVisible?: boolean; resizeNotifier: ResizeNotifier; @@ -473,7 +472,6 @@ export default class RoomSublist extends React.Component { private toggleCollapsed = () => { this.layout.isCollapsed = this.state.isExpanded; this.setState({isExpanded: !this.layout.isCollapsed}); - setImmediate(() => this.props.onResize()); // needs to happen when the DOM is updated }; private onHeaderKeyDown = (ev: React.KeyboardEvent) => { diff --git a/src/utils/ResizeNotifier.js b/src/utils/ResizeNotifier.js index fd12a454f6..4d46d10f6c 100644 --- a/src/utils/ResizeNotifier.js +++ b/src/utils/ResizeNotifier.js @@ -74,12 +74,6 @@ export default class ResizeNotifier extends EventEmitter { // can be called in quick succession notifyWindowResized() { - // no need to throttle this one, - // also it could make scrollbars appear for - // a split second when the room list manual layout is now - // taller than the available space - this.emit("leftPanelResized"); - this._updateMiddlePanel(); } } From a57887cc61154bc2e3b280b047b7d45a26cad173 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 09:53:22 +0100 Subject: [PATCH 137/174] Prevent layout trashing on EffectsOverlay --- src/components/views/elements/EffectsOverlay.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/views/elements/EffectsOverlay.tsx b/src/components/views/elements/EffectsOverlay.tsx index 7bed0222b0..00d9d147f1 100644 --- a/src/components/views/elements/EffectsOverlay.tsx +++ b/src/components/views/elements/EffectsOverlay.tsx @@ -17,7 +17,8 @@ import React, { FunctionComponent, useEffect, useRef } from 'react'; import dis from '../../../dispatcher/dispatcher'; import ICanvasEffect from '../../../effects/ICanvasEffect'; -import {CHAT_EFFECTS} from '../../../effects' +import { CHAT_EFFECTS } from '../../../effects' +import UIStore, { UI_EVENTS } from "../../../stores/UIStore"; interface IProps { roomWidth: number; @@ -45,8 +46,8 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) => { useEffect(() => { const resize = () => { - if (canvasRef.current) { - canvasRef.current.height = window.innerHeight; + if (canvasRef.current && canvasRef.current?.height !== UIStore.instance.windowHeight) { + canvasRef.current.height = UIStore.instance.windowHeight; } }; const onAction = (payload: { action: string }) => { @@ -58,12 +59,12 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) => { } const dispatcherRef = dis.register(onAction); const canvas = canvasRef.current; - canvas.height = window.innerHeight; - window.addEventListener('resize', resize, true); + canvas.height = UIStore.instance.windowHeight; + UIStore.instance.on(UI_EVENTS.Resize, resize); return () => { dis.unregister(dispatcherRef); - window.removeEventListener('resize', resize); + UIStore.instance.off(UI_EVENTS.Resize, resize); // eslint-disable-next-line react-hooks/exhaustive-deps const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored for (const effect in currentEffects) { From f156c2db15e7e1bef5fd5e444be5dec644aac18d Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 10:25:36 +0100 Subject: [PATCH 138/174] prevent reflow in app when accessing window dimensions --- .eslintrc.js | 18 ++++++++++++++++++ src/components/structures/ContextMenu.tsx | 15 ++++++++------- src/components/structures/LeftPanel.tsx | 4 +++- src/components/structures/LeftPanelWidget.tsx | 3 ++- src/components/structures/MatrixChat.tsx | 2 +- src/components/structures/RoomView.tsx | 3 ++- .../views/directory/NetworkDropdown.tsx | 3 ++- src/components/views/elements/Tooltip.tsx | 7 ++++--- src/components/views/messages/TextualBody.js | 3 ++- .../views/right_panel/RoomSummaryCard.tsx | 5 +++-- src/components/views/right_panel/UserInfo.tsx | 5 +++-- .../views/right_panel/WidgetCard.tsx | 3 ++- src/components/views/rooms/AppsDrawer.js | 3 ++- src/stores/UIStore.ts | 10 +++++++--- 14 files changed, 59 insertions(+), 25 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 4959b133a0..9ae51f9bc5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -30,6 +30,24 @@ module.exports = { "quotes": "off", "no-extra-boolean-cast": "off", + "no-restricted-properties": [ + "error", + ...buildRestrictedPropertiesOptions( + ["window.innerHeight", "window.innerWidth", "window.visualViewport"], + "Use UIStore to access window dimensions instead", + ), + ], }, }], }; + +function buildRestrictedPropertiesOptions(properties, message) { + return properties.map(prop => { + const [object, property] = prop.split("."); + return { + object, + property, + message, + }; + }); +} diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index ad0f75e162..9d8665c176 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -23,6 +23,7 @@ import classNames from "classnames"; import {Key} from "../../Keyboard"; import {Writeable} from "../../@types/common"; import {replaceableComponent} from "../../utils/replaceableComponent"; +import UIStore from "../../stores/UIStore"; // Shamelessly ripped off Modal.js. There's probably a better way // of doing reusable widgets like dialog boxes & menus where we go and @@ -410,12 +411,12 @@ export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None const buttonBottom = elementRect.bottom + window.pageYOffset; const buttonTop = elementRect.top + window.pageYOffset; // Align the right edge of the menu to the right edge of the button - menuOptions.right = window.innerWidth - buttonRight; + menuOptions.right = UIStore.instance.windowWidth - buttonRight; // Align the menu vertically on whichever side of the button has more space available. - if (buttonBottom < window.innerHeight / 2) { + if (buttonBottom < UIStore.instance.windowHeight / 2) { menuOptions.top = buttonBottom + vPadding; } else { - menuOptions.bottom = (window.innerHeight - buttonTop) + vPadding; + menuOptions.bottom = (UIStore.instance.windowHeight - buttonTop) + vPadding; } return menuOptions; @@ -430,12 +431,12 @@ export const alwaysAboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFac const buttonBottom = elementRect.bottom + window.pageYOffset; const buttonTop = elementRect.top + window.pageYOffset; // Align the right edge of the menu to the right edge of the button - menuOptions.right = window.innerWidth - buttonRight; + menuOptions.right = UIStore.instance.windowWidth - buttonRight; // Align the menu vertically on whichever side of the button has more space available. - if (buttonBottom < window.innerHeight / 2) { + if (buttonBottom < UIStore.instance.windowHeight / 2) { menuOptions.top = buttonBottom + vPadding; } else { - menuOptions.bottom = (window.innerHeight - buttonTop) + vPadding; + menuOptions.bottom = (UIStore.instance.windowHeight - buttonTop) + vPadding; } return menuOptions; @@ -451,7 +452,7 @@ export const alwaysAboveRightOf = (elementRect: DOMRect, chevronFace = ChevronFa // Align the left edge of the menu to the left edge of the button menuOptions.left = buttonLeft; // Align the menu vertically above the menu - menuOptions.bottom = (window.innerHeight - buttonTop) + vPadding; + menuOptions.bottom = (UIStore.instance.windowHeight - buttonTop) + vPadding; return menuOptions; }; diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 465d4cac49..e929306940 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -43,6 +43,7 @@ import {replaceableComponent} from "../../utils/replaceableComponent"; import {mediaFromMxc} from "../../customisations/Media"; import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore"; import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager"; +import UIStore from "../../stores/UIStore"; interface IProps { isMinimized: boolean; @@ -223,7 +224,8 @@ export default class LeftPanel extends React.Component { header.classList.add("mx_RoomSublist_headerContainer_stickyBottom"); } - const offset = window.innerHeight - (list.parentElement.offsetTop + list.parentElement.offsetHeight); + const offset = UIStore.instance.windowHeight - + (list.parentElement.offsetTop + list.parentElement.offsetHeight); const newBottom = `${offset}px`; if (header.style.bottom !== newBottom) { header.style.bottom = newBottom; diff --git a/src/components/structures/LeftPanelWidget.tsx b/src/components/structures/LeftPanelWidget.tsx index 89c0744cf8..16142069c4 100644 --- a/src/components/structures/LeftPanelWidget.tsx +++ b/src/components/structures/LeftPanelWidget.tsx @@ -27,6 +27,7 @@ import WidgetUtils, {IWidgetEvent} from "../../utils/WidgetUtils"; import {useAccountData} from "../../hooks/useAccountData"; import AppTile from "../views/elements/AppTile"; import {useSettingValue} from "../../hooks/useSettings"; +import UIStore from "../../stores/UIStore"; const MIN_HEIGHT = 100; const MAX_HEIGHT = 500; // or 50% of the window height @@ -63,7 +64,7 @@ const LeftPanelWidget: React.FC = () => { content = { setHeight(height + d.height); }} diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 1d794b05c4..c01437b313 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1819,7 +1819,7 @@ export default class MatrixChat extends React.PureComponent { handleResize = () => { const LHS_THRESHOLD = 1000; - const width = UIStore.instance.windowWith; + const width = UIStore.instance.windowWidth; if (width <= LHS_THRESHOLD && !this.state.collapseLhs) { dis.dispatch({ action: 'hide_left_panel' }); diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index d822b6a839..e3b0c10fb2 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -83,6 +83,7 @@ import { objectHasDiff } from "../../utils/objects"; import SpaceRoomView from "./SpaceRoomView"; import { IOpts } from "../../createRoom"; import {replaceableComponent} from "../../utils/replaceableComponent"; +import UIStore from "../../stores/UIStore"; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -1585,7 +1586,7 @@ export default class RoomView extends React.Component { // a maxHeight on the underlying remote video tag. // header + footer + status + give us at least 120px of scrollback at all times. - let auxPanelMaxHeight = window.innerHeight - + let auxPanelMaxHeight = UIStore.instance.windowHeight - (54 + // height of RoomHeader 36 + // height of the status area 51 + // minimum height of the message compmoser diff --git a/src/components/views/directory/NetworkDropdown.tsx b/src/components/views/directory/NetworkDropdown.tsx index 66b7321ce0..08787812f6 100644 --- a/src/components/views/directory/NetworkDropdown.tsx +++ b/src/components/views/directory/NetworkDropdown.tsx @@ -38,13 +38,14 @@ import withValidation from "../elements/Validation"; import { SettingLevel } from "../../../settings/SettingLevel"; import TextInputDialog from "../dialogs/TextInputDialog"; import QuestionDialog from "../dialogs/QuestionDialog"; +import UIStore from "../../../stores/UIStore"; export const ALL_ROOMS = Symbol("ALL_ROOMS"); const SETTING_NAME = "room_directory_servers"; const inPlaceOf = (elementRect: Pick) => ({ - right: window.innerWidth - elementRect.right, + right: UIStore.instance.windowWidth - elementRect.right, top: elementRect.top, chevronOffset: 0, chevronFace: ChevronFace.None, diff --git a/src/components/views/elements/Tooltip.tsx b/src/components/views/elements/Tooltip.tsx index 062d26c852..7e9ce9745c 100644 --- a/src/components/views/elements/Tooltip.tsx +++ b/src/components/views/elements/Tooltip.tsx @@ -22,6 +22,7 @@ import React, {Component, CSSProperties} from 'react'; import ReactDOM from 'react-dom'; import classNames from 'classnames'; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import UIStore from "../../../stores/UIStore"; const MIN_TOOLTIP_HEIGHT = 25; @@ -97,15 +98,15 @@ export default class Tooltip extends React.Component { // we need so that we're still centered. offset = Math.floor(parentBox.height - MIN_TOOLTIP_HEIGHT); } - + const width = UIStore.instance.windowWidth; const baseTop = (parentBox.top - 2 + this.props.yOffset) + window.pageYOffset; const top = baseTop + offset; - const right = window.innerWidth - parentBox.right - window.pageXOffset - 16; + const right = width - parentBox.right - window.pageXOffset - 16; const left = parentBox.right + window.pageXOffset + 6; const horizontalCenter = parentBox.right - window.pageXOffset - (parentBox.width / 2); switch (this.props.alignment) { case Alignment.Natural: - if (parentBox.right > window.innerWidth / 2) { + if (parentBox.right > width / 2) { style.right = right; style.top = top; break; diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index b963e741a1..dc644f1009 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -36,6 +36,7 @@ import {toRightOf} from "../../structures/ContextMenu"; import {copyPlaintext} from "../../../utils/strings"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import UIStore from "../../../stores/UIStore"; @replaceableComponent("views.messages.TextualBody") export default class TextualBody extends React.Component { @@ -143,7 +144,7 @@ export default class TextualBody extends React.Component { _addCodeExpansionButton(div, pre) { // Calculate how many percent does the pre element take up. // If it's less than 30% we don't add the expansion button. - const percentageOfViewport = pre.offsetHeight / window.innerHeight * 100; + const percentageOfViewport = pre.offsetHeight / UIStore.instance.windowHeight * 100; if (percentageOfViewport < 30) return; const button = document.createElement("span"); diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 88928290f4..937037f644 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -46,6 +46,7 @@ import WidgetContextMenu from "../context_menus/WidgetContextMenu"; import {useRoomMemberCount} from "../../../hooks/useRoomMembers"; import { Container, MAX_PINNED, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; import RoomName from "../elements/RoomName"; +import UIStore from "../../../stores/UIStore"; interface IProps { room: Room; @@ -116,8 +117,8 @@ const AppRow: React.FC = ({ app, room }) => { const rect = handle.current.getBoundingClientRect(); contextMenu = ; diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index 9798b282f6..6e56b9259b 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -66,6 +66,7 @@ import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRight import RoomAvatar from "../avatars/RoomAvatar"; import RoomName from "../elements/RoomName"; import {mediaFromMxc} from "../../../customisations/Media"; +import UIStore from "../../../stores/UIStore"; export interface IDevice { deviceId: string; @@ -1448,8 +1449,8 @@ const UserInfoHeader: React.FC<{ = ({ room, widgetId, onClose }) => { contextMenu = ( entry.target === document.body) .contentRect; - this.windowWith = width; + this.windowWidth = width; this.windowHeight = height; this.emit(UI_EVENTS.Resize, entries); From 45678de9a12cd3ea748a09b2c8b99a9869346262 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 25 May 2021 11:29:54 +0100 Subject: [PATCH 139/174] Stop overscroll in Firefox Nightly for macOS Firefox is working on an overscroll feature for macOS, similar to the one Safari has had for some time now. It doesn't really make sense in an application context, so this disables it. --- res/css/_common.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/css/_common.scss b/res/css/_common.scss index d6f85edb86..a05ec7eadd 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -45,6 +45,8 @@ html { N.B. Breaks things when we have legitimate horizontal overscroll */ height: 100%; overflow: hidden; + // Stop similar overscroll bounce in Firefox Nightly for macOS + overscroll-behavior: none; } body { From 85a73f2504408b58405762d1d54d36f3f358be1f Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 11:48:45 +0100 Subject: [PATCH 140/174] Fix copyright header in UIStore file --- src/stores/UIStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/UIStore.ts b/src/stores/UIStore.ts index 8e3a6fb31a..e7f6070627 100644 --- a/src/stores/UIStore.ts +++ b/src/stores/UIStore.ts @@ -1,5 +1,5 @@ /* -Copyright 2020 The Matrix.org Foundation C.I.C. +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. From cb88f37bbd146c62c4ead8043b10b1c3496be2e7 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 25 May 2021 12:28:16 +0100 Subject: [PATCH 141/174] Remove outdated diagnostic log The cited issue (https://github.com/vector-im/element-web/issues/11120) has since been fixed, so this "temporary" (2 years ago) logging is no longer needed. --- src/components/views/rooms/EventTile.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 19c5a7acaa..67df5a84ba 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -790,13 +790,6 @@ export default class EventTile extends React.Component { return null; } const eventId = this.props.mxEvent.getId(); - if (!eventId) { - // XXX: Temporary diagnostic logging for https://github.com/vector-im/element-web/issues/11120 - console.error("EventTile attempted to get relations for an event without an ID"); - // Use event's special `toJSON` method to log key data. - console.log(JSON.stringify(this.props.mxEvent, null, 4)); - console.trace("Stacktrace for https://github.com/vector-im/element-web/issues/11120"); - } return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction"); }; From 88af74e4a4b07325246ff6e8f9301f60c7955f97 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 12:45:19 +0100 Subject: [PATCH 142/174] Improve addEventsToTimeline performance scoping WhoIsTypingTile::setState --- src/components/views/rooms/WhoIsTypingTile.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/WhoIsTypingTile.js b/src/components/views/rooms/WhoIsTypingTile.js index a25b43fc3a..e69406505e 100644 --- a/src/components/views/rooms/WhoIsTypingTile.js +++ b/src/components/views/rooms/WhoIsTypingTile.js @@ -87,7 +87,9 @@ export default class WhoIsTypingTile extends React.Component { const userId = event.getSender(); // remove user from usersTyping const usersTyping = this.state.usersTyping.filter((m) => m.userId !== userId); - this.setState({usersTyping}); + if (usersTyping.length !== this.state.usersTyping.length) { + this.setState({usersTyping}); + } // abort timer if any this._abortUserTimer(userId); } From 7303166924b0c6ea492811f768354e27b35cd974 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 13:53:20 +0100 Subject: [PATCH 143/174] fix sticky headers when results num get displayed --- res/css/views/rooms/_RoomSublist.scss | 2 +- src/components/structures/LeftPanel.tsx | 15 ++++++--------- src/components/views/rooms/RoomListNumResults.tsx | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/res/css/views/rooms/_RoomSublist.scss b/res/css/views/rooms/_RoomSublist.scss index 1aafa8da0e..fa94425659 100644 --- a/res/css/views/rooms/_RoomSublist.scss +++ b/res/css/views/rooms/_RoomSublist.scss @@ -62,7 +62,7 @@ limitations under the License. position: fixed; height: 32px; // to match the header container // width set by JS - width: calc(100% - 22px); + width: calc(100% - 15px); } // We don't have a top style because the top is dependent on the room list header's diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index e929306940..4d7b80726f 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -110,6 +110,11 @@ export default class LeftPanel extends React.Component { dis.fire(Action.ViewRoomDirectory); }; + private refreshStickyHeaders = () => { + if (!this.listContainerRef.current) return; // ignore: no headers to sticky + this.handleStickyHeaders(this.listContainerRef.current); + } + private onBreadcrumbsUpdate = () => { const newVal = BreadcrumbsStore.instance.visible; if (newVal !== this.state.showBreadcrumbs) { @@ -243,18 +248,10 @@ export default class LeftPanel extends React.Component { if (!header.classList.contains("mx_RoomSublist_headerContainer_sticky")) { header.classList.add("mx_RoomSublist_headerContainer_sticky"); } - - const newWidth = `${headerStickyWidth}px`; - if (header.style.width !== newWidth) { - header.style.width = newWidth; - } } else if (!style.stickyTop && !style.stickyBottom) { if (header.classList.contains("mx_RoomSublist_headerContainer_sticky")) { header.classList.remove("mx_RoomSublist_headerContainer_sticky"); } - if (header.style.width) { - header.style.removeProperty('width'); - } } } @@ -432,7 +429,7 @@ export default class LeftPanel extends React.Component { {this.renderHeader()} {this.renderSearchExplore()} {this.renderBreadcrumbs()} - +
{ +interface IProps { + onVisibilityChange?: () => void +} + +const RoomListNumResults: React.FC = ({ onVisibilityChange }) => { const [count, setCount] = useState(null); useEventEmitter(RoomListStore.instance, LISTS_UPDATE_EVENT, () => { if (RoomListStore.instance.getFirstNameFilterCondition()) { @@ -32,6 +36,12 @@ const RoomListNumResults: React.FC = () => { } }); + useEffect(() => { + if (onVisibilityChange) { + onVisibilityChange(); + } + }, [count, onVisibilityChange]); + if (typeof count !== "number") return null; return
From a803e33ffe3ae4b17548a69161ae9c0cb2c160f8 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 14:10:16 +0100 Subject: [PATCH 144/174] Convert WhoIsTypingTile to TypeScript --- ...WhoIsTypingTile.js => WhoIsTypingTile.tsx} | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) rename src/components/views/rooms/{WhoIsTypingTile.js => WhoIsTypingTile.tsx} (83%) diff --git a/src/components/views/rooms/WhoIsTypingTile.js b/src/components/views/rooms/WhoIsTypingTile.tsx similarity index 83% rename from src/components/views/rooms/WhoIsTypingTile.js rename to src/components/views/rooms/WhoIsTypingTile.tsx index e69406505e..eaade3016b 100644 --- a/src/components/views/rooms/WhoIsTypingTile.js +++ b/src/components/views/rooms/WhoIsTypingTile.tsx @@ -16,36 +16,44 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import * as WhoIsTyping from '../../../WhoIsTyping'; import Timer from '../../../utils/Timer'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; import MemberAvatar from '../avatars/MemberAvatar'; -import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; + +import Room from "matrix-js-sdk/src/models/room"; +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + +interface IProps { + // the room this statusbar is representing. + room: Room, + onShown?: () => void, + onHidden?: () => void, + // Number of names to display in typing indication. E.g. set to 3, will + // result in "X, Y, Z and 100 others are typing." + whoIsTypingLimit: number, +} + +interface IState { + usersTyping: RoomMember[], + // a map with userid => Timer to delay + // hiding the "x is typing" message for a + // user so hiding it can coincide + // with the sent message by the other side + // resulting in less timeline jumpiness + delayedStopTypingTimers: any +} @replaceableComponent("views.rooms.WhoIsTypingTile") -export default class WhoIsTypingTile extends React.Component { - static propTypes = { - // the room this statusbar is representing. - room: PropTypes.object.isRequired, - onShown: PropTypes.func, - onHidden: PropTypes.func, - // Number of names to display in typing indication. E.g. set to 3, will - // result in "X, Y, Z and 100 others are typing." - whoIsTypingLimit: PropTypes.number, - }; - +export default class WhoIsTypingTile extends React.Component { static defaultProps = { whoIsTypingLimit: 3, }; state = { usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room), - // a map with userid => Timer to delay - // hiding the "x is typing" message for a - // user so hiding it can coincide - // with the sent message by the other side - // resulting in less timeline jumpiness delayedStopTypingTimers: {}, }; @@ -74,15 +82,15 @@ export default class WhoIsTypingTile extends React.Component { Object.values(this.state.delayedStopTypingTimers).forEach((t) => t.abort()); } - _isVisible(state) { + _isVisible(state: IState): boolean { return state.usersTyping.length !== 0 || Object.keys(state.delayedStopTypingTimers).length !== 0; } - isVisible = () => { + isVisible: () => boolean = () => { return this._isVisible(this.state); }; - onRoomTimeline = (event, room) => { + onRoomTimeline = (event: MatrixEvent, room: Room): void => { if (room?.roomId === this.props.room?.roomId) { const userId = event.getSender(); // remove user from usersTyping @@ -95,7 +103,7 @@ export default class WhoIsTypingTile extends React.Component { } }; - onRoomMemberTyping = (ev, member) => { + onRoomMemberTyping = (): void => { const usersTyping = WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room); this.setState({ delayedStopTypingTimers: this._updateDelayedStopTypingTimers(usersTyping), @@ -103,7 +111,7 @@ export default class WhoIsTypingTile extends React.Component { }); }; - _updateDelayedStopTypingTimers(usersTyping) { + _updateDelayedStopTypingTimers(usersTyping: RoomMember[]): void { const usersThatStoppedTyping = this.state.usersTyping.filter((a) => { return !usersTyping.some((b) => a.userId === b.userId); }); @@ -141,7 +149,7 @@ export default class WhoIsTypingTile extends React.Component { return delayedStopTypingTimers; } - _abortUserTimer(userId) { + _abortUserTimer(userId: string): void { const timer = this.state.delayedStopTypingTimers[userId]; if (timer) { timer.abort(); @@ -149,7 +157,7 @@ export default class WhoIsTypingTile extends React.Component { } } - _removeUserTimer(userId) { + _removeUserTimer(userId: string): void { const timer = this.state.delayedStopTypingTimers[userId]; if (timer) { const delayedStopTypingTimers = Object.assign({}, this.state.delayedStopTypingTimers); @@ -158,7 +166,7 @@ export default class WhoIsTypingTile extends React.Component { } } - _renderTypingIndicatorAvatars(users, limit) { + _renderTypingIndicatorAvatars(users: RoomMember[], limit: number): void { let othersCount = 0; if (users.length > limit) { othersCount = users.length - limit + 1; From d6443384213fc13bafcaf0830ed1f832c1fe08ec Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 14:34:19 +0100 Subject: [PATCH 145/174] WhoIsTypingTile TypeScript conversion --- .../views/rooms/WhoIsTypingTile.tsx | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/components/views/rooms/WhoIsTypingTile.tsx b/src/components/views/rooms/WhoIsTypingTile.tsx index eaade3016b..21afbc30f4 100644 --- a/src/components/views/rooms/WhoIsTypingTile.tsx +++ b/src/components/views/rooms/WhoIsTypingTile.tsx @@ -16,34 +16,34 @@ limitations under the License. */ import React from 'react'; +import Room from "matrix-js-sdk/src/models/room"; +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + import * as WhoIsTyping from '../../../WhoIsTyping'; import Timer from '../../../utils/Timer'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import MemberAvatar from '../avatars/MemberAvatar'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -import Room from "matrix-js-sdk/src/models/room"; -import { RoomMember } from "matrix-js-sdk/src/models/room-member"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; - interface IProps { // the room this statusbar is representing. - room: Room, - onShown?: () => void, - onHidden?: () => void, + room: Room; + onShown?: () => void; + onHidden?: () => void; // Number of names to display in typing indication. E.g. set to 3, will // result in "X, Y, Z and 100 others are typing." - whoIsTypingLimit: number, + whoIsTypingLimit: number; } interface IState { - usersTyping: RoomMember[], + usersTyping: RoomMember[]; // a map with userid => Timer to delay // hiding the "x is typing" message for a // user so hiding it can coincide // with the sent message by the other side // resulting in less timeline jumpiness - delayedStopTypingTimers: any + delayedStopTypingTimers: Record; } @replaceableComponent("views.rooms.WhoIsTypingTile") @@ -79,18 +79,18 @@ export default class WhoIsTypingTile extends React.Component { client.removeListener("RoomMember.typing", this.onRoomMemberTyping); client.removeListener("Room.timeline", this.onRoomTimeline); } - Object.values(this.state.delayedStopTypingTimers).forEach((t) => t.abort()); + Object.values(this.state.delayedStopTypingTimers).forEach((t) => (t as Timer).abort()); } - _isVisible(state: IState): boolean { + private _isVisible(state: IState): boolean { return state.usersTyping.length !== 0 || Object.keys(state.delayedStopTypingTimers).length !== 0; } - isVisible: () => boolean = () => { + public isVisible = (): boolean => { return this._isVisible(this.state); }; - onRoomTimeline = (event: MatrixEvent, room: Room): void => { + private onRoomTimeline = (event: MatrixEvent, room: Room): void => { if (room?.roomId === this.props.room?.roomId) { const userId = event.getSender(); // remove user from usersTyping @@ -99,19 +99,19 @@ export default class WhoIsTypingTile extends React.Component { this.setState({usersTyping}); } // abort timer if any - this._abortUserTimer(userId); + this.abortUserTimer(userId); } }; - onRoomMemberTyping = (): void => { + private onRoomMemberTyping = (): void => { const usersTyping = WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room); this.setState({ - delayedStopTypingTimers: this._updateDelayedStopTypingTimers(usersTyping), + delayedStopTypingTimers: this.updateDelayedStopTypingTimers(usersTyping), usersTyping, }); }; - _updateDelayedStopTypingTimers(usersTyping: RoomMember[]): void { + private updateDelayedStopTypingTimers(usersTyping: RoomMember[]): Record { const usersThatStoppedTyping = this.state.usersTyping.filter((a) => { return !usersTyping.some((b) => a.userId === b.userId); }); @@ -139,7 +139,7 @@ export default class WhoIsTypingTile extends React.Component { delayedStopTypingTimers[m.userId] = timer; timer.start(); timer.finished().then( - () => this._removeUserTimer(m.userId), // on elapsed + () => this.removeUserTimer(m.userId), // on elapsed () => {/* aborted */}, ); } @@ -149,15 +149,15 @@ export default class WhoIsTypingTile extends React.Component { return delayedStopTypingTimers; } - _abortUserTimer(userId: string): void { + private abortUserTimer(userId: string): void { const timer = this.state.delayedStopTypingTimers[userId]; if (timer) { timer.abort(); - this._removeUserTimer(userId); + this.removeUserTimer(userId); } } - _removeUserTimer(userId: string): void { + private removeUserTimer(userId: string): void { const timer = this.state.delayedStopTypingTimers[userId]; if (timer) { const delayedStopTypingTimers = Object.assign({}, this.state.delayedStopTypingTimers); @@ -166,7 +166,7 @@ export default class WhoIsTypingTile extends React.Component { } } - _renderTypingIndicatorAvatars(users: RoomMember[], limit: number): void { + private renderTypingIndicatorAvatars(users: RoomMember[], limit: number): JSX.Element[] { let othersCount = 0; if (users.length > limit) { othersCount = users.length - limit + 1; @@ -220,7 +220,7 @@ export default class WhoIsTypingTile extends React.Component { return (
  • - { this._renderTypingIndicatorAvatars(usersTyping, this.props.whoIsTypingLimit) } + { this.renderTypingIndicatorAvatars(usersTyping, this.props.whoIsTypingLimit) }
    { typingString } From b09dd8f1f89046ac0c94c1eb71e6b4025f557623 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 14:54:27 +0100 Subject: [PATCH 146/174] remove unused values --- src/components/structures/LeftPanel.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 4d7b80726f..22c60bff1e 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -157,9 +157,6 @@ export default class LeftPanel extends React.Component { const bottomEdge = list.offsetHeight + list.scrollTop; const sublists = list.querySelectorAll(".mx_RoomSublist:not(.mx_RoomSublist_hidden)"); - const headerRightMargin = 15; // calculated from margins and widths to align with non-sticky tiles - const headerStickyWidth = list.clientWidth - headerRightMargin; - // We track which styles we want on a target before making the changes to avoid // excessive layout updates. const targetStyles = new Map Date: Tue, 25 May 2021 14:57:07 +0100 Subject: [PATCH 147/174] remove CSS out of sync comment --- res/css/views/rooms/_RoomSublist.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/res/css/views/rooms/_RoomSublist.scss b/res/css/views/rooms/_RoomSublist.scss index fa94425659..b3e907af04 100644 --- a/res/css/views/rooms/_RoomSublist.scss +++ b/res/css/views/rooms/_RoomSublist.scss @@ -61,7 +61,6 @@ limitations under the License. &.mx_RoomSublist_headerContainer_sticky { position: fixed; height: 32px; // to match the header container - // width set by JS width: calc(100% - 15px); } From 17bbbff4797fac7b796f9d1b773b6cf0cefc4783 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 25 May 2021 16:12:34 +0100 Subject: [PATCH 148/174] Remove Promise allSettled polyfill as its widespread enough now and js-sdk uses it directly --- src/GroupAddressPicker.js | 3 +-- src/components/structures/GroupView.js | 6 +++--- .../views/dialogs/SpaceSettingsDialog.tsx | 3 +-- src/utils/promise.ts | 18 ------------------ 4 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/GroupAddressPicker.js b/src/GroupAddressPicker.js index d956189f0d..9497d9de4c 100644 --- a/src/GroupAddressPicker.js +++ b/src/GroupAddressPicker.js @@ -21,7 +21,6 @@ import MultiInviter from './utils/MultiInviter'; import { _t } from './languageHandler'; import {MatrixClientPeg} from './MatrixClientPeg'; import GroupStore from './stores/GroupStore'; -import {allSettled} from "./utils/promise"; import StyledCheckbox from './components/views/elements/StyledCheckbox'; export function showGroupInviteDialog(groupId) { @@ -120,7 +119,7 @@ function _onGroupInviteFinished(groupId, addrs) { function _onGroupAddRoomFinished(groupId, addrs, addRoomsPublicly) { const matrixClient = MatrixClientPeg.get(); const errorList = []; - return allSettled(addrs.map((addr) => { + return Promise.allSettled(addrs.map((addr) => { return GroupStore .addRoomToGroup(groupId, addr.address, addRoomsPublicly) .catch(() => { errorList.push(addr.address); }) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 3ab009d7b8..3a2c611cc9 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -36,7 +36,7 @@ import FlairStore from '../../stores/FlairStore'; import { showGroupAddRoomDialog } from '../../GroupAddressPicker'; import {makeGroupPermalink, makeUserPermalink} from "../../utils/permalinks/Permalinks"; import {Group} from "matrix-js-sdk/src/models/group"; -import {allSettled, sleep} from "../../utils/promise"; +import {sleep} from "../../utils/promise"; import RightPanelStore from "../../stores/RightPanelStore"; import AutoHideScrollbar from "./AutoHideScrollbar"; import {mediaFromMxc} from "../../customisations/Media"; @@ -99,7 +99,7 @@ class CategoryRoomList extends React.Component { onFinished: (success, addrs) => { if (!success) return; const errorList = []; - allSettled(addrs.map((addr) => { + Promise.allSettled(addrs.map((addr) => { return GroupStore .addRoomToGroupSummary(this.props.groupId, addr.address) .catch(() => { errorList.push(addr.address); }); @@ -274,7 +274,7 @@ class RoleUserList extends React.Component { onFinished: (success, addrs) => { if (!success) return; const errorList = []; - allSettled(addrs.map((addr) => { + Promise.allSettled(addrs.map((addr) => { return GroupStore .addUserToGroupSummary(addr.address) .catch(() => { errorList.push(addr.address); }); diff --git a/src/components/views/dialogs/SpaceSettingsDialog.tsx b/src/components/views/dialogs/SpaceSettingsDialog.tsx index dc6052650a..7453ff1d8b 100644 --- a/src/components/views/dialogs/SpaceSettingsDialog.tsx +++ b/src/components/views/dialogs/SpaceSettingsDialog.tsx @@ -30,7 +30,6 @@ import ToggleSwitch from "../elements/ToggleSwitch"; import AccessibleButton from "../elements/AccessibleButton"; import Modal from "../../../Modal"; import defaultDispatcher from "../../../dispatcher/dispatcher"; -import {allSettled} from "../../../utils/promise"; import {useDispatcher} from "../../../hooks/useDispatcher"; import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView"; @@ -91,7 +90,7 @@ const SpaceSettingsDialog: React.FC = ({ matrixClient: cli, space, onFin promises.push(cli.sendStateEvent(space.roomId, EventType.RoomJoinRules, { join_rule: joinRule }, "")); } - const results = await allSettled(promises); + const results = await Promise.allSettled(promises); setBusy(false); const failures = results.filter(r => r.status === "rejected"); if (failures.length > 0) { diff --git a/src/utils/promise.ts b/src/utils/promise.ts index f828ddfdaf..4ebbb27141 100644 --- a/src/utils/promise.ts +++ b/src/utils/promise.ts @@ -51,24 +51,6 @@ export function defer(): IDeferred { return {resolve, reject, promise}; } -// Promise.allSettled polyfill until browser support is stable in Firefox -export function allSettled(promises: Promise[]): Promise | ISettledRejected>> { - if (Promise.allSettled) { - return Promise.allSettled(promises); - } - - // @ts-ignore - typescript isn't smart enough to see the disjoint here - return Promise.all(promises.map((promise) => { - return promise.then(value => ({ - status: "fulfilled", - value, - })).catch(reason => ({ - status: "rejected", - reason, - })); - })); -} - // Helper method to retry a Promise a given number of times or until a predicate fails export async function retry(fn: () => Promise, num: number, predicate?: (e: E) => boolean) { let lastErr: E; From e934f81521dbe50dbdfca807fce003fb1f816573 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 16:34:52 +0100 Subject: [PATCH 149/174] Skip generatePreview if event is not part of the live timeline --- src/stores/room-list/MessagePreviewStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index 10e5cf554e..4de612c7bd 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -176,7 +176,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient { if (payload.action === 'MatrixActions.Room.timeline' || payload.action === 'MatrixActions.Event.decrypted') { const event = payload.event; // TODO: Type out the dispatcher - if (!this.previews.has(event.getRoomId())) return; // not important + if (!this.previews.has(event.getRoomId()) || !event.isLiveEvent) return; // not important await this.generatePreview(this.matrixClient.getRoom(event.getRoomId()), TAG_ANY); } } From 80bd1304213b6b9818a1ad4a5bf6cc855422114b Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 16:58:23 +0100 Subject: [PATCH 150/174] Prevent DecoratedRoomAvatar to update its state for the same value --- src/components/views/avatars/DecoratedRoomAvatar.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/avatars/DecoratedRoomAvatar.tsx b/src/components/views/avatars/DecoratedRoomAvatar.tsx index f15538eabf..42aef24086 100644 --- a/src/components/views/avatars/DecoratedRoomAvatar.tsx +++ b/src/components/views/avatars/DecoratedRoomAvatar.tsx @@ -119,7 +119,10 @@ export default class DecoratedRoomAvatar extends React.PureComponent Date: Tue, 25 May 2021 17:26:43 +0100 Subject: [PATCH 151/174] Fix accessing currentState on an invalid joinedRoom --- src/components/structures/SpaceRoomDirectory.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index 3f1679c97e..3985553b20 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -102,7 +102,7 @@ const Tile: React.FC = ({ children, }) => { const cli = MatrixClientPeg.get(); - const joinedRoom = cli.getRoom(room.room_id)?.getMyMembership() === "join" && cli.getRoom(room.room_id); + 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] || (room.room_type === RoomType.Space ? _t("Unnamed Space") : _t("Unnamed Room")); @@ -162,7 +162,7 @@ const Tile: React.FC = ({ description += " · " + _t("%(count)s rooms", { count: numChildRooms }); } - const topic = joinedRoom?.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || room.topic; + const topic = joinedRoom?.currentState?.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || room.topic; if (topic) { description += " · " + topic; } From a4907f8061a1d0e2ec84c6edf5f9669fa7bbc821 Mon Sep 17 00:00:00 2001 From: Jaiwanth Date: Wed, 26 May 2021 12:57:39 +0530 Subject: [PATCH 152/174] Destroy playback instance on unmount --- src/components/views/messages/MVoiceMessageBody.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/MVoiceMessageBody.tsx b/src/components/views/messages/MVoiceMessageBody.tsx index 4a2a83465d..a6bd30ac6e 100644 --- a/src/components/views/messages/MVoiceMessageBody.tsx +++ b/src/components/views/messages/MVoiceMessageBody.tsx @@ -71,10 +71,16 @@ export default class MVoiceMessageBody extends React.PureComponent Date: Wed, 26 May 2021 13:07:57 +0530 Subject: [PATCH 153/174] Update src/components/views/messages/MVoiceMessageBody.tsx Co-authored-by: Michael Telatynski <7t3chguy@googlemail.com> --- src/components/views/messages/MVoiceMessageBody.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/views/messages/MVoiceMessageBody.tsx b/src/components/views/messages/MVoiceMessageBody.tsx index a6bd30ac6e..d65de7697a 100644 --- a/src/components/views/messages/MVoiceMessageBody.tsx +++ b/src/components/views/messages/MVoiceMessageBody.tsx @@ -76,9 +76,7 @@ export default class MVoiceMessageBody extends React.PureComponent Date: Wed, 26 May 2021 10:15:31 +0100 Subject: [PATCH 154/174] Fix preview generate check --- src/stores/room-list/MessagePreviewStore.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index 4de612c7bd..f5b9d9bc6a 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -176,7 +176,8 @@ export class MessagePreviewStore extends AsyncStoreWithClient { if (payload.action === 'MatrixActions.Room.timeline' || payload.action === 'MatrixActions.Event.decrypted') { const event = payload.event; // TODO: Type out the dispatcher - if (!this.previews.has(event.getRoomId()) || !event.isLiveEvent) return; // not important + const isHistoricalEvent = payload.hasOwnProperty("isLiveEvent") && !payload.isLiveEvent + if (!this.previews.has(event.getRoomId()) || isHistoricalEvent) return; // not important await this.generatePreview(this.matrixClient.getRoom(event.getRoomId()), TAG_ANY); } } From 3f10279e152c20b05569e53a9e60d7b65d62871d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 26 May 2021 16:38:02 +0100 Subject: [PATCH 155/174] Invite Dialog don't show warning modals after unmount, it is jarring --- src/components/views/dialogs/InviteDialog.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index ec9c71ccbe..868d89bfa4 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -351,6 +351,7 @@ export default class InviteDialog extends React.PureComponent { this.setState({consultFirst: ev.target.checked}); } @@ -1027,6 +1032,7 @@ export default class InviteDialog extends React.PureComponent 0) { const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog'); From d4ca1babbe5228d028ebcc9ca1e89f66f1955337 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 26 May 2021 16:47:21 +0100 Subject: [PATCH 156/174] Update reactions row on event decryption This fixes a race (perhaps revealed by the recent lazy decryption work) where the reactions row have reactions to show, but the event would not be decrypted, so they wouldn't render. Adding a decryption listener gets things moving again. Fixes https://github.com/vector-im/element-web/issues/17461 --- .../views/messages/ReactionsRow.tsx | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/components/views/messages/ReactionsRow.tsx b/src/components/views/messages/ReactionsRow.tsx index f5910473ff..797f997d27 100644 --- a/src/components/views/messages/ReactionsRow.tsx +++ b/src/components/views/messages/ReactionsRow.tsx @@ -81,18 +81,38 @@ export default class ReactionsRow extends React.PureComponent { constructor(props, context) { super(props, context); - if (props.reactions) { - props.reactions.on("Relations.add", this.onReactionsChange); - props.reactions.on("Relations.remove", this.onReactionsChange); - props.reactions.on("Relations.redaction", this.onReactionsChange); - } - this.state = { myReactions: this.getMyReactions(), showAll: false, }; } + componentDidMount() { + const { mxEvent, reactions } = this.props; + + if (mxEvent.isBeingDecrypted() || mxEvent.shouldAttemptDecryption()) { + mxEvent.once("Event.decrypted", this.onDecrypted); + } + + if (reactions) { + reactions.on("Relations.add", this.onReactionsChange); + reactions.on("Relations.remove", this.onReactionsChange); + reactions.on("Relations.redaction", this.onReactionsChange); + } + } + + componentWillUnmount() { + const { mxEvent, reactions } = this.props; + + mxEvent.off("Event.decrypted", this.onDecrypted); + + if (reactions) { + reactions.off("Relations.add", this.onReactionsChange); + reactions.off("Relations.remove", this.onReactionsChange); + reactions.off("Relations.redaction", this.onReactionsChange); + } + } + componentDidUpdate(prevProps) { if (prevProps.reactions !== this.props.reactions) { this.props.reactions.on("Relations.add", this.onReactionsChange); @@ -102,21 +122,9 @@ export default class ReactionsRow extends React.PureComponent { } } - componentWillUnmount() { - if (this.props.reactions) { - this.props.reactions.removeListener( - "Relations.add", - this.onReactionsChange, - ); - this.props.reactions.removeListener( - "Relations.remove", - this.onReactionsChange, - ); - this.props.reactions.removeListener( - "Relations.redaction", - this.onReactionsChange, - ); - } + private onDecrypted = () => { + // Decryption changes whether the event is actionable + this.forceUpdate(); } onReactionsChange = () => { From 60d161caf58e63f41650af9286bb03d08440ba65 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 26 May 2021 16:47:46 +0100 Subject: [PATCH 157/174] Apply some actual typescripting to this file --- src/components/views/dialogs/InviteDialog.tsx | 195 +++++++++--------- 1 file changed, 96 insertions(+), 99 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index 868d89bfa4..b9a5a68f83 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -47,6 +47,8 @@ import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import {replaceableComponent} from "../../../utils/replaceableComponent"; import {mediaFromMxc} from "../../../customisations/Media"; import {getAddressType} from "../../../UserAddress"; +import BaseAvatar from '../avatars/BaseAvatar'; +import AccessibleButton from '../elements/AccessibleButton'; // we have a number of types defined from the Matrix spec which can't reasonably be altered here. /* eslint-disable camelcase */ @@ -61,43 +63,41 @@ const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is c // This is the interface that is expected by various components in this file. It is a bit // awkward because it also matches the RoomMember class from the js-sdk with some extra support // for 3PIDs/email addresses. -// -// XXX: We should use TypeScript interfaces instead of this weird "abstract" class. -class Member { +abstract class Member { /** * The display name of this Member. For users this should be their profile's display * name or user ID if none set. For 3PIDs this should be the 3PID address (email). */ - get name(): string { throw new Error("Member class not implemented"); } + public abstract get name(): string; /** * The ID of this Member. For users this should be their user ID. For 3PIDs this should * be the 3PID address (email). */ - get userId(): string { throw new Error("Member class not implemented"); } + public abstract get userId(): string; /** * Gets the MXC URL of this Member's avatar. For users this should be their profile's * avatar MXC URL or null if none set. For 3PIDs this should always be null. */ - getMxcAvatarUrl(): string { throw new Error("Member class not implemented"); } + public abstract getMxcAvatarUrl(): string; } class DirectoryMember extends Member { - _userId: string; - _displayName: string; - _avatarUrl: string; + private readonly _userId: string; + private readonly displayName: string; + private readonly avatarUrl: string; constructor(userDirResult: {user_id: string, display_name: string, avatar_url: string}) { super(); this._userId = userDirResult.user_id; - this._displayName = userDirResult.display_name; - this._avatarUrl = userDirResult.avatar_url; + this.displayName = userDirResult.display_name; + this.avatarUrl = userDirResult.avatar_url; } // These next class members are for the Member interface get name(): string { - return this._displayName || this._userId; + return this.displayName || this._userId; } get userId(): string { @@ -105,32 +105,32 @@ class DirectoryMember extends Member { } getMxcAvatarUrl(): string { - return this._avatarUrl; + return this.avatarUrl; } } class ThreepidMember extends Member { - _id: string; + private readonly id: string; constructor(id: string) { super(); - this._id = id; + this.id = id; } // This is a getter that would be falsey on all other implementations. Until we have // better type support in the react-sdk we can use this trick to determine the kind // of 3PID we're dealing with, if any. get isEmail(): boolean { - return this._id.includes('@'); + return this.id.includes('@'); } // These next class members are for the Member interface get name(): string { - return this._id; + return this.id; } get userId(): string { - return this._id; + return this.id; } getMxcAvatarUrl(): string { @@ -140,11 +140,11 @@ class ThreepidMember extends Member { interface IDMUserTileProps { member: RoomMember; - onRemove: (RoomMember) => any; + onRemove(member: RoomMember): void; } class DMUserTile extends React.PureComponent { - _onRemove = (e) => { + private onRemove = (e) => { // Stop the browser from highlighting text e.preventDefault(); e.stopPropagation(); @@ -153,9 +153,6 @@ class DMUserTile extends React.PureComponent { }; render() { - const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar"); - const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); - const avatarSize = 20; const avatar = this.props.member.isEmail ? { closeButton = ( {_t('Remove')} { interface IDMRoomTileProps { member: RoomMember; lastActiveTs: number; - onToggle: (RoomMember) => any; + onToggle(member: RoomMember): void; highlightWord: string; isSelected: boolean; } class DMRoomTile extends React.PureComponent { - _onClick = (e) => { + private onClick = (e) => { // Stop the browser from highlighting text e.preventDefault(); e.stopPropagation(); @@ -215,7 +212,7 @@ class DMRoomTile extends React.PureComponent { this.props.onToggle(this.props.member); }; - _highlightName(str: string) { + private highlightName(str: string) { if (!this.props.highlightWord) return str; // We convert things to lowercase for index searching, but pull substrings from @@ -252,8 +249,6 @@ class DMRoomTile extends React.PureComponent { } render() { - const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar"); - let timestamp = null; if (this.props.lastActiveTs) { const humanTs = humanizeTime(this.props.lastActiveTs); @@ -291,13 +286,13 @@ class DMRoomTile extends React.PureComponent { const caption = this.props.member.isEmail ? _t("Invite by email") - : this._highlightName(this.props.member.userId); + : this.highlightName(this.props.member.userId); return ( -
    +
    {stackedAvatar} -
    {this._highlightName(this.props.member.name)}
    +
    {this.highlightName(this.props.member.name)}
    {caption}
    {timestamp} @@ -308,7 +303,7 @@ class DMRoomTile extends React.PureComponent { interface IInviteDialogProps { // Takes an array of user IDs/emails to invite. - onFinished: (toInvite?: string[]) => any; + onFinished: (toInvite?: string[]) => void; // The kind of invite being performed. Assumed to be KIND_DM if // not provided. @@ -349,8 +344,8 @@ export default class InviteDialog extends React.PureComponent(); private unmounted = false; constructor(props) { @@ -379,7 +374,7 @@ export default class InviteDialog extends React.PureComponent): {userId: string, user: RoomMember, lastActive: number}[] { + public static buildRecents(excludedTargetIds: Set): { + userId: string, + user: RoomMember, + lastActive: number, + }[] { const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room // Also pull in all the rooms tagged as DefaultTagID.DM so we don't miss anything. Sometimes the @@ -472,7 +469,7 @@ export default class InviteDialog extends React.PureComponent): {userId: string, user: RoomMember}[] { + private buildSuggestions(excludedTargetIds: Set): {userId: string, user: RoomMember}[] { const maxConsideredMembers = 200; const joinedRooms = MatrixClientPeg.get().getRooms() .filter(r => r.getMyMembership() === 'join' && r.getJoinedMemberCount() <= maxConsideredMembers); @@ -590,7 +587,7 @@ export default class InviteDialog extends React.PureComponent ({userId: m.member.userId, user: m.member})); } - _shouldAbortAfterInviteError(result): boolean { + private shouldAbortAfterInviteError(result): boolean { const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error'); if (failedUsers.length > 0) { console.log("Failed to invite users: ", result); @@ -605,7 +602,7 @@ export default class InviteDialog extends React.PureComponent { + private startDm = async () => { this.setState({busy: true}); const client = MatrixClientPeg.get(); - const targets = this._convertFilter(); + const targets = this.convertFilter(); const targetIds = targets.map(t => t.userId); // Check if there is already a DM with these people and reuse it if possible. @@ -699,11 +696,11 @@ export default class InviteDialog extends React.PureComponent { + private inviteUsers = async () => { const startTime = CountlyAnalytics.getTimestamp(); this.setState({busy: true}); - this._convertFilter(); - const targets = this._convertFilter(); + this.convertFilter(); + const targets = this.convertFilter(); const targetIds = targets.map(t => t.userId); const cli = MatrixClientPeg.get(); @@ -720,7 +717,7 @@ export default class InviteDialog extends React.PureComponent { - this._convertFilter(); - const targets = this._convertFilter(); + private transferCall = async () => { + this.convertFilter(); + const targets = this.convertFilter(); const targetIds = targets.map(t => t.userId); if (targetIds.length > 1) { this.setState({ @@ -795,26 +792,26 @@ export default class InviteDialog extends React.PureComponent { + private onKeyDown = (e) => { if (this.state.busy) return; const value = e.target.value.trim(); const hasModifiers = e.ctrlKey || e.shiftKey || e.metaKey; if (!value && this.state.targets.length > 0 && e.key === Key.BACKSPACE && !hasModifiers) { // when the field is empty and the user hits backspace remove the right-most target e.preventDefault(); - this._removeMember(this.state.targets[this.state.targets.length - 1]); + this.removeMember(this.state.targets[this.state.targets.length - 1]); } else if (value && e.key === Key.ENTER && !hasModifiers) { // when the user hits enter with something in their field try to convert it e.preventDefault(); - this._convertFilter(); + this.convertFilter(); } else if (value && e.key === Key.SPACE && !hasModifiers && value.includes("@") && !value.includes(" ")) { // when the user hits space and their input looks like an e-mail/MXID then try to convert it e.preventDefault(); - this._convertFilter(); + this.convertFilter(); } }; - _updateSuggestions = async (term) => { + private updateSuggestions = async (term) => { MatrixClientPeg.get().searchUserDirectory({term}).then(async r => { if (term !== this.state.filterText) { // Discard the results - we were probably too slow on the server-side to make @@ -923,30 +920,30 @@ export default class InviteDialog extends React.PureComponent { + private updateFilter = (e) => { const term = e.target.value; this.setState({filterText: term}); // Debounce server lookups to reduce spam. We don't clear the existing server // results because they might still be vaguely accurate, likewise for races which // could happen here. - if (this._debounceTimer) { - clearTimeout(this._debounceTimer); + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); } - this._debounceTimer = setTimeout(() => { - this._updateSuggestions(term); + this.debounceTimer = setTimeout(() => { + this.updateSuggestions(term); }, 150); // 150ms debounce (human reaction time + some) }; - _showMoreRecents = () => { + private showMoreRecents = () => { this.setState({numRecentsShown: this.state.numRecentsShown + INCREMENT_ROOMS_SHOWN}); }; - _showMoreSuggestions = () => { + private showMoreSuggestions = () => { this.setState({numSuggestionsShown: this.state.numSuggestionsShown + INCREMENT_ROOMS_SHOWN}); }; - _toggleMember = (member: Member) => { + private toggleMember = (member: Member) => { if (!this.state.busy) { let filterText = this.state.filterText; const targets = this.state.targets.map(t => t); // cheap clone for mutation @@ -959,13 +956,13 @@ export default class InviteDialog extends React.PureComponent { + private removeMember = (member: Member) => { const targets = this.state.targets.map(t => t); // cheap clone for mutation const idx = targets.indexOf(member); if (idx >= 0) { @@ -973,12 +970,12 @@ export default class InviteDialog extends React.PureComponent { + private onPaste = async (e) => { if (this.state.filterText) { // if the user has already typed something, just let them // paste normally. @@ -1049,17 +1046,17 @@ export default class InviteDialog extends React.PureComponent { + private onClickInputArea = (e) => { // Stop the browser from highlighting text e.preventDefault(); e.stopPropagation(); - if (this._editorRef && this._editorRef.current) { - this._editorRef.current.focus(); + if (this.editorRef && this.editorRef.current) { + this.editorRef.current.focus(); } }; - _onUseDefaultIdentityServerClick = (e) => { + private onUseDefaultIdentityServerClick = (e) => { e.preventDefault(); // Update the IS in account data. Actually using it may trigger terms. @@ -1068,21 +1065,21 @@ export default class InviteDialog extends React.PureComponent { + private onManageSettingsClick = (e) => { e.preventDefault(); dis.fire(Action.ViewUserSettings); this.props.onFinished(); }; - _onCommunityInviteClick = (e) => { + private onCommunityInviteClick = (e) => { this.props.onFinished(); showCommunityInviteDialog(CommunityPrototypeStore.instance.getSelectedCommunityId()); }; - _renderSection(kind: "recents"|"suggestions") { + private renderSection(kind: "recents"|"suggestions") { let sourceMembers = kind === 'recents' ? this.state.recents : this.state.suggestions; let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown; - const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this); + const showMoreFn = kind === 'recents' ? this.showMoreRecents.bind(this) : this.showMoreSuggestions.bind(this); const lastActive = (m) => kind === 'recents' ? m.lastActive : null; let sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions"); let sectionSubname = null; @@ -1162,7 +1159,7 @@ export default class InviteDialog extends React.PureComponent t.userId === r.userId)} /> @@ -1177,32 +1174,32 @@ export default class InviteDialog extends React.PureComponent ( - + )); const input = ( ); return ( -
    +
    {targets} {input}
    ); } - _renderIdentityServerWarning() { + private renderIdentityServerWarning() { if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer || !SettingsStore.getValue(UIFeature.IdentityServer) ) { @@ -1220,8 +1217,8 @@ export default class InviteDialog extends React.PureComponent {sub}, - settings: sub => {sub}, + default: sub => {sub}, + settings: sub => {sub}, }, )}
    ); @@ -1231,7 +1228,7 @@ export default class InviteDialog extends React.PureComponentSettings.", {}, { - settings: sub => {sub}, + settings: sub => {sub}, }, )}
    ); @@ -1304,7 +1301,7 @@ export default class InviteDialog extends React.PureComponent{sub} ); }, @@ -1315,7 +1312,7 @@ export default class InviteDialog extends React.PureComponent; } buttonText = _t("Go"); - goButtonFn = this._startDm; + goButtonFn = this.startDm; } else if (this.props.kind === KIND_INVITE) { const room = MatrixClientPeg.get()?.getRoom(this.props.roomId); const isSpace = SettingsStore.getValue("feature_spaces") && room?.isSpaceRoom(); @@ -1354,7 +1351,7 @@ export default class InviteDialog extends React.PureComponent
    { { - setPanelCollapsed(!isPanelCollapsed); - if (menuDisplayed) closeMenu(); - }} + onClick={() => setPanelCollapsed(!isPanelCollapsed)} title={expandCollapseButtonTitle} /> { contextMenu } From be22a325f6d42b5d52b666462449b6aae30f8b2e Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 27 May 2021 08:57:27 +0100 Subject: [PATCH 160/174] Prevent having duplicates in pending room state --- src/components/structures/UserMenu.tsx | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index c05f74a436..32fd3aa471 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -69,7 +69,7 @@ interface IState { contextMenuPosition: PartialDOMRect; isDarkTheme: boolean; selectedSpace?: Room; - pendingRoomJoin: string[] + pendingRoomJoin: Set } @replaceableComponent("structures.UserMenu") @@ -86,7 +86,7 @@ export default class UserMenu extends React.Component { this.state = { contextMenuPosition: null, isDarkTheme: this.isUserOnDarkTheme(), - pendingRoomJoin: [], + pendingRoomJoin: new Set(), }; OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); @@ -168,28 +168,24 @@ export default class UserMenu extends React.Component { } }; - private addPendingJoinRoom(roomId) { + private addPendingJoinRoom(roomId: string): void { this.setState({ - pendingRoomJoin: [ - ...this.state.pendingRoomJoin, - roomId, - ], + pendingRoomJoin: new Set(this.state.pendingRoomJoin) + .add(roomId), }); } - private removePendingJoinRoom(roomId) { - const newPendingRoomJoin = this.state.pendingRoomJoin.filter(pendingJoinRoomId => { - return pendingJoinRoomId !== roomId; - }); - if (newPendingRoomJoin.length !== this.state.pendingRoomJoin.length) { + private removePendingJoinRoom(roomId: string): void { + if (this.state.pendingRoomJoin.has(roomId)) { + this.state.pendingRoomJoin.delete(roomId); this.setState({ - pendingRoomJoin: newPendingRoomJoin, + pendingRoomJoin: new Set(this.state.pendingRoomJoin), }) } } get hasPendingActions(): boolean { - return this.state.pendingRoomJoin.length > 0; + return Array.from(this.state.pendingRoomJoin).length > 0; } private onOpenMenuClick = (ev: React.MouseEvent) => { From 9007afabfa7b12c9b1358c9f9c4584b49b71c3f1 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 27 May 2021 08:57:48 +0100 Subject: [PATCH 161/174] Fix JoinRoomError action name typo --- src/dispatcher/actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 0c9a4160b5..9fc0b54eea 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -152,5 +152,5 @@ export enum Action { /** * Fired when joining a room failed */ - JoinRoomError = "join_room", + JoinRoomError = "join_room_error", } From 2d15d66df81de03f502d1c32d83c623f38384041 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 27 May 2021 08:58:11 +0100 Subject: [PATCH 162/174] Listen to home server sync update to remove pending rooms --- src/components/structures/UserMenu.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 32fd3aa471..7543dff953 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -106,6 +106,7 @@ export default class UserMenu extends React.Component { this.dispatcherRef = defaultDispatcher.register(this.onAction); this.themeWatcherRef = SettingsStore.watchSetting("theme", null, this.onThemeChanged); this.tagStoreRef = GroupFilterOrderStore.addListener(this.onTagStoreUpdate); + MatrixClientPeg.get().on("Room", this.onRoom); } public componentWillUnmount() { @@ -117,6 +118,11 @@ export default class UserMenu extends React.Component { if (SettingsStore.getValue("feature_spaces")) { SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate); } + MatrixClientPeg.get().removeListener("Room", this.onRoom); + } + + private onRoom = (room: Room): void => { + this.removePendingJoinRoom(room.roomId); } private onTagStoreUpdate = () => { From fbb6a42d86b3f84fbe248752c80cf2129f6f2e28 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 27 May 2021 09:05:51 +0100 Subject: [PATCH 163/174] fix reading Set length --- src/components/structures/UserMenu.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 7543dff953..cc50db02ee 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -190,8 +190,8 @@ export default class UserMenu extends React.Component { } } - get hasPendingActions(): boolean { - return Array.from(this.state.pendingRoomJoin).length > 0; + get pendingActionsCount(): number { + return Array.from(this.state.pendingRoomJoin).length; } private onOpenMenuClick = (ev: React.MouseEvent) => { @@ -655,11 +655,11 @@ export default class UserMenu extends React.Component { /> {name} - {this.hasPendingActions && ( + {this.pendingActionsCount > 0 && ( )} From bd653ac5a89b1a47b7182c73bc5464f835645f07 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 27 May 2021 09:11:43 +0100 Subject: [PATCH 164/174] fix edge cases around space panel auto collapsing/closing menu --- src/components/views/spaces/SpacePanel.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index aac1f609d5..eb63b21f0e 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -128,7 +128,9 @@ const SpacePanel = () => { const [isPanelCollapsed, setPanelCollapsed] = useState(true); useEffect(() => { - closeMenu(); + if (!isPanelCollapsed && menuDisplayed) { + closeMenu(); + } }, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps const newClasses = classNames("mx_SpaceButton_new", { @@ -239,8 +241,8 @@ const SpacePanel = () => { className={newClasses} tooltip={menuDisplayed ? _t("Cancel") : _t("Create a space")} onClick={menuDisplayed ? closeMenu : () => { - openMenu(); if (!isPanelCollapsed) setPanelCollapsed(true); + openMenu(); }} isNarrow={isPanelCollapsed} /> From b8a7d5d730094f5442d063ebfdb9bff6df38dcb3 Mon Sep 17 00:00:00 2001 From: Germain Date: Thu, 27 May 2021 09:23:56 +0100 Subject: [PATCH 165/174] Better Set handling Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/UserMenu.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index cc50db02ee..ff00905a59 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -69,7 +69,7 @@ interface IState { contextMenuPosition: PartialDOMRect; isDarkTheme: boolean; selectedSpace?: Room; - pendingRoomJoin: Set + pendingRoomJoin: Set; } @replaceableComponent("structures.UserMenu") @@ -182,8 +182,7 @@ export default class UserMenu extends React.Component { } private removePendingJoinRoom(roomId: string): void { - if (this.state.pendingRoomJoin.has(roomId)) { - this.state.pendingRoomJoin.delete(roomId); + if (this.state.pendingRoomJoin.delete(roomId)) { this.setState({ pendingRoomJoin: new Set(this.state.pendingRoomJoin), }) From f31ec343f4b587d9fb928653418ccbb03e00158c Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 27 May 2021 09:26:26 +0100 Subject: [PATCH 166/174] Use Set::size instead of Array.from()::length --- src/components/structures/UserMenu.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index ff00905a59..fb4829f879 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -189,10 +189,6 @@ export default class UserMenu extends React.Component { } } - get pendingActionsCount(): number { - return Array.from(this.state.pendingRoomJoin).length; - } - private onOpenMenuClick = (ev: React.MouseEvent) => { ev.preventDefault(); ev.stopPropagation(); @@ -654,11 +650,11 @@ export default class UserMenu extends React.Component { /> {name} - {this.pendingActionsCount > 0 && ( + {this.state.pendingRoomJoin.size > 0 && ( )} From d6d09227530da472ba5ecd9de99c32891ed9e82c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 27 May 2021 10:11:28 +0100 Subject: [PATCH 167/174] Fix misleading child counts in spaces --- src/components/structures/SpaceRoomDirectory.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index 3985553b20..8d59fe6c68 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -319,7 +319,7 @@ export const HierarchyLevel = ({ key={roomId} room={rooms.get(roomId)} numChildRooms={Array.from(relations.get(roomId)?.values() || []) - .filter(ev => rooms.get(ev.state_key)?.room_type !== RoomType.Space).length} + .filter(ev => rooms.has(ev.state_key) && !rooms.get(ev.state_key).room_type).length} suggested={relations.get(spaceId)?.get(roomId)?.content.suggested} selected={selectedMap?.get(spaceId)?.has(roomId)} onViewRoomClick={(autoJoin) => { @@ -437,7 +437,7 @@ export const SpaceHierarchy: React.FC = ({ let content; if (roomsMap) { - const numRooms = Array.from(roomsMap.values()).filter(r => r.room_type !== RoomType.Space).length; + const numRooms = Array.from(roomsMap.values()).filter(r => !r.room_type).length; const numSpaces = roomsMap.size - numRooms - 1; // -1 at the end to exclude the space we are looking at let countsStr; From fcae19f8318a3bf0bd8908162e56bd0b3d2f3884 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 27 May 2021 12:36:16 +0100 Subject: [PATCH 168/174] Track left panel width using ResizeObserver --- src/@types/global.d.ts | 2 + src/components/structures/LeftPanel.tsx | 8 +++- src/stores/UIStore.ts | 53 +++++++++++++++++++++---- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 63966d96fa..22280b8a28 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -43,6 +43,7 @@ import TypingStore from "../stores/TypingStore"; import { EventIndexPeg } from "../indexing/EventIndexPeg"; import {VoiceRecordingStore} from "../stores/VoiceRecordingStore"; import PerformanceMonitor from "../performance"; +import UIStore from "../stores/UIStore"; declare global { interface Window { @@ -82,6 +83,7 @@ declare global { mxEventIndexPeg: EventIndexPeg; mxPerformanceMonitor: PerformanceMonitor; mxPerformanceEntryNames: any; + mxUIStore: UIStore; } interface Document { diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 22c60bff1e..80cd9bc465 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -67,6 +67,7 @@ const cssClasses = [ @replaceableComponent("structures.LeftPanel") export default class LeftPanel extends React.Component { + private ref: React.RefObject = createRef(); private listContainerRef: React.RefObject = createRef(); private groupFilterPanelWatcherRef: string; private bgImageWatcherRef: string; @@ -93,6 +94,10 @@ export default class LeftPanel extends React.Component { }); } + public componentDidMount() { + UIStore.instance.trackElementDimensions("LeftPanel", this.ref.current); + } + public componentWillUnmount() { SettingsStore.unwatchSetting(this.groupFilterPanelWatcherRef); SettingsStore.unwatchSetting(this.bgImageWatcherRef); @@ -100,6 +105,7 @@ export default class LeftPanel extends React.Component { RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); OwnProfileStore.instance.off(UPDATE_EVENT, this.onBackgroundImageUpdate); SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace); + UIStore.instance.stopTrackingElementDimensions("LeftPanel"); } private updateActiveSpace = (activeSpace: Room) => { @@ -420,7 +426,7 @@ export default class LeftPanel extends React.Component { ); return ( -
    +
    {leftLeftPanel}