From 9485003ffd81893bf05cc01a8a216e3a544cd090 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 19:43:06 +0000 Subject: [PATCH] Refactor DM settings into a dedicated modal - Created `DMSettingsModalController` to manage the new settings modal UI. - Added `components/dm-settings-modal.html` with sections for Privacy and DM Relay Hints. - Updated `AppShell.js` to delegate the settings action to `onOpenSettings` callback, bypassing the legacy popover. - Updated `ProfileModalController.js` to wire up the new modal controller, handling state persistence for relays and privacy settings. - This improves the UX for managing NIP-17 DM relay hints and privacy options. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- components/dm-settings-modal.html | 108 +++++++++++ js/ui/dm/AppShell.js | 9 +- js/ui/dm/DMSettingsModalController.js | 261 ++++++++++++++++++++++++++ js/ui/profileModalController.js | 69 ++++++- verification/relay_ui.png | Bin 64622 -> 0 bytes verification/verify_relay_ui.py | 75 -------- 6 files changed, 441 insertions(+), 81 deletions(-) create mode 100644 components/dm-settings-modal.html create mode 100644 js/ui/dm/DMSettingsModalController.js delete mode 100644 verification/relay_ui.png delete mode 100644 verification/verify_relay_ui.py diff --git a/components/dm-settings-modal.html b/components/dm-settings-modal.html new file mode 100644 index 00000000..dfc500b4 --- /dev/null +++ b/components/dm-settings-modal.html @@ -0,0 +1,108 @@ +
diff --git a/js/ui/dm/AppShell.js b/js/ui/dm/AppShell.js index 5a050e5a..742933cb 100644 --- a/js/ui/dm/AppShell.js +++ b/js/ui/dm/AppShell.js @@ -179,6 +179,12 @@ export class AppShell { settingsBtn.ariaLabel = "Direct message settings"; settingsBtn.addEventListener("click", (e) => { e.stopPropagation(); + + if (typeof onOpenSettings === "function") { + onOpenSettings(); + return; + } + let popover = sidebar.querySelector(".dm-settings-popover"); if (popover) { popover.remove(); @@ -198,9 +204,6 @@ export class AppShell { const relaySettings = DMRelaySettings({ document: doc }); popover.appendChild(relaySettings); sidebarHeader.appendChild(popover); - if (typeof onOpenSettings === "function") { - onOpenSettings(popover); - } const closeHandler = (event) => { if (!popover.contains(event.target) && event.target !== settingsBtn && !settingsBtn.contains(event.target)) { diff --git a/js/ui/dm/DMSettingsModalController.js b/js/ui/dm/DMSettingsModalController.js new file mode 100644 index 00000000..bde25d0c --- /dev/null +++ b/js/ui/dm/DMSettingsModalController.js @@ -0,0 +1,261 @@ +import { userLogger } from "../../utils/logger.js"; +import { sanitizeRelayList } from "../../nostr/nip46Client.js"; + +export class DMSettingsModalController { + constructor() { + this.modal = null; + this.initialized = false; + this.callbacks = {}; + this.state = { + privacySettings: {}, + relayHints: [], + }; + } + + async load() { + if (this.initialized) return; + + try { + // Check if already in DOM (e.g. from server-side render or previous load) + if (document.getElementById("dmSettingsModal")) { + this.modal = document.getElementById("dmSettingsModal"); + this.bindElements(); + this.initialized = true; + return; + } + + const response = await fetch("components/dm-settings-modal.html"); + if (!response.ok) throw new Error("HTML not found"); + const html = await response.text(); + const template = document.createElement("template"); + template.innerHTML = html; + + document.body.appendChild(template.content); + + this.modal = document.getElementById("dmSettingsModal"); + this.bindElements(); + this.initialized = true; + } catch (error) { + userLogger.error("Failed to load DM settings modal:", error); + } + } + + bindElements() { + if (!this.modal) return; + + this.closeButton = this.modal.querySelector("#closeDmSettingsModal"); + this.backdrop = this.modal.querySelector(".bv-modal-backdrop"); + + // Privacy + this.readReceiptsToggle = this.modal.querySelector("#dmSettingsReadReceipts"); + this.typingIndicatorsToggle = this.modal.querySelector("#dmSettingsTypingIndicators"); + + // Relays + this.relayInput = this.modal.querySelector("#dmSettingsRelayInput"); + this.relayAddBtn = this.modal.querySelector("#dmSettingsRelayAddBtn"); + this.relayList = this.modal.querySelector("#dmSettingsRelayList"); + this.relayEmpty = this.modal.querySelector("#dmSettingsRelayEmpty"); + this.relayPublishBtn = this.modal.querySelector("#dmSettingsRelayPublishBtn"); + this.relayStatus = this.modal.querySelector("#dmSettingsRelayStatus"); + + // Events + this.closeButton?.addEventListener("click", () => this.hide()); + this.backdrop?.addEventListener("click", () => this.hide()); + + this.readReceiptsToggle?.addEventListener("change", (e) => { + this.handlePrivacyChange("readReceiptsEnabled", e.target.checked); + }); + + this.typingIndicatorsToggle?.addEventListener("change", (e) => { + this.handlePrivacyChange("typingIndicatorsEnabled", e.target.checked); + }); + + this.relayAddBtn?.addEventListener("click", () => this.handleAddRelay()); + this.relayInput?.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + e.preventDefault(); + this.handleAddRelay(); + } + }); + + this.relayPublishBtn?.addEventListener("click", () => this.handlePublish()); + } + + /** + * @param {Object} options + * @param {Object} options.privacySettings { readReceiptsEnabled, typingIndicatorsEnabled } + * @param {Array} options.relayHints Array of relay URLs + * @param {Function} options.onPrivacyChange (key, value) => void + * @param {Function} options.onAddRelay (url) => void + * @param {Function} options.onRemoveRelay (url) => void + * @param {Function} options.onPublishRelays (urls) => Promise?#acgm8#BFU1?KSMC%u#KJkxB7apW$hK(;pZ%ryCJ%
zannEM7>gcW?<#~cRG5{IG^>!+YhF%-BbVoMPB9qvmg?>r&h1+rCDzYHiwZZPS 8NL;nIVd3~r8hy>Al_>YZA2?}|}yE?5eUbwG9u!$xAS_zBc;heyqT
zJW^7Bk^nnm2C{a96q7y(#KPl+;e$!ScgK3-rL#-K0Dy!E^yPKm0`V}5uK$kq^t8t?
z>&iwiz8>sZO|tJp!Nk-pGeY@<^`_?OM(U2?6rix_!+5yYmX-`fdO(bA!4#gEoa_wn
zmCVwpv7@~123>^jKmfI*{m~KsT>3{RTb^dU`k H{hlUjkL8@_F?Q~MSjL_gn$fv;Uzp?%hMG-5~H
zf&FY7`eR~(<1%T8|8?M%Ub-qODgtdr(P$sG_z(i
z#0l?}j_V4phVkzv@)dy4MGC!+rF%UiARL)*w{0v0IR4~UH(;7KFT|8MdWyEorYD~L
za~F2F@QsVDE$bl#suIm!Uq{Efy=;D(`*vyj9Y+VU!4J@zR*Kr
Ry1&!1Bkw{joO2^3
zCB@9ljP&krG6}p?2|7k8Z^XRa`%fi;W`SXsbBk9FjEBELAYRyVBZ3c@>*c;A!~4lf
z@J$}XI2PmX))J>*_?Z^Js_vL}l`(??bU~>S$>Lm3^tl;H4mOfKss}#nIi!&E^1s(}
znm#&9wQ%3t-4lZ_>l42geKV<`aE7_Z`$yj_HAV)C&K0&HxTV&0cpwW-urFA6Sec$Q=JHIO(keLkB8Hlesdy12U}6q~&sadko>cCRMTj6i0;42`
zt~7KX_@!WZXx)K;hV8gj(WkWYYdZik+x+;U0z6a!;I_=R(%L=49*?&TV#R!0ySHgp
z*5Z{~mO!%LcSd&~#rJJ+uui@qT8Bns%fKyS++i4)$@9&=d^v^{%~R5X;jHejpl#bH
ztzI$g60Xyot-r3$cl@Xb7eVlZzMc~B_Rl>gE(19gb908*Bb;u?>B=D@^coZ`$Yl(J
zk7%c&E(m^=&f{G@DB&D|i>p^S2jf_^S*w=*YPF4AtSllcX~k4kL_}n4NEX5+!@S7u
zsb|(DlO$aFKQG|y4I6K~p~n5Ki?M!4`QO8eSvI`dYS^
mG
zv{-Ob-#Ym1AhlVCrCa#9LfdL%6Z~^mzuy*=gbl6D^lGOTrC|5I1#~lyoFcy}E~>Zi
z%S%axjB@U<8rSSqkEWVikajglLeZ*USSqhzt*tU0BvJ}|Ig85DjK)Li5Q=8006rhe
z6`xOjHywM+hXqxCo*<^*4mES`-HI?@1hWIB`de+(pM!%8ebVUuNiXp8ANetHanLur
zZ&f~({@lFWD|m;?JNuXa)yUPg%^JS14|yQ!LVlSRj=epObL_kYL{`D@ddXUf)U|$R
zc_;XvO>E8DU*b7Q85!dQOW|6_`@OpPhu_Q^?Cr_havydDmL)#m^YL;@S<334dZ79}
zk9966_$c5&dPc9CEr;vpF(y;8xN7K#3Pxc$6Ldr}rMhu>^v2~=kNUTle@A0UHZse6
z)*ULHwjPGwfx~w^z3!y;4CS#-m^b%OMBH-JT>Tpi^A=y3n?kaBuyC-LHhF~p*%y`Q
ze9z#I$8W_$HMY$Dk#fwonYZkil@GqUmKp`VL{#i{JUbKYv~7UW-1#z=KAVFN~x8
zRus*A7YwAc_{m@aGkJBk>$&qmaGIVi>s$-WwxavY;tvsyu>GO=7W9UFeUA$RzkcoC
z!_v`p$BOAjX`fbI6p=t)$NArw!=1x-O`e0?U&ET^9rui93gbLBXX^v7GpuWjRwvLv
zbxfd3hRwtj!wMs7$kEq+zJHg0Ym9cx*ae&1y{DsuIvnFtTI7G8v1J8Y{#YO8pJVvY
zQocGMWPuiF+l@7GY2Te({Sf &jBf
Ul$s%xkpqo}FsDo?nCE=Wh}6QFzc?EViDR%EDiy2`{U|X_(Q^c!YXLzZHqO zX~WKUM-7#1yHoXJHxaWJCnOaWtI0La>?OG!U#Vz}mrS^GQW}_$r*35KN9`xRi0aWF>Wbi5j$PjEv gCmdHLkNXg+a>UDDuWTF=OWXMHIR0^F~;L(CaD7Z12g #)?{6Q#H0ye2vzw5Kd#lIKmec6|U{;uH^!n_d+RCiW z%)GB;u{8J9OVtyX6f0Hqm-VcYx6AWmb``~C3A=#$50$|b}DcUT;v{0-JG3+nr` zm@7&`+uvL{YTf*IF2HecYS7PQR- hK2jJBz2f^uw} z^0vwVO-M?`5L72KGw%B%shGX&shKbueyLaRqQP0;&n%+|3ls0^;gkk2#FLm`d91Oi z$ *s3JY z5ohxxOQ**%ua}vSnk2?Omv-CFN!s9Bzzh5w&2hbz-&fn8M#J+|s3-3UNy=V<9Hk_4 zpQf0Hg?lD6UA7<6a9k?C6^L#pD+?O**cvc$YN~XQzozKz)hg>TSn`~d4;Bs5!50_b z$cryetSWn#F6!Zo4v%v|9P(t8zApAru3wKHG4_5x-ReKsa$VW_q!}>SvRRta)s&LK z&l+fROo0!)Lpp`(WgHBU3NF;FTHRb!9l15vsTrt@UZ&sSSi9l2FKGT3z1+$TJgo^( z&1iGUoDJyybZCvMJh|B|ur-<*anviCz@^KEa;^Nq9#cZ3nik*EWz h- zn1B1dzAoB{qjgqTIomGpAhvp{tZC5O|DJ5kmeO1G{q6w?=kqvaD{=Qc4E0AXO+%lG zl8kI6v5kfAt*|s)NL*Co*#{5B)7wWCCVTGmv-GX{R1@D5xt}tM3L^NFTDb;vq^>Uk zYzIks$^@JlDQp3HrXHWA_~D+uhsSvZp;z%*z27ok^H!9LdL>U&MMf!2>{%K?W_ET? z@%bmqudeW$&fdIq4%md(T#aOq50TKuKp^Toi s@Jy0O8B UwtHL!dK?yx6izT&86PI$Ta4nFAj8fA8r&yT nNru<;fRU%MlzhhH z#kbalX`Q|f$s+j VRmQx;K!MM}Q z9K8#0nALVjT3xeDP&;GiQ}!s5wK_Q7_N>gT{~-{VTpUp9-v0ZtAvZgZRW2eetp3N9 zgPsb?P)bV#O iSAC7uDn@Mu?;nbA{!#pUTrU4m7X1H&9B 4E>du8*4kx6=)UGtsFAccn`On0RafE(23jD7hv*E6c3 zp55$~#oXt_RoUzM)YpYkm9z+k @&+J69U% J)!)+fwqaWl-R|%G16 +y6V`Qb9SCSDA4u{`%Va@t3IueVd*S;o(IPZVB4gi`rUQQyiiVwe9^6 z6B}CO!oPIK6fM{~bB#Grq)KGlzwu8X`h37$-1s&zci{CT&T-7YC}`PU9jyKocr>;B z)mIWmkx-=t txeh!slV0!}fHCIilM}TZXrm$VY%5(hZd68TRnnt6OP%L*W`lA?E zPuSZsji>JZ?vdnauVk6G9)em$VjU_^O{IML1^sGdEFJe1Lxev #}&vQrcr7i;AW@buvC=~n-`nV&gHS` zFsL^!)b`}OCS!fN%$Uw!H=eR?Z##jr?3r55nv_)r!C<48+6zl42wntP{a91QK#P #_evUz zu=gXtIG3|%*MX%aSH;C{Ob pWyD@ |0V0>)aQ6^Jhd>$@ zf8=svl&NF6APSf4nK7)*x(2O=C4$;X*BpPujh-dme9fmP7s^sf%bK7NPWD}NLlxAM zFou*&Ob?7@JT6fJEock|eYF{;-xJ_Cc-vPOVI-TLC2R$X$4e;f3RvaS+pcL&(a$d9 zivpG`lUpq5gnuh;YRHvStitb>Rd8!Q^C5-_$d^zG+ec>B4cU7$(iyKBMk&7Uz41Sw z%x;=({3>SdYx3N&{Y4Ll&)prKNhC{ly&G`t2&$hLMo4s4A;|=m-w6FP{9Oi^+c#{R z`-D5LkhJi3V|E%KAFMo$ViCsC%s`-~O$Br)rqw u3`8@HT!@XfMu{nNhG5NZiy%gQ)7cK^<-rmr)P~>+l7ScL>Bovk{R*v zukz*$!in?50nuwZpC9~vkJ|o-U~Q_NQJghfi{dHbohHBgmTh6LlU W#8- zi(>le7d0|!z!atSxInEStY8kW5^ND}r4?$bN2%62JE^HfY??ilm}Cc#?bBbhvPk}1 zNxC4v)lW|O!%eBABC8@7{$}Rf;%$ )kdnz@pWNXPNh>F9H?02P`oY?(p&|A5x0^ep<)t!( z=zBd~AP| qyWh5e$9 gy2WsQ^hQV-S9YLY%rE Hj3 z@!DoK(tNG9afat-r9%dZih0DsRAWgBDIr0KEC_q`<1b#C>$zd@>ts@Rs+k1O6mEt? zAuk6FezXn?i-0xTFtwqk63v{-CS`WAr}qgfAft$2kX=n8X6Fs@^)=0`W=P4rKFoq| z7tg-G8loIMiyO218Q#@JvvzNbGHuH#K;16ghec_96oas7`B2kPuq7yi$c?aPAAhBL z>qI6D5_FywM1Tf@hrDi4_wVCuhu)v022P%fUUd%!3(w9#Dpa#-4R}gMax_$Eeouc) zR1gsOIcgkF1JJ~NC}=!XW^ZE#Sm=1#UoTBi{@=NPw*J$bv+r(sJUXq7p1Wg9OJ#jU zu*=sZbSxA4@50(a15#PXJ3kY?Q#Z2dsWWXCYV31!FQr9+LaqAWx4E|j1sjKKWc&JG z-9>^D996p2|MKcj>ALaSAZJ(F0v1s$CkLe^@cQ}6tTlJgi?TyLf_$7TBw6G?`)>;B zwRI|@857tH+}%G%(NW(WT8K@QwmVp9QFk0v;rGi-G&O4WQ$`bReTyun7mbTKdc9s@ z#ln-^>B#4>BLz(@;wtl9$olRCc#YWqvp!-SGlcS*E$Vw*REb6L)VI5D63G#NJ2UQ= zZHZ>1QK+z+j?=tzcLrR2H}4DIf0V!rV8{j{>d5(anvhhdAXU|;k3AdoMYHF3Ie4Qg zqm4Nn>_7eN2Bh}>Lw?MDrJWbIt9jKGKds=EKi*W#buHH1AG3>lIWEI-;3&qQ!EPVq zUjl9%x6aSSd01(@1r0Is4oPsBF4v2Y#7=xs>Cl>{CVxl>$X_d=tCQ3PFc 3N}Z&(npMN!jQMZCIXwDd3;YLfhujz8HbkER6MOn2 zpDXgPWh?a3SN69$jR#-!IS1?g>=FEloBg|Dp%>4^^CjZA5Ui1qK?b-nhnCV@1%!h< zkImh|nk~&cR;Cqo;bR<7`wb8IstJ30LlxDENUtN{VBIodNQz#?Et+*Z0mU}*?BrR- zUpBKy!Z*Rk^^LkjJ?$Cx-mTGjea%Xue7hggBU#CIQVR DHJ3eHC!EH+_~cV6WNhr%>hPmC3(oT?Opud3RFcdZWHw+a9|f!acH;t zF?BXbHr_&CHSYEd+C)@3^eoE2kp5PJ(++aAiV7ko@kZlb2aY$NOP?zZh-*>_1F4y9 z#uFsRf^yb`U`yCcWM1w%I(zTZt7jiui*I`^jvsRxZ+iGv+me#VzQ8-ylATz<^w1h2 zGNi8tfEr?zN@?A>W;L(1Yzx&$nTUal@+m5diabWr0oo|9U2k0VG_AGSIp{51&Vc z8Cy2%C~oUqUr1$xMWJ5r!?=l8p0m(~ >#-e!{DQ~{G zK8j*Tjf=Be&tMAfc#eOvX21-T_`duNV3w7=RU;!W&P3K&_AopH|J=t-J9aE&;B?7f zl35_zy|;98)S!t+KOMqHW4b(@P7*Zz6qwIdO7O|5@OnSI+>Er2eLK)6UpZ4eCRWFV zW;waa+L$=NPoYibaosOB^$QiJojD-tmT}^5m_W-jOR3D7=)7s^Ptwd8;I&P-GtEIl zM#O`ZzJJ14{Ohl!z<-J&Bo+9z(BvAhS@MA$h;cesLa$#cS6y|`he-nK2W7OLvWWbm z9Zk=gE@pgTFN(_i?pcHLck((H;c+|b7sglM#d5?Oq=$|AOIWPi+x5|dMmNN~duIRV z5zPy}_0^tst1AYZ2!nXvT&{@TGt1{Y9`-^da^6?ZG)j51#)aGq&&TmYEyLszS;)$Z zeVH}o7sld)kvuj0;fGF?{uIb0pD#yXu|r;WYQ%e`#6|sh9Ys~xa%#fvXh5c%7=;(r z*i6={)acgo<@1>tF;GNvDEW=2;;iM5!Dys4&o!##OY4sHS+Amz6$v;&MfC2o`)TCOwNdH@EQ4Dvu7Jy1?5S=AA7G z8-<8=<^PQt)N-94oB4h=dYw?YwH_JBU<&q=N)DCDTn~JY89yy|f4xCy=I &I1ZD{9;?&0(O8%JVyy8|!CYXG$r%=fGEjN6)weR1-ryBem>r>>Pl< zb@{7$^D-M0gp&FZO%I^65QFMwf$v%R+zqM;$lbpD*uiN-&I~mxE(6GcRljXRSNyy} z exrcE4XS%1o=pb;NFN!)SWbK4wfpgN3!M%}Mp~fQd9RM^|_< zpHXUvWbsYYOMG`5DMl@b#yea0Fp!n?`RiyImN=?(SFWE-&2Tu*7>ui2WNRd^zrYmQ zBlkHdV~Q#| *?dwUYury)8v@Sob4FT3Qg9sN((X~A8YY| zm1s(3)-UG?Wz}X3)RV`V$PIB%D3{@es#dMSBojV}I_2pWY3ccrf>hifWweFR!EFpo zUib!Deb=WVR_7NJ%`{^X_$uuyvMM+c`St4Hrok@V^%tkIKD_@yXn|g0uKEJ4AofT( zosVI2r}n8U--@uM`=fdt?)Wjw!g?S#qEV?o>#SXD3g5%}^9xl#N#^xhVSza9@Oq98 za6T4)p|rJ{IgvRbj#D5d_P(M|pi9S{wwF6m<#Ii0p>j0$itLd`Aj=EY1QGK@U*YA# zi9j@Cnm)4s-IF?D@?l+Rw{B+V0o$dTUPGtuxeJd02gZpCp}czS7<^v=e~KG2B@xPa zlBEsYFo{k63zKYn(*`$ghvqv^#kV%38PW6+-<$QW7^%5;`*qdYRUU)46eNBXQ+~T% zs0_`t;>fbWUfbXMxwB}-WlKIMBx_UZBcW3u>>i Di$PXeN&LJDMw8V~k k# z=iGH!=T6^OGtH(64|And#8moBLA~EOEm0KiN)K$Q1iu0D=G6?YNHl=`wv~^N8<0Fr zcloRC9>dndqcF|N=f-eeI%(#8>`wGxs1hbc)K;kHiCMulqW-M#BP+vdLwxIAN65%Y zG+4y=DN}p;3;GB1;pJ6lFB!=QS Whf>Sw!FTrZu#+VoQWmk zEc *dDe_Vo2QWg;27`T?&vi38Kaxx|j^>< >KW1MYel@!w1PNTyqhFCdw5^F-+1 z)}1~9e17zI$N$l~XH1Pn<}27*3bn%L=lMeV3(5Uq`G?7TH!BGTi{s@1i_xG9DLo#w z+-tqV45778%mgMuvOCuQUeqabx6T7yK>f}ZkF>9C+Or?_TN0IyYTk< |OYDEo zgcWFT!P&N9;P4_%-02Ye ^Eh*Wa?Y5~{{NHT$RU*56j0T3nM3v;PhX5Y=3& znUuQEZa6-QvCLnPx=-+8UJpwcl6>vmB`~*t6I|02C|PUX+eEFkiP0f2@vt5GA+)ir zTzt(;v?M;~I~P5-)m+(?UQkn6XkLdou+w5J1fTx9&ZaNXyr6eid<9wlg5Z5&{^m@p zdMe+IsV##dXWJPv_^i#Oa${HjYW{X>Qb3@_0p0A857E(S2sufwTpETybS&|;UOcFr zIB#vZxbHKV4BI%dRYwFaHu )b6(r$n9S~&Ut2OSRkFeT! zbRl`AV1C5NPn(|1%J^r5eWH~QwRlCld3#!eQBk@-PY?TjvDG}92?Vdf5{yc3kE+U= zD=s(ap~3!Ws8wmTu2c}}ZfGhb!=%Y^2DS0!ZIzmDmJf8a56163x!3uApDO*3TuBQPk)qN6{9$H=8P_`c3N_3C;|S?DVP zz9kWN#_96vS=bS}YNfF*!?|x-l?K*-kG`$pw=UXPf6-)rAWPIAoO>nyF~MQ4UA={c z9xdE}hY78Ew9%Kh-Lxp*^S}=M=F1pSqx9gzgHrFtf4dWS%I%Sv!y0{ybj=y@a)fuy z)47tQ*LQiG4hO*9(&8e}ba}2uh4_=!`}HLg9X%~^^XvXzL4q@*