From 9be524faf249093c50904392f1d6b5eea5e79b78 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Thu, 17 Sep 2026 13:12:03 +0200 Subject: [PATCH 01/11] Start documenting Headless Page Builder --- .../field_type_reference/pagefield.md | 6 + .../img/page-edit-headless.png | Bin 0 -> 46644 bytes .../pages/headless_page_builder.md | 223 ++++++++++++++++++ docs/content_management/pages/pages.md | 1 + mkdocs.yml | 1 + 5 files changed, 231 insertions(+) create mode 100644 docs/content_management/img/page-edit-headless.png create mode 100644 docs/content_management/pages/headless_page_builder.md diff --git a/docs/content_management/field_types/field_type_reference/pagefield.md b/docs/content_management/field_types/field_type_reference/pagefield.md index b8497dc0d2b..c90a4b046cb 100644 --- a/docs/content_management/field_types/field_type_reference/pagefield.md +++ b/docs/content_management/field_types/field_type_reference/pagefield.md @@ -72,3 +72,9 @@ As a whole a sample layout could look as follows: ``` html+twig [[= include_file('code_samples/page/pagefield_layout.html.twig') =]] ``` + +### Headless rendering + +When used headless, the front-end have to fetch the page data and render the zones and blocks by itself. +It's also possible for the front-end to provide a URL that can communicate with the Page Builder and display a preview in it. +See [Headless front-end preview in Page Builder](headless_page_builder.md) for more information. diff --git a/docs/content_management/img/page-edit-headless.png b/docs/content_management/img/page-edit-headless.png new file mode 100644 index 0000000000000000000000000000000000000000..ebd239930c0501c081d20ff753e355eacc2d787b GIT binary patch literal 46644 zcmdRW^LO3P_jTCZpkZU%Xk**9)ik#4q)B7jwr$&L%*M8p=cavs*7t9Ce#lxc%$=D# zbMBda&fX`#mfN{XM)pkci-hS}p#)cWALY!(@&0>E&WcwcILJAJh6Sho~11F<}ghNFqdlln$&x){LY)dYVk z#5zZuIis)1hS&R(>tz!MGzHhk!`rZklA8}M&v7w)gV3sP-oo;o6HP8t@%^o+*8~x^ zE;rerI&QXgcgV$XLD9ED62$@0LQBXK5paN~cnxlI=m82|_#GuL0bUch>JSFvf^u-5 zK|rm*cg}(LY=&y$M$*zCRKRNh2xyQg2n6s76!?b)`~z-Q7TEuufaYd_|L+>i{^Mn< zt(_7G2p@>J5WkWO=t(-H$Cp9$LEW^?vzW~@4K5Sk?K1_63fe${=!P$}a%FOPaugax z+j6U0k*%$telNalSktvGwUQu;*(G+Rde}8`ayD$Nr8b_AuV9jggF`|ce>RZCWxn%c zrO_qi9I$O?cw<4e45_#H8PB6cBqYS9pVUmM004jz5j%iiVJpks{um-AgQCpi>FXmJm;fKJmq8SU z;fN~iUTCSD0kPbxOQ;{d^Ou`o{+&bfL*3ZHOt7B-**-oXSJ&4&-rg{9a7^T?D=LSs zgKd?_2h3YyMgG|#u>jyy0(=mIr6H8?C-GTcIdjsr(uU^y<5ySLhStW@)6&$deZSJF z(Ls0@*VfO3A^KlM5il?WXK=fhKt#i$?`>`I)6vn15%K@~S^-_y(0mts$(Q%{s-`;w z;gWc^TwxxSs3Ky!gP8cdZc87UZfgge<*q)7dLl!Kn>x?Y!Sf(2);ou%z;0GQET=DKuZa;N7tH0 z`uX`O2^1+11&{ssq50n`OM(bc*lr7rpl>bJ?n87!c!CT|U~qdrg-P36ePOLI5sD6^ zdkp;7B@?Kzv9UxP1!23(r2sT3cOYR{7LR@wrB0j6|1CNp;x2@iHm!{9WM-${X>jPi zw;)El+pPpURdB69oP8qLzs|aT`V<=D+|^v?5;VElGXx;vpl|LH{BM!?98i3m0>0+* zc_pHbL=%ST74$v}A^-bkd;lR<&^1@k2z$U!2AGAooK`PN-TxMskHI&}6dO*T#NvN# z*+P5(#G=uOZvPESK+rS9M~MB`J_t7{z=*VP42o ztP1T;ndiGx)&%(S=i!$#u_?4*UIy**#j#!^i7=r(#|fWxK;GR1Uj8 zMQ%y#E(Z*!yXNQ^7qIiBi?CtM1}nz1 zQf~JiQLi-=J-?i2b#3J#yCHy(I*QNwo?0Dt!uk9>EA{zTe4#8o`d&ZpKdW7?7tGFfe167bzx~{Gf@OYGh@UHHQXA2Jp z!c+B0m2FikwH0r9J)e|LchYVxxltw#X&SeB`a?(33LqOnk4=)nCl2R|XB`gh`k1;< zylV083DIjfTZS+Pa_fmFD zTCFx^M=W_knDRUPV!mfIp2Q+^PJ6c3(cvHhy&vL!z3*|oaCc4rb#iygdBmlsA>`6` z4}Yd=-^JWt&*pxIv*qjqV%{5yoH!E+jNZK=xtu~+W_}h zyFX&fRr>zQ>bWA}IY7Uv+3yM9d}>?!x*d&#n^;&_IBmKK7J!N#O9wX)|LHT{hyjQD zJck^m0!iVWrDkbrgYxL!T%9ekMxz(F^o#Acdb1_Jy>VSW=krzNQmh1O&4ht4tn3Bf zLH6ueoSR!Y<^^B!9VXKmg?Xy@G8dX>PxeYBL2!kZD-E%Xjhy1! zeRGes5p{aqL}qfGbdE1|?GlYvYi>_oH)<`8#|>tJ9c``;l(Rv5uPr`CuP-Quqbc!& zE?~;qwfiqQ9JUTOfiP%!m%L%weOp|VJYFvW(o!qMGU>8#DCa}+28Sc_)J)I&hkyDs z@7D>pNKhZ zw@Dn2r&Et?pZLK$mDFFBEM{BJ(P;u?ui2#vT`sqazqVV8dQ9%nvOaKW7xTS&C{+tU zYBae(zjAp}8IH#YSJL%t$UZb)o;GgGO6-oI2wm><7doA4%vP+qKWOY_`Rd*k#*)j* zS9WA7h8js9WO}O>!!!H1?hVI>ibTF;9FLbB`7>a?jRQ2=jmnp0lNdmmx1N;@2y0B` zWHOzy@X2MfL0>zwIO@%d9V(yITCKoenLIx+TQ9`zy>aNSZGgUkD%F`FVO^fujKopq zj=+Pmo87QI?M!w8_Mz_{#&LPQ(iHN?2G?gZddXf~9UoLudW%%s#GKWv>tZ#*Xi@yB1{ z@?LPBER7Lea>LPNRms?O$(;zn z{M5TyxcI$=(-kxCuC8v4m_Md!c)@b&&G{m0q28UIR(UxLi&M9VscgE)j!L;S z`?b-Ov5-CQGwxlvnR826a=raNeUU>0K5tlN5vfdq1hvLCaX3^1vlGk|nRIeNzF54@ z@%>{W7VnZNs4u9-%3Y?}A*12+-dK7eE{BI&YNuYdN|l%`EoSD1#h$Ot&7v)>{X&|? zZAyJTt!6`slNYl~a6&hAX#%CaQ#iA5{MqlvPSfI9!=WvrD*@ORFl5%sno$+kuIzcV!EBh8Ds=#wlj6%6a*Jfu17*qbVyPFC}oEmo!K z-S0EnsWzIg_v^JhPE+bF%B!!#K3TnXJj6`q2Mnawwm_*5nGP%F&n8qpDDGkohQ$n_TdDtL3?ecv$)%AFc|1_aqv%gTG)%?Bg(h=KW;4RB=B0UH<$n=H)9z{NA5p#iNvMJgm`sx~7Yy$nUe^=g<&Z zjCD}Ijd~(umDNYtrK7*+*9nO$nxc?P%O9#yEB^+)rvr;jF1n`CsR?UvSURV|hXte6 zpNaE0r&lml!=4^dEz8;6efH?b_cjfB@Ji+}C`e%S)>p=d6^lQU#(#flRaom}zy301 z^ui?>-;1CTBAWgbUWZPvQ$!>Zft5S){v4e<&;5EnqO6f@hpMc-8Q7JQqf+C*UXG~S z+xh;c8qb`3zNO^~TlDPm`ZJtr?GBf-{zgZA?shK--mw#ipd?-^Y;*vU32zk-e&$L) zuRS*EO=m@p7prqwTrTv&X;J0XJpx8K91fxu^2AZfB@^kE(RDqtv%PCo^~zH#k0q6H zGX#`v;utiBS&=zZyr||E+wzzFQeka32+kg_H_?{QkEMG6=@`IZ__KWylWWP7{*Gv1 zmuziYrb22zuZexprlPQcEIVqw!5|&2@X<|)AZww1cu1hMnIOj2U^=rXo;11$A{8Rn z$*IAQR+}v%XOHHvr>W}v$s}RNGZCWogQ@D;Bb!Bt?-48wg;KA-0!?5}&%J5(v+?y# zrC%wQxN@?=r4lJ?Su!}q?UR`I(Md_XHJj9gy|35qX@qLc(do3mkt^;a@H>T$_KfT1 zD5;mbN3N@wHdtXfTyPAabg&gQOc28U%kM6eb-;usbokw&w%&WmR>@D=8~yiKY-w zi|3qoDoyHmx-=4*2aCb;Qv{yh6vX_`xGFh%0gB(&fzF?4d=|s>Vma^1w18&{O zOO+A4MU2X-YNdK%&eBu%(cmlEyl*`*t>H-A@<2G+7RLN;XK@=i6ugP!@xuJw6+2PY zqulXIW92k@C#Cm~$kf2--f;(1syK3VJiUo%V$S|>Y}G?&RCaj(33eoOi%%}1{q!0& z+IyF9Nzt3P#_&MEn)PtG6BtHY^>r>@`RaRrNJ?yVGeNk%;^3kf!AjIf_67b|0 z*QC6*=>2qiF#7g7_thh-jK%`q-*ga%;U^@@=E|oepoeG~FuE{KOd^MJ79MsrhKCaq4|R7N5mqZV&SCkQ(er_SQN-X_ANmM0NE zgHij#GDNx1=%xzz3V2G z$J5P`m*9=0k<#_*hfO|3TDAC^{jZ1apH3W0!t(7Rym_$A(sFWHx-ZmK#3oWn+P zbgY_iMeKBBf4dJihg}axD1Mmtd!6P^t^F>Wz0Ix@=U4Q05lOcS`Z1O)RGGAe7%YKO zsz}8H@N+~lTuz6q)n>btQicXpMjq9bYewXYPZ3*M7XGQJoV*YaB}Q9Xo89WQB98G= z%<-e;%B7zSS)tLjoE<_CvkutCZ@1gY!j_gkIjrs-Te@?-c#_Xo6VZ<_K&3UZ!{69+ zZl;Zao(g)c!Z+!J0*jdj~)CL65Rl^0)E zmBa25XL)EbLac0jnn2kv&_zKv3OJpztYLHXfsWYWM?`4H0H0k6tzp7!(|M~U#UUkJ#?O~a?3)Af8U|I#itO(sey&-~NdIfK^ z-Pkix)H=Mn#KF;i#i8yAx@!l(x_5~}PGPt;XYO|Zx2iS1tkAeYG? zG2w9Q%3>dJTSM4%+|9u;5w@_(HW-Y+mMuS!`&}#Y{n`KbaepJ@`-Kf-jkG4aA)#@R*{8ls08*qiTJLHI|*(O7ql}gBMIX>3`_iD>^rZ*NN-TalmgTuN$L6Dq% z)_Nxvji4+<;a+LB-&cS3%;ON_hTqs)LvT}uS10Db5OFcw zv03MGoqGjieJ+s3!c<=xGrZKR)nnc6UE#ytYzk0U52>E@!@JP*sgg|RsGk93598|Z z2Z($=J{CV%zQy*iw7T#Q+69F_r&~|wig%F;KC1B##hW7II@#p;Nz5PeXV`V)y&fJd zQVrbM`9AntqWP-i?>gsGEqtr1 zQq@XIdq16aydeBEVlcW-89G@@EVZR`-RA4sFCIl8K?*+ro*)@O&a12Pe{RdLbm$k*NAuqIdcIj~3nnW-bsg=mY7D zA~}=#-EPk$U8<+o*2Y;9t0S)m5sVfukR*cn7^=Y`q%)pS9;?*hjRv%6eh{3vyaYI% zF6VNHI*DY<8+cW51(xpHZj+C>9Sxj4sH1DNSg?0aHAlDH9|)dEWpjXQnVYz{{I)o2 zd#sDnW&7!R#*>Q3fYOx+{)uwSh-soOI)u9+3cq4(3)1<_#_4QX8ih57^5>2>}Dks`1>ld3*Pzxv#7kY{>)2N1Ql;jAJk8I5)YVF9y8r%Sbw8+SV0 zvazPc=82OO@Xk_P1kxvmxn6wQtqyzG%gZl_r|6;9N=^F>Ci4q9$q*Y&C+Yo+!gB?b zv1Bug)5O1^x{86Z=gB~df}>E?ZtntI>}N0z35TNx7AOae7D^cUrf^)wn5U{#oAyuP z@#a(!o=PQgcHP2-0eYHf+wu*Td}&rzKGY{C>a(VQifmuL@XCbhS=`?B;^LlsvJYap3xa;>+sg^?lYP9}G7Cy( zG*)R_jGc55=LjBW10|!wu&2~)s5>H9T9IuA8rm`(TD8)#f*CNa32oZHelBqvqhL6c3HSOm&HPJeg{u8%_#j$fZu_us) zXYVIVHI%Fm-6v0xKF;A;w*ii4Mc_N>48Q$6tG9(8vx_s`xIxdS_yJNlyb1Vb$fY%( z;0|O@h6K}FomBUIp$2n=VNuSYJojK;DvYEu1vWfcV-``A>nT}VTfF;Sm)uoNMJURw z(w8Lw)wA%lYE5#)l#?JGfxYQ4Oic}}@Mkom+CZ9eBW;3>r*xP4=e+@-?S&Y5TDthY(rgt`T zvGZSovQ4ganfjb$}XJB;V za(!J#YXSP|>gxNf5Rq3&r{r%}2HXO=By>yZDr`$y_2=}-jTe#!ojR-Sw^FohH>nr7h7#pSh*G0OFf1&B>OJ*n$eaclg%F> zqF?Gj4H)zc;n|?-P%m8uO@^wh54zU1RSqZ8*h_U>Ao1z<0Pb(cJ^M6pEHw)ajnc|4CZ zIG$#;>wkg({D4^1ol42J)Q{rf<^95E9(3VH;A;Su)H@c+#;v6v8+p#HOC?9pX&bp2 zXpy8sxcRMy5d@DRi*Rd=ISKM|=Ijz>aCl`jHHJ*OhhFLVs@+Ms+UHZK&z)Z^XQaGu zK6_R;qMr+&Kjlp<$L)L=yo)+o3K1TQCAbdX>ASFQe;_yaVdSplQgnB$OeTjQo<#?( z96{!{!sP2MQe_5Tw9lvb5iln?m1mQ1V?#IuQ#QO?JU)!#xn$#}hua%*D4`kf=TGUJ z&NAFId9s-i8=a%&6}WYbnN;&}JKzvG0Y#)fP;w7|E~!XtNl4LVCwr(8Td2HC*f=-E zI8C~B=B-4IaJD5Ecd%7)Qu(` zl~7~>ZdC8X2<3e7XGbxbxlHMV$=+|!L8SzfJ!`z1nEnv^Q~%dg0i`wPsfb5h)&-FB z>sR^c^dAaSTg0L+WTEvCuyA8(NYF`j4R)mXR61i%w_(A5;+c?$L0~_<;aU}hs@apy zw<2!tqZ+nss)M^oq@s+JL0u~nXS`G^E7nD$U)OiPKwO7F3?go#|2jmtTkU&*8T)?WP^ME(CEKjT z+Ed`|GNeCtcc~UNhJx=Uy9H7KFtS^d;bD9dI${)r0%|hgCdDk zZ&8V3|NY20wXYt+CnS;cW5|SS%Xa~_emi>XklT9 zvH+xY=xBnEJ0SPhg$FT4G$?|hPPSVoN}LD(j~#rlf#!Sc?&L9O3ukz;=^jdk{pc&e zX9u7LO-ybTlyrpS{58+$<5^db0Ej&jRXN3?iZN}N3zmC^{G*^g;XdWR0pJOI3`MGr znEcL)^?GG|tC7j8=0*kbL}}tmg@B`I_YM-xLvDG{K@MBk_6V z{ls)<*0e;LLXY*E4Qb>8cN5mu)*dzli&BXF{?v=iEbA^s9lKfJcO7BA=46PgqF1+j%7FVdxJF%IJIf*=N`L+H?h`{DkH zqlDjMLuhDlb#YOXL51o?1O;;B7$$xk@dW%}FfajeusK~>e9Bomm`(!C{-sV=u+j-7 zw1?6wd(!sodoD}=Bmlli!hDZSOH9Km6>NtwKNC}&^${%9W-st1WESJDHT3feg$bjq z5T)oU=n_m4m|Ci!doDNo%;2Fs*dQ-2H`Pd(@*Drm*+Zclw_|Iic>Fb)03Tv#3^2f2 z^HE{7{^5$hy9=9s>1H0XniI`Rf(teOj~_KSv%JJ4VK;pb4JO~vhI2*{;>uME8vJ>u zFt|Tom6%u*>D2~l!{<<{vkevgwVG%MIKn*E9z>yFhTD?`?dDxIV6w^h^YaSFv?eTZ z?v&hThKPW6q_lO8)b*q&6j(@tlNl0~f4uaG2;X_BAeGYB0+UfP@09JR<<-I!92m;5 z#vX@LmPDf;EGz<7n!?FWb4C|Il*<%owknrqvXS$hf=AJ`|5m*U9kd;R#XD%Od@zO6 zwlCS;akL|2&}|YKn`1(Q5yn%Ce*8fs29bLpc+Y7GYxf~Z!osuf7gRl5Y(_Cbm^dL1 z2^Cm4bn#ny1J?tj?mI}R!2l;JLGs_91U?98>H4CXC^~<4^ot=6js*2LTqa8@zl9!( z9yxiq(C|%q?o>LtDr+1C>=y69fww(+X!?1ggB&P<1Jcg;HRhUy&|1i?mZ9Wyt1P}i?XW`-+TrdJPW8_(o#T|^=}BE=gt~cD>0V@J zDTD76VHEg@n47FjSC8V6)|tkZoS}fZD}^Ld-v%muFta;)Gs*idm%-Is%-_yI1M%=# zJA>&Q$iUt_OdF79r9G0YI-uKCP)ETUALBhfLS4`xR+wQ+PmSIg6YdZ6d5&Tay(G79)Hd|x9 z4C}5)T8k=FkSF4eQ=?D0q=9jWOxlE`@0G12gZ^SD{=MGcfa}cwSxhXC^^+njFe#YQ zfpx>YJCf`_{)1f|ggrJj6}uC|&@~7;w(jk6Kht5;8>-kQTOf|bQiH?u39jB`9eBAS z17v>l^TcCk3dU1`B;1QqyBo7cs}t@i5H^LNQ;Rqr&8g69>Z>eryWh{Y+#V})nlC&g zg?ynBYj1VV1(G_moz$xGKW`3AXChH3Ok+qSI$<6rFzW+I=^*LUPk9WG z(a}?j3^T{rDn=Z{$<^89ut;QtUl(D-F=`COL4CidckiXXB|=i$yDW z=Gug*pDd?|IGoS89FJB5-=3{wXUZ1Jv}Maz8gj{HGp2xqRgv^wbP+J!n>);Hf_^)>>0$4l_0LSy?WN9l=_fqhD-xe<)}* zYT`!mqHH6PT&(d}XL|_)>wgfN1qxdB|eGA2{~!?S&%%88eQaR75cVAv^8!5u+Mp@rhz|$B)Pc z>yFn5MjQJ_e2oxt*aRSzqgYK8sC-px;pMOB52KaEbBT91&nfAq{Zf8z5Y5*Vl1QtV zG7y1R41ti)BJ6{xaOi?P0so(*$p;$3xt0T#nlMMSV^7ntk*c=+BY zjr-=O1hPt9K789lNLOY8K#-!?mBlE?B~aj`%k z4Aa3eVH@JCL5s&DjOB7&vfci0eh3^KoSfmou6{R}r?SQnbzCH=C1DjuowaChkauAk zZq1Kj0rQ25dkxYCAjR(J>A(R-lm)o$YMItq+V(C<% zjBjqUFTrJ}XqvCDsmj|aek7j4y`fBk(bowK{VaOJdysBrA}(#*aVlaU5?hd0CPg3= zjXpcZq5}KsmY}5<&p|Ac+E|8E?uK$$GJ%4BGJ}_uDcwxDw1i1)B^nnWnN%uoWNAp| zv#Z^H&UfL^l1t**;-P|nfJZ3GXMMNyj|!(7D2o7Ky1=18n%n=$Y1`NHGnA+2x#G*i zr7I|jGqEpWn1FSFj6Vi(`mW88)ezc3BOib%eEPtaDuWr#xDByXLEU>ZUp)+=*kNe0 zAM3e0CBhZoZyb}JVhKWBV(nA#$zR{oLyXu0`mrTBqNS? zvL8y{Np5~-2%%a9W3?A-0zA>Br)ImDjH&C#{GcJRCLl!GyXP-EEHw;xo4#H1=+xBD z4bpC+1U2Ld*eq`<>`MMS(bq%x#K^H~45_-vW{iHvG5ol$?*R}|ROS~{6P&pSoPaO57n@w+Np4T$=@bZfG-)Fuib(wRVUmzq$B(VT7j1QPA9QzLd zD)B-{La~SWqXCazJaHOsZiY>SWZi!|fa}|Mnrmt1<>@plKU<(96cC8?jlg>m3|h1| z0Y`rhQ$`|AAEjr+B38)hAO7rmgIKR{XUT_mzrKZs(@)RPiLmPy1QYy(DcUn)gj!fw z7ZL}wzQ3n>Bw+F8ra*hPz_!CW9}~0L1w0T_0~tO5Swbax5t^}URNC*~G}tnHwrpP} zq@ic%*oODUM2l8x-m7XNTA zUma4{OJQ5^$cdD9QAkBh+ucbn}f53{De(zw^XiIuth*8xBIJ9Tb-cY z5Zi%YZv;&w?y%_AC=Ni}F18ZVi!38%|9pqr@_qrDzvX&>T+5X3Uq=yyh>4}bykYFz z)@v=o=yW;+^z`)gExz%5HK&`srdANKfg`x(B>*hL=l5W8-uCxR9#?nQUj#6Y>?8so z=Cp|cre?`0_!BOUfgh~J=`_V~v2WFPg=y4dsLe8#rl)n9D$GLDmlVfpy{(ujhYb&} zA;|DQJtdA50kdWqgV(wHyBCEa5mkVUOQErxZkb+IZ?{nveHk27DxdDx<#RHQ^ke#!)j>{TnCg*tvfS&WmhBME#p= zEmL;czb)a&2zn?!>Ef8Cd|1V%AR$4|fnf8DUB*XqUkoX}0C~6$wi%|6#=uaEFJL;z zE`OH`XqUmt5I`Mk_jRAPhe2xe*`HxWYbo?uls*{!I_GnqcaJ6r264Vho>!CUnEWk)uGZ8w_uKsVQq`bTvId;s8THoO z<2-Vy6sf4UeBLLDmJ)#SS^j9^;X$GIO~>cu7$>VW9h*48!j^iNJAP0bh9bUJLFF{wRA^t4!#iKMNqIrs_3dIOtx() z*Nh1nInyU{?mQ++EL#Yz*V_VoOk?;|0NN zy^IpJNQlXP0f$OHhYgq0E3LtLgI_$BY}Y`VLiTJ`#P!{BSqdoFhyinDUMW#7m5a#} zKX_@ro~seQmaCO}x!l(^%kO97Ql825ewFs60WqI{NGh@A;ac z1Gs}!TWm(FE@n#w+_d`*1bUy=6D*f!S{$+CnlF(vQIfi;iy`Naktl!NFy-iJB>t6x|t+&3h>$eXZYiW>%!-c!!BEtyV!PV&mt$`cV#< zg8WvErpVQSnLwkF1)-95gL*KP-5Xb;LG`rPdR{DCYsBQWY7+mxP`;wnq6HZ; zDEaggwJJ}9&xcn!nJLeZb`2r=4?1J_Z z(Oi&`J?OGP6RJSRk{HH0AxPU^Gk81&J|6|nK@(~g+=vdapR6=S>RdQn=zhi% zXn*#S0jekX_m?bh7B5jKWd8m(l0XK6TO5voKV8y%Y3zlWu;f~uV?%U*S<=;lez$Re zVw&LbS19B4!Oj7101YQ#=W7G#r5YCu^;D03H%?XMKI@6sp}{~MWp*0Y_cKhPGQ(;SMi}eEUw)8RrG@TI z-;Y7hN%X_|NSkU!Jk+Ssa{PY|Y8lbf19iLp8QSdh7VB-KaX#OW161)~>%UOOD5fx( z9wat*me@r>Ee{U7v)F1cfXUKNrmxrptPN&E9_quU;#VUQ20-G_!dLkRK5~<@edqFe z5psRzhxK#%>74Cgc?Qh2*13+VkQT`q@i<9(kELLzF_x&7mb`6|CNWE*wM%@It`Pi0 zlGCwoe=dSLqdY0|d4mBp5Mx{#wOW>CH2KP@(~dxRvi|jFI_7HW{W&imu}FkI?Z9^q z!9vY46QrxlVlnm_<3bVX6valm%xTB7sr~l`x#Y^MOM*6!#}P!hr(%7`UJ!3db#8Zu zLKVwpd7vQhFjpbpCCrExm)$K4C~?n<$@Oa6&x2)(|2+_3{HxYPFjq7}ZUR)9w?bGa z`5RRbc+%&u>t9rSvThBno8i-Wa>>H6DG{1XPe?B3yFeitod5`{4xdw)@*{AUXMqYF z)9xN&4}T8oNzS;?d!*&MW4?Ja4*|??d8&?NjLkXDCyT!A&noic9JfWby`PReHhrcW zEX~dO@w`63L$EpoLGY93SH4?AeRk?UaA;z-d5_w9VcGh?iIkF5tYPUX!eil&6S$;_(zf%gXB-^&40(cvuLG$Wk{k{m02KZ znAXA5q*ShfU^blzMy`f+d8{!!{2(-QKhrH=d~de9#X_ajQiJA~^i8=MqaJ?zR(&zt zceabKT3I4x?!sCuo0$Qj{7jb#6%cNz)@)XP$AR0MI?I?7AlTj11Cw1+e3)3{WHQ-I{3-HdCGXF0 zEKuoh*d*I#aBKF>9igW6Yp>39^gqv$! zMlL{^-^I3YGA-M|LP22vi6=<(Bs>v_GO48j8VYw7ON$0p0!500arnQN`k(GeLj`a{ zO(4?9kx3;?;@brBsg&w$kg!;t;*{G?{>GW>LYu3P*CR`?D(MF>7&4bPb$~WC{en8-=a(P(FWDf5gsA_8(j!Lw11(>?Ap@SrhYr|vTVaUXbpN1> zS5_Z%6XgG$YJiUn2>sIzsrQQM(9O>DrKPHV0D92{a>4&WWrk;PqpC}Qfz0HAS-p0k zJdu~{5qqa?XtDkO;`K2SpMI`tDi9-)zi_(8ok6V?3by6&x^3xL=pU#CW=t7D)haU} zJMka)=HwH5DtZ9pGUL?cM8F!9{v>@3;i3OH1-7`ZT+zq_tjXQv1XVnj6?P%w6`NLA z;xUGFN=35zHgKJio|#pKm0ty4mLw_v6^mKRf>0B1=}RAcqwlqkCC?go$%f9gWIxtwrN8iA@E2d zZs1ieIB#F&DY#gL2o8Vz`CGoGhYzME`+1ZtpwXw;Q=^+&Wu-Ld_dGb_6U@&_N%FzJ zdnK^>N}_lFHTNDRH|>9W^Akt`4Nb+i{R<%wNg;$VgrP+!h5kh{M1nwD`EelrUu9P? zGLZ4ffyn*xH_MC&77Yw3D8Aj0RQ|Wo&>tJ^ClB)WdCkFiKG@)19x8K9qp15PD6o80wZluaPpAP+JX{*M z0Tqr(;hmn(@UOS`oi{g!wG5G+l0W3+5Vbq(0)eR4_(!M5<2Tv|OTu#9&S*Ry4>5Fl z?HpiD=gz~51((}RaD&y_-NfTY0iF2Y6?f5vUR1`&?zLX1VU|vPvrpnez~d0IgH3tx6P zUN{X#tDG@SDOmn>^--yV$Ll5Fe75RY`i-Re-=eDlnwC)mM1Wvf+U;VKKWVjwNm;ujcDc4Zw27{#VdU;?n`U8-N<)|O&c<*{Y*gIXRGaghKiD73< z=XTDp+Z_=ejv);fyA(+wm(5ftoY0O(b3VSKHCxQXM5R&l8HmKS%!~f7XfzlD^cjZO zDmF9}mYp+*;zy}UWp@4XZ$)5QozkoA;m;A)Oy2Qt>#1#0$@t`Qn=Jug*^)sB%AmhW zrM6_F)p|jl>Fm^qvN?s$OVeEQPGKCA={GFiOVO*_+x+_Dg;`=2Xs5%~Uvye+V$f<7 zmdkB{UKz}xOe|~R*u1_+^A*yEj6bqwjVI)iXYx_2sZ}dxV)921j_Qw<+SHKDR*R6D zTU#k>GeSeDfq`NiJRXlgZwHeDyWix|YEH_f%kpGVxdG*xFCos(qkDs|>SjS4W9e&w=93&*jX=D0iM zN$vBjw`+A7N=!n$>p#93?Dp(?lbKty3RGq(qH|l`=}v9?WC74;ALVnPZ+zomYt<= zzq3$kvf-CVVj#>R{pZWNG?8t91+qrr5E^~yt@lMbYaAl6uxMkl7}woj!*)$H5L4%k zN_XgWI`V*`>FCZ*d~;6zxQIZxkS~;T)gg8Mj~I~bmj$>k_v%D_;WFBP4(JrFLM+kxM0z@Gq*F90e7@9T@kY5|~w zNZN2Hgz^vs;r6;x!=2rkpD^c1OHWs8A~i;-?{REUHR1sb4gxu*FjK`QX|Jsg{QO42|LFY9HNZarj8i<#FL-R@=;+!QLlh-R zEZyz_GufoYZZGEx_DHEXAXVpn&SPgl-@;O^N!|TX7zeCUMOCg;Q4T*pS#M3ulCHN} z@@s2vkK=UA=_?-2$N1m>0{B&=wNx{r59;@84Yf#OSAwenB`5@qatt1+5&9fNxmf8} zU^Fl47Duh&_{jca+O|s{A%14aN#EwcGyS})Dvhp8@@t7oaar5B)A^F*1l`1lFc(ng zeiHE(_09NB#i$jKR`NfE1}mb^YIhP(TTyyb>~DfF56|<`OGE^OyKf&6-oHZh6zDGV zjh=1Ha-yBl#0S&Xf zs=*>DyAp=J3Pf4mQ{3-QZvBin!3;3ZMB-&1j-Q|fpcfAz1(8l6PgGCD?=XK|V24W< zc2R?(;t<|D$_@gim-L(6O|f^k>A%A{!v~)lQVvi>0-x|(Jdve5C7$exv{M@zh<#YC z*YiZV_OTPZyO#O>%cgdHg&^>$dN7gDf!t!L^z*#j76wY^6#RxWXlq)*!N=^>e3=;g~pcwpho0T1*4YWCLyK?Wkz zT<0yk@ieOcW`^B`zV1R&yMrdU{oU^B7!X)u_l{>%RsAp2^ubJjyC#6hNeAw&i6{-~ zf8Hn+h<4mi2-)fWS;Djo5Lwj`C&2tOnN4XRvTFYog@gXF)c{`;av-u=Mv{d5d)G*Q zak9k7;q>x=&G=ET3M>ZP#GdWHfdCrB6YBqC?=7RUjJmE-L6DRb5Tv^iq&uWRx>Hg> zx;q5vlbEDhQID{$q$5Jzx3GbNs`IjL7U znZ!ei!)4MhIQ{)nHC|%2sX8p;D_7j&Yx8g*O=VC`Ooo~DmBtnP>Uv}my6x8?&~iAb zb}j??hrKbIHzE5(U1<=D&r+EUsWGTky@pa6`2h<{Hq36F?_8Oy_(N0e%RQLxF7$2% z&k5%9i2dN@W-1In{p`BWx6PB8h~6qM8cJqKGB5I{G#MowFH}O9tu!Xs7>@Jrr^@T& zaM^o(%XF^N;ChZ3!@@TPQ3R zvpx|uF4fd_Hyv=MOW)CXR)n8Dg&$mTLNI|aAI7PjcBgqNY?!k2%*|alqrKOCv&KRq z<}NeLZu%>4$s>L17_?+3qOGSs94`f1jy^k&e4eyXDjX5|iUpKiYc zwj11>z&N2qvp(_e)(koZ)SvMf24)q2$pR&Q83=ZOBc+yWdM%yAB)-pPI_(t{LoMxk zc97K<mdwN{#^5^X78c3=NG1vxxsWlP$OZ&& zLG>JRg$4=KSNZ^}o3)(@N`OF;FOUx+6POIsA)$}gBH(lQsD%NDBBY}Xc$;(qt!;YV z(W@|C?J8p!yg!jgz*BecHI&L#W*lkxm;ngE>W1VC@akSPvq!V?MM+}oDZKI;tp?1PU!^^QJiNgGu;apFrFNCmI2Pw3-1X5%Wq zy_>kGb16p=;-t8$65={j6wmw5eiq>PQS9LzlwX5(UHHd%5NE* z&*|y3YBV?ebE}tY{Y+CFjg2X)p*V3d(@okG&*ci_Z*-z6jQd5zqevNdqMx2k9yncr z(cuO=q1x%3N zzdXY)b^V@&!R^u=hJ$Sy0t;zEk3i})!*L{J@9$u)-yP^T)(6ThH>&AX<_n1+@P3*| zWHH@7?rgd36U=JjEZLi>9E%ce`4wF-_G#hkOT|J1ans3c#{Jo5|L#ccapAp%N|+d0 zIiItWWr9gZ289OZiDE7P6AdZ%9EsTUiTfL2TVMnbdT$P9eYCL=qAvyMCduMTMRHQZ!)aXrpF9BT?bCye%4 ze_XtQe&~Du<+g}Xxc(TW-xGO*E4DLLBm`ip%jVeGpFiond>>>FB3?`SsA!FegaXD1 zX@@7kLv7S070WFuK1lRm{klU6((!O% zZG##u|B`2aLJ;g7+0jwkkcCRC#rkJF`j9pS_22)*Iyi2(3t1|}c-FzxHo)6Zd_}k0 zn5kmW`hi=ZhOq}HzVnzaxxqrcTykU-$C-+aVGN^gSza{5b!{#3AeWPn;ZtKlt_a zKazwm|BX>aY>=NWu-8vGkl9?2MZEYoh9#+oc>)rxD*#Av+@Og)e;R`w&p-cn^i}{s z;(EYkLp+TM+rS#SR`Wq!y^rXHR2rTIc^xaP8;AMg8^Mz^x0l`2uHk zfof!XG`DjF+wT~te=oN+o~ zWDPi-cxs4&Zh#z+z{>3^mrz=!&Qsq`n$MYlvj6GLrEOmd7gn{|LZW=Gl)wXWuWzAG zN2mBuc>CbmGRtgY0vrzVVG$yfij);tSqEA2RpRdEniAl#=*1peO>_C$R7-5B%;_E&1Y&t;W25`XKjwhsS;asiNLFw za=h6uXfRhsi3NrnSv!LHMOyLcGw{rRhqZyxT)j351dkhhoRPGFb zUw;Ke>VMk$V;SS+cD9GjVm1>53@QM%MVwbf zdyhcvm16r@Ko%e^{ag^TUq>e3&CF?G$Xn!TRQR4LOl7&~R`vN3o-99Ij>nMbSgrs~ zvB>D9=~7K7wZ+5^i`~`;>THc636Q}mbV5TxQGQELYNVFHVPx9@Lob!9gVxb(BJc z-@tYQ4o#LU4PG(FMH-|aP@TbVRC0Fjo%vXgCY01Q0!KHt*v-s2zRcypM50J@Ha5`KdcyYZ|2ot?reYGVrHoch-=Qg8kod^!6DijX9%K!ahoKg&{$T*jCE z3-9&mdlbQuawb<-Dgj@oPci_5Wdvg}P$^^sV$ra7wWNZ-F=%Z?pv`S<*s!tMYC(*620Q=7+i zr1u*|fyjQt`u>Knp@irA+8M^xy?R@K5Q$__e#NlwE#pAY9op1yMcOv`8WQo-96g1$5Ba_1Em{Xd+!R3r`^U@E8?nU>aaEb%2af>up_@@wH}59$)N0&bxYcQX2DuC%0RI9`_j z9^wd4E%0h9b6`W26MB(AFc(@ZG|2Kyrt7{a(r(6acB-SXY@kheSIKWC5IDU}7?JX_ zFZwb-K1U*F=mjJsL!)xBY#8W@YQwiR+iG0-xiv6Txu=XD_haQK3#~J_pz7^Xo5JCZ zA3`*PrS|bqbReh!z9(>344O*~0rl_p#9u;Zsa)NuHr3m^0?(-|G-Yzy>fd(5Z}d@h z!{mQLd7r~U(AE5pM)eXt(1nCh%}DmS_>R{O`;J8S1`6-WA8pcnn!jYuF1@+2MnUAP z>OXYR8ObvS6B(J{pDI1ep|?BXj4U5<;oGMs1&F$sWpN&>Iu}5`HIj&$j0H^iN|Uj4 zcmC(Qx|1P$2d%m-V{W$6@h+62VSAPzD5;)eKcPCD+lW!aCo&qpe=0&>fE$9|Qj1eQ z1br09U&4Tdu#1eb`B#x?htlhcM{Rl4NBnYc=M#B3a_P74y+s04OvNUI zOy2V{Rz8mer8HIwQxr+6rwH|j9J2H#2!TBGLl48Le&#b#IC6iutVZteH{@b|q8b(J zy%SY@zd3dnp=87U9nCtNu1h6wj2umwtL=@u-Z+-Rm|b<2~z&&>+}%CpS&1J5lg0N!0)f{5Q|=x&mrWGKdND!bYQ1X7G%JW zo^L?*IzQn${&6%=L96D@dDH`fiYN>~aElO$2LFnA`DB14GIf?P`F9JD9!R9;%)S3g zfpU`I_Of$7pU(M}%6qoLER+fE^94T~xASrI*}>d-S7poQt|XV^zKecCd@7+bY?e~d z6ws{Q>#M}_4JQhf%-PG@r*gV~{fc=1wN$&==zpYyH@CN2*K+!w09g3x_h)%UXlJ?r z0r*Fe&NF}C;4elIX`xr;Zy882R>_SY!FV@KtXXg42mIJt4{TXx{`%%Hfk2`6%KM=O z1GI*G+HduOw_6A6z?q0I+6zu!1TPjPN3hw-~!jeKzlYGhp5+wjq($@eZ}!L?V` zQtLq#vPK&cymnU?ayE{R2%V}j1Uvl9!td8{mlW+4qmObq*B_Y6NQwwfd+J0sW6Ww3 z%kQJ7+Y>?R?%XavZB2b4){#S`Bm(EoX5G-WOCoKcbOz@1XnHMm)5R(( z^Xa-s9M*gp=acme)TQvuC^E@%YS(y&gLz?VXMvV8^I6%>ppDQ>;7utCjJF6q`8=sr zYM6OkZb0W8Z1zJ@NoLGgHjTG={B&n2^tCuJ$jNmFz~o@Tl~TaxV*H1e2DMIUvD~61 z0@j#LTW)7x47EIUYT_+!ZJA72Y6jHrJq}wqvmpNI+smmXQ}{4-7GB_(rj9P7meB{$ zB${+Qb6~0F5719zmya~U%IWd z3ny~#Q?RmW=K!wtWK2<%TE4FQsY8PBH>wyY_muPRh{fmT30LNh7f7m*1$=#pjeCM~ z6Gj&V64j}cId|=vCIGoifF@Y+8`qz4Dh0Vsz%>rk9i7HQ!?zQ7-b-sWo@WDGDLMS;mdoB|uGXJGe!XNOXQ?JeV=^rjnXTfL0b2xJJ?#lXD(WNc; z6|qz*XXuaDZzF6BhCfWGqTJpNx14SpYkisvOWCsM8VoPds7Y6(lBdQX zfRumKmnHERDuPCbd5)^1miv6SoIb@WF;tf{DAcWPm%#^$QKDyW_J<$KSE-#aiC!5r z9+$rvb*ZsR20ZgTl!mddn0Gg~CF0$H`V)geEfEQiQ&5e`SQ*b5gSB%oo>ghXa-xDm zsql&s`a**o&ey@0W%aM~-*QBe+z@P&--yBG`5?Sg%M~Kx@wg;2{fTP+MlPL!ztAu` z_2ot3F%H{j{+zz(Ja+t#GH7qo{3J8q!+pA{qH0>VT>Z_z5H)n>aO^h?{dUPd1c9jQ zQ+z8<$N97U^nAl}#EYRt)_GkB#EW>zR+2D(b)=ii%FUfNj^8z>3+kA=ELlRLVe*Sj z&WHj~h&hrmnWUe;!o!auQg;Spyz_-)MxZr30`{!!SBI)bSVnX_>6N9b8QjoNU*GA| znTe7GVU2yO%#HGrwujroF4J}W=J!Im!p~~#xvz8}TIWwXt3fL zuVS|!`M2J()StwMnDcXU+TQG{N2IW1z_yil${>uXM(Gy9 zyNj3A>Vw$+&T8M9c&Wi$YI-{V#<=&p5Q1<&G&9TapZVyJ2oSScpbzh%$GG=!f(FCi zx)3CFMdp_vYald2YyH!j_c_*|5a(v%*+fr$rKeHeR~aSbJT8`wfl1LOB_Jmh^v6^7 z>UuV~^43huMYY<2j%WEhDb|74-)CiPbI%6h)MZO?IilD2b<6p^L@K`a^Bd!7!!|Y% zPShLsMhSDkUtBG!2pWRMr@jngT8OX53k&nx&%)oNaN5u2A1uWb!S>$+VewaZOv+Y< zmi;{2O*(x|u_~JhQy-N1*MVs8Sl=4!>2NURowE?U>4&<9_ zm~c4vBWFIKL!e?=_KZIP>6|#5#k^uJ+qwXUSa=u#(fl#w&BRgQTDN@V?~>j;2^K?_ z#$iD!jmhGb#=Od8VZ1*P1?y`iAe-%N^>IZ~!GPnRj{Vuocek>|HxAgGpQqn$;cg`_ z^Zee~K{KaV?H^OtEfzVK5B?;ai1{AK)QRHPSO0K{UO2AR7!5=Sgytxg=wTnWNx^Le zv;e|bt7%LQC>|8u-?m}UsQUs|S21R!0&-?RDbvY%13s1BT%F~G?a`=4LWiPQ7Qik70vodjjXb>Cp0n1 zrOwxzw0bc_Tb&rn)R2eb6tc$ir9+cR$3|bB9SDP0j(ltJ`IYB~mpc`KGca0qftzgpQe|zeN;E5uI`$RU zDc>&)LdSIWR>6%9eHt8JR4EB|Mr7n*E?YZA4?rT5k*=Q9^(54pt!AzbU&to8%>dx*8I@N0S(>c6r+E=Z>~oi)#< zd;Ky{w)J(MJ2k}Hv-gn?e>f9RbHsya3JJR8$vW{tW+~h*6Nv^QNq<6jmVa^lQewN&S@Lk02`pjHn*w%H{wh8uhpN-S7VG`SkNhbL% zX7!hCdQ=&m=Ma7@j{iyYZeeu~q!3ed^~UdO9r3;7UYWnX8-0^;z9@*AeNK!g>{6+V zC2(4c)d8=(havn*E-1(^msmbq{5xT@&=wE0AR3z3b&KOFev(z&`;R|U4sY(QT^lR8 z-VJO1UMSlO%!aY?-2!fqrtb#k}nK*M~*i_VG|Su+3q2@i|Cicy`aRrpy;_F zUc-DKr(S3Fal;@fQiy{`EaG#cfIuUJK(mT{_Jw)q*vO2O9}@goz{-Zq^RLi9d_)Lg z3@00syfHWlegpi?qolvC_iopE+Hwdj{WV8_Qc2`9xQJ(PbwvD(HqZSUpSDaU1EW|r z4U(r_2zlwPzkqjq+LB!ojA$)uL>_iWy8QK7XV1%52gLt5n9d%x#m}J+yAx}D2@w?e zhH_r`X-ir#s%)CUdDvZq*Bi)JBy6&E!B1PNfswIuCCk%cvq4dcSfpEFJ#ASDM*nY~ zpjS(hJC?bMh{)HDjt(&!n~H#NnSl^_Lb0}%1M)6; zLR#8k?pljEa*%6ddSc}5ZOnnHZD&wSRBWsf(&fdKSWKT%w&kHVu$1!Kn_!sT#L~v4 z>y&&I_fg1Svz+xu0;@E+8>iflG!AJ&At8ooduO7Dz0^fN9N2A=_jPi22^&D?38iIk zF*xduN5_~9c2smE{fD#+C(?@Ur7N8|vCPdED_CSUZ?rZ#I^M6%W*eS5W;-Q=JNgYS z+04jk`IDQwdyaik?C~hVaJ>sbnX+oxkLT+t=UqMP!{l{m`XppzovNxFaJXSGP*4UO z6l?@PZe2{nCYFIiqrK-8OcC!;+lA`mGm-@7;xT&<+`t#A*>MA1+q2$!=Z;V8=SLWn*l5H&Qt*}L-e!e0=)L$1k##OBjo32wE@ShJ_-w8dupNkT$ zG_+ZS7-7z)fv0<8I-_G_(jkT*PqXPCWW)K`woX)9<=%~?_QNhRnpSOeVDpFjwc+SG z)o771asu~;t%+<)s@vP&c!QkhjJ?R^dXptOWy+Ch@InoNqL-H`6P9(uK3~2_xoe_8 z8r7eJe|E~yrJXN&C?pms`5xa*TiYxz3tV3H59f$Q5*z6ltkWlCn(Itd2fMMFR8*PH z$S=C@zO+SzdwqRQAWLIfdpY3w`21)*JU==4N{Y2ZhQ~Mg#-vK`cFqnKC@h;#N-wT1 zB)lP^2j&F=&}7K^+>v<^(^BCB%5XXD#M%+QCPf3AY1W?cc0+ zIH*)H67Mv?vOOLY!oxwe;ZSQ8Mpu3MH7_NXG+B}jG(R@`>2KB(y`N_Tjb4{&fpT?X z)6FVE>1NE$_$n2Oa%+cyt`->?g<*nThxt19@NZ(cm5?9vlX7h*Q^o~*;V^Bcg-VsV zO*f+m)Vu{z8TSYyP=1im?e{xE5oZDfur)Dbo?|c?N)}NMGi)~+`>yTrG?9S=u8#r3 ze(5I~z7_jkycsGfRP=2Cj*rZ?@lF%aE7q&>-6)p$yMEiLZP&5#%U# zj23mgw`Gwu_FQk@@<&uz<#d@wpm|J{b4gbSPFXBK$4=DjbFHU$g}|FCYl}3fzcrmW z-Cxkq_B^4RsWz@n=Ei0SIEUJ`m7e@rvju}cAS~b+A@*_jcz5R3G=ak`_H=Jyn3Q=L zi!!!M)1;K^c$;El(e)r!I5W}bYgd6Ovwe13Dj((!)TRVh(dl8B`wLPed(@om^|0C*X7 zqOdox4xO6bJ5hc?=Eagj(KhncM75bQx~+tmRK#N9_(--j30OnLZ{A~D;<11u2~lXR;bE?O|_d z<;#tFwaOSXHJG(<^;EL0K9a~^z!Ab~9BMbOe~?D^$57H48{h9m|LyZ|6TG0f4nnl=;fkX&i%M0xW`2qqATMg^VLzaDs;Q+j5|IqD(kNx3(l6! z@cF>f@9^+&FY^fyDihek!NIYxoeXt{?4jOrFV{3S9$U?}6b3>0?UmMe+6$ygJq7(( zuPnwaU?I^fAe}d~i|j#sw`!ca4d=iW5L{hp@{RnsBVc8ADI$0Rs!a0n8|hHE_G4`g z92wuyXnNA-u=#VHE~FbXzi&*QH5xe(rufXfQ?Izf~AzfL4&bH0_U?I~U-*UeqTEFsjH%Hg0 zZjN(a{NZ?0`3ZFf5CbH&wLM{Metr!-?i?>8Ki`5Cd*O)lue|IKgfb*mHx317^yKVUjt6zFl zU;=Jf3`;Vz`{d}S2&Ubd&kNL^rP?GW)R>%Kfpsq|!!m#evr5zheL z&UU-BIX-Fkt1#ME4-Dn;2y;b@9l3!i5hF=P@H&X-`kSnM%hpTn-1tL>6=6YO~2`omjd!bB)@BHERl)zAEf@O3b4< zN=2CRYS5p(&#V`&Ud|fj&b^Es&!y8?U+;AqI}8Xgr4Eub0BeV+p{P3tdKOV>Gu(4yz2MiE& zboi(g8mdnBel64u5n8plos^81YW1rUuu~CdHi%cWvT7RojYl;Unqhsb@^+uAv5afJ zKI=7%n~3d6i4-9PDxPhL3NMX=y!sQ(Nt|LTQa!uI!XoRVm8BS-O8AQ_I*%YVfQ$ksuvCRTs z%h27=J5x)2`?U+iAX$@^xP-@`QAMTngSd7PfCyVZt zW|qMbnwp+8Ms@ScH*5(<`8WwKC?u*fR8$3ta&;3Mqdx_7c)m)#4~i7%e1O38a**0Y zBA6Q7;a{dvwPUE2BaYB{(K`$w#~Zvysw}6J;a^ee7QawcL_z9shCJnIq(NXLuiK)< zdOcen1y{bmxoH`eBF>@Hm3;d-*}f*d>|jnxw66D6Q%(W2F@qIZ=2Gm|%IhkG?PHXj z--Ah#+wx?CuZff)uF|bO3{%aN^_xEg;2s%Be1dnJIhcL&(vSgxfxVhu_nFRrMm|UlHho7HhxLQUU|Nk%H(63`1s)VM{>TKe&tZ@oe(M2Y^v)HhepsW!EbAXy zbhI~GO({MO3n z3B9byh=t0429-&F(vdoVu}e6B6wY8r%W&cfsj|q)=_EC#ndreL(r|W0+ zMSW%>3vtLUGSzx{F>#?vWue4+saDs7if|_~v>+q`qUPHgAQ#(aKig$7jaNl0#_cE> zRkU*4t{2PAS9;gpQESCJyIAcS4uGztmUTiPW$Z@B%KfAPcP>fb=oULly6P08O%T6s~#nF~xn8UV- ziN4BewSS9#u5B@=3&^*JaD77p!nNP_2|r5op7(9b8(vx}ST$|B!j?w*a1gHmCe*}% z!o?s##J-1vYkFiU9>^@><)dM2wdX5bPps7D18O{&|GxQ%8d)B|=DQeN*hjEQ55VT; z5T5IYN3i+8p0OzOdOw2A|NR=cUfTbEyd-}d8+PHW^T-%^g*zzX=!^p{F0aLoMnBeb zdrv$-Dv(=+dS&TVxm*%CZ}x7W*`7vUT>SnJ%EZJ3FsDlCkJ4O3kK(|K%U|&)RSekI zkLix5XsaCKmBY=887yZ&D?KMAk(N%?d?luABT+bzT&Grwt13h-{A$Q%^6nOfl!#H_ zCEaZrd~#V?S$0lN;MT^wQ_qL>TG6g-DvFoN1%6z6)Nni9H=|)X9MLv_I1mF&7bO9{ zTtH&5nW9f%zO)?+Z&we0igHSjVTst{JE>n{zN}_ZpExpPQL0a=2$QprX?>t5>M@`p z5#^x(&QmcS&&y&g7F@+aH%^Z6p~tA>GD|~CxZB?ewwa;23xe9uGt>m`$AP3|WE+Ky z0h%u!L`qP}W>eB8I;~Y{QlfwZpl>uS{X3=$jnH zf++dS_qk>VBN;*!hpi9~kdpSoIpdXVty=hj%9{*zkjZ~utPn0#EG0EpuZ^83ni2zQ ziX4MpwBxx$Pccx$BNf*21?zbcXJllIX1H_NninzrVq~oNJOdy2>%8A|3OKCeNb>W0 z*|Yawhv9KS??b{KV0o)krMsYiRc887AtA zywHtGfTar@owW)y@=A-X|w zL-Gi_a?k6TI+IGN6sF#QUy@lmkp>vg1E8QR!Ga@?EUn|aA2CdZeXt!HfV2J9?guYk zZ?mz!@3{1>s8NfoEX3moH^z@15i# zz&wMa)tn0PYcF;zU;951TuQC6l4H5j27Q}4Vr_q{W^(uGwcifI%A~7{fCVN=TW-%9 zQG0u|8aF1UmLe=$uH=D5SkQ_w6^g26rBNs>9E~jhqT4Q>^GUTzjz%p_<@g%RLlCh_ z^jg!4PE94+DD>?&M`npq`&QF)wG<|%Tb_npOZGmzWlS5VhMLCzytCgdoId8;_g->~ zQ<>#f_30MlaKuKSW%XSDs4q?{*RMNpt#1eld!x5I7UVUyhtHAw+^Un??b}4#u7PpP zhJV6Xj>oM_1u%{?87uuVkkZ0$(R@jsaCv)9E~j@8&KG|LwB9iQ3n!U-&*XJ>cTcGj zhV!xtUY#CLSvKDleoxNz_**^C=@f9fGb02_zNAZ1x0>oT<`m()#}TKK?v&`fDU_gz zEQX*Zjq302#JkC<*4-E4rly5ZFK#yy#~fBySJ#7k)5L$icu9J8VZF__td4y-f5K$A znn!=0To#`8K)*TO9%|PgeHCERyCBI8rP6Vu%F=OHnyoh5t*9D+c;c9m(5;xgA@n6B z2O~;vNwT%V3z=3+9BkCqqHKQKC5p>>37=lf8T)N@x?=UlR?A%_|Kd9&FV2oK?VWcy z859W^fl-CE-_bOp#dZ_XH2h;;D;^K8k{c3`otq<36iXL^lJSA8x)^8`7=SHRhiHLp zX%nHqa*T>j+}~Zc*GHzgm?6ySA}1-3kO=`z{wF<_NtVpatQ8zCPb2v23~u3Vg2R_)~hc#+@P|1Kav+wFnN; z3Su?n4`c!+R+tbeJO<+MDCXH5I(}wmW@w#mixNB*NZ$7>v9-x(KyrP%mm{`992Fmt zKdD_IS!JbVaF(+>9rgDX82coY_Ku)9uYO;lHyiQ5vv|NtUf-Z_9mj~Ix$Zhl);wI_ zmqtu#(e!>htck0e9utWUL0Pc<=-UGc=lwkxC_bv!uL+DKeR8>ld}V7a=Zr8v^(U4O zRky6wlNAxlt;8l-GBecmrT%(#Vw~nG;PQ8?)Vf>>Lb%UiLn5X_rn^b040#uxjFnXx z370$j`rRS)@=(8Qq@*NIQbU+;0Lu<}RotNI!t4I6vbS%pf>dLhi?wP$NW{<=K;l|- zz;usDSAOBc2rbvh}`kd~z-^%Ce36Q*(^GDz4^dx$Z zg4?0E*RH=`DRelUkz&-F1H1Yj-0sM<-$UQ9v^_dueGDCRA-emu<&|iQ*8{#2c#txf zM*XbWpWC=NLhm}J%uxG`0-Nmbv8<&2?wHelk1!mOfCvGLj+MR{2H!<7Vj?#;qMq9~ zD}r2HDv?R}PW5kIN5RHuo{Z&8x%IaYdG9aFZbus`OlMi)Aw|PXixES2{S5u<9MWCW zKlJ_G{{CfqES{7!KvbTQ2m@VwAsl+jMopWLVX4vKen3d{10m$NWSd4VLptl<`#8^A zCG-CXKov288`U6*#dg%xyTNu=sI4vLISbda4Y<_(^|GAD6(TWUxIkP;TNYJyIWQ-0{YlS#?7KDaEt{flbZ>lo8pb?Gsg4fgksGK&%Dga z;uddTeA3*CZ#tUo@0$3kqm{1?y&sZF{_Ghuq4;|NMe{3cIj7ywDQ2xk?|J9*`}M0N zi&%J!e#w>}?owU2rG75M;VxYPLoeADh|MO8Vb#S?~P*?0*IQQM9x{zG?D{BP5ch#{! zJ|2B@_1(2TA2VaY2~Uv$eaB;Fu}qBq=NG#VuRL{P35J=bvVd~#r0}PU3^SwtuY6%! zKAx;dzKEP1VL@?7G15h0MjS?mg6gJVE!PPR;vLM&!=Z|oG_bM38{|I)9IuPN>9!H( zei`7P?YTeqbJFONC* zi=DL@Un+~whR>IXlB?t9cFz7a~h{r{xpo4cW;oobVKpO7&w`EtIY z-PmpDsS1kx`trfI`a2o1d=3MTtkT00d*)v&pIxecCQsu&rdF-Oq|2WMdT?MokSH01 z-=z&(n`~^jO_Dt-4C+o{*EI%u1ihDX^obt-hY%td)EG#L2 z;`LR@+|KeKCp(^BSYFlzgtBhx@EY9Hvl7P(A=*jF$S4O12}%0)?OPcGLTz?7W@bfD z@yyB1m5`DOAtfc15EBb{MXf2;ewrMtba!K04IqW&t;=6}Wpw8s2SX{5Hm6&1CKH7@ z7Z(@nmp7g^?Vxsvcpgias8hviO@jXB4GkzSd;=;{wfbWfD*3AX@j|t=G`ob&2kJrZ zfqDoCDzjYtsW_SG!W(RQe|O7tzbV|H+bL!xTMqKRgqlP)OX|=!ENFauEeK&A07HwM z9!rME%gY-8b13-|7Z$45_!eD`K+rH#x;j!}7#vKFI3s}1?Ia6I;|wTS!AhW~fMU#e zAZoX)S%g}tkPTBYjC%0F9lcsjPc|hps@VNmNE2BcP(2a(;M!u&Uf``F$jH< zN=?SdN6SA7X(hiaA+9@G?&9=V^@Uflrcx;N>8|;$~`XlhT93<=%s}xlb;FL)*x6vt9 z4Wqo;3JA`bO=n-Zc%5c?t(W`*Jt1UoYY9l!XizrQfgpc!|y*34Xo=}rhY?4oLj(ZuRA+S% zxn)Q1&V-pktCvTv#~FfNMk=fOSKp%kmu2=Y+Ac0%yTpT%NkqzD5DBVYobS!hi28(! z0pkq&U&=(I!tTF|4;9}FL9JHWZ8PM`U?`3daxb~xf0=jKrIVM^r>8`TkD>AWmLc@w zjaNDV`_n3>pf33MxuXjUqhsE;|P3Z3ve!GR(Sjr z(TBhox%7H1;I@+6sCE4k|A9DO6mDJXgmvskA*I#bq|q1;^2)JVdpQND@SIZ%9$~E7 z1Q)E-gNo$x{C4Vwm8)I*Hd;baJMR?kOIs2Nr6MJ3TW11srO3vs!lGMBX)?M(MC^s`n(nFH~cDL1*XnxO7bsfpaqV%Y{+J=rHgpw)3ZKJg;* z_6FFDN|dEEj~B}cb!Nv)B_7vM2C#%vVK|bG|YZg-Xt{rjr4(jHu$W zZQCAymEEjc_dQomwhsWLOKu` zSZTexm5S;IwGxBX>|rgRKwiT`wzKaKk0uO6__^~%{#=*nJ&XXSj)2c|O)V7S=+@R} z1uBQfv?jl?8&pLU%Pc^GrOVMOFEP^S>`2B!r$nt>43e(y73$B^>D~<)nG+41S<2JA zm34=U)f&qZH@D|}P?)okB%+m7!M5mVXr%N9+03TnBe1Av87%=Z>!+HrF(V(F*Kh-5 z3Py868&T4T=us-O6~r&{g=5w%gUSjK*j3fS1I7rER7y;939+bE#RQor@G+6$p_-)< zMyWkee^-{3g*S-45sOk6E5{^q*b?OlT@VP6B}_$`C#{}e?h2MKLfDHi50n?_mTh)X z!gEc0(k3+}d39@uv@X;pevY{VcSN zyLw98YRu2OxXH*LeYaZuzA_uoc9>kHGnD#Bz3qr5Bvg(i9P)D#uzQQ|DnLu)|9gL; z;xo(^u_eQ6m9r>&nHm{t{4;CAPdD&_uF5@kJ6r>AU%C6c4)Wp$@JzSc(4{jn!6uV2 zCs3G_*%5TTP&T3FL}7OoP&SAfdIO3GUguAh5$%PBxZYU0IfMJV=W0fX!Hs0{Ipfj} zwA9qWG1iJ}uQ=53)aavs!VpMn^HbN~;`oQ*vRc3DFoVaUGI*aSz9#ZeOPwl;ZK&8I z4(Jz3*JZ&SXOB@LG~FU8=5y>+zj?=88a=nVc?h%rb~3;oDRt>c&g^#@rVhs3C-)!y z^tf_~vJmvb@K{6b6s~Vj47vHfu7^EOB(}il=nKm)qUWO6Wq%OGHh~4Vm4<$H!A>cI z$;1E@=HhihS3H;0XltB8ODuuWFr(w97zh!k^Uf#Iw4YMCWIYsWu@9z8a&Uw57B-*} z0R(m;d;x=mk4ehg`nLvuO3_iXxqmlaxT+=yNl!tH%U>2B$i&tt5tw^dRCduy9aUf2 z(-)CQ#?r<}C$S-ildh#=GebC8+$aM;RD|mx=_%>Bm7I`v9sp97)Hutgkb02>Wj({_It8NETc2h)SBwD5d)I%~Z>jj7?MoTWdJDk}zEc)!qEs%<=U8gs zEv7e^$!KMnk$yJ~OrA)`@`j^V6Z;km4G~C)V{`l%=RE6)73Zk5o9cEGzXpf>T=hAB z@pm^#s)4}cMs74>wglTsH@uRgz>6I!%Y-dCqkWx`GOnsjk>}TKKRGYg$bKB*7 zNVWdZLWTvQVoLS<^dOMaXt(J)Xy<>Ye=IX00kjI+LhZ%RHtK#NuRFLr?`dlK!VFrvOUwVL#WW zJ>rsr5ejS4Kef0FsEnl|oIj`)pTV(!VVeO3`*BryFs=&8iV z1L|w8^z%=Gv1LLq`cgjg>glju!6}_<*8M96{_hF?KfHr~99yne_d>Zm)N@0Z2(L-j zI;N&5R6-5JzCyjY?4OvLigp>4*xbQYe!6v!Pvx(5N9BIDHP!%6-9^NT(<;Q7Ki#O# zj_NX0%G9Si=)87Y<;mh4Bmfg%O-(hRUTq?&@QE7 zqx=Cv=kN!ET$OgCti%))iDUU14ay%xuJV{=eGGsj%P*$ZCR}qvGdl#mTi9CfycG^CZvm5CYkUq!CHGbTQX}i*47v7>qU9)wDMQLYnx4_^`J!d zV1wv~<9Sr_Sc|UyY`r`DJVsfTk59d_zLDQxf49?ox`tP~X^--wr zAv7gB;KJq{G#}WTC4xcjJ`xn%&M`eP%YvvkkTH1I{nI}+4aomkAvfmQCVp@kEmUm4 zJaAkK%@ozI_-zJkR(Bq0z%F8Kw(8?Q$U@skNy)>zy2_xUQrT_zTnVGVEi!k$ab19$ z*KW*{y8k|*0oiPM*hG?Env849eSRaoJ4YD)0KZd_iogJD-&*Q)Ey!a;hAzM{l8k6C zwc|5sbZblQs|;b?uz6p|NXOSO4WD*5ViFQ*bR%HW-uuPzZ~U6^Cgm=brc*y1Lzln` zV@V%5_}bt9{>k0IWPHpEb&-%tii>VUL7Y;}pja?s$2<85GyW2Ybvb|!o}%CNC(7&Y zZdUb~ULtyy_>+FB~t`YWS`lk{M~C&6^SgPk3Nv?*9AuCH%iT?OhCy-`d}K7_2K zr<5!eGrJUh7-z*K1dZPiqif!723>pM6ZvMLN-mL!3&!!KR1NYn?Cc7Nt+}^%{;^%0 zt2=GEg9UW+0V&(cF4TKwI9(#y-VDkc7#R5B;>dm$3CU4-va-7RNK9-(!1e@_BB-kZ zEE&TX-5=So#Fr#OFxF zecD0bRf$A~O0jjM=`ua-H`x`MCNl}DCw{zH@Y>uTNC&oGd@tRBIjiScYj*bp4gYMaB^j351qO^5u=DEKTij;&biBmZ$(}VdH4E`E9_e1|y zX=o?`FT&)B{tihG7~rlNFeBf>g5MTKG?L3_Jyi>%U?{^;N+4HPR=5n&NWFg8y2nWs zkmfHtkd2=cQ&LJUk+ZGQ(^I_%PYs!w%&UzT0_XARt1TwQIotSBq#AGu0f)OCPShy# z8&)0Yd*FYTdOe;xDX%9RuwJ9E;J*6FL&p2PYm41-Hm-;4*n+UT^dlmvQ5`?I3MqXS z7+kf`9KL(IrT0A(ViGuVUqDu_u;JbYs>_yCPmz3PF{NlX%?M2g_4F%?bCRpJ?QZF* z-IGRSf>1}PPl`Ac|Lcea<~EXK|S?N3TkhZ-M^fi(bEk;+i@X1`R8iK@r@<1(5_ zfYzihx05ZkYPa=2?Ems?{PhQ&2Vbr>qe=^>CNUK)PFIzpC~7I7o-zLcYtHb!0s)p8 z9IR+@q2nW>F?mQ;^V8X=Uqh(HoK6HI+jPCx>E!z-KlPCs*c13UCxt#BN~P?L6XqoN z=5IUUaRVZb-w{Acnn5QYNdx|-#Fwm09NK)Kdj6L0Ui`jSv)Z8OkwuCNSi%?f&X=+r z0O$Dx)K0OD>W?0k#~5wB!!;OXvr4JM;IOxsIuq4fJe2d$iUu-)Qo9_ecFXvcrEp7= z3rn$SbGp*boZxON_9ykHZ4{&rxXN1^JdXjs(cSfbKhS)0b|9vJUJ?frwSC{7Z-C3< z{;sYY?emF@w(h|$uX_1@1X*hP|NV0}9}a3+KJPK!?VE2n9LhW|TE^`nWb$2e7F`|~ zlik9FR~qM=0F&kQ4uh|ado7m(jt9dv|JGG<-wiWLkERX+TQzOwXHJJrDvVndSsAXr zEYfG{eWOSLJUat^&jM>n25Rx|ei;lr+UF6mA3ztd?6Klj4#&#u@0Ip@EOM})gfNjr ze4ZTrH*QDoXy0+>4k~5BC6Tc)oDabddQ0SoGhbbJHldiJ`*m9qAiYf$P~AX2vHX0$ zS_eZVrdKL**rppU6hlxX`8A>GxX1AZ^E5=dH2+ybEYKcwr|QNkyvEu(r0g~~@6K}v zM=Q3u3wPvjxy+$V3=OAn-Uc+Qx<~Qcx(PABlnt@8+$eGsX}i97F-?f&;uRL}{d||S z{a}rMm@RH?Y_Lx?6{r=ZBax{pt$b$w$F)u8bNo8B4zy_{RoTQjf!z3ak0fQ?>u~6F z@er9+j!&+VSUN#d&e2%E9-`ods1C(T?bUcmZi%Uh?yU9t_})R>>EgrPmg6u$jI1BF z5D+isFr(_yaIT`;dr_!-XnU~tiL&oSJjFEfNG9*yaJmCwl32>518q1kJ!RVOTsU!t zkR?`G_4eyFns+uf{F_4Mjo5hUy~!4(>jxiD2^46z`QLoFuge4=HG3SQIXSDp<0=n>rMO7 zqWjI?6rr>K`7NGeLqJ~^5V3q|x?Yq~d6Ervuek}`{Ba{jFE<|0%|Cn@*GFDA&*yAZ z3q!tD1)2nFi&!?1=tXd6*g#>9X40(3dS4+MG;dmf9WX~G4`)a)319r_l@hsv6CYF= z=qci=X&Q!C{tR_o5x`-fUg2ob}9g%_N!b-ty$zxVRtw$k%B7JBJ^Bd>z z(C_S4>8*GD^^`pu``>kRz>nPy&A$b11Y@h`Z|4R^8L?K{s>J#mJuraUkL6X}03T&z zNXH23oxp&%dOP&~87J~@0Jxk7JfV{&Y?T6Zc6tSpPOo-B44&Wu%nyssI|qZ+YYu7F zfnal~E$!dngG2w~cjbKPN%|_SkHJWQuv}>#v}S$F=Xn(xlqyKb~%Zgsb;UoTsB$QrB#kuaB`fh_yIt*K3Mamp*L?xj&PZ^x8ba>*sW8LhXDqQV^7 z=@RywLZxeXkO}=I|J=h5@c9ymo>hgAhotK9} z53Gob|4c&XnHA#PCGZ9q#21koz+`h>Qf?82JO_4Eg;IkAoZR5Eq$Ytt zgzhQm`Sea5+0KEs+cN`CN=i14f9dQY-Eu1B6~MzSO~UFJ)TG@RR)9Eu-F%Tc$hsC; z?S+!C5>NSkmiCr&A~sx!s>`U%QZR*zY0qb+*e+%*^jyimT(5Y{c8lsxmsX?6B$zKr zNC?neB%xfJyN?)1pY2?Jbm*~e_+XRojG1aopctV!tepFFwtN}i8)$Cg_WrIUSEZb` zmIJd}p%GKvl3(e$6V9W-C_!WLgW_!Dk!Q1Tr3*=A1+X9^EynA8WfKL9TpkY0dpds_ z^iN)i!o5Y zgb#LTol(|OapxpwB<>H*6f<{X7X3AG7mnw?zd1$GXL0NCS^vJ>M3I@aLYU3p+|ri~dKMg|A$4vsMP^uz zm;i`4#?*}phvuQPlTr^$#(Gdsm`{d{`%JGn4G#&ePMuv%K%H;TV0wHME&oQWdQ4Rk z`|@MAZ-&KZ^)5a>io-F;MJ}b~bv!iZNb-2*DkGJ^NBfDQZ*41phVaL1>@Jf_=++XF zpmvia^g(4#iVbQdI`I4G30_sNCdHTg6HH7)LN0UZVlH{xc?`EnC`cqE@0v%4Gz#3n zIn~lQK3H0OYFj=9ZpJPXv{=TAI&PNHI1 z_?6t5Tc)9jTd@uQyP{qHDnp_LFe9|yCxxi$Q?z!a!Ag7};B#+i@yEOq1SU?MA}`_m z9bWEyMVud1kdIdQsO}d-LB9}i!$nY>eX7XAYNAo)sOb)Qh^&wio^;cw$y2eN8_5a?4s>^J%$qq&IiI`uAtjK5Czh)TL@&2tw^s-N- z>Q+TlD=~qP@>d3rQj4i_+kmXEs+L6tl0I6QNoSu)!U@x@>dJjPNGAejQ=6%_DJ>@1 z&pbF!4I`Wr)c;u)iLaR{Hp~1nhdSD*((M~{+pCwBTIFu?#7|$exexX+raV%0m9H|% z_N(|rKycn4^ICH7fz^aK`a0Edq{KfPlGi17J@)=M{Wz>Z#;W~!dxs@> zaO=!5Cw}o?Bfr~iFQ+Bb9q5Jyoa@P-Rrj9IzCdz;BGqRqiYBX()baiBblRtZ9d?ap zgc&ocIi}5du_uxr6>~<|1!vxAWdzs_vDQZ~Ga|kAsNhP$%5=hCKVC!XKo{gFVlpUVvfuxH&^s&vssR7+>x3&Qo0qzh89B(_hq;l)N4yjP)Y2{Vs*A9t~y5nDxY7D=pt4 z)5cf<#R^7DBaV5RYcABr!tlx27J?n|Vc~1W-zbVK%^t(d#^Na2@cdM%LV6*cQ?_5> z#rkDuZ_#oJ>5`6oZfcxUAqQQnoojL5I`dwj`H%HQOX#Vea}Fh`<@_W6)xe9Xh|ZjL#RQ%wEOkz8{ZdjZQzhx!2n4U$sc}*S1;T)v#i=xTi$xx zLmS`-DemoQv$b-^N4XC!Bj^?9qTjMM+WtH@%n4GE*S}W?*UiTSG3w;H2s2y{uwA@N zwU}o`kU*XD@(TvP7&bq8jxi*Je%)JGjUS!&wR}4XLtq`JOKdFJ&te2B9sz;&sB?`Y z>K#7HK0T4H0u?|yT5{PBD}^w%hU{iE^S-28~^lgP%~=7``k^9 zC@)CoGEWwFoGM91JJR5n1)M+xf=C0e3|F+eh-vr}(jaGh)IXjWBUtCxIM-&gK`{aE zRA_gKsK3;m4m(%@Z9Z-=iuE)YOX?9#ILZgzT#yu?tcf)D1K5JH7dsSZkuvOT^UVoEOs&-n_PMucXsFm$=w%f@qeqw0qKW0^aZ1_q{Mv3|C$tT^{u%9U~Rs@)F|688lX`Y2F&vtD0Z>mO_fR3c&_T{Y(JieDGpt2S32NCcYJ ztG41tjy>;wA|}=)Fj6@raOEb$X`ffwc)T|>Y;(*wfO2g?L_T;nWK-$$aHIx$eOok; z2oy-d`>}ln&<%9U!b5i-Gh3)CDa259SO~e*3YydyNcEDsM2#J{TM$vKKT-!rs|vZI z3St|iIy#%F{g{iI}BMo3{)m`H3mA&8N(wIaI=JyDe?~oU-)(iKs zLTgUrQCx1{h`ToD!e6??WT4c9{oIzFX6rX2X1a+!y$E9&I2ZUXRxY}ZEVH!MT9M~+ zyG`6y0%+rdsei*Rg2YGyFNjC=1Wliq3HE3HI0urIz@{~JjkPh7n+j}!w|(Zmg>i&6`tK_|$#x8}38^2;g3Md2#6M_!W*SeR z$_@v5025vcp-x~Xo3Z*yCU_|x%kzbw3w#>q19rMO9$q5+i3+U5>Y$ zsPId3gJHQhbUWbtpd$ESpJNhH;6whu$ezaS|I+sV8z&0Dzrh(4JFVPb*hZca7wg+&0tFThpeAod%xP5F)@BSgua+8 zh=*)83fP*$oP=?ySCiW6+vY8Md4mIj@bWcMQ?NV8*Xu)Rt8)-u*S|>mW*|sESbh<& zo;;BJ_v+&{4-R{rh`USCH(a;K0ifg;#@-AJ3{Yd9I$AkadBtqjbCbA9kMVaZDvdXc zmrv|GtAcS$OUuD-MG}h65O@T%Wf0Z4E}8bkGVuWaV=5c;av#Q)4h=ln132n~*}VP_ z6;Z%DeF?KU;9R#TP&!?fY}p;%aAL9Am-rBtp3c2CmHr%vN*VvtP_0S$r?{=U!R43c7@)Yj$qi35GMoW2 zwJ=8uW(K`pAcD2Fw#N9a+c$i7#&~R6_ExmwK)Klgim=x_r3nSZR|qJV7)p1DlYV%Sqf#TfG=m!=g@gA6$FUt6X$9j z?B%odwD~rFvfVWC_m;SBDPHV#Qm!G8J0goI{1r_HFoMJimquW8qA|*y+4)9H*TVAdCKBxv54#A!6MH ze@%?I2h18EWX*#HB36qZjTe0}Uc9;mHKy25*`qcb-+jTGY?RMasK2~!-rB4EIPRzU zu%J=3Qa1hmml&*h%mjeuHJrBGwT{pDGI_!xV?6^tRRy-J-~91@($P2=p}bHM+Kky5 zL9F2lv~+t)&drBp4o)&E)T%7Ixs0&?a=%B@Fw=1U=O|*5)dLQLoYQ8I*i3cVM-N${ z>PEFr76|MdzmQsc$RC=*fd63J;4K29Iu}8!o|r_A7h!$CPyGj53zr#~|vWpgyAG*>8 z+BLCN5vatg;nZdn1R|^OBVw=De z4&KHna2bF-B^RFdHJLXkIXE1J66f@TuaWw6f?pix_0`hXPG-CM8!PRisH+#%Q$deZ zp+5y4%6sLsABR2teZ(LdONjS?&cwZsFz7~G?t1wEsAwzr|^iisa!5#1ni$Aw!iH<08xw!xFvYvVD3h3!80{ax5Smdw> zzm$UgDzu)bjF(eI%8c7|)r!NjXJ3USxJ+K<$c;ywe#KNIBf!!~pL#wIK!UI=uXzi@ zSiQL%nI>irLm8aq-+H{%-A8d7#Mi{ruxVSxE*>T@zywMrGb)CUbJsXBq@h?Xtm{EGgX~Wt@_p@6*%iailFn7&Cfs5C~5{^*`Yfv2NA0h87?*|PFR?&G#)W9N844i z!ziI7Y7qzo!cbjS-!sLB5>(4Ta-SL);0L@uP?O&?OIHGNUI81R0TTrq@&h>a#_n~^ zfalGTE_=R3j>{)vH?vX!M$)3ukLNuiP;D^9SL>w}EfPD8KvJX*l>K59I4~6EpTr-M zyOaQ)B=KXxL81kTTeE^Et zQs)TY*j{QiTY&a@Wb$`q_XM6LVk{?@fI7^*~9b{aXh})P&d!i9sjYdr5Gf zRm~+vai{k%p#(1Kiq)*A_E8MRjd7I1k76iydM9f18n#n1)?%kk6XWHsl(HF)n;1O& z{hOwY0nf;v9`M=O-b+myA`znhv<|ljmZho%zH5LZel7Xe#yl5$#85ocDUK9uQmC(@ zgTdev2dckfs?j&zG;S>ZtPS8>GfELO1}W=3iDP{R_&UHKurx%+n7fmRER<}jTZ6H) zVYfYe2){-Z88`L0*}vffw+scSTGI5R;g9KT*e>*o^JY$r46mwf(4N;MMpN&%ErT10JC>kIU!|<)9wDH^b6zS%=C>{O!_V3d zW@~(-)ua`wZ>a;8iLwii*ebUK!RaCk{D2;wL;AQ~y=exThj_>STx1GmWg$*2z(QPm z<^q_Q<6PIA^Wy6gO+AvTTLb= zjZx_^i1mW=e7#!8gx8EJ_LCCHkhauGfiVUxvUJ)Xt3{c1{2YlpXHELE5 z%?miMfhNV-=)JazvE>F8$f$-XpWB`ZPYg|{CWu(LQLn9%?SE*`2z6z&c1ur8CX0zPWWT1trL<{|$N D)kYs% literal 0 HcmV?d00001 diff --git a/docs/content_management/pages/headless_page_builder.md b/docs/content_management/pages/headless_page_builder.md new file mode 100644 index 00000000000..f7524da5117 --- /dev/null +++ b/docs/content_management/pages/headless_page_builder.md @@ -0,0 +1,223 @@ +--- +description: The Page Builder can preview pages outside the DXP. +edition: experience +month_change: true +--- + +# Headless front-end preview in Page Builder + +The Page Builder can preview pages hosted outside the DXP. + +You provide a single URL for the front-end page and let it communicate with the Page Builder using the JavaScript message API. + +The usage of this front-end page instead of the DXP one is set per content type. TODO: This is on-prem only, SaaS don't have the choice + +## Configuration TODO: How this is declared on Saas? + +First, set up the feature, for example, in `config/packages/ibexa_page_builder.yaml`: + +```yaml +ibexa: + system: + admin_group: + page_builder: + headless: + enabled: true + base_url: 'https://example.com/page.html' # The front-end URL loaded by the Page Builder's iframe +``` + +Then, edit the content types with Landing page field type that are used headless, +edit that field, and check the option "Edit in the headless Page Builder". + +![Checked "Edit in the headless Page Builder"](page-edit-headless.png) + +## Communication protocol + +The front-end resource targeted by `base_url` is loaded by the Page Builder when editing a content having a Landing page field "Edit in the headless Page Builder" enabled. +This resource must follow a protocol to communicate with the Page Builder from the iframe is loaded in. +This protocol is based on the JavaScript message API. + +The Page Builder sends messages to the framed front-end resource. +They can be received by listening the [message event](https://developer.mozilla.org/en-US/docs/Web/API/EventSource/message_event). + +The front-end resource sends back messages to the Page Builder. +They can be sent using the [`postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) method. +The target origin of those messages must be the Page Builder's origin. + +```js +const pbOrigin = 'https://admin.example.com/'; +window.parent.postMessage(message, pbOrigin); +``` + +Those messages are JS objects with the following structure: + +```js +let message = { + type: 'PREFIX:MESSAGE_TYPE', + data: {} +}; +``` + +The `PREFIX` sort message types by their sender. +`PB:` for messages sent by the Page Builder, `APP:` for messages sent by the front-end resource. + +The data depends on the message type. + +Message types are then sorted by capabilities. +So the front-end can declare which capabilities it supports and the Page Builder can refrain from sending or expecting unsupported messages. + +### Message types and capabilities + +| Capability | Message type | Description | +|------------------|---------------------------|-----------------------------------------------------------------------------------------------------------| +| (handshake) | APP:INITIALIZED | To establish the communication and declare which protocol version and capabilities are supported. | +| (handshake) | PB:INIT_MODE | To validate the communication and give information about the edited content draft. | +| (core) | PB:UPDATE_FIELD_DATA | To notify that the field has changed, like when a block have been edited or the layout has been switched. | +| (core) | PB:DISPATCH_EVENT | To help converting message into custom event. TODO | +| blocks.dnd | PB:DRAG_START_PREVIEW | TODO | +| blocks.dnd | PB:DRAG_OVER | To give the position of the mouse while a new block is dragged. | +| blocks.dnd | PB:DRAG_END_PREVIEW | TODO | +| blocks.dnd | PB:DROP | To notify that a new block has been dropped. | +| blocks.dnd | APP:DROP_RESPONSE | To notify that the drop action has been processed and declare which zone welcomed the new block. | +| blocks.dnd | PB:SCROLL_BY | TODO | +| blocks.geometry | APP:POSITIONS_UPDATE | To declare the actual position of the blocks (so the block editing menus can be positionned). | +| blocks.geometry | APP:SCROLL_END | TODO | +| blocks.remove | PB:BLOCK_REMOVE | TODO | +| blocks.remove | APP:BLOCK_REMOVE_RESPONSE | TODO | +| blocks.remove | APP:BLOCK_REMOVE_REQUEST | TODO | +| blocks.reveal | PB:SCROLL_INTO_BLOCK | TODO | +| blocks.select | APP:BLOCK_CLICKED | To notify that a block has been clicked. | +| pointer.tracking | APP:MOUSE_POSITION | To declare the actual position of the mouse (so the block editing menus can be displayed on hover). | +| preview.params | PB:UPDATE_PREVIEW_PARAMS | TODO | + +### Communication initialization + +First, the front-end send an initialization message to the Page Builder, indicating which version of the protocol and which capabilities are supported: + +```js +const initializedMessage = { + type: 'APP:INITIALIZED', + data: { + protocol: { + supported: [1] + }, + capabilities: [ + 'blocks.dnd', + 'blocks.geometry', + 'blocks.select', + 'pointer.tracking', + // … + ], + } +}; +``` + +The Page Builder replies with a confirmation message. +The initialization message might be sent several times until the Page Builder replies to it. + +This confirmation `data` contains: +- the actual version of the protocol used (`protocol.version`) and the other supported versions (`protocol.supported`). +- a list of all available capabilities (`capabilities`) +- a list of the existing block types, their attributes, and their configuration (`blocksConfig`) +- information about the actually edited content draft (`intentParameters`) +- the current value of the Landing page field being edited (`fieldValue`) including the layout, zones, and blocks. +- a block ID to name mapping (`blocksIdMap`) +- a list of translations for the front-end to use (`translations`) + +```json +{ + "type": "PB:INIT_MODE", + "data": { + "protocol": { + "version": 1, + "supported": [ + 1 + ] + }, + "capabilities": [ + "blocks.dnd", + "blocks.geometry", + "blocks.remove", + "blocks.reveal", + "blocks.select", + "pointer.tracking", + "preview.params" + ], + "blocksConfig": [ + { + "type": "block_type", + "name": "Block type name", + "category": "Block category", + "thumbnail": "path/to/block/thumbnail.file", + "visible": true, + "views": { + "default": { + "name": "Default" + } + }, + "attributes": [ + { + "id": "attribute_id", + "name": "Attribute name", + "type": "attribute_type", + "value": null, + "constraints": { + "not_blank": { + "message": "Please select…" + } + } + } + ] + } + ], + "intentParameters": { + "locationId": "2", + "contentId": 52, + "versionNo": 6, + "languageCode": "eng-GB" + }, + "fieldValue": { + "layout": "ibexa_fieldtype_page.layouts..identifier", + "zones": [ + { + "id": "123", + "name": "ibexa_fieldtype_page.layouts..zones..name", + "blocks": [ + { + "visible": true, + "id": "456", + "type": "block_type", + "name": "Block name", + "view": "default", + "class": null, + "style": null, + "compiled": "", + "since": null, + "till": null, + "attributes": [ + { + "id": "789", + "name": "attribute_name", + "value": "…" + } + ] + } + ] + } + ] + }, + "blocksIdMap": { + "456": "Block name" + }, + "translations": { + "block.attribute.invalid": "%name% is invalid", + "block.no_availability.content": "You have to delete it to publish", + "block.no_availability.delete": "Delete", + "block.no_availability.title": "This element is not available in this page", + "block.unknown.type": "Unknown block type: %type% (block name: %name%)", + "drag.drop.blocks.here": "Drag and drop blocks here", + "structure.drop.zone": "Drop zone %number%" + } + } +} +``` diff --git a/docs/content_management/pages/pages.md b/docs/content_management/pages/pages.md index f5fb0af210f..71da6027254 100644 --- a/docs/content_management/pages/pages.md +++ b/docs/content_management/pages/pages.md @@ -14,4 +14,5 @@ Pages are block-based special types of content that editors can create and modif "content_management/pages/page_block_attributes", "content_management/pages/page_block_validators", "content_management/pages/create_custom_page_block", + "content_management/pages/headless_page_builder", ], columns=3) =]] diff --git a/mkdocs.yml b/mkdocs.yml index 1b8db944909..ea1bdc80524 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -214,6 +214,7 @@ nav: - Create custom Page block: content_management/pages/create_custom_page_block.md - React App page block: content_management/pages/react_app_block.md - Ibexa Connect scenario block: content_management/pages/ibexa_connect_scenario_block.md + - Headless Page Builder: content_management/pages/headless_page_builder.md - Forms: - Forms: content_management/forms/forms.md - Form Builder guide: content_management/forms/form_builder_guide.md From f611a092d87078109c125c6c80d654ec2a686a1f Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Thu, 17 Sep 2026 17:40:40 +0200 Subject: [PATCH 02/11] Continue documenting Headless Page Builder --- .../pages/headless_page_builder.md | 121 ++++++++++++++---- 1 file changed, 98 insertions(+), 23 deletions(-) diff --git a/docs/content_management/pages/headless_page_builder.md b/docs/content_management/pages/headless_page_builder.md index f7524da5117..f89c3a3d66b 100644 --- a/docs/content_management/pages/headless_page_builder.md +++ b/docs/content_management/pages/headless_page_builder.md @@ -68,27 +68,29 @@ So the front-end can declare which capabilities it supports and the Page Builder ### Message types and capabilities -| Capability | Message type | Description | -|------------------|---------------------------|-----------------------------------------------------------------------------------------------------------| -| (handshake) | APP:INITIALIZED | To establish the communication and declare which protocol version and capabilities are supported. | -| (handshake) | PB:INIT_MODE | To validate the communication and give information about the edited content draft. | -| (core) | PB:UPDATE_FIELD_DATA | To notify that the field has changed, like when a block have been edited or the layout has been switched. | -| (core) | PB:DISPATCH_EVENT | To help converting message into custom event. TODO | -| blocks.dnd | PB:DRAG_START_PREVIEW | TODO | -| blocks.dnd | PB:DRAG_OVER | To give the position of the mouse while a new block is dragged. | -| blocks.dnd | PB:DRAG_END_PREVIEW | TODO | -| blocks.dnd | PB:DROP | To notify that a new block has been dropped. | -| blocks.dnd | APP:DROP_RESPONSE | To notify that the drop action has been processed and declare which zone welcomed the new block. | -| blocks.dnd | PB:SCROLL_BY | TODO | -| blocks.geometry | APP:POSITIONS_UPDATE | To declare the actual position of the blocks (so the block editing menus can be positionned). | -| blocks.geometry | APP:SCROLL_END | TODO | -| blocks.remove | PB:BLOCK_REMOVE | TODO | -| blocks.remove | APP:BLOCK_REMOVE_RESPONSE | TODO | -| blocks.remove | APP:BLOCK_REMOVE_REQUEST | TODO | -| blocks.reveal | PB:SCROLL_INTO_BLOCK | TODO | -| blocks.select | APP:BLOCK_CLICKED | To notify that a block has been clicked. | -| pointer.tracking | APP:MOUSE_POSITION | To declare the actual position of the mouse (so the block editing menus can be displayed on hover). | -| preview.params | PB:UPDATE_PREVIEW_PARAMS | TODO | +The handshake and core messages are mandatory and not related to an optional capability. + +| Capability | Message type | Description | +|------------------|----------------------------------------------------|-----------------------------------------------------------------------------------------------------------| +| (handshake) | [`APP:INITIALIZED`](#communication-initialization) | To establish the communication and declare which protocol version and capabilities are supported. | +| (handshake) | [`PB:INIT_MODE`](#communication-initialization) | To validate the communication and give information about the edited content draft. | +| (core) | [`PB:UPDATE_FIELD_DATA`](#on-field-update) | To notify that the field has changed, like when a block have been edited or the layout has been switched. | +| (core) | [`PB:DISPATCH_EVENT`](##re-dispatching-events) | TODO: To notify of event that should be dispached | +| blocks.dnd | [`PB:DRAG_START_PREVIEW`](#drag-and-drop) | To notify that an existing block starts being dragged in the preview. | +| blocks.dnd | [`PB:DRAG_OVER`](#drag-and-drop) | To give the position of the mouse while a block is dragged. | +| blocks.dnd | [`PB:DRAG_END_PREVIEW`](#drag-and-drop) | To notify that an existing block stopped being dragged in the preview. | +| blocks.dnd | [`PB:DROP`](#drag-and-drop) | To notify that a block has been dropped. | +| blocks.dnd | [`APP:DROP_RESPONSE`](#drag-and-drop) | To notify that the drop action has been processed and declare which zone welcomed the block. | +| blocks.dnd | [`PB:SCROLL_BY`](#drag-and-drop) | TODO | +| blocks.geometry | APP:POSITIONS_UPDATE | To declare the actual position of the blocks (so the block editing menus can be positionned). | +| blocks.geometry | APP:SCROLL_END | TODO | +| blocks.remove | PB:BLOCK_REMOVE | TODO | +| blocks.remove | APP:BLOCK_REMOVE_RESPONSE | TODO | +| blocks.remove | APP:BLOCK_REMOVE_REQUEST | TODO | +| blocks.reveal | PB:SCROLL_INTO_BLOCK | TODO | +| blocks.select | APP:BLOCK_CLICKED | To notify that a block has been clicked. | +| pointer.tracking | APP:MOUSE_POSITION | To declare the actual position of the mouse (so the block editing menus can be displayed on hover). | +| preview.params | PB:UPDATE_PREVIEW_PARAMS | TODO | ### Communication initialization @@ -116,12 +118,12 @@ The Page Builder replies with a confirmation message. The initialization message might be sent several times until the Page Builder replies to it. This confirmation `data` contains: -- the actual version of the protocol used (`protocol.version`) and the other supported versions (`protocol.supported`). +- the actual version of the protocol used (`protocol.version`) and the other supported versions (`protocol.supported`) - a list of all available capabilities (`capabilities`) - a list of the existing block types, their attributes, and their configuration (`blocksConfig`) - information about the actually edited content draft (`intentParameters`) - the current value of the Landing page field being edited (`fieldValue`) including the layout, zones, and blocks. -- a block ID to name mapping (`blocksIdMap`) +- a block-ID-to-name mapping (`blocksIdMap`) - a list of translations for the front-end to use (`translations`) ```json @@ -221,3 +223,76 @@ This confirmation `data` contains: } } ``` + +### On field update + +The `PB:UPDATE_FIELD_DATA` message is sent from the Page Builder to the front-end resource when the Landing page field value is updated. + +Its data contains the new value of the field with the following structure: +- a list of the existing block types, their attributes, and their configuration (`blocksConfig`) +- the current value of the Landing page field being edited (`fieldValue`) including the layout, zones, and blocks. +- a block-ID-to-name mapping (`blocksIdMap`) +- a list of Ids from the new blocks that have been added (`highlightedBlockIds`) + +```json +{ + "type": "PB:UPDATE_FIELD_DATA", + "data": { + "blocksConfig": [], + "fieldValue": { + "layout": "…", + "zones": [] + }, + "blocksIdMap": {}, + "highlightedBlockIds": [] + } +} +``` + +### Re-dispatching events + +The `PB:DISPATCH_EVENT` message is sent from the Page Builder to the front-end resource for being re-dispatched there as a custom event. + +```js +window.addEventListener('message', (messageEvent) => { + switch (messageEvent.data.type) { + case 'PB:DISPATCH_EVENT': + document.body.dispatchEvent(new CustomEvent(messageEvent.data.data.eventName, { detail: messageEvent.data.data.eventDetail })); + break; + } +}); +``` + +TODO: Available events +- `ibexa-active-block-clicked`? +- `ibexa-post-update-blocks-preview`? + +### Drag and drop (`blocks.dnd`) + +`PB:DRAG_OVER` message is sent from the Page Builder to the front-end preview to give the position of the mouse while a (new or existing) block is dragged. + +`PB:DRAG_START_PREVIEW` and `PB:DRAG_END_PREVIEW` are sent at the beginning and at the end of a drag operation on an existing block in the front-end preview. +Its data contains the ID of the block being dragged (`blockId`). + +`PB:DROP` message is sent from the Page Builder to the front-end preview to notify that a block has been dropped. +It has no data. Combined with the last `PB:DRAG_OVER` message, the front-end can determine where the block has been dropped. + +`APP:DROP_RESPONSE` message is sent from the front-end preview to the Page Builder to tell where the block has been dropped. + +Its data contains: +- the ID of the zone where the block has been dropped (`zoneId`) +- the ID of the block that is now next to the dropped block (`nextBlockId`) if the dropped block isn't the last one of the zone. + +In the following example, `targetBlockId` is the ID of a block the dropped block was dropped on or just before, so the dragged block takes its place and move it below, or `null` when dropped at the bottom of the zone. + +```js +const dropResponseMessage = { + type: 'APP:DROP_RESPONSE', + data: { + zoneId: zoneId, + nextBlockId: targetBlockId, + } +}; +``` + +TODO: `PB:SCROLL_BY` when a block is dragged near the top or bottom of the preview, and it needs to be scrolled up or down. From 00941501be36e2963899c538d6ce73923d1e1540 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 18 Sep 2026 10:40:54 +0200 Subject: [PATCH 03/11] Continue documenting Headless Page Builder --- .../pages/headless_page_builder.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/content_management/pages/headless_page_builder.md b/docs/content_management/pages/headless_page_builder.md index f89c3a3d66b..6ecbca92a42 100644 --- a/docs/content_management/pages/headless_page_builder.md +++ b/docs/content_management/pages/headless_page_builder.md @@ -281,9 +281,9 @@ It has no data. Combined with the last `PB:DRAG_OVER` message, the front-end can Its data contains: - the ID of the zone where the block has been dropped (`zoneId`) -- the ID of the block that is now next to the dropped block (`nextBlockId`) if the dropped block isn't the last one of the zone. +- the ID of a block that is now below the dropped block (`nextBlockId`) if the dropped block isn't the last one of the zone. -In the following example, `targetBlockId` is the ID of a block the dropped block was dropped on or just before, so the dragged block takes its place and move it below, or `null` when dropped at the bottom of the zone. +In the following example, `targetBlockId` value is the ID of a block the dropped block was dropped on or just before, so the dragged block takes its place and move it below, or `null` when dropped at the bottom of the zone. ```js const dropResponseMessage = { @@ -295,4 +295,15 @@ const dropResponseMessage = { }; ``` -TODO: `PB:SCROLL_BY` when a block is dragged near the top or bottom of the preview, and it needs to be scrolled up or down. +`PB:SCROLL_BY` message is sent from the Page Builder when a block is dragged near a border of the preview which needs to be scrolled. +Its data contains the `top` or `left` amount to scroll by. + +```js +window.addEventListener('message', (messageEvent) => { + switch (messageEvent.data.type) { + case 'PB:SCROLL_BY': + window.scrollBy(messageEvent.data.data); + break; + } +}); +``` From 2e1165054539ba47114a17583fbb6b373866773b Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 18 Sep 2026 11:56:55 +0200 Subject: [PATCH 04/11] Continue documenting Headless Page Builder --- .../pages/headless_page_builder.md | 94 ++++++++++++++----- 1 file changed, 73 insertions(+), 21 deletions(-) diff --git a/docs/content_management/pages/headless_page_builder.md b/docs/content_management/pages/headless_page_builder.md index 6ecbca92a42..8b1ec3b4f47 100644 --- a/docs/content_management/pages/headless_page_builder.md +++ b/docs/content_management/pages/headless_page_builder.md @@ -70,27 +70,27 @@ So the front-end can declare which capabilities it supports and the Page Builder The handshake and core messages are mandatory and not related to an optional capability. -| Capability | Message type | Description | -|------------------|----------------------------------------------------|-----------------------------------------------------------------------------------------------------------| -| (handshake) | [`APP:INITIALIZED`](#communication-initialization) | To establish the communication and declare which protocol version and capabilities are supported. | -| (handshake) | [`PB:INIT_MODE`](#communication-initialization) | To validate the communication and give information about the edited content draft. | -| (core) | [`PB:UPDATE_FIELD_DATA`](#on-field-update) | To notify that the field has changed, like when a block have been edited or the layout has been switched. | -| (core) | [`PB:DISPATCH_EVENT`](##re-dispatching-events) | TODO: To notify of event that should be dispached | -| blocks.dnd | [`PB:DRAG_START_PREVIEW`](#drag-and-drop) | To notify that an existing block starts being dragged in the preview. | -| blocks.dnd | [`PB:DRAG_OVER`](#drag-and-drop) | To give the position of the mouse while a block is dragged. | -| blocks.dnd | [`PB:DRAG_END_PREVIEW`](#drag-and-drop) | To notify that an existing block stopped being dragged in the preview. | -| blocks.dnd | [`PB:DROP`](#drag-and-drop) | To notify that a block has been dropped. | -| blocks.dnd | [`APP:DROP_RESPONSE`](#drag-and-drop) | To notify that the drop action has been processed and declare which zone welcomed the block. | -| blocks.dnd | [`PB:SCROLL_BY`](#drag-and-drop) | TODO | -| blocks.geometry | APP:POSITIONS_UPDATE | To declare the actual position of the blocks (so the block editing menus can be positionned). | -| blocks.geometry | APP:SCROLL_END | TODO | -| blocks.remove | PB:BLOCK_REMOVE | TODO | -| blocks.remove | APP:BLOCK_REMOVE_RESPONSE | TODO | -| blocks.remove | APP:BLOCK_REMOVE_REQUEST | TODO | -| blocks.reveal | PB:SCROLL_INTO_BLOCK | TODO | -| blocks.select | APP:BLOCK_CLICKED | To notify that a block has been clicked. | -| pointer.tracking | APP:MOUSE_POSITION | To declare the actual position of the mouse (so the block editing menus can be displayed on hover). | -| preview.params | PB:UPDATE_PREVIEW_PARAMS | TODO | +| Capability | Message type | Description | +|------------------|---------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------| +| (handshake) | [`APP:INITIALIZED`](#communication-initialization) | To establish the communication and declare which protocol version and capabilities are supported. | +| (handshake) | [`PB:INIT_MODE`](#communication-initialization) | To validate the communication and give information about the edited content draft. | +| (core) | [`PB:UPDATE_FIELD_DATA`](#on-field-update) | To notify that the field has changed, like when a block have been edited or the layout has been switched. | +| (core) | [`PB:DISPATCH_EVENT`](#re-dispatching-events) | TODO: To notify of event that should be dispached | +| blocks.dnd | [`PB:DRAG_START_PREVIEW`](#drag-and-drop-blocksdnd) | To notify that an existing block starts being dragged in the preview. | +| blocks.dnd | [`PB:DRAG_OVER`](#drag-and-drop-blocksdnd) | To give the position of the mouse while a block is dragged. | +| blocks.dnd | [`PB:DRAG_END_PREVIEW`](#drag-and-drop-blocksdnd) | To notify that an existing block stopped being dragged in the preview. | +| blocks.dnd | [`PB:DROP`](#drag-and-drop-blocksdnd) | To notify that a block has been dropped. | +| blocks.dnd | [`APP:DROP_RESPONSE`](#drag-and-drop-blocksdnd) | To notify that the drop action has been processed and declare which zone welcomed the block. | +| blocks.dnd | [`PB:SCROLL_BY`](#drag-and-drop-blocksdnd) | TODO | +| blocks.geometry | [`APP:POSITIONS_UPDATE`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | To declare the actual position of the blocks (so the block editing menus can be positionned). | +| blocks.geometry | [`APP:SCROLL_END`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | TODO | +| blocks.remove | PB:BLOCK_REMOVE | TODO | +| blocks.remove | APP:BLOCK_REMOVE_RESPONSE | TODO | +| blocks.remove | APP:BLOCK_REMOVE_REQUEST | TODO | +| blocks.reveal | PB:SCROLL_INTO_BLOCK | TODO | +| blocks.select | APP:BLOCK_CLICKED | To notify that a block has been clicked. | +| pointer.tracking | [`APP:MOUSE_POSITION`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | To declare the actual position of the mouse (so the block editing menus can be displayed on hover). | +| preview.params | PB:UPDATE_PREVIEW_PARAMS | TODO | ### Communication initialization @@ -267,10 +267,62 @@ TODO: Available events - `ibexa-active-block-clicked`? - `ibexa-post-update-blocks-preview`? +### Geometry and pointer tracking (`blocks.geometry` and `pointer.tracking`) + +The pointer tracking and the geometry helps the Page Builder to position the block editing menus above the front-end preview. +Such menu is a `.c-pb-headless-preview-menu` element positioned by the Page Builder from its DOM above the preview `iframe`. + +`APP:MOUSE_POSITION` message is sent from the front-end preview to the Page Builder to declare the actual position of the mouse. + +```js +window.addEventListener('mousemove', (mouseEvent) => { + window.parent.postMessage({ + type: 'APP:MOUSE_POSITION', + data: { + x: mouseEvent.clientX, + y: mouseEvent.clientY, + }, + }, pbOrigin); +}); +``` + +`APP:POSITIONS_UPDATE` message is sent from the front-end preview to the Page Builder to declare the actual position of the blocks. + +```js +const positionsUpdate = () => { + let blocks = []; + for (const blockElement of document.getElementsByClassName('landing-page__block')) { + const blockId = blockElement.dataset.ibexaBlockId; + const blockRect = blockElement.getBoundingClientRect(); + blocks.push({ + id: blockId, + top: blockRect.top, + left: blockRect.left, + right: blockRect.right, + bottom: blockRect.bottom, + width: blockRect.width, + height: blockRect.height, + }); + } + window.parent.postMessage({ + type: 'APP:POSITIONS_UPDATE', + data: { + blocks: blocks, + }, + }, pbOrigin); +}; +``` + +TODO: `APP:SCROLL_END` message is sent from the front-end preview to the Page Builder to notify that a scroll operation has ended. + ### Drag and drop (`blocks.dnd`) `PB:DRAG_OVER` message is sent from the Page Builder to the front-end preview to give the position of the mouse while a (new or existing) block is dragged. +!!! tip +- `APP:MOUSE_POSITION` helps the Page Builder to know where the mouse is when moved over the preview. +- `PB:DRAG_OVER` helps the front-end to know where the mouse is when a block is dragged over the preview. + `PB:DRAG_START_PREVIEW` and `PB:DRAG_END_PREVIEW` are sent at the beginning and at the end of a drag operation on an existing block in the front-end preview. Its data contains the ID of the block being dragged (`blockId`). From a4e73a7abe7a86c2eae69d0c33c454daedc8d3d2 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:37:17 +0200 Subject: [PATCH 05/11] headless_page_builder.md: Fix MD032/blanks-around-lists --- docs/content_management/pages/headless_page_builder.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/content_management/pages/headless_page_builder.md b/docs/content_management/pages/headless_page_builder.md index 8b1ec3b4f47..8dc0a515cb4 100644 --- a/docs/content_management/pages/headless_page_builder.md +++ b/docs/content_management/pages/headless_page_builder.md @@ -118,6 +118,7 @@ The Page Builder replies with a confirmation message. The initialization message might be sent several times until the Page Builder replies to it. This confirmation `data` contains: + - the actual version of the protocol used (`protocol.version`) and the other supported versions (`protocol.supported`) - a list of all available capabilities (`capabilities`) - a list of the existing block types, their attributes, and their configuration (`blocksConfig`) @@ -229,6 +230,7 @@ This confirmation `data` contains: The `PB:UPDATE_FIELD_DATA` message is sent from the Page Builder to the front-end resource when the Landing page field value is updated. Its data contains the new value of the field with the following structure: + - a list of the existing block types, their attributes, and their configuration (`blocksConfig`) - the current value of the Landing page field being edited (`fieldValue`) including the layout, zones, and blocks. - a block-ID-to-name mapping (`blocksIdMap`) @@ -264,6 +266,7 @@ window.addEventListener('message', (messageEvent) => { ``` TODO: Available events + - `ibexa-active-block-clicked`? - `ibexa-post-update-blocks-preview`? @@ -320,8 +323,9 @@ TODO: `APP:SCROLL_END` message is sent from the front-end preview to the Page Bu `PB:DRAG_OVER` message is sent from the Page Builder to the front-end preview to give the position of the mouse while a (new or existing) block is dragged. !!! tip -- `APP:MOUSE_POSITION` helps the Page Builder to know where the mouse is when moved over the preview. -- `PB:DRAG_OVER` helps the front-end to know where the mouse is when a block is dragged over the preview. + + - `APP:MOUSE_POSITION` helps the Page Builder to know where the mouse is when moved over the preview. + - `PB:DRAG_OVER` helps the front-end to know where the mouse is when a block is dragged over the preview. `PB:DRAG_START_PREVIEW` and `PB:DRAG_END_PREVIEW` are sent at the beginning and at the end of a drag operation on an existing block in the front-end preview. Its data contains the ID of the block being dragged (`blockId`). @@ -332,6 +336,7 @@ It has no data. Combined with the last `PB:DRAG_OVER` message, the front-end can `APP:DROP_RESPONSE` message is sent from the front-end preview to the Page Builder to tell where the block has been dropped. Its data contains: + - the ID of the zone where the block has been dropped (`zoneId`) - the ID of a block that is now below the dropped block (`nextBlockId`) if the dropped block isn't the last one of the zone. From 7c282d7f5b4907c57639ee109dd352aa18523de7 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 18 Sep 2026 16:38:33 +0200 Subject: [PATCH 06/11] Continue documenting Headless Page Builder --- .../pages/headless_page_builder.md | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/content_management/pages/headless_page_builder.md b/docs/content_management/pages/headless_page_builder.md index 8dc0a515cb4..8caeb1e91fe 100644 --- a/docs/content_management/pages/headless_page_builder.md +++ b/docs/content_management/pages/headless_page_builder.md @@ -290,6 +290,8 @@ window.addEventListener('mousemove', (mouseEvent) => { ``` `APP:POSITIONS_UPDATE` message is sent from the front-end preview to the Page Builder to declare the actual position of the blocks. +Its data contains a list of objects with block IDs, their positions, and dimensions in the front-end preview. +This format is close to [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) method. ```js const positionsUpdate = () => { @@ -316,7 +318,32 @@ const positionsUpdate = () => { }; ``` -TODO: `APP:SCROLL_END` message is sent from the front-end preview to the Page Builder to notify that a scroll operation has ended. +This message should be sent each time the positions of the blocks change. +It should be sent after updating the blocks, like in response to [`PB:UPDATE_FIELD_DATA`](#on-field-update). +It should be sent after scrolling or resizing. + +`APP:SCROLL_END` message is sent from the front-end preview to the Page Builder to notify that a scroll operation has ended. +It has no data. + +```js +window.addEventListener('scrollend', (event) => { + window.parent.postMessage({ + type: 'APP:SCROLL_END', + }, pbOrigin); + positionsUpdate(); +}); +window.addEventListener('resize', (event) => { + positionsUpdate(); +}); +window.addEventListener('message', (messageEvent) => { + switch (messageEvent.data.type) { + case 'PB:UPDATE_FIELD_DATA': + renderZones(messageEvent.data.data.fieldValue.zones); + positionsUpdate(); + break; + } +}); +``` ### Drag and drop (`blocks.dnd`) From 975b6ae056ef13f598e2dce413fd2e311ba5691a Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 18 Sep 2026 16:58:34 +0200 Subject: [PATCH 07/11] More TODOs about Headless Page Builder --- docs/content_management/pages/headless_page_builder.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/content_management/pages/headless_page_builder.md b/docs/content_management/pages/headless_page_builder.md index 8caeb1e91fe..df480e4e8ac 100644 --- a/docs/content_management/pages/headless_page_builder.md +++ b/docs/content_management/pages/headless_page_builder.md @@ -345,6 +345,8 @@ window.addEventListener('message', (messageEvent) => { }); ``` +TODO: Is there other events that should trigger a positions update? + ### Drag and drop (`blocks.dnd`) `PB:DRAG_OVER` message is sent from the Page Builder to the front-end preview to give the position of the mouse while a (new or existing) block is dragged. @@ -391,3 +393,5 @@ window.addEventListener('message', (messageEvent) => { } }); ``` + +TODO: See `APP:SCROLL_END` message to declare the end of the scroll operation and trigger a `APP:POSITIONS_UPDATE` message to update the blocks positions. From 2980d36035270a330ec9fff6f74a466dd805d347 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 18 Sep 2026 17:46:31 +0200 Subject: [PATCH 08/11] Continue documenting Headless Page Builder --- .../pages/headless_page_builder.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/content_management/pages/headless_page_builder.md b/docs/content_management/pages/headless_page_builder.md index df480e4e8ac..7acc89e8e5e 100644 --- a/docs/content_management/pages/headless_page_builder.md +++ b/docs/content_management/pages/headless_page_builder.md @@ -395,3 +395,16 @@ window.addEventListener('message', (messageEvent) => { ``` TODO: See `APP:SCROLL_END` message to declare the end of the scroll operation and trigger a `APP:POSITIONS_UPDATE` message to update the blocks positions. + +### Block removal (`blocks.remove`) + +`PB:BLOCK_REMOVE` message is sent from the Page Builder to the front-end preview to notify that a block should be removed, for example, from the Structure view. +Its data contains the ID of the block to remove (`blockId`). + +`APP:BLOCK_REMOVE_RESPONSE` message is sent from the front-end preview to the Page Builder to confirm that the block has been removed as requested by `PB:BLOCK_REMOVE`. +Its data contains the ID of the removed block (`blockId`). +It can be sent immediately or after removal animation. + +`APP:BLOCK_REMOVE_REQUEST` message is sent from the front-end preview to the Page Builder to request the removal of a block. +Its data contains the ID of the block to remove (`blockId`). +The Page Builder responses with a `PB:UPDATE_FIELD_DATA`. From 2c6ecae8ea914f7a82913ccb13632f9ad7a0be17 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 18 Sep 2026 18:10:14 +0200 Subject: [PATCH 09/11] Continue documenting Headless Page Builder --- .../pages/headless_page_builder.md | 62 ++++++++++++------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/docs/content_management/pages/headless_page_builder.md b/docs/content_management/pages/headless_page_builder.md index 7acc89e8e5e..4d7fb49a77d 100644 --- a/docs/content_management/pages/headless_page_builder.md +++ b/docs/content_management/pages/headless_page_builder.md @@ -70,27 +70,27 @@ So the front-end can declare which capabilities it supports and the Page Builder The handshake and core messages are mandatory and not related to an optional capability. -| Capability | Message type | Description | -|------------------|---------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------| -| (handshake) | [`APP:INITIALIZED`](#communication-initialization) | To establish the communication and declare which protocol version and capabilities are supported. | -| (handshake) | [`PB:INIT_MODE`](#communication-initialization) | To validate the communication and give information about the edited content draft. | -| (core) | [`PB:UPDATE_FIELD_DATA`](#on-field-update) | To notify that the field has changed, like when a block have been edited or the layout has been switched. | -| (core) | [`PB:DISPATCH_EVENT`](#re-dispatching-events) | TODO: To notify of event that should be dispached | -| blocks.dnd | [`PB:DRAG_START_PREVIEW`](#drag-and-drop-blocksdnd) | To notify that an existing block starts being dragged in the preview. | -| blocks.dnd | [`PB:DRAG_OVER`](#drag-and-drop-blocksdnd) | To give the position of the mouse while a block is dragged. | -| blocks.dnd | [`PB:DRAG_END_PREVIEW`](#drag-and-drop-blocksdnd) | To notify that an existing block stopped being dragged in the preview. | -| blocks.dnd | [`PB:DROP`](#drag-and-drop-blocksdnd) | To notify that a block has been dropped. | -| blocks.dnd | [`APP:DROP_RESPONSE`](#drag-and-drop-blocksdnd) | To notify that the drop action has been processed and declare which zone welcomed the block. | -| blocks.dnd | [`PB:SCROLL_BY`](#drag-and-drop-blocksdnd) | TODO | -| blocks.geometry | [`APP:POSITIONS_UPDATE`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | To declare the actual position of the blocks (so the block editing menus can be positionned). | -| blocks.geometry | [`APP:SCROLL_END`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | TODO | -| blocks.remove | PB:BLOCK_REMOVE | TODO | -| blocks.remove | APP:BLOCK_REMOVE_RESPONSE | TODO | -| blocks.remove | APP:BLOCK_REMOVE_REQUEST | TODO | -| blocks.reveal | PB:SCROLL_INTO_BLOCK | TODO | -| blocks.select | APP:BLOCK_CLICKED | To notify that a block has been clicked. | -| pointer.tracking | [`APP:MOUSE_POSITION`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | To declare the actual position of the mouse (so the block editing menus can be displayed on hover). | -| preview.params | PB:UPDATE_PREVIEW_PARAMS | TODO | +| Capability | Message type | Description | +|--------------------|---------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------| +| (handshake) | [`APP:INITIALIZED`](#communication-initialization) | To establish the communication and declare which protocol version and capabilities are supported. | +| (handshake) | [`PB:INIT_MODE`](#communication-initialization) | To validate the communication and give information about the edited content draft. | +| (core) | [`PB:UPDATE_FIELD_DATA`](#on-field-update) | To notify that the field has changed, like when a block have been edited or the layout has been switched. | +| (core) | [`PB:DISPATCH_EVENT`](#re-dispatching-events) | To notify of event that should be dispached in front-end preview. | +| `blocks.dnd` | [`PB:DRAG_START_PREVIEW`](#drag-and-drop-blocksdnd) | To notify that an existing block starts being dragged in the preview. | +| `blocks.dnd` | [`PB:DRAG_OVER`](#drag-and-drop-blocksdnd) | To give the position of the mouse while a block is dragged. | +| `blocks.dnd` | [`PB:DRAG_END_PREVIEW`](#drag-and-drop-blocksdnd) | To notify that an existing block stopped being dragged in the preview. | +| `blocks.dnd` | [`PB:DROP`](#drag-and-drop-blocksdnd) | To notify that a block has been dropped. | +| `blocks.dnd` | [`APP:DROP_RESPONSE`](#drag-and-drop-blocksdnd) | To notify that the drop action has been processed and declare which zone welcomed the block. | +| `blocks.dnd` | [`PB:SCROLL_BY`](#drag-and-drop-blocksdnd) | To notify that the preview should be scrolled. | +| `blocks.geometry` | [`APP:POSITIONS_UPDATE`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | To declare the actual position of the blocks (so the block editing menus can be positionned). | +| `blocks.geometry` | [`APP:SCROLL_END`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | To notify that the preview stopped scrolling. | +| `blocks.remove` | [`PB:BLOCK_REMOVE`](#block-removal-blocksremove) | To notify the preview that a block is removed. | +| `blocks.remove` | [`APP:BLOCK_REMOVE_RESPONSE`](#block-removal-blocksremove) | To the the Page Builder that the block has been removed. | +| `blocks.remove` | [`APP:BLOCK_REMOVE_REQUEST`](#block-removal-blocksremove) | To ask the Page Builder to remove a block. | +| `blocks.reveal` | [`PB:SCROLL_INTO_BLOCK`](#block-reveal-blocksreveal) | To notify the preview to scroll to have a specific block into view. | +| `blocks.select` | `APP:BLOCK_CLICKED` | TODO: To notify that a block has been clicked. | +| `pointer.tracking` | [`APP:MOUSE_POSITION`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | To declare the actual position of the mouse (so the block editing menus can be displayed on hover). | +| `preview.params` | `PB:UPDATE_PREVIEW_PARAMS` | TODO | ### Communication initialization @@ -394,7 +394,25 @@ window.addEventListener('message', (messageEvent) => { }); ``` -TODO: See `APP:SCROLL_END` message to declare the end of the scroll operation and trigger a `APP:POSITIONS_UPDATE` message to update the blocks positions. +See [Geometry (`blocks.geometry`)](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking)'s `APP:SCROLL_END` message to declare the end of the scroll operation +and `APP:POSITIONS_UPDATE` message to update the blocks positions. + +### Block reveal (`blocks.reveal`) + +`PB:SCROLL_INTO_BLOCK` is send by the Page Builder to the front-end preview to request that a block is scrolled into view. +Its data contains the ID of the block to scroll into view (`blockId`). +It can be used with the [`scrollIntoView()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) method. + +```js +window.addEventListener('message', (messageEvent) => { + switch (messageEvent.data.type) { + case 'PB:SCROLL_INTO_BLOCK': + const blockElementToScrollInto = document.getElementById('block_' + messageEvent.data.data.blockId); + blockElementToScrollInto.scrollIntoView({ behavior: 'smooth', block: 'center' }); + break; + } +}); +``` ### Block removal (`blocks.remove`) From a66a830b841420305b6a8cc6385bd4204edb00fa Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 18 Sep 2026 18:12:36 +0200 Subject: [PATCH 10/11] Continue documenting Headless Page Builder --- docs/content_management/pages/headless_page_builder.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content_management/pages/headless_page_builder.md b/docs/content_management/pages/headless_page_builder.md index 4d7fb49a77d..b004721f115 100644 --- a/docs/content_management/pages/headless_page_builder.md +++ b/docs/content_management/pages/headless_page_builder.md @@ -85,7 +85,7 @@ The handshake and core messages are mandatory and not related to an optional cap | `blocks.geometry` | [`APP:POSITIONS_UPDATE`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | To declare the actual position of the blocks (so the block editing menus can be positionned). | | `blocks.geometry` | [`APP:SCROLL_END`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | To notify that the preview stopped scrolling. | | `blocks.remove` | [`PB:BLOCK_REMOVE`](#block-removal-blocksremove) | To notify the preview that a block is removed. | -| `blocks.remove` | [`APP:BLOCK_REMOVE_RESPONSE`](#block-removal-blocksremove) | To the the Page Builder that the block has been removed. | +| `blocks.remove` | [`APP:BLOCK_REMOVE_RESPONSE`](#block-removal-blocksremove) | To tell the Page Builder that the block has been removed. | | `blocks.remove` | [`APP:BLOCK_REMOVE_REQUEST`](#block-removal-blocksremove) | To ask the Page Builder to remove a block. | | `blocks.reveal` | [`PB:SCROLL_INTO_BLOCK`](#block-reveal-blocksreveal) | To notify the preview to scroll to have a specific block into view. | | `blocks.select` | `APP:BLOCK_CLICKED` | TODO: To notify that a block has been clicked. | From e8814cb248bb8d6a3e60a7a75f7166ff1ef0a584 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 18 Sep 2026 18:16:00 +0200 Subject: [PATCH 11/11] Shorten message type Description column w/ Copilot --- .../pages/headless_page_builder.md | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/content_management/pages/headless_page_builder.md b/docs/content_management/pages/headless_page_builder.md index b004721f115..c9f71253de2 100644 --- a/docs/content_management/pages/headless_page_builder.md +++ b/docs/content_management/pages/headless_page_builder.md @@ -70,27 +70,27 @@ So the front-end can declare which capabilities it supports and the Page Builder The handshake and core messages are mandatory and not related to an optional capability. -| Capability | Message type | Description | -|--------------------|---------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------| -| (handshake) | [`APP:INITIALIZED`](#communication-initialization) | To establish the communication and declare which protocol version and capabilities are supported. | -| (handshake) | [`PB:INIT_MODE`](#communication-initialization) | To validate the communication and give information about the edited content draft. | -| (core) | [`PB:UPDATE_FIELD_DATA`](#on-field-update) | To notify that the field has changed, like when a block have been edited or the layout has been switched. | -| (core) | [`PB:DISPATCH_EVENT`](#re-dispatching-events) | To notify of event that should be dispached in front-end preview. | -| `blocks.dnd` | [`PB:DRAG_START_PREVIEW`](#drag-and-drop-blocksdnd) | To notify that an existing block starts being dragged in the preview. | -| `blocks.dnd` | [`PB:DRAG_OVER`](#drag-and-drop-blocksdnd) | To give the position of the mouse while a block is dragged. | -| `blocks.dnd` | [`PB:DRAG_END_PREVIEW`](#drag-and-drop-blocksdnd) | To notify that an existing block stopped being dragged in the preview. | -| `blocks.dnd` | [`PB:DROP`](#drag-and-drop-blocksdnd) | To notify that a block has been dropped. | -| `blocks.dnd` | [`APP:DROP_RESPONSE`](#drag-and-drop-blocksdnd) | To notify that the drop action has been processed and declare which zone welcomed the block. | -| `blocks.dnd` | [`PB:SCROLL_BY`](#drag-and-drop-blocksdnd) | To notify that the preview should be scrolled. | -| `blocks.geometry` | [`APP:POSITIONS_UPDATE`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | To declare the actual position of the blocks (so the block editing menus can be positionned). | -| `blocks.geometry` | [`APP:SCROLL_END`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | To notify that the preview stopped scrolling. | -| `blocks.remove` | [`PB:BLOCK_REMOVE`](#block-removal-blocksremove) | To notify the preview that a block is removed. | -| `blocks.remove` | [`APP:BLOCK_REMOVE_RESPONSE`](#block-removal-blocksremove) | To tell the Page Builder that the block has been removed. | -| `blocks.remove` | [`APP:BLOCK_REMOVE_REQUEST`](#block-removal-blocksremove) | To ask the Page Builder to remove a block. | -| `blocks.reveal` | [`PB:SCROLL_INTO_BLOCK`](#block-reveal-blocksreveal) | To notify the preview to scroll to have a specific block into view. | -| `blocks.select` | `APP:BLOCK_CLICKED` | TODO: To notify that a block has been clicked. | -| `pointer.tracking` | [`APP:MOUSE_POSITION`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | To declare the actual position of the mouse (so the block editing menus can be displayed on hover). | -| `preview.params` | `PB:UPDATE_PREVIEW_PARAMS` | TODO | +| Capability | Message type | Description | +|--------------------|---------------------------------------------------------------------------------------------|--------------------------------------| +| (handshake) | [`APP:INITIALIZED`](#communication-initialization) | Establish protocol and capabilities. | +| (handshake) | [`PB:INIT_MODE`](#communication-initialization) | Confirm protocol and draft info. | +| (core) | [`PB:UPDATE_FIELD_DATA`](#on-field-update) | Send updated field data. | +| (core) | [`PB:DISPATCH_EVENT`](#re-dispatching-events) | Re-dispatch a front-end event. | +| `blocks.dnd` | [`PB:DRAG_START_PREVIEW`](#drag-and-drop-blocksdnd) | Existing block drag started. | +| `blocks.dnd` | [`PB:DRAG_OVER`](#drag-and-drop-blocksdnd) | Mouse position during drag. | +| `blocks.dnd` | [`PB:DRAG_END_PREVIEW`](#drag-and-drop-blocksdnd) | Existing block drag ended. | +| `blocks.dnd` | [`PB:DROP`](#drag-and-drop-blocksdnd) | Drop notification. | +| `blocks.dnd` | [`APP:DROP_RESPONSE`](#drag-and-drop-blocksdnd) | Report where the block was dropped. | +| `blocks.dnd` | [`PB:SCROLL_BY`](#drag-and-drop-blocksdnd) | Scroll the preview. | +| `blocks.geometry` | [`APP:POSITIONS_UPDATE`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | Report block positions. | +| `blocks.geometry` | [`APP:SCROLL_END`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | Scroll ended. | +| `blocks.remove` | [`PB:BLOCK_REMOVE`](#block-removal-blocksremove) | Remove a block. | +| `blocks.remove` | [`APP:BLOCK_REMOVE_RESPONSE`](#block-removal-blocksremove) | Confirm removal. | +| `blocks.remove` | [`APP:BLOCK_REMOVE_REQUEST`](#block-removal-blocksremove) | Request block removal. | +| `blocks.reveal` | [`PB:SCROLL_INTO_BLOCK`](#block-reveal-blocksreveal) | Scroll a block into view. | +| `blocks.select` | `APP:BLOCK_CLICKED` | TODO: Block clicked. | +| `pointer.tracking` | [`APP:MOUSE_POSITION`](#geometry-and-pointer-tracking-blocksgeometry-and-pointertracking) | Report mouse position. | +| `preview.params` | `PB:UPDATE_PREVIEW_PARAMS` | TODO: Update preview params. | ### Communication initialization