From d16ab09866dae02538903ec63b457ef92ad1fec5 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 29 Aug 2024 16:26:10 +0200 Subject: [PATCH] Display pinned messages on a banner at the top of a room (#12917) * Move pinned message hooks to a dedicated file * Add a banner at the top of a room to display the pinned messages * Put the pinning banner behind labs pinning labs flag * Add redacted event support * Handle UTD in pinning message banner * Add tests for redaction * Make all the banner clickable * Add tests for PinnedMessageBanner.tsx * Add e2e tests for the pinned message banner * Review changes --- playwright/e2e/pinned-messages/index.ts | 34 ++- .../pinned-messages/pinned-messages.spec.ts | 63 +++++ .../pinned-message-banner-1-Msg1-linux.png | Bin 0 -> 1325 bytes .../pinned-message-banner-2-Msg1-linux.png | Bin 0 -> 4174 bytes .../pinned-message-banner-2-Msg2-linux.png | Bin 0 -> 4290 bytes .../pinned-message-banner-2-linux.png | Bin 0 -> 13394 bytes .../pinned-message-banner-4-Msg1-linux.png | Bin 0 -> 4142 bytes .../pinned-message-banner-4-Msg2-linux.png | Bin 0 -> 4410 bytes .../pinned-message-banner-4-Msg3-linux.png | Bin 0 -> 4449 bytes .../pinned-message-banner-4-Msg4-linux.png | Bin 0 -> 4142 bytes .../pinned-messages-list-messages-2-linux.png | Bin 13654 -> 0 bytes .../pinned-messages-list-messages-3-linux.png | Bin 16907 -> 0 bytes .../pinned-messages-list-pin-3-linux.png | Bin 0 -> 16914 bytes .../pinned-messages-list-unpin-2-linux.png | Bin 0 -> 13658 bytes res/css/_components.pcss | 1 + res/css/views/rooms/_PinnedMessageBanner.pcss | 119 +++++++++ src/components/structures/RoomView.tsx | 10 + .../views/context_menus/RoomContextMenu.tsx | 2 +- .../right_panel/LegacyRoomHeaderButtons.tsx | 2 +- .../views/right_panel/PinnedMessagesCard.tsx | 166 +----------- .../views/right_panel/RoomSummaryCard.tsx | 2 +- .../views/rooms/PinnedMessageBanner.tsx | 252 ++++++++++++++++++ src/hooks/usePinnedEvents.ts | 212 +++++++++++++++ src/i18n/strings/en_EN.json | 7 + src/utils/PinningUtils.ts | 14 +- .../right_panel/PinnedMessagesCard-test.tsx | 7 +- .../views/rooms/PinnedMessageBanner-test.tsx | 235 ++++++++++++++++ .../PinnedMessageBanner-test.tsx.snap | 166 ++++++++++++ test/utils/PinningUtils-test.ts | 18 +- 29 files changed, 1130 insertions(+), 180 deletions(-) create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-1-Msg1-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-2-Msg1-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-2-Msg2-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-2-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg1-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg2-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg3-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg4-linux.png delete mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-messages-2-linux.png delete mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-messages-3-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-pin-3-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-unpin-2-linux.png create mode 100644 res/css/views/rooms/_PinnedMessageBanner.pcss create mode 100644 src/components/views/rooms/PinnedMessageBanner.tsx create mode 100644 src/hooks/usePinnedEvents.ts create mode 100644 test/components/views/rooms/PinnedMessageBanner-test.tsx create mode 100644 test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap diff --git a/playwright/e2e/pinned-messages/index.ts b/playwright/e2e/pinned-messages/index.ts index 5e61b11e85..bb7705ba98 100644 --- a/playwright/e2e/pinned-messages/index.ts +++ b/playwright/e2e/pinned-messages/index.ts @@ -168,9 +168,8 @@ export class Helpers { /** * Return the right panel - * @private */ - private getRightPanel() { + public getRightPanel() { return this.page.locator("#mx_RightPanel"); } @@ -183,7 +182,6 @@ export class Helpers { await expect(rightPanel.getByRole("heading", { name: "Pinned messages" })).toHaveText( `${messages.length} Pinned messages`, ); - await expect(rightPanel).toMatchScreenshot(`pinned-messages-list-messages-${messages.length}.png`); const list = rightPanel.getByRole("list"); await expect(list.getByRole("listitem")).toHaveCount(messages.length); @@ -243,6 +241,36 @@ export class Helpers { await item.getByRole("button").click(); await this.page.getByRole("menu", { name: "Open menu" }).getByRole("menuitem", { name: "Unpin" }).click(); } + + /** + * Return the banner + * @private + */ + public getBanner() { + return this.page.getByTestId("pinned-message-banner"); + } + + /** + * Assert that the banner contains the given message + * @param msg + */ + async assertMessageInBanner(msg: string) { + await expect(this.getBanner().getByText(msg)).toBeVisible(); + } + + /** + * Return the view all button + */ + public getViewAllButton() { + return this.page.getByRole("button", { name: "View all" }); + } + + /** + * Return the close list button + */ + public getCloseListButton() { + return this.page.getByRole("button", { name: "Close list" }); + } } export { expect }; diff --git a/playwright/e2e/pinned-messages/pinned-messages.spec.ts b/playwright/e2e/pinned-messages/pinned-messages.spec.ts index 53f657ea7f..339c3b1f0e 100644 --- a/playwright/e2e/pinned-messages/pinned-messages.spec.ts +++ b/playwright/e2e/pinned-messages/pinned-messages.spec.ts @@ -48,6 +48,7 @@ test.describe("Pinned messages", () => { await util.openRoomInfo(); await util.openPinnedMessagesList(); await util.assertPinnedMessagesList(["Msg1", "Msg2", "Msg4"]); + await expect(util.getRightPanel()).toMatchScreenshot(`pinned-messages-list-pin-3.png`); }); test("should unpin one message", async ({ page, app, room1, util }) => { @@ -59,6 +60,7 @@ test.describe("Pinned messages", () => { await util.openPinnedMessagesList(); await util.unpinMessageFromMessageList("Msg2"); await util.assertPinnedMessagesList(["Msg1", "Msg4"]); + await expect(util.getRightPanel()).toMatchScreenshot(`pinned-messages-list-unpin-2.png`); await util.backPinnedMessagesList(); await util.assertPinnedCountInRoomInfo(2); }); @@ -87,4 +89,65 @@ test.describe("Pinned messages", () => { await util.pinMessagesFromQuickActions(["Msg1"], true); await util.assertPinnedCountInRoomInfo(0); }); + + test("should display one message in the banner", async ({ page, app, room1, util }) => { + await util.goTo(room1); + await util.receiveMessages(room1, ["Msg1"]); + await util.pinMessages(["Msg1"]); + await util.assertMessageInBanner("Msg1"); + await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-1-Msg1.png"); + }); + + test("should display 2 messages in the banner", async ({ page, app, room1, util }) => { + await util.goTo(room1); + await util.receiveMessages(room1, ["Msg1", "Msg2"]); + await util.pinMessages(["Msg1", "Msg2"]); + + await util.assertMessageInBanner("Msg1"); + await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-2-Msg1.png"); + + await util.getBanner().click(); + await util.assertMessageInBanner("Msg2"); + await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-2-Msg2.png"); + + await util.getBanner().click(); + await util.assertMessageInBanner("Msg1"); + await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-2-Msg1.png"); + }); + + test("should display 4 messages in the banner", async ({ page, app, room1, util }) => { + await util.goTo(room1); + await util.receiveMessages(room1, ["Msg1", "Msg2", "Msg3", "Msg4"]); + await util.pinMessages(["Msg1", "Msg2", "Msg3", "Msg4"]); + + for (const msg of ["Msg1", "Msg4", "Msg3", "Msg2"]) { + await util.assertMessageInBanner(msg); + await expect(util.getBanner()).toMatchScreenshot(`pinned-message-banner-4-${msg}.png`); + await util.getBanner().click(); + } + }); + + test("should open the pinned messages list from the banner", async ({ page, app, room1, util }) => { + await util.goTo(room1); + await util.receiveMessages(room1, ["Msg1", "Msg2"]); + await util.pinMessages(["Msg1", "Msg2"]); + + await util.getViewAllButton().click(); + await util.assertPinnedMessagesList(["Msg1", "Msg2"]); + await expect(util.getRightPanel()).toMatchScreenshot("pinned-message-banner-2.png"); + + await expect(util.getCloseListButton()).toBeVisible(); + }); + + test("banner should listen to pinned message list", async ({ page, app, room1, util }) => { + await util.goTo(room1); + await util.receiveMessages(room1, ["Msg1", "Msg2"]); + await util.pinMessages(["Msg1", "Msg2"]); + + await expect(util.getViewAllButton()).toBeVisible(); + + await util.openRoomInfo(); + await util.openPinnedMessagesList(); + await expect(util.getCloseListButton()).toBeVisible(); + }); }); diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-1-Msg1-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-1-Msg1-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..d6892c5bbff22d3ae7c10bd62f3de57ebbc44189 GIT binary patch literal 1325 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-2-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-2-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..f583649aaa95f77fb5d5072f6b83e36f771a78b6 GIT binary patch literal 13394 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg1-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg1-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..8169ca9cfd14b9ae5ab628bc8fd8c2c281e3086e GIT binary patch literal 4142 zcmbtXS6oxew+?cSAWBg{q?>~%J#tW@v|wlkrAQ|bklrL5YG?^6AP54AVx%7hM1%kW z5kgf^q(}fMp(OMogc>@LaJSr-`*2_WzyE&Nd(Ag{)_nV$wb#sgY-()4%_YJG0)e;@ zH+9WGAa)dBn{pll?!JEUw}1y*pqT+2R7w_G0D*XaB6M$9gk~+%!fY(u4!^B4zn?0O zemZqb=HjjN$w#~SHEeS)PriBMm5?yPyLs&D=i7bX?dhi#zj9X@b!mSW_(wkV48ElE z&)e;P{watzRdsUp$3BTKZCCB z+PV1caNwH(4-%63+F=62XdMW^gu+%v6VQ32SN?Vx^kRpJLWhYRDu+4tua*kQnUi1! z*U9(&I+m#KH3Xnzvy}q`dOFZn zw|MkK<T@1AtvuW!og8%J+FwJn4kj8-!#T~_TTm{?-s8>yhY!Cy$~3oX@z0i`kfMQdK- z2tpm^L#YZgSJ}{OLFI_>MK#=bQuoTg?qG56_w3(mXkj!CFuOzUA+7XD zJ(b+G+*02PJIr#^3kK`roIcxq7?nC>a<0f?S}h2*`yT(mOg5$s_Y^ie3fakmS&p~4 zCXarq;BIqiq-_uj;+(%H88r&?mv8ldDCb}2HX6u`+uOdk=PVsRacQ|IC}_SyOAkCRxAKETeaKPzktg?*$bp%Z52@jBbSvy0BmL3a#aV2_C0*YR zRC(ij17JCcM%h`A$V&5Wgp%fq1V$z=Vo0(I+T2w-;L&o?D}jp{b(S%@EDDd;bSXRm zR)bbQhrOuSwxO<9-4cED#QbK;7POa(08cd(RJj^f_@j?(Z1;ji?s)4y#~R1o3Bw{c zKiZLotslY+PyL#h_rc@87<2|Vp&Vy4dZ@1&81u3ljn~pm(d_9N8nG8>85%vxrkmH2g=A3vrgPlG`H@Zi6NtkAS|Nu-vC@XA#u*U`h62%}I_ z`>H)kZVVs09g!b?FVaLOz|}( zY2Hi~=$W--FZe-sn~y^-{|w2NMlixj(*6>dqa` z({opGwvK*?o5EovEBCp8dNM>IUX9uL{z2Zrqt?MK#fNn}&oQMxPP2iu6)i56Nm(l? z(o5|+tr+*5>8*nS1&rL?C}!;Aj(-e2QZldG@+NS_@<9VDGBVQfBg;6a2Va|u;GF#I z(O^P0eoNfJo5LmT!G{iQ);;gL`Lc2)`M7zUG49S}B6zhen9$R~q>^$sOqu1eu?6<{ zvTJy^d<$VYOE@Ru=6hJ7aa!t|-N_ciyaIA+2CEpl@h7GAjT5$rDKe^u5R1rvkD&D??CE1qh?K^>ZvAI)p%7LJDds@MxZe z1~6;$y%IYV#~`7X_vzdm*yx#ogvZdbWi5;uXYZ$~9IN=vECu@hS$Mopl>5qiiQloM zI`H&l=UiceE4AwsH`6{Z%yh-d#U&3D%`O6!iwa*_-)ops^>Lh;@zYwS|EyBb8Z<2u zTcr{hxl7{EyqS&W8k#|$k|r|D@b;-w22~4JG~Gu#*z#idRwbxj-gj$C%Bmkfn7mw= z+?Urh!m4%Lj{YSGTP|z;{2Yzi8L8O+*U$81;-l23RY-7*Otno3_MOE|B*BW)e|Ai= za$B!Ws+c>8qj5+nbesbGw4L%YHY}GVeldVLcDT;3>DjYqadnxEqD4vGL0G5ftuF(k zkBMk_+zBy{o>!PVZmA<90RJPHUR~8)zN*BU;}2?ZiQ;t#X_;x`-x@h`Rq}2J#;l+E+ve8YyhUl|RXU)+fsyEDSJc&V%I!9t%)N8ap z1F;?|GU&(Y%6K+^8m}>9-lHgmgl~bfU2vj3QTi;&s$t zN|V;$7z0WGx><(uf&UUTYA?8GN;j9{=DX|!k_c5+COyrvSPUe* ziDN!X>j>aMqE8e1z7H(}`p=#93^;iO9LF1kMi;Io1Di0MQ(nHqoA)?cL*v>Yie(g2?CJk6HH60C(6JTfO?UxqL z&O2P-dJT?F$6KftP3%Sgk`zCm!tG>;8n?I>!?b?odYxoc#oKhk!9KSWgJK=gH28fG zNB_9Yu*Msge(Y9vcXvzkJ^z=p!Kzl-^=ltPLKc3Jv%*LulF3F;u`r3x(%q-`pOo&Z zl0Bcwxv~TI+xOFKubLb?C%RZ7>)ye0WIVg-eHMui)wnj_{(IB6yp`tkG43}JKS8q zDYYD;i`}s58MPh0-q<*estei+==^x!%Wt5+$eLYKqiM~1ajW91ymc8V$kT2EIF;P@ zKtvzs4i8%0Zh5`C+H~mD^DkSk@&|bQ=jh`vndj?yVaPkCrsZ?b(*{)s;hdQ2oV>9$ z+lFw0UAKg>F$C)E8un|RUr$f(#0i5~zLzh#`OhSK6GvjC3~oJikzF1oa-%9c3S10C zMSZ|u(!YGsiCg@U*yyb$v@*=3J*Wv_X>&~BO8nlg^c5R$@34bFIm$UX(YR{@lKgd; zBQ6oQl$7vW^s-B_d{PEU*s5J-Y$7A#E0r3qudknd{dwrM$@rooue+c6_f{1(Fj{+U zy)N*0hX!gV&(DdwQLAGO@3P0UOjw#*-ix!HvTi#?UEWuOR)z!vFRpUf6%X&k3 zewsg5WCPT=-PG8Sl=P%AMLkIC`d;-od3Zz>6S893&Z5I%&jg|fCFVszDv1(kO)057 zo=akyS^wlmRF`6zn>mML%`I{K$r76G165vhJTgt(`!h4K8HyeG=$nbdM(~PHEkLEU z^Li}~g|)6;t#171a0g~}hRaQvInBeOX#uCKG}GdNhH%pS{5;2>U8SxSrkuTvSaooW zEw=Ij`#!H&&mk<^-pt(G(vVhSn<7C=Gjy9te%IfB1+$Z*syfKSvz;6DVKxQ!w}wVX zl*0C)@2E6lgsAe@<-6YWLBgVyOi8bO=&bk1^is&4w;QAb(0kVo(0g{&FosWuRnJc< znMm)mlpDxL;QYFC>RcD%qT^24RcyArkhh*C%*}0OXRGJu+dsogaDl0`2#1CYxtMn{ zLR#a^R>cF!JK6Q2?F@)Ra}Eg zGtSzbjTI7`W!}`FQk$DqMbDiJqAwy>?n{;-mFHqsi~Es>ITQh4|{7bVna zsh3Q0vi^26bar;8h3{SffkG`*>>{w$rdKIukcGO~!#muhwG-}K#d@BeKl4ayz^K!w zPs>@Rb$iZsq!nA2ZSU-u+`e71A_1H%x{7?2i^)aBtLnP5 z35&+ zvP-K)U(s)_Vf*#T_gq~!|LA19WrIZWNNPn+wOmiBYKA_4zA;uf1H>q3acv)Nd-v`{Ymz8GKmYU8RQ)IC)_-1vLaPs$JB5XX zJBtH8Gi}cpiB&|TZ+LiZeGActve|zXD7t(tsso+kc*K{MmLBnltNBmq>gcqjhMSqC z=H*SN$y>iLEV!_c)BDbuCp`h6K$Sy{CQaszee+G+-*ib$kYdZaYg0lwZPu}~Yoi_>?UM^)>a@D+pO wAkZh3`+NUI`T8H%#}-8k!uu-#KhXZpVf`KGZBMMxMW6*j=o#yl!tXx(ACKzjxBvhE literal 0 HcmV?d00001 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg2-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg2-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..7b6c9948212df8b8cdcd71027b06ae0b4debdcd4 GIT binary patch literal 4410 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg3-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg3-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..dfec23cb59097a8272ed2f0b0bb50bada67f921c GIT binary patch literal 4449 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg4-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg4-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..72abbbb2606cd0b0b5670ae7e146ff8e7d436bd9 GIT binary patch literal 4142 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-messages-2-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-messages-2-linux.png deleted file mode 100644 index 82666b0d95ff5d0728af128f5f1e66757199a8df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13654 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 qftEl1i*t(qu991O6xldi>DfLB)Ohm;VD*8PDke diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-messages-3-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-messages-3-linux.png deleted file mode 100644 index 98e804d897d1de7194d1c924ef1f3170f44e81f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16907 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-unpin-2-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-unpin-2-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..4eede005d886b1dc170f92b78cc32ae3692362ab GIT binary patch literal 13658 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 WTVS)$10I%QdiqHBVfh2=*Z%{J+P+Bu literal 0 HcmV?d00001 diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 96c285bc0a..bfcab19879 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -298,6 +298,7 @@ @import "./views/rooms/_NewRoomIntro.pcss"; @import "./views/rooms/_NotificationBadge.pcss"; @import "./views/rooms/_PinnedEventTile.pcss"; +@import "./views/rooms/_PinnedMessageBanner.pcss"; @import "./views/rooms/_PresenceLabel.pcss"; @import "./views/rooms/_ReadReceiptGroup.pcss"; @import "./views/rooms/_ReplyPreview.pcss"; diff --git a/res/css/views/rooms/_PinnedMessageBanner.pcss b/res/css/views/rooms/_PinnedMessageBanner.pcss new file mode 100644 index 0000000000..c6889aba75 --- /dev/null +++ b/res/css/views/rooms/_PinnedMessageBanner.pcss @@ -0,0 +1,119 @@ +/* + * Copyright 2024 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.mx_PinnedMessageBanner { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--cpd-space-4x); + /* 80px = 79px + 1px from the bottom border */ + height: 79px; + padding: 0 var(--cpd-space-4x); + + background-color: var(--cpd-color-bg-canvas-default); + border-bottom: 1px solid var(--cpd-color-gray-400); + + /* From figma */ + box-shadow: 0 var(--cpd-space-2x) var(--cpd-space-6x) calc(var(--cpd-space-2x) * -1) rgba(27, 29, 34, 0.1); + + .mx_PinnedMessageBanner_main { + background: transparent; + border: none; + text-align: start; + cursor: pointer; + + height: 100%; + flex-grow: 1; + display: flex; + align-items: center; + + .mx_PinnedMessageBanner_content { + display: grid; + grid-template: + "indicators pinIcon title" auto + "indicators pinIcon message" auto; + column-gap: var(--cpd-space-2x); + } + + .mx_PinnedMessageBanner_Indicators { + grid-area: indicators; + display: flex; + flex-direction: column; + gap: var(--cpd-space-0-5x); + height: 100%; + + .mx_PinnedMessageBanner_Indicator { + width: var(--cpd-space-0-5x); + background-color: var(--cpd-color-gray-600); + height: 100%; + } + + .mx_PinnedMessageBanner_Indicator--active { + background-color: var(--cpd-color-icon-accent-primary); + } + + .mx_PinnedMessageBanner_Indicator--hidden { + background-color: transparent; + } + } + + .mx_PinnedMessageBanner_PinIcon { + grid-area: pinIcon; + align-self: center; + fill: var(--cpd-color-icon-secondary-alpha); + } + + .mx_PinnedMessageBanner_title { + grid-area: title; + font: var(--cpd-font-body-sm-regular); + color: var(--cpd-color-text-action-accent); + height: 20px; + + .mx_PinnedMessageBanner_title_counter { + font: var(--cpd-font-body-sm-semibold); + } + } + + .mx_PinnedMessageBanner_message { + grid-area: message; + font: var(--cpd-font-body-sm-regular); + height: 20px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .mx_PinnedMessageBanner_redactedMessage { + grid-area: message; + height: 20px; + display: flex; + align-items: center; + } + } + + .mx_PinnedMessageBanner_actions { + white-space: nowrap; + } +} + +.mx_PinnedMessageBanner[data-single-message="true"] { + /* 64px = 63px + 1px from the bottom border */ + height: 63px; + + .mx_PinnedMessageBanner_content { + grid-template: "pinIcon message" auto; + } +} diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 70ee16b542..9c7469346d 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -133,6 +133,7 @@ import { SubmitAskToJoinPayload } from "../../dispatcher/payloads/SubmitAskToJoi import RightPanelStore from "../../stores/right-panel/RightPanelStore"; import { onView3pidInvite } from "../../stores/right-panel/action-handlers"; import RoomSearchAuxPanel from "../views/rooms/RoomSearchAuxPanel"; +import { PinnedMessageBanner } from "../views/rooms/PinnedMessageBanner"; const DEBUG = false; const PREVENT_MULTIPLE_JITSI_WITHIN = 30_000; @@ -2409,6 +2410,14 @@ export class RoomView extends React.Component { ); + const isPinningEnabled = SettingsStore.getValue("feature_pinning"); + let pinnedMessageBanner; + if (isPinningEnabled) { + pinnedMessageBanner = ( + + ); + } + let messageComposer; const showComposer = // joined and not showing search results @@ -2537,6 +2546,7 @@ export class RoomView extends React.Component { )} {auxPanel} + {pinnedMessageBanner}
{topUnreadMessagesBar} diff --git a/src/components/views/context_menus/RoomContextMenu.tsx b/src/components/views/context_menus/RoomContextMenu.tsx index 0dbd4b5395..6c105dd0cc 100644 --- a/src/components/views/context_menus/RoomContextMenu.tsx +++ b/src/components/views/context_menus/RoomContextMenu.tsx @@ -35,7 +35,6 @@ import { RoomNotifState } from "../../../RoomNotifs"; import Modal from "../../../Modal"; import ExportDialog from "../dialogs/ExportDialog"; import { useFeatureEnabled } from "../../../hooks/useSettings"; -import { usePinnedEvents } from "../right_panel/PinnedMessagesCard"; import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases"; import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog"; import { useEventEmitterState } from "../../../hooks/useEventEmitter"; @@ -53,6 +52,7 @@ import { UIComponent } from "../../../settings/UIFeature"; import { DeveloperToolsOption } from "./DeveloperToolsOption"; import { tagRoom } from "../../../utils/room/tagRoom"; import { useIsVideoRoom } from "../../../utils/video-rooms"; +import { usePinnedEvents } from "../../../hooks/usePinnedEvents"; interface IProps extends IContextMenuProps { room: Room; diff --git a/src/components/views/right_panel/LegacyRoomHeaderButtons.tsx b/src/components/views/right_panel/LegacyRoomHeaderButtons.tsx index 207c97ec7b..c3fc1fe95d 100644 --- a/src/components/views/right_panel/LegacyRoomHeaderButtons.tsx +++ b/src/components/views/right_panel/LegacyRoomHeaderButtons.tsx @@ -28,7 +28,6 @@ import HeaderButtons, { HeaderKind } from "./HeaderButtons"; import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases"; import { ActionPayload } from "../../../dispatcher/payloads"; import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; -import { useReadPinnedEvents, usePinnedEvents } from "./PinnedMessagesCard"; import { showThreadPanel } from "../../../dispatcher/dispatch-actions/threads"; import SettingsStore from "../../../settings/SettingsStore"; import { @@ -40,6 +39,7 @@ import { SummarizedNotificationState } from "../../../stores/notifications/Summa import PosthogTrackers from "../../../PosthogTrackers"; import { ButtonEvent } from "../elements/AccessibleButton"; import { doesRoomOrThreadHaveUnreadMessages } from "../../../Unread"; +import { usePinnedEvents, useReadPinnedEvents } from "../../../hooks/usePinnedEvents"; const ROOM_INFO_PHASES = [ RightPanelPhases.RoomSummary, diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index 85be2e6d03..0f1f856786 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -14,17 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useCallback, useEffect, useState, JSX } from "react"; -import { - Room, - RoomEvent, - RoomStateEvent, - MatrixEvent, - EventType, - RelationType, - EventTimeline, -} from "matrix-js-sdk/src/matrix"; -import { logger } from "matrix-js-sdk/src/logger"; +import React, { useCallback, useEffect, JSX } from "react"; +import { Room, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix"; import { Button, Separator } from "@vector-im/compound-web"; import classNames from "classnames"; import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin"; @@ -33,9 +24,6 @@ import { _t } from "../../../languageHandler"; import BaseCard from "./BaseCard"; import Spinner from "../elements/Spinner"; import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; -import { useTypedEventEmitter } from "../../../hooks/useEventEmitter"; -import PinningUtils from "../../../utils/PinningUtils"; -import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; import { PinnedEventTile } from "../rooms/PinnedEventTile"; import { useRoomState } from "../../../hooks/useRoomState"; import RoomContext, { TimelineRenderingType, useRoomContext } from "../../../contexts/RoomContext"; @@ -46,155 +34,7 @@ import { filterBoolean } from "../../../utils/arrays"; import Modal from "../../../Modal"; import { UnpinAllDialog } from "../dialogs/UnpinAllDialog"; import EmptyState from "./EmptyState"; - -/** - * Get the pinned event IDs from a room. - * @param room - */ -function getPinnedEventIds(room?: Room): string[] { - return ( - room - ?.getLiveTimeline() - .getState(EventTimeline.FORWARDS) - ?.getStateEvents(EventType.RoomPinnedEvents, "") - ?.getContent()?.pinned ?? [] - ); -} - -/** - * Get the pinned event IDs from a room. - * @param room - */ -export const usePinnedEvents = (room?: Room): string[] => { - const [pinnedEvents, setPinnedEvents] = useState(getPinnedEventIds(room)); - - // Update the pinned events when the room state changes - // Filter out events that are not pinned events - const update = useCallback( - (ev?: MatrixEvent) => { - if (ev && ev.getType() !== EventType.RoomPinnedEvents) return; - setPinnedEvents(getPinnedEventIds(room)); - }, - [room], - ); - - useTypedEventEmitter(room?.getLiveTimeline().getState(EventTimeline.FORWARDS), RoomStateEvent.Events, update); - useEffect(() => { - setPinnedEvents(getPinnedEventIds(room)); - return () => { - setPinnedEvents([]); - }; - }, [room]); - return pinnedEvents; -}; - -/** - * Get the read pinned event IDs from a room. - * @param room - */ -function getReadPinnedEventIds(room?: Room): Set { - return new Set(room?.getAccountData(ReadPinsEventId)?.getContent()?.event_ids ?? []); -} - -/** - * Get the read pinned event IDs from a room. - * @param room - */ -export const useReadPinnedEvents = (room?: Room): Set => { - const [readPinnedEvents, setReadPinnedEvents] = useState>(new Set()); - - // Update the read pinned events when the room state changes - // Filter out events that are not read pinned events - const update = useCallback( - (ev?: MatrixEvent) => { - if (ev && ev.getType() !== ReadPinsEventId) return; - setReadPinnedEvents(getReadPinnedEventIds(room)); - }, - [room], - ); - - useTypedEventEmitter(room, RoomEvent.AccountData, update); - useEffect(() => { - setReadPinnedEvents(getReadPinnedEventIds(room)); - return () => { - setReadPinnedEvents(new Set()); - }; - }, [room]); - return readPinnedEvents; -}; - -/** - * Fetch the pinned events - * @param room - * @param pinnedEventIds - */ -function useFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Array | null { - const cli = useMatrixClientContext(); - - return useAsyncMemo( - () => { - const promises = pinnedEventIds.map(async (eventId): Promise => { - const timelineSet = room.getUnfilteredTimelineSet(); - // Get the event from the local timeline - const localEvent = timelineSet - ?.getTimelineForEvent(eventId) - ?.getEvents() - .find((e) => e.getId() === eventId); - - // Decrypt the event if it's encrypted - // Can happen when the tab is refreshed and the pinned events card is opened directly - if (localEvent?.isEncrypted()) { - await cli.decryptEventIfNeeded(localEvent); - } - - // If the event is available locally, return it if it's pinnable - // Otherwise, return null - if (localEvent) return PinningUtils.isPinnable(localEvent) ? localEvent : null; - - try { - // The event is not available locally, so we fetch the event and latest edit in parallel - const [ - evJson, - { - events: [edit], - }, - ] = await Promise.all([ - cli.fetchRoomEvent(room.roomId, eventId), - cli.relations(room.roomId, eventId, RelationType.Replace, null, { limit: 1 }), - ]); - - const event = new MatrixEvent(evJson); - - // Decrypt the event if it's encrypted - if (event.isEncrypted()) { - await cli.decryptEventIfNeeded(event); - } - - // Handle poll events - await room.processPollEvents([event]); - - const senderUserId = event.getSender(); - if (senderUserId && PinningUtils.isPinnable(event)) { - // Inject sender information - event.sender = room.getMember(senderUserId); - // Also inject any edits we've found - if (edit) event.makeReplaced(edit); - - return event; - } - } catch (err) { - logger.error("Error looking up pinned event " + eventId + " in room " + room.roomId); - logger.error(err); - } - return null; - }); - - return Promise.all(promises); - }, - [cli, room, pinnedEventIds], - null, - ); -} +import { useFetchedPinnedEvents, usePinnedEvents, useReadPinnedEvents } from "../../../hooks/usePinnedEvents"; /** * List the pinned messages in a room inside a Card. diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 9dfe049a52..ece150495e 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -58,7 +58,6 @@ import { E2EStatus } from "../../../utils/ShieldUtils"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import { useFeatureEnabled } from "../../../hooks/useSettings"; -import { usePinnedEvents } from "./PinnedMessagesCard"; import RoomName from "../elements/RoomName"; import ExportDialog from "../dialogs/ExportDialog"; import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; @@ -81,6 +80,7 @@ import { Action } from "../../../dispatcher/actions"; import { Key } from "../../../Keyboard"; import { useTransition } from "../../../hooks/useTransition"; import { useIsVideoRoom } from "../../../utils/video-rooms"; +import { usePinnedEvents } from "../../../hooks/usePinnedEvents"; interface IProps { room: Room; diff --git a/src/components/views/rooms/PinnedMessageBanner.tsx b/src/components/views/rooms/PinnedMessageBanner.tsx new file mode 100644 index 0000000000..f7010b8838 --- /dev/null +++ b/src/components/views/rooms/PinnedMessageBanner.tsx @@ -0,0 +1,252 @@ +/* + * Copyright 2024 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { JSX, useEffect, useMemo, useState } from "react"; +import { Icon as PinIcon } from "@vector-im/compound-design-tokens/icons/pin-solid.svg"; +import { Button } from "@vector-im/compound-web"; +import { Room } from "matrix-js-sdk/src/matrix"; +import classNames from "classnames"; + +import { usePinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents"; +import { _t } from "../../../languageHandler"; +import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; +import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases"; +import { useEventEmitter } from "../../../hooks/useEventEmitter"; +import { UPDATE_EVENT } from "../../../stores/AsyncStore"; +import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; +import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; +import dis from "../../../dispatcher/dispatcher"; +import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import { Action } from "../../../dispatcher/actions"; +import MessageEvent from "../messages/MessageEvent"; + +/** + * The props for the {@link PinnedMessageBanner} component. + */ +interface PinnedMessageBannerProps { + /** + * The permalink creator to use. + */ + permalinkCreator: RoomPermalinkCreator; + /** + * The room where the banner is displayed + */ + room: Room; +} + +/** + * A banner that displays the pinned messages in a room. + */ +export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBannerProps): JSX.Element | null { + const pinnedEventIds = usePinnedEvents(room); + const pinnedEvents = useSortedFetchedPinnedEvents(room, pinnedEventIds); + const eventCount = pinnedEvents.length; + const isSinglePinnedEvent = eventCount === 1; + + const [currentEventIndex, setCurrentEventIndex] = useState(eventCount - 1); + // If the list of pinned events changes, we need to make sure the current index isn't out of bound + useEffect(() => { + setCurrentEventIndex((currentEventIndex) => { + // If the current index is out of bound, we set it to the last index + if (currentEventIndex < 0 || currentEventIndex >= eventCount) return eventCount - 1; + return currentEventIndex; + }); + }, [eventCount]); + + const pinnedEvent = pinnedEvents[currentEventIndex]; + // Generate a preview for the pinned event + const eventPreview = useMemo(() => { + if (!pinnedEvent || pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure()) return null; + return MessagePreviewStore.instance.generatePreviewForEvent(pinnedEvent); + }, [pinnedEvent]); + + if (!pinnedEvent) return null; + + const shouldUseMessageEvent = pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure(); + + const onBannerClick = (): void => { + // Scroll to the pinned message + dis.dispatch({ + action: Action.ViewRoom, + event_id: pinnedEvent.getId(), + highlighted: true, + room_id: room.roomId, + metricsTrigger: undefined, // room doesn't change + }); + + // Cycle through the pinned messages + // When we reach the first message, we go back to the last message + setCurrentEventIndex((currentEventIndex) => (--currentEventIndex === -1 ? eventCount - 1 : currentEventIndex)); + }; + + return ( +
+ + {!isSinglePinnedEvent && } +
+ ); +} + +const MAX_INDICATORS = 3; + +/** + * The props for the {@link IndicatorsProps} component. + */ +interface IndicatorsProps { + /** + * The number of messages pinned + */ + count: number; + /** + * The current index of the pinned message + */ + currentIndex: number; +} + +/** + * A component that displays vertical indicators for the pinned messages. + */ +function Indicators({ count, currentIndex }: IndicatorsProps): JSX.Element { + // We only display a maximum of 3 indicators at one time. + // When there is more than 3 messages pinned, we will cycle through the indicators + + // If there is only 2 messages pinned, we will display 2 indicators + // In case of 1 message pinned, the indicators are not displayed, see {@link PinnedMessageBanner} logic. + const numberOfIndicators = Math.min(count, MAX_INDICATORS); + // The index of the active indicator + const index = currentIndex % numberOfIndicators; + + // We hide the indicators when we are on the last cycle and there are less than 3 remaining messages pinned + const numberOfCycles = Math.ceil(count / numberOfIndicators); + // If the current index is greater than the last cycle index, we are on the last cycle + const isLastCycle = currentIndex >= (numberOfCycles - 1) * MAX_INDICATORS; + // The index of the last message in the last cycle + const lastCycleIndex = numberOfIndicators - (numberOfCycles * numberOfIndicators - count); + + return ( +
+ {Array.from({ length: numberOfIndicators }).map((_, i) => ( +
+ ); +} + +/** + * The props for the {@link Indicator} component. + */ +interface IndicatorProps { + /** + * Whether the indicator is active + */ + active: boolean; + /** + * Whether the indicator is hidden + */ + hidden: boolean; +} + +/** + * A component that displays a vertical indicator for a pinned message. + */ +function Indicator({ active, hidden }: IndicatorProps): JSX.Element { + return ( +
+ ); +} + +function getRightPanelPhase(roomId: string): RightPanelPhases | null { + if (!RightPanelStore.instance.isOpenForRoom(roomId)) return null; + return RightPanelStore.instance.currentCard.phase; +} + +/** + * The props for the {@link BannerButton} component. + */ +interface BannerButtonProps { + /** + * The room where the banner is displayed + */ + room: Room; +} + +/** + * A button that allows the user to view or close the list of pinned messages. + */ +function BannerButton({ room }: BannerButtonProps): JSX.Element { + const [currentPhase, setCurrentPhase] = useState(getRightPanelPhase(room.roomId)); + useEventEmitter(RightPanelStore.instance, UPDATE_EVENT, () => setCurrentPhase(getRightPanelPhase(room.roomId))); + const isPinnedMessagesPhase = currentPhase === RightPanelPhases.PinnedMessages; + + return ( + + ); +} diff --git a/src/hooks/usePinnedEvents.ts b/src/hooks/usePinnedEvents.ts new file mode 100644 index 0000000000..eb53151138 --- /dev/null +++ b/src/hooks/usePinnedEvents.ts @@ -0,0 +1,212 @@ +/* + * Copyright 2024 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 { useCallback, useEffect, useMemo, useState } from "react"; +import { + Room, + RoomEvent, + RoomStateEvent, + MatrixEvent, + EventType, + RelationType, + EventTimeline, + MatrixClient, +} from "matrix-js-sdk/src/matrix"; +import { logger } from "matrix-js-sdk/src/logger"; + +import { useTypedEventEmitter } from "./useEventEmitter"; +import { ReadPinsEventId } from "../components/views/right_panel/types"; +import { useMatrixClientContext } from "../contexts/MatrixClientContext"; +import { useAsyncMemo } from "./useAsyncMemo"; +import PinningUtils from "../utils/PinningUtils"; + +/** + * Get the pinned event IDs from a room. + * @param room + */ +function getPinnedEventIds(room?: Room): string[] { + return ( + room + ?.getLiveTimeline() + .getState(EventTimeline.FORWARDS) + ?.getStateEvents(EventType.RoomPinnedEvents, "") + ?.getContent()?.pinned ?? [] + ); +} + +/** + * Get the pinned event IDs from a room. + * @param room + */ +export const usePinnedEvents = (room?: Room): string[] => { + const [pinnedEvents, setPinnedEvents] = useState(getPinnedEventIds(room)); + + // Update the pinned events when the room state changes + // Filter out events that are not pinned events + const update = useCallback( + (ev?: MatrixEvent) => { + if (ev && ev.getType() !== EventType.RoomPinnedEvents) return; + setPinnedEvents(getPinnedEventIds(room)); + }, + [room], + ); + + useTypedEventEmitter(room?.getLiveTimeline().getState(EventTimeline.FORWARDS), RoomStateEvent.Events, update); + useEffect(() => { + setPinnedEvents(getPinnedEventIds(room)); + return () => { + setPinnedEvents([]); + }; + }, [room]); + return pinnedEvents; +}; + +/** + * Get the read pinned event IDs from a room. + * @param room + */ +function getReadPinnedEventIds(room?: Room): Set { + return new Set(room?.getAccountData(ReadPinsEventId)?.getContent()?.event_ids ?? []); +} + +/** + * Get the read pinned event IDs from a room. + * @param room + */ +export const useReadPinnedEvents = (room?: Room): Set => { + const [readPinnedEvents, setReadPinnedEvents] = useState>(new Set()); + + // Update the read pinned events when the room state changes + // Filter out events that are not read pinned events + const update = useCallback( + (ev?: MatrixEvent) => { + if (ev && ev.getType() !== ReadPinsEventId) return; + setReadPinnedEvents(getReadPinnedEventIds(room)); + }, + [room], + ); + + useTypedEventEmitter(room, RoomEvent.AccountData, update); + useEffect(() => { + setReadPinnedEvents(getReadPinnedEventIds(room)); + return () => { + setReadPinnedEvents(new Set()); + }; + }, [room]); + return readPinnedEvents; +}; + +/** + * Fetch the pinned event + * @param room + * @param pinnedEventId + * @param cli + */ +async function fetchPinnedEvent(room: Room, pinnedEventId: string, cli: MatrixClient): Promise { + const timelineSet = room.getUnfilteredTimelineSet(); + // Get the event from the local timeline + const localEvent = timelineSet + ?.getTimelineForEvent(pinnedEventId) + ?.getEvents() + .find((e) => e.getId() === pinnedEventId); + + // Decrypt the event if it's encrypted + // Can happen when the tab is refreshed and the pinned events card is opened directly + if (localEvent?.isEncrypted()) { + await cli.decryptEventIfNeeded(localEvent); + } + + // If the event is available locally, return it if it's pinnable + // or if it's redacted (to show the redacted event and to be able to unpin it) + // Otherwise, return null + if (localEvent) return PinningUtils.isUnpinnable(localEvent) ? localEvent : null; + + try { + // The event is not available locally, so we fetch the event and latest edit in parallel + const [ + evJson, + { + events: [edit], + }, + ] = await Promise.all([ + cli.fetchRoomEvent(room.roomId, pinnedEventId), + cli.relations(room.roomId, pinnedEventId, RelationType.Replace, null, { limit: 1 }), + ]); + + const event = new MatrixEvent(evJson); + + // Decrypt the event if it's encrypted + if (event.isEncrypted()) { + await cli.decryptEventIfNeeded(event); + } + + // Handle poll events + await room.processPollEvents([event]); + + const senderUserId = event.getSender(); + if (senderUserId && PinningUtils.isUnpinnable(event)) { + // Inject sender information + event.sender = room.getMember(senderUserId); + // Also inject any edits we've found + if (edit) event.makeReplaced(edit); + + return event; + } + } catch (err) { + logger.error(`Error looking up pinned event ${pinnedEventId} in room ${room.roomId}`); + logger.error(err); + } + return null; +} + +/** + * Fetch the pinned events + * @param room + * @param pinnedEventIds + */ +export function useFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Array | null { + const cli = useMatrixClientContext(); + + return useAsyncMemo( + () => + Promise.all( + pinnedEventIds.map( + async (eventId): Promise => fetchPinnedEvent(room, eventId, cli), + ), + ), + [cli, room, pinnedEventIds], + null, + ); +} + +/** + * Fetch the pinned events and sort them by from the oldest to the newest + * The order is determined by the event timestamp + * @param room + * @param pinnedEventIds + */ +export function useSortedFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Array { + const pinnedEvents = useFetchedPinnedEvents(room, pinnedEventIds); + return useMemo(() => { + if (!pinnedEvents) return []; + + return pinnedEvents.sort((a, b) => { + if (!a) return -1; + if (!b) return 1; + return a.getTs() - b.getTs(); + }); + }, [pinnedEvents]); +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 889fc157e9..352ab43762 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2048,6 +2048,13 @@ "not_found_title": "This room or space does not exist.", "not_found_title_name": "%(roomName)s does not exist.", "peek_join_prompt": "You're previewing %(roomName)s. Want to join it?", + "pinned_message_banner": { + "button_close_list": "Close list", + "button_view_all": "View all", + "description": "This room has pinned messages. Click to view them.", + "go_to_message": "View the pinned message in the timeline.", + "title": "%(index)s of %(length)s Pinned messages" + }, "read_topic": "Click to read topic", "rejecting": "Rejecting inviteā€¦", "rejoin_button": "Re-join", diff --git a/src/utils/PinningUtils.ts b/src/utils/PinningUtils.ts index 22db64a6f1..9a20a721b9 100644 --- a/src/utils/PinningUtils.ts +++ b/src/utils/PinningUtils.ts @@ -37,11 +37,19 @@ export default class PinningUtils { * @return {boolean} True if the event may be pinned, false otherwise. */ public static isPinnable(event: MatrixEvent): boolean { - if (!event) return false; - if (!this.PINNABLE_EVENT_TYPES.includes(event.getType())) return false; if (event.isRedacted()) return false; + return PinningUtils.isUnpinnable(event); + } - return true; + /** + * Determines if the given event may be unpinned. + * @param {MatrixEvent} event The event to check. + * @return {boolean} True if the event may be unpinned, false otherwise. + */ + public static isUnpinnable(event: MatrixEvent): boolean { + if (!event) return false; + if (event.isRedacted()) return true; + return this.PINNABLE_EVENT_TYPES.includes(event.getType()); } /** diff --git a/test/components/views/right_panel/PinnedMessagesCard-test.tsx b/test/components/views/right_panel/PinnedMessagesCard-test.tsx index 64961ca144..cfa32fa490 100644 --- a/test/components/views/right_panel/PinnedMessagesCard-test.tsx +++ b/test/components/views/right_panel/PinnedMessagesCard-test.tsx @@ -20,7 +20,6 @@ import { mocked, MockedObject } from "jest-mock"; import { MatrixEvent, RoomStateEvent, - IEvent, Room, IMinimalEvent, EventType, @@ -266,9 +265,8 @@ describe("", () => { // Redacted messages are unpinnable const pin = mkEvent({ event: true, - type: EventType.RoomMessage, + type: EventType.RoomCreate, content: {}, - unsigned: { redacted_because: {} as unknown as IEvent }, room: "!room:example.org", user: "@alice:example.org", }); @@ -280,9 +278,8 @@ describe("", () => { // Redacted messages are unpinnable const pin = mkEvent({ event: true, - type: EventType.RoomMessage, + type: EventType.RoomCreate, content: {}, - unsigned: { redacted_because: {} as unknown as IEvent }, room: "!room:example.org", user: "@alice:example.org", }); diff --git a/test/components/views/rooms/PinnedMessageBanner-test.tsx b/test/components/views/rooms/PinnedMessageBanner-test.tsx new file mode 100644 index 0000000000..4df0127d82 --- /dev/null +++ b/test/components/views/rooms/PinnedMessageBanner-test.tsx @@ -0,0 +1,235 @@ +/* + * Copyright 2024 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 { act, screen, render } from "@testing-library/react"; +import React from "react"; +import { EventType, IEvent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; +import userEvent from "@testing-library/user-event"; + +import * as pinnedEventHooks from "../../../../src/hooks/usePinnedEvents"; +import { PinnedMessageBanner } from "../../../../src/components/views/rooms/PinnedMessageBanner"; +import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; +import { stubClient } from "../../../test-utils"; +import dis from "../../../../src/dispatcher/dispatcher"; +import RightPanelStore from "../../../../src/stores/right-panel/RightPanelStore"; +import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases"; +import { UPDATE_EVENT } from "../../../../src/stores/AsyncStore"; +import { Action } from "../../../../src/dispatcher/actions"; + +describe("", () => { + const userId = "@alice:server.org"; + const roomId = "!room:server.org"; + + let mockClient: MatrixClient; + let room: Room; + let permalinkCreator: RoomPermalinkCreator; + beforeEach(() => { + mockClient = stubClient(); + room = new Room(roomId, mockClient, userId); + permalinkCreator = new RoomPermalinkCreator(room); + jest.spyOn(dis, "dispatch").mockReturnValue(undefined); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + /** + * Create a pinned event with the given content. + * @param content + */ + function makePinEvent(content?: Partial) { + return new MatrixEvent({ + type: EventType.RoomMessage, + sender: userId, + content: { + body: "First pinned message", + msgtype: "m.text", + }, + room_id: roomId, + origin_server_ts: 0, + event_id: "$eventId", + ...content, + }); + } + + const event1 = makePinEvent(); + const event2 = makePinEvent({ + event_id: "$eventId2", + content: { body: "Second pinned message" }, + }); + const event3 = makePinEvent({ + event_id: "$eventId3", + content: { body: "Third pinned message" }, + }); + const event4 = makePinEvent({ + event_id: "$eventId4", + content: { body: "Fourth pinned message" }, + }); + + /** + * Render the banner + */ + function renderBanner() { + return render(); + } + + it("should render nothing when there are no pinned events", async () => { + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([]); + const { container } = renderBanner(); + expect(container).toBeEmptyDOMElement(); + }); + + it("should render a single pinned event", async () => { + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1]); + + const { asFragment } = renderBanner(); + + expect(screen.getByText("First pinned message")).toBeVisible(); + expect(screen.queryByRole("button", { name: "View all" })).toBeNull(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should render 2 pinned event", async () => { + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2]); + + const { asFragment } = renderBanner(); + + expect(screen.getByText("Second pinned message")).toBeVisible(); + expect(screen.getByTestId("banner-counter")).toHaveTextContent("2 of 2 Pinned messages"); + expect(screen.getAllByTestId("banner-indicator")).toHaveLength(2); + expect(screen.queryByRole("button", { name: "View all" })).toBeVisible(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should render 4 pinned event", async () => { + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([ + event1.getId()!, + event2.getId()!, + event3.getId()!, + event4.getId()!, + ]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2, event3, event4]); + + const { asFragment } = renderBanner(); + + expect(screen.getByText("Fourth pinned message")).toBeVisible(); + expect(screen.getByTestId("banner-counter")).toHaveTextContent("4 of 4 Pinned messages"); + expect(screen.getAllByTestId("banner-indicator")).toHaveLength(3); + expect(screen.queryByRole("button", { name: "View all" })).toBeVisible(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should rotate the pinned events when the banner is clicked", async () => { + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2]); + + renderBanner(); + expect(screen.getByText("Second pinned message")).toBeVisible(); + + await userEvent.click(screen.getByRole("button", { name: "View the pinned message in the timeline." })); + expect(screen.getByText("First pinned message")).toBeVisible(); + expect(screen.getByTestId("banner-counter")).toHaveTextContent("1 of 2 Pinned messages"); + expect(dis.dispatch).toHaveBeenCalledWith({ + action: Action.ViewRoom, + event_id: event2.getId(), + highlighted: true, + room_id: room.roomId, + metricsTrigger: undefined, // room doesn't change + }); + + await userEvent.click(screen.getByRole("button", { name: "View the pinned message in the timeline." })); + expect(screen.getByText("Second pinned message")).toBeVisible(); + expect(screen.getByTestId("banner-counter")).toHaveTextContent("2 of 2 Pinned messages"); + expect(dis.dispatch).toHaveBeenCalledWith({ + action: Action.ViewRoom, + event_id: event1.getId(), + highlighted: true, + room_id: room.roomId, + metricsTrigger: undefined, // room doesn't change + }); + }); + + describe("Right button", () => { + beforeEach(() => { + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2]); + }); + + it("should display View all button if the right panel is closed", async () => { + // The Right panel is closed + jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(false); + + renderBanner(); + expect(screen.getByRole("button", { name: "View all" })).toBeVisible(); + }); + + it("should display View all button if the right panel is not opened on the pinned message list", async () => { + // The Right panel is opened on another card + jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(true); + jest.spyOn(RightPanelStore.instance, "currentCard", "get").mockReturnValue({ + phase: RightPanelPhases.RoomMemberList, + }); + + renderBanner(); + expect(screen.getByRole("button", { name: "View all" })).toBeVisible(); + }); + + it("should display Close list button if the message pinning list is displayed", async () => { + // The Right panel is closed + jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(true); + jest.spyOn(RightPanelStore.instance, "currentCard", "get").mockReturnValue({ + phase: RightPanelPhases.PinnedMessages, + }); + + renderBanner(); + expect(screen.getByRole("button", { name: "Close list" })).toBeVisible(); + }); + + it("should open or close the message pinning list", async () => { + // The Right panel is closed + jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(true); + jest.spyOn(RightPanelStore.instance, "currentCard", "get").mockReturnValue({ + phase: RightPanelPhases.PinnedMessages, + }); + jest.spyOn(RightPanelStore.instance, "showOrHidePhase").mockReturnValue(); + + renderBanner(); + await userEvent.click(screen.getByRole("button", { name: "Close list" })); + expect(RightPanelStore.instance.showOrHidePhase).toHaveBeenCalledWith(RightPanelPhases.PinnedMessages); + }); + + it("should listen to the right panel", async () => { + // The Right panel is closed + jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(true); + jest.spyOn(RightPanelStore.instance, "currentCard", "get").mockReturnValue({ + phase: RightPanelPhases.PinnedMessages, + }); + + renderBanner(); + expect(screen.getByRole("button", { name: "Close list" })).toBeVisible(); + + jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(false); + act(() => { + RightPanelStore.instance.emit(UPDATE_EVENT); + }); + expect(screen.getByRole("button", { name: "View all" })).toBeVisible(); + }); + }); +}); diff --git a/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap b/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap new file mode 100644 index 0000000000..fa4c793d90 --- /dev/null +++ b/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap @@ -0,0 +1,166 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render 2 pinned event 1`] = ` + +
+ + +
+
+`; + +exports[` should render 4 pinned event 1`] = ` + +
+ + +
+
+`; + +exports[` should render a single pinned event 1`] = ` + +
+ +
+
+`; diff --git a/test/utils/PinningUtils-test.ts b/test/utils/PinningUtils-test.ts index 47434c4fca..adfd268bf1 100644 --- a/test/utils/PinningUtils-test.ts +++ b/test/utils/PinningUtils-test.ts @@ -73,15 +73,27 @@ describe("PinningUtils", () => { ).mockReturnValue(true); }); - describe("isPinnable", () => { + describe("isUnpinnable", () => { test.each(PinningUtils.PINNABLE_EVENT_TYPES)("should return true for pinnable event types", (eventType) => { const event = makePinEvent({ type: eventType }); - expect(PinningUtils.isPinnable(event)).toBe(true); + expect(PinningUtils.isUnpinnable(event)).toBe(true); }); test("should return false for a non pinnable event type", () => { const event = makePinEvent({ type: EventType.RoomCreate }); - expect(PinningUtils.isPinnable(event)).toBe(false); + expect(PinningUtils.isUnpinnable(event)).toBe(false); + }); + + test("should return true for a redacted event", () => { + const event = makePinEvent({ unsigned: { redacted_because: "because" as unknown as IEvent } }); + expect(PinningUtils.isUnpinnable(event)).toBe(true); + }); + }); + + describe("isPinnable", () => { + test.each(PinningUtils.PINNABLE_EVENT_TYPES)("should return true for pinnable event types", (eventType) => { + const event = makePinEvent({ type: eventType }); + expect(PinningUtils.isPinnable(event)).toBe(true); }); test("should return false for a redacted event", () => {