ÿØÿà JFIF    ÿÛ „ !.%+&8&+/1555$;@;4?.451 4,$,44444444444414444444444444444444444444444444444444ÿÀ  á á" ÿÄ     ÿÄ ?    !1AQaq"2‘¡±ÁðBRbrÑá#‚’¢²3S CñÿÄ   ÿÄ !    !1QAa‘2ÿÚ   ? 5˜Z¯V¦cø)›t/? z¨±>Õ5€¶‹Á¤·¼z¼Ü¬+ñ®v¤¨_ˆR­BFn©—˜ý®ç̝P8gýt·ÉSTŦˆìät?þé¼íìN/Þa)ì–í6ô… Ï¿øÃj´¿KÇü]ÿ ªô¹-eKànëÕHTx}ýSÜ›ÿ ”7Ø×&µ<¦  ¥ÑO¶[Ù¯ä¨ÞÃÿ PZ-¬;#õ|•oaÿ ©CìÞz3˜öː/¤­ñTûIØ}š^ mÓ%ªxˆ¥ÉŸu=Z+ISe¿45™¼u;ú&WØ÷€æßQ™®{|íx*TC“#ZŠìZ§²‹ 6pv…³¿¡äª*áZÐ%ÒOáˆo"x«OHk w±æ+¬V(kMúŸ5Vö«$ ÁrÏbàb57/luR ¸ÑÛj Òµì`Мq­û žICÀÊ•©4€Âcà¨Ï€O´<èÐ:›ù(Ë^L8þ‘ÍÌ#¸Ð_Ì©ÙK(Öz 4¬û+¸;ü’V’84‘¬ÃŽ:[â‡ÔÌáõp¢~§ªlæ£ö{®G>J¼"°‡7¯ÆÉèßû ‹É‹§ÁòÃýâßî ^ƾÙõ‹×óH#«LP½ïX=xÑÍ$|W?•~• îëÔ©ª‹ {ÝT…Kÿ ”hûâá)J*ö˜–ÔU;iÇ€/ ÆþjóZ\ýwØ=Ìm ºèËL9 ýèÆð/¨’¥öo=nË.%Îì ŽÕ¯È|{Oj²ƒE6e/ßdÄõ²Ìâ1O®ò×TsəԸhOMýíMˆ¿¼H˜l²,7Â¥#MF/Úf°Ö½± ¸–dr‹NýÊ íjqx{œÉ ä-È ¦ øÄër¨q°ð †nцýÑÄÆ’mä…n<0È™;ÁÝá¯ÁZƒ7FÀmì­ É&9ˆîéi¶ùN§Y• ÃZãAâ?•‡©‰ , ó¾IŸŠc1 4â&y­&pŠ­6;M À 0¹qç»p.á …ŸÅáK@%6·y6ƒ‰3?”úºŽ‰éX5ªPT §µ!=Mž«Ú½‹ÅgÂSâÉaþÓoö–¯ÁÔìR>5éÿ üs¶ÆUcÌ kÇR ]ÿ ù¬¼«VŽ;Â|‡~¢¦”ÏŰæ {L™Õ°Óv¹ò¸írޡעCÃ!íVÕ {¶»sŒNPg/ "uÕbkm²“$ďå¿é¹§°½æz¯6 †s¿!s–wÚÝ“™Œ °.ûj>·+™Òa…©Œ&rÝÎtÛë긪Ît’LAVp%c Úý[ÄzJ¾ÇàXXç@˜ó<êL]·T˜¾¥1Ó©V‡g´æ½¦Ý@¹óø!_@´ÞâSÁ —S3™•& ]@JHÚý©ZŽ €×æÔr»Áf!‡yÞ4Mv*èÓã_{‘åóUuљØ«Oïé*®EvÑ Œ÷‡U \"㪒ÍK+À 4“M¡ï:0¥5í!'<@î´”>Ç»&Z–ïCCV˜Ì5Šo&îhè.žû |ÓK©h$s6KìŒëã)¹hI¦GïOåóI;ììü#É$Š0…Ææ¥TØ.5­¾gn´ “ÂÖ\:hœ89G)J@„}œ:’Ò{/Š"¦_Æ×7Æ3VÇŠÊa]ÚŒÙ€Ä–=®uÁßâACZƒ§§£ Qnâ:«,×{tyø¬iÛcœÜÄ€H½ÄÍCk´÷šß .W'b¤Íåh]÷€=,Žv×cÚEÚHXJX¶îo¨FÒtèöŸ>ªª6[J®Fµ£sGÁeqõfe\íjÒÐïÄÐGˆe1Ø‹.Ø”‘Ëuø Y­ˆÜ ŽG|zùªüMpDnQWÄ”%JŠ™)â*p@Örš«ÕT2Ð%ˆG#ª„ ·¤!°ŸOTÂT¸aÚ%4&h™LµšØüÐ.F¿²ÐÞ_Ç‚¾ÅÃaÜ÷09Æ q€öy˜v‡85õN÷]¬äѼóS{°_MެúÔ#°Ç¸0åÞè2ëôPcvÆw9®ií1Ä8F™˜à‰´+‰Ik1òÝ7“Ñ×ÒsÝ\x‚h`ÞÑ`ó"|µEcý£n˜h`}GÞ !±ù²Ápü²ß6 0ïi󜵩SÈÇ7˜-ÕURO˜¦´f$ªž-Í6(œ}<„ éc øs]ŽŽ„*—¾ ìdŽ„)méª\¿êÎIg¾ØÞ~I#C/¼¼´EÁÈŽi8“©õådô·>euä ƒ'Ê×लR1ÉJE1ÐAát`t;ÇР%Ý<‡¥„ÍÆ`×Oyó)õiI€ñQaŸ4Ûù\áàaÃÔ¹HÃu¹*k€¦<„e S‡&õÏ B!ŽhüÞ`yj}mªf×\¿ Ç~æ­9‡û\՞Ǖg²1Žû5V7 !àöšm° c`ܬøÇìµÒ'P"?…´Ö,"§^•õލsÔ)6˜sæéÍR¼ ò|Sl”‹7 nPW Gòú÷½§O¯‡„l¡kSÞŒr½PÊ@æ¢pŽ-mÿ #Ÿ˜Àº¶Áä¦;ïÔæ$1££`“Õ>„—·ž)ßð³ñ#Ï Ô$¶œ‰ÊE‹À;÷º ¯«P:Ñ”8–IÊtpÞ3ª“>ê“þës4ò2OÏÕ­±zô†Õ§‰.÷ä¸;¿˜“'œ›žª}«Œ{ª±Ì 9ÔóÞÕ‡0 $íWV3Üì¬ —@kÝ4@¿r¼±½¬™›?øØæ´'Áé®CË3-g$˜ö‡×auÚi´Žp/êÛ æF›Ú2v‹ã¿¿,nB1̨ƃqÞa5͝@&Æû“él÷ \C²½UÍc ¯k×¢U ÖéQå™—-r wô ÞÏ<Ò=&=ÿ Ôê Òêˈt,i—;LîÜ á¸*ÚÃ1$êL•LÍ <É)ýÐà’ ;F™{ƒ™˜€&'}‚ãÄK`¡ÞT@I;®žZóè‚s’7®°›+§O­Åq©é»²9<Ô J ¼9O’HL»Ùïì¸rk¼Ž_ý‘TŸu[²ßÚŒ·ü÷B%¯E ŸÔX5êO´ Ç•€’I0 ÉJX` ñ¹õ%;µŸD‘«´€àwÒ™U ûئžÖö\×®×´8 ½‡ºÐÆÓ§?Àkmœ=;d5*@-ì0F Rªýš[Ü6âö̃ڸr*KA9· u*µæ£?U¸Âêí†8@¦X4 e-ò„0s{ HâUpU?¼mñRa°®a%Ð'tÉ×’\¾ÊÉ]t›h>·(Ë@R¼¡Ãt h}’O÷au<+nT…Ö…MӐ??Óe95 q>í/;&JSû °¯ÊéÞ øƒ*Ã2½Ài&:nôUl=¾¿5eˆ3”ñc|Ú2V”>„»&eE;«ÚäC p¢Û úy 9š[ŒÌx¼擼A&DåÒ¯ˆ¤ÀÌ;"˜ ÏQä¸åhÊ}Ûq«Û0WžÒ|»€ø®öCm5•\ÇÀ§Pe3£]0ÃàLDÉ‰1øªxjgwT‚÷¿LΨK‹›ùs—xˆÜ±µ kæ¸f‰‰ÜGk/LÛØ6d9ò¶ùA{ƒA3š/¬D¬khÓk‰`˜"㯒r¿±Óã jx‡°e}<Ñø\3y:'À•/h½Í€Ç4~g ?Û(¼]v‘ªlKÎâ~?O‚W%{Ì:“'©úNq¾›úo(X’¥¯ˆ nFê{Ç€ü?º'ë ø‹ì Þ09ŒÌç9Æ —ËC`j@ÓÄ(+a‹un¸#ÂꟋ{K`‘ÑÍÍ'à´»/Û,KW;Þ4²þð ï Nm|~fGÏ(…³Ã)«1ö­Õ ¥‡¨©ƒÃ™ü-s=à=U66Ï«Ýc蓦W¹íž®›nÔ%êÇìŒ<#Ü×84ån®Ð ÒåOC` ñânÑs‡¢ç 1õ%Îhì½Ã½® e:ݼUZo™`  ÅZŸŒÊ«ê1ÏÄo$q¹Þ€©ˆhÐÉä¯ñ[!…Ú˜àJ:x2$Íß&PåT£6ç— ‡Í*4Ýšçjÿ ‰É nófÐ ó(L5C•åÆ\rMÒ@ò }y-W}™üýVù—ú¢=Ù”c®‘< M ž ´Phr ¦©TD ‘ù.$´÷O‡‘V2Æò.=IUŒ=ž‡â¬i™aþÓåÙ?òUø'ØÖ•.~* šTŒ!•-×áºTâ®ä#õü'´ eýlYÅÓeÕKÂrT"CÚ@u!Óxƒ{š3€}1¿(r}%«nËamjÑ%ÑNEò v ˜à  σöK³,*º.àzù¨™Ó ÚçâU¦*¿ 9{%Ö¹ njûdaXöb) kÛÆ±ûÓ\°M7ˆÂ=û›ç¿Ã‚­V»Cg–8ÙêE- j)k$º`Ã-ùEýeBÆÇ]c¡°ñty&Òd0nõ'¡W+ƒ*|–øµFa\GQªEAÔp5\Ǽ·¼Ç8·õ -â§Ú[ ‡ uZeÖ 3}×d'+¹:ð+K†Û®s!Ï$úe€<Û”x)1»a­¡LC]¸µík…ÚàA»AYº{†ªS[¦5HÒ7ù --,ísòDØ€èk ÞÀîÜ ò@â( ËNˆë›4ô½•/¦o‡€Û7 ê•ÆêòðÜy'Án½µ á˜ݦ ndeo…[ì¶Ê,¥R³Ä=À±—–ß;£™´ñSâ*g§”ïaið‘Jå~™ÓÞ ß³Õ¢»8x埒²52>AÊb&-÷\7´éÄù€T˜,w;3{ï˜k…à¹ÄqÀ«œ{€\ ˆ¾[´¨јr &Úé„Ívˆ±8†¿]|¬ņ4I×pÞS1ÈÖz‰#Ìv‡G!YNògñ:màTz¢Ý1ô©^O=~ë|5Bã™ç•¼µõ•bÆ@úÕS¬ÈŒ#¬zünrŸ û” Z²•èðV"ÁHÚý©wÝ €7¼Ìu1hÑa3Éä û f$o¿É ™Ú›ÝçnpÒ3äÌ3†Í§,Äï]$‰/pê †«À¼¸e9­Æê_C]žƒ·ý·frÁN«, E=›Çq -‰öŒ:aÏ¿±í&£Í:-} 84‘ÿ eƒQÑeëSsuiA ³g㟥ú£?ÿ ʼn*”“÷aühe:ÊWa@ÒÞk±eØ] F Ô—r.åä˜ @ö¥ªZoÐýYL·¥S²G/‡ñ <~*ZÆ´è>JlòàÛÆ½ÿ 窘ìGN¢:I®KšJp/`íIÁÀõ#Ä-€ö­šµŒoF4|ÆQØÆ@Ì|£Ô…¢À{9˜è½Üó›€ôYÒÎYsið;ís¤€à²ˆ‚4qÉVŒI$ ‰"° æµ8cXGjœˏ¡Aâý•ËÜ¢ûï e·çLx']á"oÅÎê3¯Ç—¹”ó0nå‚âg{Œñ> S´˜îè°g238‚ãköÝfÚd´6Ò€;ò÷±¢™¼›º ¢Æ'¥Ðx'e¬ç ]bÈÆV¢ó‹kýBO ðÊâ$Ÿ!×T 3Mýמ žìٍàÌü‘8÷€àæØ8æ©6‰©L´«…oãpð„~Çk‰!ñ;‹”ÛžÍ àž±z Ÿôû øŸÝužÏ;ÿ #|u6™Þ¬ÚˆÐõA4¶â|ôl|Ê2ŽÇ¤ÝÅÇY.<#Aí.k§hóF‚”Y; M½Ö4hŸ4&›­¿tès´%FìL¥£Ãk‰ÇT¤haÁ¤ÚxfÉ`ÑìË›>i 3t‚:,–+^÷´–{Û–Nxi"x‘Ûg î¨>¥Õ܁ùZH,2Û“:8xÊ¢Çí9.É-Ìâã-=çjwµS˜dütžçwýGòú®®ûº_ˆýx$–¡ãøO EÚÛÏ÷R„×w+3£Á£öUMyR²¹âŒ°š›¸Ñãò9§Ó_Dl+Ùßc›úšGÅÌc†Ž!Ko=¶.‘Îÿ c²(2®V mª.ÿ ¹B›¹å ù„öŸSV>™ü¯$y:G¢Z×àøúdî¹û­·ýÇ´:•c LÍõi_‹ö+ÎæGÊè>OŠ•äž´§Þ{X}¨1ÚTc›»Qþ•êô°t¿OP?eæ~É{5]•ÙR£r5†nZ\ã@ &îJõ ¾àC°þV>fé¥/ü5ñÊIº_é5 ;e­h<@ Ä&æÃëE%;X,ÒãÆÞ`Oò¦kŸm#˜!ÀyÄ¢| óLšò¥Ä` ¶R=|ÈCâh5ò3DˆïF†ðÒ#ÅìÛœ?¸yhBãœí ZxßÎÄhºRK„`Þödvײ™ÀÈÑÒgŒuY w³%†ƒÓzõ ÖÏp‚dH®¦A´ù§»ÓÇMæ~)ˆð‡û:ù&Ä •vGD´À n ݇¼Ö8Fö óáà£~Ë¥x`oK|Ä?fxiØü%pìR>éò+Û±éÎ>núlFŤ'tq8LZÏvÃ?„¡ß±È⽆¯³íü@x|PöUäèØã¡ð‚ŒAìÏ"vÍwóŸÍ{ ý0.z È•Ö{,N¡£¡ŸKÕÙž>Ýœþ ÍÀ°<×EA!Å‚D™IúOÍ¡>ôG}Â` ÍßkÜL™Ž Þð™ {IøF²¹òQ3&!ÃÂÞz.d&Ï-sH¸,Ôõ˜ŽP€ 77ˆÝ¼ÊëÜw =cÕ Ú,ØÐ5ÎYÐ)ì´öœgŒ[¤ßv㙑8心>h]§µháYš£²ºÑ.{Ï7Sð•?´~×SÃKýJÛ˜ ™Íäiúu<µX¶1õ^kâçIÑ£sZ4h>j*ÔšD:4­¿_ ÷¸ Õxæÿ ¸?Mù _•­ÊÐ ä ÷ý ÑwL œ­ïnTkÛUÍN©ë:¦fV ¶ÜÔÜMªÅâA½–¿R×TXš-%iTÊT•‡Ù‚JôϐZxWÑè‰f‰òG º ×Õû2aZ7OU3[“×AT–ÞŒ…-‘¤”Ì ì&(ˆ¿­•ƒkï’:ðY¦W‘ Å)“†‘˜³Åtcø˜ñTÂwÚÇ4|üLÇªí–v- qˆèU qPE.†â‘˜µ Æ,ÐÅs]8¾„oúÑ i>ÜxxÈó)ƒ ´æÁâØ$À‰vžŸf$Ž |ãw;ÀÁIJ»b` {¦Ó¤Ú$©YÀ‘n@Óïž«9J¼êG m¤ ܯ¹ÌW4€ÐÒÅÛ‡#褕Ÿn-?í|с¥÷Ú¹¬'´ÞÜ9ÓK `hê£SÄSà?7—Wí_´…óB›»:=Ãïq`<8ñÓŒÑlú2d¬ê³£hÖ[l|$vÝro~'R®‰§°ñmY ͧäP |PUª¹·:3Œ[Û{Xÿ ºâ@‚W–Äé u‚ ¯´*=íή.pûÒdt @G‰¬ s¸ ëÉücr ÞæÑ¨Ê@>¤¢Ö±. Þ'¯°ÌME[YéïĵÂCå½ Ué©Áû'Ê9%eÔðNU”ë‘ÌsD3/®+UI˜9h.WC”빓$#:pz:YÓ ¿xž* ³$Í +$kñAŠ‹†¢ Uê>¸)_š¬÷©ßAÂÔb9ÇU ¯¾á•9¯ÏÏ÷O÷¼¼Fähal1‰3Ì[Ïr•´UCksNÐ] R‘¸¥H+§Šé†c©vÖÞ0iÓ76s†î!§=ß ¼~Ô'°Ãmäoäš³ªøi1úÉ)³yV8 CLÄØÁ‘WYïi€H6ÖÑiámø^ÈY´°Ñ7¥Û*—Ñ©L«Qƒï—Ùrÿ ›£Ð*š¸ˆL©ˆ$ˆ ÷¾D§9È®«qbqC)–ˆïv´çñsÑVT­Ø, <àïºÀO«Jý·õ àfPìð .wFšir´þ’2_Y *Æ€x\« ì€9š@ Ž|F⇥ˆkZ@hÖÄ0t¿-<“‹qµ¾*ZL¤Ú)&BJpÓF5=$„at*Zš$’ÑtdûÝRI1 2މ$€$I$#‰SÞ’Hë¬ï;Á$¡t$’`<(ñÇt)$‡Ð.Êf¢X’Kt=Éé$‚ˆªè¢oÝëòI%Rgcª÷ŠyI%¡‰ÿ !ñ)´õ $¤ Ô’IIGÿÙ/** * @output wp-includes/js/customize-preview-widgets.js */ /* global _wpWidgetCustomizerPreviewSettings */ /** * Handles the initialization, refreshing and rendering of widget partials and sidebar widgets. * * @since 4.5.0 * * @namespace wp.customize.widgetsPreview * * @param {jQuery} $ The jQuery object. * @param {Object} _ The utilities library. * @param {Object} wp Current WordPress environment instance. * @param {Object} api Information from the API. * * @return {Object} Widget-related variables. */ wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function( $, _, wp, api ) { var self; self = { renderedSidebars: {}, renderedWidgets: {}, registeredSidebars: [], registeredWidgets: {}, widgetSelectors: [], preview: null, l10n: { widgetTooltip: '' }, selectiveRefreshableWidgets: {} }; /** * Initializes the widgets preview. * * @since 4.5.0 * * @memberOf wp.customize.widgetsPreview * * @return {void} */ self.init = function() { var self = this; self.preview = api.preview; if ( ! _.isEmpty( self.selectiveRefreshableWidgets ) ) { self.addPartials(); } self.buildWidgetSelectors(); self.highlightControls(); self.preview.bind( 'highlight-widget', self.highlightWidget ); api.preview.bind( 'active', function() { self.highlightControls(); } ); /* * Refresh a partial when the controls pane requests it. This is used currently just by the * Gallery widget so that when an attachment's caption is updated in the media modal, * the widget in the preview will then be refreshed to show the change. Normally doing this * would not be necessary because all of the state should be contained inside the changeset, * as everything done in the Customizer should not make a change to the site unless the * changeset itself is published. Attachments are a current exception to this rule. * For a proposal to include attachments in the customized state, see #37887. */ api.preview.bind( 'refresh-widget-partial', function( widgetId ) { var partialId = 'widget[' + widgetId + ']'; if ( api.selectiveRefresh.partial.has( partialId ) ) { api.selectiveRefresh.partial( partialId ).refresh(); } else if ( self.renderedWidgets[ widgetId ] ) { api.preview.send( 'refresh' ); // Fallback in case theme does not support 'customize-selective-refresh-widgets'. } } ); }; self.WidgetPartial = api.selectiveRefresh.Partial.extend(/** @lends wp.customize.widgetsPreview.WidgetPartial.prototype */{ /** * Represents a partial widget instance. * * @since 4.5.0 * * @constructs * @augments wp.customize.selectiveRefresh.Partial * * @alias wp.customize.widgetsPreview.WidgetPartial * @memberOf wp.customize.widgetsPreview * * @param {string} id The partial's ID. * @param {Object} options Options used to initialize the partial's * instance. * @param {Object} options.params The options parameters. */ initialize: function( id, options ) { var partial = this, matches; matches = id.match( /^widget\[(.+)]$/ ); if ( ! matches ) { throw new Error( 'Illegal id for widget partial.' ); } partial.widgetId = matches[1]; partial.widgetIdParts = self.parseWidgetId( partial.widgetId ); options = options || {}; options.params = _.extend( { settings: [ self.getWidgetSettingId( partial.widgetId ) ], containerInclusive: true }, options.params || {} ); api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options ); }, /** * Refreshes the widget partial. * * @since 4.5.0 * * @return {Promise|void} Either a promise postponing the refresh, or void. */ refresh: function() { var partial = this, refreshDeferred; if ( ! self.selectiveRefreshableWidgets[ partial.widgetIdParts.idBase ] ) { refreshDeferred = $.Deferred(); refreshDeferred.reject(); partial.fallback(); return refreshDeferred.promise(); } else { return api.selectiveRefresh.Partial.prototype.refresh.call( partial ); } }, /** * Sends the widget-updated message to the parent so the spinner will get * removed from the widget control. * * @inheritDoc * @param {wp.customize.selectiveRefresh.Placement} placement The placement * function. * * @return {void} */ renderContent: function( placement ) { var partial = this; if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) { api.preview.send( 'widget-updated', partial.widgetId ); api.selectiveRefresh.trigger( 'widget-updated', partial ); } } }); self.SidebarPartial = api.selectiveRefresh.Partial.extend(/** @lends wp.customize.widgetsPreview.SidebarPartial.prototype */{ /** * Represents a partial widget area. * * @since 4.5.0 * * @class * @augments wp.customize.selectiveRefresh.Partial * * @memberOf wp.customize.widgetsPreview * @alias wp.customize.widgetsPreview.SidebarPartial * * @param {string} id The partial's ID. * @param {Object} options Options used to initialize the partial's instance. * @param {Object} options.params The options parameters. */ initialize: function( id, options ) { var partial = this, matches; matches = id.match( /^sidebar\[(.+)]$/ ); if ( ! matches ) { throw new Error( 'Illegal id for sidebar partial.' ); } partial.sidebarId = matches[1]; options = options || {}; options.params = _.extend( { settings: [ 'sidebars_widgets[' + partial.sidebarId + ']' ] }, options.params || {} ); api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options ); if ( ! partial.params.sidebarArgs ) { throw new Error( 'The sidebarArgs param was not provided.' ); } if ( partial.params.settings.length > 1 ) { throw new Error( 'Expected SidebarPartial to only have one associated setting' ); } }, /** * Sets up the partial. * * @since 4.5.0 * * @return {void} */ ready: function() { var sidebarPartial = this; // Watch for changes to the sidebar_widgets setting. _.each( sidebarPartial.settings(), function( settingId ) { api( settingId ).bind( _.bind( sidebarPartial.handleSettingChange, sidebarPartial ) ); } ); // Trigger an event for this sidebar being updated whenever a widget inside is rendered. api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) { var isAssignedWidgetPartial = ( placement.partial.extended( self.WidgetPartial ) && ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), placement.partial.widgetId ) ) ); if ( isAssignedWidgetPartial ) { api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial ); } } ); // Make sure that a widget partial has a container in the DOM prior to a refresh. api.bind( 'change', function( widgetSetting ) { var widgetId, parsedId; parsedId = self.parseWidgetSettingId( widgetSetting.id ); if ( ! parsedId ) { return; } widgetId = parsedId.idBase; if ( parsedId.number ) { widgetId += '-' + String( parsedId.number ); } if ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), widgetId ) ) { sidebarPartial.ensureWidgetPlacementContainers( widgetId ); } } ); }, /** * Gets the before/after boundary nodes for all instances of this sidebar * (usually one). * * Note that TreeWalker is not implemented in IE8. * * @since 4.5.0 * * @return {Array.<{before: Comment, after: Comment, instanceNumber: number}>} * An array with an object for each sidebar instance, containing the * node before and after the sidebar instance and its instance number. */ findDynamicSidebarBoundaryNodes: function() { var partial = this, regExp, boundaryNodes = {}, recursiveCommentTraversal; regExp = /^(dynamic_sidebar_before|dynamic_sidebar_after):(.+):(\d+)$/; recursiveCommentTraversal = function( childNodes ) { _.each( childNodes, function( node ) { var matches; if ( 8 === node.nodeType ) { matches = node.nodeValue.match( regExp ); if ( ! matches || matches[2] !== partial.sidebarId ) { return; } if ( _.isUndefined( boundaryNodes[ matches[3] ] ) ) { boundaryNodes[ matches[3] ] = { before: null, after: null, instanceNumber: parseInt( matches[3], 10 ) }; } if ( 'dynamic_sidebar_before' === matches[1] ) { boundaryNodes[ matches[3] ].before = node; } else { boundaryNodes[ matches[3] ].after = node; } } else if ( 1 === node.nodeType ) { recursiveCommentTraversal( node.childNodes ); } } ); }; recursiveCommentTraversal( document.body.childNodes ); return _.values( boundaryNodes ); }, /** * Gets the placements for this partial. * * @since 4.5.0 * * @return {Array} An array containing placement objects for each of the * dynamic sidebar boundary nodes. */ placements: function() { var partial = this; return _.map( partial.findDynamicSidebarBoundaryNodes(), function( boundaryNodes ) { return new api.selectiveRefresh.Placement( { partial: partial, container: null, startNode: boundaryNodes.before, endNode: boundaryNodes.after, context: { instanceNumber: boundaryNodes.instanceNumber } } ); } ); }, /** * Get the list of widget IDs associated with this widget area. * * @since 4.5.0 * * @throws {Error} If there's no settingId. * @throws {Error} If the setting doesn't exist in the API. * @throws {Error} If the API doesn't pass an array of widget IDs. * * @return {Array} A shallow copy of the array containing widget IDs. */ getWidgetIds: function() { var sidebarPartial = this, settingId, widgetIds; settingId = sidebarPartial.settings()[0]; if ( ! settingId ) { throw new Error( 'Missing associated setting.' ); } if ( ! api.has( settingId ) ) { throw new Error( 'Setting does not exist.' ); } widgetIds = api( settingId ).get(); if ( ! _.isArray( widgetIds ) ) { throw new Error( 'Expected setting to be array of widget IDs' ); } return widgetIds.slice( 0 ); }, /** * Reflows widgets in the sidebar, ensuring they have the proper position in the * DOM. * * @since 4.5.0 * * @return {Array.} List of placements * that were reflowed. */ reflowWidgets: function() { var sidebarPartial = this, sidebarPlacements, widgetIds, widgetPartials, sortedSidebarContainers = []; widgetIds = sidebarPartial.getWidgetIds(); sidebarPlacements = sidebarPartial.placements(); widgetPartials = {}; _.each( widgetIds, function( widgetId ) { var widgetPartial = api.selectiveRefresh.partial( 'widget[' + widgetId + ']' ); if ( widgetPartial ) { widgetPartials[ widgetId ] = widgetPartial; } } ); _.each( sidebarPlacements, function( sidebarPlacement ) { var sidebarWidgets = [], needsSort = false, thisPosition, lastPosition = -1; // Gather list of widget partial containers in this sidebar, and determine if a sort is needed. _.each( widgetPartials, function( widgetPartial ) { _.each( widgetPartial.placements(), function( widgetPlacement ) { if ( sidebarPlacement.context.instanceNumber === widgetPlacement.context.sidebar_instance_number ) { thisPosition = widgetPlacement.container.index(); sidebarWidgets.push( { partial: widgetPartial, placement: widgetPlacement, position: thisPosition } ); if ( thisPosition < lastPosition ) { needsSort = true; } lastPosition = thisPosition; } } ); } ); if ( needsSort ) { _.each( sidebarWidgets, function( sidebarWidget ) { sidebarPlacement.endNode.parentNode.insertBefore( sidebarWidget.placement.container[0], sidebarPlacement.endNode ); // @todo Rename partial-placement-moved? api.selectiveRefresh.trigger( 'partial-content-moved', sidebarWidget.placement ); } ); sortedSidebarContainers.push( sidebarPlacement ); } } ); if ( sortedSidebarContainers.length > 0 ) { api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial ); } return sortedSidebarContainers; }, /** * Makes sure there is a widget instance container in this sidebar for the given * widget ID. * * @since 4.5.0 * * @param {string} widgetId The widget ID. * * @return {wp.customize.selectiveRefresh.Partial} The widget instance partial. */ ensureWidgetPlacementContainers: function( widgetId ) { var sidebarPartial = this, widgetPartial, wasInserted = false, partialId = 'widget[' + widgetId + ']'; widgetPartial = api.selectiveRefresh.partial( partialId ); if ( ! widgetPartial ) { widgetPartial = new self.WidgetPartial( partialId, { params: {} } ); } // Make sure that there is a container element for the widget in the sidebar, if at least a placeholder. _.each( sidebarPartial.placements(), function( sidebarPlacement ) { var foundWidgetPlacement, widgetContainerElement; foundWidgetPlacement = _.find( widgetPartial.placements(), function( widgetPlacement ) { return ( widgetPlacement.context.sidebar_instance_number === sidebarPlacement.context.instanceNumber ); } ); if ( foundWidgetPlacement ) { return; } widgetContainerElement = $( sidebarPartial.params.sidebarArgs.before_widget.replace( /%1\$s/g, widgetId ).replace( /%2\$s/g, 'widget' ) + sidebarPartial.params.sidebarArgs.after_widget ); // Handle rare case where before_widget and after_widget are empty. if ( ! widgetContainerElement[0] ) { return; } widgetContainerElement.attr( 'data-customize-partial-id', widgetPartial.id ); widgetContainerElement.attr( 'data-customize-partial-type', 'widget' ); widgetContainerElement.attr( 'data-customize-widget-id', widgetId ); /* * Make sure the widget container element has the customize-container context data. * The sidebar_instance_number is used to disambiguate multiple instances of the * same sidebar are rendered onto the template, and so the same widget is embedded * multiple times. */ widgetContainerElement.data( 'customize-partial-placement-context', { 'sidebar_id': sidebarPartial.sidebarId, 'sidebar_instance_number': sidebarPlacement.context.instanceNumber } ); sidebarPlacement.endNode.parentNode.insertBefore( widgetContainerElement[0], sidebarPlacement.endNode ); wasInserted = true; } ); api.selectiveRefresh.partial.add( widgetPartial ); if ( wasInserted ) { sidebarPartial.reflowWidgets(); } return widgetPartial; }, /** * Handles changes to the sidebars_widgets[] setting. * * @since 4.5.0 * * @param {Array} newWidgetIds New widget IDs. * @param {Array} oldWidgetIds Old widget IDs. * * @return {void} */ handleSettingChange: function( newWidgetIds, oldWidgetIds ) { var sidebarPartial = this, needsRefresh, widgetsRemoved, widgetsAdded, addedWidgetPartials = []; needsRefresh = ( ( oldWidgetIds.length > 0 && 0 === newWidgetIds.length ) || ( newWidgetIds.length > 0 && 0 === oldWidgetIds.length ) ); if ( needsRefresh ) { sidebarPartial.fallback(); return; } // Handle removal of widgets. widgetsRemoved = _.difference( oldWidgetIds, newWidgetIds ); _.each( widgetsRemoved, function( removedWidgetId ) { var widgetPartial = api.selectiveRefresh.partial( 'widget[' + removedWidgetId + ']' ); if ( widgetPartial ) { _.each( widgetPartial.placements(), function( placement ) { var isRemoved = ( placement.context.sidebar_id === sidebarPartial.sidebarId || ( placement.context.sidebar_args && placement.context.sidebar_args.id === sidebarPartial.sidebarId ) ); if ( isRemoved ) { placement.container.remove(); } } ); } delete self.renderedWidgets[ removedWidgetId ]; } ); // Handle insertion of widgets. widgetsAdded = _.difference( newWidgetIds, oldWidgetIds ); _.each( widgetsAdded, function( addedWidgetId ) { var widgetPartial = sidebarPartial.ensureWidgetPlacementContainers( addedWidgetId ); addedWidgetPartials.push( widgetPartial ); self.renderedWidgets[ addedWidgetId ] = true; } ); _.each( addedWidgetPartials, function( widgetPartial ) { widgetPartial.refresh(); } ); api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial ); }, /** * Refreshes the sidebar partial. * * Note that the meat is handled in handleSettingChange because it has the * context of which widgets were removed. * * @since 4.5.0 * * @return {Promise} A promise postponing the refresh. */ refresh: function() { var partial = this, deferred = $.Deferred(); deferred.fail( function() { partial.fallback(); } ); if ( 0 === partial.placements().length ) { deferred.reject(); } else { _.each( partial.reflowWidgets(), function( sidebarPlacement ) { api.selectiveRefresh.trigger( 'partial-content-rendered', sidebarPlacement ); } ); deferred.resolve(); } return deferred.promise(); } }); api.selectiveRefresh.partialConstructor.sidebar = self.SidebarPartial; api.selectiveRefresh.partialConstructor.widget = self.WidgetPartial; /** * Adds partials for the registered widget areas (sidebars). * * @since 4.5.0 * * @return {void} */ self.addPartials = function() { _.each( self.registeredSidebars, function( registeredSidebar ) { var partial, partialId = 'sidebar[' + registeredSidebar.id + ']'; partial = api.selectiveRefresh.partial( partialId ); if ( ! partial ) { partial = new self.SidebarPartial( partialId, { params: { sidebarArgs: registeredSidebar } } ); api.selectiveRefresh.partial.add( partial ); } } ); }; /** * Calculates the selector for the sidebar's widgets based on the registered * sidebar's info. * * @memberOf wp.customize.widgetsPreview * * @since 3.9.0 * * @return {void} */ self.buildWidgetSelectors = function() { var self = this; $.each( self.registeredSidebars, function( i, sidebar ) { var widgetTpl = [ sidebar.before_widget, sidebar.before_title, sidebar.after_title, sidebar.after_widget ].join( '' ), emptyWidget, widgetSelector, widgetClasses; emptyWidget = $( widgetTpl ); widgetSelector = emptyWidget.prop( 'tagName' ) || ''; widgetClasses = emptyWidget.prop( 'className' ) || ''; // Prevent a rare case when before_widget, before_title, after_title and after_widget is empty. if ( ! widgetClasses ) { return; } // Remove class names that incorporate the string formatting placeholders %1$s and %2$s. widgetClasses = widgetClasses.replace( /\S*%[12]\$s\S*/g, '' ); widgetClasses = widgetClasses.replace( /^\s+|\s+$/g, '' ); if ( widgetClasses ) { widgetSelector += '.' + widgetClasses.split( /\s+/ ).join( '.' ); } self.widgetSelectors.push( widgetSelector ); }); }; /** * Highlights the widget on widget updates or widget control mouse overs. * * @memberOf wp.customize.widgetsPreview * * @since 3.9.0 * @param {string} widgetId ID of the widget. * * @return {void} */ self.highlightWidget = function( widgetId ) { var $body = $( document.body ), $widget = $( '#' + widgetId ); $body.find( '.widget-customizer-highlighted-widget' ).removeClass( 'widget-customizer-highlighted-widget' ); $widget.addClass( 'widget-customizer-highlighted-widget' ); setTimeout( function() { $widget.removeClass( 'widget-customizer-highlighted-widget' ); }, 500 ); }; /** * Shows a title and highlights widgets on hover. On shift+clicking focuses the * widget control. * * @memberOf wp.customize.widgetsPreview * * @since 3.9.0 * * @return {void} */ self.highlightControls = function() { var self = this, selector = this.widgetSelectors.join( ',' ); // Skip adding highlights if not in the customizer preview iframe. if ( ! api.settings.channel ) { return; } $( selector ).attr( 'title', this.l10n.widgetTooltip ); // Highlights widget when entering the widget editor. $( document ).on( 'mouseenter', selector, function() { self.preview.send( 'highlight-widget-control', $( this ).prop( 'id' ) ); }); // Open expand the widget control when shift+clicking the widget element. $( document ).on( 'click', selector, function( e ) { if ( ! e.shiftKey ) { return; } e.preventDefault(); self.preview.send( 'focus-widget-control', $( this ).prop( 'id' ) ); }); }; /** * Parses a widget ID. * * @memberOf wp.customize.widgetsPreview * * @since 4.5.0 * * @param {string} widgetId The widget ID. * * @return {{idBase: string, number: number|null}} An object containing the idBase * and number of the parsed widget ID. */ self.parseWidgetId = function( widgetId ) { var matches, parsed = { idBase: '', number: null }; matches = widgetId.match( /^(.+)-(\d+)$/ ); if ( matches ) { parsed.idBase = matches[1]; parsed.number = parseInt( matches[2], 10 ); } else { parsed.idBase = widgetId; // Likely an old single widget. } return parsed; }; /** * Parses a widget setting ID. * * @memberOf wp.customize.widgetsPreview * * @since 4.5.0 * * @param {string} settingId Widget setting ID. * * @return {{idBase: string, number: number|null}|null} Either an object containing the idBase * and number of the parsed widget setting ID, * or null. */ self.parseWidgetSettingId = function( settingId ) { var matches, parsed = { idBase: '', number: null }; matches = settingId.match( /^widget_([^\[]+?)(?:\[(\d+)])?$/ ); if ( ! matches ) { return null; } parsed.idBase = matches[1]; if ( matches[2] ) { parsed.number = parseInt( matches[2], 10 ); } return parsed; }; /** * Converts a widget ID into a Customizer setting ID. * * @memberOf wp.customize.widgetsPreview * * @since 4.5.0 * * @param {string} widgetId The widget ID. * * @return {string} The setting ID. */ self.getWidgetSettingId = function( widgetId ) { var parsed = this.parseWidgetId( widgetId ), settingId; settingId = 'widget_' + parsed.idBase; if ( parsed.number ) { settingId += '[' + String( parsed.number ) + ']'; } return settingId; }; api.bind( 'preview-ready', function() { $.extend( self, _wpWidgetCustomizerPreviewSettings ); self.init(); }); return self; })( jQuery, _, wp, wp.customize );