13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/dist/js/bundle.js:
--------------------------------------------------------------------------------
1 | /*! For license information please see bundle.js.LICENSE.txt */
2 | (()=>{"use strict";var t={d:(e,i)=>{for(var n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:i[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}};(()=>{var e={};t.r(e),t.d(e,{afterMain:()=>w,afterRead:()=>b,afterWrite:()=>T,applyStyles:()=>D,arrow:()=>G,auto:()=>r,basePlacements:()=>a,beforeMain:()=>v,beforeRead:()=>g,beforeWrite:()=>A,bottom:()=>n,clippingParents:()=>h,computeStyles:()=>et,createPopper:()=>Dt,createPopperBase:()=>St,createPopperLite:()=>$t,detectOverflow:()=>_t,end:()=>c,eventListeners:()=>nt,flip:()=>bt,hide:()=>wt,left:()=>o,main:()=>y,modifierPhases:()=>C,offset:()=>At,placements:()=>m,popper:()=>d,popperGenerator:()=>Lt,popperOffsets:()=>Et,preventOverflow:()=>Tt,read:()=>_,reference:()=>f,right:()=>s,start:()=>l,top:()=>i,variationPlacements:()=>p,viewport:()=>u,write:()=>E});var i="top",n="bottom",s="right",o="left",r="auto",a=[i,n,s,o],l="start",c="end",h="clippingParents",u="viewport",d="popper",f="reference",p=a.reduce((function(t,e){return t.concat([e+"-"+l,e+"-"+c])}),[]),m=[].concat(a,[r]).reduce((function(t,e){return t.concat([e,e+"-"+l,e+"-"+c])}),[]),g="beforeRead",_="read",b="afterRead",v="beforeMain",y="main",w="afterMain",A="beforeWrite",E="write",T="afterWrite",C=[g,_,b,v,y,w,A,E,T];function O(t){return t?(t.nodeName||"").toLowerCase():null}function x(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function k(t){return t instanceof x(t).Element||t instanceof Element}function L(t){return t instanceof x(t).HTMLElement||t instanceof HTMLElement}function S(t){return"undefined"!=typeof ShadowRoot&&(t instanceof x(t).ShadowRoot||t instanceof ShadowRoot)}const D={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];L(s)&&O(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});L(n)&&O(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function $(t){return t.split("-")[0]}var I=Math.max,N=Math.min,P=Math.round;function j(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function M(){return!/^((?!chrome|android).)*safari/i.test(j())}function F(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&L(t)&&(s=t.offsetWidth>0&&P(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&P(n.height)/t.offsetHeight||1);var r=(k(t)?x(t):window).visualViewport,a=!M()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,u=n.height/o;return{width:h,height:u,top:c,right:l+h,bottom:c+u,left:l,x:l,y:c}}function H(t){var e=F(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function W(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&S(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function B(t){return x(t).getComputedStyle(t)}function z(t){return["table","td","th"].indexOf(O(t))>=0}function R(t){return((k(t)?t.ownerDocument:t.document)||window.document).documentElement}function q(t){return"html"===O(t)?t:t.assignedSlot||t.parentNode||(S(t)?t.host:null)||R(t)}function V(t){return L(t)&&"fixed"!==B(t).position?t.offsetParent:null}function K(t){for(var e=x(t),i=V(t);i&&z(i)&&"static"===B(i).position;)i=V(i);return i&&("html"===O(i)||"body"===O(i)&&"static"===B(i).position)?e:i||function(t){var e=/firefox/i.test(j());if(/Trident/i.test(j())&&L(t)&&"fixed"===B(t).position)return null;var i=q(t);for(S(i)&&(i=i.host);L(i)&&["html","body"].indexOf(O(i))<0;){var n=B(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Q(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function X(t,e,i){return I(t,N(e,i))}function Y(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function U(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const G={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,r=t.state,l=t.name,c=t.options,h=r.elements.arrow,u=r.modifiersData.popperOffsets,d=$(r.placement),f=Q(d),p=[o,s].indexOf(d)>=0?"height":"width";if(h&&u){var m=function(t,e){return Y("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:U(t,a))}(c.padding,r),g=H(h),_="y"===f?i:o,b="y"===f?n:s,v=r.rects.reference[p]+r.rects.reference[f]-u[f]-r.rects.popper[p],y=u[f]-r.rects.reference[f],w=K(h),A=w?"y"===f?w.clientHeight||0:w.clientWidth||0:0,E=v/2-y/2,T=m[_],C=A-g[p]-m[b],O=A/2-g[p]/2+E,x=X(T,O,C),k=f;r.modifiersData[l]=((e={})[k]=x,e.centerOffset=x-O,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&W(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function J(t){return t.split("-")[1]}var Z={top:"auto",right:"auto",bottom:"auto",left:"auto"};function tt(t){var e,r=t.popper,a=t.popperRect,l=t.placement,h=t.variation,u=t.offsets,d=t.position,f=t.gpuAcceleration,p=t.adaptive,m=t.roundOffsets,g=t.isFixed,_=u.x,b=void 0===_?0:_,v=u.y,y=void 0===v?0:v,w="function"==typeof m?m({x:b,y}):{x:b,y};b=w.x,y=w.y;var A=u.hasOwnProperty("x"),E=u.hasOwnProperty("y"),T=o,C=i,O=window;if(p){var k=K(r),L="clientHeight",S="clientWidth";if(k===x(r)&&"static"!==B(k=R(r)).position&&"absolute"===d&&(L="scrollHeight",S="scrollWidth"),l===i||(l===o||l===s)&&h===c)C=n,y-=(g&&k===O&&O.visualViewport?O.visualViewport.height:k[L])-a.height,y*=f?1:-1;if(l===o||(l===i||l===n)&&h===c)T=s,b-=(g&&k===O&&O.visualViewport?O.visualViewport.width:k[S])-a.width,b*=f?1:-1}var D,$=Object.assign({position:d},p&&Z),I=!0===m?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:P(i*s)/s||0,y:P(n*s)/s||0}}({x:b,y},x(r)):{x:b,y};return b=I.x,y=I.y,f?Object.assign({},$,((D={})[C]=E?"0":"",D[T]=A?"0":"",D.transform=(O.devicePixelRatio||1)<=1?"translate("+b+"px, "+y+"px)":"translate3d("+b+"px, "+y+"px, 0)",D)):Object.assign({},$,((e={})[C]=E?y+"px":"",e[T]=A?b+"px":"",e.transform="",e))}const et={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:$(e.placement),variation:J(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,tt(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,tt(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var it={passive:!0};const nt={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=x(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,it)})),a&&l.addEventListener("resize",i.update,it),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,it)})),a&&l.removeEventListener("resize",i.update,it)}},data:{}};var st={left:"right",right:"left",bottom:"top",top:"bottom"};function ot(t){return t.replace(/left|right|bottom|top/g,(function(t){return st[t]}))}var rt={start:"end",end:"start"};function at(t){return t.replace(/start|end/g,(function(t){return rt[t]}))}function lt(t){var e=x(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ct(t){return F(R(t)).left+lt(t).scrollLeft}function ht(t){var e=B(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function ut(t){return["html","body","#document"].indexOf(O(t))>=0?t.ownerDocument.body:L(t)&&ht(t)?t:ut(q(t))}function dt(t,e){var i;void 0===e&&(e=[]);var n=ut(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=x(n),r=s?[o].concat(o.visualViewport||[],ht(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(dt(q(r)))}function ft(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function pt(t,e,i){return e===u?ft(function(t,e){var i=x(t),n=R(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=M();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+ct(t),y:l}}(t,i)):k(e)?function(t,e){var i=F(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):ft(function(t){var e,i=R(t),n=lt(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=I(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=I(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ct(t),l=-n.scrollTop;return"rtl"===B(s||i).direction&&(a+=I(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(R(t)))}function mt(t,e,i,n){var s="clippingParents"===e?function(t){var e=dt(q(t)),i=["absolute","fixed"].indexOf(B(t).position)>=0&&L(t)?K(t):t;return k(i)?e.filter((function(t){return k(t)&&W(t,i)&&"body"!==O(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=pt(t,i,n);return e.top=I(s.top,e.top),e.right=N(s.right,e.right),e.bottom=N(s.bottom,e.bottom),e.left=I(s.left,e.left),e}),pt(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}function gt(t){var e,r=t.reference,a=t.element,h=t.placement,u=h?$(h):null,d=h?J(h):null,f=r.x+r.width/2-a.width/2,p=r.y+r.height/2-a.height/2;switch(u){case i:e={x:f,y:r.y-a.height};break;case n:e={x:f,y:r.y+r.height};break;case s:e={x:r.x+r.width,y:p};break;case o:e={x:r.x-a.width,y:p};break;default:e={x:r.x,y:r.y}}var m=u?Q(u):null;if(null!=m){var g="y"===m?"height":"width";switch(d){case l:e[m]=e[m]-(r[g]/2-a[g]/2);break;case c:e[m]=e[m]+(r[g]/2-a[g]/2)}}return e}function _t(t,e){void 0===e&&(e={});var o=e,r=o.placement,l=void 0===r?t.placement:r,c=o.strategy,p=void 0===c?t.strategy:c,m=o.boundary,g=void 0===m?h:m,_=o.rootBoundary,b=void 0===_?u:_,v=o.elementContext,y=void 0===v?d:v,w=o.altBoundary,A=void 0!==w&&w,E=o.padding,T=void 0===E?0:E,C=Y("number"!=typeof T?T:U(T,a)),O=y===d?f:d,x=t.rects.popper,L=t.elements[A?O:y],S=mt(k(L)?L:L.contextElement||R(t.elements.popper),g,b,p),D=F(t.elements.reference),$=gt({reference:D,element:x,strategy:"absolute",placement:l}),I=ft(Object.assign({},x,$)),N=y===d?I:D,P={top:S.top-N.top+C.top,bottom:N.bottom-S.bottom+C.bottom,left:S.left-N.left+C.left,right:N.right-S.right+C.right},j=t.modifiersData.offset;if(y===d&&j){var M=j[l];Object.keys(P).forEach((function(t){var e=[s,n].indexOf(t)>=0?1:-1,o=[i,n].indexOf(t)>=0?"y":"x";P[t]+=M[o]*e}))}return P}const bt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,c=t.options,h=t.name;if(!e.modifiersData[h]._skip){for(var u=c.mainAxis,d=void 0===u||u,f=c.altAxis,g=void 0===f||f,_=c.fallbackPlacements,b=c.padding,v=c.boundary,y=c.rootBoundary,w=c.altBoundary,A=c.flipVariations,E=void 0===A||A,T=c.allowedAutoPlacements,C=e.options.placement,O=$(C),x=_||(O===C||!E?[ot(C)]:function(t){if($(t)===r)return[];var e=ot(t);return[at(t),e,at(e)]}(C)),k=[C].concat(x).reduce((function(t,i){return t.concat($(i)===r?function(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,l=i.flipVariations,c=i.allowedAutoPlacements,h=void 0===c?m:c,u=J(n),d=u?l?p:p.filter((function(t){return J(t)===u})):a,f=d.filter((function(t){return h.indexOf(t)>=0}));0===f.length&&(f=d);var g=f.reduce((function(e,i){return e[i]=_t(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[$(i)],e}),{});return Object.keys(g).sort((function(t,e){return g[t]-g[e]}))}(e,{placement:i,boundary:v,rootBoundary:y,padding:b,flipVariations:E,allowedAutoPlacements:T}):i)}),[]),L=e.rects.reference,S=e.rects.popper,D=new Map,I=!0,N=k[0],P=0;P=0,W=H?"width":"height",B=_t(e,{placement:j,boundary:v,rootBoundary:y,altBoundary:w,padding:b}),z=H?F?s:o:F?n:i;L[W]>S[W]&&(z=ot(z));var R=ot(z),q=[];if(d&&q.push(B[M]<=0),g&&q.push(B[z]<=0,B[R]<=0),q.every((function(t){return t}))){N=j,I=!1;break}D.set(j,q)}if(I)for(var V=function(t){var e=k.find((function(e){var i=D.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return N=e,"break"},K=E?3:1;K>0;K--){if("break"===V(K))break}e.placement!==N&&(e.modifiersData[h]._skip=!0,e.placement=N,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function vt(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function yt(t){return[i,s,n,o].some((function(e){return t[e]>=0}))}const wt={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=_t(e,{elementContext:"reference"}),a=_t(e,{altBoundary:!0}),l=vt(r,n),c=vt(a,s,o),h=yt(l),u=yt(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:u},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":u})}};const At={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,n=t.options,r=t.name,a=n.offset,l=void 0===a?[0,0]:a,c=m.reduce((function(t,n){return t[n]=function(t,e,n){var r=$(t),a=[o,i].indexOf(r)>=0?-1:1,l="function"==typeof n?n(Object.assign({},e,{placement:t})):n,c=l[0],h=l[1];return c=c||0,h=(h||0)*a,[o,s].indexOf(r)>=0?{x:h,y:c}:{x:c,y:h}}(n,e.rects,l),t}),{}),h=c[e.placement],u=h.x,d=h.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=u,e.modifiersData.popperOffsets.y+=d),e.modifiersData[r]=c}};const Et={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=gt({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}};const Tt={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,r=t.options,a=t.name,c=r.mainAxis,h=void 0===c||c,u=r.altAxis,d=void 0!==u&&u,f=r.boundary,p=r.rootBoundary,m=r.altBoundary,g=r.padding,_=r.tether,b=void 0===_||_,v=r.tetherOffset,y=void 0===v?0:v,w=_t(e,{boundary:f,rootBoundary:p,padding:g,altBoundary:m}),A=$(e.placement),E=J(e.placement),T=!E,C=Q(A),O="x"===C?"y":"x",x=e.modifiersData.popperOffsets,k=e.rects.reference,L=e.rects.popper,S="function"==typeof y?y(Object.assign({},e.rects,{placement:e.placement})):y,D="number"==typeof S?{mainAxis:S,altAxis:S}:Object.assign({mainAxis:0,altAxis:0},S),P=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,j={x:0,y:0};if(x){if(h){var M,F="y"===C?i:o,W="y"===C?n:s,B="y"===C?"height":"width",z=x[C],R=z+w[F],q=z-w[W],V=b?-L[B]/2:0,Y=E===l?k[B]:L[B],U=E===l?-L[B]:-k[B],G=e.elements.arrow,Z=b&&G?H(G):{width:0,height:0},tt=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},et=tt[F],it=tt[W],nt=X(0,k[B],Z[B]),st=T?k[B]/2-V-nt-et-D.mainAxis:Y-nt-et-D.mainAxis,ot=T?-k[B]/2+V+nt+it+D.mainAxis:U+nt+it+D.mainAxis,rt=e.elements.arrow&&K(e.elements.arrow),at=rt?"y"===C?rt.clientTop||0:rt.clientLeft||0:0,lt=null!=(M=null==P?void 0:P[C])?M:0,ct=z+ot-lt,ht=X(b?N(R,z+st-lt-at):R,z,b?I(q,ct):q);x[C]=ht,j[C]=ht-z}if(d){var ut,dt="x"===C?i:o,ft="x"===C?n:s,pt=x[O],mt="y"===O?"height":"width",gt=pt+w[dt],bt=pt-w[ft],vt=-1!==[i,o].indexOf(A),yt=null!=(ut=null==P?void 0:P[O])?ut:0,wt=vt?gt:pt-k[mt]-L[mt]-yt+D.altAxis,At=vt?pt+k[mt]+L[mt]-yt-D.altAxis:bt,Et=b&&vt?function(t,e,i){var n=X(t,e,i);return n>i?i:n}(wt,pt,At):X(b?wt:gt,pt,b?At:bt);x[O]=Et,j[O]=Et-pt}e.modifiersData[a]=j}},requiresIfExists:["offset"]};function Ct(t,e,i){void 0===i&&(i=!1);var n,s,o=L(e),r=L(e)&&function(t){var e=t.getBoundingClientRect(),i=P(e.width)/t.offsetWidth||1,n=P(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=R(e),l=F(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==O(e)||ht(a))&&(c=(n=e)!==x(n)&&L(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:lt(n)),L(e)?((h=F(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=ct(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function Ot(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var xt={placement:"bottom",modifiers:[],strategy:"absolute"};function kt(){for(var t=arguments.length,e=new Array(t),i=0;iIt.has(t)&&It.get(t).get(e)||null,remove(t,e){if(!It.has(t))return;const i=It.get(t);i.delete(e),0===i.size&&It.delete(t)}},Pt="transitionend",jt=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),Mt=t=>{t.dispatchEvent(new Event(Pt))},Ft=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),Ht=t=>Ft(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(jt(t)):null,Wt=t=>{if(!Ft(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},Bt=t=>!t||t.nodeType!==Node.ELEMENT_NODE||(!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled"))),zt=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?zt(t.parentNode):null},Rt=()=>{},qt=t=>{t.offsetHeight},Vt=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,Kt=[],Qt=()=>"rtl"===document.documentElement.dir,Xt=t=>{var e;e=()=>{const e=Vt();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(Kt.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of Kt)t()})),Kt.push(e)):e()},Yt=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,Ut=(t,e,i=!0)=>{if(!i)return void Yt(t);const n=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let s=!1;const o=({target:i})=>{i===e&&(s=!0,e.removeEventListener(Pt,o),Yt(t))};e.addEventListener(Pt,o),setTimeout((()=>{s||Mt(e)}),n)},Gt=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},Jt=/[^.]*(?=\..*)\.|.*/,Zt=/\..*/,te=/::\d+$/,ee={};let ie=1;const ne={mouseenter:"mouseover",mouseleave:"mouseout"},se=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function oe(t,e){return e&&`${e}::${ie++}`||t.uidEvent||ie++}function re(t){const e=oe(t);return t.uidEvent=e,ee[e]=ee[e]||{},ee[e]}function ae(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function le(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=de(t);return se.has(o)||(o=t),[n,s,o]}function ce(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=le(e,i,n);if(e in ne){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=re(t),c=l[a]||(l[a]={}),h=ae(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const u=oe(r,e.replace(Jt,"")),d=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return pe(s,{delegateTarget:r}),n.oneOff&&fe.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return pe(n,{delegateTarget:t}),i.oneOff&&fe.off(t,n.type,e),e.apply(t,[n])}}(t,r);d.delegationSelector=o?i:null,d.callable=r,d.oneOff=s,d.uidEvent=u,c[u]=d,t.addEventListener(a,d,o)}function he(t,e,i,n,s){const o=ae(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function ue(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&he(t,e,i,r.callable,r.delegationSelector)}function de(t){return t=t.replace(Zt,""),ne[t]||t}const fe={on(t,e,i,n){ce(t,e,i,n,!1)},one(t,e,i,n){ce(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=le(e,i,n),a=r!==e,l=re(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))ue(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(te,"");a&&!e.includes(s)||he(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;he(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=Vt();let s=null,o=!0,r=!0,a=!1;e!==de(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=pe(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function pe(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function me(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function ge(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const _e={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${ge(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${ge(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=me(t.dataset[n])}return e},getDataAttribute:(t,e)=>me(t.getAttribute(`data-bs-${ge(e)}`))};class be{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=Ft(e)?_e.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...Ft(e)?_e.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],o=Ft(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(o))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${o}" but expected type "${s}".`)}var i}}class ve extends be{constructor(t,e){super(),(t=Ht(t))&&(this._element=t,this._config=this._getConfig(e),Nt.set(this._element,this.constructor.DATA_KEY,this))}dispose(){Nt.remove(this._element,this.constructor.DATA_KEY),fe.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){Ut(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return Nt.get(Ht(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.3"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const ye=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e?e.split(",").map((t=>jt(t))).join(","):null},we={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!Bt(t)&&Wt(t)))},getSelectorFromElement(t){const e=ye(t);return e&&we.findOne(e)?e:null},getElementFromSelector(t){const e=ye(t);return e?we.findOne(e):null},getMultipleElementsFromSelector(t){const e=ye(t);return e?we.find(e):[]}},Ae=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;fe.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),Bt(this))return;const s=we.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},Ee=".bs.alert",Te=`close${Ee}`,Ce=`closed${Ee}`;class Oe extends ve{static get NAME(){return"alert"}close(){if(fe.trigger(this._element,Te).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),fe.trigger(this._element,Ce),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Oe.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}Ae(Oe,"close"),Xt(Oe);const xe='[data-bs-toggle="button"]';class ke extends ve{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=ke.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}fe.on(document,"click.bs.button.data-api",xe,(t=>{t.preventDefault();const e=t.target.closest(xe);ke.getOrCreateInstance(e).toggle()})),Xt(ke);const Le=".bs.swipe",Se=`touchstart${Le}`,De=`touchmove${Le}`,$e=`touchend${Le}`,Ie=`pointerdown${Le}`,Ne=`pointerup${Le}`,Pe={endCallback:null,leftCallback:null,rightCallback:null},je={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class Me extends be{constructor(t,e){super(),this._element=t,t&&Me.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return Pe}static get DefaultType(){return je}static get NAME(){return"swipe"}dispose(){fe.off(this._element,Le)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),Yt(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&Yt(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(fe.on(this._element,Ie,(t=>this._start(t))),fe.on(this._element,Ne,(t=>this._end(t))),this._element.classList.add("pointer-event")):(fe.on(this._element,Se,(t=>this._start(t))),fe.on(this._element,De,(t=>this._move(t))),fe.on(this._element,$e,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const Fe=".bs.carousel",He=".data-api",We="ArrowLeft",Be="ArrowRight",ze="next",Re="prev",qe="left",Ve="right",Ke=`slide${Fe}`,Qe=`slid${Fe}`,Xe=`keydown${Fe}`,Ye=`mouseenter${Fe}`,Ue=`mouseleave${Fe}`,Ge=`dragstart${Fe}`,Je=`load${Fe}${He}`,Ze=`click${Fe}${He}`,ti="carousel",ei="active",ii=".active",ni=".carousel-item",si=ii+ni,oi={[We]:Ve,[Be]:qe},ri={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},ai={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class li extends ve{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=we.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===ti&&this.cycle()}static get Default(){return ri}static get DefaultType(){return ai}static get NAME(){return"carousel"}next(){this._slide(ze)}nextWhenVisible(){!document.hidden&&Wt(this._element)&&this.next()}prev(){this._slide(Re)}pause(){this._isSliding&&Mt(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?fe.one(this._element,Qe,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void fe.one(this._element,Qe,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?ze:Re;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&fe.on(this._element,Xe,(t=>this._keydown(t))),"hover"===this._config.pause&&(fe.on(this._element,Ye,(()=>this.pause())),fe.on(this._element,Ue,(()=>this._maybeEnableCycle()))),this._config.touch&&Me.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of we.find(".carousel-item img",this._element))fe.on(t,Ge,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(qe)),rightCallback:()=>this._slide(this._directionToOrder(Ve)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new Me(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=oi[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=we.findOne(ii,this._indicatorsElement);e.classList.remove(ei),e.removeAttribute("aria-current");const i=we.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(ei),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===ze,s=e||Gt(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>fe.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(Ke).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),qt(s),i.classList.add(l),s.classList.add(l);this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(ei),i.classList.remove(ei,c,l),this._isSliding=!1,r(Qe)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return we.findOne(si,this._element)}_getItems(){return we.find(ni,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return Qt()?t===qe?Re:ze:t===qe?ze:Re}_orderToDirection(t){return Qt()?t===Re?qe:Ve:t===Re?Ve:qe}static jQueryInterface(t){return this.each((function(){const e=li.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}fe.on(document,Ze,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=we.getElementFromSelector(this);if(!e||!e.classList.contains(ti))return;t.preventDefault();const i=li.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===_e.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),fe.on(window,Je,(()=>{const t=we.find('[data-bs-ride="carousel"]');for(const e of t)li.getOrCreateInstance(e)})),Xt(li);const ci=".bs.collapse",hi=`show${ci}`,ui=`shown${ci}`,di=`hide${ci}`,fi=`hidden${ci}`,pi=`click${ci}.data-api`,mi="show",gi="collapse",_i="collapsing",bi=`:scope .${gi} .${gi}`,vi='[data-bs-toggle="collapse"]',yi={parent:null,toggle:!0},wi={parent:"(null|element)",toggle:"boolean"};class Ai extends ve{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=we.find(vi);for(const t of i){const e=we.getSelectorFromElement(t),i=we.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return yi}static get DefaultType(){return wi}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Ai.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(fe.trigger(this._element,hi).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(gi),this._element.classList.add(_i),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(_i),this._element.classList.add(gi,mi),this._element.style[e]="",fe.trigger(this._element,ui)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(fe.trigger(this._element,di).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,qt(this._element),this._element.classList.add(_i),this._element.classList.remove(gi,mi);for(const t of this._triggerArray){const e=we.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0;this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(_i),this._element.classList.add(gi),fe.trigger(this._element,fi)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(mi)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=Ht(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(vi);for(const e of t){const t=we.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=we.find(bi,this._config.parent);return we.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Ai.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}fe.on(document,pi,vi,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of we.getMultipleElementsFromSelector(this))Ai.getOrCreateInstance(t,{toggle:!1}).toggle()})),Xt(Ai);const Ei="dropdown",Ti=".bs.dropdown",Ci=".data-api",Oi="ArrowUp",xi="ArrowDown",ki=`hide${Ti}`,Li=`hidden${Ti}`,Si=`show${Ti}`,Di=`shown${Ti}`,$i=`click${Ti}${Ci}`,Ii=`keydown${Ti}${Ci}`,Ni=`keyup${Ti}${Ci}`,Pi="show",ji='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',Mi=`${ji}.${Pi}`,Fi=".dropdown-menu",Hi=Qt()?"top-end":"top-start",Wi=Qt()?"top-start":"top-end",Bi=Qt()?"bottom-end":"bottom-start",zi=Qt()?"bottom-start":"bottom-end",Ri=Qt()?"left-start":"right-start",qi=Qt()?"right-start":"left-start",Vi={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},Ki={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"};class Qi extends ve{constructor(t,e){super(t,e),this._popper=null,this._parent=this._element.parentNode,this._menu=we.next(this._element,Fi)[0]||we.prev(this._element,Fi)[0]||we.findOne(Fi,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return Vi}static get DefaultType(){return Ki}static get NAME(){return Ei}toggle(){return this._isShown()?this.hide():this.show()}show(){if(Bt(this._element)||this._isShown())return;const t={relatedTarget:this._element};if(!fe.trigger(this._element,Si,t).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(".navbar-nav"))for(const t of[].concat(...document.body.children))fe.on(t,"mouseover",Rt);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Pi),this._element.classList.add(Pi),fe.trigger(this._element,Di,t)}}hide(){if(Bt(this._element)||!this._isShown())return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){if(!fe.trigger(this._element,ki,t).defaultPrevented){if("ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.off(t,"mouseover",Rt);this._popper&&this._popper.destroy(),this._menu.classList.remove(Pi),this._element.classList.remove(Pi),this._element.setAttribute("aria-expanded","false"),_e.removeDataAttribute(this._menu,"popper"),fe.trigger(this._element,Li,t)}}_getConfig(t){if("object"==typeof(t=super._getConfig(t)).reference&&!Ft(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ei.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(){if(void 0===e)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let t=this._element;"parent"===this._config.reference?t=this._parent:Ft(this._config.reference)?t=Ht(this._config.reference):"object"==typeof this._config.reference&&(t=this._config.reference);const i=this._getPopperConfig();this._popper=Dt(t,this._menu,i)}_isShown(){return this._menu.classList.contains(Pi)}_getPlacement(){const t=this._parent;if(t.classList.contains("dropend"))return Ri;if(t.classList.contains("dropstart"))return qi;if(t.classList.contains("dropup-center"))return"top";if(t.classList.contains("dropdown-center"))return"bottom";const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?Wi:Hi:e?zi:Bi}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(_e.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...Yt(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=we.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>Wt(t)));i.length&&Gt(i,e,t===xi,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=Qi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=we.find(Mi);for(const i of e){const e=Qi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Oi,xi].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(ji)?this:we.prev(this,ji)[0]||we.next(this,ji)[0]||we.findOne(ji,t.delegateTarget.parentNode),o=Qi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}fe.on(document,Ii,ji,Qi.dataApiKeydownHandler),fe.on(document,Ii,Fi,Qi.dataApiKeydownHandler),fe.on(document,$i,Qi.clearMenus),fe.on(document,Ni,Qi.clearMenus),fe.on(document,$i,ji,(function(t){t.preventDefault(),Qi.getOrCreateInstance(this).toggle()})),Xt(Qi);const Xi="backdrop",Yi="show",Ui=`mousedown.bs.${Xi}`,Gi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Ji={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Zi extends be{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Gi}static get DefaultType(){return Ji}static get NAME(){return Xi}show(t){if(!this._config.isVisible)return void Yt(t);this._append();const e=this._getElement();this._config.isAnimated&&qt(e),e.classList.add(Yi),this._emulateAnimation((()=>{Yt(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Yi),this._emulateAnimation((()=>{this.dispose(),Yt(t)}))):Yt(t)}dispose(){this._isAppended&&(fe.off(this._element,Ui),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=Ht(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),fe.on(t,Ui,(()=>{Yt(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){Ut(t,this._getElement(),this._config.isAnimated)}}const tn=".bs.focustrap",en=`focusin${tn}`,nn=`keydown.tab${tn}`,sn="backward",on={autofocus:!0,trapElement:null},rn={autofocus:"boolean",trapElement:"element"};class an extends be{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return on}static get DefaultType(){return rn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),fe.off(document,tn),fe.on(document,en,(t=>this._handleFocusin(t))),fe.on(document,nn,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,fe.off(document,tn))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=we.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===sn?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?sn:"forward")}}const ln=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",cn=".sticky-top",hn="padding-right",un="margin-right";class dn{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,hn,(e=>e+t)),this._setElementAttributes(ln,hn,(e=>e+t)),this._setElementAttributes(cn,un,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,hn),this._resetElementAttributes(ln,hn),this._resetElementAttributes(cn,un)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&_e.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=_e.getDataAttribute(t,e);null!==i?(_e.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(Ft(t))e(t);else for(const i of we.find(t,this._element))e(i)}}const fn=".bs.modal",pn=`hide${fn}`,mn=`hidePrevented${fn}`,gn=`hidden${fn}`,_n=`show${fn}`,bn=`shown${fn}`,vn=`resize${fn}`,yn=`click.dismiss${fn}`,wn=`mousedown.dismiss${fn}`,An=`keydown.dismiss${fn}`,En=`click${fn}.data-api`,Tn="modal-open",Cn="show",On="modal-static",xn={backdrop:!0,focus:!0,keyboard:!0},kn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Ln extends ve{constructor(t,e){super(t,e),this._dialog=we.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new dn,this._addEventListeners()}static get Default(){return xn}static get DefaultType(){return kn}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){if(this._isShown||this._isTransitioning)return;fe.trigger(this._element,_n,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(Tn),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;fe.trigger(this._element,pn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Cn),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated()))}dispose(){fe.off(window,fn),fe.off(this._dialog,fn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Zi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new an({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=we.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),qt(this._element),this._element.classList.add(Cn);this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,fe.trigger(this._element,bn,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){fe.on(this._element,An,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),fe.on(window,vn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),fe.on(this._element,wn,(t=>{fe.one(this._element,yn,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Tn),this._resetAdjustments(),this._scrollBar.reset(),fe.trigger(this._element,gn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(fe.trigger(this._element,mn).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(On)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(On),this._queueCallback((()=>{this._element.classList.remove(On),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=Qt()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=Qt()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Ln.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}fe.on(document,En,'[data-bs-toggle="modal"]',(function(t){const e=we.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),fe.one(e,_n,(t=>{t.defaultPrevented||fe.one(e,gn,(()=>{Wt(this)&&this.focus()}))}));const i=we.findOne(".modal.show");i&&Ln.getInstance(i).hide();Ln.getOrCreateInstance(e).toggle(this)})),Ae(Ln),Xt(Ln);const Sn=".bs.offcanvas",Dn=".data-api",$n=`load${Sn}${Dn}`,In="show",Nn="showing",Pn="hiding",jn=".offcanvas.show",Mn=`show${Sn}`,Fn=`shown${Sn}`,Hn=`hide${Sn}`,Wn=`hidePrevented${Sn}`,Bn=`hidden${Sn}`,zn=`resize${Sn}`,Rn=`click${Sn}${Dn}`,qn=`keydown.dismiss${Sn}`,Vn={backdrop:!0,keyboard:!0,scroll:!1},Kn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class Qn extends ve{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Vn}static get DefaultType(){return Kn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){if(this._isShown)return;if(fe.trigger(this._element,Mn,{relatedTarget:t}).defaultPrevented)return;this._isShown=!0,this._backdrop.show(),this._config.scroll||(new dn).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Nn);this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(In),this._element.classList.remove(Nn),fe.trigger(this._element,Fn,{relatedTarget:t})}),this._element,!0)}hide(){if(!this._isShown)return;if(fe.trigger(this._element,Hn).defaultPrevented)return;this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(Pn),this._backdrop.hide();this._queueCallback((()=>{this._element.classList.remove(In,Pn),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new dn).reset(),fe.trigger(this._element,Bn)}),this._element,!0)}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Zi({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():fe.trigger(this._element,Wn)}:null})}_initializeFocusTrap(){return new an({trapElement:this._element})}_addEventListeners(){fe.on(this._element,qn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():fe.trigger(this._element,Wn))}))}static jQueryInterface(t){return this.each((function(){const e=Qn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}fe.on(document,Rn,'[data-bs-toggle="offcanvas"]',(function(t){const e=we.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),Bt(this))return;fe.one(e,Bn,(()=>{Wt(this)&&this.focus()}));const i=we.findOne(jn);i&&i!==e&&Qn.getInstance(i).hide();Qn.getOrCreateInstance(e).toggle(this)})),fe.on(window,$n,(()=>{for(const t of we.find(jn))Qn.getOrCreateInstance(t).show()})),fe.on(window,zn,(()=>{for(const t of we.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&Qn.getOrCreateInstance(t).hide()})),Ae(Qn),Xt(Qn);const Xn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Yn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Un=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Gn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Yn.has(i)||Boolean(Un.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))};const Jn={allowList:Xn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:""},Zn={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},ts={entry:"(string|element|function|null)",selector:"(string|element)"};class es extends be{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Jn}static get DefaultType(){return Zn}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},ts)}_setContent(t,e,i){const n=we.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?Ft(e)?this._putElementInTemplate(Ht(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Gn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return Yt(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const is=new Set(["sanitize","allowList","sanitizeFn"]),ns="fade",ss="show",os=".tooltip-inner",rs=".modal",as="hide.bs.modal",ls="hover",cs="focus",hs={AUTO:"auto",TOP:"top",RIGHT:Qt()?"left":"right",BOTTOM:"bottom",LEFT:Qt()?"right":"left"},us={allowList:Xn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'
',title:"",trigger:"hover focus"},ds={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class fs extends ve{constructor(t,i){if(void 0===e)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,i),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return us}static get DefaultType(){return ds}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),fe.off(this._element.closest(rs),as,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=fe.trigger(this._element,this.constructor.eventName("show")),e=(zt(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),fe.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(ss),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.on(t,"mouseover",Rt);this._queueCallback((()=>{fe.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(!this._isShown())return;if(fe.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented)return;if(this._getTipElement().classList.remove(ss),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.off(t,"mouseover",Rt);this._activeTrigger.click=!1,this._activeTrigger[cs]=!1,this._activeTrigger[ls]=!1,this._isHovered=null;this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),fe.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(ns,ss),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(ns),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new es({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{[os]:this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(ns)}_isShown(){return this.tip&&this.tip.classList.contains(ss)}_createPopper(t){const e=Yt(this._config.placement,[this,t,this._element]),i=hs[e.toUpperCase()];return Dt(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return Yt(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...Yt(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)fe.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===ls?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===ls?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");fe.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?cs:ls]=!0,e._enter()})),fe.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?cs:ls]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},fe.on(this._element.closest(rs),as,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=_e.getDataAttributes(this._element);for(const t of Object.keys(e))is.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:Ht(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=fs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}Xt(fs);const ps=".popover-header",ms=".popover-body",gs={...fs.Default,content:"",offset:[0,8],placement:"right",template:'
',trigger:"click"},_s={...fs.DefaultType,content:"(null|string|element|function)"};class bs extends fs{static get Default(){return gs}static get DefaultType(){return _s}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{[ps]:this._getTitle(),[ms]:this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=bs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}Xt(bs);const vs=".bs.scrollspy",ys=`activate${vs}`,ws=`click${vs}`,As=`load${vs}.data-api`,Es="active",Ts="[href]",Cs=".nav-link",Os=`${Cs}, .nav-item > ${Cs}, .list-group-item`,xs={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},ks={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Ls extends ve{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return xs}static get DefaultType(){return ks}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=Ht(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(fe.off(this._config.target,ws),fe.on(this._config.target,ws,Ts,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=we.find(Ts,this._config.target);for(const e of t){if(!e.hash||Bt(e))continue;const t=we.findOne(decodeURI(e.hash),this._element);Wt(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(Es),this._activateParents(t),fe.trigger(this._element,ys,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))we.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(Es);else for(const e of we.parents(t,".nav, .list-group"))for(const t of we.prev(e,Os))t.classList.add(Es)}_clearActiveClass(t){t.classList.remove(Es);const e=we.find(`${Ts}.${Es}`,t);for(const t of e)t.classList.remove(Es)}static jQueryInterface(t){return this.each((function(){const e=Ls.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}fe.on(window,As,(()=>{for(const t of we.find('[data-bs-spy="scroll"]'))Ls.getOrCreateInstance(t)})),Xt(Ls);const Ss=".bs.tab",Ds=`hide${Ss}`,$s=`hidden${Ss}`,Is=`show${Ss}`,Ns=`shown${Ss}`,Ps=`click${Ss}`,js=`keydown${Ss}`,Ms=`load${Ss}`,Fs="ArrowLeft",Hs="ArrowRight",Ws="ArrowUp",Bs="ArrowDown",zs="Home",Rs="End",qs="active",Vs="fade",Ks="show",Qs=".dropdown-toggle",Xs=`:not(${Qs})`,Ys='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Us=`${`.nav-link${Xs}, .list-group-item${Xs}, [role="tab"]${Xs}`}, ${Ys}`,Gs=`.${qs}[data-bs-toggle="tab"], .${qs}[data-bs-toggle="pill"], .${qs}[data-bs-toggle="list"]`;class Js extends ve{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),fe.on(this._element,js,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?fe.trigger(e,Ds,{relatedTarget:t}):null;fe.trigger(t,Is,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){if(!t)return;t.classList.add(qs),this._activate(we.getElementFromSelector(t));this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),fe.trigger(t,Ns,{relatedTarget:e})):t.classList.add(Ks)}),t,t.classList.contains(Vs))}_deactivate(t,e){if(!t)return;t.classList.remove(qs),t.blur(),this._deactivate(we.getElementFromSelector(t));this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),fe.trigger(t,$s,{relatedTarget:e})):t.classList.remove(Ks)}),t,t.classList.contains(Vs))}_keydown(t){if(![Fs,Hs,Ws,Bs,zs,Rs].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!Bt(t)));let i;if([zs,Rs].includes(t.key))i=e[t.key===zs?0:e.length-1];else{const n=[Hs,Bs].includes(t.key);i=Gt(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Js.getOrCreateInstance(i).show())}_getChildren(){return we.find(Us,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=we.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=we.findOne(t,i);s&&s.classList.toggle(n,e)};n(Qs,qs),n(".dropdown-menu",Ks),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(qs)}_getInnerElement(t){return t.matches(Us)?t:we.findOne(Us,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Js.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}fe.on(document,Ps,Ys,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),Bt(this)||Js.getOrCreateInstance(this).show()})),fe.on(window,Ms,(()=>{for(const t of we.find(Gs))Js.getOrCreateInstance(t)})),Xt(Js);const Zs=".bs.toast",to=`mouseover${Zs}`,eo=`mouseout${Zs}`,io=`focusin${Zs}`,no=`focusout${Zs}`,so=`hide${Zs}`,oo=`hidden${Zs}`,ro=`show${Zs}`,ao=`shown${Zs}`,lo="hide",co="show",ho="showing",uo={animation:"boolean",autohide:"boolean",delay:"number"},fo={animation:!0,autohide:!0,delay:5e3};class po extends ve{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return fo}static get DefaultType(){return uo}static get NAME(){return"toast"}show(){if(fe.trigger(this._element,ro).defaultPrevented)return;this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");this._element.classList.remove(lo),qt(this._element),this._element.classList.add(co,ho),this._queueCallback((()=>{this._element.classList.remove(ho),fe.trigger(this._element,ao),this._maybeScheduleHide()}),this._element,this._config.animation)}hide(){if(!this.isShown())return;if(fe.trigger(this._element,so).defaultPrevented)return;this._element.classList.add(ho),this._queueCallback((()=>{this._element.classList.add(lo),this._element.classList.remove(ho,co),fe.trigger(this._element,oo)}),this._element,this._config.animation)}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(co),super.dispose()}isShown(){return this._element.classList.contains(co)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){fe.on(this._element,to,(t=>this._onInteraction(t,!0))),fe.on(this._element,eo,(t=>this._onInteraction(t,!1))),fe.on(this._element,io,(t=>this._onInteraction(t,!0))),fe.on(this._element,no,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=po.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}Ae(po),Xt(po),document.body.style.color="blue"})()})();
3 | //# sourceMappingURL=bundle.js.map
--------------------------------------------------------------------------------
/dist/js/bundle.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v5.3.3 (https://getbootstrap.com/)
3 | * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
5 | */
6 |
--------------------------------------------------------------------------------
/dist/second.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Второй заголовок | Проект
9 |
10 |
11 |
12 |
Вторая страница.
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/dist/uploads/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/dist/uploads/test.jpg
--------------------------------------------------------------------------------
/docs/article.md:
--------------------------------------------------------------------------------
1 | # Простой статический сайт на Webpack 4
2 |
3 | После прочтения ряда статей (например, [этой](https://habr.com/ru/company/mailru/blog/340922/)) решил перейти на современный подход с использованием Node.js при написании простых сайтов с подхода «динозавров». Ниже представлен разбор примера сборки простого статического сайта с помощью [Webpack 4](https://webpack.js.org/). Статья написана, так как инструкции с решением моей задачи не нашел: пришлось собирать всё по кусочкам.
4 |
5 | ## Постановка задачи
6 |
7 | Сайт представляет собой простой набор HTML-страниц со своим CSS стилями и файлом JavaScript. Необходимо написать проект, который бы собирал наш сайт из исходников:
8 |
9 | - из SASS (точнее SCSS) файлов формируется один CSS файл;
10 | - из различных JavaScript библиотек и пользовательского кода формируется один JavaScript файл;
11 | - HTML страницы собираются с помощью шаблона, где содержимое шапки и футера можно разнести по отдельным файлам.
12 |
13 | В собранном сайте не должны использоваться React, Vue.js.
14 |
15 | При выборе технологий выбираются по возможности наиболее популярные на данный момент. По этой причине отказался и от Grunt и Gulp в пользу Webpack, хотя, если честно, синтаксис Gulp мне понравился больше своим однообразием.
16 |
17 | Для примера будет сверстано несколько страничек на базе Bootstrap 4. Но это только для примера.
18 |
19 | Предполагается, что [Node.js](https://nodejs.org/) установлен (в Windows просто нужно скачать установщик и установить в стиле «далее, далее»), и вы умеете работать с командной строкой CLI.
20 |
21 | Нужно получить набор готовых HTML страниц, которые можно залить на хостинг без дополнительных настроек (например, на GitHub Pages) или открыть локально на компьютере.
22 |
23 | ## Структура проекта
24 |
25 | Общая структура проекта представлена ниже:
26 |
27 | ```text
28 | .
29 | ├── dist - папка, куда будет собираться сайт
30 | ├─┬ src - папка с исходниками сайта
31 | │ ├── favicon - папка с файлами иконок для сайта
32 | │ ├── fonts - папка со шрифтами
33 | │ ├─┬ html - папка заготовок HTML страниц
34 | │ │ ├── includes - папка с встраиваемыми шаблонами (header, footer)
35 | │ │ └── views - папка с самими HTML страницами
36 | │ ├── img - папка с общими изображениями (логотип, иконки и др.)
37 | │ ├── js - папка с JavaScript файлами
38 | │ ├── scss - папка с SСSS файлами
39 | │ └── uploads - папка с файлами статей (картинки, архивы и др.)
40 | ├── package.json - файл настроек Node.js
41 | └── webpack.config.js - файл настроек Webpack
42 | ```
43 |
44 | Та же структура, но с показом файлов, которые присутствуют в примере:
45 |
46 | ```text
47 | .
48 | ├── dist
49 | ├─┬ src
50 | │ ├─┬ favicon
51 | │ │ └── favicon.ico
52 | │ ├─┬ fonts
53 | │ │ └── Roboto-Regular.ttf
54 | │ ├─┬ html
55 | │ │ ├─┬ includes
56 | │ │ │ ├── footer.html
57 | │ │ │ └── header.html
58 | │ │ └─┬ views
59 | │ │ ├── index.html
60 | │ │ └── second.html
61 | │ ├─┬ img
62 | │ │ └── logo.svg
63 | │ ├─┬ js
64 | │ │ └── index.js
65 | │ ├─┬ scss
66 | │ │ └── style.scss
67 | │ └─┬ uploads
68 | │ └── test.jpg
69 | ├── package.json
70 | └── webpack.config.js
71 | ```
72 |
73 | Под favicon выделена целая папка, так как в современном web обычным одним ICO файлом не обойтись. Но для примера используется только этот один файл.
74 |
75 | Спорным решением может показаться разделение картинок на две папки: `img` и `uploads`. Но здесь использовал идеологию расположения файлов из Wordpress. На мой взгляд, кидать все изображения в одну папку - не очень хорошая идея.
76 |
77 | Для работы с проектом использую [Visual Studio Code](https://code.visualstudio.com/), которым очень доволен. Особенно мне нравится, что командная строка встроена в программу и вызывается через `Ctrl` + `` ` ``.
78 |
79 | 
80 |
81 | Сделаем болванку Node.js проекта. Для этого создадим папку нашего проекта с вышеописанной структурой и перейдем в неё в командной строке, где вызовем команду для создания файла `package.json`.
82 |
83 | ```shell
84 | npm init
85 | ```
86 |
87 | На все вопросы можно просто отвечать, нажимая `Enter`, если заполнять подробную информацию не хочется.
88 |
89 | Установим три общих пакета, которые нам потребуются в любом случае: `webpack`, `webpack-cli` (работу с командной строкой в webpack вынесли в отдельный пакет) и `webpack-dev-server` (для запуска локального сервера, чтобы в браузере сразу отображались сохраненные изменения проекта).
90 |
91 | ```shell
92 | npm install webpack webpack-cli webpack-dev-server --save-dev
93 | ```
94 |
95 | Файл package.json сейчас выглядит примерно так:
96 |
97 | ```json
98 | {
99 | "name": "static-site-webpack-habr",
100 | "version": "1.0.0",
101 | "description": "",
102 | "main": "index.js",
103 | "scripts": {
104 | "test": "echo \"Error: no test specified\" && exit 1"
105 | },
106 | "license": "ISC",
107 | "devDependencies": {
108 | "webpack": "^4.1.1",
109 | "webpack-cli": "^2.0.11",
110 | "webpack-dev-server": "^3.1.1"
111 | }
112 | }
113 | ```
114 |
115 | Также создастся файл `package-lock.json`, который вообще не трогаем. Но в git репозиторий добавлять этот файл нужно, в отличии от папки `node_modules`, которую нужно прописать в файле `.gitignore`, если пользуетесь git.
116 |
117 | ## Собираем JavaScript
118 |
119 | Так как Webpack создан в первую очередь для сборки JS файлов, то эта часть будем самой простой. Чтобы можно было писать JavaScript в современном виде ES2015, который не поддерживается браузерами, поставим пакеты `babel-core`, `babel-loader`, `babel-preset-env`.
120 |
121 | ```shell
122 | npm install babel-core babel-loader babel-preset-env --save-dev
123 | ```
124 |
125 | После создаем файл настроек `webpack.config.js` с таким содержимым:
126 |
127 | ```javascript
128 | const path = require("path");
129 |
130 | module.exports = {
131 | entry: ["./src/js/index.js"],
132 | output: {
133 | filename: "./js/bundle.js",
134 | },
135 | devtool: "source-map",
136 | module: {
137 | rules: [
138 | {
139 | test: /\.js$/,
140 | include: path.resolve(__dirname, "src/js"),
141 | use: {
142 | loader: "babel-loader",
143 | options: {
144 | presets: "env",
145 | },
146 | },
147 | },
148 | ],
149 | },
150 | plugins: [],
151 | };
152 | ```
153 |
154 | В разделе `entry` (точки входа) указываем, какой JS файл будем собирать, в разделе `output` указываем путь в папке `dist`, куда будем помещаться собранный файл. Обратите внимание, что в webpack 4 в пути `output` саму папку `dist` указывать не нужно! И да, как же мне не нравится, что в одном файле webpack в одних случаях нужно писать относительный путь, в других случаях относительный путь в специальной папке, в третьих случаях нужен уже абсолютный путь (например, его получаем этой командой `path.resolve(__dirname, 'src/js')`).
155 |
156 | Также указано значение параметра `devtool`, равное: `source-map`, что позволит создавать [карты исходников](https://habr.com/ru/post/178743/) для JS и CSS файлов.
157 |
158 | Для обработки конкретных файлов (по расширению, по месторасположению) в webpack создаются правила в разделе `rules`. Сейчас у нас там стоит правило, что все JS файлы пропускаем через транслятор Babel, который преобразует наш новомодный ES2015 в стандартный JavaScript вариант, понятный браузерам.
159 |
160 | В нашем тестовом примере мы верстаем наши странице на [Boostrap 4](https://getbootstrap.com/). Поэтому нам нужно будет установить три пакета: `bootstrap`, `jquery`, `popper.js`. Второй и третий пакет мы устанавливаем по требованию Bootstrap.
161 |
162 | ```shell
163 | npm install bootstrap jquery popper.js --save
164 | ```
165 |
166 | Обратите внимание на то, что эти три пакета нам нужны именно для самого сайта, а не для его сборки. Поэтому эти пакеты мы устанавливаем с флагом `--save`, а не `--save-dev`.
167 |
168 | Теперь можно приступить к написанию нашего `index.js` файла:
169 |
170 | ```javascript
171 | import jQuery from "jquery";
172 | import popper from "popper.js";
173 | import bootstrap from "bootstrap";
174 |
175 | jQuery(function () {
176 | jQuery("body").css("color", "blue");
177 | });
178 | ```
179 |
180 | В качестве примера пользовательского кода JS просто перекрасили цвет текста на синий.
181 |
182 | Теперь можно перейти к сборке JS файла. Для этого в файле `package.json` в разделе `scripts` пропишем следующие npm скрипты:
183 |
184 | ```json
185 | "scripts": {
186 | "dev": "webpack --mode development",
187 | "build": "webpack --mode production",
188 | "watch": "webpack --mode development --watch",
189 | "start": "webpack --mode development serve"
190 | },
191 | ```
192 |
193 | Теперь при запуске в командной строке строчки **npm run dev** произойдет сборка проекта (CSS и HTML файлы потом также будут собираться этой командой), и в папке `/dist/js` появятся файлы `bundle.js` и `bundle.js.map`.
194 |
195 | При запуске команды **npm run build** также произойдет сборка проекта, но уже итоговая (с оптимизацией, максимальной минимизацией файла), которую можно выкладывать на хостинг.
196 |
197 | При запуске **npm run watch** запускается режим автоматического просмотра изменений файлов проекта с автоматическим сборкой измененных файлов. Да, чтобы в командной строке отключить этот режим (например, чтобы можно было написать другие команды) можно нажать `Ctrl` + `C` (как минимум в PowerShell).
198 |
199 | При запуске **npm run start** запустится локальный сервер, который запустит HTML страницу и также будет отслеживать изменения в файлах. Но пока этой командой не пользуемся, так как сборку HTML страниц не добавили.
200 |
201 | Режим построения проекта создает или переписывает файлы в папке `dist`. Но во время разработки проекта при разных сборках файлы могут переименовываться, удаляться. И Webpack не будет следить, чтобы уже ненужные файлы, оставшиеся после предыдущих сборок, удалялись из папки `dist`. ~~Поэтому добавим еще один пакет `clean-webpack-plugin`, который будет очищать папку `dist` перед каждой сборкой проекта.~~
202 |
203 | Пришлось отказаться от `clean-webpack-plugin`. Почему? Когда запускаешь сервер через команду `npm run start` (`webpack serve`), то webpack компилирует файлы автоматом, не сохраняя их в папку `dist`. И это нормально. Но при этом папка `dist` очищается из-за наличия `clean-webpack-plugin`. В результате в режиме работы локального сервера папка `dist` пустует, что негативно сказывается на работе с git (только в случае, если вы в git репозиторий сохраняется сборку проекта, как и я): после каждого запуска сервера появляется куча изменений из-за удаленных файлов. Было бы хорошо, чтобы очистка папки `dist` происходила только при полноценной сборке, например, `npm run build-and-beautify` (об этой команде ниже). Плагин `clean-webpack-plugin` настроить нужным способом не смог. Поэтому использую другой плагин `del-cli`, который не связан с webpack и работает отдельно.
204 |
205 | ```shell
206 | npm install del-cli --save-dev
207 | ```
208 |
209 | Внесем изменения в файл `package.json`.
210 |
211 | ```json
212 | {
213 | ...
214 | "scripts": {
215 | ...
216 | "clear": "del-cli dist"
217 | },
218 | ...
219 | }
220 | ```
221 |
222 | ## Сборка CSS файла
223 |
224 | CSS файл будем собирать из SCSS файлов, под которые у нас зарезервирована папка `src/scss`. В ней создадим файл `style.scss`, например, со следующим содержимым:
225 |
226 | ```scss
227 | $font-stack: -apple-system, BlinkMacSystemFont, Roboto, "Open Sans", "Helvetica Neue", sans-serif;
228 |
229 | @import "~bootstrap/scss/bootstrap";
230 |
231 | @font-face {
232 | font-family: "Roboto";
233 | font-style: normal;
234 | font-weight: 400;
235 | src: url(../fonts/Roboto-Regular.ttf);
236 | }
237 |
238 | body {
239 | font-family: $font-stack;
240 | #logo {
241 | width: 10rem;
242 | }
243 | .container {
244 | img {
245 | width: 20rem;
246 | }
247 | }
248 | }
249 | ```
250 |
251 | Обратите внимание на то, что стили Bootstrap подключаем не через его CSS файл, а через SСSS (~~`@import "node_modules/bootstrap/scss/bootstrap"`~~ `@import "~bootstrap/scss/bootstrap";`), который позволит в случае надобности переписать те или иные свойства библиотеки, использовать его миксины и др. Но что печалит. Если при сборке JS файла при подключении JS файла Bootstrap библиотеки Webpack знает, где находятся нужные файлы, то при подключении стилей нужно указывать путь к папке в `node_modules`.
252 |
253 | Для обработки css файлов нам будут нужны следующие модули: `node-sass`, `sass-loader`, `css-loader` и `extract-text-webpack-plugin` (говорят, что в следующей версии Webpack в последнем плагине надобность отпадет).
254 |
255 | Важно! На момент написания статьи плагин `extract-text-webpack-plugin` в стабильной версии не умеет работать с Webpack 4. Поэтому нужно устанавливать его beta версию через `@next`:
256 |
257 | ```shell
258 | npm install node-sass sass-loader css-loader extract-text-webpack-plugin@next --save-dev
259 | ```
260 |
261 | Надеюсь, что вскоре можно будет устанавливать все плагины по нормальному:
262 |
263 | ```shell
264 | npm install node-sass sass-loader css-loader extract-text-webpack-plugin --save-dev
265 | ```
266 |
267 | В `webpack.config.js` добавим следующие изменения:
268 |
269 | ```javascript
270 | ...
271 | const ExtractTextPlugin = require("extract-text-webpack-plugin");
272 | ...
273 |
274 | module.exports = {
275 | entry: [
276 | ...
277 | './src/scss/style.scss'
278 | ],
279 | ...
280 | module: {
281 | rules: [{
282 | ...
283 | {
284 | test: /\.(sass|scss)$/,
285 | include: path.resolve(__dirname, 'src/scss'),
286 | use: ExtractTextPlugin.extract({
287 | use: [{
288 | loader: "css-loader",
289 | options: {
290 | sourceMap: true,
291 | minimize: true,
292 | url: false
293 | }
294 | },
295 | {
296 | loader: "sass-loader",
297 | options: {
298 | sourceMap: true
299 | }
300 | }
301 | ]
302 | })
303 | },
304 | ]
305 | },
306 | plugins: [
307 | new ExtractTextPlugin({
308 | filename: './css/style.bundle.css',
309 | allChunks: true,
310 | }),
311 | ...
312 | ]
313 | };
314 | ```
315 |
316 | Обратите внимание на то, что в точках входа `entry` мы добавили новый входной файл `style.scss`, но выходной файл указали не в `output`, а в вызове плагина ExtractTextPlugin в разделе `plugins`. Включаем поддержку карт источников sourceMap для пакетов `sass-loader` и `css-loader`.
317 |
318 | Также можно заметить, что тут нет пакета `style-loader`, который чаще всего упоминается при работе с CSS в Webpack. Данный пакет встраивает CSS код в файл HTML, что может быть удобно для одностраничных приложений, но никак не для многостраничного.
319 |
320 | И самый спорный момент. Для пакета `css-loader` мы добавили параметр `url`, равный `false`. Зачем? По умолчанию `url=true`, и если Webpack при сборке CSS находит ссылки на внешние файлы: фоновые изображения, шрифты (например, в нашем случае есть ссылка на файл шрифта `url(../fonts/Roboto-Regular.ttf)`), то он эти файлы попросит как-то обработать. Для этого используют чаще всего пакеты `file-loader` (копирует файлы в папку сборки) или `url-loader` (маленькие файлы пытается встроить в HTML код). При этом прописанные относительные пути к файлам в собранном CSS могут быть изменены.
321 |
322 | Но с какой проблемой столкнулся на практике. Есть у меня папка `src/scss` с SСSS кодом. Есть папка `src/img` с картинками, на которые ссылаются в SСSS коде. Всё хорошо. Но, например, мне потребовалось подключить на сайт стороннюю библиотеку (например, lightgallery). SCSS код у неё располагается в папке `node_modules/lightgallery/src/sass`, который ссылается на картинки из папки `node_modules/lightgallery/src/img` через относительные пути. И если добавить стили библиотеки в наш `style.scss`, то `file-loader` будет искать картинки библиотеки `lightgallery` в моей папке `src/img`, а не там, где они находятся. И побороть я это не смог.
323 |
324 | **Update.** С последней проблемой можно справиться, как подсказал [Odrin](https://habr.com/ru/users/odrin/), с помощью пакета [resolve-url-loader](https://github.com/bholloway/resolve-url-loader) и file-loader.
325 |
326 | Пример решения:
327 |
328 | ```javascript
329 | ...
330 |
331 | module.exports = {
332 | ...
333 | module: {
334 | rules: [
335 | ...
336 | {
337 | test: /\.(png|jpg|gif)$/,
338 | use: [
339 | {
340 | loader: 'file-loader',
341 | options: {name: 'img/[name].[ext]'}
342 | }
343 | ]
344 | },
345 | {
346 | test: /\.(sass|scss)$/,
347 | include: path.resolve(__dirname, 'src/scss'),
348 | use: ExtractTextPlugin.extract({
349 | use: [{
350 | loader: "css-loader",
351 | options: {
352 | sourceMap: true,
353 | minimize: true//,
354 | //url: false
355 | }
356 | },
357 | {
358 | loader: "resolve-url-loader"
359 | },
360 | {
361 | loader: "sass-loader",
362 | options: {
363 | sourceMap: true
364 | }
365 | }
366 | ]
367 | })
368 | }
369 | ...
370 | ]
371 | },
372 | ...
373 | };
374 | ```
375 |
376 | То есть пакет resolve-url-loader вместо относительных путей ставит пути, которые webpack поймет. А уже file-loader будет копировать нужные файлы. Проблема в свойстве name в file-loader. Если его указать как `name: '[path]/[name].[ext]'`, то в моей примере в папке `dist` появится папка `dist\node_modules\lightgallery\src\img`, в которой уже находятся изображения. Нет, в CSS будут прописанные верные пути до этой папки, но это будет не красиво. Поэтому лучше название файла указывать без пути (например `name: 'img/[name].[ext]'`). Правда, тогда все картинки пойдут в одну папку - не всегда это будет полезно.
377 |
378 | Поэтому установкой `url=false` говорим, что все ссылки на файлы в SCSS коде не трогаем, пути не меняем, никакие файлы не копируем и не встраиваем: с ними разберемся потом отдельно. Возможно, это решение плохое, и вы предложите более правильный подход.
379 |
380 | ## Сборка HTML страниц
381 |
382 | Перейдем к самому веселому: к сборке HTML страниц, где у меня возникли самые большие трудности.
383 |
384 | Для сборки HTML страниц будем использовать плагин `html-webpack-plugin`, который поддерживает различные виды шаблонизаторов. Также нам потребуются пакет `raw-loader`.
385 |
386 | ```shell
387 | npm install html-webpack-plugin raw-loader --save-dev
388 | ```
389 |
390 | В качестве шаблонизатора HTML будем использовать шаблонизатор по умолчанию lodash. Вот так будет выглядеть типичная HTML страница до сборки:
391 |
392 | ```html
393 | <% var data = { title: "Заголовок | Проект", author: "Harrix" }; %> <%=
394 | _.template(require('./../includes/header.html'))(data) %>
395 |
396 |
text
397 |
398 | <%= _.template(require('./../includes/footer.html'))(data) %>
399 | ```
400 |
401 | Вначале в переменной `data` прописываем все наши переменные страницы, которые хотим использовать на этой странице. Потом встраиваем шаблоны шапки и футера через `_.template(require())`.
402 |
403 | **Важное уточнение.** В статьях про сборку HTML страниц через `html-webpack-plugin` обычно подключают встраиваемые шаблоны просто через команду:
404 |
405 | ```javascript
406 | require("html-loader!./../includes/header.html");
407 | ```
408 |
409 | Но при этом в этих встраиваемых шаблонах синтаксис lodash работать не будет (так и не понял, почему так происходит). И данные из переменной `data` туда не передадутся. Поэтому принудительно говорим webpack, что мы встраиваем именно шаблон, который надо обработать как lodash шаблон.
410 |
411 | Теперь мы можем использовать полноценные lodash синтаксис в встраиваемых шаблонах. В коде файла `header.html` ниже через `<%=title%>` печатаем заголовок статьи.
412 |
413 | ```html
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 | <%=title%>
424 |
425 |
426 |
427 |
428 |
429 | ```
430 |
431 | В пакете html-webpack-plugin [есть возможность](https://github.com/jantimon/html-webpack-plugin#generating-multiple-html-files) генерировать несколько HTML страниц:
432 |
433 | ```javascript
434 | plugins: [
435 | new HtmlWebpackPlugin(), // Generates default index.html
436 | new HtmlWebpackPlugin({
437 | // Also generate a test.html
438 | filename: "test.html",
439 | template: "src/assets/test.html",
440 | }),
441 | ];
442 | ```
443 |
444 | Но прописывать для каждой страницы создание своего экземпляра плагина точно не есть хорошо. Поэтому автоматизируем этот процесс, найдя все HTML файлы в папке `src/html/views` и создадим для них свои версии `new HtmlWebpackPlugin()`.
445 |
446 | Для этого в файле `webpack.config.js` внесем следующие изменения:
447 |
448 | ```javascript
449 | ...
450 | const HtmlWebpackPlugin = require('html-webpack-plugin');
451 | const fs = require('fs')
452 |
453 | function generateHtmlPlugins(templateDir) {
454 | const templateFiles = fs.readdirSync(path.resolve(__dirname, templateDir));
455 | return templateFiles.map(item => {
456 | const parts = item.split('.');
457 | const name = parts[0];
458 | const extension = parts[1];
459 | return new HtmlWebpackPlugin({
460 | filename: `${name}.html`,
461 | template: path.resolve(__dirname, `${templateDir}/${name}.${extension}`),
462 | inject: false,
463 | })
464 | })
465 | }
466 |
467 | const htmlPlugins = generateHtmlPlugins('./src/html/views')
468 |
469 | module.exports = {
470 | module: {
471 | ...
472 | {
473 | test: /\.html$/,
474 | include: path.resolve(__dirname, 'src/html/includes'),
475 | use: ['raw-loader']
476 | },
477 | ]
478 | },
479 | plugins: [
480 | ...
481 | ].concat(htmlPlugins)
482 | };
483 | ```
484 |
485 | Функция `generateHtmlPlugins` будет осуществлять поиск всех HTML страниц. Обратите внимание, что в коде функции есть настройка `inject: false`, которая говорит Webpack, что не нужно встраивать ссылки на JS и CSS файл в HTML код самостоятельно: мы сделаем всё сами вручную в шаблонах `header.html` и `footer.html`.
486 |
487 | Также нужно отметить, что встраиваемые шаблоны обрабатываются плагином `raw-loader` (содержимое файла просто загрузить как текст), а не `html-loader`, как чаще всего предлагают. И также, как в случае с CSS, не использую пакеты `file-loader` или `url-loader`.
488 |
489 | И остается последний необязательный момент для работы с HTML. JavaScript файл и CSS файл у нас будут минимизироваться. А вот HTML файлы хочу, наоборот, сделать красивыми и не минимизировать. Поэтому после сборки всех HTML файлов хочется пройтись по ним каким-то beautify плагином. И тут меня ждала подстава: не нашел способа как это сделать в Webpack. Проблема в том, что обработать файлы нужно после того, как будут вставлены встраиваемые шаблоны.
490 |
491 | Нашел пакет [html-cli](https://www.npmjs.com/package/html-cli), который может это сделать независимо от Webpack. Но у него 38 установок в месяц. То есть это означает два варианта: либо никому не нужно приводить к красивому внешнему виду HTML файлы, либо есть другое популярное решение, о котором я не знаю. А ради только одной этой функции Gulp прикручивать не хочется.
492 |
493 | Устанавливаем этот плагин:
494 |
495 | ```shell
496 | npm install html-cli --save-dev
497 | ```
498 |
499 | И в файле `package.json` прописываем еще два скрипта, которые после работы Webpack будут приводить к красивому внешнему виду HTML файлы с установкой табуляции в два пробела.
500 |
501 | ```json
502 | "scripts": {
503 | "build-and-beautify": "del-cli dist && webpack --mode production && html dist/*.html --indent-size 2",
504 | "beautify": "html dist/*.html --indent-size 2"
505 | },
506 | ```
507 |
508 | Обратите внимание на то, что в команду `build-and-beautify` я добавил еще `del-cli dist`, который очищает папку `dist` перед сборкой.
509 |
510 | Поэтому для итоговой сборки рекомендую использовать не команду `npm run build`, а команду **npm run build-and-beautify**.
511 |
512 | ## Копирование оставшихся файлов
513 |
514 | Мы получили JS, CSS файлы, HTML страницы. Остались файлы изображений, шрифтов и др., которые мы не трогали и сознательно не копировали через `file-loader` или `url-loader`. Поэтому скопируем все оставшиеся папки через плагин `copy-webpack-plugin`:
515 |
516 | ```shell
517 | npm install copy-webpack-plugin --save-dev
518 | ```
519 |
520 | В файле `webpack.config.js` внесем изменения:
521 |
522 | ```javascript
523 | ...
524 | const HtmlWebpackPlugin = require('html-webpack-plugin');
525 | ...
526 |
527 | module.exports = {
528 | ...
529 | plugins: [
530 | ...
531 | new CopyWebpackPlugin([{
532 | from: './src/fonts',
533 | to: './fonts'
534 | },
535 | {
536 | from: './src/favicon',
537 | to: './favicon'
538 | },
539 | {
540 | from: './src/img',
541 | to: './img'
542 | },
543 | {
544 | from: './src/uploads',
545 | to: './uploads'
546 | }
547 | ]),
548 | ]...
549 | };
550 | ```
551 |
552 | Всё. Теперь командой **npm run build-and-beautify** собираем проект и в папке `dist` появится собранный статический сайт.
553 |
554 | 
555 |
556 | ## Итоговые файлы
557 |
558 | Итоговые файлы смотрите в репозитории [https://github.com/Harrix/static-site-webpack-habr](https://github.com/Harrix/static-site-webpack-habr). Они могут частично отличаться от приведенных в статье, так как проект развивается.
559 |
--------------------------------------------------------------------------------
/docs/article_new.md:
--------------------------------------------------------------------------------
1 | # Простой статический сайт на webpack 5
2 |
3 | Данная статья является документацией к проекту [static-site-webpack-habr](https://github.com/Harrix/static-site-webpack-habr). С помощью пакета webpack под Node.js и других пакетов, включая HTML шаблонизатор, создается набор HTML страниц, которые удобно использовать для тестирования HTML шаблонов или для генерации простого статического сайта.
4 |
5 | На Хабре находится устаревшая версия этой [статьи](https://habr.com/ru/post/350886/). За это время некоторые подходы изменились, некоторые пакеты обновились, а некоторые пакеты устарели. Так что в этой статье представлен сильно переработанный вариант того материала.
6 |
7 | ## Постановка задачи
8 |
9 | «Сайт» представляет собой простой набор HTML страниц со своими CSS стилями и файлом JavaScript. Необходимо написать проект, который бы собирал наш сайт из исходников:
10 |
11 | - из SASS (точнее [SCSS](https://sass-lang.com/documentation/syntax)) файлов формируется один CSS файл;
12 | - из различных JavaScript библиотек и пользовательского кода формируется один JavaScript файл;
13 | - HTML страницы собираются с помощью шаблонов, где содержимое шапки и футера можно разнести по отдельным файлам.
14 |
15 |
16 | Что не используем
17 |
18 | В собранном сайте не используются, например, [React](https://reactjs.org), [Vue.js](https://vuejs.org/), [Angular](https://angularjs.org/) или другие подобные фреймворки и библиотеки. Причина не в том, что не люблю эти фреймворки, а в том, что хочу в тестовом примере использовать универсальный подход без привязки к какому-то конкретному JavaScript фреймворку.
19 |
20 | При выборе технологий выбираются по возможности наиболее популярные на текущий момент. По этой причине отказался и от [Grunt](https://gruntjs.com/), и [Gulp](https://gulpjs.com/) в пользу [webpack](https://webpack.js.org), хотя, если честно, синтаксис Gulp мне понравился больше своим однообразием при составлении скриптов.
21 |
22 |
23 |
24 | Будем использовать для сборки [webpack](https://webpack.js.org/).
25 |
26 | Для примера будет сверстано несколько страниц на базе CSS фреймворка [Bootstrap 5](https://getbootstrap.com). Но это только для примера, и вы можете спокойно использовать любой другой CSS движок (например, [Bulma](https://bulma.io)) или не использовать вообще никакой, и всё писать самому.
27 |
28 | Предполагается, что [Node.js](https://nodejs.org) у вас уже установлен (в Windows просто нужно скачать файл-установщик и установить в стиле «далее, далее»), и вы умеете работать с командной строкой [CLI](https://ru.wikipedia.org/wiki/Интерфейс_командной_строки) (в Windows это `cmd` или `PowerShell`).
29 |
30 | Нужно получить в итоге набор готовых HTML страниц, которые можно залить на любой хостинг без дополнительных действий (например, на [GitHub Pages](https://pages.github.com/)) или открыть локально на компьютере без поднятия сервера.
31 |
32 | ## Структура проекта
33 |
34 | Общая структура проекта представлена ниже:
35 |
36 | ```text
37 | .
38 | ├── dist - папка, куда будет собираться итоговый сайт
39 | ├─┬ src - папка с исходниками сайта
40 | │ ├── favicon - папка с файлами иконок для сайта
41 | │ ├── fonts - папка со шрифтами
42 | │ ├─┬ html - папка шаблонов HTML страниц
43 | │ │ ├── includes - папка с встраиваемыми шаблонами (header, footer и др.)
44 | │ │ └── views - папка с самими HTML страницами (без header, footer и др.)
45 | │ ├── img - папка с общими изображениями (логотип, иконки и др.)
46 | │ ├── js - папка с JavaScript файлами
47 | │ ├── scss - папка с SСSS файлами стилей
48 | │ └── uploads - папка с файлами статей (картинки, архивы и др.)
49 | ├── package-lock.json - файл со списком версий пакетов (редактируется автоматически)
50 | ├── package.json - файл настроек Node.js
51 | └── webpack.config.js - файл настроек webpack
52 | ```
53 |
54 | Ниже приведена та же структура проекта, но с отображением файлов, которые присутствуют в примере данной статьи:
55 |
56 | ```text
57 | .
58 | ├── dist
59 | ├─┬ src
60 | │ ├─┬ favicon
61 | │ │ └── favicon.ico
62 | │ ├─┬ fonts
63 | │ │ └── Roboto-Regular.ttf
64 | │ ├─┬ html
65 | │ │ ├─┬ includes
66 | │ │ │ ├── footer.html
67 | │ │ │ └── header.html
68 | │ │ └─┬ views
69 | │ │ ├── index.html
70 | │ │ └── second.html
71 | │ ├─┬ img
72 | │ │ └── logo.svg
73 | │ ├─┬ js
74 | │ │ └── index.js
75 | │ ├─┬ scss
76 | │ │ └── style.scss
77 | │ └─┬ uploads
78 | │ └── test.jpg
79 | ├── package-lock.json
80 | ├── package.json
81 | └── webpack.config.js
82 | ```
83 |
84 | Спорным решением может показаться разделение файлов изображений на две папки: `img` и `uploads`. Так как на мой взгляд размещать все изображения в одной папке — это не очень хороший подход.
85 |
86 |
87 | Про дополнительные файлы
88 |
89 | В папке проекта [static-site-webpack-habr](https://github.com/Harrix/static-site-webpack-habr) находятся также дополнительные служебные файлы и папки (например, `docs` с документацией к проекту, файл `README.md` с описанием проекта в формате Markdown и др.). Но эти остальные файлы и папки несут служебную функцию, так что можно копировать в свой проект только вышеприведенные файлы.
90 |
91 |
92 |
93 | При установке пакетов появится папка `node_modules` с устанавливаемыми пакетами на основании информации из файлов `package.json` и `package-lock.json` (второй файл вы сами не трогаете, так как он генерируется самим Node.js на основании первого файла `package.json`). Эту папку не надо заливать, например, на GitHub через коммиты (через `.gitignore`). С ней вручную вообще лучше ничего не делать.
94 |
95 | Для работы с проектом использую текстовой редактор [Visual Studio Code](https://code.visualstudio.com/), которым крайне доволен. Особенно нравится, что командная строка встроена в саму программу и вызывается через комбинацию клавиш `Ctrl` + `` ` ``.
96 |
97 | 
98 |
99 | Сделаем шаблон Node.js нашего проекта. Для этого создадим папки проекта с вышеописанной структурой (`dist`, `src` и так далее), перейдем в неё в командной строке, где вызовем команду для создания файла `package.json`:
100 |
101 | ```shell
102 | npm init
103 | ```
104 |
105 | На предложенные вопросы можно не отвечать, нажимая клавишу `Enter`, если не хочется заполнять подробную информацию о проекте (он у нас же тестовый проект).
106 |
107 | После этого установим пакеты общего назначения, которые потребуются в любом случае: `webpack`, `webpack-cli` (работу с командной строкой в webpack вынесли в отдельный пакет) и `webpack-dev-server` (для запуска локального сервера на вашем компьютере, чтобы изменения сразу отображались в браузере):
108 |
109 | ```shell
110 | npm install webpack webpack-cli webpack-dev-server --save-dev
111 | ```
112 |
113 | Файл `package.json` на данном этапе выглядит примерно так (версии пакетов у вас будут другими, так как они регулярно обновляться):
114 |
115 | ```json
116 | {
117 | "name": "static-site-webpack-habr",
118 | "version": "2.0.0",
119 | "description": "",
120 | "main": "index.js",
121 | "scripts": {
122 | "test": "echo \"Error: no test specified\" && exit 1"
123 | },
124 | "license": "ISC",
125 | "devDependencies": {
126 | "webpack": "^5.75.0",
127 | "webpack-cli": "^5.0.1",
128 | "webpack-dev-server": "^4.11.1"
129 | }
130 | }
131 | ```
132 |
133 | ## Собираем JavaScript
134 |
--------------------------------------------------------------------------------
/docs/featured-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/docs/featured-image.png
--------------------------------------------------------------------------------
/docs/img/dist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/docs/img/dist.png
--------------------------------------------------------------------------------
/docs/img/visual_studio_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/docs/img/visual_studio_code.png
--------------------------------------------------------------------------------
/img/featured-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/img/featured-image.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "static-site-webpack-habr",
3 | "version": "2.0.0",
4 | "description": "HTML template",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "dev": "webpack --mode development",
8 | "watch": "webpack --mode development --watch",
9 | "start": "webpack serve --no-client-overlay-warnings",
10 | "build": "webpack --mode production && prettier --print-width=120 --parser html --write dist/*.html"
11 | },
12 | "dependencies": {
13 | "@popperjs/core": "^2.11.8",
14 | "bootstrap": "^5.3.3"
15 | },
16 | "devDependencies": {
17 | "clean-webpack-plugin": "^4.0.0",
18 | "copy-webpack-plugin": "^12.0.2",
19 | "css-loader": "^7.1.2",
20 | "css-minimizer-webpack-plugin": "^7.0.0",
21 | "html-webpack-plugin": "^5.6.0",
22 | "mini-css-extract-plugin": "^2.9.0",
23 | "prettier": "^3.3.2",
24 | "raw-loader": "^4.0.2",
25 | "sass": "^1.77.6",
26 | "sass-loader": "^14.2.1",
27 | "terser-webpack-plugin": "^5.3.10",
28 | "webpack": "^5.92.1",
29 | "webpack-cli": "^5.1.4",
30 | "webpack-dev-server": "^5.0.4"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/src/favicon/favicon.ico
--------------------------------------------------------------------------------
/src/fonts/Roboto-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/src/fonts/Roboto-Regular.ttf
--------------------------------------------------------------------------------
/src/html/includes/footer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |