commit e99673964501ff953e70e0addff5e059b673a30d Author: mailcow-bot (from Travis CI) Date: Thu Apr 1 10:20:45 2021 +0000 Deploy mailcow/mailcow-dockerized-docs to github.com/mailcow/mailcow-dockerized-docs.git:gh-pages diff --git a/404.html b/404.html new file mode 100644 index 000000000..233069135 --- /dev/null +++ b/404.html @@ -0,0 +1,1872 @@ + + + + + + + + + + + + + + + + + mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+ +

404 - Not found

+ + +
+
+
+ +
+ + + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 000000000..1cf13b9f9 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/javascripts/bundle.d892486b.min.js b/assets/javascripts/bundle.d892486b.min.js new file mode 100644 index 000000000..a7d263987 --- /dev/null +++ b/assets/javascripts/bundle.d892486b.min.js @@ -0,0 +1,32 @@ +(()=>{var Ri=Object.create,ut=Object.defineProperty,Pi=Object.getPrototypeOf,zt=Object.prototype.hasOwnProperty,$i=Object.getOwnPropertyNames,Vi=Object.getOwnPropertyDescriptor,lt=Object.getOwnPropertySymbols,xr=Object.prototype.propertyIsEnumerable;var Sr=(e,t,r)=>t in e?ut(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,$=(e,t)=>{for(var r in t||(t={}))zt.call(t,r)&&Sr(e,r,t[r]);if(lt)for(var r of lt(t))xr.call(t,r)&&Sr(e,r,t[r]);return e},Di=e=>ut(e,"__esModule",{value:!0});var wr=(e,t)=>{var r={};for(var n in e)zt.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&<)for(var n of lt(e))t.indexOf(n)<0&&xr.call(e,n)&&(r[n]=e[n]);return r},ft=(e,t)=>()=>(t||(t={exports:{}},e(t.exports,t)),t.exports);var Ui=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of $i(t))!zt.call(e,n)&&n!=="default"&&ut(e,n,{get:()=>t[n],enumerable:!(r=Vi(t,n))||r.enumerable});return e},Be=e=>Ui(Di(ut(e!=null?Ri(Pi(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var Or=ft((Qt,Er)=>{(function(e,t){typeof Qt=="object"&&typeof Er!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Qt,function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(T){return!!(T&&T!==document&&T.nodeName!=="HTML"&&T.nodeName!=="BODY"&&"classList"in T&&"contains"in T.classList)}function c(T){var at=T.type,st=T.tagName;return!!(st==="INPUT"&&a[at]&&!T.readOnly||st==="TEXTAREA"&&!T.readOnly||T.isContentEditable)}function u(T){T.classList.contains("focus-visible")||(T.classList.add("focus-visible"),T.setAttribute("data-focus-visible-added",""))}function l(T){!T.hasAttribute("data-focus-visible-added")||(T.classList.remove("focus-visible"),T.removeAttribute("data-focus-visible-added"))}function m(T){T.metaKey||T.altKey||T.ctrlKey||(s(r.activeElement)&&u(r.activeElement),n=!0)}function f(T){n=!1}function h(T){!s(T.target)||(n||c(T.target))&&u(T.target)}function y(T){!s(T.target)||(T.target.classList.contains("focus-visible")||T.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),l(T.target))}function b(T){document.visibilityState==="hidden"&&(o&&(n=!0),z())}function z(){document.addEventListener("mousemove",C),document.addEventListener("mousedown",C),document.addEventListener("mouseup",C),document.addEventListener("pointermove",C),document.addEventListener("pointerdown",C),document.addEventListener("pointerup",C),document.addEventListener("touchmove",C),document.addEventListener("touchstart",C),document.addEventListener("touchend",C)}function P(){document.removeEventListener("mousemove",C),document.removeEventListener("mousedown",C),document.removeEventListener("mouseup",C),document.removeEventListener("pointermove",C),document.removeEventListener("pointerdown",C),document.removeEventListener("pointerup",C),document.removeEventListener("touchmove",C),document.removeEventListener("touchstart",C),document.removeEventListener("touchend",C)}function C(T){T.target.nodeName&&T.target.nodeName.toLowerCase()==="html"||(n=!1,P())}document.addEventListener("keydown",m,!0),document.addEventListener("mousedown",f,!0),document.addEventListener("pointerdown",f,!0),document.addEventListener("touchstart",f,!0),document.addEventListener("visibilitychange",b,!0),z(),r.addEventListener("focus",h,!0),r.addEventListener("blur",y,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var Qr=ft((za,pt)=>{var Tr,_r,Mr,Ar,Lr,Hr,Cr,kr,jr,mt,qt,Fr,Ir,Rr,Pe,Pr,$r,Vr,Dr,Ur,Nr,Wr,zr,dt;(function(e){var t=typeof global=="object"?global:typeof self=="object"?self:typeof this=="object"?this:{};typeof define=="function"&&define.amd?define("tslib",["exports"],function(n){e(r(t,r(n)))}):typeof pt=="object"&&typeof pt.exports=="object"?e(r(t,r(pt.exports))):e(r(t));function r(n,o){return n!==t&&(typeof Object.create=="function"?Object.defineProperty(n,"__esModule",{value:!0}):n.__esModule=!0),function(i,a){return n[i]=o?o(i,a):a}}})(function(e){var t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,o){n.__proto__=o}||function(n,o){for(var i in o)Object.prototype.hasOwnProperty.call(o,i)&&(n[i]=o[i])};Tr=function(n,o){if(typeof o!="function"&&o!==null)throw new TypeError("Class extends value "+String(o)+" is not a constructor or null");t(n,o);function i(){this.constructor=n}n.prototype=o===null?Object.create(o):(i.prototype=o.prototype,new i)},_r=Object.assign||function(n){for(var o,i=1,a=arguments.length;i=0;l--)(u=n[l])&&(c=(s<3?u(c):s>3?u(o,i,c):u(o,i))||c);return s>3&&c&&Object.defineProperty(o,i,c),c},Lr=function(n,o){return function(i,a){o(i,a,n)}},Hr=function(n,o){if(typeof Reflect=="object"&&typeof Reflect.metadata=="function")return Reflect.metadata(n,o)},Cr=function(n,o,i,a){function s(c){return c instanceof i?c:new i(function(u){u(c)})}return new(i||(i=Promise))(function(c,u){function l(h){try{f(a.next(h))}catch(y){u(y)}}function m(h){try{f(a.throw(h))}catch(y){u(y)}}function f(h){h.done?c(h.value):s(h.value).then(l,m)}f((a=a.apply(n,o||[])).next())})},kr=function(n,o){var i={label:0,sent:function(){if(c[0]&1)throw c[1];return c[1]},trys:[],ops:[]},a,s,c,u;return u={next:l(0),throw:l(1),return:l(2)},typeof Symbol=="function"&&(u[Symbol.iterator]=function(){return this}),u;function l(f){return function(h){return m([f,h])}}function m(f){if(a)throw new TypeError("Generator is already executing.");for(;i;)try{if(a=1,s&&(c=f[0]&2?s.return:f[0]?s.throw||((c=s.return)&&c.call(s),0):s.next)&&!(c=c.call(s,f[1])).done)return c;switch(s=0,c&&(f=[f[0]&2,c.value]),f[0]){case 0:case 1:c=f;break;case 4:return i.label++,{value:f[1],done:!1};case 5:i.label++,s=f[1],f=[0];continue;case 7:f=i.ops.pop(),i.trys.pop();continue;default:if(c=i.trys,!(c=c.length>0&&c[c.length-1])&&(f[0]===6||f[0]===2)){i=0;continue}if(f[0]===3&&(!c||f[1]>c[0]&&f[1]=n.length&&(n=void 0),{value:n&&n[a++],done:!n}}};throw new TypeError(o?"Object is not iterable.":"Symbol.iterator is not defined.")},qt=function(n,o){var i=typeof Symbol=="function"&&n[Symbol.iterator];if(!i)return n;var a=i.call(n),s,c=[],u;try{for(;(o===void 0||o-- >0)&&!(s=a.next()).done;)c.push(s.value)}catch(l){u={error:l}}finally{try{s&&!s.done&&(i=a.return)&&i.call(a)}finally{if(u)throw u.error}}return c},Fr=function(){for(var n=[],o=0;o1||l(b,z)})})}function l(b,z){try{m(a[b](z))}catch(P){y(c[0][3],P)}}function m(b){b.value instanceof Pe?Promise.resolve(b.value.v).then(f,h):y(c[0][2],b)}function f(b){l("next",b)}function h(b){l("throw",b)}function y(b,z){b(z),c.shift(),c.length&&l(c[0][0],c[0][1])}},$r=function(n){var o,i;return o={},a("next"),a("throw",function(s){throw s}),a("return"),o[Symbol.iterator]=function(){return this},o;function a(s,c){o[s]=n[s]?function(u){return(i=!i)?{value:Pe(n[s](u)),done:s==="return"}:c?c(u):u}:c}},Vr=function(n){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var o=n[Symbol.asyncIterator],i;return o?o.call(n):(n=typeof mt=="function"?mt(n):n[Symbol.iterator](),i={},a("next"),a("throw"),a("return"),i[Symbol.asyncIterator]=function(){return this},i);function a(c){i[c]=n[c]&&function(u){return new Promise(function(l,m){u=n[c](u),s(l,m,u.done,u.value)})}}function s(c,u,l,m){Promise.resolve(m).then(function(f){c({value:f,done:l})},u)}},Dr=function(n,o){return Object.defineProperty?Object.defineProperty(n,"raw",{value:o}):n.raw=o,n};var r=Object.create?function(n,o){Object.defineProperty(n,"default",{enumerable:!0,value:o})}:function(n,o){n.default=o};Ur=function(n){if(n&&n.__esModule)return n;var o={};if(n!=null)for(var i in n)i!=="default"&&Object.prototype.hasOwnProperty.call(n,i)&&dt(o,n,i);return r(o,n),o},Nr=function(n){return n&&n.__esModule?n:{default:n}},Wr=function(n,o){if(!o.has(n))throw new TypeError("attempted to get private field on non-instance");return o.get(n)},zr=function(n,o,i){if(!o.has(n))throw new TypeError("attempted to set private field on non-instance");return o.set(n,i),i},e("__extends",Tr),e("__assign",_r),e("__rest",Mr),e("__decorate",Ar),e("__param",Lr),e("__metadata",Hr),e("__awaiter",Cr),e("__generator",kr),e("__exportStar",jr),e("__createBinding",dt),e("__values",mt),e("__read",qt),e("__spread",Fr),e("__spreadArrays",Ir),e("__spreadArray",Rr),e("__await",Pe),e("__asyncGenerator",Pr),e("__asyncDelegator",$r),e("__asyncValues",Vr),e("__makeTemplateObject",Dr),e("__importStar",Ur),e("__importDefault",Nr),e("__classPrivateFieldGet",Wr),e("__classPrivateFieldSet",zr)})});var lr=ft((it,ur)=>{(function(t,r){typeof it=="object"&&typeof ur=="object"?ur.exports=r():typeof define=="function"&&define.amd?define([],r):typeof it=="object"?it.ClipboardJS=r():t.ClipboardJS=r()})(it,function(){return function(){var e={134:function(n,o,i){"use strict";i.d(o,{default:function(){return Fi}});var a=i(279),s=i.n(a),c=i(370),u=i.n(c),l=i(817),m=i.n(l);function f(w){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?f=function(d){return typeof d}:f=function(d){return d&&typeof Symbol=="function"&&d.constructor===Symbol&&d!==Symbol.prototype?"symbol":typeof d},f(w)}function h(w,v){if(!(w instanceof v))throw new TypeError("Cannot call a class as a function")}function y(w,v){for(var d=0;d0&&arguments[0]!==void 0?arguments[0]:{};this.action=d.action,this.container=d.container,this.emitter=d.emitter,this.target=d.target,this.text=d.text,this.trigger=d.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"createFakeElement",value:function(){var d=document.documentElement.getAttribute("dir")==="rtl";this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[d?"right":"left"]="-9999px";var A=window.pageYOffset||document.documentElement.scrollTop;return this.fakeElem.style.top="".concat(A,"px"),this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.fakeElem}},{key:"selectFake",value:function(){var d=this,A=this.createFakeElement();this.fakeHandlerCallback=function(){return d.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.container.appendChild(A),this.selectedText=m()(A),this.copyText(),this.removeFake()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=m()(this.target),this.copyText()}},{key:"copyText",value:function(){var d;try{d=document.execCommand(this.action)}catch(A){d=!1}this.handleResult(d)}},{key:"handleResult",value:function(d){this.emitter.emit(d?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),document.activeElement.blur(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var d=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"copy";if(this._action=d,this._action!=="copy"&&this._action!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(d){if(d!==void 0)if(d&&f(d)==="object"&&d.nodeType===1){if(this.action==="copy"&&d.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(this.action==="cut"&&(d.hasAttribute("readonly")||d.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`);this._target=d}else throw new Error('Invalid "target" value, use a valid Element')},get:function(){return this._target}}]),w}(),P=z;function C(w){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?C=function(d){return typeof d}:C=function(d){return d&&typeof Symbol=="function"&&d.constructor===Symbol&&d!==Symbol.prototype?"symbol":typeof d},C(w)}function T(w,v){if(!(w instanceof v))throw new TypeError("Cannot call a class as a function")}function at(w,v){for(var d=0;d0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof I.action=="function"?I.action:this.defaultAction,this.target=typeof I.target=="function"?I.target:this.defaultTarget,this.text=typeof I.text=="function"?I.text:this.defaultText,this.container=C(I.container)==="object"?I.container:document.body}},{key:"listenClick",value:function(I){var X=this;this.listener=u()(I,"click",function(Ye){return X.onClick(Ye)})}},{key:"onClick",value:function(I){var X=I.delegateTarget||I.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new P({action:this.action(X),target:this.target(X),text:this.text(X),container:this.container,trigger:X,emitter:this})}},{key:"defaultAction",value:function(I){return Wt("action",I)}},{key:"defaultTarget",value:function(I){var X=Wt("target",I);if(X)return document.querySelector(X)}},{key:"defaultText",value:function(I){return Wt("text",I)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var I=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],X=typeof I=="string"?[I]:I,Ye=!!document.queryCommandSupported;return X.forEach(function(Ii){Ye=Ye&&!!document.queryCommandSupported(Ii)}),Ye}}]),d}(s()),Fi=ji},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,c){for(;s&&s.nodeType!==o;){if(typeof s.matches=="function"&&s.matches(c))return s;s=s.parentNode}}n.exports=a},438:function(n,o,i){var a=i(828);function s(l,m,f,h,y){var b=u.apply(this,arguments);return l.addEventListener(f,b,y),{destroy:function(){l.removeEventListener(f,b,y)}}}function c(l,m,f,h,y){return typeof l.addEventListener=="function"?s.apply(null,arguments):typeof f=="function"?s.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(b){return s(b,m,f,h,y)}))}function u(l,m,f,h){return function(y){y.delegateTarget=a(y.target,m),y.delegateTarget&&h.call(l,y)}}n.exports=c},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(n,o,i){var a=i(879),s=i(438);function c(f,h,y){if(!f&&!h&&!y)throw new Error("Missing required arguments");if(!a.string(h))throw new TypeError("Second argument must be a String");if(!a.fn(y))throw new TypeError("Third argument must be a Function");if(a.node(f))return u(f,h,y);if(a.nodeList(f))return l(f,h,y);if(a.string(f))return m(f,h,y);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function u(f,h,y){return f.addEventListener(h,y),{destroy:function(){f.removeEventListener(h,y)}}}function l(f,h,y){return Array.prototype.forEach.call(f,function(b){b.addEventListener(h,y)}),{destroy:function(){Array.prototype.forEach.call(f,function(b){b.removeEventListener(h,y)})}}}function m(f,h,y){return s(document.body,f,h,y)}n.exports=c},817:function(n){function o(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),u=document.createRange();u.selectNodeContents(i),c.removeAllRanges(),c.addRange(u),a=c.toString()}return a}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,a,s){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var c=this;function u(){c.off(i,u),a.apply(s,arguments)}return u._=a,this.on(i,u,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),c=0,u=s.length;for(c;c{"use strict";var Aa=/["'&<>]/;ni.exports=La;function La(e){var t=""+e,r=Aa.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r.scheduled||(r.scheduled=Ve.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){if(o===void 0&&(o=0),o!=null&&o>0||o==null&&this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);r.actions.length===0&&(Ve.cancelAnimationFrame(n),r.scheduled=void 0)},t}(xt);var un=function(e){K(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this.active=!0,this.scheduled=void 0;var n=this.actions,o,i=-1;r=r||n.shift();var a=n.length;do if(o=r.execute(r.state,r.delay))break;while(++i=2,!0))}function ie(e){e=e||{};var t=e.connector,r=t===void 0?function(){return new _}:t,n=e.resetOnComplete,o=n===void 0?!0:n,i=e.resetOnError,a=i===void 0?!0:i,s=e.resetOnRefCountZero,c=s===void 0?!0:s,u=null,l=null,m=0,f=!1,h=!1,y=function(){u=l=null,f=h=!1};return g(function(b,z){return m++,l=l!=null?l:r(),l.subscribe(z),u||(u=Se(b).subscribe({next:function(P){return l.next(P)},error:function(P){h=!0;var C=l;a&&y(),C.error(P)},complete:function(){f=!0;var P=l;o&&y(),P.complete()}})),function(){if(m--,c&&!m&&!h&&!f){var P=u;y(),P==null||P.unsubscribe()}}})}function re(e,t,r){var n,o,i,a=!1;return e&&typeof e=="object"?(i=(n=e.bufferSize)!==null&&n!==void 0?n:Infinity,t=(o=e.windowTime)!==null&&o!==void 0?o:Infinity,a=!!e.refCount,r=e.scheduler):i=e!=null?e:Infinity,ie({connector:function(){return new yt(i,t,r)},resetOnError:!0,resetOnComplete:!1,resetOnRefCountZero:a})}function ar(e){return L(function(t,r){return e<=r})}function Cn(e){return g(function(t,r){var n=!1,o=new x(r,function(){o==null||o.unsubscribe(),n=!0},void 0,Z);W(e).subscribe(o),t.subscribe(new x(r,function(i){return n&&r.next(i)}))})}function D(){for(var e=[],t=0;tt==="focus"),D(e===ke()))}var Dn=new _,da=Te(()=>F(new ResizeObserver(e=>{for(let t of e)Dn.next(t)}))).pipe(E(e=>ee.pipe(D(e)).pipe(V(()=>e.disconnect()))),re(1));function Le(e){return{width:e.offsetWidth,height:e.offsetHeight}}function kt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function He(e){return da.pipe(k(t=>t.observe(e)),E(t=>Dn.pipe(L(({target:r})=>r===e),V(()=>t.unobserve(e)),p(()=>Le(e)))),D(Le(e)))}function Un(e){return{x:e.scrollLeft,y:e.scrollTop}}function ha(e){return R(O(e,"scroll"),O(window,"resize")).pipe(p(()=>Un(e)),D(Un(e)))}function Nn(e,t=16){return ha(e).pipe(p(({y:r})=>{let n=Le(e),o=kt(e);return r>=o.height-n.height-t}),Q())}function Wn(e){if(e instanceof HTMLInputElement)e.select();else throw new Error("Not implemented")}var jt={drawer:pe("[data-md-toggle=drawer]"),search:pe("[data-md-toggle=search]")};function zn(e){return jt[e].checked}function Fe(e,t){jt[e].checked!==t&&jt[e].click()}function Ft(e){let t=jt[e];return O(t,"change").pipe(p(()=>t.checked),D(t.checked))}function ba(e){switch(e.tagName){case"INPUT":case"SELECT":case"TEXTAREA":return!0;default:return e.isContentEditable}}function Qn(){return O(window,"keydown").pipe(L(e=>!(e.metaKey||e.ctrlKey)),p(e=>({mode:zn("search")?"search":"global",type:e.key,claim(){e.preventDefault(),e.stopPropagation()}})),L(({mode:e})=>{if(e==="global"){let t=ke();if(typeof t!="undefined")return!ba(t)}return!0}),ie())}function qn(){return new URL(location.href)}function Kn(e){location.href=e.href}function Jn(){return new _}function Yn(){return location.hash.substring(1)}function Bn(e){let t=Qe("a");t.href=e,t.addEventListener("click",r=>r.stopPropagation()),t.click()}function va(){return O(window,"hashchange").pipe(p(Yn),D(Yn()),L(e=>e.length>0),ie())}function Gn(){return va().pipe(E(e=>F(ae(`[id="${e}"]`))))}function qe(e){let t=matchMedia(e);return O(t,"change").pipe(p(r=>r.matches),D(t.matches))}function Xn(){return R(qe("print").pipe(L(Boolean)),O(window,"beforeprint")).pipe(ne(void 0))}function sr(e,t){return e.pipe(E(r=>r?t():ee))}function It(e,t={credentials:"same-origin"}){return Se(fetch(`${e}`,t)).pipe(L(r=>r.status===200))}function ye(e,t){return It(e,t).pipe(E(r=>r.json()),re(1))}function Zn(e,t){let r=new DOMParser;return It(e,t).pipe(E(n=>n.text()),p(n=>r.parseFromString(n,"text/xml")),re(1))}function eo(){return{x:Math.max(0,pageXOffset),y:Math.max(0,pageYOffset)}}function cr({x:e,y:t}){window.scrollTo(e||0,t||0)}function to(){return R(O(window,"scroll",{passive:!0}),O(window,"resize",{passive:!0})).pipe(p(eo),D(eo()))}function ro(){return{width:innerWidth,height:innerHeight}}function no(){return O(window,"resize",{passive:!0}).pipe(p(ro),D(ro()))}function oo(){return B([to(),no()]).pipe(p(([e,t])=>({offset:e,size:t})),re(1))}function Rt(e,{viewport$:t,header$:r}){let n=t.pipe(N("size")),o=B([n,r]).pipe(p(()=>({x:e.offsetLeft,y:e.offsetTop})));return B([r,t,o]).pipe(p(([{height:i},{offset:a,size:s},{x:c,y:u}])=>({offset:{x:a.x-c,y:a.y-u+i},size:s})))}function io(e,{tx$:t}){let r=O(e,"message").pipe(p(({data:n})=>n));return t.pipe(In(()=>r,{leading:!0,trailing:!0}),k(n=>e.postMessage(n)),kn(r),ie())}var ga=pe("#__config"),Ke=JSON.parse(ga.textContent);Ke.base=new URL(Ke.base,qn()).toString().replace(/\/$/,"");function se(){return Ke}function Pt(e){return Ke.features.includes(e)}function G(e,t){return typeof t!="undefined"?Ke.translations[e].replace("#",t.toString()):Ke.translations[e]}function Ce(e,t=document){return pe(`[data-md-component=${e}]`,t)}function me(e,t=document){return q(`[data-md-component=${e}]`,t)}var Wo=Be(lr());function ao(e,t=0){e.setAttribute("tabindex",t.toString())}function so(e){e.removeAttribute("tabindex")}function co(e,t){e.setAttribute("data-md-state","lock"),e.style.top=`-${t}px`}function uo(e){let t=-1*parseInt(e.style.top,10);e.removeAttribute("data-md-state"),e.style.top="",t&&window.scrollTo(0,t)}function lo(e,t){e.setAttribute("data-md-state",t)}function fo(e){e.removeAttribute("data-md-state")}function po(e,t){e.classList.toggle("md-nav__link--active",t)}function mo(e){e.classList.remove("md-nav__link--active")}function ho(e,t){e.firstElementChild.innerHTML=t}function bo(e,t){e.setAttribute("data-md-state",t)}function vo(e){e.removeAttribute("data-md-state")}function go(e,t){e.setAttribute("data-md-state",t)}function yo(e){e.removeAttribute("data-md-state")}function xo(e,t){e.setAttribute("data-md-state",t)}function So(e){e.removeAttribute("data-md-state")}function wo(e,t){e.placeholder=t}function Eo(e){e.placeholder=G("search.placeholder")}function Oo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Oo(e,r)}function U(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="boolean"?n.setAttribute(o,t[o]):t[o]&&n.setAttribute(o,"");for(let o of r)Oo(n,o);return n}function To(e,t){let r=t;if(e.length>r){for(;e[r]!==" "&&--r>0;);return`${e.substring(0,r)}...`}return e}function $t(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function _o(e,t){switch(t){case 0:e.textContent=G("search.result.none");break;case 1:e.textContent=G("search.result.one");break;default:e.textContent=G("search.result.other",$t(t))}}function Mo(e){e.textContent=G("search.result.placeholder")}function Ao(e,t){e.appendChild(t)}function Lo(e){e.innerHTML=""}function Ho(e,t){e.style.top=`${t}px`}function Co(e){e.style.top=""}function ko(e,t){let r=e.firstElementChild;r.style.height=`${t-2*r.offsetTop}px`}function jo(e){let t=e.firstElementChild;t.style.height=""}function Fo(e,t){e.lastElementChild.appendChild(t)}function Io(e,t){e.lastElementChild.setAttribute("data-md-state",t)}function Ro(e,t){e.setAttribute("data-md-state",t)}function fr(e){e.removeAttribute("data-md-state")}function Po(e,t){e.setAttribute("data-md-state",t)}function pr(e){e.removeAttribute("data-md-state")}function $o(e){return U("button",{class:"md-clipboard md-icon",title:G("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}var Ie;(function(e){e[e.TEASER=1]="TEASER",e[e.PARENT=2]="PARENT"})(Ie||(Ie={}));function mr(e,t){let r=t&2,n=t&1,o=Object.keys(e.terms).filter(a=>!e.terms[a]).map(a=>[U("del",null,a)," "]).flat().slice(0,-1),i=e.location;return U("a",{href:i,class:"md-search-result__link",tabIndex:-1},U("article",{class:["md-search-result__article",...r?["md-search-result__article--document"]:[]].join(" "),"data-md-score":e.score.toFixed(2)},r>0&&U("div",{class:"md-search-result__icon md-icon"}),U("h1",{class:"md-search-result__title"},e.title),n>0&&e.text.length>0&&U("p",{class:"md-search-result__teaser"},To(e.text,320)),n>0&&o.length>0&&U("p",{class:"md-search-result__terms"},G("search.result.term.missing"),": ",o)))}function Vo(e){let t=e[0].score,r=[...e],n=r.findIndex(u=>!u.location.includes("#")),[o]=r.splice(n,1),i=r.findIndex(u=>u.scoremr(u,1)),...s.length?[U("details",{class:"md-search-result__more"},U("summary",{tabIndex:-1},s.length>0&&s.length===1?G("search.result.more.one"):G("search.result.more.other",s.length)),s.map(u=>mr(u,1)))]:[]];return U("li",{class:"md-search-result__item"},c)}function Do(e){return U("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>U("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?$t(r):r)))}function Uo(e){return U("div",{class:"md-typeset__scrollwrap"},U("div",{class:"md-typeset__table"},e))}function ya(e){let t=se(),r=new URL(`${e.version}/`,t.base);return U("li",{class:"md-version__item"},U("a",{href:r.toString(),class:"md-version__link"},e.title))}function No(e){let t=se(),[,r]=t.base.match(/([^/]+)\/?$/),n=e.find(({version:o,aliases:i})=>o===r||i.includes(r))||e[0];return U("div",{class:"md-version"},U("span",{class:"md-version__current"},n.title),U("ul",{class:"md-version__list"},e.map(ya)))}var xa=0;function Sa(e,{viewport$:t}){let r=F(e).pipe(E(n=>{let o=n.closest("[data-tabs]");return o instanceof HTMLElement?R(...q("input",o).map(i=>O(i,"change"))):ee}));return R(t.pipe(N("size")),r).pipe(p(()=>{let n=Le(e);return{scroll:kt(e).width>n.width}}),N("scroll"))}function zo(e,t){let r=new _;if(r.pipe(ge(qe("(hover)"))).subscribe(([{scroll:n},o])=>{n&&o?ao(e):so(e)}),Wo.default.isSupported()){let n=e.closest("pre");n.id=`__code_${xa++}`,n.insertBefore($o(n.id),e)}return Sa(e,t).pipe(k(r),V(()=>r.complete()),p(n=>$({ref:e},n)))}function wa(e,{target$:t,print$:r}){return t.pipe(p(n=>n.closest("details:not([open])")),L(n=>e===n),Ct(r),ne(e))}function Qo(e,t){let r=new _;return r.subscribe(()=>{e.setAttribute("open",""),e.scrollIntoView()}),wa(e,t).pipe(k(r),V(()=>r.complete()),ne({ref:e}))}var qo=Qe("table");function Ko(e){return je(e,qo),je(qo,Uo(e)),F({ref:e})}function Jo(e,{target$:t,viewport$:r,print$:n}){return R(...q("pre > code",e).map(o=>zo(o,{viewport$:r})),...q("table:not([class])",e).map(o=>Ko(o)),...q("details",e).map(o=>Qo(o,{target$:t,print$:n})))}function Ea(e,{alert$:t}){return t.pipe(E(r=>R(F(!0),F(!1).pipe(Me(2e3))).pipe(p(n=>({message:r,open:n})))))}function Yo(e,t){let r=new _;return r.pipe(Y(J)).subscribe(({message:n,open:o})=>{ho(e,n),o?bo(e,"open"):vo(e)}),Ea(e,t).pipe(k(r),V(()=>r.complete()),p(n=>$({ref:e},n)))}function Oa({viewport$:e}){if(!Pt("header.autohide"))return F(!1);let t=e.pipe(p(({offset:{y:o}})=>o),ve(2,1),p(([o,i])=>[oMath.abs(i-o.y)>100),p(([,[o]])=>o),Q()),n=Ft("search");return B([e,n]).pipe(p(([{offset:o},i])=>o.y>400&&!i),Q(),E(o=>o?r:F(!1)),D(!1))}function Bo(e,t){return Te(()=>{let r=getComputedStyle(e);return F(r.position==="sticky"||r.position==="-webkit-sticky")}).pipe(or(He(e),Oa(t)),p(([r,{height:n},o])=>({height:r?n:0,sticky:r,hidden:o})),Q((r,n)=>r.sticky===n.sticky&&r.height===n.height&&r.hidden===n.hidden),re(1))}function Go(e,{header$:t,main$:r}){let n=new _;return n.pipe(N("active"),or(t),Y(J)).subscribe(([{active:o},{hidden:i}])=>{o?go(e,i?"hidden":"shadow"):yo(e)}),r.subscribe(o=>n.next(o)),t.pipe(p(o=>$({ref:e},o)))}function Ta(e,{viewport$:t,header$:r}){return Rt(e,{header$:r,viewport$:t}).pipe(p(({offset:{y:n}})=>{let{height:o}=Le(e);return{active:n>=o}}),N("active"))}function Xo(e,t){let r=new _;r.pipe(Y(J)).subscribe(({active:o})=>{o?xo(e,"active"):So(e)});let n=ae("article h1");return typeof n=="undefined"?ee:Ta(n,t).pipe(k(r),V(()=>r.complete()),p(o=>$({ref:e},o)))}function Zo(e,{viewport$:t,header$:r}){let n=r.pipe(p(({height:i})=>i),Q()),o=n.pipe(E(()=>He(e).pipe(p(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),N("bottom"))));return B([n,o,t]).pipe(p(([i,{top:a,bottom:s},{offset:{y:c},size:{height:u}}])=>(u=Math.max(0,u-Math.max(0,a-c,i)-Math.max(0,u+c-s)),{offset:a-i,height:u,active:a-i<=c})),Q((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function _a(e){let t=localStorage.getItem(__prefix("__palette")),r=JSON.parse(t)||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},n=F(...e).pipe(te(o=>O(o,"change").pipe(ne(o))),D(e[Math.max(0,r.index)]),p(o=>({index:e.indexOf(o),color:{scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),re(1));return n.subscribe(o=>{localStorage.setItem(__prefix("__palette"),JSON.stringify(o))}),n}function ei(e){let t=new _;t.subscribe(n=>{for(let[o,i]of Object.entries(n.color))typeof i=="string"&&document.body.setAttribute(`data-md-color-${o}`,i);for(let o=0;ot.complete()),p(n=>$({ref:e},n)))}var dr=Be(lr());function ti({alert$:e}){dr.default.isSupported()&&new M(t=>{new dr.default("[data-clipboard-target], [data-clipboard-text]").on("success",r=>t.next(r))}).subscribe(()=>e.next(G("clipboard.copied")))}function Ma(e){if(e.length<2)return e;let[t,r]=e.sort((i,a)=>i.length-a.length).map(i=>i.replace(/[^/]+$/,"")),n=0;if(t===r)n=t.length;else for(;t.charCodeAt(n)===r.charCodeAt(n);)n++;let o=se();return e.map(i=>i.replace(t.slice(0,n),`${o.base}/`))}function ri({document$:e,location$:t,viewport$:r}){let n=se();if(location.protocol==="file:")return;"scrollRestoration"in history&&(history.scrollRestoration="manual",O(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}));let o=ae("link[rel=icon]");typeof o!="undefined"&&(o.href=o.href);let i=Zn(`${n.base}/sitemap.xml`).pipe(p(u=>Ma(q("loc",u).map(l=>l.textContent))),E(u=>O(document.body,"click").pipe(L(l=>!l.metaKey&&!l.ctrlKey),E(l=>{if(l.target instanceof Element){let m=l.target.closest("a");if(m&&!m.target&&u.includes(m.href))return l.preventDefault(),F({url:new URL(m.href)})}return ee}))),ie()),a=O(window,"popstate").pipe(L(u=>u.state!==null),p(u=>({url:new URL(location.href),offset:u.state})),ie());R(i,a).pipe(Q((u,l)=>u.url.href===l.url.href),p(({url:u})=>u)).subscribe(t);let s=t.pipe(N("pathname"),E(u=>It(u.href).pipe(rt(()=>(Kn(u),ee)))),ie());i.pipe(ot(s)).subscribe(({url:u})=>{history.pushState({},"",`${u}`)});let c=new DOMParser;s.pipe(E(u=>u.text()),p(u=>c.parseFromString(u,"text/html"))).subscribe(e),R(i,a).pipe(ot(e)).subscribe(({url:u,offset:l})=>{u.hash&&!l?Bn(u.hash):cr(l||{y:0})}),e.pipe(ar(1)).subscribe(u=>{for(let l of["title","link[rel=canonical]","meta[name=author]","meta[name=description]","[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=logo], .md-logo","[data-md-component=skip]"]){let m=ae(l),f=ae(l,u);typeof m!="undefined"&&typeof f!="undefined"&&je(m,f)}}),e.pipe(ar(1),p(()=>Ce("container")),E(u=>F(...q("script",u))),Tn(u=>{let l=Qe("script");if(u.src){for(let m of u.getAttributeNames())l.setAttribute(m,u.getAttribute(m));return je(u,l),new M(m=>{l.onload=()=>m.complete()})}else return l.textContent=u.textContent,je(u,l),he})).subscribe(),r.pipe(Cn(i),_n(250),N("offset")).subscribe(({offset:u})=>{history.replaceState(u,"")}),R(i,a).pipe(ve(2,1),L(([u,l])=>u.url.pathname===l.url.pathname),p(([,u])=>u)).subscribe(({offset:u})=>{cr(u||{y:0})})}var Ha=Be(oi());function ii(e){return e.split(/"([^"]+)"/g).map((t,r)=>r&1?t.replace(/^\b|^(?![^\x00-\x7F]|$)|\s+/g," +"):t).join("").replace(/"|(?:^|\s+)[*+\-:^~]+(?=\s+|$)/g,"").trim()}var we;(function(e){e[e.SETUP=0]="SETUP",e[e.READY=1]="READY",e[e.QUERY=2]="QUERY",e[e.RESULT=3]="RESULT"})(we||(we={}));function ai(e){return e.type===1}function si(e){return e.type===2}function Vt(e){return e.type===3}function Ca({config:e,docs:t,index:r}){e.lang.length===1&&e.lang[0]==="en"&&(e.lang=[G("search.config.lang")]),e.separator==="[\\s\\-]+"&&(e.separator=G("search.config.separator"));let n=G("search.config.pipeline").split(/\s*,\s*/).filter(Boolean);return{config:e,docs:t,index:r,pipeline:n}}function ci(e,t){let r=se(),n=new Worker(e),o=new _,i=io(n,{tx$:o}).pipe(p(a=>{if(Vt(a))for(let s of a.data)for(let c of s)c.location=`${r.base}/${c.location}`;return a}),ie());return Se(t).pipe(p(a=>({type:we.SETUP,data:Ca(a)}))).subscribe(o.next.bind(o)),{tx$:o,rx$:i}}function ui(){let e=se();ye(new URL("versions.json",e.base)).subscribe(t=>{pe(".md-header__topic").appendChild(No(t))})}function ka(e){let t=(__search==null?void 0:__search.transform)||ii,r=Vn(e),n=R(O(e,"keyup"),O(e,"focus").pipe(Me(1))).pipe(p(()=>t(e.value)),Q());return B([n,r]).pipe(p(([o,i])=>({value:o,focus:i})))}function li(e,{tx$:t}){let r=new _;return r.pipe(N("value"),p(({value:n})=>({type:we.QUERY,data:n}))).subscribe(t.next.bind(t)),r.pipe(N("focus")).subscribe(({focus:n})=>{n?(Fe("search",n),wo(e,"")):Eo(e)}),O(e.form,"reset").pipe(jn(r.pipe(An(1)))).subscribe(()=>Ae(e)),ka(e).pipe(k(r),V(()=>r.complete()),p(n=>$({ref:e},n)))}function fi(e,{rx$:t},{query$:r}){let n=new _,o=Nn(e.parentElement).pipe(L(Boolean)),i=pe(":scope > :first-child",e);n.pipe(Y(J),ge(r)).subscribe(([{data:c},{value:u}])=>{u?_o(i,c.length):Mo(i)});let a=pe(":scope > :last-child",e);return n.pipe(Y(J),k(()=>Lo(a)),E(({data:c})=>R(F(...c.slice(0,10)),F(...c.slice(10)).pipe(ve(4),Pn(o),E(([u])=>F(...u)))))).subscribe(c=>{Ao(a,Vo(c))}),t.pipe(L(Vt),p(({data:c})=>({data:c})),D({data:[]})).pipe(k(n),V(()=>n.complete()),p(c=>$({ref:e},c)))}function pi(e,{index$:t,keyboard$:r}){let n=se(),o=ci(n.search,t),i=Ce("search-query",e),a=Ce("search-result",e),{tx$:s,rx$:c}=o;s.pipe(L(si),ot(c.pipe(L(ai))),nt(1)).subscribe(s.next.bind(s)),r.pipe(L(({mode:l})=>l==="search")).subscribe(l=>{let m=ke();switch(l.type){case"Enter":m===i&&l.claim();break;case"Escape":case"Tab":Fe("search",!1),Ae(i,!1);break;case"ArrowUp":case"ArrowDown":if(typeof m=="undefined")Ae(i);else{let f=[i,...q(":not(details) > [href], summary, details[open] [href]",a)],h=Math.max(0,(Math.max(0,f.indexOf(m))+f.length+(l.type==="ArrowUp"?-1:1))%f.length);Ae(f[h])}l.claim();break;default:i!==ke()&&Ae(i)}}),r.pipe(L(({mode:l})=>l==="global")).subscribe(l=>{switch(l.type){case"f":case"s":case"/":Ae(i),Wn(i),l.claim();break}});let u=li(i,o);return R(u,fi(a,o,{query$:u}))}function ja(e,{viewport$:t,main$:r}){let n=e.parentElement.offsetTop-e.parentElement.parentElement.offsetTop;return B([r,t]).pipe(p(([{offset:o,height:i},{offset:{y:a}}])=>(i=i+Math.min(n,Math.max(0,a-o))-n,{height:i,locked:a>=o+n})),Q((o,i)=>o.height===i.height&&o.locked===i.locked))}function hr(e,n){var{header$:t}=n,r=wr(n,["header$"]);let o=new _;return o.pipe(Y(J),ge(t)).subscribe({next([{height:i},{height:a}]){ko(e,i),Ho(e,a)},complete(){Co(e),jo(e)}}),ja(e,r).pipe(k(o),V(()=>o.complete()),p(i=>$({ref:e},i)))}function mi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return Ht(ye(`${r}/releases/latest`).pipe(p(n=>({version:n.tag_name})),ze({})),ye(r).pipe(p(n=>({stars:n.stargazers_count,forks:n.forks_count})),ze({}))).pipe(p(([n,o])=>$($({},n),o)))}else{let r=`https://api.github.com/repos/${e}`;return ye(r).pipe(p(n=>({repositories:n.public_repos})),ze({}))}}function di(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return ye(r).pipe(p(({star_count:n,forks_count:o})=>({stars:n,forks:o})),ze({}))}function hi(e){let[t]=e.match(/(git(?:hub|lab))/i)||[];switch(t.toLowerCase()){case"github":let[,r,n]=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);return mi(r,n);case"gitlab":let[,o,i]=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i);return di(o,i);default:return ee}}var Fa;function Ia(e){return Fa||(Fa=Te(()=>{let t=sessionStorage.getItem(__prefix("__source"));if(t)return F(JSON.parse(t));{let r=hi(e.href);return r.subscribe(n=>{try{sessionStorage.setItem(__prefix("__source"),JSON.stringify(n))}catch(o){}}),r}}).pipe(rt(()=>ee),L(t=>Object.keys(t).length>0),p(t=>({facts:t})),re(1)))}function bi(e){let t=new _;return t.subscribe(({facts:r})=>{Fo(e,Do(r)),Io(e,"done")}),Ia(e).pipe(k(t),V(()=>t.complete()),p(r=>$({ref:e},r)))}function Ra(e,{viewport$:t,header$:r}){return He(document.body).pipe(E(()=>Rt(e,{header$:r,viewport$:t})),p(({offset:{y:n}})=>({hidden:n>=10})),N("hidden"))}function vi(e,t){let r=new _;return r.pipe(Y(J)).subscribe({next({hidden:n}){n?Ro(e,"hidden"):fr(e)},complete(){fr(e)}}),Ra(e,t).pipe(k(r),V(()=>r.complete()),p(n=>$({ref:e},n)))}function Pa(e,{viewport$:t,header$:r}){let n=new Map;for(let a of e){let s=decodeURIComponent(a.hash.substring(1)),c=ae(`[id="${s}"]`);typeof c!="undefined"&&n.set(a,c)}let o=r.pipe(p(a=>24+a.height));return He(document.body).pipe(N("height"),p(()=>{let a=[];return[...n].reduce((s,[c,u])=>{for(;a.length&&n.get(a[a.length-1]).tagName>=u.tagName;)a.pop();let l=u.offsetTop;for(;!l&&u.parentElement;)u=u.parentElement,l=u.offsetTop;return s.set([...a=[...a,c]].reverse(),l)},new Map)}),p(a=>new Map([...a].sort(([,s],[,c])=>s-c))),E(a=>B([o,t]).pipe(Hn(([s,c],[u,{offset:{y:l}}])=>{for(;c.length;){let[,m]=c[0];if(m-u=l)c=[s.pop(),...c];else break}return[s,c]},[[],[...a]]),Q((s,c)=>s[0]===c[0]&&s[1]===c[1])))).pipe(p(([a,s])=>({prev:a.map(([c])=>c),next:s.map(([c])=>c)})),D({prev:[],next:[]}),ve(2,1),p(([a,s])=>a.prev.length{for(let[a]of i)mo(a),fo(a);for(let[a,[s]]of o.entries())po(s,a===o.length-1),lo(s,"blur")});let n=q("[href^=\\#]",e);return Pa(n,t).pipe(k(r),V(()=>r.complete()),p(o=>$({ref:e},o)))}function $a(e,{viewport$:t,main$:r}){let n=t.pipe(p(({offset:{y:i}})=>i),ve(2,1),p(([i,a])=>i>a),Q()),o=r.pipe(N("active"));return B([o,n]).pipe(p(([{active:i},a])=>({hidden:!(i&&a)})),Q((i,a)=>i.hidden===a.hidden))}function yi(e,t){let r=new _;return r.pipe(Y(J)).subscribe({next({hidden:n}){n?Po(e,"hidden"):pr(e)},complete(){pr(e)}}),$a(e,t).pipe(k(r),V(()=>r.complete()),p(n=>$({ref:e},n)))}function xi({document$:e,tablet$:t}){e.pipe(E(()=>F(...q("[data-md-state=indeterminate]"))),k(r=>{r.indeterminate=!0,r.checked=!1}),te(r=>O(r,"change").pipe(Fn(()=>r.hasAttribute("data-md-state")),ne(r))),ge(t)).subscribe(([r,n])=>{r.removeAttribute("data-md-state"),n&&(r.checked=!1)})}function Va(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Si({document$:e}){e.pipe(E(()=>F(...q("[data-md-scrollfix]"))),k(t=>t.removeAttribute("data-md-scrollfix")),L(Va),te(t=>O(t,"touchstart").pipe(ne(t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function wi({viewport$:e,tablet$:t}){B([Ft("search"),t]).pipe(p(([r,n])=>r&&!n),E(r=>F(r).pipe(Me(r?400:100),Y(J))),ge(e)).subscribe(([r,{offset:{y:n}}])=>{r?co(document.body,n):uo(document.body)})}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var Je=$n(),br=Jn(),vr=Gn(),gr=Qn(),le=oo(),Dt=qe("(min-width: 960px)"),Ei=qe("(min-width: 1220px)"),Oi=Xn(),Ti=se(),Da=document.forms.namedItem("search")?(__search==null?void 0:__search.index)||ye(`${Ti.base}/search/search_index.json`):ee,yr=new _;ti({alert$:yr});Pt("navigation.instant")&&ri({document$:Je,location$:br,viewport$:le});var _i;((_i=Ti.version)==null?void 0:_i.provider)==="mike"&&ui();R(br,vr).pipe(Me(125)).subscribe(()=>{Fe("drawer",!1),Fe("search",!1)});gr.pipe(L(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=ae("[href][rel=prev]");typeof t!="undefined"&&t.click();break;case"n":case".":let r=ae("[href][rel=next]");typeof r!="undefined"&&r.click();break}});xi({document$:Je,tablet$:Dt});Si({document$:Je});wi({viewport$:le,tablet$:Dt});var Re=Bo(Ce("header"),{viewport$:le}),Ut=Je.pipe(p(()=>Ce("main")),E(e=>Zo(e,{viewport$:le,header$:Re})),re(1)),Ua=R(...me("dialog").map(e=>Yo(e,{alert$:yr})),...me("header").map(e=>Go(e,{viewport$:le,header$:Re,main$:Ut})),...me("palette").map(e=>ei(e)),...me("search").map(e=>pi(e,{index$:Da,keyboard$:gr})),...me("source").map(e=>bi(e))),Na=Te(()=>R(...me("content").map(e=>Jo(e,{target$:vr,viewport$:le,print$:Oi})),...me("header-title").map(e=>Xo(e,{viewport$:le,header$:Re})),...me("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?sr(Ei,()=>hr(e,{viewport$:le,header$:Re,main$:Ut})):sr(Dt,()=>hr(e,{viewport$:le,header$:Re,main$:Ut}))),...me("tabs").map(e=>vi(e,{viewport$:le,header$:Re})),...me("toc").map(e=>gi(e,{viewport$:le,header$:Re})),...me("top").map(e=>yi(e,{viewport$:le,main$:Ut})))),Mi=Je.pipe(E(()=>Na),Ct(Ua),re(1));Mi.subscribe();window.document$=Je;window.location$=br;window.target$=vr;window.keyboard$=gr;window.viewport$=le;window.tablet$=Dt;window.screen$=Ei;window.print$=Oi;window.alert$=yr;window.component$=Mi;})(); +/*! + * clipboard.js v2.0.8 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */ +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ +//# sourceMappingURL=bundle.d892486b.min.js.map + diff --git a/assets/javascripts/bundle.d892486b.min.js.map b/assets/javascripts/bundle.d892486b.min.js.map new file mode 100644 index 000000000..e57c79da4 --- /dev/null +++ b/assets/javascripts/bundle.d892486b.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/tslib/tslib.js", "node_modules/clipboard/dist/clipboard.js", "node_modules/escape-html/index.js", "src/assets/javascripts/bundle.ts", "node_modules/tslib/modules/index.js", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/caughtSchedule.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/fromArray.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/concatMap.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/sample.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/switchMapTo.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/assets/javascripts/browser/document/index.ts", "src/assets/javascripts/browser/element/_/index.ts", "src/assets/javascripts/browser/element/focus/index.ts", "src/assets/javascripts/browser/element/size/index.ts", "src/assets/javascripts/browser/element/offset/index.ts", "src/assets/javascripts/browser/element/selection/index.ts", "src/assets/javascripts/browser/toggle/index.ts", "src/assets/javascripts/browser/keyboard/index.ts", "src/assets/javascripts/browser/location/_/index.ts", "src/assets/javascripts/browser/location/hash/index.ts", "src/assets/javascripts/browser/media/index.ts", "src/assets/javascripts/browser/request/index.ts", "src/assets/javascripts/browser/viewport/offset/index.ts", "src/assets/javascripts/browser/viewport/size/index.ts", "src/assets/javascripts/browser/viewport/_/index.ts", "src/assets/javascripts/browser/worker/index.ts", "src/assets/javascripts/_/index.ts", "src/assets/javascripts/components/_/index.ts", "src/assets/javascripts/components/content/code/index.ts", "src/assets/javascripts/actions/_/index.ts", "src/assets/javascripts/actions/anchor/index.ts", "src/assets/javascripts/actions/dialog/index.ts", "src/assets/javascripts/actions/header/_/index.ts", "src/assets/javascripts/actions/header/title/index.ts", "src/assets/javascripts/actions/search/query/index.ts", "src/assets/javascripts/utilities/h/index.ts", "src/assets/javascripts/utilities/string/index.ts", "src/assets/javascripts/actions/search/result/index.ts", "src/assets/javascripts/actions/sidebar/index.ts", "src/assets/javascripts/actions/source/index.ts", "src/assets/javascripts/actions/tabs/index.ts", "src/assets/javascripts/actions/top/index.ts", "src/assets/javascripts/templates/clipboard/index.tsx", "src/assets/javascripts/templates/search/index.tsx", "src/assets/javascripts/templates/source/index.tsx", "src/assets/javascripts/templates/table/index.tsx", "src/assets/javascripts/templates/version/index.tsx", "src/assets/javascripts/components/content/details/index.ts", "src/assets/javascripts/components/content/table/index.ts", "src/assets/javascripts/components/content/_/index.ts", "src/assets/javascripts/components/dialog/index.ts", "src/assets/javascripts/components/header/_/index.ts", "src/assets/javascripts/components/header/title/index.ts", "src/assets/javascripts/components/main/index.ts", "src/assets/javascripts/components/palette/index.ts", "src/assets/javascripts/integrations/clipboard/index.ts", "src/assets/javascripts/integrations/instant/index.ts", "src/assets/javascripts/integrations/search/document/index.ts", "src/assets/javascripts/integrations/search/query/transform/index.ts", "src/assets/javascripts/integrations/search/worker/message/index.ts", "src/assets/javascripts/integrations/search/worker/_/index.ts", "src/assets/javascripts/integrations/version/index.ts", "src/assets/javascripts/components/search/query/index.ts", "src/assets/javascripts/components/search/result/index.ts", "src/assets/javascripts/components/search/_/index.ts", "src/assets/javascripts/components/sidebar/index.ts", "src/assets/javascripts/components/source/facts/github/index.ts", "src/assets/javascripts/components/source/facts/gitlab/index.ts", "src/assets/javascripts/components/source/facts/_/index.ts", "src/assets/javascripts/components/source/_/index.ts", "src/assets/javascripts/components/tabs/index.ts", "src/assets/javascripts/components/toc/index.ts", "src/assets/javascripts/components/top/index.ts", "src/assets/javascripts/patches/indeterminate/index.ts", "src/assets/javascripts/patches/scrollfix/index.ts", "src/assets/javascripts/patches/scrolllock/index.ts"], + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global global, define, System, Reflect, Promise */\r\nvar __extends;\r\nvar __assign;\r\nvar __rest;\r\nvar __decorate;\r\nvar __param;\r\nvar __metadata;\r\nvar __awaiter;\r\nvar __generator;\r\nvar __exportStar;\r\nvar __values;\r\nvar __read;\r\nvar __spread;\r\nvar __spreadArrays;\r\nvar __spreadArray;\r\nvar __await;\r\nvar __asyncGenerator;\r\nvar __asyncDelegator;\r\nvar __asyncValues;\r\nvar __makeTemplateObject;\r\nvar __importStar;\r\nvar __importDefault;\r\nvar __classPrivateFieldGet;\r\nvar __classPrivateFieldSet;\r\nvar __createBinding;\r\n(function (factory) {\r\n var root = typeof global === \"object\" ? global : typeof self === \"object\" ? self : typeof this === \"object\" ? this : {};\r\n if (typeof define === \"function\" && define.amd) {\r\n define(\"tslib\", [\"exports\"], function (exports) { factory(createExporter(root, createExporter(exports))); });\r\n }\r\n else if (typeof module === \"object\" && typeof module.exports === \"object\") {\r\n factory(createExporter(root, createExporter(module.exports)));\r\n }\r\n else {\r\n factory(createExporter(root));\r\n }\r\n function createExporter(exports, previous) {\r\n if (exports !== root) {\r\n if (typeof Object.create === \"function\") {\r\n Object.defineProperty(exports, \"__esModule\", { value: true });\r\n }\r\n else {\r\n exports.__esModule = true;\r\n }\r\n }\r\n return function (id, v) { return exports[id] = previous ? previous(id, v) : v; };\r\n }\r\n})\r\n(function (exporter) {\r\n var extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n\r\n __extends = function (d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n };\r\n\r\n __assign = Object.assign || function (t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n };\r\n\r\n __rest = function (s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n };\r\n\r\n __decorate = function (decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n };\r\n\r\n __param = function (paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n };\r\n\r\n __metadata = function (metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n };\r\n\r\n __awaiter = function (thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n };\r\n\r\n __generator = function (thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (_) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n };\r\n\r\n __exportStar = function(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n };\r\n\r\n __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\r\n }) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n });\r\n\r\n __values = function (o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n };\r\n\r\n __read = function (o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n };\r\n\r\n /** @deprecated */\r\n __spread = function () {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n };\r\n\r\n /** @deprecated */\r\n __spreadArrays = function () {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n };\r\n\r\n __spreadArray = function (to, from) {\r\n for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)\r\n to[j] = from[i];\r\n return to;\r\n };\r\n\r\n __await = function (v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n };\r\n\r\n __asyncGenerator = function (thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n };\r\n\r\n __asyncDelegator = function (o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n };\r\n\r\n __asyncValues = function (o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n };\r\n\r\n __makeTemplateObject = function (cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n };\r\n\r\n var __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n }) : function(o, v) {\r\n o[\"default\"] = v;\r\n };\r\n\r\n __importStar = function (mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n };\r\n\r\n __importDefault = function (mod) {\r\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\r\n };\r\n\r\n __classPrivateFieldGet = function (receiver, privateMap) {\r\n if (!privateMap.has(receiver)) {\r\n throw new TypeError(\"attempted to get private field on non-instance\");\r\n }\r\n return privateMap.get(receiver);\r\n };\r\n\r\n __classPrivateFieldSet = function (receiver, privateMap, value) {\r\n if (!privateMap.has(receiver)) {\r\n throw new TypeError(\"attempted to set private field on non-instance\");\r\n }\r\n privateMap.set(receiver, value);\r\n return value;\r\n };\r\n\r\n exporter(\"__extends\", __extends);\r\n exporter(\"__assign\", __assign);\r\n exporter(\"__rest\", __rest);\r\n exporter(\"__decorate\", __decorate);\r\n exporter(\"__param\", __param);\r\n exporter(\"__metadata\", __metadata);\r\n exporter(\"__awaiter\", __awaiter);\r\n exporter(\"__generator\", __generator);\r\n exporter(\"__exportStar\", __exportStar);\r\n exporter(\"__createBinding\", __createBinding);\r\n exporter(\"__values\", __values);\r\n exporter(\"__read\", __read);\r\n exporter(\"__spread\", __spread);\r\n exporter(\"__spreadArrays\", __spreadArrays);\r\n exporter(\"__spreadArray\", __spreadArray);\r\n exporter(\"__await\", __await);\r\n exporter(\"__asyncGenerator\", __asyncGenerator);\r\n exporter(\"__asyncDelegator\", __asyncDelegator);\r\n exporter(\"__asyncValues\", __asyncValues);\r\n exporter(\"__makeTemplateObject\", __makeTemplateObject);\r\n exporter(\"__importStar\", __importStar);\r\n exporter(\"__importDefault\", __importDefault);\r\n exporter(\"__classPrivateFieldGet\", __classPrivateFieldGet);\r\n exporter(\"__classPrivateFieldSet\", __classPrivateFieldSet);\r\n});\r\n", "/*!\n * clipboard.js v2.0.8\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 134:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/clipboard-action.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n\n/**\n * Inner class which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n */\n\nvar ClipboardAction = /*#__PURE__*/function () {\n /**\n * @param {Object} options\n */\n function ClipboardAction(options) {\n _classCallCheck(this, ClipboardAction);\n\n this.resolveOptions(options);\n this.initSelection();\n }\n /**\n * Defines base properties passed from constructor.\n * @param {Object} options\n */\n\n\n _createClass(ClipboardAction, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = options.action;\n this.container = options.container;\n this.emitter = options.emitter;\n this.target = options.target;\n this.text = options.text;\n this.trigger = options.trigger;\n this.selectedText = '';\n }\n /**\n * Decides which selection strategy is going to be applied based\n * on the existence of `text` and `target` properties.\n */\n\n }, {\n key: \"initSelection\",\n value: function initSelection() {\n if (this.text) {\n this.selectFake();\n } else if (this.target) {\n this.selectTarget();\n }\n }\n /**\n * Creates a fake textarea element, sets its value from `text` property,\n */\n\n }, {\n key: \"createFakeElement\",\n value: function createFakeElement() {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n this.fakeElem = document.createElement('textarea'); // Prevent zooming on iOS\n\n this.fakeElem.style.fontSize = '12pt'; // Reset box model\n\n this.fakeElem.style.border = '0';\n this.fakeElem.style.padding = '0';\n this.fakeElem.style.margin = '0'; // Move element out of screen horizontally\n\n this.fakeElem.style.position = 'absolute';\n this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n this.fakeElem.style.top = \"\".concat(yPosition, \"px\");\n this.fakeElem.setAttribute('readonly', '');\n this.fakeElem.value = this.text;\n return this.fakeElem;\n }\n /**\n * Get's the value of fakeElem,\n * and makes a selection on it.\n */\n\n }, {\n key: \"selectFake\",\n value: function selectFake() {\n var _this = this;\n\n var fakeElem = this.createFakeElement();\n\n this.fakeHandlerCallback = function () {\n return _this.removeFake();\n };\n\n this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true;\n this.container.appendChild(fakeElem);\n this.selectedText = select_default()(fakeElem);\n this.copyText();\n this.removeFake();\n }\n /**\n * Only removes the fake element after another click event, that way\n * a user can hit `Ctrl+C` to copy because selection still exists.\n */\n\n }, {\n key: \"removeFake\",\n value: function removeFake() {\n if (this.fakeHandler) {\n this.container.removeEventListener('click', this.fakeHandlerCallback);\n this.fakeHandler = null;\n this.fakeHandlerCallback = null;\n }\n\n if (this.fakeElem) {\n this.container.removeChild(this.fakeElem);\n this.fakeElem = null;\n }\n }\n /**\n * Selects the content from element passed on `target` property.\n */\n\n }, {\n key: \"selectTarget\",\n value: function selectTarget() {\n this.selectedText = select_default()(this.target);\n this.copyText();\n }\n /**\n * Executes the copy operation based on the current selection.\n */\n\n }, {\n key: \"copyText\",\n value: function copyText() {\n var succeeded;\n\n try {\n succeeded = document.execCommand(this.action);\n } catch (err) {\n succeeded = false;\n }\n\n this.handleResult(succeeded);\n }\n /**\n * Fires an event based on the copy operation result.\n * @param {Boolean} succeeded\n */\n\n }, {\n key: \"handleResult\",\n value: function handleResult(succeeded) {\n this.emitter.emit(succeeded ? 'success' : 'error', {\n action: this.action,\n text: this.selectedText,\n trigger: this.trigger,\n clearSelection: this.clearSelection.bind(this)\n });\n }\n /**\n * Moves focus away from `target` and back to the trigger, removes current selection.\n */\n\n }, {\n key: \"clearSelection\",\n value: function clearSelection() {\n if (this.trigger) {\n this.trigger.focus();\n }\n\n document.activeElement.blur();\n window.getSelection().removeAllRanges();\n }\n /**\n * Sets the `action` to be performed which can be either 'copy' or 'cut'.\n * @param {String} action\n */\n\n }, {\n key: \"destroy\",\n\n /**\n * Destroy lifecycle.\n */\n value: function destroy() {\n this.removeFake();\n }\n }, {\n key: \"action\",\n set: function set() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy';\n this._action = action;\n\n if (this._action !== 'copy' && this._action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n }\n }\n /**\n * Gets the `action` property.\n * @return {String}\n */\n ,\n get: function get() {\n return this._action;\n }\n /**\n * Sets the `target` property using an element\n * that will be have its content copied.\n * @param {Element} target\n */\n\n }, {\n key: \"target\",\n set: function set(target) {\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (this.action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n\n this._target = target;\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n }\n }\n /**\n * Gets the `target` property.\n * @return {String|HTMLElement}\n */\n ,\n get: function get() {\n return this._target;\n }\n }]);\n\n return ClipboardAction;\n}();\n\n/* harmony default export */ var clipboard_action = (ClipboardAction);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction clipboard_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction clipboard_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction clipboard_createClass(Constructor, protoProps, staticProps) { if (protoProps) clipboard_defineProperties(Constructor.prototype, protoProps); if (staticProps) clipboard_defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n clipboard_classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n clipboard_createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n\n if (this.clipboardAction) {\n this.clipboardAction = null;\n }\n\n this.clipboardAction = new clipboard_action({\n action: this.action(trigger),\n target: this.target(trigger),\n text: this.text(trigger),\n container: this.container,\n trigger: trigger,\n emitter: this\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n\n if (this.clipboardAction) {\n this.clipboardAction.destroy();\n this.clipboardAction = null;\n }\n }\n }], [{\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(134);\n/******/ })()\n.default;\n});", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\nimport { NEVER, Subject, defer, merge } from \"rxjs\"\nimport {\n delay,\n filter,\n map,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs/operators\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getElement,\n requestJSON,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountBackToTop,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountSearch,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantLoading,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget()\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? __search?.index || requestJSON(\n `${config.base}/search/search_index.json`\n )\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up instant loading, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantLoading({ document$, location$, viewport$ })\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector()\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getElement(\"[href][rel=prev]\")\n if (typeof prev !== \"undefined\")\n prev.click()\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getElement(\"[href][rel=next]\")\n if (typeof next !== \"undefined\")\n next.click()\n break\n }\n })\n\n/* Set up patches */\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { target$, viewport$, print$ })),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, { viewport$, header$ })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, main$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Tablet observable */\nwindow.screen$ = screen$ /* Screen observable */\nwindow.print$ = print$ /* Print mode observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.component$ = component$ /* Component observable */\n", "import tslib from '../tslib.js';\r\nconst {\r\n __extends,\r\n __assign,\r\n __rest,\r\n __decorate,\r\n __param,\r\n __metadata,\r\n __awaiter,\r\n __generator,\r\n __exportStar,\r\n __createBinding,\r\n __values,\r\n __read,\r\n __spread,\r\n __spreadArrays,\r\n __spreadArray,\r\n __await,\r\n __asyncGenerator,\r\n __asyncDelegator,\r\n __asyncValues,\r\n __makeTemplateObject,\r\n __importStar,\r\n __importDefault,\r\n __classPrivateFieldGet,\r\n __classPrivateFieldSet,\r\n} = tslib;\r\nexport {\r\n __extends,\r\n __assign,\r\n __rest,\r\n __decorate,\r\n __param,\r\n __metadata,\r\n __awaiter,\r\n __generator,\r\n __exportStar,\r\n __createBinding,\r\n __values,\r\n __read,\r\n __spread,\r\n __spreadArrays,\r\n __spreadArray,\r\n __await,\r\n __asyncGenerator,\r\n __asyncDelegator,\r\n __asyncValues,\r\n __makeTemplateObject,\r\n __importStar,\r\n __importDefault,\r\n __classPrivateFieldGet,\r\n __classPrivateFieldSet,\r\n};\r\n", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ReplaySubject, Subject, fromEvent } from \"rxjs\"\nimport { mapTo } from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch document\n *\n * Documents are implemented as subjects, so all downstream observables are\n * automatically updated when a new document is emitted.\n *\n * @returns Document subject\n */\nexport function watchDocument(): Subject {\n const document$ = new ReplaySubject()\n fromEvent(document, \"DOMContentLoaded\")\n .pipe(\n mapTo(document)\n )\n .subscribe(document$)\n\n /* Return document */\n return document$\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve an element matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element or nothing\n */\nexport function getElement(\n selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T]\n\nexport function getElement(\n selector: string, node?: ParentNode\n): T | undefined\n\nexport function getElement(\n selector: string, node: ParentNode = document\n): T | undefined {\n return node.querySelector(selector) || undefined\n}\n\n/**\n * Retrieve an element matching a query selector or throw a reference error\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getElementOrThrow(\n selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T]\n\nexport function getElementOrThrow(\n selector: string, node?: ParentNode\n): T\n\nexport function getElementOrThrow(\n selector: string, node: ParentNode = document\n): T {\n const el = getElement(selector, node)\n if (typeof el === \"undefined\")\n throw new ReferenceError(\n `Missing element: expected \"${selector}\" to be present`\n )\n return el\n}\n\n/**\n * Retrieve the currently active element\n *\n * @returns Element or nothing\n */\nexport function getActiveElement(): HTMLElement | undefined {\n return document.activeElement instanceof HTMLElement\n ? document.activeElement\n : undefined\n}\n\n/**\n * Retrieve all elements matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getElements(\n selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T][]\n\nexport function getElements(\n selector: string, node?: ParentNode\n): T[]\n\nexport function getElements(\n selector: string, node: ParentNode = document\n): T[] {\n return Array.from(node.querySelectorAll(selector))\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Create an element\n *\n * @template T - Tag name type\n *\n * @param tagName - Tag name\n *\n * @returns Element\n */\nexport function createElement(\n tagName: T\n): HTMLElementTagNameMap[T] {\n return document.createElement(tagName)\n}\n\n/**\n * Replace an element with the given list of nodes\n *\n * @param el - Element\n * @param nodes - Replacement nodes\n */\nexport function replaceElement(\n el: HTMLElement, ...nodes: Node[]\n): void {\n el.replaceWith(...nodes)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, merge } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\nimport { getActiveElement } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set element focus\n *\n * @param el - Element\n * @param value - Whether the element should be focused\n */\nexport function setElementFocus(\n el: HTMLElement, value = true\n): void {\n if (value)\n el.focus()\n else\n el.blur()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element focus\n *\n * @param el - Element\n *\n * @returns Element focus observable\n */\nexport function watchElementFocus(\n el: HTMLElement\n): Observable {\n return merge(\n fromEvent(el, \"focus\"),\n fromEvent(el, \"blur\")\n )\n .pipe(\n map(({ type }) => type === \"focus\"),\n startWith(el === getActiveElement())\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n NEVER,\n Observable,\n Subject,\n defer,\n of\n} from \"rxjs\"\nimport {\n filter,\n finalize,\n map,\n shareReplay,\n startWith,\n switchMap,\n tap\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Element offset\n */\nexport interface ElementSize {\n width: number /* Element width */\n height: number /* Element height */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Resize observer entry subject\n */\nconst entry$ = new Subject()\n\n/**\n * Resize observer observable\n *\n * This observable will create a `ResizeObserver` on the first subscription\n * and will automatically terminate it when there are no more subscribers.\n * It's quite important to centralize observation in a single `ResizeObserver`,\n * as the performance difference can be quite dramatic, as the link shows.\n *\n * @see https://bit.ly/3iIYfEm - Google Groups on performance\n */\nconst observer$ = defer(() => of(\n new ResizeObserver(entries => {\n for (const entry of entries)\n entry$.next(entry)\n })\n))\n .pipe(\n switchMap(resize => NEVER.pipe(startWith(resize))\n .pipe(\n finalize(() => resize.disconnect())\n )\n ),\n shareReplay(1)\n )\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element size\n *\n * @param el - Element\n *\n * @returns Element size\n */\nexport function getElementSize(el: HTMLElement): ElementSize {\n return {\n width: el.offsetWidth,\n height: el.offsetHeight\n }\n}\n\n/**\n * Retrieve element content size, i.e. including overflowing content\n *\n * @param el - Element\n *\n * @returns Element size\n */\nexport function getElementContentSize(el: HTMLElement): ElementSize {\n return {\n width: el.scrollWidth,\n height: el.scrollHeight\n }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element size\n *\n * This function returns an observable that subscribes to a single internal\n * instance of `ResizeObserver` upon subscription, and emit resize events until\n * termination. Note that this function should not be called with the same\n * element twice, as the first unsubscription will terminate observation.\n *\n * Sadly, we can't use the `DOMRect` objects returned by the observer, because\n * we need the emitted values to be consistent with `getElementSize`, which will\n * return the used values (rounded) and not actual values (unrounded). Thus, we\n * use the `offset*` properties. See the linked GitHub issue.\n *\n * @see https://bit.ly/3m0k3he - GitHub issue\n *\n * @param el - Element\n *\n * @returns Element size observable\n */\nexport function watchElementSize(\n el: HTMLElement\n): Observable {\n return observer$\n .pipe(\n tap(observer => observer.observe(el)),\n switchMap(observer => entry$\n .pipe(\n filter(({ target }) => target === el),\n finalize(() => observer.unobserve(el)),\n map(() => getElementSize(el))\n )\n ),\n startWith(getElementSize(el))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, merge } from \"rxjs\"\nimport {\n distinctUntilChanged,\n map,\n startWith\n} from \"rxjs/operators\"\n\nimport {\n getElementContentSize,\n getElementSize\n} from \"../size\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Element offset\n */\nexport interface ElementOffset {\n x: number /* Horizontal offset */\n y: number /* Vertical offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element offset\n *\n * @param el - Element\n *\n * @returns Element offset\n */\nexport function getElementOffset(el: HTMLElement): ElementOffset {\n return {\n x: el.scrollLeft,\n y: el.scrollTop\n }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element offset\n *\n * @param el - Element\n *\n * @returns Element offset observable\n */\nexport function watchElementOffset(\n el: HTMLElement\n): Observable {\n return merge(\n fromEvent(el, \"scroll\"),\n fromEvent(window, \"resize\")\n )\n .pipe(\n map(() => getElementOffset(el)),\n startWith(getElementOffset(el))\n )\n}\n\n/**\n * Watch element threshold\n *\n * This function returns an observable which emits whether the bottom scroll\n * offset of an elements is within a certain threshold.\n *\n * @param el - Element\n * @param threshold - Threshold\n *\n * @returns Element threshold observable\n */\nexport function watchElementThreshold(\n el: HTMLElement, threshold = 16\n): Observable {\n return watchElementOffset(el)\n .pipe(\n map(({ y }) => {\n const visible = getElementSize(el)\n const content = getElementContentSize(el)\n return y >= (\n content.height - visible.height - threshold\n )\n }),\n distinctUntilChanged()\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set element text selection\n *\n * @param el - Element\n */\nexport function setElementSelection(\n el: HTMLElement\n): void {\n if (el instanceof HTMLInputElement)\n el.select()\n else\n throw new Error(\"Not implemented\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\nimport { getElementOrThrow } from \"../element\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Toggle\n */\nexport type Toggle =\n | \"drawer\" /* Toggle for drawer */\n | \"search\" /* Toggle for search */\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Toggle map\n */\nconst toggles: Record = {\n drawer: getElementOrThrow(\"[data-md-toggle=drawer]\"),\n search: getElementOrThrow(\"[data-md-toggle=search]\")\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve the value of a toggle\n *\n * @param name - Toggle\n *\n * @returns Toggle value\n */\nexport function getToggle(name: Toggle): boolean {\n return toggles[name].checked\n}\n\n/**\n * Set toggle\n *\n * Simulating a click event seems to be the most cross-browser compatible way\n * of changing the value while also emitting a `change` event. Before, Material\n * used `CustomEvent` to programmatically change the value of a toggle, but this\n * is a much simpler and cleaner solution which doesn't require a polyfill.\n *\n * @param name - Toggle\n * @param value - Toggle value\n */\nexport function setToggle(name: Toggle, value: boolean): void {\n if (toggles[name].checked !== value)\n toggles[name].click()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch toggle\n *\n * @param name - Toggle\n *\n * @returns Toggle value observable\n */\nexport function watchToggle(name: Toggle): Observable {\n const el = toggles[name]\n return fromEvent(el, \"change\")\n .pipe(\n map(() => el.checked),\n startWith(el.checked)\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent } from \"rxjs\"\nimport { filter, map, share } from \"rxjs/operators\"\n\nimport { getActiveElement } from \"../element\"\nimport { getToggle } from \"../toggle\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Keyboard mode\n */\nexport type KeyboardMode =\n | \"global\" /* Global */\n | \"search\" /* Search is open */\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Keyboard\n */\nexport interface Keyboard {\n mode: KeyboardMode /* Keyboard mode */\n type: string /* Key type */\n claim(): void /* Key claim */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Check whether an element may receive keyboard input\n *\n * @param el - Element\n *\n * @returns Test result\n */\nfunction isSusceptibleToKeyboard(el: HTMLElement): boolean {\n switch (el.tagName) {\n\n /* Form elements */\n case \"INPUT\":\n case \"SELECT\":\n case \"TEXTAREA\":\n return true\n\n /* Everything else */\n default:\n return el.isContentEditable\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch keyboard\n *\n * @returns Keyboard observable\n */\nexport function watchKeyboard(): Observable {\n return fromEvent(window, \"keydown\")\n .pipe(\n filter(ev => !(ev.metaKey || ev.ctrlKey)),\n map(ev => ({\n mode: getToggle(\"search\") ? \"search\" : \"global\",\n type: ev.key,\n claim() {\n ev.preventDefault()\n ev.stopPropagation()\n }\n } as Keyboard)),\n filter(({ mode }) => {\n if (mode === \"global\") {\n const active = getActiveElement()\n if (typeof active !== \"undefined\")\n return !isSusceptibleToKeyboard(active)\n }\n return true\n }),\n share()\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Subject } from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve location\n *\n * This function returns a `URL` object (and not `Location`) to normalize the\n * typings across the application. Furthermore, locations need to be tracked\n * without setting them and `Location` is a singleton which represents the\n * current location.\n *\n * @returns URL\n */\nexport function getLocation(): URL {\n return new URL(location.href)\n}\n\n/**\n * Set location\n *\n * @param url - URL to change to\n */\nexport function setLocation(url: URL): void {\n location.href = url.href\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch location\n *\n * @returns Location subject\n */\nexport function watchLocation(): Subject {\n return new Subject()\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, of } from \"rxjs\"\nimport { filter, map, share, startWith, switchMap } from \"rxjs/operators\"\n\nimport { createElement, getElement } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve location hash\n *\n * @returns Location hash\n */\nexport function getLocationHash(): string {\n return location.hash.substring(1)\n}\n\n/**\n * Set location hash\n *\n * Setting a new fragment identifier via `location.hash` will have no effect\n * if the value doesn't change. When a new fragment identifier is set, we want\n * the browser to target the respective element at all times, which is why we\n * use this dirty little trick.\n *\n * @param hash - Location hash\n */\nexport function setLocationHash(hash: string): void {\n const el = createElement(\"a\")\n el.href = hash\n el.addEventListener(\"click\", ev => ev.stopPropagation())\n el.click()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch location hash\n *\n * @returns Location hash observable\n */\nexport function watchLocationHash(): Observable {\n return fromEvent(window, \"hashchange\")\n .pipe(\n map(getLocationHash),\n startWith(getLocationHash()),\n filter(hash => hash.length > 0),\n share()\n )\n}\n\n/**\n * Watch location target\n *\n * @returns Location target observable\n */\nexport function watchLocationTarget(): Observable {\n return watchLocationHash()\n .pipe(\n switchMap(id => of(getElement(`[id=\"${id}\"]`)!))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable, fromEvent, merge } from \"rxjs\"\nimport {\n filter,\n map,\n mapTo,\n startWith,\n switchMap\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch media query\n *\n * @param query - Media query\n *\n * @returns Media observable\n */\nexport function watchMedia(query: string): Observable {\n const media = matchMedia(query)\n return fromEvent(media, \"change\")\n .pipe(\n map(ev => ev.matches),\n startWith(media.matches)\n )\n}\n\n/**\n * Watch print mode, cross-browser\n *\n * @returns Print mode observable\n */\nexport function watchPrint(): Observable {\n return merge(\n watchMedia(\"print\").pipe(filter(Boolean)), /* Webkit */\n fromEvent(window, \"beforeprint\") /* IE, FF */\n )\n .pipe(\n mapTo(undefined)\n )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Toggle an observable with a media observable\n *\n * @template T - Data type\n *\n * @param query$ - Media observable\n * @param factory - Observable factory\n *\n * @returns Toggled observable\n */\nexport function at(\n query$: Observable, factory: () => Observable\n): Observable {\n return query$\n .pipe(\n switchMap(active => active ? factory() : NEVER)\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, from } from \"rxjs\"\nimport {\n filter,\n map,\n shareReplay,\n switchMap\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch the given URL\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Response observable\n */\nexport function request(\n url: URL | string, options: RequestInit = { credentials: \"same-origin\" }\n): Observable {\n return from(fetch(`${url}`, options))\n .pipe(\n filter(res => res.status === 200),\n )\n}\n\n/**\n * Fetch JSON from the given URL\n *\n * @template T - Data type\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Data observable\n */\nexport function requestJSON(\n url: URL | string, options?: RequestInit\n): Observable {\n return request(url, options)\n .pipe(\n switchMap(res => res.json()),\n shareReplay(1)\n )\n}\n\n/**\n * Fetch XML from the given URL\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Data observable\n */\nexport function requestXML(\n url: URL | string, options?: RequestInit\n): Observable {\n const dom = new DOMParser()\n return request(url, options)\n .pipe(\n switchMap(res => res.text()),\n map(res => dom.parseFromString(res, \"text/xml\")),\n shareReplay(1)\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, merge } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport offset\n */\nexport interface ViewportOffset {\n x: number /* Horizontal offset */\n y: number /* Vertical offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve viewport offset\n *\n * On iOS Safari, viewport offset can be negative due to overflow scrolling.\n * As this may induce strange behaviors downstream, we'll just limit it to 0.\n *\n * @returns Viewport offset\n */\nexport function getViewportOffset(): ViewportOffset {\n return {\n x: Math.max(0, pageXOffset),\n y: Math.max(0, pageYOffset)\n }\n}\n\n/**\n * Set viewport offset\n *\n * @param offset - Viewport offset\n */\nexport function setViewportOffset(\n { x, y }: Partial\n): void {\n window.scrollTo(x || 0, y || 0)\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport offset\n *\n * @returns Viewport offset observable\n */\nexport function watchViewportOffset(): Observable {\n return merge(\n fromEvent(window, \"scroll\", { passive: true }),\n fromEvent(window, \"resize\", { passive: true })\n )\n .pipe(\n map(getViewportOffset),\n startWith(getViewportOffset())\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport size\n */\nexport interface ViewportSize {\n width: number /* Viewport width */\n height: number /* Viewport height */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve viewport size\n *\n * @returns Viewport size\n */\nexport function getViewportSize(): ViewportSize {\n return {\n width: innerWidth,\n height: innerHeight\n }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport size\n *\n * @returns Viewport size observable\n */\nexport function watchViewportSize(): Observable {\n return fromEvent(window, \"resize\", { passive: true })\n .pipe(\n map(getViewportSize),\n startWith(getViewportSize())\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, combineLatest } from \"rxjs\"\nimport {\n distinctUntilKeyChanged,\n map,\n shareReplay\n} from \"rxjs/operators\"\n\nimport { Header } from \"~/components\"\n\nimport {\n ViewportOffset,\n watchViewportOffset\n} from \"../offset\"\nimport {\n ViewportSize,\n watchViewportSize\n} from \"../size\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport\n */\nexport interface Viewport {\n offset: ViewportOffset /* Viewport offset */\n size: ViewportSize /* Viewport size */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch at options\n */\ninterface WatchAtOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
/* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport\n *\n * @returns Viewport observable\n */\nexport function watchViewport(): Observable {\n return combineLatest([\n watchViewportOffset(),\n watchViewportSize()\n ])\n .pipe(\n map(([offset, size]) => ({ offset, size })),\n shareReplay(1)\n )\n}\n\n/**\n * Watch viewport relative to element\n *\n * @param el - Element\n * @param options - Options\n *\n * @returns Viewport observable\n */\nexport function watchViewportAt(\n el: HTMLElement, { viewport$, header$ }: WatchAtOptions\n): Observable {\n const size$ = viewport$\n .pipe(\n distinctUntilKeyChanged(\"size\")\n )\n\n /* Compute element offset */\n const offset$ = combineLatest([size$, header$])\n .pipe(\n map((): ViewportOffset => ({\n x: el.offsetLeft,\n y: el.offsetTop\n }))\n )\n\n /* Compute relative viewport, return hot observable */\n return combineLatest([header$, viewport$, offset$])\n .pipe(\n map(([{ height }, { offset, size }, { x, y }]) => ({\n offset: {\n x: offset.x - x,\n y: offset.y - y + height\n },\n size\n }))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, Subject, fromEvent } from \"rxjs\"\nimport {\n map,\n share,\n switchMapTo,\n tap,\n throttle\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Worker message\n */\nexport interface WorkerMessage {\n type: unknown /* Message type */\n data?: unknown /* Message data */\n}\n\n/**\n * Worker handler\n *\n * @template T - Message type\n */\nexport interface WorkerHandler<\n T extends WorkerMessage\n> {\n tx$: Subject /* Message transmission subject */\n rx$: Observable /* Message receive observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n *\n * @template T - Worker message type\n */\ninterface WatchOptions {\n tx$: Observable /* Message transmission observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch a web worker\n *\n * This function returns an observable that sends all values emitted by the\n * message observable to the web worker. Web worker communication is expected\n * to be bidirectional (request-response) and synchronous. Messages that are\n * emitted during a pending request are throttled, the last one is emitted.\n *\n * @param worker - Web worker\n * @param options - Options\n *\n * @returns Worker message observable\n */\nexport function watchWorker(\n worker: Worker, { tx$ }: WatchOptions\n): Observable {\n\n /* Intercept messages from worker-like objects */\n const rx$ = fromEvent(worker, \"message\")\n .pipe(\n map(({ data }) => data as T)\n )\n\n /* Send and receive messages, return hot observable */\n return tx$\n .pipe(\n throttle(() => rx$, { leading: true, trailing: true }),\n tap(message => worker.postMessage(message)),\n switchMapTo(rx$),\n share()\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { getElementOrThrow, getLocation } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Feature flag\n */\nexport type Flag =\n | \"header.autohide\" /* Hide header */\n | \"navigation.expand\" /* Automatic expansion */\n | \"navigation.instant\" /* Instant loading */\n | \"navigation.sections\" /* Sections navigation */\n | \"navigation.tabs\" /* Tabs navigation */\n | \"navigation.top\" /* Back-to-top button */\n | \"toc.integrate\" /* Integrated table of contents */\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Translation\n */\nexport type Translation =\n | \"clipboard.copy\" /* Copy to clipboard */\n | \"clipboard.copied\" /* Copied to clipboard */\n | \"search.config.lang\" /* Search language */\n | \"search.config.pipeline\" /* Search pipeline */\n | \"search.config.separator\" /* Search separator */\n | \"search.placeholder\" /* Search */\n | \"search.result.placeholder\" /* Type to start searching */\n | \"search.result.none\" /* No matching documents */\n | \"search.result.one\" /* 1 matching document */\n | \"search.result.other\" /* # matching documents */\n | \"search.result.more.one\" /* 1 more on this page */\n | \"search.result.more.other\" /* # more on this page */\n | \"search.result.term.missing\" /* Missing */\n\n/**\n * Translations\n */\nexport type Translations = Record\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Versioning\n */\nexport interface Versioning {\n provider: \"mike\" /* Version provider */\n}\n\n/**\n * Configuration\n */\nexport interface Config {\n base: string /* Base URL */\n features: Flag[] /* Feature flags */\n translations: Translations /* Translations */\n search: string /* Search worker URL */\n version?: Versioning /* Versioning */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve global configuration and make base URL absolute\n */\nconst script = getElementOrThrow(\"#__config\")\nconst config: Config = JSON.parse(script.textContent!)\nconfig.base = new URL(config.base, getLocation())\n .toString()\n .replace(/\\/$/, \"\")\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve global configuration\n *\n * @returns Global configuration\n */\nexport function configuration(): Config {\n return config\n}\n\n/**\n * Check whether a feature flag is enabled\n *\n * @param flag - Feature flag\n *\n * @returns Test result\n */\nexport function feature(flag: Flag): boolean {\n return config.features.includes(flag)\n}\n\n/**\n * Retrieve the translation for the given key\n *\n * @param key - Key to be translated\n * @param value - Positional value, if any\n *\n * @returns Translation\n */\nexport function translation(\n key: Translation, value?: string | number\n): string {\n return typeof value !== \"undefined\"\n ? config.translations[key].replace(\"#\", value.toString())\n : config.translations[key]\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { getElementOrThrow, getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Component\n */\nexport type ComponentType =\n | \"announce\" /* Announcement bar */\n | \"container\" /* Container */\n | \"content\" /* Content */\n | \"dialog\" /* Dialog */\n | \"header\" /* Header */\n | \"header-title\" /* Header title */\n | \"header-topic\" /* Header topic */\n | \"main\" /* Main area */\n | \"palette\" /* Color palette */\n | \"search\" /* Search */\n | \"search-query\" /* Search input */\n | \"search-result\" /* Search results */\n | \"sidebar\" /* Sidebar */\n | \"skip\" /* Skip link */\n | \"source\" /* Repository information */\n | \"tabs\" /* Navigation tabs */\n | \"toc\" /* Table of contents */\n | \"top\" /* Back-to-top button */\n\n/**\n * A component\n *\n * @template T - Component type\n * @template U - Reference type\n */\nexport type Component<\n T extends {} = {},\n U extends HTMLElement = HTMLElement\n> =\n T & {\n ref: U /* Component reference */\n }\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Component type map\n */\ninterface ComponentTypeMap {\n \"announce\": HTMLElement /* Announcement bar */\n \"container\": HTMLElement /* Container */\n \"content\": HTMLElement /* Content */\n \"dialog\": HTMLElement /* Dialog */\n \"header\": HTMLElement /* Header */\n \"header-title\": HTMLElement /* Header title */\n \"header-topic\": HTMLElement /* Header topic */\n \"main\": HTMLElement /* Main area */\n \"palette\": HTMLElement /* Color palette */\n \"search\": HTMLElement /* Search */\n \"search-query\": HTMLInputElement /* Search input */\n \"search-result\": HTMLElement /* Search results */\n \"sidebar\": HTMLElement /* Sidebar */\n \"skip\": HTMLAnchorElement /* Skip link */\n \"source\": HTMLAnchorElement /* Repository information */\n \"tabs\": HTMLElement /* Navigation tabs */\n \"toc\": HTMLElement /* Table of contents */\n \"top\": HTMLAnchorElement /* Back-to-top button */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve the element for a given component or throw a reference error\n *\n * @template T - Component type\n *\n * @param type - Component type\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getComponentElement(\n type: T, node: ParentNode = document\n): ComponentTypeMap[T] {\n return getElementOrThrow(`[data-md-component=${type}]`, node)\n}\n\n/**\n * Retrieve all elements for a given component\n *\n * @template T - Component type\n *\n * @param type - Component type\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getComponentElements(\n type: T, node: ParentNode = document\n): ComponentTypeMap[T][] {\n return getElements(`[data-md-component=${type}]`, node)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ClipboardJS from \"clipboard\"\nimport {\n NEVER,\n Observable,\n Subject,\n fromEvent,\n merge,\n of\n} from \"rxjs\"\nimport {\n distinctUntilKeyChanged,\n finalize,\n map,\n switchMap,\n tap,\n withLatestFrom\n} from \"rxjs/operators\"\n\nimport { resetFocusable, setFocusable } from \"~/actions\"\nimport {\n Viewport,\n getElementContentSize,\n getElementSize,\n getElements,\n watchMedia\n} from \"~/browser\"\nimport { renderClipboardButton } from \"~/templates\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Code block\n */\nexport interface CodeBlock {\n scroll: boolean /* Code block overflows */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Global index for Clipboard.js integration\n */\nlet index = 0\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch code block\n *\n * This function monitors size changes of the viewport, as well as switches of\n * content tabs with embedded code blocks, as both may trigger overflow.\n *\n * @param el - Code block element\n * @param options - Options\n *\n * @returns Code block observable\n */\nexport function watchCodeBlock(\n el: HTMLElement, { viewport$ }: WatchOptions\n): Observable {\n const container$ = of(el)\n .pipe(\n switchMap(child => {\n const container = child.closest(\"[data-tabs]\")\n if (container instanceof HTMLElement) {\n return merge(\n ...getElements(\"input\", container)\n .map(input => fromEvent(input, \"change\"))\n )\n }\n return NEVER\n })\n )\n\n /* Check overflow on resize and tab change */\n return merge(\n viewport$.pipe(distinctUntilKeyChanged(\"size\")),\n container$\n )\n .pipe(\n map(() => {\n const visible = getElementSize(el)\n const content = getElementContentSize(el)\n return {\n scroll: content.width > visible.width\n }\n }),\n distinctUntilKeyChanged(\"scroll\")\n )\n}\n\n/**\n * Mount code block\n *\n * This function ensures that an overflowing code block is focusable through\n * keyboard, so it can be scrolled without a mouse to improve on accessibility.\n *\n * @param el - Code block element\n * @param options - Options\n *\n * @returns Code block component observable\n */\nexport function mountCodeBlock(\n el: HTMLElement, options: MountOptions\n): Observable> {\n const internal$ = new Subject()\n internal$\n .pipe(\n withLatestFrom(watchMedia(\"(hover)\"))\n )\n .subscribe(([{ scroll }, hover]) => {\n if (scroll && hover)\n setFocusable(el)\n else\n resetFocusable(el)\n })\n\n /* Render button for Clipboard.js integration */\n if (ClipboardJS.isSupported()) {\n const parent = el.closest(\"pre\")!\n parent.id = `__code_${index++}`\n parent.insertBefore(\n renderClipboardButton(parent.id),\n el\n )\n }\n\n /* Create and return component */\n return watchCodeBlock(el, options)\n .pipe(\n tap(internal$),\n finalize(() => internal$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set focusable property\n *\n * @param el - Element\n * @param value - Tabindex value\n */\nexport function setFocusable(\n el: HTMLElement, value = 0\n): void {\n el.setAttribute(\"tabindex\", value.toString())\n}\n\n/**\n * Reset focusable property\n *\n * @param el - Element\n */\nexport function resetFocusable(\n el: HTMLElement\n): void {\n el.removeAttribute(\"tabindex\")\n}\n\n/**\n * Set scroll lock\n *\n * @param el - Scrollable element\n * @param value - Vertical offset\n */\nexport function setScrollLock(\n el: HTMLElement, value: number\n): void {\n el.setAttribute(\"data-md-state\", \"lock\")\n el.style.top = `-${value}px`\n}\n\n/**\n * Reset scroll lock\n *\n * @param el - Scrollable element\n */\nexport function resetScrollLock(\n el: HTMLElement\n): void {\n const value = -1 * parseInt(el.style.top, 10)\n el.removeAttribute(\"data-md-state\")\n el.style.top = \"\"\n if (value)\n window.scrollTo(0, value)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set anchor state\n *\n * @param el - Anchor element\n * @param state - Anchor state\n */\nexport function setAnchorState(\n el: HTMLElement, state: \"blur\"\n): void {\n el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset anchor state\n *\n * @param el - Anchor element\n */\nexport function resetAnchorState(\n el: HTMLElement\n): void {\n el.removeAttribute(\"data-md-state\")\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Set anchor active\n *\n * @param el - Anchor element\n * @param value - Whether the anchor is active\n */\nexport function setAnchorActive(\n el: HTMLElement, value: boolean\n): void {\n el.classList.toggle(\"md-nav__link--active\", value)\n}\n\n/**\n * Reset anchor active\n *\n * @param el - Anchor element\n */\nexport function resetAnchorActive(\n el: HTMLElement\n): void {\n el.classList.remove(\"md-nav__link--active\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set dialog message\n *\n * @param el - Dialog element\n * @param value - Dialog message\n */\nexport function setDialogMessage(\n el: HTMLElement, value: string\n): void {\n el.firstElementChild!.innerHTML = value\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Set dialog state\n *\n * @param el - Dialog element\n * @param state - Dialog state\n */\nexport function setDialogState(\n el: HTMLElement, state: \"open\"\n): void {\n el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset dialog state\n *\n * @param el - Dialog element\n */\nexport function resetDialogState(\n el: HTMLElement\n): void {\n el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set header state\n *\n * @param el - Header element\n * @param state - Header state\n */\nexport function setHeaderState(\n el: HTMLElement, state: \"shadow\" | \"hidden\"\n): void {\n el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset header state\n *\n * @param el - Header element\n */\nexport function resetHeaderState(\n el: HTMLElement\n): void {\n el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set header title state\n *\n * @param el - Header title element\n * @param state - Header title state\n */\nexport function setHeaderTitleState(\n el: HTMLElement, state: \"active\"\n): void {\n el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset header title state\n *\n * @param el - Header title element\n */\nexport function resetHeaderTitleState(\n el: HTMLElement\n): void {\n el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set search query placeholder\n *\n * @param el - Search query element\n * @param value - Placeholder\n */\nexport function setSearchQueryPlaceholder(\n el: HTMLInputElement, value: string\n): void {\n el.placeholder = value\n}\n\n/**\n * Reset search query placeholder\n *\n * @param el - Search query element\n */\nexport function resetSearchQueryPlaceholder(\n el: HTMLInputElement\n): void {\n el.placeholder = translation(\"search.placeholder\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { JSX as JSXInternal } from \"preact\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * HTML attributes\n */\ntype Attributes =\n & JSXInternal.HTMLAttributes\n & JSXInternal.SVGAttributes\n & Record\n\n/**\n * Child element\n */\ntype Child =\n | HTMLElement\n | Text\n | string\n | number\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Append a child node to an element\n *\n * @param el - Element\n * @param child - Child node(s)\n */\nfunction appendChild(el: HTMLElement, child: Child | Child[]): void {\n\n /* Handle primitive types (including raw HTML) */\n if (typeof child === \"string\" || typeof child === \"number\") {\n el.innerHTML += child.toString()\n\n /* Handle nodes */\n } else if (child instanceof Node) {\n el.appendChild(child)\n\n /* Handle nested children */\n } else if (Array.isArray(child)) {\n for (const node of child)\n appendChild(el, node)\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * JSX factory\n *\n * @param tag - HTML tag\n * @param attributes - HTML attributes\n * @param children - Child elements\n *\n * @returns Element\n */\nexport function h(\n tag: string, attributes: Attributes | null, ...children: Child[]\n): HTMLElement {\n const el = document.createElement(tag)\n\n /* Set attributes, if any */\n if (attributes)\n for (const attr of Object.keys(attributes))\n if (typeof attributes[attr] !== \"boolean\")\n el.setAttribute(attr, attributes[attr])\n else if (attributes[attr])\n el.setAttribute(attr, \"\")\n\n /* Append child nodes */\n for (const child of children)\n appendChild(el, child)\n\n /* Return element */\n return el\n}\n\n/* ----------------------------------------------------------------------------\n * Namespace\n * ------------------------------------------------------------------------- */\n\nexport declare namespace h {\n namespace JSX {\n type Element = HTMLElement\n type IntrinsicElements = JSXInternal.IntrinsicElements\n }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Truncate a string after the given number of characters\n *\n * This is not a very reasonable approach, since the summaries kind of suck.\n * It would be better to create something more intelligent, highlighting the\n * search occurrences and making a better summary out of it, but this note was\n * written three years ago, so who knows if we'll ever fix it.\n *\n * @param value - Value to be truncated\n * @param n - Number of characters\n *\n * @returns Truncated value\n */\nexport function truncate(value: string, n: number): string {\n let i = n\n if (value.length > i) {\n while (value[i] !== \" \" && --i > 0) { /* keep eating */ }\n return `${value.substring(0, i)}...`\n }\n return value\n}\n\n/**\n * Round a number for display with repository facts\n *\n * This is a reverse-engineered version of GitHub's weird rounding algorithm\n * for stars, forks and all other numbers. While all numbers below `1,000` are\n * returned as-is, bigger numbers are converted to fixed numbers:\n *\n * - `1,049` => `1k`\n * - `1,050` => `1.1k`\n * - `1,949` => `1.9k`\n * - `1,950` => `2k`\n *\n * @param value - Original value\n *\n * @returns Rounded value\n */\nexport function round(value: number): string {\n if (value > 999) {\n const digits = +((value - 950) % 1000 > 99)\n return `${((value + 0.000001) / 1000).toFixed(digits)}k`\n } else {\n return value.toString()\n }\n}\n\n/**\n * Simple hash function\n *\n * @see https://bit.ly/2wsVjJ4 - Original source\n *\n * @param value - Value to be hashed\n *\n * @returns Hash as 32bit integer\n */\nexport function hash(value: string): number {\n let h = 0\n for (let i = 0, len = value.length; i < len; i++) {\n h = ((h << 5) - h) + value.charCodeAt(i)\n h |= 0 // Convert to 32bit integer\n }\n return h\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport { round } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set number of search results\n *\n * @param el - Search result metadata element\n * @param value - Number of results\n */\nexport function setSearchResultMeta(\n el: HTMLElement, value: number\n): void {\n switch (value) {\n\n /* No results */\n case 0:\n el.textContent = translation(\"search.result.none\")\n break\n\n /* One result */\n case 1:\n el.textContent = translation(\"search.result.one\")\n break\n\n /* Multiple result */\n default:\n el.textContent = translation(\"search.result.other\", round(value))\n }\n}\n\n/**\n * Reset number of search results\n *\n * @param el - Search result metadata element\n */\nexport function resetSearchResultMeta(\n el: HTMLElement\n): void {\n el.textContent = translation(\"search.result.placeholder\")\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Add an element to the search result list\n *\n * @param el - Search result list element\n * @param child - Search result element\n */\nexport function addToSearchResultList(\n el: HTMLElement, child: Element\n): void {\n el.appendChild(child)\n}\n\n/**\n * Reset search result list\n *\n * @param el - Search result list element\n */\nexport function resetSearchResultList(\n el: HTMLElement\n): void {\n el.innerHTML = \"\"\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set sidebar offset\n *\n * @param el - Sidebar element\n * @param value - Sidebar offset\n */\nexport function setSidebarOffset(\n el: HTMLElement, value: number\n): void {\n el.style.top = `${value}px`\n}\n\n/**\n * Reset sidebar offset\n *\n * @param el - Sidebar element\n */\nexport function resetSidebarOffset(\n el: HTMLElement\n): void {\n el.style.top = \"\"\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Set sidebar height\n *\n * This function doesn't set the height of the actual sidebar, but of its first\n * child \u2013 the `.md-sidebar__scrollwrap` element in order to mitigiate jittery\n * sidebars when the footer is scrolled into view. At some point we switched\n * from `absolute` / `fixed` positioning to `sticky` positioning, significantly\n * reducing jitter in some browsers (respectively Firefox and Safari) when\n * scrolling from the top. However, top-aligned sticky positioning means that\n * the sidebar snaps to the bottom when the end of the container is reached.\n * This is what leads to the mentioned jitter, as the sidebar's height may be\n * updated too slowly.\n *\n * This behaviour can be mitigiated by setting the height of the sidebar to `0`\n * while preserving the padding, and the height on its first element.\n *\n * @param el - Sidebar element\n * @param value - Sidebar height\n */\nexport function setSidebarHeight(\n el: HTMLElement, value: number\n): void {\n const scrollwrap = el.firstElementChild as HTMLElement\n scrollwrap.style.height = `${value - 2 * scrollwrap.offsetTop}px`\n}\n\n/**\n * Reset sidebar height\n *\n * @param el - Sidebar element\n */\nexport function resetSidebarHeight(\n el: HTMLElement\n): void {\n const scrollwrap = el.firstElementChild as HTMLElement\n scrollwrap.style.height = \"\"\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set repository facts\n *\n * @param el - Repository element\n * @param child - Repository facts element\n */\nexport function setSourceFacts(\n el: HTMLElement, child: Element\n): void {\n el.lastElementChild!.appendChild(child)\n}\n\n/**\n * Set repository state\n *\n * @param el - Repository element\n * @param state - Repository state\n */\nexport function setSourceState(\n el: HTMLElement, state: \"done\"\n): void {\n el.lastElementChild!.setAttribute(\"data-md-state\", state)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set tabs state\n *\n * @param el - Tabs element\n * @param state - Tabs state\n */\nexport function setTabsState(\n el: HTMLElement, state: \"hidden\"\n): void {\n el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset tabs state\n *\n * @param el - Tabs element\n */\nexport function resetTabsState(\n el: HTMLElement\n): void {\n el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set back-to-top state\n *\n * @param el - Back-to-top element\n * @param state - Back-to-top state\n */\nexport function setBackToTopState(\n el: HTMLElement, state: \"hidden\"\n): void {\n el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset back-to-top state\n *\n * @param el - Back-to-top element\n */\nexport function resetBackToTopState(\n el: HTMLElement\n): void {\n el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a 'copy-to-clipboard' button\n *\n * @param id - Unique identifier\n *\n * @returns Element\n */\nexport function renderClipboardButton(id: string): HTMLElement {\n return (\n code`}\n >\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport {\n SearchDocument,\n SearchMetadata,\n SearchResult\n} from \"~/integrations/search\"\nimport { h, truncate } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Render flag\n */\nconst enum Flag {\n TEASER = 1, /* Render teaser */\n PARENT = 2 /* Render as parent */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper function\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a search document\n *\n * @param document - Search document\n * @param flag - Render flags\n *\n * @returns Element\n */\nfunction renderSearchDocument(\n document: SearchDocument & SearchMetadata, flag: Flag\n): HTMLElement {\n const parent = flag & Flag.PARENT\n const teaser = flag & Flag.TEASER\n\n /* Render missing query terms */\n const missing = Object.keys(document.terms)\n .filter(key => !document.terms[key])\n .map(key => [{key}, \" \"])\n .flat()\n .slice(0, -1)\n\n /* Render article or section, depending on flags */\n const url = document.location\n return (\n \n \n {parent > 0 &&
}\n

{document.title}

\n {teaser > 0 && document.text.length > 0 &&\n

\n {truncate(document.text, 320)}\n

\n }\n {teaser > 0 && missing.length > 0 &&\n

\n {translation(\"search.result.term.missing\")}: {...missing}\n

\n }\n \n
\n )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a search result\n *\n * @param result - Search result\n *\n * @returns Element\n */\nexport function renderSearchResult(\n result: SearchResult\n): HTMLElement {\n const threshold = result[0].score\n const docs = [...result]\n\n /* Find and extract parent article */\n const parent = docs.findIndex(doc => !doc.location.includes(\"#\"))\n const [article] = docs.splice(parent, 1)\n\n /* Determine last index above threshold */\n let index = docs.findIndex(doc => doc.score < threshold)\n if (index === -1)\n index = docs.length\n\n /* Partition sections */\n const best = docs.slice(0, index)\n const more = docs.slice(index)\n\n /* Render children */\n const children = [\n renderSearchDocument(article, Flag.PARENT | +(!parent && index === 0)),\n ...best.map(section => renderSearchDocument(section, Flag.TEASER)),\n ...more.length ? [\n
\n \n {more.length > 0 && more.length === 1\n ? translation(\"search.result.more.one\")\n : translation(\"search.result.more.other\", more.length)\n }\n \n {...more.map(section => renderSearchDocument(section, Flag.TEASER))}\n
\n ] : []\n ]\n\n /* Render search result */\n return (\n
  • \n {children}\n
  • \n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SourceFacts } from \"~/components\"\nimport { h, round } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render repository facts\n *\n * @param facts - Repository facts\n *\n * @returns Element\n */\nexport function renderSourceFacts(facts: SourceFacts): HTMLElement {\n return (\n
      \n {Object.entries(facts).map(([key, value]) => (\n
    • \n {typeof value === \"number\" ? round(value) : value}\n
    • \n ))}\n
    \n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a table inside a wrapper to improve scrolling on mobile\n *\n * @param table - Table element\n *\n * @returns Element\n */\nexport function renderTable(table: HTMLElement): HTMLElement {\n return (\n
    \n
    \n {table}\n
    \n
    \n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { configuration } from \"~/_\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Version\n */\nexport interface Version {\n version: string /* Version identifier */\n title: string /* Version title */\n aliases: string[] /* Version aliases */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a version\n *\n * @param version - Version\n *\n * @returns Element\n */\nfunction renderVersion(version: Version): HTMLElement {\n const config = configuration()\n\n /* Ensure trailing slash, see https://bit.ly/3rL5u3f */\n const url = new URL(`${version.version}/`, config.base)\n return (\n
  • \n \n {version.title}\n \n
  • \n )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a version selector\n *\n * @param versions - Versions\n *\n * @returns Element\n */\nexport function renderVersionSelector(versions: Version[]): HTMLElement {\n const config = configuration()\n\n /* Determine active version */\n const [, current] = config.base.match(/([^/]+)\\/?$/)!\n const active =\n versions.find(({ version, aliases }) => (\n version === current || aliases.includes(current)\n )) || versions[0]\n\n /* Render version selector */\n return (\n
    \n \n {active.title}\n \n
      \n {versions.map(renderVersion)}\n
    \n
    \n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, Subject } from \"rxjs\"\nimport {\n filter,\n finalize,\n map,\n mapTo,\n mergeWith,\n tap\n} from \"rxjs/operators\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Details\n */\nexport interface Details {}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n target$: Observable /* Location target observable */\n print$: Observable /* Print mode observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n target$: Observable /* Location target observable */\n print$: Observable /* Print mode observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch details\n *\n * @param el - Details element\n * @param options - Options\n *\n * @returns Details observable\n */\nexport function watchDetails(\n el: HTMLDetailsElement, { target$, print$ }: WatchOptions\n): Observable
    {\n return target$\n .pipe(\n map(target => target.closest(\"details:not([open])\")!),\n filter(details => el === details),\n mergeWith(print$),\n mapTo(el)\n )\n}\n\n/**\n * Mount details\n *\n * This function ensures that `details` tags are opened on anchor jumps and\n * prior to printing, so the whole content of the page is visible.\n *\n * @param el - Details element\n * @param options - Options\n *\n * @returns Details component observable\n */\nexport function mountDetails(\n el: HTMLDetailsElement, options: MountOptions\n): Observable> {\n const internal$ = new Subject
    ()\n internal$.subscribe(() => {\n el.setAttribute(\"open\", \"\")\n el.scrollIntoView()\n })\n\n /* Create and return component */\n return watchDetails(el, options)\n .pipe(\n tap(internal$),\n finalize(() => internal$.complete()),\n mapTo({ ref: el })\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, of } from \"rxjs\"\n\nimport { createElement, replaceElement } from \"~/browser\"\nimport { renderTable } from \"~/templates\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Data table\n */\nexport interface DataTable {}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Sentinel for replacement\n */\nconst sentinel = createElement(\"table\")\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount data table\n *\n * This function wraps a data table in another scrollable container, so it can\n * be smoothly scrolled on smaller screen sizes and won't break the layout.\n *\n * @param el - Data table element\n *\n * @returns Data table component observable\n */\nexport function mountDataTable(\n el: HTMLElement\n): Observable> {\n replaceElement(el, sentinel)\n replaceElement(sentinel, renderTable(el))\n\n /* Create and return component */\n return of({ ref: el })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, merge } from \"rxjs\"\n\nimport { Viewport, getElements } from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { CodeBlock, mountCodeBlock } from \"../code\"\nimport { Details, mountDetails } from \"../details\"\nimport { DataTable, mountDataTable } from \"../table\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Content\n */\nexport type Content =\n | CodeBlock\n | DataTable\n | Details\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n target$: Observable /* Location target observable */\n viewport$: Observable /* Viewport observable */\n print$: Observable /* Print mode observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount content\n *\n * This function mounts all components that are found in the content of the\n * actual article, including code blocks, data tables and details.\n *\n * @param el - Content element\n * @param options - Options\n *\n * @returns Content component observable\n */\nexport function mountContent(\n el: HTMLElement, { target$, viewport$, print$ }: MountOptions\n): Observable> {\n return merge(\n\n /* Code blocks */\n ...getElements(\"pre > code\", el)\n .map(child => mountCodeBlock(child, { viewport$ })),\n\n /* Data tables */\n ...getElements(\"table:not([class])\", el)\n .map(child => mountDataTable(child)),\n\n /* Details */\n ...getElements(\"details\", el)\n .map(child => mountDetails(child, { target$, print$ }))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n animationFrameScheduler,\n merge,\n of\n} from \"rxjs\"\nimport {\n delay,\n finalize,\n map,\n observeOn,\n switchMap,\n tap\n} from \"rxjs/operators\"\n\nimport {\n resetDialogState,\n setDialogMessage,\n setDialogState\n} from \"~/actions\"\n\nimport { Component } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Dialog\n */\nexport interface Dialog {\n message: string /* Dialog message */\n open: boolean /* Dialog is visible */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n alert$: Subject /* Alert subject */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n alert$: Subject /* Alert subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch dialog\n *\n * @param _el - Dialog element\n * @param options - Options\n *\n * @returns Dialog observable\n */\nexport function watchDialog(\n _el: HTMLElement, { alert$ }: WatchOptions\n): Observable {\n return alert$\n .pipe(\n switchMap(message => merge(\n of(true),\n of(false).pipe(delay(2000))\n )\n .pipe(\n map(open => ({ message, open }))\n )\n )\n )\n}\n\n/**\n * Mount dialog\n *\n * This function reveals the dialog in the right cornerwhen a new alert is\n * emitted through the subject that is passed as part of the options.\n *\n * @param el - Dialog element\n * @param options - Options\n *\n * @returns Dialog component observable\n */\nexport function mountDialog(\n el: HTMLElement, options: MountOptions\n): Observable> {\n const internal$ = new Subject()\n internal$\n .pipe(\n observeOn(animationFrameScheduler)\n )\n .subscribe(({ message, open }) => {\n setDialogMessage(el, message)\n if (open)\n setDialogState(el, \"open\")\n else\n resetDialogState(el)\n })\n\n /* Create and return component */\n return watchDialog(el, options)\n .pipe(\n tap(internal$),\n finalize(() => internal$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n animationFrameScheduler,\n combineLatest,\n defer,\n of\n} from \"rxjs\"\nimport {\n bufferCount,\n combineLatestWith,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n filter,\n map,\n observeOn,\n shareReplay,\n startWith,\n switchMap\n} from \"rxjs/operators\"\n\nimport { feature } from \"~/_\"\nimport { resetHeaderState, setHeaderState } from \"~/actions\"\nimport {\n Viewport,\n watchElementSize,\n watchToggle\n} from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Main } from \"../../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Header\n */\nexport interface Header {\n height: number /* Header visible height */\n sticky: boolean /* Header stickyness */\n hidden: boolean /* User scrolled past threshold */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n main$: Observable
    /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Compute whether the header is hidden\n *\n * If the user scrolls past a certain threshold, the header can be hidden when\n * scrolling down, and shown when scrolling up.\n *\n * @param options - Options\n *\n * @returns Toggle observable\n */\nfunction isHidden({ viewport$ }: WatchOptions): Observable {\n if (!feature(\"header.autohide\"))\n return of(false)\n\n /* Compute direction and turning point */\n const direction$ = viewport$\n .pipe(\n map(({ offset: { y } }) => y),\n bufferCount(2, 1),\n map(([a, b]) => [a < b, b] as const),\n distinctUntilKeyChanged(0)\n )\n\n /* Compute whether header should be hidden */\n const hidden$ = combineLatest([viewport$, direction$])\n .pipe(\n filter(([{ offset }, [, y]]) => Math.abs(y - offset.y) > 100),\n map(([, [direction]]) => direction),\n distinctUntilChanged()\n )\n\n /* Compute threshold for hiding */\n const search$ = watchToggle(\"search\")\n return combineLatest([viewport$, search$])\n .pipe(\n map(([{ offset }, search]) => offset.y > 400 && !search),\n distinctUntilChanged(),\n switchMap(active => active ? hidden$ : of(false)),\n startWith(false)\n )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch header\n *\n * @param el - Header element\n * @param options - Options\n *\n * @returns Header observable\n */\nexport function watchHeader(\n el: HTMLElement, options: WatchOptions\n): Observable
    {\n return defer(() => {\n const styles = getComputedStyle(el)\n return of(\n styles.position === \"sticky\" ||\n styles.position === \"-webkit-sticky\"\n )\n })\n .pipe(\n combineLatestWith(watchElementSize(el), isHidden(options)),\n map(([sticky, { height }, hidden]) => ({\n height: sticky ? height : 0,\n sticky,\n hidden\n })),\n distinctUntilChanged((a, b) => (\n a.sticky === b.sticky &&\n a.height === b.height &&\n a.hidden === b.hidden\n )),\n shareReplay(1)\n )\n}\n\n/**\n * Mount header\n *\n * This function manages the different states of the header, i.e. whether it's\n * hidden or rendered with a shadow. This depends heavily on the main area.\n *\n * @param el - Header element\n * @param options - Options\n *\n * @returns Header component observable\n */\nexport function mountHeader(\n el: HTMLElement, { header$, main$ }: MountOptions\n): Observable> {\n const internal$ = new Subject
    ()\n internal$\n .pipe(\n distinctUntilKeyChanged(\"active\"),\n combineLatestWith(header$),\n observeOn(animationFrameScheduler)\n )\n .subscribe(([{ active }, { hidden }]) => {\n if (active)\n setHeaderState(el, hidden ? \"hidden\" : \"shadow\")\n else\n resetHeaderState(el)\n })\n\n /* Connect to long-living subject and return component */\n main$.subscribe(main => internal$.next(main))\n return header$\n .pipe(\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n NEVER,\n Observable,\n Subject,\n animationFrameScheduler\n} from \"rxjs\"\nimport {\n distinctUntilKeyChanged,\n finalize,\n map,\n observeOn,\n tap\n} from \"rxjs/operators\"\n\nimport {\n resetHeaderTitleState,\n setHeaderTitleState\n} from \"~/actions\"\nimport {\n Viewport,\n getElement,\n getElementSize,\n watchViewportAt\n} from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Header } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Header\n */\nexport interface HeaderTitle {\n active: boolean /* User scrolled past first headline */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch header title\n *\n * @param el - Heading element\n * @param options - Options\n *\n * @returns Header title observable\n */\nexport function watchHeaderTitle(\n el: HTMLHeadingElement, { viewport$, header$ }: WatchOptions\n): Observable {\n return watchViewportAt(el, { header$, viewport$ })\n .pipe(\n map(({ offset: { y } }) => {\n const { height } = getElementSize(el)\n return {\n active: y >= height\n }\n }),\n distinctUntilKeyChanged(\"active\")\n )\n}\n\n/**\n * Mount header title\n *\n * This function swaps the header title from the site title to the title of the\n * current page when the user scrolls past the first headline.\n *\n * @param el - Header title element\n * @param options - Options\n *\n * @returns Header title component observable\n */\nexport function mountHeaderTitle(\n el: HTMLElement, options: MountOptions\n): Observable> {\n const internal$ = new Subject()\n internal$\n .pipe(\n observeOn(animationFrameScheduler)\n )\n .subscribe(({ active }) => {\n if (active)\n setHeaderTitleState(el, \"active\")\n else\n resetHeaderTitleState(el)\n })\n\n /* Obtain headline, if any */\n const headline = getElement(\"article h1\")\n if (typeof headline === \"undefined\")\n return NEVER\n\n /* Create and return component */\n return watchHeaderTitle(headline, options)\n .pipe(\n tap(internal$),\n finalize(() => internal$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n combineLatest\n} from \"rxjs\"\nimport {\n distinctUntilChanged,\n distinctUntilKeyChanged,\n map,\n switchMap\n} from \"rxjs/operators\"\n\nimport { Viewport, watchElementSize } from \"~/browser\"\n\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Main area\n */\nexport interface Main {\n offset: number /* Main area top offset */\n height: number /* Main area visible height */\n active: boolean /* User scrolled past header */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch main area\n *\n * This function returns an observable that computes the visual parameters of\n * the main area which depends on the viewport vertical offset and height, as\n * well as the height of the header element, if the header is fixed.\n *\n * @param el - Main area element\n * @param options - Options\n *\n * @returns Main area observable\n */\nexport function watchMain(\n el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable
    {\n\n /* Compute necessary adjustment for header */\n const adjust$ = header$\n .pipe(\n map(({ height }) => height),\n distinctUntilChanged()\n )\n\n /* Compute the main area's top and bottom borders */\n const border$ = adjust$\n .pipe(\n switchMap(() => watchElementSize(el)\n .pipe(\n map(({ height }) => ({\n top: el.offsetTop,\n bottom: el.offsetTop + height\n })),\n distinctUntilKeyChanged(\"bottom\")\n )\n )\n )\n\n /* Compute the main area's offset, visible height and if we scrolled past */\n return combineLatest([adjust$, border$, viewport$])\n .pipe(\n map(([header, { top, bottom }, { offset: { y }, size: { height } }]) => {\n height = Math.max(0, height\n - Math.max(0, top - y, header)\n - Math.max(0, height + y - bottom)\n )\n return {\n offset: top - header,\n height,\n active: top - header <= y\n }\n }),\n distinctUntilChanged((a, b) => (\n a.offset === b.offset &&\n a.height === b.height &&\n a.active === b.active\n ))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n fromEvent,\n of\n} from \"rxjs\"\nimport {\n finalize,\n map,\n mapTo,\n mergeMap,\n shareReplay,\n startWith,\n tap\n} from \"rxjs/operators\"\n\nimport { getElements } from \"~/browser\"\n\nimport { Component } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Palette colors\n */\nexport interface PaletteColor {\n scheme?: string /* Color scheme */\n primary?: string /* Primary color */\n accent?: string /* Accent color */\n}\n\n/**\n * Palette\n */\nexport interface Palette {\n index: number /* Palette index */\n color: PaletteColor /* Palette colors */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch color palette\n *\n * @param inputs - Color palette element\n *\n * @returns Color palette observable\n */\nexport function watchPalette(\n inputs: HTMLInputElement[]\n): Observable {\n const data = localStorage.getItem(__prefix(\"__palette\"))!\n const current = JSON.parse(data) || {\n index: inputs.findIndex(input => (\n matchMedia(input.getAttribute(\"data-md-color-media\")!).matches\n ))\n }\n\n /* Emit changes in color palette */\n const palette$ = of(...inputs)\n .pipe(\n mergeMap(input => fromEvent(input, \"change\")\n .pipe(\n mapTo(input)\n )\n ),\n startWith(inputs[Math.max(0, current.index)]),\n map(input => ({\n index: inputs.indexOf(input),\n color: {\n scheme: input.getAttribute(\"data-md-color-scheme\"),\n primary: input.getAttribute(\"data-md-color-primary\"),\n accent: input.getAttribute(\"data-md-color-accent\")\n }\n } as Palette)),\n shareReplay(1)\n )\n\n /* Persist preference in local storage */\n palette$.subscribe(palette => {\n localStorage.setItem(__prefix(\"__palette\"), JSON.stringify(palette))\n })\n\n /* Return palette */\n return palette$\n}\n\n/**\n * Mount color palette\n *\n * @param el - Color palette element\n *\n * @returns Color palette component observable\n */\nexport function mountPalette(\n el: HTMLElement\n): Observable> {\n const internal$ = new Subject()\n\n /* Set color palette */\n internal$.subscribe(palette => {\n for (const [key, value] of Object.entries(palette.color))\n if (typeof value === \"string\")\n document.body.setAttribute(`data-md-color-${key}`, value)\n\n /* Toggle visibility */\n for (let index = 0; index < inputs.length; index++) {\n const label = inputs[index].nextElementSibling as HTMLElement\n label.hidden = palette.index !== index\n }\n })\n\n /* Create and return component */\n const inputs = getElements(\"input\", el)\n return watchPalette(inputs)\n .pipe(\n tap(internal$),\n finalize(() => internal$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ClipboardJS from \"clipboard\"\nimport { Observable, Subject } from \"rxjs\"\n\nimport { translation } from \"~/_\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n alert$: Subject /* Alert subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up Clipboard.js integration\n *\n * @param options - Options\n */\nexport function setupClipboardJS(\n { alert$ }: SetupOptions\n): void {\n if (ClipboardJS.isSupported()) {\n new Observable(subscriber => {\n new ClipboardJS(\"[data-clipboard-target], [data-clipboard-text]\")\n .on(\"success\", ev => subscriber.next(ev))\n })\n .subscribe(() => alert$.next(translation(\"clipboard.copied\")))\n }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n fromEvent,\n merge,\n of\n} from \"rxjs\"\nimport {\n bufferCount,\n catchError,\n concatMap,\n debounceTime,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n filter,\n map,\n sample,\n share,\n skip,\n skipUntil,\n switchMap\n} from \"rxjs/operators\"\n\nimport { configuration } from \"~/_\"\nimport {\n Viewport,\n ViewportOffset,\n createElement,\n getElement,\n getElements,\n replaceElement,\n request,\n requestXML,\n setLocation,\n setLocationHash,\n setViewportOffset\n} from \"~/browser\"\nimport { getComponentElement } from \"~/components\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * History state\n */\nexport interface HistoryState {\n url: URL /* State URL */\n offset?: ViewportOffset /* State viewport offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n document$: Subject /* Document subject */\n location$: Subject /* Location subject */\n viewport$: Observable /* Viewport observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Preprocess a list of URLs\n *\n * This function replaces the `site_url` in the sitemap with the actual base\n * URL, to allow instant loading to work in occasions like Netlify previews.\n *\n * @param urls - URLs\n *\n * @returns Processed URLs\n */\nfunction preprocess(urls: string[]): string[] {\n if (urls.length < 2)\n return urls\n\n /* Take the first two URLs and remove everything after the last slash */\n const [root, next] = urls\n .sort((a, b) => a.length - b.length)\n .map(url => url.replace(/[^/]+$/, \"\"))\n\n /* Compute common prefix */\n let index = 0\n if (root === next)\n index = root.length\n else\n while (root.charCodeAt(index) === next.charCodeAt(index))\n index++\n\n /* Replace common prefix (i.e. base) with effective base */\n const config = configuration()\n return urls.map(url => (\n url.replace(root.slice(0, index), `${config.base}/`)\n ))\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up instant loading\n *\n * When fetching, theoretically, we could use `responseType: \"document\"`, but\n * since all MkDocs links are relative, we need to make sure that the current\n * location matches the document we just loaded. Otherwise any relative links\n * in the document could use the old location.\n *\n * This is the reason why we need to synchronize history events and the process\n * of fetching the document for navigation changes (except `popstate` events):\n *\n * 1. Fetch document via `XMLHTTPRequest`\n * 2. Set new location via `history.pushState`\n * 3. Parse and emit fetched document\n *\n * For `popstate` events, we must not use `history.pushState`, or the forward\n * history will be irreversibly overwritten. In case the request fails, the\n * location change is dispatched regularly.\n *\n * @param options - Options\n */\nexport function setupInstantLoading(\n { document$, location$, viewport$ }: SetupOptions\n): void {\n const config = configuration()\n if (location.protocol === \"file:\")\n return\n\n /* Disable automatic scroll restoration */\n if (\"scrollRestoration\" in history) {\n history.scrollRestoration = \"manual\"\n\n /* Hack: ensure that reloads restore viewport offset */\n fromEvent(window, \"beforeunload\")\n .subscribe(() => {\n history.scrollRestoration = \"auto\"\n })\n }\n\n /* Hack: ensure absolute favicon link to omit 404s when switching */\n const favicon = getElement(\"link[rel=icon]\")\n if (typeof favicon !== \"undefined\")\n favicon.href = favicon.href\n\n /* Intercept internal navigation */\n const push$ = requestXML(`${config.base}/sitemap.xml`)\n .pipe(\n map(sitemap => preprocess(getElements(\"loc\", sitemap)\n .map(node => node.textContent!)\n )),\n switchMap(urls => fromEvent(document.body, \"click\")\n .pipe(\n filter(ev => !ev.metaKey && !ev.ctrlKey),\n switchMap(ev => {\n\n /* Handle HTML and SVG elements */\n if (ev.target instanceof Element) {\n const el = ev.target.closest(\"a\")\n if (el && !el.target && urls.includes(el.href)) {\n ev.preventDefault()\n return of({\n url: new URL(el.href)\n })\n }\n }\n return NEVER\n })\n )\n ),\n share()\n )\n\n /* Intercept history back and forward */\n const pop$ = fromEvent(window, \"popstate\")\n .pipe(\n filter(ev => ev.state !== null),\n map(ev => ({\n url: new URL(location.href),\n offset: ev.state\n })),\n share()\n )\n\n /* Emit location change */\n merge(push$, pop$)\n .pipe(\n distinctUntilChanged((a, b) => a.url.href === b.url.href),\n map(({ url }) => url)\n )\n .subscribe(location$)\n\n /* Fetch document via `XMLHTTPRequest` */\n const response$ = location$\n .pipe(\n distinctUntilKeyChanged(\"pathname\"),\n switchMap(url => request(url.href)\n .pipe(\n catchError(() => {\n setLocation(url)\n return NEVER\n })\n )\n ),\n share()\n )\n\n /* Set new location via `history.pushState` */\n push$\n .pipe(\n sample(response$)\n )\n .subscribe(({ url }) => {\n history.pushState({}, \"\", `${url}`)\n })\n\n /* Parse and emit fetched document */\n const dom = new DOMParser()\n response$\n .pipe(\n switchMap(res => res.text()),\n map(res => dom.parseFromString(res, \"text/html\"))\n )\n .subscribe(document$)\n\n /* Emit history state change */\n merge(push$, pop$)\n .pipe(\n sample(document$)\n )\n .subscribe(({ url, offset }) => {\n if (url.hash && !offset)\n setLocationHash(url.hash)\n else\n setViewportOffset(offset || { y: 0 })\n })\n\n /* Replace meta tags and components */\n document$\n .pipe(\n skip(1)\n )\n .subscribe(replacement => {\n for (const selector of [\n\n /* Meta tags */\n \"title\",\n \"link[rel=canonical]\",\n \"meta[name=author]\",\n \"meta[name=description]\",\n\n /* Components */\n \"[data-md-component=announce]\",\n \"[data-md-component=container]\",\n \"[data-md-component=header-topic]\",\n \"[data-md-component=logo], .md-logo\", // compat\n \"[data-md-component=skip]\"\n ]) {\n const source = getElement(selector)\n const target = getElement(selector, replacement)\n if (\n typeof source !== \"undefined\" &&\n typeof target !== \"undefined\"\n ) {\n replaceElement(source, target)\n }\n }\n })\n\n /* Re-evaluate scripts */\n document$\n .pipe(\n skip(1),\n map(() => getComponentElement(\"container\")),\n switchMap(el => of(...getElements(\"script\", el))),\n concatMap(el => {\n const script = createElement(\"script\")\n if (el.src) {\n for (const name of el.getAttributeNames())\n script.setAttribute(name, el.getAttribute(name)!)\n replaceElement(el, script)\n\n /* Complete when script is loaded */\n return new Observable(observer => {\n script.onload = () => observer.complete()\n })\n\n /* Complete immediately */\n } else {\n script.textContent = el.textContent\n replaceElement(el, script)\n return EMPTY\n }\n })\n )\n .subscribe()\n\n /* Debounce update of viewport offset */\n viewport$\n .pipe(\n skipUntil(push$),\n debounceTime(250),\n distinctUntilKeyChanged(\"offset\")\n )\n .subscribe(({ offset }) => {\n history.replaceState(offset, \"\")\n })\n\n /* Set viewport offset from history */\n merge(push$, pop$)\n .pipe(\n bufferCount(2, 1),\n filter(([a, b]) => a.url.pathname === b.url.pathname),\n map(([, state]) => state)\n )\n .subscribe(({ offset }) => {\n setViewportOffset(offset || { y: 0 })\n })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexDocument } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search document\n */\nexport interface SearchDocument extends SearchIndexDocument {\n parent?: SearchIndexDocument /* Parent article */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search document mapping\n */\nexport type SearchDocumentMap = Map\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document mapping\n *\n * @param docs - Search index documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n docs: SearchIndexDocument[]\n): SearchDocumentMap {\n const documents = new Map()\n const parents = new Set()\n for (const doc of docs) {\n const [path, hash] = doc.location.split(\"#\")\n\n /* Extract location and title */\n const location = doc.location\n const title = doc.title\n\n /* Escape and cleanup text */\n const text = escapeHTML(doc.text)\n .replace(/\\s+(?=[,.:;!?])/g, \"\")\n .replace(/\\s+/g, \" \")\n\n /* Handle section */\n if (hash) {\n const parent = documents.get(path)!\n\n /* Ignore first section, override article */\n if (!parents.has(parent)) {\n parent.title = doc.title\n parent.text = text\n\n /* Remember that we processed the article */\n parents.add(parent)\n\n /* Add subsequent section */\n } else {\n documents.set(location, {\n location,\n title,\n text,\n parent\n })\n }\n\n /* Add article */\n } else {\n documents.set(location, {\n location,\n title,\n text\n })\n }\n }\n return documents\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search transformation function\n *\n * @param value - Query value\n *\n * @returns Transformed query value\n */\nexport type SearchTransformFn = (value: string) => string\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Default transformation function\n *\n * 1. Search for terms in quotation marks and prepend a `+` modifier to denote\n * that the resulting document must contain all terms, converting the query\n * to an `AND` query (as opposed to the default `OR` behavior). While users\n * may expect terms enclosed in quotation marks to map to span queries, i.e.\n * for which order is important, Lunr.js doesn't support them, so the best\n * we can do is to convert the terms to an `AND` query.\n *\n * 2. Replace control characters which are not located at the beginning of the\n * query or preceded by white space, or are not followed by a non-whitespace\n * character or are at the end of the query string. Furthermore, filter\n * unmatched quotation marks.\n *\n * 3. Trim excess whitespace from left and right.\n *\n * @param query - Query value\n *\n * @returns Transformed query value\n */\nexport function defaultTransform(query: string): string {\n return query\n .split(/\"([^\"]+)\"/g) /* => 1 */\n .map((terms, index) => index & 1\n ? terms.replace(/^\\b|^(?![^\\x00-\\x7F]|$)|\\s+/g, \" +\")\n : terms\n )\n .join(\"\")\n .replace(/\"|(?:^|\\s+)[*+\\-:^~]+(?=\\s+|$)/g, \"\") /* => 2 */\n .trim() /* => 3 */\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndex, SearchResult } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search message type\n */\nexport const enum SearchMessageType {\n SETUP, /* Search index setup */\n READY, /* Search index ready */\n QUERY, /* Search query */\n RESULT /* Search results */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message containing the data necessary to setup the search index\n */\nexport interface SearchSetupMessage {\n type: SearchMessageType.SETUP /* Message type */\n data: SearchIndex /* Message data */\n}\n\n/**\n * A message indicating the search index is ready\n */\nexport interface SearchReadyMessage {\n type: SearchMessageType.READY /* Message type */\n}\n\n/**\n * A message containing a search query\n */\nexport interface SearchQueryMessage {\n type: SearchMessageType.QUERY /* Message type */\n data: string /* Message data */\n}\n\n/**\n * A message containing results for a search query\n */\nexport interface SearchResultMessage {\n type: SearchMessageType.RESULT /* Message type */\n data: SearchResult[] /* Message data */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message exchanged with the search worker\n */\nexport type SearchMessage =\n | SearchSetupMessage\n | SearchReadyMessage\n | SearchQueryMessage\n | SearchResultMessage\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Type guard for search setup messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchSetupMessage(\n message: SearchMessage\n): message is SearchSetupMessage {\n return message.type === SearchMessageType.SETUP\n}\n\n/**\n * Type guard for search ready messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchReadyMessage(\n message: SearchMessage\n): message is SearchReadyMessage {\n return message.type === SearchMessageType.READY\n}\n\n/**\n * Type guard for search query messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchQueryMessage(\n message: SearchMessage\n): message is SearchQueryMessage {\n return message.type === SearchMessageType.QUERY\n}\n\n/**\n * Type guard for search result messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchResultMessage(\n message: SearchMessage\n): message is SearchResultMessage {\n return message.type === SearchMessageType.RESULT\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ObservableInput, Subject, from } from \"rxjs\"\nimport { map, share } from \"rxjs/operators\"\n\nimport { configuration, translation } from \"~/_\"\nimport { WorkerHandler, watchWorker } from \"~/browser\"\n\nimport { SearchIndex, SearchIndexPipeline } from \"../../_\"\nimport {\n SearchMessage,\n SearchMessageType,\n SearchSetupMessage,\n isSearchResultMessage\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search worker\n */\nexport type SearchWorker = WorkerHandler\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up search index\n *\n * @param data - Search index\n *\n * @returns Search index\n */\nfunction setupSearchIndex(\n { config, docs, index }: SearchIndex\n): SearchIndex {\n\n /* Override default language with value from translation */\n if (config.lang.length === 1 && config.lang[0] === \"en\")\n config.lang = [\n translation(\"search.config.lang\")\n ]\n\n /* Override default separator with value from translation */\n if (config.separator === \"[\\\\s\\\\-]+\")\n config.separator = translation(\"search.config.separator\")\n\n /* Set pipeline from translation */\n const pipeline = translation(\"search.config.pipeline\")\n .split(/\\s*,\\s*/)\n .filter(Boolean) as SearchIndexPipeline\n\n /* Return search index after defaulting */\n return { config, docs, index, pipeline }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up search worker\n *\n * This function creates a web worker to set up and query the search index,\n * which is done using Lunr.js. The index must be passed as an observable to\n * enable hacks like _localsearch_ via search index embedding as JSON.\n *\n * @param url - Worker URL\n * @param index - Search index observable input\n *\n * @returns Search worker\n */\nexport function setupSearchWorker(\n url: string, index: ObservableInput\n): SearchWorker {\n const config = configuration()\n const worker = new Worker(url)\n\n /* Create communication channels and resolve relative links */\n const tx$ = new Subject()\n const rx$ = watchWorker(worker, { tx$ })\n .pipe(\n map(message => {\n if (isSearchResultMessage(message)) {\n for (const result of message.data)\n for (const document of result)\n document.location = `${config.base}/${document.location}`\n }\n return message\n }),\n share()\n )\n\n /* Set up search index */\n from(index)\n .pipe(\n map(data => ({\n type: SearchMessageType.SETUP,\n data: setupSearchIndex(data)\n }))\n )\n .subscribe(tx$.next.bind(tx$))\n\n /* Return search worker */\n return { tx$, rx$ }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { configuration } from \"~/_\"\nimport { getElementOrThrow, requestJSON } from \"~/browser\"\nimport { Version, renderVersionSelector } from \"~/templates\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up version selector\n */\nexport function setupVersionSelector(): void {\n const config = configuration()\n requestJSON(new URL(\"versions.json\", config.base))\n .subscribe(versions => {\n const topic = getElementOrThrow(\".md-header__topic\")\n topic.appendChild(renderVersionSelector(versions))\n })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n combineLatest,\n fromEvent,\n merge\n} from \"rxjs\"\nimport {\n delay,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n finalize,\n map,\n takeLast,\n takeUntil,\n tap\n} from \"rxjs/operators\"\n\nimport {\n resetSearchQueryPlaceholder,\n setSearchQueryPlaceholder\n} from \"~/actions\"\nimport {\n setElementFocus,\n setToggle,\n watchElementFocus\n} from \"~/browser\"\nimport {\n SearchMessageType,\n SearchQueryMessage,\n SearchWorker,\n defaultTransform\n} from \"~/integrations\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query\n */\nexport interface SearchQuery {\n value: string /* Query value */\n focus: boolean /* Query focus */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch search query\n *\n * Note that the focus event which triggers re-reading the current query value\n * is delayed by `1ms` so the input's empty state is allowed to propagate.\n *\n * @param el - Search query element\n *\n * @returns Search query observable\n */\nexport function watchSearchQuery(\n el: HTMLInputElement\n): Observable {\n const fn = __search?.transform || defaultTransform\n\n /* Intercept focus and input events */\n const focus$ = watchElementFocus(el)\n const value$ = merge(\n fromEvent(el, \"keyup\"),\n fromEvent(el, \"focus\").pipe(delay(1))\n )\n .pipe(\n map(() => fn(el.value)),\n distinctUntilChanged()\n )\n\n /* Combine into single observable */\n return combineLatest([value$, focus$])\n .pipe(\n map(([value, focus]) => ({ value, focus }))\n )\n}\n\n/**\n * Mount search query\n *\n * @param el - Search query element\n * @param worker - Search worker\n *\n * @returns Search query component observable\n */\nexport function mountSearchQuery(\n el: HTMLInputElement, { tx$ }: SearchWorker\n): Observable> {\n const internal$ = new Subject()\n\n /* Handle value changes */\n internal$\n .pipe(\n distinctUntilKeyChanged(\"value\"),\n map(({ value }): SearchQueryMessage => ({\n type: SearchMessageType.QUERY,\n data: value\n }))\n )\n .subscribe(tx$.next.bind(tx$))\n\n /* Handle focus changes */\n internal$\n .pipe(\n distinctUntilKeyChanged(\"focus\")\n )\n .subscribe(({ focus }) => {\n if (focus) {\n setToggle(\"search\", focus)\n setSearchQueryPlaceholder(el, \"\")\n } else {\n resetSearchQueryPlaceholder(el)\n }\n })\n\n /* Handle reset */\n fromEvent(el.form!, \"reset\")\n .pipe(\n takeUntil(internal$.pipe(takeLast(1)))\n )\n .subscribe(() => setElementFocus(el))\n\n /* Create and return component */\n return watchSearchQuery(el)\n .pipe(\n tap(internal$),\n finalize(() => internal$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n animationFrameScheduler,\n merge,\n of\n} from \"rxjs\"\nimport {\n bufferCount,\n filter,\n finalize,\n map,\n observeOn,\n startWith,\n switchMap,\n tap,\n withLatestFrom,\n zipWith\n} from \"rxjs/operators\"\n\nimport {\n addToSearchResultList,\n resetSearchResultList,\n resetSearchResultMeta,\n setSearchResultMeta\n} from \"~/actions\"\nimport {\n getElementOrThrow,\n watchElementThreshold\n} from \"~/browser\"\nimport {\n SearchResult as SearchResultData,\n SearchWorker,\n isSearchResultMessage\n} from \"~/integrations\"\nimport { renderSearchResult } from \"~/templates\"\n\nimport { Component } from \"../../_\"\nimport { SearchQuery } from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search result\n */\nexport interface SearchResult {\n data: SearchResultData[] /* Search result data */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n query$: Observable /* Search query observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search result list\n *\n * This function performs a lazy rendering of the search results, depending on\n * the vertical offset of the search result container.\n *\n * @param el - Search result list element\n * @param worker - Search worker\n * @param options - Options\n *\n * @returns Search result list component observable\n */\nexport function mountSearchResult(\n el: HTMLElement, { rx$ }: SearchWorker, { query$ }: MountOptions\n): Observable> {\n const internal$ = new Subject()\n const boundary$ = watchElementThreshold(el.parentElement!)\n .pipe(\n filter(Boolean)\n )\n\n /* Update search result metadata */\n const meta = getElementOrThrow(\":scope > :first-child\", el)\n internal$\n .pipe(\n observeOn(animationFrameScheduler),\n withLatestFrom(query$)\n )\n .subscribe(([{ data }, { value }]) => {\n if (value)\n setSearchResultMeta(meta, data.length)\n else\n resetSearchResultMeta(meta)\n })\n\n /* Update search result list */\n const list = getElementOrThrow(\":scope > :last-child\", el)\n internal$\n .pipe(\n observeOn(animationFrameScheduler),\n tap(() => resetSearchResultList(list)),\n switchMap(({ data }) => merge(\n of(...data.slice(0, 10)),\n of(...data.slice(10))\n .pipe(\n bufferCount(4),\n zipWith(boundary$),\n switchMap(([chunk]) => of(...chunk))\n )\n ))\n )\n .subscribe(result => {\n addToSearchResultList(list, renderSearchResult(result))\n })\n\n /* Filter search result list */\n const result$ = rx$\n .pipe(\n filter(isSearchResultMessage),\n map(({ data }) => ({ data })),\n startWith({ data: [] })\n )\n\n /* Create and return component */\n return result$\n .pipe(\n tap(internal$),\n finalize(() => internal$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable, ObservableInput, merge } from \"rxjs\"\nimport { filter, sample, take } from \"rxjs/operators\"\n\nimport { configuration } from \"~/_\"\nimport {\n Keyboard,\n getActiveElement,\n getElements,\n setElementFocus,\n setElementSelection,\n setToggle\n} from \"~/browser\"\nimport {\n SearchIndex,\n isSearchQueryMessage,\n isSearchReadyMessage,\n setupSearchWorker\n} from \"~/integrations\"\n\nimport { Component, getComponentElement } from \"../../_\"\nimport { SearchQuery, mountSearchQuery } from \"../query\"\nimport { SearchResult, mountSearchResult } from \"../result\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search\n */\nexport type Search =\n | SearchQuery\n | SearchResult\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n index$: ObservableInput /* Search index observable */\n keyboard$: Observable /* Keyboard observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search\n *\n * This function sets up the search functionality, including the underlying\n * web worker and all keyboard bindings.\n *\n * @param el - Search element\n * @param options - Options\n *\n * @returns Search component observable\n */\nexport function mountSearch(\n el: HTMLElement, { index$, keyboard$ }: MountOptions\n): Observable> {\n const config = configuration()\n const worker = setupSearchWorker(config.search, index$)\n\n /* Retrieve nested components */\n const query = getComponentElement(\"search-query\", el)\n const result = getComponentElement(\"search-result\", el)\n\n /* Re-emit query when search is ready */\n const { tx$, rx$ } = worker\n tx$\n .pipe(\n filter(isSearchQueryMessage),\n sample(rx$.pipe(filter(isSearchReadyMessage))),\n take(1)\n )\n .subscribe(tx$.next.bind(tx$))\n\n /* Set up search keyboard handlers */\n keyboard$\n .pipe(\n filter(({ mode }) => mode === \"search\")\n )\n .subscribe(key => {\n const active = getActiveElement()\n switch (key.type) {\n\n /* Enter: prevent form submission */\n case \"Enter\":\n if (active === query)\n key.claim()\n break\n\n /* Escape or Tab: close search */\n case \"Escape\":\n case \"Tab\":\n setToggle(\"search\", false)\n setElementFocus(query, false)\n break\n\n /* Vertical arrows: select previous or next search result */\n case \"ArrowUp\":\n case \"ArrowDown\":\n if (typeof active === \"undefined\") {\n setElementFocus(query)\n } else {\n const els = [query, ...getElements(\n \":not(details) > [href], summary, details[open] [href]\",\n result\n )]\n const i = Math.max(0, (\n Math.max(0, els.indexOf(active)) + els.length + (\n key.type === \"ArrowUp\" ? -1 : +1\n )\n ) % els.length)\n setElementFocus(els[i])\n }\n\n /* Prevent scrolling of page */\n key.claim()\n break\n\n /* All other keys: hand to search query */\n default:\n if (query !== getActiveElement())\n setElementFocus(query)\n }\n })\n\n /* Set up global keyboard handlers */\n keyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\"),\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Open search and select query */\n case \"f\":\n case \"s\":\n case \"/\":\n setElementFocus(query)\n setElementSelection(query)\n key.claim()\n break\n }\n })\n\n /* Create and return component */\n const query$ = mountSearchQuery(query, worker)\n return merge(\n query$,\n mountSearchResult(result, worker, { query$ })\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n animationFrameScheduler,\n combineLatest\n} from \"rxjs\"\nimport {\n distinctUntilChanged,\n finalize,\n map,\n observeOn,\n tap,\n withLatestFrom\n} from \"rxjs/operators\"\n\nimport {\n resetSidebarHeight,\n resetSidebarOffset,\n setSidebarHeight,\n setSidebarOffset\n} from \"~/actions\"\nimport { Viewport } from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\nimport { Main } from \"../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Sidebar\n */\nexport interface Sidebar {\n height: number /* Sidebar height */\n locked: boolean /* User scrolled past header */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n main$: Observable
    /* Main area observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n main$: Observable
    /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch sidebar\n *\n * This function returns an observable that computes the visual parameters of\n * the sidebar which depends on the vertical viewport offset, as well as the\n * height of the main area. When the page is scrolled beyond the header, the\n * sidebar is locked and fills the remaining space.\n *\n * @param el - Sidebar element\n * @param options - Options\n *\n * @returns Sidebar observable\n */\nexport function watchSidebar(\n el: HTMLElement, { viewport$, main$ }: WatchOptions\n): Observable {\n const adjust =\n el.parentElement!.offsetTop -\n el.parentElement!.parentElement!.offsetTop\n\n /* Compute the sidebar's available height and if it should be locked */\n return combineLatest([main$, viewport$])\n .pipe(\n map(([{ offset, height }, { offset: { y } }]) => {\n height = height\n + Math.min(adjust, Math.max(0, y - offset))\n - adjust\n return {\n height,\n locked: y >= offset + adjust\n }\n }),\n distinctUntilChanged((a, b) => (\n a.height === b.height &&\n a.locked === b.locked\n ))\n )\n}\n\n/**\n * Mount sidebar\n *\n * @param el - Sidebar element\n * @param options - Options\n *\n * @returns Sidebar component observable\n */\nexport function mountSidebar(\n el: HTMLElement, { header$, ...options }: MountOptions\n): Observable> {\n const internal$ = new Subject()\n internal$\n .pipe(\n observeOn(animationFrameScheduler),\n withLatestFrom(header$)\n )\n .subscribe({\n\n /* Update height and offset */\n next([{ height }, { height: offset }]) {\n setSidebarHeight(el, height)\n setSidebarOffset(el, offset)\n },\n\n /* Reset on complete */\n complete() {\n resetSidebarOffset(el)\n resetSidebarHeight(el)\n }\n })\n\n /* Create and return component */\n return watchSidebar(el, options)\n .pipe(\n tap(internal$),\n finalize(() => internal$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Repo, User } from \"github-types\"\nimport { Observable, zip } from \"rxjs\"\nimport { defaultIfEmpty, map } from \"rxjs/operators\"\n\nimport { requestJSON } from \"~/browser\"\n\nimport { SourceFacts } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * GitHub release (partial)\n */\ninterface Release {\n tag_name: string /* Tag name */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch GitHub repository facts\n *\n * @param user - GitHub user\n * @param repo - GitHub repository\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFactsFromGitHub(\n user: string, repo?: string\n): Observable {\n if (typeof repo !== \"undefined\") {\n const url = `https://api.github.com/repos/${user}/${repo}`\n return zip(\n\n /* Fetch version */\n requestJSON(`${url}/releases/latest`)\n .pipe(\n map(release => ({\n version: release.tag_name\n })),\n defaultIfEmpty({})\n ),\n\n /* Fetch stars and forks */\n requestJSON(url)\n .pipe(\n map(info => ({\n stars: info.stargazers_count,\n forks: info.forks_count\n })),\n defaultIfEmpty({})\n )\n )\n .pipe(\n map(([release, info]) => ({ ...release, ...info }))\n )\n\n /* User or organization */\n } else {\n const url = `https://api.github.com/repos/${user}`\n return requestJSON(url)\n .pipe(\n map(info => ({\n repositories: info.public_repos\n })),\n defaultIfEmpty({})\n )\n }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ProjectSchema } from \"gitlab\"\nimport { Observable } from \"rxjs\"\nimport { defaultIfEmpty, map } from \"rxjs/operators\"\n\nimport { requestJSON } from \"~/browser\"\n\nimport { SourceFacts } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch GitLab repository facts\n *\n * @param base - GitLab base\n * @param project - GitLab project\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFactsFromGitLab(\n base: string, project: string\n): Observable {\n const url = `https://${base}/api/v4/projects/${encodeURIComponent(project)}`\n return requestJSON(url)\n .pipe(\n map(({ star_count, forks_count }) => ({\n stars: star_count,\n forks: forks_count\n })),\n defaultIfEmpty({})\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable } from \"rxjs\"\n\nimport { fetchSourceFactsFromGitHub } from \"../github\"\nimport { fetchSourceFactsFromGitLab } from \"../gitlab\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository facts for repositories\n */\nexport interface RepositoryFacts {\n stars?: number /* Number of stars */\n forks?: number /* Number of forks */\n version?: string /* Latest version */\n}\n\n/**\n * Repository facts for organizations\n */\nexport interface OrganizationFacts {\n repositories?: number /* Number of repositories */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Repository facts\n */\nexport type SourceFacts =\n | RepositoryFacts\n | OrganizationFacts\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch repository facts\n *\n * @param url - Repository URL\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFacts(\n url: string\n): Observable {\n const [type] = url.match(/(git(?:hub|lab))/i) || []\n switch (type.toLowerCase()) {\n\n /* GitHub repository */\n case \"github\":\n const [, user, repo] = url.match(/^.+github\\.com\\/([^/]+)\\/?([^/]+)?/i)!\n return fetchSourceFactsFromGitHub(user, repo)\n\n /* GitLab repository */\n case \"gitlab\":\n const [, base, slug] = url.match(/^.+?([^/]*gitlab[^/]+)\\/(.+?)\\/?$/i)!\n return fetchSourceFactsFromGitLab(base, slug)\n\n /* Everything else */\n default:\n return NEVER\n }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable, Subject, defer, of } from \"rxjs\"\nimport {\n catchError,\n filter,\n finalize,\n map,\n shareReplay,\n tap\n} from \"rxjs/operators\"\n\nimport { setSourceFacts, setSourceState } from \"~/actions\"\nimport { renderSourceFacts } from \"~/templates\"\n\nimport { Component } from \"../../_\"\nimport { SourceFacts, fetchSourceFacts } from \"../facts\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository information\n */\nexport interface Source {\n facts: SourceFacts /* Repository facts */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository information observable\n */\nlet fetch$: Observable\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch repository information\n *\n * This function tries to read the repository facts from session storage, and\n * if unsuccessful, fetches them from the underlying provider.\n *\n * @param el - Repository information element\n *\n * @returns Repository information observable\n */\nexport function watchSource(\n el: HTMLAnchorElement\n): Observable {\n return fetch$ ||= defer(() => {\n const data = sessionStorage.getItem(__prefix(\"__source\"))\n if (data) {\n return of(JSON.parse(data))\n } else {\n const value$ = fetchSourceFacts(el.href)\n value$.subscribe(value => {\n try {\n sessionStorage.setItem(__prefix(\"__source\"), JSON.stringify(value))\n } catch (err) {\n /* Uncritical, just swallow */\n }\n })\n\n /* Return value */\n return value$\n }\n })\n .pipe(\n catchError(() => NEVER),\n filter(facts => Object.keys(facts).length > 0),\n map(facts => ({ facts })),\n shareReplay(1)\n )\n}\n\n/**\n * Mount repository information\n *\n * @param el - Repository information element\n *\n * @returns Repository information component observable\n */\nexport function mountSource(\n el: HTMLAnchorElement\n): Observable> {\n const internal$ = new Subject()\n internal$.subscribe(({ facts }) => {\n setSourceFacts(el, renderSourceFacts(facts))\n setSourceState(el, \"done\")\n })\n\n /* Create and return component */\n return watchSource(el)\n .pipe(\n tap(internal$),\n finalize(() => internal$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, Subject, animationFrameScheduler } from \"rxjs\"\nimport {\n distinctUntilKeyChanged,\n finalize,\n map,\n observeOn,\n switchMap,\n tap\n} from \"rxjs/operators\"\n\nimport { resetTabsState, setTabsState } from \"~/actions\"\nimport {\n Viewport,\n watchElementSize,\n watchViewportAt\n} from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Navigation tabs\n */\nexport interface Tabs {\n hidden: boolean /* User scrolled past tabs */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch navigation tabs\n *\n * @param el - Navigation tabs element\n * @param options - Options\n *\n * @returns Navigation tabs observable\n */\nexport function watchTabs(\n el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable {\n return watchElementSize(document.body)\n .pipe(\n switchMap(() => watchViewportAt(el, { header$, viewport$ })),\n map(({ offset: { y } }) => {\n return {\n hidden: y >= 10\n }\n }),\n distinctUntilKeyChanged(\"hidden\")\n )\n}\n\n/**\n * Mount navigation tabs\n *\n * This function hides the navigation tabs when scrolling past the threshold\n * and makes them reappear in a nice CSS animation when scrolling back up.\n *\n * @param el - Navigation tabs element\n * @param options - Options\n *\n * @returns Navigation tabs component observable\n */\nexport function mountTabs(\n el: HTMLElement, options: MountOptions\n): Observable> {\n const internal$ = new Subject()\n internal$\n .pipe(\n observeOn(animationFrameScheduler)\n )\n .subscribe({\n\n /* Update state */\n next({ hidden }) {\n if (hidden)\n setTabsState(el, \"hidden\")\n else\n resetTabsState(el)\n },\n\n /* Reset on complete */\n complete() {\n resetTabsState(el)\n }\n })\n\n /* Create and return component */\n return watchTabs(el, options)\n .pipe(\n tap(internal$),\n finalize(() => internal$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n animationFrameScheduler,\n combineLatest\n} from \"rxjs\"\nimport {\n bufferCount,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n finalize,\n map,\n observeOn,\n scan,\n startWith,\n switchMap,\n tap\n} from \"rxjs/operators\"\n\nimport {\n resetAnchorActive,\n resetAnchorState,\n setAnchorActive,\n setAnchorState\n} from \"~/actions\"\nimport {\n Viewport,\n getElement,\n getElements,\n watchElementSize\n} from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Table of contents\n */\nexport interface TableOfContents {\n prev: HTMLAnchorElement[][] /* Anchors (previous) */\n next: HTMLAnchorElement[][] /* Anchors (next) */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch table of contents\n *\n * This is effectively a scroll spy implementation which will account for the\n * fixed header and automatically re-calculate anchor offsets when the viewport\n * is resized. The returned observable will only emit if the table of contents\n * needs to be repainted.\n *\n * This implementation tracks an anchor element's entire path starting from its\n * level up to the top-most anchor element, e.g. `[h3, h2, h1]`. Although the\n * Material theme currently doesn't make use of this information, it enables\n * the styling of the entire hierarchy through customization.\n *\n * Note that the current anchor is the last item of the `prev` anchor list.\n *\n * @param anchors - Anchor elements\n * @param options - Options\n *\n * @returns Table of contents observable\n */\nexport function watchTableOfContents(\n anchors: HTMLAnchorElement[], { viewport$, header$ }: WatchOptions\n): Observable {\n const table = new Map()\n for (const anchor of anchors) {\n const id = decodeURIComponent(anchor.hash.substring(1))\n const target = getElement(`[id=\"${id}\"]`)\n if (typeof target !== \"undefined\")\n table.set(anchor, target)\n }\n\n /* Compute necessary adjustment for header */\n const adjust$ = header$\n .pipe(\n map(header => 24 + header.height)\n )\n\n /* Compute partition of previous and next anchors */\n const partition$ = watchElementSize(document.body)\n .pipe(\n distinctUntilKeyChanged(\"height\"),\n\n /* Build index to map anchor paths to vertical offsets */\n map(() => {\n let path: HTMLAnchorElement[] = []\n return [...table].reduce((index, [anchor, target]) => {\n while (path.length) {\n const last = table.get(path[path.length - 1])!\n if (last.tagName >= target.tagName) {\n path.pop()\n } else {\n break\n }\n }\n\n /* If the current anchor is hidden, continue with its parent */\n let offset = target.offsetTop\n while (!offset && target.parentElement) {\n target = target.parentElement\n offset = target.offsetTop\n }\n\n /* Map reversed anchor path to vertical offset */\n return index.set(\n [...path = [...path, anchor]].reverse(),\n offset\n )\n }, new Map())\n }),\n\n /* Sort index by vertical offset (see https://bit.ly/30z6QSO) */\n map(index => new Map([...index].sort(([, a], [, b]) => a - b))),\n\n /* Re-compute partition when viewport offset changes */\n switchMap(index => combineLatest([adjust$, viewport$])\n .pipe(\n scan(([prev, next], [adjust, { offset: { y } }]) => {\n\n /* Look forward */\n while (next.length) {\n const [, offset] = next[0]\n if (offset - adjust < y) {\n prev = [...prev, next.shift()!]\n } else {\n break\n }\n }\n\n /* Look backward */\n while (prev.length) {\n const [, offset] = prev[prev.length - 1]\n if (offset - adjust >= y) {\n next = [prev.pop()!, ...next]\n } else {\n break\n }\n }\n\n /* Return partition */\n return [prev, next]\n }, [[], [...index]]),\n distinctUntilChanged((a, b) => (\n a[0] === b[0] &&\n a[1] === b[1]\n ))\n )\n )\n )\n\n /* Compute and return anchor list migrations */\n return partition$\n .pipe(\n map(([prev, next]) => ({\n prev: prev.map(([path]) => path),\n next: next.map(([path]) => path)\n })),\n\n /* Extract anchor list migrations */\n startWith({ prev: [], next: [] }),\n bufferCount(2, 1),\n map(([a, b]) => {\n\n /* Moving down */\n if (a.prev.length < b.prev.length) {\n return {\n prev: b.prev.slice(Math.max(0, a.prev.length - 1), b.prev.length),\n next: []\n }\n\n /* Moving up */\n } else {\n return {\n prev: b.prev.slice(-1),\n next: b.next.slice(0, b.next.length - a.next.length)\n }\n }\n })\n )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Mount table of contents\n *\n * @param el - Anchor list element\n * @param options - Options\n *\n * @returns Table of contents component observable\n */\nexport function mountTableOfContents(\n el: HTMLElement, options: MountOptions\n): Observable> {\n const internal$ = new Subject()\n internal$\n .pipe(\n observeOn(animationFrameScheduler),\n )\n .subscribe(({ prev, next }) => {\n\n /* Look forward */\n for (const [anchor] of next) {\n resetAnchorActive(anchor)\n resetAnchorState(anchor)\n }\n\n /* Look backward */\n for (const [index, [anchor]] of prev.entries()) {\n setAnchorActive(anchor, index === prev.length - 1)\n setAnchorState(anchor, \"blur\")\n }\n })\n\n /* Create and return component */\n const anchors = getElements(\"[href^=\\\\#]\", el)\n return watchTableOfContents(anchors, options)\n .pipe(\n tap(internal$),\n finalize(() => internal$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n animationFrameScheduler,\n combineLatest\n} from \"rxjs\"\nimport {\n bufferCount,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n finalize,\n map,\n observeOn,\n tap\n} from \"rxjs/operators\"\n\nimport { resetBackToTopState, setBackToTopState } from \"~/actions\"\nimport { Viewport } from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Main } from \"../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Back-to-top button\n */\nexport interface BackToTop {\n hidden: boolean /* User scrolled up */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n main$: Observable
    /* Main area observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n main$: Observable
    /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch back-to-top\n *\n * @param _el - Back-to-top element\n * @param options - Options\n *\n * @returns Back-to-top observable\n */\nexport function watchBackToTop(\n _el: HTMLElement, { viewport$, main$ }: WatchOptions\n): Observable {\n\n /* Compute direction */\n const direction$ = viewport$\n .pipe(\n map(({ offset: { y } }) => y),\n bufferCount(2, 1),\n map(([a, b]) => a > b),\n distinctUntilChanged()\n )\n\n /* Compute whether button should be hidden */\n const hidden$ = main$\n .pipe(\n distinctUntilKeyChanged(\"active\")\n )\n\n /* Compute threshold for hiding */\n return combineLatest([hidden$, direction$])\n .pipe(\n map(([{ active }, direction]) => ({\n hidden: !(active && direction)\n })),\n distinctUntilChanged((a, b) => (\n a.hidden === b.hidden\n ))\n )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Mount back-to-top\n *\n * @param el - Back-to-top element\n * @param options - Options\n *\n * @returns Back-to-top component observable\n */\nexport function mountBackToTop(\n el: HTMLElement, options: MountOptions\n): Observable> {\n const internal$ = new Subject()\n internal$\n .pipe(\n observeOn(animationFrameScheduler)\n )\n .subscribe({\n\n /* Update state */\n next({ hidden }) {\n if (hidden)\n setBackToTopState(el, \"hidden\")\n else\n resetBackToTopState(el)\n },\n\n /* Reset on complete */\n complete() {\n resetBackToTopState(el)\n }\n })\n\n /* Create and return component */\n return watchBackToTop(el, options)\n .pipe(\n tap(internal$),\n finalize(() => internal$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, of } from \"rxjs\"\nimport {\n mapTo,\n mergeMap,\n switchMap,\n takeWhile,\n tap,\n withLatestFrom\n} from \"rxjs/operators\"\n\nimport { getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n document$: Observable /* Document observable */\n tablet$: Observable /* Tablet breakpoint observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch indeterminate checkboxes\n *\n * This function replaces the indeterminate \"pseudo state\" with the actual\n * indeterminate state, which is used to keep navigation always expanded.\n *\n * @param options - Options\n */\nexport function patchIndeterminate(\n { document$, tablet$ }: PatchOptions\n): void {\n document$\n .pipe(\n switchMap(() => of(...getElements(\n \"[data-md-state=indeterminate]\"\n ))),\n tap(el => {\n el.indeterminate = true\n el.checked = false\n }),\n mergeMap(el => fromEvent(el, \"change\")\n .pipe(\n takeWhile(() => el.hasAttribute(\"data-md-state\")),\n mapTo(el)\n )\n ),\n withLatestFrom(tablet$)\n )\n .subscribe(([el, tablet]) => {\n el.removeAttribute(\"data-md-state\")\n if (tablet)\n el.checked = false\n })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, of } from \"rxjs\"\nimport {\n filter,\n mapTo,\n mergeMap,\n switchMap,\n tap\n} from \"rxjs/operators\"\n\nimport { getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n document$: Observable /* Document observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Check whether the given device is an Apple device\n *\n * @returns Test result\n */\nfunction isAppleDevice(): boolean {\n return /(iPad|iPhone|iPod)/.test(navigator.userAgent)\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch all elements with `data-md-scrollfix` attributes\n *\n * This is a year-old patch which ensures that overflow scrolling works at the\n * top and bottom of containers on iOS by ensuring a `1px` scroll offset upon\n * the start of a touch event.\n *\n * @see https://bit.ly/2SCtAOO - Original source\n *\n * @param options - Options\n */\nexport function patchScrollfix(\n { document$ }: PatchOptions\n): void {\n document$\n .pipe(\n switchMap(() => of(...getElements(\"[data-md-scrollfix]\"))),\n tap(el => el.removeAttribute(\"data-md-scrollfix\")),\n filter(isAppleDevice),\n mergeMap(el => fromEvent(el, \"touchstart\")\n .pipe(\n mapTo(el)\n )\n )\n )\n .subscribe(el => {\n const top = el.scrollTop\n\n /* We're at the top of the container */\n if (top === 0) {\n el.scrollTop = 1\n\n /* We're at the bottom of the container */\n } else if (top + el.offsetHeight === el.scrollHeight) {\n el.scrollTop = top - 1\n }\n })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n animationFrameScheduler,\n combineLatest,\n of\n} from \"rxjs\"\nimport {\n delay,\n map,\n observeOn,\n switchMap,\n withLatestFrom\n} from \"rxjs/operators\"\n\nimport { resetScrollLock, setScrollLock } from \"~/actions\"\nimport { Viewport, watchToggle } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n viewport$: Observable /* Viewport observable */\n tablet$: Observable /* Tablet breakpoint observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch the document body to lock when search is open\n *\n * For mobile and tablet viewports, the search is rendered full screen, which\n * leads to scroll leaking when at the top or bottom of the search result. This\n * function locks the body when the search is in full screen mode, and restores\n * the scroll position when leaving.\n *\n * @param options - Options\n */\nexport function patchScrolllock(\n { viewport$, tablet$ }: PatchOptions\n): void {\n combineLatest([watchToggle(\"search\"), tablet$])\n .pipe(\n map(([active, tablet]) => active && !tablet),\n switchMap(active => of(active)\n .pipe(\n delay(active ? 400 : 100),\n observeOn(animationFrameScheduler)\n )\n ),\n withLatestFrom(viewport$)\n )\n .subscribe(([active, { offset: { y }}]) => {\n if (active)\n setScrollLock(document.body, y)\n else\n resetScrollLock(document.body)\n })\n}\n"], + "mappings": "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", + "names": [] +} diff --git a/assets/javascripts/lunr/min/lunr.ar.min.js b/assets/javascripts/lunr/min/lunr.ar.min.js new file mode 100644 index 000000000..248ddc5d1 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.ar.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ar=function(){this.pipeline.reset(),this.pipeline.add(e.ar.trimmer,e.ar.stopWordFilter,e.ar.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ar.stemmer))},e.ar.wordCharacters="ء-ٛٱـ",e.ar.trimmer=e.trimmerSupport.generateTrimmer(e.ar.wordCharacters),e.Pipeline.registerFunction(e.ar.trimmer,"trimmer-ar"),e.ar.stemmer=function(){var e=this;return e.result=!1,e.preRemoved=!1,e.sufRemoved=!1,e.pre={pre1:"ف ك ب و س ل ن ا ي ت",pre2:"ال لل",pre3:"بال وال فال تال كال ولل",pre4:"فبال كبال وبال وكال"},e.suf={suf1:"ه ك ت ن ا ي",suf2:"نك نه ها وك يا اه ون ين تن تم نا وا ان كم كن ني نن ما هم هن تك ته ات يه",suf3:"تين كهم نيه نهم ونه وها يهم ونا ونك وني وهم تكم تنا تها تني تهم كما كها ناه نكم هنا تان يها",suf4:"كموه ناها ونني ونهم تكما تموه تكاه كماه ناكم ناهم نيها وننا"},e.patterns=JSON.parse('{"pt43":[{"pt":[{"c":"ا","l":1}]},{"pt":[{"c":"ا,ت,ن,ي","l":0}],"mPt":[{"c":"ف","l":0,"m":1},{"c":"ع","l":1,"m":2},{"c":"ل","l":2,"m":3}]},{"pt":[{"c":"و","l":2}],"mPt":[{"c":"ف","l":0,"m":0},{"c":"ع","l":1,"m":1},{"c":"ل","l":2,"m":3}]},{"pt":[{"c":"ا","l":2}]},{"pt":[{"c":"ي","l":2}],"mPt":[{"c":"ف","l":0,"m":0},{"c":"ع","l":1,"m":1},{"c":"ا","l":2},{"c":"ل","l":3,"m":3}]},{"pt":[{"c":"م","l":0}]}],"pt53":[{"pt":[{"c":"ت","l":0},{"c":"ا","l":2}]},{"pt":[{"c":"ا,ن,ت,ي","l":0},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ت","l":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":0},{"c":"ا","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ع","l":2,"m":3},{"c":"ل","l":3,"m":4},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":0},{"c":"ا","l":3}],"mPt":[{"c":"ف","l":0,"m":1},{"c":"ع","l":1,"m":2},{"c":"ل","l":2,"m":4}]},{"pt":[{"c":"ا","l":3},{"c":"ن","l":4}]},{"pt":[{"c":"ت","l":0},{"c":"ي","l":3}]},{"pt":[{"c":"م","l":0},{"c":"و","l":3}]},{"pt":[{"c":"ا","l":1},{"c":"و","l":3}]},{"pt":[{"c":"و","l":1},{"c":"ا","l":2}]},{"pt":[{"c":"م","l":0},{"c":"ا","l":3}]},{"pt":[{"c":"م","l":0},{"c":"ي","l":3}]},{"pt":[{"c":"ا","l":2},{"c":"ن","l":3}]},{"pt":[{"c":"م","l":0},{"c":"ن","l":1}],"mPt":[{"c":"ا","l":0},{"c":"ن","l":1},{"c":"ف","l":2,"m":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"م","l":0},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ت","l":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"م","l":0},{"c":"ا","l":2}]},{"pt":[{"c":"م","l":1},{"c":"ا","l":3}]},{"pt":[{"c":"ي,ت,ا,ن","l":0},{"c":"ت","l":1}],"mPt":[{"c":"ف","l":0,"m":2},{"c":"ع","l":1,"m":3},{"c":"ا","l":2},{"c":"ل","l":3,"m":4}]},{"pt":[{"c":"ت,ي,ا,ن","l":0},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ت","l":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":2},{"c":"ي","l":3}]},{"pt":[{"c":"ا,ي,ت,ن","l":0},{"c":"ن","l":1}],"mPt":[{"c":"ا","l":0},{"c":"ن","l":1},{"c":"ف","l":2,"m":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":3},{"c":"ء","l":4}]}],"pt63":[{"pt":[{"c":"ا","l":0},{"c":"ت","l":2},{"c":"ا","l":4}]},{"pt":[{"c":"ا,ت,ن,ي","l":0},{"c":"س","l":1},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"س","l":1},{"c":"ت","l":2},{"c":"ف","l":3,"m":3},{"c":"ع","l":4,"m":4},{"c":"ا","l":5},{"c":"ل","l":6,"m":5}]},{"pt":[{"c":"ا,ن,ت,ي","l":0},{"c":"و","l":3}]},{"pt":[{"c":"م","l":0},{"c":"س","l":1},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"س","l":1},{"c":"ت","l":2},{"c":"ف","l":3,"m":3},{"c":"ع","l":4,"m":4},{"c":"ا","l":5},{"c":"ل","l":6,"m":5}]},{"pt":[{"c":"ي","l":1},{"c":"ي","l":3},{"c":"ا","l":4},{"c":"ء","l":5}]},{"pt":[{"c":"ا","l":0},{"c":"ن","l":1},{"c":"ا","l":4}]}],"pt54":[{"pt":[{"c":"ت","l":0}]},{"pt":[{"c":"ا,ي,ت,ن","l":0}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ع","l":2,"m":2},{"c":"ل","l":3,"m":3},{"c":"ر","l":4,"m":4},{"c":"ا","l":5},{"c":"ر","l":6,"m":4}]},{"pt":[{"c":"م","l":0}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ع","l":2,"m":2},{"c":"ل","l":3,"m":3},{"c":"ر","l":4,"m":4},{"c":"ا","l":5},{"c":"ر","l":6,"m":4}]},{"pt":[{"c":"ا","l":2}]},{"pt":[{"c":"ا","l":0},{"c":"ن","l":2}]}],"pt64":[{"pt":[{"c":"ا","l":0},{"c":"ا","l":4}]},{"pt":[{"c":"م","l":0},{"c":"ت","l":1}]}],"pt73":[{"pt":[{"c":"ا","l":0},{"c":"س","l":1},{"c":"ت","l":2},{"c":"ا","l":5}]}],"pt75":[{"pt":[{"c":"ا","l":0},{"c":"ا","l":5}]}]}'),e.execArray=["cleanWord","removeDiacritics","cleanAlef","removeStopWords","normalizeHamzaAndAlef","removeStartWaw","removePre432","removeEndTaa","wordCheck"],e.stem=function(){var r=0;for(e.result=!1,e.preRemoved=!1,e.sufRemoved=!1;r=0)return!0},e.normalizeHamzaAndAlef=function(){return e.word=e.word.replace("ؤ","ء"),e.word=e.word.replace("ئ","ء"),e.word=e.word.replace(/([\u0627])\1+/gi,"ا"),!1},e.removeEndTaa=function(){return!(e.word.length>2)||(e.word=e.word.replace(/[\u0627]$/,""),e.word=e.word.replace("ة",""),!1)},e.removeStartWaw=function(){return e.word.length>3&&"و"==e.word[0]&&"و"==e.word[1]&&(e.word=e.word.slice(1)),!1},e.removePre432=function(){var r=e.word;if(e.word.length>=7){var t=new RegExp("^("+e.pre.pre4.split(" ").join("|")+")");e.word=e.word.replace(t,"")}if(e.word==r&&e.word.length>=6){var c=new RegExp("^("+e.pre.pre3.split(" ").join("|")+")");e.word=e.word.replace(c,"")}if(e.word==r&&e.word.length>=5){var l=new RegExp("^("+e.pre.pre2.split(" ").join("|")+")");e.word=e.word.replace(l,"")}return r!=e.word&&(e.preRemoved=!0),!1},e.patternCheck=function(r){for(var t=0;t3){var t=new RegExp("^("+e.pre.pre1.split(" ").join("|")+")");e.word=e.word.replace(t,"")}return r!=e.word&&(e.preRemoved=!0),!1},e.removeSuf1=function(){var r=e.word;if(0==e.sufRemoved&&e.word.length>3){var t=new RegExp("("+e.suf.suf1.split(" ").join("|")+")$");e.word=e.word.replace(t,"")}return r!=e.word&&(e.sufRemoved=!0),!1},e.removeSuf432=function(){var r=e.word;if(e.word.length>=6){var t=new RegExp("("+e.suf.suf4.split(" ").join("|")+")$");e.word=e.word.replace(t,"")}if(e.word==r&&e.word.length>=5){var c=new RegExp("("+e.suf.suf3.split(" ").join("|")+")$");e.word=e.word.replace(c,"")}if(e.word==r&&e.word.length>=4){var l=new RegExp("("+e.suf.suf2.split(" ").join("|")+")$");e.word=e.word.replace(l,"")}return r!=e.word&&(e.sufRemoved=!0),!1},e.wordCheck=function(){for(var r=(e.word,[e.removeSuf432,e.removeSuf1,e.removePre1]),t=0,c=!1;e.word.length>=7&&!e.result&&t=f.limit)return;f.cursor++}for(;!f.out_grouping(w,97,248);){if(f.cursor>=f.limit)return;f.cursor++}d=f.cursor,d=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(c,32),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del();break;case 2:f.in_grouping_b(p,97,229)&&f.slice_del()}}function t(){var e,r=f.limit-f.cursor;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.find_among_b(l,4)?(f.bra=f.cursor,f.limit_backward=e,f.cursor=f.limit-r,f.cursor>f.limit_backward&&(f.cursor--,f.bra=f.cursor,f.slice_del())):f.limit_backward=e)}function s(){var e,r,i,n=f.limit-f.cursor;if(f.ket=f.cursor,f.eq_s_b(2,"st")&&(f.bra=f.cursor,f.eq_s_b(2,"ig")&&f.slice_del()),f.cursor=f.limit-n,f.cursor>=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(m,5),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del(),i=f.limit-f.cursor,t(),f.cursor=f.limit-i;break;case 2:f.slice_from("løs")}}function o(){var e;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.out_grouping_b(w,97,248)?(f.bra=f.cursor,u=f.slice_to(u),f.limit_backward=e,f.eq_v_b(u)&&f.slice_del()):f.limit_backward=e)}var a,d,u,c=[new r("hed",-1,1),new r("ethed",0,1),new r("ered",-1,1),new r("e",-1,1),new r("erede",3,1),new r("ende",3,1),new r("erende",5,1),new r("ene",3,1),new r("erne",3,1),new r("ere",3,1),new r("en",-1,1),new r("heden",10,1),new r("eren",10,1),new r("er",-1,1),new r("heder",13,1),new r("erer",13,1),new r("s",-1,2),new r("heds",16,1),new r("es",16,1),new r("endes",18,1),new r("erendes",19,1),new r("enes",18,1),new r("ernes",18,1),new r("eres",18,1),new r("ens",16,1),new r("hedens",24,1),new r("erens",24,1),new r("ers",16,1),new r("ets",16,1),new r("erets",28,1),new r("et",-1,1),new r("eret",30,1)],l=[new r("gd",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("elig",1,1),new r("els",-1,1),new r("løst",-1,2)],w=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],p=[239,254,42,3,0,0,0,0,0,0,0,0,0,0,0,0,16],f=new i;this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var r=f.cursor;return e(),f.limit_backward=r,f.cursor=f.limit,n(),f.cursor=f.limit,t(),f.cursor=f.limit,s(),f.cursor=f.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.da.stemmer,"stemmer-da"),e.da.stopWordFilter=e.generateStopWordFilter("ad af alle alt anden at blev blive bliver da de dem den denne der deres det dette dig din disse dog du efter eller en end er et for fra ham han hans har havde have hende hendes her hos hun hvad hvis hvor i ikke ind jeg jer jo kunne man mange med meget men mig min mine mit mod ned noget nogle nu når og også om op os over på selv sig sin sine sit skal skulle som sådan thi til ud under var vi vil ville vor være været".split(" ")),e.Pipeline.registerFunction(e.da.stopWordFilter,"stopWordFilter-da")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.de.min.js b/assets/javascripts/lunr/min/lunr.de.min.js new file mode 100644 index 000000000..f3b5c108c --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.de.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `German` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.de=function(){this.pipeline.reset(),this.pipeline.add(e.de.trimmer,e.de.stopWordFilter,e.de.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.de.stemmer))},e.de.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.de.trimmer=e.trimmerSupport.generateTrimmer(e.de.wordCharacters),e.Pipeline.registerFunction(e.de.trimmer,"trimmer-de"),e.de.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(e,r,n){return!(!v.eq_s(1,e)||(v.ket=v.cursor,!v.in_grouping(p,97,252)))&&(v.slice_from(r),v.cursor=n,!0)}function i(){for(var r,n,i,s,t=v.cursor;;)if(r=v.cursor,v.bra=r,v.eq_s(1,"ß"))v.ket=v.cursor,v.slice_from("ss");else{if(r>=v.limit)break;v.cursor=r+1}for(v.cursor=t;;)for(n=v.cursor;;){if(i=v.cursor,v.in_grouping(p,97,252)){if(s=v.cursor,v.bra=s,e("u","U",i))break;if(v.cursor=s,e("y","Y",i))break}if(i>=v.limit)return void(v.cursor=n);v.cursor=i+1}}function s(){for(;!v.in_grouping(p,97,252);){if(v.cursor>=v.limit)return!0;v.cursor++}for(;!v.out_grouping(p,97,252);){if(v.cursor>=v.limit)return!0;v.cursor++}return!1}function t(){m=v.limit,l=m;var e=v.cursor+3;0<=e&&e<=v.limit&&(d=e,s()||(m=v.cursor,m=v.limit)return;v.cursor++}}}function c(){return m<=v.cursor}function u(){return l<=v.cursor}function a(){var e,r,n,i,s=v.limit-v.cursor;if(v.ket=v.cursor,(e=v.find_among_b(w,7))&&(v.bra=v.cursor,c()))switch(e){case 1:v.slice_del();break;case 2:v.slice_del(),v.ket=v.cursor,v.eq_s_b(1,"s")&&(v.bra=v.cursor,v.eq_s_b(3,"nis")&&v.slice_del());break;case 3:v.in_grouping_b(g,98,116)&&v.slice_del()}if(v.cursor=v.limit-s,v.ket=v.cursor,(e=v.find_among_b(f,4))&&(v.bra=v.cursor,c()))switch(e){case 1:v.slice_del();break;case 2:if(v.in_grouping_b(k,98,116)){var t=v.cursor-3;v.limit_backward<=t&&t<=v.limit&&(v.cursor=t,v.slice_del())}}if(v.cursor=v.limit-s,v.ket=v.cursor,(e=v.find_among_b(_,8))&&(v.bra=v.cursor,u()))switch(e){case 1:v.slice_del(),v.ket=v.cursor,v.eq_s_b(2,"ig")&&(v.bra=v.cursor,r=v.limit-v.cursor,v.eq_s_b(1,"e")||(v.cursor=v.limit-r,u()&&v.slice_del()));break;case 2:n=v.limit-v.cursor,v.eq_s_b(1,"e")||(v.cursor=v.limit-n,v.slice_del());break;case 3:if(v.slice_del(),v.ket=v.cursor,i=v.limit-v.cursor,!v.eq_s_b(2,"er")&&(v.cursor=v.limit-i,!v.eq_s_b(2,"en")))break;v.bra=v.cursor,c()&&v.slice_del();break;case 4:v.slice_del(),v.ket=v.cursor,e=v.find_among_b(b,2),e&&(v.bra=v.cursor,u()&&1==e&&v.slice_del())}}var d,l,m,h=[new r("",-1,6),new r("U",0,2),new r("Y",0,1),new r("ä",0,3),new r("ö",0,4),new r("ü",0,5)],w=[new r("e",-1,2),new r("em",-1,1),new r("en",-1,2),new r("ern",-1,1),new r("er",-1,1),new r("s",-1,3),new r("es",5,2)],f=[new r("en",-1,1),new r("er",-1,1),new r("st",-1,2),new r("est",2,1)],b=[new r("ig",-1,1),new r("lich",-1,1)],_=[new r("end",-1,1),new r("ig",-1,2),new r("ung",-1,1),new r("lich",-1,3),new r("isch",-1,2),new r("ik",-1,2),new r("heit",-1,3),new r("keit",-1,4)],p=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32,8],g=[117,30,5],k=[117,30,4],v=new n;this.setCurrent=function(e){v.setCurrent(e)},this.getCurrent=function(){return v.getCurrent()},this.stem=function(){var e=v.cursor;return i(),v.cursor=e,t(),v.limit_backward=e,v.cursor=v.limit,a(),v.cursor=v.limit_backward,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.de.stemmer,"stemmer-de"),e.de.stopWordFilter=e.generateStopWordFilter("aber alle allem allen aller alles als also am an ander andere anderem anderen anderer anderes anderm andern anderr anders auch auf aus bei bin bis bist da damit dann das dasselbe dazu daß dein deine deinem deinen deiner deines dem demselben den denn denselben der derer derselbe derselben des desselben dessen dich die dies diese dieselbe dieselben diesem diesen dieser dieses dir doch dort du durch ein eine einem einen einer eines einig einige einigem einigen einiger einiges einmal er es etwas euch euer eure eurem euren eurer eures für gegen gewesen hab habe haben hat hatte hatten hier hin hinter ich ihm ihn ihnen ihr ihre ihrem ihren ihrer ihres im in indem ins ist jede jedem jeden jeder jedes jene jenem jenen jener jenes jetzt kann kein keine keinem keinen keiner keines können könnte machen man manche manchem manchen mancher manches mein meine meinem meinen meiner meines mich mir mit muss musste nach nicht nichts noch nun nur ob oder ohne sehr sein seine seinem seinen seiner seines selbst sich sie sind so solche solchem solchen solcher solches soll sollte sondern sonst um und uns unse unsem unsen unser unses unter viel vom von vor war waren warst was weg weil weiter welche welchem welchen welcher welches wenn werde werden wie wieder will wir wird wirst wo wollen wollte während würde würden zu zum zur zwar zwischen über".split(" ")),e.Pipeline.registerFunction(e.de.stopWordFilter,"stopWordFilter-de")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.du.min.js b/assets/javascripts/lunr/min/lunr.du.min.js new file mode 100644 index 000000000..49a0f3f0a --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.du.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Dutch` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");console.warn('[Lunr Languages] Please use the "nl" instead of the "du". The "nl" code is the standard code for Dutch language, and "du" will be removed in the next major versions.'),e.du=function(){this.pipeline.reset(),this.pipeline.add(e.du.trimmer,e.du.stopWordFilter,e.du.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.du.stemmer))},e.du.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.du.trimmer=e.trimmerSupport.generateTrimmer(e.du.wordCharacters),e.Pipeline.registerFunction(e.du.trimmer,"trimmer-du"),e.du.stemmer=function(){var r=e.stemmerSupport.Among,i=e.stemmerSupport.SnowballProgram,n=new function(){function e(){for(var e,r,i,o=C.cursor;;){if(C.bra=C.cursor,e=C.find_among(b,11))switch(C.ket=C.cursor,e){case 1:C.slice_from("a");continue;case 2:C.slice_from("e");continue;case 3:C.slice_from("i");continue;case 4:C.slice_from("o");continue;case 5:C.slice_from("u");continue;case 6:if(C.cursor>=C.limit)break;C.cursor++;continue}break}for(C.cursor=o,C.bra=o,C.eq_s(1,"y")?(C.ket=C.cursor,C.slice_from("Y")):C.cursor=o;;)if(r=C.cursor,C.in_grouping(q,97,232)){if(i=C.cursor,C.bra=i,C.eq_s(1,"i"))C.ket=C.cursor,C.in_grouping(q,97,232)&&(C.slice_from("I"),C.cursor=r);else if(C.cursor=i,C.eq_s(1,"y"))C.ket=C.cursor,C.slice_from("Y"),C.cursor=r;else if(n(r))break}else if(n(r))break}function n(e){return C.cursor=e,e>=C.limit||(C.cursor++,!1)}function o(){_=C.limit,f=_,t()||(_=C.cursor,_<3&&(_=3),t()||(f=C.cursor))}function t(){for(;!C.in_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}for(;!C.out_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}return!1}function s(){for(var e;;)if(C.bra=C.cursor,e=C.find_among(p,3))switch(C.ket=C.cursor,e){case 1:C.slice_from("y");break;case 2:C.slice_from("i");break;case 3:if(C.cursor>=C.limit)return;C.cursor++}}function u(){return _<=C.cursor}function c(){return f<=C.cursor}function a(){var e=C.limit-C.cursor;C.find_among_b(g,3)&&(C.cursor=C.limit-e,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del()))}function l(){var e;w=!1,C.ket=C.cursor,C.eq_s_b(1,"e")&&(C.bra=C.cursor,u()&&(e=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-e,C.slice_del(),w=!0,a())))}function m(){var e;u()&&(e=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-e,C.eq_s_b(3,"gem")||(C.cursor=C.limit-e,C.slice_del(),a())))}function d(){var e,r,i,n,o,t,s=C.limit-C.cursor;if(C.ket=C.cursor,e=C.find_among_b(h,5))switch(C.bra=C.cursor,e){case 1:u()&&C.slice_from("heid");break;case 2:m();break;case 3:u()&&C.out_grouping_b(z,97,232)&&C.slice_del()}if(C.cursor=C.limit-s,l(),C.cursor=C.limit-s,C.ket=C.cursor,C.eq_s_b(4,"heid")&&(C.bra=C.cursor,c()&&(r=C.limit-C.cursor,C.eq_s_b(1,"c")||(C.cursor=C.limit-r,C.slice_del(),C.ket=C.cursor,C.eq_s_b(2,"en")&&(C.bra=C.cursor,m())))),C.cursor=C.limit-s,C.ket=C.cursor,e=C.find_among_b(k,6))switch(C.bra=C.cursor,e){case 1:if(c()){if(C.slice_del(),i=C.limit-C.cursor,C.ket=C.cursor,C.eq_s_b(2,"ig")&&(C.bra=C.cursor,c()&&(n=C.limit-C.cursor,!C.eq_s_b(1,"e")))){C.cursor=C.limit-n,C.slice_del();break}C.cursor=C.limit-i,a()}break;case 2:c()&&(o=C.limit-C.cursor,C.eq_s_b(1,"e")||(C.cursor=C.limit-o,C.slice_del()));break;case 3:c()&&(C.slice_del(),l());break;case 4:c()&&C.slice_del();break;case 5:c()&&w&&C.slice_del()}C.cursor=C.limit-s,C.out_grouping_b(j,73,232)&&(t=C.limit-C.cursor,C.find_among_b(v,4)&&C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-t,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del())))}var f,_,w,b=[new r("",-1,6),new r("á",0,1),new r("ä",0,1),new r("é",0,2),new r("ë",0,2),new r("í",0,3),new r("ï",0,3),new r("ó",0,4),new r("ö",0,4),new r("ú",0,5),new r("ü",0,5)],p=[new r("",-1,3),new r("I",0,2),new r("Y",0,1)],g=[new r("dd",-1,-1),new r("kk",-1,-1),new r("tt",-1,-1)],h=[new r("ene",-1,2),new r("se",-1,3),new r("en",-1,2),new r("heden",2,1),new r("s",-1,3)],k=[new r("end",-1,1),new r("ig",-1,2),new r("ing",-1,1),new r("lijk",-1,3),new r("baar",-1,4),new r("bar",-1,5)],v=[new r("aa",-1,-1),new r("ee",-1,-1),new r("oo",-1,-1),new r("uu",-1,-1)],q=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],j=[1,0,0,17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],z=[17,67,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],C=new i;this.setCurrent=function(e){C.setCurrent(e)},this.getCurrent=function(){return C.getCurrent()},this.stem=function(){var r=C.cursor;return e(),C.cursor=r,o(),C.limit_backward=r,C.cursor=C.limit,d(),C.cursor=C.limit_backward,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.du.stemmer,"stemmer-du"),e.du.stopWordFilter=e.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),e.Pipeline.registerFunction(e.du.stopWordFilter,"stopWordFilter-du")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.es.min.js b/assets/javascripts/lunr/min/lunr.es.min.js new file mode 100644 index 000000000..2989d3426 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.es.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Spanish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,s){"function"==typeof define&&define.amd?define(s):"object"==typeof exports?module.exports=s():s()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.es=function(){this.pipeline.reset(),this.pipeline.add(e.es.trimmer,e.es.stopWordFilter,e.es.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.es.stemmer))},e.es.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.es.trimmer=e.trimmerSupport.generateTrimmer(e.es.wordCharacters),e.Pipeline.registerFunction(e.es.trimmer,"trimmer-es"),e.es.stemmer=function(){var s=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,n=new function(){function e(){if(A.out_grouping(x,97,252)){for(;!A.in_grouping(x,97,252);){if(A.cursor>=A.limit)return!0;A.cursor++}return!1}return!0}function n(){if(A.in_grouping(x,97,252)){var s=A.cursor;if(e()){if(A.cursor=s,!A.in_grouping(x,97,252))return!0;for(;!A.out_grouping(x,97,252);){if(A.cursor>=A.limit)return!0;A.cursor++}}return!1}return!0}function i(){var s,r=A.cursor;if(n()){if(A.cursor=r,!A.out_grouping(x,97,252))return;if(s=A.cursor,e()){if(A.cursor=s,!A.in_grouping(x,97,252)||A.cursor>=A.limit)return;A.cursor++}}g=A.cursor}function a(){for(;!A.in_grouping(x,97,252);){if(A.cursor>=A.limit)return!1;A.cursor++}for(;!A.out_grouping(x,97,252);){if(A.cursor>=A.limit)return!1;A.cursor++}return!0}function t(){var e=A.cursor;g=A.limit,p=g,v=g,i(),A.cursor=e,a()&&(p=A.cursor,a()&&(v=A.cursor))}function o(){for(var e;;){if(A.bra=A.cursor,e=A.find_among(k,6))switch(A.ket=A.cursor,e){case 1:A.slice_from("a");continue;case 2:A.slice_from("e");continue;case 3:A.slice_from("i");continue;case 4:A.slice_from("o");continue;case 5:A.slice_from("u");continue;case 6:if(A.cursor>=A.limit)break;A.cursor++;continue}break}}function u(){return g<=A.cursor}function w(){return p<=A.cursor}function c(){return v<=A.cursor}function m(){var e;if(A.ket=A.cursor,A.find_among_b(y,13)&&(A.bra=A.cursor,(e=A.find_among_b(q,11))&&u()))switch(e){case 1:A.bra=A.cursor,A.slice_from("iendo");break;case 2:A.bra=A.cursor,A.slice_from("ando");break;case 3:A.bra=A.cursor,A.slice_from("ar");break;case 4:A.bra=A.cursor,A.slice_from("er");break;case 5:A.bra=A.cursor,A.slice_from("ir");break;case 6:A.slice_del();break;case 7:A.eq_s_b(1,"u")&&A.slice_del()}}function l(e,s){if(!c())return!0;A.slice_del(),A.ket=A.cursor;var r=A.find_among_b(e,s);return r&&(A.bra=A.cursor,1==r&&c()&&A.slice_del()),!1}function d(e){return!c()||(A.slice_del(),A.ket=A.cursor,A.eq_s_b(2,e)&&(A.bra=A.cursor,c()&&A.slice_del()),!1)}function b(){var e;if(A.ket=A.cursor,e=A.find_among_b(S,46)){switch(A.bra=A.cursor,e){case 1:if(!c())return!1;A.slice_del();break;case 2:if(d("ic"))return!1;break;case 3:if(!c())return!1;A.slice_from("log");break;case 4:if(!c())return!1;A.slice_from("u");break;case 5:if(!c())return!1;A.slice_from("ente");break;case 6:if(!w())return!1;A.slice_del(),A.ket=A.cursor,e=A.find_among_b(C,4),e&&(A.bra=A.cursor,c()&&(A.slice_del(),1==e&&(A.ket=A.cursor,A.eq_s_b(2,"at")&&(A.bra=A.cursor,c()&&A.slice_del()))));break;case 7:if(l(P,3))return!1;break;case 8:if(l(F,3))return!1;break;case 9:if(d("at"))return!1}return!0}return!1}function f(){var e,s;if(A.cursor>=g&&(s=A.limit_backward,A.limit_backward=g,A.ket=A.cursor,e=A.find_among_b(W,12),A.limit_backward=s,e)){if(A.bra=A.cursor,1==e){if(!A.eq_s_b(1,"u"))return!1;A.slice_del()}return!0}return!1}function _(){var e,s,r,n;if(A.cursor>=g&&(s=A.limit_backward,A.limit_backward=g,A.ket=A.cursor,e=A.find_among_b(L,96),A.limit_backward=s,e))switch(A.bra=A.cursor,e){case 1:r=A.limit-A.cursor,A.eq_s_b(1,"u")?(n=A.limit-A.cursor,A.eq_s_b(1,"g")?A.cursor=A.limit-n:A.cursor=A.limit-r):A.cursor=A.limit-r,A.bra=A.cursor;case 2:A.slice_del()}}function h(){var e,s;if(A.ket=A.cursor,e=A.find_among_b(z,8))switch(A.bra=A.cursor,e){case 1:u()&&A.slice_del();break;case 2:u()&&(A.slice_del(),A.ket=A.cursor,A.eq_s_b(1,"u")&&(A.bra=A.cursor,s=A.limit-A.cursor,A.eq_s_b(1,"g")&&(A.cursor=A.limit-s,u()&&A.slice_del())))}}var v,p,g,k=[new s("",-1,6),new s("á",0,1),new s("é",0,2),new s("í",0,3),new s("ó",0,4),new s("ú",0,5)],y=[new s("la",-1,-1),new s("sela",0,-1),new s("le",-1,-1),new s("me",-1,-1),new s("se",-1,-1),new s("lo",-1,-1),new s("selo",5,-1),new s("las",-1,-1),new s("selas",7,-1),new s("les",-1,-1),new s("los",-1,-1),new s("selos",10,-1),new s("nos",-1,-1)],q=[new s("ando",-1,6),new s("iendo",-1,6),new s("yendo",-1,7),new s("ándo",-1,2),new s("iéndo",-1,1),new s("ar",-1,6),new s("er",-1,6),new s("ir",-1,6),new s("ár",-1,3),new s("ér",-1,4),new s("ír",-1,5)],C=[new s("ic",-1,-1),new s("ad",-1,-1),new s("os",-1,-1),new s("iv",-1,1)],P=[new s("able",-1,1),new s("ible",-1,1),new s("ante",-1,1)],F=[new s("ic",-1,1),new s("abil",-1,1),new s("iv",-1,1)],S=[new s("ica",-1,1),new s("ancia",-1,2),new s("encia",-1,5),new s("adora",-1,2),new s("osa",-1,1),new s("ista",-1,1),new s("iva",-1,9),new s("anza",-1,1),new s("logía",-1,3),new s("idad",-1,8),new s("able",-1,1),new s("ible",-1,1),new s("ante",-1,2),new s("mente",-1,7),new s("amente",13,6),new s("ación",-1,2),new s("ución",-1,4),new s("ico",-1,1),new s("ismo",-1,1),new s("oso",-1,1),new s("amiento",-1,1),new s("imiento",-1,1),new s("ivo",-1,9),new s("ador",-1,2),new s("icas",-1,1),new s("ancias",-1,2),new s("encias",-1,5),new s("adoras",-1,2),new s("osas",-1,1),new s("istas",-1,1),new s("ivas",-1,9),new s("anzas",-1,1),new s("logías",-1,3),new s("idades",-1,8),new s("ables",-1,1),new s("ibles",-1,1),new s("aciones",-1,2),new s("uciones",-1,4),new s("adores",-1,2),new s("antes",-1,2),new s("icos",-1,1),new s("ismos",-1,1),new s("osos",-1,1),new s("amientos",-1,1),new s("imientos",-1,1),new s("ivos",-1,9)],W=[new s("ya",-1,1),new s("ye",-1,1),new s("yan",-1,1),new s("yen",-1,1),new s("yeron",-1,1),new s("yendo",-1,1),new s("yo",-1,1),new s("yas",-1,1),new s("yes",-1,1),new s("yais",-1,1),new s("yamos",-1,1),new s("yó",-1,1)],L=[new s("aba",-1,2),new s("ada",-1,2),new s("ida",-1,2),new s("ara",-1,2),new s("iera",-1,2),new s("ía",-1,2),new s("aría",5,2),new s("ería",5,2),new s("iría",5,2),new s("ad",-1,2),new s("ed",-1,2),new s("id",-1,2),new s("ase",-1,2),new s("iese",-1,2),new s("aste",-1,2),new s("iste",-1,2),new s("an",-1,2),new s("aban",16,2),new s("aran",16,2),new s("ieran",16,2),new s("ían",16,2),new s("arían",20,2),new s("erían",20,2),new s("irían",20,2),new s("en",-1,1),new s("asen",24,2),new s("iesen",24,2),new s("aron",-1,2),new s("ieron",-1,2),new s("arán",-1,2),new s("erán",-1,2),new s("irán",-1,2),new s("ado",-1,2),new s("ido",-1,2),new s("ando",-1,2),new s("iendo",-1,2),new s("ar",-1,2),new s("er",-1,2),new s("ir",-1,2),new s("as",-1,2),new s("abas",39,2),new s("adas",39,2),new s("idas",39,2),new s("aras",39,2),new s("ieras",39,2),new s("ías",39,2),new s("arías",45,2),new s("erías",45,2),new s("irías",45,2),new s("es",-1,1),new s("ases",49,2),new s("ieses",49,2),new s("abais",-1,2),new s("arais",-1,2),new s("ierais",-1,2),new s("íais",-1,2),new s("aríais",55,2),new s("eríais",55,2),new s("iríais",55,2),new s("aseis",-1,2),new s("ieseis",-1,2),new s("asteis",-1,2),new s("isteis",-1,2),new s("áis",-1,2),new s("éis",-1,1),new s("aréis",64,2),new s("eréis",64,2),new s("iréis",64,2),new s("ados",-1,2),new s("idos",-1,2),new s("amos",-1,2),new s("ábamos",70,2),new s("áramos",70,2),new s("iéramos",70,2),new s("íamos",70,2),new s("aríamos",74,2),new s("eríamos",74,2),new s("iríamos",74,2),new s("emos",-1,1),new s("aremos",78,2),new s("eremos",78,2),new s("iremos",78,2),new s("ásemos",78,2),new s("iésemos",78,2),new s("imos",-1,2),new s("arás",-1,2),new s("erás",-1,2),new s("irás",-1,2),new s("ís",-1,2),new s("ará",-1,2),new s("erá",-1,2),new s("irá",-1,2),new s("aré",-1,2),new s("eré",-1,2),new s("iré",-1,2),new s("ió",-1,2)],z=[new s("a",-1,1),new s("e",-1,2),new s("o",-1,1),new s("os",-1,1),new s("á",-1,1),new s("é",-1,2),new s("í",-1,1),new s("ó",-1,1)],x=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,4,10],A=new r;this.setCurrent=function(e){A.setCurrent(e)},this.getCurrent=function(){return A.getCurrent()},this.stem=function(){var e=A.cursor;return t(),A.limit_backward=e,A.cursor=A.limit,m(),A.cursor=A.limit,b()||(A.cursor=A.limit,f()||(A.cursor=A.limit,_())),A.cursor=A.limit,h(),A.cursor=A.limit_backward,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.es.stemmer,"stemmer-es"),e.es.stopWordFilter=e.generateStopWordFilter("a al algo algunas algunos ante antes como con contra cual cuando de del desde donde durante e el ella ellas ellos en entre era erais eran eras eres es esa esas ese eso esos esta estaba estabais estaban estabas estad estada estadas estado estados estamos estando estar estaremos estará estarán estarás estaré estaréis estaría estaríais estaríamos estarían estarías estas este estemos esto estos estoy estuve estuviera estuvierais estuvieran estuvieras estuvieron estuviese estuvieseis estuviesen estuvieses estuvimos estuviste estuvisteis estuviéramos estuviésemos estuvo está estábamos estáis están estás esté estéis estén estés fue fuera fuerais fueran fueras fueron fuese fueseis fuesen fueses fui fuimos fuiste fuisteis fuéramos fuésemos ha habida habidas habido habidos habiendo habremos habrá habrán habrás habré habréis habría habríais habríamos habrían habrías habéis había habíais habíamos habían habías han has hasta hay haya hayamos hayan hayas hayáis he hemos hube hubiera hubierais hubieran hubieras hubieron hubiese hubieseis hubiesen hubieses hubimos hubiste hubisteis hubiéramos hubiésemos hubo la las le les lo los me mi mis mucho muchos muy más mí mía mías mío míos nada ni no nos nosotras nosotros nuestra nuestras nuestro nuestros o os otra otras otro otros para pero poco por porque que quien quienes qué se sea seamos sean seas seremos será serán serás seré seréis sería seríais seríamos serían serías seáis sido siendo sin sobre sois somos son soy su sus suya suyas suyo suyos sí también tanto te tendremos tendrá tendrán tendrás tendré tendréis tendría tendríais tendríamos tendrían tendrías tened tenemos tenga tengamos tengan tengas tengo tengáis tenida tenidas tenido tenidos teniendo tenéis tenía teníais teníamos tenían tenías ti tiene tienen tienes todo todos tu tus tuve tuviera tuvierais tuvieran tuvieras tuvieron tuviese tuvieseis tuviesen tuvieses tuvimos tuviste tuvisteis tuviéramos tuviésemos tuvo tuya tuyas tuyo tuyos tú un una uno unos vosotras vosotros vuestra vuestras vuestro vuestros y ya yo él éramos".split(" ")),e.Pipeline.registerFunction(e.es.stopWordFilter,"stopWordFilter-es")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.fi.min.js b/assets/javascripts/lunr/min/lunr.fi.min.js new file mode 100644 index 000000000..29f5dfcea --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.fi.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Finnish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(i,e){"function"==typeof define&&define.amd?define(e):"object"==typeof exports?module.exports=e():e()(i.lunr)}(this,function(){return function(i){if(void 0===i)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===i.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");i.fi=function(){this.pipeline.reset(),this.pipeline.add(i.fi.trimmer,i.fi.stopWordFilter,i.fi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(i.fi.stemmer))},i.fi.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",i.fi.trimmer=i.trimmerSupport.generateTrimmer(i.fi.wordCharacters),i.Pipeline.registerFunction(i.fi.trimmer,"trimmer-fi"),i.fi.stemmer=function(){var e=i.stemmerSupport.Among,r=i.stemmerSupport.SnowballProgram,n=new function(){function i(){f=A.limit,d=f,n()||(f=A.cursor,n()||(d=A.cursor))}function n(){for(var i;;){if(i=A.cursor,A.in_grouping(W,97,246))break;if(A.cursor=i,i>=A.limit)return!0;A.cursor++}for(A.cursor=i;!A.out_grouping(W,97,246);){if(A.cursor>=A.limit)return!0;A.cursor++}return!1}function t(){return d<=A.cursor}function s(){var i,e;if(A.cursor>=f)if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,i=A.find_among_b(h,10)){switch(A.bra=A.cursor,A.limit_backward=e,i){case 1:if(!A.in_grouping_b(x,97,246))return;break;case 2:if(!t())return}A.slice_del()}else A.limit_backward=e}function o(){var i,e,r;if(A.cursor>=f)if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,i=A.find_among_b(v,9))switch(A.bra=A.cursor,A.limit_backward=e,i){case 1:r=A.limit-A.cursor,A.eq_s_b(1,"k")||(A.cursor=A.limit-r,A.slice_del());break;case 2:A.slice_del(),A.ket=A.cursor,A.eq_s_b(3,"kse")&&(A.bra=A.cursor,A.slice_from("ksi"));break;case 3:A.slice_del();break;case 4:A.find_among_b(p,6)&&A.slice_del();break;case 5:A.find_among_b(g,6)&&A.slice_del();break;case 6:A.find_among_b(j,2)&&A.slice_del()}else A.limit_backward=e}function l(){return A.find_among_b(q,7)}function a(){return A.eq_s_b(1,"i")&&A.in_grouping_b(L,97,246)}function u(){var i,e,r;if(A.cursor>=f)if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,i=A.find_among_b(C,30)){switch(A.bra=A.cursor,A.limit_backward=e,i){case 1:if(!A.eq_s_b(1,"a"))return;break;case 2:case 9:if(!A.eq_s_b(1,"e"))return;break;case 3:if(!A.eq_s_b(1,"i"))return;break;case 4:if(!A.eq_s_b(1,"o"))return;break;case 5:if(!A.eq_s_b(1,"ä"))return;break;case 6:if(!A.eq_s_b(1,"ö"))return;break;case 7:if(r=A.limit-A.cursor,!l()&&(A.cursor=A.limit-r,!A.eq_s_b(2,"ie"))){A.cursor=A.limit-r;break}if(A.cursor=A.limit-r,A.cursor<=A.limit_backward){A.cursor=A.limit-r;break}A.cursor--,A.bra=A.cursor;break;case 8:if(!A.in_grouping_b(W,97,246)||!A.out_grouping_b(W,97,246))return}A.slice_del(),k=!0}else A.limit_backward=e}function c(){var i,e,r;if(A.cursor>=d)if(e=A.limit_backward,A.limit_backward=d,A.ket=A.cursor,i=A.find_among_b(P,14)){if(A.bra=A.cursor,A.limit_backward=e,1==i){if(r=A.limit-A.cursor,A.eq_s_b(2,"po"))return;A.cursor=A.limit-r}A.slice_del()}else A.limit_backward=e}function m(){var i;A.cursor>=f&&(i=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,A.find_among_b(F,2)?(A.bra=A.cursor,A.limit_backward=i,A.slice_del()):A.limit_backward=i)}function w(){var i,e,r,n,t,s;if(A.cursor>=f){if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,A.eq_s_b(1,"t")&&(A.bra=A.cursor,r=A.limit-A.cursor,A.in_grouping_b(W,97,246)&&(A.cursor=A.limit-r,A.slice_del(),A.limit_backward=e,n=A.limit-A.cursor,A.cursor>=d&&(A.cursor=d,t=A.limit_backward,A.limit_backward=A.cursor,A.cursor=A.limit-n,A.ket=A.cursor,i=A.find_among_b(S,2))))){if(A.bra=A.cursor,A.limit_backward=t,1==i){if(s=A.limit-A.cursor,A.eq_s_b(2,"po"))return;A.cursor=A.limit-s}return void A.slice_del()}A.limit_backward=e}}function _(){var i,e,r,n;if(A.cursor>=f){for(i=A.limit_backward,A.limit_backward=f,e=A.limit-A.cursor,l()&&(A.cursor=A.limit-e,A.ket=A.cursor,A.cursor>A.limit_backward&&(A.cursor--,A.bra=A.cursor,A.slice_del())),A.cursor=A.limit-e,A.ket=A.cursor,A.in_grouping_b(y,97,228)&&(A.bra=A.cursor,A.out_grouping_b(W,97,246)&&A.slice_del()),A.cursor=A.limit-e,A.ket=A.cursor,A.eq_s_b(1,"j")&&(A.bra=A.cursor,r=A.limit-A.cursor,A.eq_s_b(1,"o")?A.slice_del():(A.cursor=A.limit-r,A.eq_s_b(1,"u")&&A.slice_del())),A.cursor=A.limit-e,A.ket=A.cursor,A.eq_s_b(1,"o")&&(A.bra=A.cursor,A.eq_s_b(1,"j")&&A.slice_del()),A.cursor=A.limit-e,A.limit_backward=i;;){if(n=A.limit-A.cursor,A.out_grouping_b(W,97,246)){A.cursor=A.limit-n;break}if(A.cursor=A.limit-n,A.cursor<=A.limit_backward)return;A.cursor--}A.ket=A.cursor,A.cursor>A.limit_backward&&(A.cursor--,A.bra=A.cursor,b=A.slice_to(),A.eq_v_b(b)&&A.slice_del())}}var k,b,d,f,h=[new e("pa",-1,1),new e("sti",-1,2),new e("kaan",-1,1),new e("han",-1,1),new e("kin",-1,1),new e("hän",-1,1),new e("kään",-1,1),new e("ko",-1,1),new e("pä",-1,1),new e("kö",-1,1)],p=[new e("lla",-1,-1),new e("na",-1,-1),new e("ssa",-1,-1),new e("ta",-1,-1),new e("lta",3,-1),new e("sta",3,-1)],g=[new e("llä",-1,-1),new e("nä",-1,-1),new e("ssä",-1,-1),new e("tä",-1,-1),new e("ltä",3,-1),new e("stä",3,-1)],j=[new e("lle",-1,-1),new e("ine",-1,-1)],v=[new e("nsa",-1,3),new e("mme",-1,3),new e("nne",-1,3),new e("ni",-1,2),new e("si",-1,1),new e("an",-1,4),new e("en",-1,6),new e("än",-1,5),new e("nsä",-1,3)],q=[new e("aa",-1,-1),new e("ee",-1,-1),new e("ii",-1,-1),new e("oo",-1,-1),new e("uu",-1,-1),new e("ää",-1,-1),new e("öö",-1,-1)],C=[new e("a",-1,8),new e("lla",0,-1),new e("na",0,-1),new e("ssa",0,-1),new e("ta",0,-1),new e("lta",4,-1),new e("sta",4,-1),new e("tta",4,9),new e("lle",-1,-1),new e("ine",-1,-1),new e("ksi",-1,-1),new e("n",-1,7),new e("han",11,1),new e("den",11,-1,a),new e("seen",11,-1,l),new e("hen",11,2),new e("tten",11,-1,a),new e("hin",11,3),new e("siin",11,-1,a),new e("hon",11,4),new e("hän",11,5),new e("hön",11,6),new e("ä",-1,8),new e("llä",22,-1),new e("nä",22,-1),new e("ssä",22,-1),new e("tä",22,-1),new e("ltä",26,-1),new e("stä",26,-1),new e("ttä",26,9)],P=[new e("eja",-1,-1),new e("mma",-1,1),new e("imma",1,-1),new e("mpa",-1,1),new e("impa",3,-1),new e("mmi",-1,1),new e("immi",5,-1),new e("mpi",-1,1),new e("impi",7,-1),new e("ejä",-1,-1),new e("mmä",-1,1),new e("immä",10,-1),new e("mpä",-1,1),new e("impä",12,-1)],F=[new e("i",-1,-1),new e("j",-1,-1)],S=[new e("mma",-1,1),new e("imma",0,-1)],y=[17,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8],W=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],L=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],x=[17,97,24,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],A=new r;this.setCurrent=function(i){A.setCurrent(i)},this.getCurrent=function(){return A.getCurrent()},this.stem=function(){var e=A.cursor;return i(),k=!1,A.limit_backward=e,A.cursor=A.limit,s(),A.cursor=A.limit,o(),A.cursor=A.limit,u(),A.cursor=A.limit,c(),A.cursor=A.limit,k?(m(),A.cursor=A.limit):(A.cursor=A.limit,w(),A.cursor=A.limit),_(),!0}};return function(i){return"function"==typeof i.update?i.update(function(i){return n.setCurrent(i),n.stem(),n.getCurrent()}):(n.setCurrent(i),n.stem(),n.getCurrent())}}(),i.Pipeline.registerFunction(i.fi.stemmer,"stemmer-fi"),i.fi.stopWordFilter=i.generateStopWordFilter("ei eivät emme en et ette että he heidän heidät heihin heille heillä heiltä heissä heistä heitä hän häneen hänelle hänellä häneltä hänen hänessä hänestä hänet häntä itse ja johon joiden joihin joiksi joilla joille joilta joina joissa joista joita joka joksi jolla jolle jolta jona jonka jos jossa josta jota jotka kanssa keiden keihin keiksi keille keillä keiltä keinä keissä keistä keitä keneen keneksi kenelle kenellä keneltä kenen kenenä kenessä kenestä kenet ketkä ketkä ketä koska kuin kuka kun me meidän meidät meihin meille meillä meiltä meissä meistä meitä mihin miksi mikä mille millä miltä minkä minkä minua minulla minulle minulta minun minussa minusta minut minuun minä minä missä mistä mitkä mitä mukaan mutta ne niiden niihin niiksi niille niillä niiltä niin niin niinä niissä niistä niitä noiden noihin noiksi noilla noille noilta noin noina noissa noista noita nuo nyt näiden näihin näiksi näille näillä näiltä näinä näissä näistä näitä nämä ole olemme olen olet olette oli olimme olin olisi olisimme olisin olisit olisitte olisivat olit olitte olivat olla olleet ollut on ovat poikki se sekä sen siihen siinä siitä siksi sille sillä sillä siltä sinua sinulla sinulle sinulta sinun sinussa sinusta sinut sinuun sinä sinä sitä tai te teidän teidät teihin teille teillä teiltä teissä teistä teitä tuo tuohon tuoksi tuolla tuolle tuolta tuon tuona tuossa tuosta tuota tähän täksi tälle tällä tältä tämä tämän tänä tässä tästä tätä vaan vai vaikka yli".split(" ")),i.Pipeline.registerFunction(i.fi.stopWordFilter,"stopWordFilter-fi")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.fr.min.js b/assets/javascripts/lunr/min/lunr.fr.min.js new file mode 100644 index 000000000..68cd0094a --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.fr.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `French` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.fr=function(){this.pipeline.reset(),this.pipeline.add(e.fr.trimmer,e.fr.stopWordFilter,e.fr.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.fr.stemmer))},e.fr.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.fr.trimmer=e.trimmerSupport.generateTrimmer(e.fr.wordCharacters),e.Pipeline.registerFunction(e.fr.trimmer,"trimmer-fr"),e.fr.stemmer=function(){var r=e.stemmerSupport.Among,s=e.stemmerSupport.SnowballProgram,i=new function(){function e(e,r,s){return!(!W.eq_s(1,e)||(W.ket=W.cursor,!W.in_grouping(F,97,251)))&&(W.slice_from(r),W.cursor=s,!0)}function i(e,r,s){return!!W.eq_s(1,e)&&(W.ket=W.cursor,W.slice_from(r),W.cursor=s,!0)}function n(){for(var r,s;;){if(r=W.cursor,W.in_grouping(F,97,251)){if(W.bra=W.cursor,s=W.cursor,e("u","U",r))continue;if(W.cursor=s,e("i","I",r))continue;if(W.cursor=s,i("y","Y",r))continue}if(W.cursor=r,W.bra=r,!e("y","Y",r)){if(W.cursor=r,W.eq_s(1,"q")&&(W.bra=W.cursor,i("u","U",r)))continue;if(W.cursor=r,r>=W.limit)return;W.cursor++}}}function t(){for(;!W.in_grouping(F,97,251);){if(W.cursor>=W.limit)return!0;W.cursor++}for(;!W.out_grouping(F,97,251);){if(W.cursor>=W.limit)return!0;W.cursor++}return!1}function u(){var e=W.cursor;if(q=W.limit,g=q,p=q,W.in_grouping(F,97,251)&&W.in_grouping(F,97,251)&&W.cursor=W.limit){W.cursor=q;break}W.cursor++}while(!W.in_grouping(F,97,251))}q=W.cursor,W.cursor=e,t()||(g=W.cursor,t()||(p=W.cursor))}function o(){for(var e,r;;){if(r=W.cursor,W.bra=r,!(e=W.find_among(h,4)))break;switch(W.ket=W.cursor,e){case 1:W.slice_from("i");break;case 2:W.slice_from("u");break;case 3:W.slice_from("y");break;case 4:if(W.cursor>=W.limit)return;W.cursor++}}}function c(){return q<=W.cursor}function a(){return g<=W.cursor}function l(){return p<=W.cursor}function w(){var e,r;if(W.ket=W.cursor,e=W.find_among_b(C,43)){switch(W.bra=W.cursor,e){case 1:if(!l())return!1;W.slice_del();break;case 2:if(!l())return!1;W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"ic")&&(W.bra=W.cursor,l()?W.slice_del():W.slice_from("iqU"));break;case 3:if(!l())return!1;W.slice_from("log");break;case 4:if(!l())return!1;W.slice_from("u");break;case 5:if(!l())return!1;W.slice_from("ent");break;case 6:if(!c())return!1;if(W.slice_del(),W.ket=W.cursor,e=W.find_among_b(z,6))switch(W.bra=W.cursor,e){case 1:l()&&(W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"at")&&(W.bra=W.cursor,l()&&W.slice_del()));break;case 2:l()?W.slice_del():a()&&W.slice_from("eux");break;case 3:l()&&W.slice_del();break;case 4:c()&&W.slice_from("i")}break;case 7:if(!l())return!1;if(W.slice_del(),W.ket=W.cursor,e=W.find_among_b(y,3))switch(W.bra=W.cursor,e){case 1:l()?W.slice_del():W.slice_from("abl");break;case 2:l()?W.slice_del():W.slice_from("iqU");break;case 3:l()&&W.slice_del()}break;case 8:if(!l())return!1;if(W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"at")&&(W.bra=W.cursor,l()&&(W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"ic")))){W.bra=W.cursor,l()?W.slice_del():W.slice_from("iqU");break}break;case 9:W.slice_from("eau");break;case 10:if(!a())return!1;W.slice_from("al");break;case 11:if(l())W.slice_del();else{if(!a())return!1;W.slice_from("eux")}break;case 12:if(!a()||!W.out_grouping_b(F,97,251))return!1;W.slice_del();break;case 13:return c()&&W.slice_from("ant"),!1;case 14:return c()&&W.slice_from("ent"),!1;case 15:return r=W.limit-W.cursor,W.in_grouping_b(F,97,251)&&c()&&(W.cursor=W.limit-r,W.slice_del()),!1}return!0}return!1}function f(){var e,r;if(W.cursor=q){if(s=W.limit_backward,W.limit_backward=q,W.ket=W.cursor,e=W.find_among_b(P,7))switch(W.bra=W.cursor,e){case 1:if(l()){if(i=W.limit-W.cursor,!W.eq_s_b(1,"s")&&(W.cursor=W.limit-i,!W.eq_s_b(1,"t")))break;W.slice_del()}break;case 2:W.slice_from("i");break;case 3:W.slice_del();break;case 4:W.eq_s_b(2,"gu")&&W.slice_del()}W.limit_backward=s}}function b(){var e=W.limit-W.cursor;W.find_among_b(U,5)&&(W.cursor=W.limit-e,W.ket=W.cursor,W.cursor>W.limit_backward&&(W.cursor--,W.bra=W.cursor,W.slice_del()))}function d(){for(var e,r=1;W.out_grouping_b(F,97,251);)r--;if(r<=0){if(W.ket=W.cursor,e=W.limit-W.cursor,!W.eq_s_b(1,"é")&&(W.cursor=W.limit-e,!W.eq_s_b(1,"è")))return;W.bra=W.cursor,W.slice_from("e")}}function k(){if(!w()&&(W.cursor=W.limit,!f()&&(W.cursor=W.limit,!m())))return W.cursor=W.limit,void _();W.cursor=W.limit,W.ket=W.cursor,W.eq_s_b(1,"Y")?(W.bra=W.cursor,W.slice_from("i")):(W.cursor=W.limit,W.eq_s_b(1,"ç")&&(W.bra=W.cursor,W.slice_from("c")))}var p,g,q,v=[new r("col",-1,-1),new r("par",-1,-1),new r("tap",-1,-1)],h=[new r("",-1,4),new r("I",0,1),new r("U",0,2),new r("Y",0,3)],z=[new r("iqU",-1,3),new r("abl",-1,3),new r("Ièr",-1,4),new r("ièr",-1,4),new r("eus",-1,2),new r("iv",-1,1)],y=[new r("ic",-1,2),new r("abil",-1,1),new r("iv",-1,3)],C=[new r("iqUe",-1,1),new r("atrice",-1,2),new r("ance",-1,1),new r("ence",-1,5),new r("logie",-1,3),new r("able",-1,1),new r("isme",-1,1),new r("euse",-1,11),new r("iste",-1,1),new r("ive",-1,8),new r("if",-1,8),new r("usion",-1,4),new r("ation",-1,2),new r("ution",-1,4),new r("ateur",-1,2),new r("iqUes",-1,1),new r("atrices",-1,2),new r("ances",-1,1),new r("ences",-1,5),new r("logies",-1,3),new r("ables",-1,1),new r("ismes",-1,1),new r("euses",-1,11),new r("istes",-1,1),new r("ives",-1,8),new r("ifs",-1,8),new r("usions",-1,4),new r("ations",-1,2),new r("utions",-1,4),new r("ateurs",-1,2),new r("ments",-1,15),new r("ements",30,6),new r("issements",31,12),new r("ités",-1,7),new r("ment",-1,15),new r("ement",34,6),new r("issement",35,12),new r("amment",34,13),new r("emment",34,14),new r("aux",-1,10),new r("eaux",39,9),new r("eux",-1,1),new r("ité",-1,7)],x=[new r("ira",-1,1),new r("ie",-1,1),new r("isse",-1,1),new r("issante",-1,1),new r("i",-1,1),new r("irai",4,1),new r("ir",-1,1),new r("iras",-1,1),new r("ies",-1,1),new r("îmes",-1,1),new r("isses",-1,1),new r("issantes",-1,1),new r("îtes",-1,1),new r("is",-1,1),new r("irais",13,1),new r("issais",13,1),new r("irions",-1,1),new r("issions",-1,1),new r("irons",-1,1),new r("issons",-1,1),new r("issants",-1,1),new r("it",-1,1),new r("irait",21,1),new r("issait",21,1),new r("issant",-1,1),new r("iraIent",-1,1),new r("issaIent",-1,1),new r("irent",-1,1),new r("issent",-1,1),new r("iront",-1,1),new r("ît",-1,1),new r("iriez",-1,1),new r("issiez",-1,1),new r("irez",-1,1),new r("issez",-1,1)],I=[new r("a",-1,3),new r("era",0,2),new r("asse",-1,3),new r("ante",-1,3),new r("ée",-1,2),new r("ai",-1,3),new r("erai",5,2),new r("er",-1,2),new r("as",-1,3),new r("eras",8,2),new r("âmes",-1,3),new r("asses",-1,3),new r("antes",-1,3),new r("âtes",-1,3),new r("ées",-1,2),new r("ais",-1,3),new r("erais",15,2),new r("ions",-1,1),new r("erions",17,2),new r("assions",17,3),new r("erons",-1,2),new r("ants",-1,3),new r("és",-1,2),new r("ait",-1,3),new r("erait",23,2),new r("ant",-1,3),new r("aIent",-1,3),new r("eraIent",26,2),new r("èrent",-1,2),new r("assent",-1,3),new r("eront",-1,2),new r("ât",-1,3),new r("ez",-1,2),new r("iez",32,2),new r("eriez",33,2),new r("assiez",33,3),new r("erez",32,2),new r("é",-1,2)],P=[new r("e",-1,3),new r("Ière",0,2),new r("ière",0,2),new r("ion",-1,1),new r("Ier",-1,2),new r("ier",-1,2),new r("ë",-1,4)],U=[new r("ell",-1,-1),new r("eill",-1,-1),new r("enn",-1,-1),new r("onn",-1,-1),new r("ett",-1,-1)],F=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,128,130,103,8,5],S=[1,65,20,0,0,0,0,0,0,0,0,0,0,0,0,0,128],W=new s;this.setCurrent=function(e){W.setCurrent(e)},this.getCurrent=function(){return W.getCurrent()},this.stem=function(){var e=W.cursor;return n(),W.cursor=e,u(),W.limit_backward=e,W.cursor=W.limit,k(),W.cursor=W.limit,b(),W.cursor=W.limit,d(),W.cursor=W.limit_backward,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.fr.stemmer,"stemmer-fr"),e.fr.stopWordFilter=e.generateStopWordFilter("ai aie aient aies ait as au aura aurai auraient aurais aurait auras aurez auriez aurions aurons auront aux avaient avais avait avec avez aviez avions avons ayant ayez ayons c ce ceci celà ces cet cette d dans de des du elle en es est et eu eue eues eurent eus eusse eussent eusses eussiez eussions eut eux eûmes eût eûtes furent fus fusse fussent fusses fussiez fussions fut fûmes fût fûtes ici il ils j je l la le les leur leurs lui m ma mais me mes moi mon même n ne nos notre nous on ont ou par pas pour qu que quel quelle quelles quels qui s sa sans se sera serai seraient serais serait seras serez seriez serions serons seront ses soi soient sois soit sommes son sont soyez soyons suis sur t ta te tes toi ton tu un une vos votre vous y à étaient étais était étant étiez étions été étée étées étés êtes".split(" ")),e.Pipeline.registerFunction(e.fr.stopWordFilter,"stopWordFilter-fr")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.hu.min.js b/assets/javascripts/lunr/min/lunr.hu.min.js new file mode 100644 index 000000000..ed9d909f7 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.hu.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Hungarian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hu=function(){this.pipeline.reset(),this.pipeline.add(e.hu.trimmer,e.hu.stopWordFilter,e.hu.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hu.stemmer))},e.hu.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.hu.trimmer=e.trimmerSupport.generateTrimmer(e.hu.wordCharacters),e.Pipeline.registerFunction(e.hu.trimmer,"trimmer-hu"),e.hu.stemmer=function(){var n=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,i=new function(){function e(){var e,n=L.cursor;if(d=L.limit,L.in_grouping(W,97,252))for(;;){if(e=L.cursor,L.out_grouping(W,97,252))return L.cursor=e,L.find_among(g,8)||(L.cursor=e,e=L.limit)return void(d=e);L.cursor++}if(L.cursor=n,L.out_grouping(W,97,252)){for(;!L.in_grouping(W,97,252);){if(L.cursor>=L.limit)return;L.cursor++}d=L.cursor}}function i(){return d<=L.cursor}function a(){var e;if(L.ket=L.cursor,(e=L.find_among_b(h,2))&&(L.bra=L.cursor,i()))switch(e){case 1:L.slice_from("a");break;case 2:L.slice_from("e")}}function t(){var e=L.limit-L.cursor;return!!L.find_among_b(p,23)&&(L.cursor=L.limit-e,!0)}function s(){if(L.cursor>L.limit_backward){L.cursor--,L.ket=L.cursor;var e=L.cursor-1;L.limit_backward<=e&&e<=L.limit&&(L.cursor=e,L.bra=e,L.slice_del())}}function c(){var e;if(L.ket=L.cursor,(e=L.find_among_b(_,2))&&(L.bra=L.cursor,i())){if((1==e||2==e)&&!t())return;L.slice_del(),s()}}function o(){L.ket=L.cursor,L.find_among_b(v,44)&&(L.bra=L.cursor,i()&&(L.slice_del(),a()))}function w(){var e;if(L.ket=L.cursor,(e=L.find_among_b(z,3))&&(L.bra=L.cursor,i()))switch(e){case 1:L.slice_from("e");break;case 2:case 3:L.slice_from("a")}}function l(){var e;if(L.ket=L.cursor,(e=L.find_among_b(y,6))&&(L.bra=L.cursor,i()))switch(e){case 1:case 2:L.slice_del();break;case 3:L.slice_from("a");break;case 4:L.slice_from("e")}}function u(){var e;if(L.ket=L.cursor,(e=L.find_among_b(j,2))&&(L.bra=L.cursor,i())){if((1==e||2==e)&&!t())return;L.slice_del(),s()}}function m(){var e;if(L.ket=L.cursor,(e=L.find_among_b(C,7))&&(L.bra=L.cursor,i()))switch(e){case 1:L.slice_from("a");break;case 2:L.slice_from("e");break;case 3:case 4:case 5:case 6:case 7:L.slice_del()}}function k(){var e;if(L.ket=L.cursor,(e=L.find_among_b(P,12))&&(L.bra=L.cursor,i()))switch(e){case 1:case 4:case 7:case 9:L.slice_del();break;case 2:case 5:case 8:L.slice_from("e");break;case 3:case 6:L.slice_from("a")}}function f(){var e;if(L.ket=L.cursor,(e=L.find_among_b(F,31))&&(L.bra=L.cursor,i()))switch(e){case 1:case 4:case 7:case 8:case 9:case 12:case 13:case 16:case 17:case 18:L.slice_del();break;case 2:case 5:case 10:case 14:case 19:L.slice_from("a");break;case 3:case 6:case 11:case 15:case 20:L.slice_from("e")}}function b(){var e;if(L.ket=L.cursor,(e=L.find_among_b(S,42))&&(L.bra=L.cursor,i()))switch(e){case 1:case 4:case 5:case 6:case 9:case 10:case 11:case 14:case 15:case 16:case 17:case 20:case 21:case 24:case 25:case 26:case 29:L.slice_del();break;case 2:case 7:case 12:case 18:case 22:case 27:L.slice_from("a");break;case 3:case 8:case 13:case 19:case 23:case 28:L.slice_from("e")}}var d,g=[new n("cs",-1,-1),new n("dzs",-1,-1),new n("gy",-1,-1),new n("ly",-1,-1),new n("ny",-1,-1),new n("sz",-1,-1),new n("ty",-1,-1),new n("zs",-1,-1)],h=[new n("á",-1,1),new n("é",-1,2)],p=[new n("bb",-1,-1),new n("cc",-1,-1),new n("dd",-1,-1),new n("ff",-1,-1),new n("gg",-1,-1),new n("jj",-1,-1),new n("kk",-1,-1),new n("ll",-1,-1),new n("mm",-1,-1),new n("nn",-1,-1),new n("pp",-1,-1),new n("rr",-1,-1),new n("ccs",-1,-1),new n("ss",-1,-1),new n("zzs",-1,-1),new n("tt",-1,-1),new n("vv",-1,-1),new n("ggy",-1,-1),new n("lly",-1,-1),new n("nny",-1,-1),new n("tty",-1,-1),new n("ssz",-1,-1),new n("zz",-1,-1)],_=[new n("al",-1,1),new n("el",-1,2)],v=[new n("ba",-1,-1),new n("ra",-1,-1),new n("be",-1,-1),new n("re",-1,-1),new n("ig",-1,-1),new n("nak",-1,-1),new n("nek",-1,-1),new n("val",-1,-1),new n("vel",-1,-1),new n("ul",-1,-1),new n("nál",-1,-1),new n("nél",-1,-1),new n("ból",-1,-1),new n("ról",-1,-1),new n("tól",-1,-1),new n("bõl",-1,-1),new n("rõl",-1,-1),new n("tõl",-1,-1),new n("ül",-1,-1),new n("n",-1,-1),new n("an",19,-1),new n("ban",20,-1),new n("en",19,-1),new n("ben",22,-1),new n("képpen",22,-1),new n("on",19,-1),new n("ön",19,-1),new n("képp",-1,-1),new n("kor",-1,-1),new n("t",-1,-1),new n("at",29,-1),new n("et",29,-1),new n("ként",29,-1),new n("anként",32,-1),new n("enként",32,-1),new n("onként",32,-1),new n("ot",29,-1),new n("ért",29,-1),new n("öt",29,-1),new n("hez",-1,-1),new n("hoz",-1,-1),new n("höz",-1,-1),new n("vá",-1,-1),new n("vé",-1,-1)],z=[new n("án",-1,2),new n("én",-1,1),new n("ánként",-1,3)],y=[new n("stul",-1,2),new n("astul",0,1),new n("ástul",0,3),new n("stül",-1,2),new n("estül",3,1),new n("éstül",3,4)],j=[new n("á",-1,1),new n("é",-1,2)],C=[new n("k",-1,7),new n("ak",0,4),new n("ek",0,6),new n("ok",0,5),new n("ák",0,1),new n("ék",0,2),new n("ök",0,3)],P=[new n("éi",-1,7),new n("áéi",0,6),new n("ééi",0,5),new n("é",-1,9),new n("ké",3,4),new n("aké",4,1),new n("eké",4,1),new n("oké",4,1),new n("áké",4,3),new n("éké",4,2),new n("öké",4,1),new n("éé",3,8)],F=[new n("a",-1,18),new n("ja",0,17),new n("d",-1,16),new n("ad",2,13),new n("ed",2,13),new n("od",2,13),new n("ád",2,14),new n("éd",2,15),new n("öd",2,13),new n("e",-1,18),new n("je",9,17),new n("nk",-1,4),new n("unk",11,1),new n("ánk",11,2),new n("énk",11,3),new n("ünk",11,1),new n("uk",-1,8),new n("juk",16,7),new n("ájuk",17,5),new n("ük",-1,8),new n("jük",19,7),new n("éjük",20,6),new n("m",-1,12),new n("am",22,9),new n("em",22,9),new n("om",22,9),new n("ám",22,10),new n("ém",22,11),new n("o",-1,18),new n("á",-1,19),new n("é",-1,20)],S=[new n("id",-1,10),new n("aid",0,9),new n("jaid",1,6),new n("eid",0,9),new n("jeid",3,6),new n("áid",0,7),new n("éid",0,8),new n("i",-1,15),new n("ai",7,14),new n("jai",8,11),new n("ei",7,14),new n("jei",10,11),new n("ái",7,12),new n("éi",7,13),new n("itek",-1,24),new n("eitek",14,21),new n("jeitek",15,20),new n("éitek",14,23),new n("ik",-1,29),new n("aik",18,26),new n("jaik",19,25),new n("eik",18,26),new n("jeik",21,25),new n("áik",18,27),new n("éik",18,28),new n("ink",-1,20),new n("aink",25,17),new n("jaink",26,16),new n("eink",25,17),new n("jeink",28,16),new n("áink",25,18),new n("éink",25,19),new n("aitok",-1,21),new n("jaitok",32,20),new n("áitok",-1,22),new n("im",-1,5),new n("aim",35,4),new n("jaim",36,1),new n("eim",35,4),new n("jeim",38,1),new n("áim",35,2),new n("éim",35,3)],W=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,52,14],L=new r;this.setCurrent=function(e){L.setCurrent(e)},this.getCurrent=function(){return L.getCurrent()},this.stem=function(){var n=L.cursor;return e(),L.limit_backward=n,L.cursor=L.limit,c(),L.cursor=L.limit,o(),L.cursor=L.limit,w(),L.cursor=L.limit,l(),L.cursor=L.limit,u(),L.cursor=L.limit,k(),L.cursor=L.limit,f(),L.cursor=L.limit,b(),L.cursor=L.limit,m(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.hu.stemmer,"stemmer-hu"),e.hu.stopWordFilter=e.generateStopWordFilter("a abban ahhoz ahogy ahol aki akik akkor alatt amely amelyek amelyekben amelyeket amelyet amelynek ami amikor amit amolyan amíg annak arra arról az azok azon azonban azt aztán azután azzal azért be belül benne bár cikk cikkek cikkeket csak de e ebben eddig egy egyes egyetlen egyik egyre egyéb egész ehhez ekkor el ellen elsõ elég elõ elõször elõtt emilyen ennek erre ez ezek ezen ezt ezzel ezért fel felé hanem hiszen hogy hogyan igen ill ill. illetve ilyen ilyenkor ismét ison itt jobban jó jól kell kellett keressünk keresztül ki kívül között közül legalább legyen lehet lehetett lenne lenni lesz lett maga magát majd majd meg mellett mely melyek mert mi mikor milyen minden mindenki mindent mindig mint mintha mit mivel miért most már más másik még míg nagy nagyobb nagyon ne nekem neki nem nincs néha néhány nélkül olyan ott pedig persze rá s saját sem semmi sok sokat sokkal szemben szerint szinte számára talán tehát teljes tovább továbbá több ugyanis utolsó után utána vagy vagyis vagyok valaki valami valamint való van vannak vele vissza viszont volna volt voltak voltam voltunk által általában át én éppen és így õ õk õket össze úgy új újabb újra".split(" ")),e.Pipeline.registerFunction(e.hu.stopWordFilter,"stopWordFilter-hu")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.it.min.js b/assets/javascripts/lunr/min/lunr.it.min.js new file mode 100644 index 000000000..344b6a3c0 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.it.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Italian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.it=function(){this.pipeline.reset(),this.pipeline.add(e.it.trimmer,e.it.stopWordFilter,e.it.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.it.stemmer))},e.it.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.it.trimmer=e.trimmerSupport.generateTrimmer(e.it.wordCharacters),e.Pipeline.registerFunction(e.it.trimmer,"trimmer-it"),e.it.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(e,r,n){return!(!x.eq_s(1,e)||(x.ket=x.cursor,!x.in_grouping(L,97,249)))&&(x.slice_from(r),x.cursor=n,!0)}function i(){for(var r,n,i,o,t=x.cursor;;){if(x.bra=x.cursor,r=x.find_among(h,7))switch(x.ket=x.cursor,r){case 1:x.slice_from("à");continue;case 2:x.slice_from("è");continue;case 3:x.slice_from("ì");continue;case 4:x.slice_from("ò");continue;case 5:x.slice_from("ù");continue;case 6:x.slice_from("qU");continue;case 7:if(x.cursor>=x.limit)break;x.cursor++;continue}break}for(x.cursor=t;;)for(n=x.cursor;;){if(i=x.cursor,x.in_grouping(L,97,249)){if(x.bra=x.cursor,o=x.cursor,e("u","U",i))break;if(x.cursor=o,e("i","I",i))break}if(x.cursor=i,x.cursor>=x.limit)return void(x.cursor=n);x.cursor++}}function o(e){if(x.cursor=e,!x.in_grouping(L,97,249))return!1;for(;!x.out_grouping(L,97,249);){if(x.cursor>=x.limit)return!1;x.cursor++}return!0}function t(){if(x.in_grouping(L,97,249)){var e=x.cursor;if(x.out_grouping(L,97,249)){for(;!x.in_grouping(L,97,249);){if(x.cursor>=x.limit)return o(e);x.cursor++}return!0}return o(e)}return!1}function s(){var e,r=x.cursor;if(!t()){if(x.cursor=r,!x.out_grouping(L,97,249))return;if(e=x.cursor,x.out_grouping(L,97,249)){for(;!x.in_grouping(L,97,249);){if(x.cursor>=x.limit)return x.cursor=e,void(x.in_grouping(L,97,249)&&x.cursor=x.limit)return;x.cursor++}k=x.cursor}function a(){for(;!x.in_grouping(L,97,249);){if(x.cursor>=x.limit)return!1;x.cursor++}for(;!x.out_grouping(L,97,249);){if(x.cursor>=x.limit)return!1;x.cursor++}return!0}function u(){var e=x.cursor;k=x.limit,p=k,g=k,s(),x.cursor=e,a()&&(p=x.cursor,a()&&(g=x.cursor))}function c(){for(var e;;){if(x.bra=x.cursor,!(e=x.find_among(q,3)))break;switch(x.ket=x.cursor,e){case 1:x.slice_from("i");break;case 2:x.slice_from("u");break;case 3:if(x.cursor>=x.limit)return;x.cursor++}}}function w(){return k<=x.cursor}function l(){return p<=x.cursor}function m(){return g<=x.cursor}function f(){var e;if(x.ket=x.cursor,x.find_among_b(C,37)&&(x.bra=x.cursor,(e=x.find_among_b(z,5))&&w()))switch(e){case 1:x.slice_del();break;case 2:x.slice_from("e")}}function v(){var e;if(x.ket=x.cursor,!(e=x.find_among_b(S,51)))return!1;switch(x.bra=x.cursor,e){case 1:if(!m())return!1;x.slice_del();break;case 2:if(!m())return!1;x.slice_del(),x.ket=x.cursor,x.eq_s_b(2,"ic")&&(x.bra=x.cursor,m()&&x.slice_del());break;case 3:if(!m())return!1;x.slice_from("log");break;case 4:if(!m())return!1;x.slice_from("u");break;case 5:if(!m())return!1;x.slice_from("ente");break;case 6:if(!w())return!1;x.slice_del();break;case 7:if(!l())return!1;x.slice_del(),x.ket=x.cursor,e=x.find_among_b(P,4),e&&(x.bra=x.cursor,m()&&(x.slice_del(),1==e&&(x.ket=x.cursor,x.eq_s_b(2,"at")&&(x.bra=x.cursor,m()&&x.slice_del()))));break;case 8:if(!m())return!1;x.slice_del(),x.ket=x.cursor,e=x.find_among_b(F,3),e&&(x.bra=x.cursor,1==e&&m()&&x.slice_del());break;case 9:if(!m())return!1;x.slice_del(),x.ket=x.cursor,x.eq_s_b(2,"at")&&(x.bra=x.cursor,m()&&(x.slice_del(),x.ket=x.cursor,x.eq_s_b(2,"ic")&&(x.bra=x.cursor,m()&&x.slice_del())))}return!0}function b(){var e,r;x.cursor>=k&&(r=x.limit_backward,x.limit_backward=k,x.ket=x.cursor,e=x.find_among_b(W,87),e&&(x.bra=x.cursor,1==e&&x.slice_del()),x.limit_backward=r)}function d(){var e=x.limit-x.cursor;if(x.ket=x.cursor,x.in_grouping_b(y,97,242)&&(x.bra=x.cursor,w()&&(x.slice_del(),x.ket=x.cursor,x.eq_s_b(1,"i")&&(x.bra=x.cursor,w()))))return void x.slice_del();x.cursor=x.limit-e}function _(){d(),x.ket=x.cursor,x.eq_s_b(1,"h")&&(x.bra=x.cursor,x.in_grouping_b(U,99,103)&&w()&&x.slice_del())}var g,p,k,h=[new r("",-1,7),new r("qu",0,6),new r("á",0,1),new r("é",0,2),new r("í",0,3),new r("ó",0,4),new r("ú",0,5)],q=[new r("",-1,3),new r("I",0,1),new r("U",0,2)],C=[new r("la",-1,-1),new r("cela",0,-1),new r("gliela",0,-1),new r("mela",0,-1),new r("tela",0,-1),new r("vela",0,-1),new r("le",-1,-1),new r("cele",6,-1),new r("gliele",6,-1),new r("mele",6,-1),new r("tele",6,-1),new r("vele",6,-1),new r("ne",-1,-1),new r("cene",12,-1),new r("gliene",12,-1),new r("mene",12,-1),new r("sene",12,-1),new r("tene",12,-1),new r("vene",12,-1),new r("ci",-1,-1),new r("li",-1,-1),new r("celi",20,-1),new r("glieli",20,-1),new r("meli",20,-1),new r("teli",20,-1),new r("veli",20,-1),new r("gli",20,-1),new r("mi",-1,-1),new r("si",-1,-1),new r("ti",-1,-1),new r("vi",-1,-1),new r("lo",-1,-1),new r("celo",31,-1),new r("glielo",31,-1),new r("melo",31,-1),new r("telo",31,-1),new r("velo",31,-1)],z=[new r("ando",-1,1),new r("endo",-1,1),new r("ar",-1,2),new r("er",-1,2),new r("ir",-1,2)],P=[new r("ic",-1,-1),new r("abil",-1,-1),new r("os",-1,-1),new r("iv",-1,1)],F=[new r("ic",-1,1),new r("abil",-1,1),new r("iv",-1,1)],S=[new r("ica",-1,1),new r("logia",-1,3),new r("osa",-1,1),new r("ista",-1,1),new r("iva",-1,9),new r("anza",-1,1),new r("enza",-1,5),new r("ice",-1,1),new r("atrice",7,1),new r("iche",-1,1),new r("logie",-1,3),new r("abile",-1,1),new r("ibile",-1,1),new r("usione",-1,4),new r("azione",-1,2),new r("uzione",-1,4),new r("atore",-1,2),new r("ose",-1,1),new r("ante",-1,1),new r("mente",-1,1),new r("amente",19,7),new r("iste",-1,1),new r("ive",-1,9),new r("anze",-1,1),new r("enze",-1,5),new r("ici",-1,1),new r("atrici",25,1),new r("ichi",-1,1),new r("abili",-1,1),new r("ibili",-1,1),new r("ismi",-1,1),new r("usioni",-1,4),new r("azioni",-1,2),new r("uzioni",-1,4),new r("atori",-1,2),new r("osi",-1,1),new r("anti",-1,1),new r("amenti",-1,6),new r("imenti",-1,6),new r("isti",-1,1),new r("ivi",-1,9),new r("ico",-1,1),new r("ismo",-1,1),new r("oso",-1,1),new r("amento",-1,6),new r("imento",-1,6),new r("ivo",-1,9),new r("ità",-1,8),new r("istà",-1,1),new r("istè",-1,1),new r("istì",-1,1)],W=[new r("isca",-1,1),new r("enda",-1,1),new r("ata",-1,1),new r("ita",-1,1),new r("uta",-1,1),new r("ava",-1,1),new r("eva",-1,1),new r("iva",-1,1),new r("erebbe",-1,1),new r("irebbe",-1,1),new r("isce",-1,1),new r("ende",-1,1),new r("are",-1,1),new r("ere",-1,1),new r("ire",-1,1),new r("asse",-1,1),new r("ate",-1,1),new r("avate",16,1),new r("evate",16,1),new r("ivate",16,1),new r("ete",-1,1),new r("erete",20,1),new r("irete",20,1),new r("ite",-1,1),new r("ereste",-1,1),new r("ireste",-1,1),new r("ute",-1,1),new r("erai",-1,1),new r("irai",-1,1),new r("isci",-1,1),new r("endi",-1,1),new r("erei",-1,1),new r("irei",-1,1),new r("assi",-1,1),new r("ati",-1,1),new r("iti",-1,1),new r("eresti",-1,1),new r("iresti",-1,1),new r("uti",-1,1),new r("avi",-1,1),new r("evi",-1,1),new r("ivi",-1,1),new r("isco",-1,1),new r("ando",-1,1),new r("endo",-1,1),new r("Yamo",-1,1),new r("iamo",-1,1),new r("avamo",-1,1),new r("evamo",-1,1),new r("ivamo",-1,1),new r("eremo",-1,1),new r("iremo",-1,1),new r("assimo",-1,1),new r("ammo",-1,1),new r("emmo",-1,1),new r("eremmo",54,1),new r("iremmo",54,1),new r("immo",-1,1),new r("ano",-1,1),new r("iscano",58,1),new r("avano",58,1),new r("evano",58,1),new r("ivano",58,1),new r("eranno",-1,1),new r("iranno",-1,1),new r("ono",-1,1),new r("iscono",65,1),new r("arono",65,1),new r("erono",65,1),new r("irono",65,1),new r("erebbero",-1,1),new r("irebbero",-1,1),new r("assero",-1,1),new r("essero",-1,1),new r("issero",-1,1),new r("ato",-1,1),new r("ito",-1,1),new r("uto",-1,1),new r("avo",-1,1),new r("evo",-1,1),new r("ivo",-1,1),new r("ar",-1,1),new r("ir",-1,1),new r("erà",-1,1),new r("irà",-1,1),new r("erò",-1,1),new r("irò",-1,1)],L=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,128,128,8,2,1],y=[17,65,0,0,0,0,0,0,0,0,0,0,0,0,0,128,128,8,2],U=[17],x=new n;this.setCurrent=function(e){x.setCurrent(e)},this.getCurrent=function(){return x.getCurrent()},this.stem=function(){var e=x.cursor;return i(),x.cursor=e,u(),x.limit_backward=e,x.cursor=x.limit,f(),x.cursor=x.limit,v()||(x.cursor=x.limit,b()),x.cursor=x.limit,_(),x.cursor=x.limit_backward,c(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.it.stemmer,"stemmer-it"),e.it.stopWordFilter=e.generateStopWordFilter("a abbia abbiamo abbiano abbiate ad agl agli ai al all alla alle allo anche avemmo avendo avesse avessero avessi avessimo aveste avesti avete aveva avevamo avevano avevate avevi avevo avrai avranno avrebbe avrebbero avrei avremmo avremo avreste avresti avrete avrà avrò avuta avute avuti avuto c che chi ci coi col come con contro cui da dagl dagli dai dal dall dalla dalle dallo degl degli dei del dell della delle dello di dov dove e ebbe ebbero ebbi ed era erano eravamo eravate eri ero essendo faccia facciamo facciano facciate faccio facemmo facendo facesse facessero facessi facessimo faceste facesti faceva facevamo facevano facevate facevi facevo fai fanno farai faranno farebbe farebbero farei faremmo faremo fareste faresti farete farà farò fece fecero feci fosse fossero fossi fossimo foste fosti fu fui fummo furono gli ha hai hanno ho i il in io l la le lei li lo loro lui ma mi mia mie miei mio ne negl negli nei nel nell nella nelle nello noi non nostra nostre nostri nostro o per perché più quale quanta quante quanti quanto quella quelle quelli quello questa queste questi questo sarai saranno sarebbe sarebbero sarei saremmo saremo sareste saresti sarete sarà sarò se sei si sia siamo siano siate siete sono sta stai stando stanno starai staranno starebbe starebbero starei staremmo staremo stareste staresti starete starà starò stava stavamo stavano stavate stavi stavo stemmo stesse stessero stessi stessimo steste stesti stette stettero stetti stia stiamo stiano stiate sto su sua sue sugl sugli sui sul sull sulla sulle sullo suo suoi ti tra tu tua tue tuo tuoi tutti tutto un una uno vi voi vostra vostre vostri vostro è".split(" ")),e.Pipeline.registerFunction(e.it.stopWordFilter,"stopWordFilter-it")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.ja.min.js b/assets/javascripts/lunr/min/lunr.ja.min.js new file mode 100644 index 000000000..5f254ebe9 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.ja.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.ja=function(){this.pipeline.reset(),this.pipeline.add(e.ja.trimmer,e.ja.stopWordFilter,e.ja.stemmer),r?this.tokenizer=e.ja.tokenizer:(e.tokenizer&&(e.tokenizer=e.ja.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.ja.tokenizer))};var t=new e.TinySegmenter;e.ja.tokenizer=function(i){var n,o,s,p,a,u,m,l,c,f;if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t.toLowerCase()):t.toLowerCase()});for(o=i.toString().toLowerCase().replace(/^\s+/,""),n=o.length-1;n>=0;n--)if(/\S/.test(o.charAt(n))){o=o.substring(0,n+1);break}for(a=[],s=o.length,c=0,l=0;c<=s;c++)if(u=o.charAt(c),m=c-l,u.match(/\s/)||c==s){if(m>0)for(p=t.segment(o.slice(l,c)).filter(function(e){return!!e}),f=l,n=0;n=C.limit)break;C.cursor++;continue}break}for(C.cursor=o,C.bra=o,C.eq_s(1,"y")?(C.ket=C.cursor,C.slice_from("Y")):C.cursor=o;;)if(e=C.cursor,C.in_grouping(q,97,232)){if(i=C.cursor,C.bra=i,C.eq_s(1,"i"))C.ket=C.cursor,C.in_grouping(q,97,232)&&(C.slice_from("I"),C.cursor=e);else if(C.cursor=i,C.eq_s(1,"y"))C.ket=C.cursor,C.slice_from("Y"),C.cursor=e;else if(n(e))break}else if(n(e))break}function n(r){return C.cursor=r,r>=C.limit||(C.cursor++,!1)}function o(){_=C.limit,d=_,t()||(_=C.cursor,_<3&&(_=3),t()||(d=C.cursor))}function t(){for(;!C.in_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}for(;!C.out_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}return!1}function s(){for(var r;;)if(C.bra=C.cursor,r=C.find_among(p,3))switch(C.ket=C.cursor,r){case 1:C.slice_from("y");break;case 2:C.slice_from("i");break;case 3:if(C.cursor>=C.limit)return;C.cursor++}}function u(){return _<=C.cursor}function c(){return d<=C.cursor}function a(){var r=C.limit-C.cursor;C.find_among_b(g,3)&&(C.cursor=C.limit-r,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del()))}function l(){var r;w=!1,C.ket=C.cursor,C.eq_s_b(1,"e")&&(C.bra=C.cursor,u()&&(r=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-r,C.slice_del(),w=!0,a())))}function m(){var r;u()&&(r=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-r,C.eq_s_b(3,"gem")||(C.cursor=C.limit-r,C.slice_del(),a())))}function f(){var r,e,i,n,o,t,s=C.limit-C.cursor;if(C.ket=C.cursor,r=C.find_among_b(h,5))switch(C.bra=C.cursor,r){case 1:u()&&C.slice_from("heid");break;case 2:m();break;case 3:u()&&C.out_grouping_b(j,97,232)&&C.slice_del()}if(C.cursor=C.limit-s,l(),C.cursor=C.limit-s,C.ket=C.cursor,C.eq_s_b(4,"heid")&&(C.bra=C.cursor,c()&&(e=C.limit-C.cursor,C.eq_s_b(1,"c")||(C.cursor=C.limit-e,C.slice_del(),C.ket=C.cursor,C.eq_s_b(2,"en")&&(C.bra=C.cursor,m())))),C.cursor=C.limit-s,C.ket=C.cursor,r=C.find_among_b(k,6))switch(C.bra=C.cursor,r){case 1:if(c()){if(C.slice_del(),i=C.limit-C.cursor,C.ket=C.cursor,C.eq_s_b(2,"ig")&&(C.bra=C.cursor,c()&&(n=C.limit-C.cursor,!C.eq_s_b(1,"e")))){C.cursor=C.limit-n,C.slice_del();break}C.cursor=C.limit-i,a()}break;case 2:c()&&(o=C.limit-C.cursor,C.eq_s_b(1,"e")||(C.cursor=C.limit-o,C.slice_del()));break;case 3:c()&&(C.slice_del(),l());break;case 4:c()&&C.slice_del();break;case 5:c()&&w&&C.slice_del()}C.cursor=C.limit-s,C.out_grouping_b(z,73,232)&&(t=C.limit-C.cursor,C.find_among_b(v,4)&&C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-t,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del())))}var d,_,w,b=[new e("",-1,6),new e("á",0,1),new e("ä",0,1),new e("é",0,2),new e("ë",0,2),new e("í",0,3),new e("ï",0,3),new e("ó",0,4),new e("ö",0,4),new e("ú",0,5),new e("ü",0,5)],p=[new e("",-1,3),new e("I",0,2),new e("Y",0,1)],g=[new e("dd",-1,-1),new e("kk",-1,-1),new e("tt",-1,-1)],h=[new e("ene",-1,2),new e("se",-1,3),new e("en",-1,2),new e("heden",2,1),new e("s",-1,3)],k=[new e("end",-1,1),new e("ig",-1,2),new e("ing",-1,1),new e("lijk",-1,3),new e("baar",-1,4),new e("bar",-1,5)],v=[new e("aa",-1,-1),new e("ee",-1,-1),new e("oo",-1,-1),new e("uu",-1,-1)],q=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],z=[1,0,0,17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],j=[17,67,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],C=new i;this.setCurrent=function(r){C.setCurrent(r)},this.getCurrent=function(){return C.getCurrent()},this.stem=function(){var e=C.cursor;return r(),C.cursor=e,o(),C.limit_backward=e,C.cursor=C.limit,f(),C.cursor=C.limit_backward,s(),!0}};return function(r){return"function"==typeof r.update?r.update(function(r){return n.setCurrent(r),n.stem(),n.getCurrent()}):(n.setCurrent(r),n.stem(),n.getCurrent())}}(),r.Pipeline.registerFunction(r.nl.stemmer,"stemmer-nl"),r.nl.stopWordFilter=r.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),r.Pipeline.registerFunction(r.nl.stopWordFilter,"stopWordFilter-nl")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.no.min.js b/assets/javascripts/lunr/min/lunr.no.min.js new file mode 100644 index 000000000..92bc7e4e8 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.no.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Norwegian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.no=function(){this.pipeline.reset(),this.pipeline.add(e.no.trimmer,e.no.stopWordFilter,e.no.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.no.stemmer))},e.no.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.no.trimmer=e.trimmerSupport.generateTrimmer(e.no.wordCharacters),e.Pipeline.registerFunction(e.no.trimmer,"trimmer-no"),e.no.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(){var e,r=w.cursor+3;if(a=w.limit,0<=r||r<=w.limit){for(s=r;;){if(e=w.cursor,w.in_grouping(d,97,248)){w.cursor=e;break}if(e>=w.limit)return;w.cursor=e+1}for(;!w.out_grouping(d,97,248);){if(w.cursor>=w.limit)return;w.cursor++}a=w.cursor,a=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(m,29),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:n=w.limit-w.cursor,w.in_grouping_b(c,98,122)?w.slice_del():(w.cursor=w.limit-n,w.eq_s_b(1,"k")&&w.out_grouping_b(d,97,248)&&w.slice_del());break;case 3:w.slice_from("er")}}function t(){var e,r=w.limit-w.cursor;w.cursor>=a&&(e=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,w.find_among_b(u,2)?(w.bra=w.cursor,w.limit_backward=e,w.cursor=w.limit-r,w.cursor>w.limit_backward&&(w.cursor--,w.bra=w.cursor,w.slice_del())):w.limit_backward=e)}function o(){var e,r;w.cursor>=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(l,11),e?(w.bra=w.cursor,w.limit_backward=r,1==e&&w.slice_del()):w.limit_backward=r)}var s,a,m=[new r("a",-1,1),new r("e",-1,1),new r("ede",1,1),new r("ande",1,1),new r("ende",1,1),new r("ane",1,1),new r("ene",1,1),new r("hetene",6,1),new r("erte",1,3),new r("en",-1,1),new r("heten",9,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",12,1),new r("s",-1,2),new r("as",14,1),new r("es",14,1),new r("edes",16,1),new r("endes",16,1),new r("enes",16,1),new r("hetenes",19,1),new r("ens",14,1),new r("hetens",21,1),new r("ers",14,1),new r("ets",14,1),new r("et",-1,1),new r("het",25,1),new r("ert",-1,3),new r("ast",-1,1)],u=[new r("dt",-1,-1),new r("vt",-1,-1)],l=[new r("leg",-1,1),new r("eleg",0,1),new r("ig",-1,1),new r("eig",2,1),new r("lig",2,1),new r("elig",4,1),new r("els",-1,1),new r("lov",-1,1),new r("elov",7,1),new r("slov",7,1),new r("hetslov",9,1)],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],c=[119,125,149,1],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,i(),w.cursor=w.limit,t(),w.cursor=w.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.no.stemmer,"stemmer-no"),e.no.stopWordFilter=e.generateStopWordFilter("alle at av bare begge ble blei bli blir blitt både båe da de deg dei deim deira deires dem den denne der dere deres det dette di din disse ditt du dykk dykkar då eg ein eit eitt eller elles en enn er et ett etter for fordi fra før ha hadde han hans har hennar henne hennes her hjå ho hoe honom hoss hossen hun hva hvem hver hvilke hvilken hvis hvor hvordan hvorfor i ikke ikkje ikkje ingen ingi inkje inn inni ja jeg kan kom korleis korso kun kunne kva kvar kvarhelst kven kvi kvifor man mange me med medan meg meget mellom men mi min mine mitt mot mykje ned no noe noen noka noko nokon nokor nokre nå når og også om opp oss over på samme seg selv si si sia sidan siden sin sine sitt sjøl skal skulle slik so som som somme somt så sånn til um upp ut uten var vart varte ved vere verte vi vil ville vore vors vort vår være være vært å".split(" ")),e.Pipeline.registerFunction(e.no.stopWordFilter,"stopWordFilter-no")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.pt.min.js b/assets/javascripts/lunr/min/lunr.pt.min.js new file mode 100644 index 000000000..6c16996d6 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.pt.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Portuguese` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.pt=function(){this.pipeline.reset(),this.pipeline.add(e.pt.trimmer,e.pt.stopWordFilter,e.pt.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.pt.stemmer))},e.pt.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.pt.trimmer=e.trimmerSupport.generateTrimmer(e.pt.wordCharacters),e.Pipeline.registerFunction(e.pt.trimmer,"trimmer-pt"),e.pt.stemmer=function(){var r=e.stemmerSupport.Among,s=e.stemmerSupport.SnowballProgram,n=new function(){function e(){for(var e;;){if(z.bra=z.cursor,e=z.find_among(k,3))switch(z.ket=z.cursor,e){case 1:z.slice_from("a~");continue;case 2:z.slice_from("o~");continue;case 3:if(z.cursor>=z.limit)break;z.cursor++;continue}break}}function n(){if(z.out_grouping(y,97,250)){for(;!z.in_grouping(y,97,250);){if(z.cursor>=z.limit)return!0;z.cursor++}return!1}return!0}function i(){if(z.in_grouping(y,97,250))for(;!z.out_grouping(y,97,250);){if(z.cursor>=z.limit)return!1;z.cursor++}return g=z.cursor,!0}function o(){var e,r,s=z.cursor;if(z.in_grouping(y,97,250))if(e=z.cursor,n()){if(z.cursor=e,i())return}else g=z.cursor;if(z.cursor=s,z.out_grouping(y,97,250)){if(r=z.cursor,n()){if(z.cursor=r,!z.in_grouping(y,97,250)||z.cursor>=z.limit)return;z.cursor++}g=z.cursor}}function t(){for(;!z.in_grouping(y,97,250);){if(z.cursor>=z.limit)return!1;z.cursor++}for(;!z.out_grouping(y,97,250);){if(z.cursor>=z.limit)return!1;z.cursor++}return!0}function a(){var e=z.cursor;g=z.limit,b=g,h=g,o(),z.cursor=e,t()&&(b=z.cursor,t()&&(h=z.cursor))}function u(){for(var e;;){if(z.bra=z.cursor,e=z.find_among(q,3))switch(z.ket=z.cursor,e){case 1:z.slice_from("ã");continue;case 2:z.slice_from("õ");continue;case 3:if(z.cursor>=z.limit)break;z.cursor++;continue}break}}function w(){return g<=z.cursor}function m(){return b<=z.cursor}function c(){return h<=z.cursor}function l(){var e;if(z.ket=z.cursor,!(e=z.find_among_b(F,45)))return!1;switch(z.bra=z.cursor,e){case 1:if(!c())return!1;z.slice_del();break;case 2:if(!c())return!1;z.slice_from("log");break;case 3:if(!c())return!1;z.slice_from("u");break;case 4:if(!c())return!1;z.slice_from("ente");break;case 5:if(!m())return!1;z.slice_del(),z.ket=z.cursor,e=z.find_among_b(j,4),e&&(z.bra=z.cursor,c()&&(z.slice_del(),1==e&&(z.ket=z.cursor,z.eq_s_b(2,"at")&&(z.bra=z.cursor,c()&&z.slice_del()))));break;case 6:if(!c())return!1;z.slice_del(),z.ket=z.cursor,e=z.find_among_b(C,3),e&&(z.bra=z.cursor,1==e&&c()&&z.slice_del());break;case 7:if(!c())return!1;z.slice_del(),z.ket=z.cursor,e=z.find_among_b(P,3),e&&(z.bra=z.cursor,1==e&&c()&&z.slice_del());break;case 8:if(!c())return!1;z.slice_del(),z.ket=z.cursor,z.eq_s_b(2,"at")&&(z.bra=z.cursor,c()&&z.slice_del());break;case 9:if(!w()||!z.eq_s_b(1,"e"))return!1;z.slice_from("ir")}return!0}function f(){var e,r;if(z.cursor>=g){if(r=z.limit_backward,z.limit_backward=g,z.ket=z.cursor,e=z.find_among_b(S,120))return z.bra=z.cursor,1==e&&z.slice_del(),z.limit_backward=r,!0;z.limit_backward=r}return!1}function d(){var e;z.ket=z.cursor,(e=z.find_among_b(W,7))&&(z.bra=z.cursor,1==e&&w()&&z.slice_del())}function v(e,r){if(z.eq_s_b(1,e)){z.bra=z.cursor;var s=z.limit-z.cursor;if(z.eq_s_b(1,r))return z.cursor=z.limit-s,w()&&z.slice_del(),!1}return!0}function p(){var e;if(z.ket=z.cursor,e=z.find_among_b(L,4))switch(z.bra=z.cursor,e){case 1:w()&&(z.slice_del(),z.ket=z.cursor,z.limit-z.cursor,v("u","g")&&v("i","c"));break;case 2:z.slice_from("c")}}function _(){if(!l()&&(z.cursor=z.limit,!f()))return z.cursor=z.limit,void d();z.cursor=z.limit,z.ket=z.cursor,z.eq_s_b(1,"i")&&(z.bra=z.cursor,z.eq_s_b(1,"c")&&(z.cursor=z.limit,w()&&z.slice_del()))}var h,b,g,k=[new r("",-1,3),new r("ã",0,1),new r("õ",0,2)],q=[new r("",-1,3),new r("a~",0,1),new r("o~",0,2)],j=[new r("ic",-1,-1),new r("ad",-1,-1),new r("os",-1,-1),new r("iv",-1,1)],C=[new r("ante",-1,1),new r("avel",-1,1),new r("ível",-1,1)],P=[new r("ic",-1,1),new r("abil",-1,1),new r("iv",-1,1)],F=[new r("ica",-1,1),new r("ância",-1,1),new r("ência",-1,4),new r("ira",-1,9),new r("adora",-1,1),new r("osa",-1,1),new r("ista",-1,1),new r("iva",-1,8),new r("eza",-1,1),new r("logía",-1,2),new r("idade",-1,7),new r("ante",-1,1),new r("mente",-1,6),new r("amente",12,5),new r("ável",-1,1),new r("ível",-1,1),new r("ución",-1,3),new r("ico",-1,1),new r("ismo",-1,1),new r("oso",-1,1),new r("amento",-1,1),new r("imento",-1,1),new r("ivo",-1,8),new r("aça~o",-1,1),new r("ador",-1,1),new r("icas",-1,1),new r("ências",-1,4),new r("iras",-1,9),new r("adoras",-1,1),new r("osas",-1,1),new r("istas",-1,1),new r("ivas",-1,8),new r("ezas",-1,1),new r("logías",-1,2),new r("idades",-1,7),new r("uciones",-1,3),new r("adores",-1,1),new r("antes",-1,1),new r("aço~es",-1,1),new r("icos",-1,1),new r("ismos",-1,1),new r("osos",-1,1),new r("amentos",-1,1),new r("imentos",-1,1),new r("ivos",-1,8)],S=[new r("ada",-1,1),new r("ida",-1,1),new r("ia",-1,1),new r("aria",2,1),new r("eria",2,1),new r("iria",2,1),new r("ara",-1,1),new r("era",-1,1),new r("ira",-1,1),new r("ava",-1,1),new r("asse",-1,1),new r("esse",-1,1),new r("isse",-1,1),new r("aste",-1,1),new r("este",-1,1),new r("iste",-1,1),new r("ei",-1,1),new r("arei",16,1),new r("erei",16,1),new r("irei",16,1),new r("am",-1,1),new r("iam",20,1),new r("ariam",21,1),new r("eriam",21,1),new r("iriam",21,1),new r("aram",20,1),new r("eram",20,1),new r("iram",20,1),new r("avam",20,1),new r("em",-1,1),new r("arem",29,1),new r("erem",29,1),new r("irem",29,1),new r("assem",29,1),new r("essem",29,1),new r("issem",29,1),new r("ado",-1,1),new r("ido",-1,1),new r("ando",-1,1),new r("endo",-1,1),new r("indo",-1,1),new r("ara~o",-1,1),new r("era~o",-1,1),new r("ira~o",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("ir",-1,1),new r("as",-1,1),new r("adas",47,1),new r("idas",47,1),new r("ias",47,1),new r("arias",50,1),new r("erias",50,1),new r("irias",50,1),new r("aras",47,1),new r("eras",47,1),new r("iras",47,1),new r("avas",47,1),new r("es",-1,1),new r("ardes",58,1),new r("erdes",58,1),new r("irdes",58,1),new r("ares",58,1),new r("eres",58,1),new r("ires",58,1),new r("asses",58,1),new r("esses",58,1),new r("isses",58,1),new r("astes",58,1),new r("estes",58,1),new r("istes",58,1),new r("is",-1,1),new r("ais",71,1),new r("eis",71,1),new r("areis",73,1),new r("ereis",73,1),new r("ireis",73,1),new r("áreis",73,1),new r("éreis",73,1),new r("íreis",73,1),new r("ásseis",73,1),new r("ésseis",73,1),new r("ísseis",73,1),new r("áveis",73,1),new r("íeis",73,1),new r("aríeis",84,1),new r("eríeis",84,1),new r("iríeis",84,1),new r("ados",-1,1),new r("idos",-1,1),new r("amos",-1,1),new r("áramos",90,1),new r("éramos",90,1),new r("íramos",90,1),new r("ávamos",90,1),new r("íamos",90,1),new r("aríamos",95,1),new r("eríamos",95,1),new r("iríamos",95,1),new r("emos",-1,1),new r("aremos",99,1),new r("eremos",99,1),new r("iremos",99,1),new r("ássemos",99,1),new r("êssemos",99,1),new r("íssemos",99,1),new r("imos",-1,1),new r("armos",-1,1),new r("ermos",-1,1),new r("irmos",-1,1),new r("ámos",-1,1),new r("arás",-1,1),new r("erás",-1,1),new r("irás",-1,1),new r("eu",-1,1),new r("iu",-1,1),new r("ou",-1,1),new r("ará",-1,1),new r("erá",-1,1),new r("irá",-1,1)],W=[new r("a",-1,1),new r("i",-1,1),new r("o",-1,1),new r("os",-1,1),new r("á",-1,1),new r("í",-1,1),new r("ó",-1,1)],L=[new r("e",-1,1),new r("ç",-1,2),new r("é",-1,1),new r("ê",-1,1)],y=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,3,19,12,2],z=new s;this.setCurrent=function(e){z.setCurrent(e)},this.getCurrent=function(){return z.getCurrent()},this.stem=function(){var r=z.cursor;return e(),z.cursor=r,a(),z.limit_backward=r,z.cursor=z.limit,_(),z.cursor=z.limit,p(),z.cursor=z.limit_backward,u(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.pt.stemmer,"stemmer-pt"),e.pt.stopWordFilter=e.generateStopWordFilter("a ao aos aquela aquelas aquele aqueles aquilo as até com como da das de dela delas dele deles depois do dos e ela elas ele eles em entre era eram essa essas esse esses esta estamos estas estava estavam este esteja estejam estejamos estes esteve estive estivemos estiver estivera estiveram estiverem estivermos estivesse estivessem estivéramos estivéssemos estou está estávamos estão eu foi fomos for fora foram forem formos fosse fossem fui fôramos fôssemos haja hajam hajamos havemos hei houve houvemos houver houvera houveram houverei houverem houveremos houveria houveriam houvermos houverá houverão houveríamos houvesse houvessem houvéramos houvéssemos há hão isso isto já lhe lhes mais mas me mesmo meu meus minha minhas muito na nas nem no nos nossa nossas nosso nossos num numa não nós o os ou para pela pelas pelo pelos por qual quando que quem se seja sejam sejamos sem serei seremos seria seriam será serão seríamos seu seus somos sou sua suas são só também te tem temos tenha tenham tenhamos tenho terei teremos teria teriam terá terão teríamos teu teus teve tinha tinham tive tivemos tiver tivera tiveram tiverem tivermos tivesse tivessem tivéramos tivéssemos tu tua tuas tém tínhamos um uma você vocês vos à às éramos".split(" ")),e.Pipeline.registerFunction(e.pt.stopWordFilter,"stopWordFilter-pt")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.ro.min.js b/assets/javascripts/lunr/min/lunr.ro.min.js new file mode 100644 index 000000000..727714018 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.ro.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Romanian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ro=function(){this.pipeline.reset(),this.pipeline.add(e.ro.trimmer,e.ro.stopWordFilter,e.ro.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ro.stemmer))},e.ro.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.ro.trimmer=e.trimmerSupport.generateTrimmer(e.ro.wordCharacters),e.Pipeline.registerFunction(e.ro.trimmer,"trimmer-ro"),e.ro.stemmer=function(){var i=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,n=new function(){function e(e,i){L.eq_s(1,e)&&(L.ket=L.cursor,L.in_grouping(W,97,259)&&L.slice_from(i))}function n(){for(var i,r;;){if(i=L.cursor,L.in_grouping(W,97,259)&&(r=L.cursor,L.bra=r,e("u","U"),L.cursor=r,e("i","I")),L.cursor=i,L.cursor>=L.limit)break;L.cursor++}}function t(){if(L.out_grouping(W,97,259)){for(;!L.in_grouping(W,97,259);){if(L.cursor>=L.limit)return!0;L.cursor++}return!1}return!0}function a(){if(L.in_grouping(W,97,259))for(;!L.out_grouping(W,97,259);){if(L.cursor>=L.limit)return!0;L.cursor++}return!1}function o(){var e,i,r=L.cursor;if(L.in_grouping(W,97,259)){if(e=L.cursor,!t())return void(h=L.cursor);if(L.cursor=e,!a())return void(h=L.cursor)}L.cursor=r,L.out_grouping(W,97,259)&&(i=L.cursor,t()&&(L.cursor=i,L.in_grouping(W,97,259)&&L.cursor=L.limit)return!1;L.cursor++}for(;!L.out_grouping(W,97,259);){if(L.cursor>=L.limit)return!1;L.cursor++}return!0}function c(){var e=L.cursor;h=L.limit,k=h,g=h,o(),L.cursor=e,u()&&(k=L.cursor,u()&&(g=L.cursor))}function s(){for(var e;;){if(L.bra=L.cursor,e=L.find_among(z,3))switch(L.ket=L.cursor,e){case 1:L.slice_from("i");continue;case 2:L.slice_from("u");continue;case 3:if(L.cursor>=L.limit)break;L.cursor++;continue}break}}function w(){return h<=L.cursor}function m(){return k<=L.cursor}function l(){return g<=L.cursor}function f(){var e,i;if(L.ket=L.cursor,(e=L.find_among_b(C,16))&&(L.bra=L.cursor,m()))switch(e){case 1:L.slice_del();break;case 2:L.slice_from("a");break;case 3:L.slice_from("e");break;case 4:L.slice_from("i");break;case 5:i=L.limit-L.cursor,L.eq_s_b(2,"ab")||(L.cursor=L.limit-i,L.slice_from("i"));break;case 6:L.slice_from("at");break;case 7:L.slice_from("aţi")}}function p(){var e,i=L.limit-L.cursor;if(L.ket=L.cursor,(e=L.find_among_b(P,46))&&(L.bra=L.cursor,m())){switch(e){case 1:L.slice_from("abil");break;case 2:L.slice_from("ibil");break;case 3:L.slice_from("iv");break;case 4:L.slice_from("ic");break;case 5:L.slice_from("at");break;case 6:L.slice_from("it")}return _=!0,L.cursor=L.limit-i,!0}return!1}function d(){var e,i;for(_=!1;;)if(i=L.limit-L.cursor,!p()){L.cursor=L.limit-i;break}if(L.ket=L.cursor,(e=L.find_among_b(F,62))&&(L.bra=L.cursor,l())){switch(e){case 1:L.slice_del();break;case 2:L.eq_s_b(1,"ţ")&&(L.bra=L.cursor,L.slice_from("t"));break;case 3:L.slice_from("ist")}_=!0}}function b(){var e,i,r;if(L.cursor>=h){if(i=L.limit_backward,L.limit_backward=h,L.ket=L.cursor,e=L.find_among_b(q,94))switch(L.bra=L.cursor,e){case 1:if(r=L.limit-L.cursor,!L.out_grouping_b(W,97,259)&&(L.cursor=L.limit-r,!L.eq_s_b(1,"u")))break;case 2:L.slice_del()}L.limit_backward=i}}function v(){var e;L.ket=L.cursor,(e=L.find_among_b(S,5))&&(L.bra=L.cursor,w()&&1==e&&L.slice_del())}var _,g,k,h,z=[new i("",-1,3),new i("I",0,1),new i("U",0,2)],C=[new i("ea",-1,3),new i("aţia",-1,7),new i("aua",-1,2),new i("iua",-1,4),new i("aţie",-1,7),new i("ele",-1,3),new i("ile",-1,5),new i("iile",6,4),new i("iei",-1,4),new i("atei",-1,6),new i("ii",-1,4),new i("ului",-1,1),new i("ul",-1,1),new i("elor",-1,3),new i("ilor",-1,4),new i("iilor",14,4)],P=[new i("icala",-1,4),new i("iciva",-1,4),new i("ativa",-1,5),new i("itiva",-1,6),new i("icale",-1,4),new i("aţiune",-1,5),new i("iţiune",-1,6),new i("atoare",-1,5),new i("itoare",-1,6),new i("ătoare",-1,5),new i("icitate",-1,4),new i("abilitate",-1,1),new i("ibilitate",-1,2),new i("ivitate",-1,3),new i("icive",-1,4),new i("ative",-1,5),new i("itive",-1,6),new i("icali",-1,4),new i("atori",-1,5),new i("icatori",18,4),new i("itori",-1,6),new i("ători",-1,5),new i("icitati",-1,4),new i("abilitati",-1,1),new i("ivitati",-1,3),new i("icivi",-1,4),new i("ativi",-1,5),new i("itivi",-1,6),new i("icităi",-1,4),new i("abilităi",-1,1),new i("ivităi",-1,3),new i("icităţi",-1,4),new i("abilităţi",-1,1),new i("ivităţi",-1,3),new i("ical",-1,4),new i("ator",-1,5),new i("icator",35,4),new i("itor",-1,6),new i("ător",-1,5),new i("iciv",-1,4),new i("ativ",-1,5),new i("itiv",-1,6),new i("icală",-1,4),new i("icivă",-1,4),new i("ativă",-1,5),new i("itivă",-1,6)],F=[new i("ica",-1,1),new i("abila",-1,1),new i("ibila",-1,1),new i("oasa",-1,1),new i("ata",-1,1),new i("ita",-1,1),new i("anta",-1,1),new i("ista",-1,3),new i("uta",-1,1),new i("iva",-1,1),new i("ic",-1,1),new i("ice",-1,1),new i("abile",-1,1),new i("ibile",-1,1),new i("isme",-1,3),new i("iune",-1,2),new i("oase",-1,1),new i("ate",-1,1),new i("itate",17,1),new i("ite",-1,1),new i("ante",-1,1),new i("iste",-1,3),new i("ute",-1,1),new i("ive",-1,1),new i("ici",-1,1),new i("abili",-1,1),new i("ibili",-1,1),new i("iuni",-1,2),new i("atori",-1,1),new i("osi",-1,1),new i("ati",-1,1),new i("itati",30,1),new i("iti",-1,1),new i("anti",-1,1),new i("isti",-1,3),new i("uti",-1,1),new i("işti",-1,3),new i("ivi",-1,1),new i("ităi",-1,1),new i("oşi",-1,1),new i("ităţi",-1,1),new i("abil",-1,1),new i("ibil",-1,1),new i("ism",-1,3),new i("ator",-1,1),new i("os",-1,1),new i("at",-1,1),new i("it",-1,1),new i("ant",-1,1),new i("ist",-1,3),new i("ut",-1,1),new i("iv",-1,1),new i("ică",-1,1),new i("abilă",-1,1),new i("ibilă",-1,1),new i("oasă",-1,1),new i("ată",-1,1),new i("ită",-1,1),new i("antă",-1,1),new i("istă",-1,3),new i("ută",-1,1),new i("ivă",-1,1)],q=[new i("ea",-1,1),new i("ia",-1,1),new i("esc",-1,1),new i("ăsc",-1,1),new i("ind",-1,1),new i("ând",-1,1),new i("are",-1,1),new i("ere",-1,1),new i("ire",-1,1),new i("âre",-1,1),new i("se",-1,2),new i("ase",10,1),new i("sese",10,2),new i("ise",10,1),new i("use",10,1),new i("âse",10,1),new i("eşte",-1,1),new i("ăşte",-1,1),new i("eze",-1,1),new i("ai",-1,1),new i("eai",19,1),new i("iai",19,1),new i("sei",-1,2),new i("eşti",-1,1),new i("ăşti",-1,1),new i("ui",-1,1),new i("ezi",-1,1),new i("âi",-1,1),new i("aşi",-1,1),new i("seşi",-1,2),new i("aseşi",29,1),new i("seseşi",29,2),new i("iseşi",29,1),new i("useşi",29,1),new i("âseşi",29,1),new i("işi",-1,1),new i("uşi",-1,1),new i("âşi",-1,1),new i("aţi",-1,2),new i("eaţi",38,1),new i("iaţi",38,1),new i("eţi",-1,2),new i("iţi",-1,2),new i("âţi",-1,2),new i("arăţi",-1,1),new i("serăţi",-1,2),new i("aserăţi",45,1),new i("seserăţi",45,2),new i("iserăţi",45,1),new i("userăţi",45,1),new i("âserăţi",45,1),new i("irăţi",-1,1),new i("urăţi",-1,1),new i("ârăţi",-1,1),new i("am",-1,1),new i("eam",54,1),new i("iam",54,1),new i("em",-1,2),new i("asem",57,1),new i("sesem",57,2),new i("isem",57,1),new i("usem",57,1),new i("âsem",57,1),new i("im",-1,2),new i("âm",-1,2),new i("ăm",-1,2),new i("arăm",65,1),new i("serăm",65,2),new i("aserăm",67,1),new i("seserăm",67,2),new i("iserăm",67,1),new i("userăm",67,1),new i("âserăm",67,1),new i("irăm",65,1),new i("urăm",65,1),new i("ârăm",65,1),new i("au",-1,1),new i("eau",76,1),new i("iau",76,1),new i("indu",-1,1),new i("ându",-1,1),new i("ez",-1,1),new i("ească",-1,1),new i("ară",-1,1),new i("seră",-1,2),new i("aseră",84,1),new i("seseră",84,2),new i("iseră",84,1),new i("useră",84,1),new i("âseră",84,1),new i("iră",-1,1),new i("ură",-1,1),new i("âră",-1,1),new i("ează",-1,1)],S=[new i("a",-1,1),new i("e",-1,1),new i("ie",1,1),new i("i",-1,1),new i("ă",-1,1)],W=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,2,32,0,0,4],L=new r;this.setCurrent=function(e){L.setCurrent(e)},this.getCurrent=function(){return L.getCurrent()},this.stem=function(){var e=L.cursor;return n(),L.cursor=e,c(),L.limit_backward=e,L.cursor=L.limit,f(),L.cursor=L.limit,d(),L.cursor=L.limit,_||(L.cursor=L.limit,b(),L.cursor=L.limit),v(),L.cursor=L.limit_backward,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.ro.stemmer,"stemmer-ro"),e.ro.stopWordFilter=e.generateStopWordFilter("acea aceasta această aceea acei aceia acel acela acele acelea acest acesta aceste acestea aceşti aceştia acolo acord acum ai aia aibă aici al ale alea altceva altcineva am ar are asemenea asta astea astăzi asupra au avea avem aveţi azi aş aşadar aţi bine bucur bună ca care caut ce cel ceva chiar cinci cine cineva contra cu cum cumva curând curînd când cât câte câtva câţi cînd cît cîte cîtva cîţi că căci cărei căror cărui către da dacă dar datorită dată dau de deci deja deoarece departe deşi din dinaintea dintr- dintre doi doilea două drept după dă ea ei el ele eram este eu eşti face fata fi fie fiecare fii fim fiu fiţi frumos fără graţie halbă iar ieri la le li lor lui lângă lîngă mai mea mei mele mereu meu mi mie mine mult multă mulţi mulţumesc mâine mîine mă ne nevoie nici nicăieri nimeni nimeri nimic nişte noastre noastră noi noroc nostru nouă noştri nu opt ori oricare orice oricine oricum oricând oricât oricînd oricît oriunde patra patru patrulea pe pentru peste pic poate pot prea prima primul prin puţin puţina puţină până pînă rog sa sale sau se spate spre sub sunt suntem sunteţi sută sînt sîntem sînteţi să săi său ta tale te timp tine toate toată tot totuşi toţi trei treia treilea tu tăi tău un una unde undeva unei uneia unele uneori unii unor unora unu unui unuia unul vi voastre voastră voi vostru vouă voştri vreme vreo vreun vă zece zero zi zice îi îl îmi împotriva în înainte înaintea încotro încât încît între întrucât întrucît îţi ăla ălea ăsta ăstea ăştia şapte şase şi ştiu ţi ţie".split(" ")),e.Pipeline.registerFunction(e.ro.stopWordFilter,"stopWordFilter-ro")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.ru.min.js b/assets/javascripts/lunr/min/lunr.ru.min.js new file mode 100644 index 000000000..186cc485c --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.ru.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Russian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ru=function(){this.pipeline.reset(),this.pipeline.add(e.ru.trimmer,e.ru.stopWordFilter,e.ru.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ru.stemmer))},e.ru.wordCharacters="Ѐ-҄҇-ԯᴫᵸⷠ-ⷿꙀ-ꚟ︮︯",e.ru.trimmer=e.trimmerSupport.generateTrimmer(e.ru.wordCharacters),e.Pipeline.registerFunction(e.ru.trimmer,"trimmer-ru"),e.ru.stemmer=function(){var n=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,t=new function(){function e(){for(;!W.in_grouping(S,1072,1103);){if(W.cursor>=W.limit)return!1;W.cursor++}return!0}function t(){for(;!W.out_grouping(S,1072,1103);){if(W.cursor>=W.limit)return!1;W.cursor++}return!0}function w(){b=W.limit,_=b,e()&&(b=W.cursor,t()&&e()&&t()&&(_=W.cursor))}function i(){return _<=W.cursor}function u(e,n){var r,t;if(W.ket=W.cursor,r=W.find_among_b(e,n)){switch(W.bra=W.cursor,r){case 1:if(t=W.limit-W.cursor,!W.eq_s_b(1,"а")&&(W.cursor=W.limit-t,!W.eq_s_b(1,"я")))return!1;case 2:W.slice_del()}return!0}return!1}function o(){return u(h,9)}function s(e,n){var r;return W.ket=W.cursor,!!(r=W.find_among_b(e,n))&&(W.bra=W.cursor,1==r&&W.slice_del(),!0)}function c(){return s(g,26)}function m(){return!!c()&&(u(C,8),!0)}function f(){return s(k,2)}function l(){return u(P,46)}function a(){s(v,36)}function p(){var e;W.ket=W.cursor,(e=W.find_among_b(F,2))&&(W.bra=W.cursor,i()&&1==e&&W.slice_del())}function d(){var e;if(W.ket=W.cursor,e=W.find_among_b(q,4))switch(W.bra=W.cursor,e){case 1:if(W.slice_del(),W.ket=W.cursor,!W.eq_s_b(1,"н"))break;W.bra=W.cursor;case 2:if(!W.eq_s_b(1,"н"))break;case 3:W.slice_del()}}var _,b,h=[new n("в",-1,1),new n("ив",0,2),new n("ыв",0,2),new n("вши",-1,1),new n("ивши",3,2),new n("ывши",3,2),new n("вшись",-1,1),new n("ившись",6,2),new n("ывшись",6,2)],g=[new n("ее",-1,1),new n("ие",-1,1),new n("ое",-1,1),new n("ые",-1,1),new n("ими",-1,1),new n("ыми",-1,1),new n("ей",-1,1),new n("ий",-1,1),new n("ой",-1,1),new n("ый",-1,1),new n("ем",-1,1),new n("им",-1,1),new n("ом",-1,1),new n("ым",-1,1),new n("его",-1,1),new n("ого",-1,1),new n("ему",-1,1),new n("ому",-1,1),new n("их",-1,1),new n("ых",-1,1),new n("ею",-1,1),new n("ою",-1,1),new n("ую",-1,1),new n("юю",-1,1),new n("ая",-1,1),new n("яя",-1,1)],C=[new n("ем",-1,1),new n("нн",-1,1),new n("вш",-1,1),new n("ивш",2,2),new n("ывш",2,2),new n("щ",-1,1),new n("ющ",5,1),new n("ующ",6,2)],k=[new n("сь",-1,1),new n("ся",-1,1)],P=[new n("ла",-1,1),new n("ила",0,2),new n("ыла",0,2),new n("на",-1,1),new n("ена",3,2),new n("ете",-1,1),new n("ите",-1,2),new n("йте",-1,1),new n("ейте",7,2),new n("уйте",7,2),new n("ли",-1,1),new n("или",10,2),new n("ыли",10,2),new n("й",-1,1),new n("ей",13,2),new n("уй",13,2),new n("л",-1,1),new n("ил",16,2),new n("ыл",16,2),new n("ем",-1,1),new n("им",-1,2),new n("ым",-1,2),new n("н",-1,1),new n("ен",22,2),new n("ло",-1,1),new n("ило",24,2),new n("ыло",24,2),new n("но",-1,1),new n("ено",27,2),new n("нно",27,1),new n("ет",-1,1),new n("ует",30,2),new n("ит",-1,2),new n("ыт",-1,2),new n("ют",-1,1),new n("уют",34,2),new n("ят",-1,2),new n("ны",-1,1),new n("ены",37,2),new n("ть",-1,1),new n("ить",39,2),new n("ыть",39,2),new n("ешь",-1,1),new n("ишь",-1,2),new n("ю",-1,2),new n("ую",44,2)],v=[new n("а",-1,1),new n("ев",-1,1),new n("ов",-1,1),new n("е",-1,1),new n("ие",3,1),new n("ье",3,1),new n("и",-1,1),new n("еи",6,1),new n("ии",6,1),new n("ами",6,1),new n("ями",6,1),new n("иями",10,1),new n("й",-1,1),new n("ей",12,1),new n("ией",13,1),new n("ий",12,1),new n("ой",12,1),new n("ам",-1,1),new n("ем",-1,1),new n("ием",18,1),new n("ом",-1,1),new n("ям",-1,1),new n("иям",21,1),new n("о",-1,1),new n("у",-1,1),new n("ах",-1,1),new n("ях",-1,1),new n("иях",26,1),new n("ы",-1,1),new n("ь",-1,1),new n("ю",-1,1),new n("ию",30,1),new n("ью",30,1),new n("я",-1,1),new n("ия",33,1),new n("ья",33,1)],F=[new n("ост",-1,1),new n("ость",-1,1)],q=[new n("ейше",-1,1),new n("н",-1,2),new n("ейш",-1,1),new n("ь",-1,3)],S=[33,65,8,232],W=new r;this.setCurrent=function(e){W.setCurrent(e)},this.getCurrent=function(){return W.getCurrent()},this.stem=function(){return w(),W.cursor=W.limit,!(W.cursor=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor++,!0}return!1},in_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e<=s&&e>=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor--,!0}return!1},out_grouping:function(t,i,s){if(this.cursors||e>3]&1<<(7&e)))return this.cursor++,!0}return!1},out_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e>s||e>3]&1<<(7&e)))return this.cursor--,!0}return!1},eq_s:function(t,i){if(this.limit-this.cursor>1),f=0,l=o0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n+_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n+_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},find_among_b:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit_backward,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o=0;m--){if(n-l==u){f=-1;break}if(f=r.charCodeAt(n-1-l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n-_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n-_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},replace_s:function(t,i,s){var e=s.length-(i-t),n=r.substring(0,t),u=r.substring(i);return r=n+s+u,this.limit+=e,this.cursor>=i?this.cursor+=e:this.cursor>t&&(this.cursor=t),e},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>r.length)throw"faulty slice operation"},slice_from:function(r){this.slice_check(),this.replace_s(this.bra,this.ket,r)},slice_del:function(){this.slice_from("")},insert:function(r,t,i){var s=this.replace_s(r,t,i);r<=this.bra&&(this.bra+=s),r<=this.ket&&(this.ket+=s)},slice_to:function(){return this.slice_check(),r.substring(this.bra,this.ket)},eq_v_b:function(r){return this.eq_s_b(r.length,r)}}}},r.trimmerSupport={generateTrimmer:function(r){var t=new RegExp("^[^"+r+"]+"),i=new RegExp("[^"+r+"]+$");return function(r){return"function"==typeof r.update?r.update(function(r){return r.replace(t,"").replace(i,"")}):r.replace(t,"").replace(i,"")}}}}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.sv.min.js b/assets/javascripts/lunr/min/lunr.sv.min.js new file mode 100644 index 000000000..3e5eb6400 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.sv.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Swedish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.sv=function(){this.pipeline.reset(),this.pipeline.add(e.sv.trimmer,e.sv.stopWordFilter,e.sv.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sv.stemmer))},e.sv.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.sv.trimmer=e.trimmerSupport.generateTrimmer(e.sv.wordCharacters),e.Pipeline.registerFunction(e.sv.trimmer,"trimmer-sv"),e.sv.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,t=new function(){function e(){var e,r=w.cursor+3;if(o=w.limit,0<=r||r<=w.limit){for(a=r;;){if(e=w.cursor,w.in_grouping(l,97,246)){w.cursor=e;break}if(w.cursor=e,w.cursor>=w.limit)return;w.cursor++}for(;!w.out_grouping(l,97,246);){if(w.cursor>=w.limit)return;w.cursor++}o=w.cursor,o=o&&(w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(u,37),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.in_grouping_b(d,98,121)&&w.slice_del()}}function i(){var e=w.limit_backward;w.cursor>=o&&(w.limit_backward=o,w.cursor=w.limit,w.find_among_b(c,7)&&(w.cursor=w.limit,w.ket=w.cursor,w.cursor>w.limit_backward&&(w.bra=--w.cursor,w.slice_del())),w.limit_backward=e)}function s(){var e,r;if(w.cursor>=o){if(r=w.limit_backward,w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(m,5))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.slice_from("lös");break;case 3:w.slice_from("full")}w.limit_backward=r}}var a,o,u=[new r("a",-1,1),new r("arna",0,1),new r("erna",0,1),new r("heterna",2,1),new r("orna",0,1),new r("ad",-1,1),new r("e",-1,1),new r("ade",6,1),new r("ande",6,1),new r("arne",6,1),new r("are",6,1),new r("aste",6,1),new r("en",-1,1),new r("anden",12,1),new r("aren",12,1),new r("heten",12,1),new r("ern",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",18,1),new r("or",-1,1),new r("s",-1,2),new r("as",21,1),new r("arnas",22,1),new r("ernas",22,1),new r("ornas",22,1),new r("es",21,1),new r("ades",26,1),new r("andes",26,1),new r("ens",21,1),new r("arens",29,1),new r("hetens",29,1),new r("erns",21,1),new r("at",-1,1),new r("andet",-1,1),new r("het",-1,1),new r("ast",-1,1)],c=[new r("dd",-1,-1),new r("gd",-1,-1),new r("nn",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1),new r("tt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("els",-1,1),new r("fullt",-1,3),new r("löst",-1,2)],l=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32],d=[119,127,149],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,t(),w.cursor=w.limit,i(),w.cursor=w.limit,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return t.setCurrent(e),t.stem(),t.getCurrent()}):(t.setCurrent(e),t.stem(),t.getCurrent())}}(),e.Pipeline.registerFunction(e.sv.stemmer,"stemmer-sv"),e.sv.stopWordFilter=e.generateStopWordFilter("alla allt att av blev bli blir blivit de dem den denna deras dess dessa det detta dig din dina ditt du där då efter ej eller en er era ert ett från för ha hade han hans har henne hennes hon honom hur här i icke ingen inom inte jag ju kan kunde man med mellan men mig min mina mitt mot mycket ni nu när någon något några och om oss på samma sedan sig sin sina sitta själv skulle som så sådan sådana sådant till under upp ut utan vad var vara varför varit varje vars vart vem vi vid vilka vilkas vilken vilket vår våra vårt än är åt över".split(" ")),e.Pipeline.registerFunction(e.sv.stopWordFilter,"stopWordFilter-sv")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.tr.min.js b/assets/javascripts/lunr/min/lunr.tr.min.js new file mode 100644 index 000000000..563f6ec1f --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.tr.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Turkish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(r,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(r.lunr)}(this,function(){return function(r){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");r.tr=function(){this.pipeline.reset(),this.pipeline.add(r.tr.trimmer,r.tr.stopWordFilter,r.tr.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(r.tr.stemmer))},r.tr.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",r.tr.trimmer=r.trimmerSupport.generateTrimmer(r.tr.wordCharacters),r.Pipeline.registerFunction(r.tr.trimmer,"trimmer-tr"),r.tr.stemmer=function(){var i=r.stemmerSupport.Among,e=r.stemmerSupport.SnowballProgram,n=new function(){function r(r,i,e){for(;;){var n=Dr.limit-Dr.cursor;if(Dr.in_grouping_b(r,i,e)){Dr.cursor=Dr.limit-n;break}if(Dr.cursor=Dr.limit-n,Dr.cursor<=Dr.limit_backward)return!1;Dr.cursor--}return!0}function n(){var i,e;i=Dr.limit-Dr.cursor,r(Wr,97,305);for(var n=0;nDr.limit_backward&&(Dr.cursor--,e=Dr.limit-Dr.cursor,i()))?(Dr.cursor=Dr.limit-e,!0):(Dr.cursor=Dr.limit-n,r()?(Dr.cursor=Dr.limit-n,!1):(Dr.cursor=Dr.limit-n,!(Dr.cursor<=Dr.limit_backward)&&(Dr.cursor--,!!i()&&(Dr.cursor=Dr.limit-n,!0))))}function u(r){return t(r,function(){return Dr.in_grouping_b(Wr,97,305)})}function o(){return u(function(){return Dr.eq_s_b(1,"n")})}function s(){return u(function(){return Dr.eq_s_b(1,"s")})}function c(){return u(function(){return Dr.eq_s_b(1,"y")})}function l(){return t(function(){return Dr.in_grouping_b(Lr,105,305)},function(){return Dr.out_grouping_b(Wr,97,305)})}function a(){return Dr.find_among_b(ur,10)&&l()}function m(){return n()&&Dr.in_grouping_b(Lr,105,305)&&s()}function d(){return Dr.find_among_b(or,2)}function f(){return n()&&Dr.in_grouping_b(Lr,105,305)&&c()}function b(){return n()&&Dr.find_among_b(sr,4)}function w(){return n()&&Dr.find_among_b(cr,4)&&o()}function _(){return n()&&Dr.find_among_b(lr,2)&&c()}function k(){return n()&&Dr.find_among_b(ar,2)}function p(){return n()&&Dr.find_among_b(mr,4)}function g(){return n()&&Dr.find_among_b(dr,2)}function y(){return n()&&Dr.find_among_b(fr,4)}function z(){return n()&&Dr.find_among_b(br,2)}function v(){return n()&&Dr.find_among_b(wr,2)&&c()}function h(){return Dr.eq_s_b(2,"ki")}function q(){return n()&&Dr.find_among_b(_r,2)&&o()}function C(){return n()&&Dr.find_among_b(kr,4)&&c()}function P(){return n()&&Dr.find_among_b(pr,4)}function F(){return n()&&Dr.find_among_b(gr,4)&&c()}function S(){return Dr.find_among_b(yr,4)}function W(){return n()&&Dr.find_among_b(zr,2)}function L(){return n()&&Dr.find_among_b(vr,4)}function x(){return n()&&Dr.find_among_b(hr,8)}function A(){return Dr.find_among_b(qr,2)}function E(){return n()&&Dr.find_among_b(Cr,32)&&c()}function j(){return Dr.find_among_b(Pr,8)&&c()}function T(){return n()&&Dr.find_among_b(Fr,4)&&c()}function Z(){return Dr.eq_s_b(3,"ken")&&c()}function B(){var r=Dr.limit-Dr.cursor;return!(T()||(Dr.cursor=Dr.limit-r,E()||(Dr.cursor=Dr.limit-r,j()||(Dr.cursor=Dr.limit-r,Z()))))}function D(){if(A()){var r=Dr.limit-Dr.cursor;if(S()||(Dr.cursor=Dr.limit-r,W()||(Dr.cursor=Dr.limit-r,C()||(Dr.cursor=Dr.limit-r,P()||(Dr.cursor=Dr.limit-r,F()||(Dr.cursor=Dr.limit-r))))),T())return!1}return!0}function G(){if(W()){Dr.bra=Dr.cursor,Dr.slice_del();var r=Dr.limit-Dr.cursor;return Dr.ket=Dr.cursor,x()||(Dr.cursor=Dr.limit-r,E()||(Dr.cursor=Dr.limit-r,j()||(Dr.cursor=Dr.limit-r,T()||(Dr.cursor=Dr.limit-r)))),nr=!1,!1}return!0}function H(){if(!L())return!0;var r=Dr.limit-Dr.cursor;return!E()&&(Dr.cursor=Dr.limit-r,!j())}function I(){var r,i=Dr.limit-Dr.cursor;return!(S()||(Dr.cursor=Dr.limit-i,F()||(Dr.cursor=Dr.limit-i,P()||(Dr.cursor=Dr.limit-i,C()))))||(Dr.bra=Dr.cursor,Dr.slice_del(),r=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,T()||(Dr.cursor=Dr.limit-r),!1)}function J(){var r,i=Dr.limit-Dr.cursor;if(Dr.ket=Dr.cursor,nr=!0,B()&&(Dr.cursor=Dr.limit-i,D()&&(Dr.cursor=Dr.limit-i,G()&&(Dr.cursor=Dr.limit-i,H()&&(Dr.cursor=Dr.limit-i,I()))))){if(Dr.cursor=Dr.limit-i,!x())return;Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,r=Dr.limit-Dr.cursor,S()||(Dr.cursor=Dr.limit-r,W()||(Dr.cursor=Dr.limit-r,C()||(Dr.cursor=Dr.limit-r,P()||(Dr.cursor=Dr.limit-r,F()||(Dr.cursor=Dr.limit-r))))),T()||(Dr.cursor=Dr.limit-r)}Dr.bra=Dr.cursor,Dr.slice_del()}function K(){var r,i,e,n;if(Dr.ket=Dr.cursor,h()){if(r=Dr.limit-Dr.cursor,p())return Dr.bra=Dr.cursor,Dr.slice_del(),i=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,W()?(Dr.bra=Dr.cursor,Dr.slice_del(),K()):(Dr.cursor=Dr.limit-i,a()&&(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()))),!0;if(Dr.cursor=Dr.limit-r,w()){if(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,e=Dr.limit-Dr.cursor,d())Dr.bra=Dr.cursor,Dr.slice_del();else{if(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,!a()&&(Dr.cursor=Dr.limit-e,!m()&&(Dr.cursor=Dr.limit-e,!K())))return!0;Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K())}return!0}if(Dr.cursor=Dr.limit-r,g()){if(n=Dr.limit-Dr.cursor,d())Dr.bra=Dr.cursor,Dr.slice_del();else if(Dr.cursor=Dr.limit-n,m())Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K());else if(Dr.cursor=Dr.limit-n,!K())return!1;return!0}}return!1}function M(r){if(Dr.ket=Dr.cursor,!g()&&(Dr.cursor=Dr.limit-r,!k()))return!1;var i=Dr.limit-Dr.cursor;if(d())Dr.bra=Dr.cursor,Dr.slice_del();else if(Dr.cursor=Dr.limit-i,m())Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K());else if(Dr.cursor=Dr.limit-i,!K())return!1;return!0}function N(r){if(Dr.ket=Dr.cursor,!z()&&(Dr.cursor=Dr.limit-r,!b()))return!1;var i=Dr.limit-Dr.cursor;return!(!m()&&(Dr.cursor=Dr.limit-i,!d()))&&(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()),!0)}function O(){var r,i=Dr.limit-Dr.cursor;return Dr.ket=Dr.cursor,!(!w()&&(Dr.cursor=Dr.limit-i,!v()))&&(Dr.bra=Dr.cursor,Dr.slice_del(),r=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,!(!W()||(Dr.bra=Dr.cursor,Dr.slice_del(),!K()))||(Dr.cursor=Dr.limit-r,Dr.ket=Dr.cursor,!(a()||(Dr.cursor=Dr.limit-r,m()||(Dr.cursor=Dr.limit-r,K())))||(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()),!0)))}function Q(){var r,i,e=Dr.limit-Dr.cursor;if(Dr.ket=Dr.cursor,!p()&&(Dr.cursor=Dr.limit-e,!f()&&(Dr.cursor=Dr.limit-e,!_())))return!1;if(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,r=Dr.limit-Dr.cursor,a())Dr.bra=Dr.cursor,Dr.slice_del(),i=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,W()||(Dr.cursor=Dr.limit-i);else if(Dr.cursor=Dr.limit-r,!W())return!0;return Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,K(),!0}function R(){var r,i,e=Dr.limit-Dr.cursor;if(Dr.ket=Dr.cursor,W())return Dr.bra=Dr.cursor,Dr.slice_del(),void K();if(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,q())if(Dr.bra=Dr.cursor,Dr.slice_del(),r=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,d())Dr.bra=Dr.cursor,Dr.slice_del();else{if(Dr.cursor=Dr.limit-r,Dr.ket=Dr.cursor,!a()&&(Dr.cursor=Dr.limit-r,!m())){if(Dr.cursor=Dr.limit-r,Dr.ket=Dr.cursor,!W())return;if(Dr.bra=Dr.cursor,Dr.slice_del(),!K())return}Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K())}else if(Dr.cursor=Dr.limit-e,!M(e)&&(Dr.cursor=Dr.limit-e,!N(e))){if(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,y())return Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,i=Dr.limit-Dr.cursor,void(a()?(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K())):(Dr.cursor=Dr.limit-i,W()?(Dr.bra=Dr.cursor,Dr.slice_del(),K()):(Dr.cursor=Dr.limit-i,K())));if(Dr.cursor=Dr.limit-e,!O()){if(Dr.cursor=Dr.limit-e,d())return Dr.bra=Dr.cursor,void Dr.slice_del();Dr.cursor=Dr.limit-e,K()||(Dr.cursor=Dr.limit-e,Q()||(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,(a()||(Dr.cursor=Dr.limit-e,m()))&&(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()))))}}}function U(){var r;if(Dr.ket=Dr.cursor,r=Dr.find_among_b(Sr,4))switch(Dr.bra=Dr.cursor,r){case 1:Dr.slice_from("p");break;case 2:Dr.slice_from("ç");break;case 3:Dr.slice_from("t");break;case 4:Dr.slice_from("k")}}function V(){for(;;){var r=Dr.limit-Dr.cursor;if(Dr.in_grouping_b(Wr,97,305)){Dr.cursor=Dr.limit-r;break}if(Dr.cursor=Dr.limit-r,Dr.cursor<=Dr.limit_backward)return!1;Dr.cursor--}return!0}function X(r,i,e){if(Dr.cursor=Dr.limit-r,V()){var n=Dr.limit-Dr.cursor;if(!Dr.eq_s_b(1,i)&&(Dr.cursor=Dr.limit-n,!Dr.eq_s_b(1,e)))return!0;Dr.cursor=Dr.limit-r;var t=Dr.cursor;return Dr.insert(Dr.cursor,Dr.cursor,e),Dr.cursor=t,!1}return!0}function Y(){var r=Dr.limit-Dr.cursor;(Dr.eq_s_b(1,"d")||(Dr.cursor=Dr.limit-r,Dr.eq_s_b(1,"g")))&&X(r,"a","ı")&&X(r,"e","i")&&X(r,"o","u")&&X(r,"ö","ü")}function $(){for(var r,i=Dr.cursor,e=2;;){for(r=Dr.cursor;!Dr.in_grouping(Wr,97,305);){if(Dr.cursor>=Dr.limit)return Dr.cursor=r,!(e>0)&&(Dr.cursor=i,!0);Dr.cursor++}e--}}function rr(r,i,e){for(;!Dr.eq_s(i,e);){if(Dr.cursor>=Dr.limit)return!0;Dr.cursor++}return(tr=i)!=Dr.limit||(Dr.cursor=r,!1)}function ir(){var r=Dr.cursor;return!rr(r,2,"ad")||(Dr.cursor=r,!rr(r,5,"soyad"))}function er(){var r=Dr.cursor;return!ir()&&(Dr.limit_backward=r,Dr.cursor=Dr.limit,Y(),Dr.cursor=Dr.limit,U(),!0)}var nr,tr,ur=[new i("m",-1,-1),new i("n",-1,-1),new i("miz",-1,-1),new i("niz",-1,-1),new i("muz",-1,-1),new i("nuz",-1,-1),new i("müz",-1,-1),new i("nüz",-1,-1),new i("mız",-1,-1),new i("nız",-1,-1)],or=[new i("leri",-1,-1),new i("ları",-1,-1)],sr=[new i("ni",-1,-1),new i("nu",-1,-1),new i("nü",-1,-1),new i("nı",-1,-1)],cr=[new i("in",-1,-1),new i("un",-1,-1),new i("ün",-1,-1),new i("ın",-1,-1)],lr=[new i("a",-1,-1),new i("e",-1,-1)],ar=[new i("na",-1,-1),new i("ne",-1,-1)],mr=[new i("da",-1,-1),new i("ta",-1,-1),new i("de",-1,-1),new i("te",-1,-1)],dr=[new i("nda",-1,-1),new i("nde",-1,-1)],fr=[new i("dan",-1,-1),new i("tan",-1,-1),new i("den",-1,-1),new i("ten",-1,-1)],br=[new i("ndan",-1,-1),new i("nden",-1,-1)],wr=[new i("la",-1,-1),new i("le",-1,-1)],_r=[new i("ca",-1,-1),new i("ce",-1,-1)],kr=[new i("im",-1,-1),new i("um",-1,-1),new i("üm",-1,-1),new i("ım",-1,-1)],pr=[new i("sin",-1,-1),new i("sun",-1,-1),new i("sün",-1,-1),new i("sın",-1,-1)],gr=[new i("iz",-1,-1),new i("uz",-1,-1),new i("üz",-1,-1),new i("ız",-1,-1)],yr=[new i("siniz",-1,-1),new i("sunuz",-1,-1),new i("sünüz",-1,-1),new i("sınız",-1,-1)],zr=[new i("lar",-1,-1),new i("ler",-1,-1)],vr=[new i("niz",-1,-1),new i("nuz",-1,-1),new i("nüz",-1,-1),new i("nız",-1,-1)],hr=[new i("dir",-1,-1),new i("tir",-1,-1),new i("dur",-1,-1),new i("tur",-1,-1),new i("dür",-1,-1),new i("tür",-1,-1),new i("dır",-1,-1),new i("tır",-1,-1)],qr=[new i("casına",-1,-1),new i("cesine",-1,-1)],Cr=[new i("di",-1,-1),new i("ti",-1,-1),new i("dik",-1,-1),new i("tik",-1,-1),new i("duk",-1,-1),new i("tuk",-1,-1),new i("dük",-1,-1),new i("tük",-1,-1),new i("dık",-1,-1),new i("tık",-1,-1),new i("dim",-1,-1),new i("tim",-1,-1),new i("dum",-1,-1),new i("tum",-1,-1),new i("düm",-1,-1),new i("tüm",-1,-1),new i("dım",-1,-1),new i("tım",-1,-1),new i("din",-1,-1),new i("tin",-1,-1),new i("dun",-1,-1),new i("tun",-1,-1),new i("dün",-1,-1),new i("tün",-1,-1),new i("dın",-1,-1),new i("tın",-1,-1),new i("du",-1,-1),new i("tu",-1,-1),new i("dü",-1,-1),new i("tü",-1,-1),new i("dı",-1,-1),new i("tı",-1,-1)],Pr=[new i("sa",-1,-1),new i("se",-1,-1),new i("sak",-1,-1),new i("sek",-1,-1),new i("sam",-1,-1),new i("sem",-1,-1),new i("san",-1,-1),new i("sen",-1,-1)],Fr=[new i("miş",-1,-1),new i("muş",-1,-1),new i("müş",-1,-1),new i("mış",-1,-1)],Sr=[new i("b",-1,1),new i("c",-1,2),new i("d",-1,3),new i("ğ",-1,4)],Wr=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,8,0,0,0,0,0,0,1],Lr=[1,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,1],xr=[1,64,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],Ar=[17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130],Er=[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],jr=[17],Tr=[65],Zr=[65],Br=[["a",xr,97,305],["e",Ar,101,252],["ı",Er,97,305],["i",jr,101,105],["o",Tr,111,117],["ö",Zr,246,252],["u",Tr,111,117]],Dr=new e;this.setCurrent=function(r){Dr.setCurrent(r)},this.getCurrent=function(){return Dr.getCurrent()},this.stem=function(){return!!($()&&(Dr.limit_backward=Dr.cursor,Dr.cursor=Dr.limit,J(),Dr.cursor=Dr.limit,nr&&(R(),Dr.cursor=Dr.limit_backward,er())))}};return function(r){return"function"==typeof r.update?r.update(function(r){return n.setCurrent(r),n.stem(),n.getCurrent()}):(n.setCurrent(r),n.stem(),n.getCurrent())}}(),r.Pipeline.registerFunction(r.tr.stemmer,"stemmer-tr"),r.tr.stopWordFilter=r.generateStopWordFilter("acaba altmış altı ama ancak arada aslında ayrıca bana bazı belki ben benden beni benim beri beş bile bin bir biri birkaç birkez birçok birşey birşeyi biz bizden bize bizi bizim bu buna bunda bundan bunlar bunları bunların bunu bunun burada böyle böylece da daha dahi de defa değil diye diğer doksan dokuz dolayı dolayısıyla dört edecek eden ederek edilecek ediliyor edilmesi ediyor elli en etmesi etti ettiği ettiğini eğer gibi göre halen hangi hatta hem henüz hep hepsi her herhangi herkesin hiç hiçbir iki ile ilgili ise itibaren itibariyle için işte kadar karşın katrilyon kendi kendilerine kendini kendisi kendisine kendisini kez ki kim kimden kime kimi kimse kırk milyar milyon mu mü mı nasıl ne neden nedenle nerde nerede nereye niye niçin o olan olarak oldu olduklarını olduğu olduğunu olmadı olmadığı olmak olması olmayan olmaz olsa olsun olup olur olursa oluyor on ona ondan onlar onlardan onları onların onu onun otuz oysa pek rağmen sadece sanki sekiz seksen sen senden seni senin siz sizden sizi sizin tarafından trilyon tüm var vardı ve veya ya yani yapacak yapmak yaptı yaptıkları yaptığı yaptığını yapılan yapılması yapıyor yedi yerine yetmiş yine yirmi yoksa yüz zaten çok çünkü öyle üzere üç şey şeyden şeyi şeyler şu şuna şunda şundan şunları şunu şöyle".split(" ")),r.Pipeline.registerFunction(r.tr.stopWordFilter,"stopWordFilter-tr")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.vi.min.js b/assets/javascripts/lunr/min/lunr.vi.min.js new file mode 100644 index 000000000..22aed28c4 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.vi.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.vi=function(){this.pipeline.reset(),this.pipeline.add(e.vi.stopWordFilter,e.vi.trimmer)},e.vi.wordCharacters="[A-Za-ẓ̀͐́͑̉̃̓ÂâÊêÔôĂ-ăĐ-đƠ-ơƯ-ư]",e.vi.trimmer=e.trimmerSupport.generateTrimmer(e.vi.wordCharacters),e.Pipeline.registerFunction(e.vi.trimmer,"trimmer-vi"),e.vi.stopWordFilter=e.generateStopWordFilter("là cái nhưng mà".split(" "))}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/tinyseg.js b/assets/javascripts/lunr/tinyseg.js new file mode 100644 index 000000000..167fa6dd6 --- /dev/null +++ b/assets/javascripts/lunr/tinyseg.js @@ -0,0 +1,206 @@ +/** + * export the module via AMD, CommonJS or as a browser global + * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js + */ +;(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory) + } else if (typeof exports === 'object') { + /** + * Node. Does not work with strict CommonJS, but + * only CommonJS-like environments that support module.exports, + * like Node. + */ + module.exports = factory() + } else { + // Browser globals (root is window) + factory()(root.lunr); + } +}(this, function () { + /** + * Just return a value to define the module export. + * This example returns an object, but the module + * can return a function as the exported value. + */ + + return function(lunr) { + // TinySegmenter 0.1 -- Super compact Japanese tokenizer in Javascript + // (c) 2008 Taku Kudo + // TinySegmenter is freely distributable under the terms of a new BSD licence. + // For details, see http://chasen.org/~taku/software/TinySegmenter/LICENCE.txt + + function TinySegmenter() { + var patterns = { + "[一二三四五六七八九十百千万億兆]":"M", + "[一-龠々〆ヵヶ]":"H", + "[ぁ-ん]":"I", + "[ァ-ヴーア-ン゙ー]":"K", + "[a-zA-Za-zA-Z]":"A", + "[0-90-9]":"N" + } + this.chartype_ = []; + for (var i in patterns) { + var regexp = new RegExp(i); + this.chartype_.push([regexp, patterns[i]]); + } + + this.BIAS__ = -332 + this.BC1__ = {"HH":6,"II":2461,"KH":406,"OH":-1378}; + this.BC2__ = {"AA":-3267,"AI":2744,"AN":-878,"HH":-4070,"HM":-1711,"HN":4012,"HO":3761,"IA":1327,"IH":-1184,"II":-1332,"IK":1721,"IO":5492,"KI":3831,"KK":-8741,"MH":-3132,"MK":3334,"OO":-2920}; + this.BC3__ = {"HH":996,"HI":626,"HK":-721,"HN":-1307,"HO":-836,"IH":-301,"KK":2762,"MK":1079,"MM":4034,"OA":-1652,"OH":266}; + this.BP1__ = {"BB":295,"OB":304,"OO":-125,"UB":352}; + this.BP2__ = {"BO":60,"OO":-1762}; + this.BQ1__ = {"BHH":1150,"BHM":1521,"BII":-1158,"BIM":886,"BMH":1208,"BNH":449,"BOH":-91,"BOO":-2597,"OHI":451,"OIH":-296,"OKA":1851,"OKH":-1020,"OKK":904,"OOO":2965}; + this.BQ2__ = {"BHH":118,"BHI":-1159,"BHM":466,"BIH":-919,"BKK":-1720,"BKO":864,"OHH":-1139,"OHM":-181,"OIH":153,"UHI":-1146}; + this.BQ3__ = {"BHH":-792,"BHI":2664,"BII":-299,"BKI":419,"BMH":937,"BMM":8335,"BNN":998,"BOH":775,"OHH":2174,"OHM":439,"OII":280,"OKH":1798,"OKI":-793,"OKO":-2242,"OMH":-2402,"OOO":11699}; + this.BQ4__ = {"BHH":-3895,"BIH":3761,"BII":-4654,"BIK":1348,"BKK":-1806,"BMI":-3385,"BOO":-12396,"OAH":926,"OHH":266,"OHK":-2036,"ONN":-973}; + this.BW1__ = {",と":660,",同":727,"B1あ":1404,"B1同":542,"、と":660,"、同":727,"」と":1682,"あっ":1505,"いう":1743,"いっ":-2055,"いる":672,"うし":-4817,"うん":665,"から":3472,"がら":600,"こう":-790,"こと":2083,"こん":-1262,"さら":-4143,"さん":4573,"した":2641,"して":1104,"すで":-3399,"そこ":1977,"それ":-871,"たち":1122,"ため":601,"った":3463,"つい":-802,"てい":805,"てき":1249,"でき":1127,"です":3445,"では":844,"とい":-4915,"とみ":1922,"どこ":3887,"ない":5713,"なっ":3015,"など":7379,"なん":-1113,"にし":2468,"には":1498,"にも":1671,"に対":-912,"の一":-501,"の中":741,"ませ":2448,"まで":1711,"まま":2600,"まる":-2155,"やむ":-1947,"よっ":-2565,"れた":2369,"れで":-913,"をし":1860,"を見":731,"亡く":-1886,"京都":2558,"取り":-2784,"大き":-2604,"大阪":1497,"平方":-2314,"引き":-1336,"日本":-195,"本当":-2423,"毎日":-2113,"目指":-724,"B1あ":1404,"B1同":542,"」と":1682}; + this.BW2__ = {"..":-11822,"11":-669,"――":-5730,"−−":-13175,"いう":-1609,"うか":2490,"かし":-1350,"かも":-602,"から":-7194,"かれ":4612,"がい":853,"がら":-3198,"きた":1941,"くな":-1597,"こと":-8392,"この":-4193,"させ":4533,"され":13168,"さん":-3977,"しい":-1819,"しか":-545,"した":5078,"して":972,"しな":939,"その":-3744,"たい":-1253,"たた":-662,"ただ":-3857,"たち":-786,"たと":1224,"たは":-939,"った":4589,"って":1647,"っと":-2094,"てい":6144,"てき":3640,"てく":2551,"ては":-3110,"ても":-3065,"でい":2666,"でき":-1528,"でし":-3828,"です":-4761,"でも":-4203,"とい":1890,"とこ":-1746,"とと":-2279,"との":720,"とみ":5168,"とも":-3941,"ない":-2488,"なが":-1313,"など":-6509,"なの":2614,"なん":3099,"にお":-1615,"にし":2748,"にな":2454,"によ":-7236,"に対":-14943,"に従":-4688,"に関":-11388,"のか":2093,"ので":-7059,"のに":-6041,"のの":-6125,"はい":1073,"はが":-1033,"はず":-2532,"ばれ":1813,"まし":-1316,"まで":-6621,"まれ":5409,"めて":-3153,"もい":2230,"もの":-10713,"らか":-944,"らし":-1611,"らに":-1897,"りし":651,"りま":1620,"れた":4270,"れて":849,"れば":4114,"ろう":6067,"われ":7901,"を通":-11877,"んだ":728,"んな":-4115,"一人":602,"一方":-1375,"一日":970,"一部":-1051,"上が":-4479,"会社":-1116,"出て":2163,"分の":-7758,"同党":970,"同日":-913,"大阪":-2471,"委員":-1250,"少な":-1050,"年度":-8669,"年間":-1626,"府県":-2363,"手権":-1982,"新聞":-4066,"日新":-722,"日本":-7068,"日米":3372,"曜日":-601,"朝鮮":-2355,"本人":-2697,"東京":-1543,"然と":-1384,"社会":-1276,"立て":-990,"第に":-1612,"米国":-4268,"11":-669}; + this.BW3__ = {"あた":-2194,"あり":719,"ある":3846,"い.":-1185,"い。":-1185,"いい":5308,"いえ":2079,"いく":3029,"いた":2056,"いっ":1883,"いる":5600,"いわ":1527,"うち":1117,"うと":4798,"えと":1454,"か.":2857,"か。":2857,"かけ":-743,"かっ":-4098,"かに":-669,"から":6520,"かり":-2670,"が,":1816,"が、":1816,"がき":-4855,"がけ":-1127,"がっ":-913,"がら":-4977,"がり":-2064,"きた":1645,"けど":1374,"こと":7397,"この":1542,"ころ":-2757,"さい":-714,"さを":976,"し,":1557,"し、":1557,"しい":-3714,"した":3562,"して":1449,"しな":2608,"しま":1200,"す.":-1310,"す。":-1310,"する":6521,"ず,":3426,"ず、":3426,"ずに":841,"そう":428,"た.":8875,"た。":8875,"たい":-594,"たの":812,"たり":-1183,"たる":-853,"だ.":4098,"だ。":4098,"だっ":1004,"った":-4748,"って":300,"てい":6240,"てお":855,"ても":302,"です":1437,"でに":-1482,"では":2295,"とう":-1387,"とし":2266,"との":541,"とも":-3543,"どう":4664,"ない":1796,"なく":-903,"など":2135,"に,":-1021,"に、":-1021,"にし":1771,"にな":1906,"には":2644,"の,":-724,"の、":-724,"の子":-1000,"は,":1337,"は、":1337,"べき":2181,"まし":1113,"ます":6943,"まっ":-1549,"まで":6154,"まれ":-793,"らし":1479,"られ":6820,"るる":3818,"れ,":854,"れ、":854,"れた":1850,"れて":1375,"れば":-3246,"れる":1091,"われ":-605,"んだ":606,"んで":798,"カ月":990,"会議":860,"入り":1232,"大会":2217,"始め":1681,"市":965,"新聞":-5055,"日,":974,"日、":974,"社会":2024,"カ月":990}; + this.TC1__ = {"AAA":1093,"HHH":1029,"HHM":580,"HII":998,"HOH":-390,"HOM":-331,"IHI":1169,"IOH":-142,"IOI":-1015,"IOM":467,"MMH":187,"OOI":-1832}; + this.TC2__ = {"HHO":2088,"HII":-1023,"HMM":-1154,"IHI":-1965,"KKH":703,"OII":-2649}; + this.TC3__ = {"AAA":-294,"HHH":346,"HHI":-341,"HII":-1088,"HIK":731,"HOH":-1486,"IHH":128,"IHI":-3041,"IHO":-1935,"IIH":-825,"IIM":-1035,"IOI":-542,"KHH":-1216,"KKA":491,"KKH":-1217,"KOK":-1009,"MHH":-2694,"MHM":-457,"MHO":123,"MMH":-471,"NNH":-1689,"NNO":662,"OHO":-3393}; + this.TC4__ = {"HHH":-203,"HHI":1344,"HHK":365,"HHM":-122,"HHN":182,"HHO":669,"HIH":804,"HII":679,"HOH":446,"IHH":695,"IHO":-2324,"IIH":321,"III":1497,"IIO":656,"IOO":54,"KAK":4845,"KKA":3386,"KKK":3065,"MHH":-405,"MHI":201,"MMH":-241,"MMM":661,"MOM":841}; + this.TQ1__ = {"BHHH":-227,"BHHI":316,"BHIH":-132,"BIHH":60,"BIII":1595,"BNHH":-744,"BOHH":225,"BOOO":-908,"OAKK":482,"OHHH":281,"OHIH":249,"OIHI":200,"OIIH":-68}; + this.TQ2__ = {"BIHH":-1401,"BIII":-1033,"BKAK":-543,"BOOO":-5591}; + this.TQ3__ = {"BHHH":478,"BHHM":-1073,"BHIH":222,"BHII":-504,"BIIH":-116,"BIII":-105,"BMHI":-863,"BMHM":-464,"BOMH":620,"OHHH":346,"OHHI":1729,"OHII":997,"OHMH":481,"OIHH":623,"OIIH":1344,"OKAK":2792,"OKHH":587,"OKKA":679,"OOHH":110,"OOII":-685}; + this.TQ4__ = {"BHHH":-721,"BHHM":-3604,"BHII":-966,"BIIH":-607,"BIII":-2181,"OAAA":-2763,"OAKK":180,"OHHH":-294,"OHHI":2446,"OHHO":480,"OHIH":-1573,"OIHH":1935,"OIHI":-493,"OIIH":626,"OIII":-4007,"OKAK":-8156}; + this.TW1__ = {"につい":-4681,"東京都":2026}; + this.TW2__ = {"ある程":-2049,"いった":-1256,"ころが":-2434,"しょう":3873,"その後":-4430,"だって":-1049,"ていた":1833,"として":-4657,"ともに":-4517,"もので":1882,"一気に":-792,"初めて":-1512,"同時に":-8097,"大きな":-1255,"対して":-2721,"社会党":-3216}; + this.TW3__ = {"いただ":-1734,"してい":1314,"として":-4314,"につい":-5483,"にとっ":-5989,"に当た":-6247,"ので,":-727,"ので、":-727,"のもの":-600,"れから":-3752,"十二月":-2287}; + this.TW4__ = {"いう.":8576,"いう。":8576,"からな":-2348,"してい":2958,"たが,":1516,"たが、":1516,"ている":1538,"という":1349,"ました":5543,"ません":1097,"ようと":-4258,"よると":5865}; + this.UC1__ = {"A":484,"K":93,"M":645,"O":-505}; + this.UC2__ = {"A":819,"H":1059,"I":409,"M":3987,"N":5775,"O":646}; + this.UC3__ = {"A":-1370,"I":2311}; + this.UC4__ = {"A":-2643,"H":1809,"I":-1032,"K":-3450,"M":3565,"N":3876,"O":6646}; + this.UC5__ = {"H":313,"I":-1238,"K":-799,"M":539,"O":-831}; + this.UC6__ = {"H":-506,"I":-253,"K":87,"M":247,"O":-387}; + this.UP1__ = {"O":-214}; + this.UP2__ = {"B":69,"O":935}; + this.UP3__ = {"B":189}; + this.UQ1__ = {"BH":21,"BI":-12,"BK":-99,"BN":142,"BO":-56,"OH":-95,"OI":477,"OK":410,"OO":-2422}; + this.UQ2__ = {"BH":216,"BI":113,"OK":1759}; + this.UQ3__ = {"BA":-479,"BH":42,"BI":1913,"BK":-7198,"BM":3160,"BN":6427,"BO":14761,"OI":-827,"ON":-3212}; + this.UW1__ = {",":156,"、":156,"「":-463,"あ":-941,"う":-127,"が":-553,"き":121,"こ":505,"で":-201,"と":-547,"ど":-123,"に":-789,"の":-185,"は":-847,"も":-466,"や":-470,"よ":182,"ら":-292,"り":208,"れ":169,"を":-446,"ん":-137,"・":-135,"主":-402,"京":-268,"区":-912,"午":871,"国":-460,"大":561,"委":729,"市":-411,"日":-141,"理":361,"生":-408,"県":-386,"都":-718,"「":-463,"・":-135}; + this.UW2__ = {",":-829,"、":-829,"〇":892,"「":-645,"」":3145,"あ":-538,"い":505,"う":134,"お":-502,"か":1454,"が":-856,"く":-412,"こ":1141,"さ":878,"ざ":540,"し":1529,"す":-675,"せ":300,"そ":-1011,"た":188,"だ":1837,"つ":-949,"て":-291,"で":-268,"と":-981,"ど":1273,"な":1063,"に":-1764,"の":130,"は":-409,"ひ":-1273,"べ":1261,"ま":600,"も":-1263,"や":-402,"よ":1639,"り":-579,"る":-694,"れ":571,"を":-2516,"ん":2095,"ア":-587,"カ":306,"キ":568,"ッ":831,"三":-758,"不":-2150,"世":-302,"中":-968,"主":-861,"事":492,"人":-123,"会":978,"保":362,"入":548,"初":-3025,"副":-1566,"北":-3414,"区":-422,"大":-1769,"天":-865,"太":-483,"子":-1519,"学":760,"実":1023,"小":-2009,"市":-813,"年":-1060,"強":1067,"手":-1519,"揺":-1033,"政":1522,"文":-1355,"新":-1682,"日":-1815,"明":-1462,"最":-630,"朝":-1843,"本":-1650,"東":-931,"果":-665,"次":-2378,"民":-180,"気":-1740,"理":752,"発":529,"目":-1584,"相":-242,"県":-1165,"立":-763,"第":810,"米":509,"自":-1353,"行":838,"西":-744,"見":-3874,"調":1010,"議":1198,"込":3041,"開":1758,"間":-1257,"「":-645,"」":3145,"ッ":831,"ア":-587,"カ":306,"キ":568}; + this.UW3__ = {",":4889,"1":-800,"−":-1723,"、":4889,"々":-2311,"〇":5827,"」":2670,"〓":-3573,"あ":-2696,"い":1006,"う":2342,"え":1983,"お":-4864,"か":-1163,"が":3271,"く":1004,"け":388,"げ":401,"こ":-3552,"ご":-3116,"さ":-1058,"し":-395,"す":584,"せ":3685,"そ":-5228,"た":842,"ち":-521,"っ":-1444,"つ":-1081,"て":6167,"で":2318,"と":1691,"ど":-899,"な":-2788,"に":2745,"の":4056,"は":4555,"ひ":-2171,"ふ":-1798,"へ":1199,"ほ":-5516,"ま":-4384,"み":-120,"め":1205,"も":2323,"や":-788,"よ":-202,"ら":727,"り":649,"る":5905,"れ":2773,"わ":-1207,"を":6620,"ん":-518,"ア":551,"グ":1319,"ス":874,"ッ":-1350,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278,"・":-3794,"一":-1619,"下":-1759,"世":-2087,"両":3815,"中":653,"主":-758,"予":-1193,"二":974,"人":2742,"今":792,"他":1889,"以":-1368,"低":811,"何":4265,"作":-361,"保":-2439,"元":4858,"党":3593,"全":1574,"公":-3030,"六":755,"共":-1880,"円":5807,"再":3095,"分":457,"初":2475,"別":1129,"前":2286,"副":4437,"力":365,"動":-949,"務":-1872,"化":1327,"北":-1038,"区":4646,"千":-2309,"午":-783,"協":-1006,"口":483,"右":1233,"各":3588,"合":-241,"同":3906,"和":-837,"員":4513,"国":642,"型":1389,"場":1219,"外":-241,"妻":2016,"学":-1356,"安":-423,"実":-1008,"家":1078,"小":-513,"少":-3102,"州":1155,"市":3197,"平":-1804,"年":2416,"広":-1030,"府":1605,"度":1452,"建":-2352,"当":-3885,"得":1905,"思":-1291,"性":1822,"戸":-488,"指":-3973,"政":-2013,"教":-1479,"数":3222,"文":-1489,"新":1764,"日":2099,"旧":5792,"昨":-661,"時":-1248,"曜":-951,"最":-937,"月":4125,"期":360,"李":3094,"村":364,"東":-805,"核":5156,"森":2438,"業":484,"氏":2613,"民":-1694,"決":-1073,"法":1868,"海":-495,"無":979,"物":461,"特":-3850,"生":-273,"用":914,"町":1215,"的":7313,"直":-1835,"省":792,"県":6293,"知":-1528,"私":4231,"税":401,"立":-960,"第":1201,"米":7767,"系":3066,"約":3663,"級":1384,"統":-4229,"総":1163,"線":1255,"者":6457,"能":725,"自":-2869,"英":785,"見":1044,"調":-562,"財":-733,"費":1777,"車":1835,"軍":1375,"込":-1504,"通":-1136,"選":-681,"郎":1026,"郡":4404,"部":1200,"金":2163,"長":421,"開":-1432,"間":1302,"関":-1282,"雨":2009,"電":-1045,"非":2066,"駅":1620,"1":-800,"」":2670,"・":-3794,"ッ":-1350,"ア":551,"グ":1319,"ス":874,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278}; + this.UW4__ = {",":3930,".":3508,"―":-4841,"、":3930,"。":3508,"〇":4999,"「":1895,"」":3798,"〓":-5156,"あ":4752,"い":-3435,"う":-640,"え":-2514,"お":2405,"か":530,"が":6006,"き":-4482,"ぎ":-3821,"く":-3788,"け":-4376,"げ":-4734,"こ":2255,"ご":1979,"さ":2864,"し":-843,"じ":-2506,"す":-731,"ず":1251,"せ":181,"そ":4091,"た":5034,"だ":5408,"ち":-3654,"っ":-5882,"つ":-1659,"て":3994,"で":7410,"と":4547,"な":5433,"に":6499,"ぬ":1853,"ね":1413,"の":7396,"は":8578,"ば":1940,"ひ":4249,"び":-4134,"ふ":1345,"へ":6665,"べ":-744,"ほ":1464,"ま":1051,"み":-2082,"む":-882,"め":-5046,"も":4169,"ゃ":-2666,"や":2795,"ょ":-1544,"よ":3351,"ら":-2922,"り":-9726,"る":-14896,"れ":-2613,"ろ":-4570,"わ":-1783,"を":13150,"ん":-2352,"カ":2145,"コ":1789,"セ":1287,"ッ":-724,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637,"・":-4371,"ー":-11870,"一":-2069,"中":2210,"予":782,"事":-190,"井":-1768,"人":1036,"以":544,"会":950,"体":-1286,"作":530,"側":4292,"先":601,"党":-2006,"共":-1212,"内":584,"円":788,"初":1347,"前":1623,"副":3879,"力":-302,"動":-740,"務":-2715,"化":776,"区":4517,"協":1013,"参":1555,"合":-1834,"和":-681,"員":-910,"器":-851,"回":1500,"国":-619,"園":-1200,"地":866,"場":-1410,"塁":-2094,"士":-1413,"多":1067,"大":571,"子":-4802,"学":-1397,"定":-1057,"寺":-809,"小":1910,"屋":-1328,"山":-1500,"島":-2056,"川":-2667,"市":2771,"年":374,"庁":-4556,"後":456,"性":553,"感":916,"所":-1566,"支":856,"改":787,"政":2182,"教":704,"文":522,"方":-856,"日":1798,"時":1829,"最":845,"月":-9066,"木":-485,"来":-442,"校":-360,"業":-1043,"氏":5388,"民":-2716,"気":-910,"沢":-939,"済":-543,"物":-735,"率":672,"球":-1267,"生":-1286,"産":-1101,"田":-2900,"町":1826,"的":2586,"目":922,"省":-3485,"県":2997,"空":-867,"立":-2112,"第":788,"米":2937,"系":786,"約":2171,"経":1146,"統":-1169,"総":940,"線":-994,"署":749,"者":2145,"能":-730,"般":-852,"行":-792,"規":792,"警":-1184,"議":-244,"谷":-1000,"賞":730,"車":-1481,"軍":1158,"輪":-1433,"込":-3370,"近":929,"道":-1291,"選":2596,"郎":-4866,"都":1192,"野":-1100,"銀":-2213,"長":357,"間":-2344,"院":-2297,"際":-2604,"電":-878,"領":-1659,"題":-792,"館":-1984,"首":1749,"高":2120,"「":1895,"」":3798,"・":-4371,"ッ":-724,"ー":-11870,"カ":2145,"コ":1789,"セ":1287,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637}; + this.UW5__ = {",":465,".":-299,"1":-514,"E2":-32768,"]":-2762,"、":465,"。":-299,"「":363,"あ":1655,"い":331,"う":-503,"え":1199,"お":527,"か":647,"が":-421,"き":1624,"ぎ":1971,"く":312,"げ":-983,"さ":-1537,"し":-1371,"す":-852,"だ":-1186,"ち":1093,"っ":52,"つ":921,"て":-18,"で":-850,"と":-127,"ど":1682,"な":-787,"に":-1224,"の":-635,"は":-578,"べ":1001,"み":502,"め":865,"ゃ":3350,"ょ":854,"り":-208,"る":429,"れ":504,"わ":419,"を":-1264,"ん":327,"イ":241,"ル":451,"ン":-343,"中":-871,"京":722,"会":-1153,"党":-654,"務":3519,"区":-901,"告":848,"員":2104,"大":-1296,"学":-548,"定":1785,"嵐":-1304,"市":-2991,"席":921,"年":1763,"思":872,"所":-814,"挙":1618,"新":-1682,"日":218,"月":-4353,"査":932,"格":1356,"機":-1508,"氏":-1347,"田":240,"町":-3912,"的":-3149,"相":1319,"省":-1052,"県":-4003,"研":-997,"社":-278,"空":-813,"統":1955,"者":-2233,"表":663,"語":-1073,"議":1219,"選":-1018,"郎":-368,"長":786,"間":1191,"題":2368,"館":-689,"1":-514,"E2":-32768,"「":363,"イ":241,"ル":451,"ン":-343}; + this.UW6__ = {",":227,".":808,"1":-270,"E1":306,"、":227,"。":808,"あ":-307,"う":189,"か":241,"が":-73,"く":-121,"こ":-200,"じ":1782,"す":383,"た":-428,"っ":573,"て":-1014,"で":101,"と":-105,"な":-253,"に":-149,"の":-417,"は":-236,"も":-206,"り":187,"る":-135,"を":195,"ル":-673,"ン":-496,"一":-277,"中":201,"件":-800,"会":624,"前":302,"区":1792,"員":-1212,"委":798,"学":-960,"市":887,"広":-695,"後":535,"業":-697,"相":753,"社":-507,"福":974,"空":-822,"者":1811,"連":463,"郎":1082,"1":-270,"E1":306,"ル":-673,"ン":-496}; + + return this; + } + TinySegmenter.prototype.ctype_ = function(str) { + for (var i in this.chartype_) { + if (str.match(this.chartype_[i][0])) { + return this.chartype_[i][1]; + } + } + return "O"; + } + + TinySegmenter.prototype.ts_ = function(v) { + if (v) { return v; } + return 0; + } + + TinySegmenter.prototype.segment = function(input) { + if (input == null || input == undefined || input == "") { + return []; + } + var result = []; + var seg = ["B3","B2","B1"]; + var ctype = ["O","O","O"]; + var o = input.split(""); + for (i = 0; i < o.length; ++i) { + seg.push(o[i]); + ctype.push(this.ctype_(o[i])) + } + seg.push("E1"); + seg.push("E2"); + seg.push("E3"); + ctype.push("O"); + ctype.push("O"); + ctype.push("O"); + var word = seg[3]; + var p1 = "U"; + var p2 = "U"; + var p3 = "U"; + for (var i = 4; i < seg.length - 3; ++i) { + var score = this.BIAS__; + var w1 = seg[i-3]; + var w2 = seg[i-2]; + var w3 = seg[i-1]; + var w4 = seg[i]; + var w5 = seg[i+1]; + var w6 = seg[i+2]; + var c1 = ctype[i-3]; + var c2 = ctype[i-2]; + var c3 = ctype[i-1]; + var c4 = ctype[i]; + var c5 = ctype[i+1]; + var c6 = ctype[i+2]; + score += this.ts_(this.UP1__[p1]); + score += this.ts_(this.UP2__[p2]); + score += this.ts_(this.UP3__[p3]); + score += this.ts_(this.BP1__[p1 + p2]); + score += this.ts_(this.BP2__[p2 + p3]); + score += this.ts_(this.UW1__[w1]); + score += this.ts_(this.UW2__[w2]); + score += this.ts_(this.UW3__[w3]); + score += this.ts_(this.UW4__[w4]); + score += this.ts_(this.UW5__[w5]); + score += this.ts_(this.UW6__[w6]); + score += this.ts_(this.BW1__[w2 + w3]); + score += this.ts_(this.BW2__[w3 + w4]); + score += this.ts_(this.BW3__[w4 + w5]); + score += this.ts_(this.TW1__[w1 + w2 + w3]); + score += this.ts_(this.TW2__[w2 + w3 + w4]); + score += this.ts_(this.TW3__[w3 + w4 + w5]); + score += this.ts_(this.TW4__[w4 + w5 + w6]); + score += this.ts_(this.UC1__[c1]); + score += this.ts_(this.UC2__[c2]); + score += this.ts_(this.UC3__[c3]); + score += this.ts_(this.UC4__[c4]); + score += this.ts_(this.UC5__[c5]); + score += this.ts_(this.UC6__[c6]); + score += this.ts_(this.BC1__[c2 + c3]); + score += this.ts_(this.BC2__[c3 + c4]); + score += this.ts_(this.BC3__[c4 + c5]); + score += this.ts_(this.TC1__[c1 + c2 + c3]); + score += this.ts_(this.TC2__[c2 + c3 + c4]); + score += this.ts_(this.TC3__[c3 + c4 + c5]); + score += this.ts_(this.TC4__[c4 + c5 + c6]); + // score += this.ts_(this.TC5__[c4 + c5 + c6]); + score += this.ts_(this.UQ1__[p1 + c1]); + score += this.ts_(this.UQ2__[p2 + c2]); + score += this.ts_(this.UQ3__[p3 + c3]); + score += this.ts_(this.BQ1__[p2 + c2 + c3]); + score += this.ts_(this.BQ2__[p2 + c3 + c4]); + score += this.ts_(this.BQ3__[p3 + c2 + c3]); + score += this.ts_(this.BQ4__[p3 + c3 + c4]); + score += this.ts_(this.TQ1__[p2 + c1 + c2 + c3]); + score += this.ts_(this.TQ2__[p2 + c2 + c3 + c4]); + score += this.ts_(this.TQ3__[p3 + c1 + c2 + c3]); + score += this.ts_(this.TQ4__[p3 + c2 + c3 + c4]); + var p = "O"; + if (score > 0) { + result.push(word); + word = ""; + p = "B"; + } + p1 = p2; + p2 = p3; + p3 = p; + word += seg[i]; + } + result.push(word); + + return result; + } + + lunr.TinySegmenter = TinySegmenter; + }; + +})); \ No newline at end of file diff --git a/assets/javascripts/workers/search.fe42c31b.min.js b/assets/javascripts/workers/search.fe42c31b.min.js new file mode 100644 index 000000000..65bea7c7a --- /dev/null +++ b/assets/javascripts/workers/search.fe42c31b.min.js @@ -0,0 +1,61 @@ +(()=>{var le=Object.create,U=Object.defineProperty,he=Object.getPrototypeOf,de=Object.prototype.hasOwnProperty,fe=Object.getOwnPropertyNames,pe=Object.getOwnPropertyDescriptor;var ge=t=>U(t,"__esModule",{value:!0});var q=(t,e)=>()=>(e||(e={exports:{}},t(e.exports,e)),e.exports);var ye=(t,e,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of fe(e))!de.call(t,n)&&n!=="default"&&U(t,n,{get:()=>e[n],enumerable:!(r=pe(e,n))||r.enumerable});return t},Y=t=>ye(ge(U(t!=null?le(he(t)):{},"default",t&&t.__esModule&&"default"in t?{get:()=>t.default,enumerable:!0}:{value:t,enumerable:!0})),t);var z=(t,e,r)=>new Promise((n,i)=>{var s=u=>{try{a(r.next(u))}catch(c){i(c)}},o=u=>{try{a(r.throw(u))}catch(c){i(c)}},a=u=>u.done?n(u.value):Promise.resolve(u.value).then(s,o);a((r=r.apply(t,e)).next())});var X=q((G,J)=>{(function(){var t=function(e){var r=new t.Builder;return r.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),r.searchPipeline.add(t.stemmer),e.call(r,r),r.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(r){e.console&&console.warn&&console.warn(r)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var r=Object.create(null),n=Object.keys(e),i=0;i0){var d=t.utils.clone(r)||{};d.position=[a,c],d.index=s.length,s.push(new t.Token(n.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,r){r in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+r),e.label=r,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var r=e.label&&e.label in this.registeredFunctions;r||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. +`,e)},t.Pipeline.load=function(e){var r=new t.Pipeline;return e.forEach(function(n){var i=t.Pipeline.registeredFunctions[n];if(i)r.add(i);else throw new Error("Cannot load unregistered function: "+n)}),r},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(r){t.Pipeline.warnIfFunctionNotRegistered(r),this._stack.push(r)},this)},t.Pipeline.prototype.after=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");n=n+1,this._stack.splice(n,0,r)},t.Pipeline.prototype.before=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");this._stack.splice(n,0,r)},t.Pipeline.prototype.remove=function(e){var r=this._stack.indexOf(e);r!=-1&&this._stack.splice(r,1)},t.Pipeline.prototype.run=function(e){for(var r=this._stack.length,n=0;n1&&(oe&&(n=s),o!=e);)i=n-r,s=r+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ou?d+=2:a==u&&(r+=n[c+1]*i[d+1],c+=2,d+=2);return r},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),r=1,n=0;r0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}if(s.str.length==0&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}s.str.length==1&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),y=s.str.charAt(1),p;y in s.node.edges?p=s.node.edges[y]:(p=new t.TokenSet,s.node.edges[y]=p),s.str.length==1&&(p.final=!0),i.push({node:p,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return n},t.TokenSet.fromString=function(e){for(var r=new t.TokenSet,n=r,i=0,s=e.length;i=e;r--){var n=this.uncheckedNodes[r],i=n.child.toString();i in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[i]:(n.child._str=i,this.minimizedNodes[i]=n.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof G=="object"?J.exports=r():e.lunr=r()}(this,function(){return t})})()});var K=q((we,Z)=>{"use strict";var me=/["'&<>]/;Z.exports=ve;function ve(t){var e=""+t,r=me.exec(e);if(!r)return e;var n,i="",s=0,o=0;for(s=r.index;s`${i}${s}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${t.separator})(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(e,"|")})`,"img");return s=>s.replace(i,r).replace(/<\/mark>(\s+)]*>/img,"$1")}}function ne(t){let e=new lunr.Query(["title","text"]);return new lunr.QueryParser(t,e).parse(),e.clauses}function ie(t,e){let r=new Set(t),n={};for(let i=0;i!n.has(i)))]}var W=class{constructor({config:e,docs:r,pipeline:n,index:i}){this.documents=te(r),this.highlight=re(e),lunr.tokenizer.separator=new RegExp(e.separator),typeof i=="undefined"?this.index=lunr(function(){e.lang.length===1&&e.lang[0]!=="en"?this.use(lunr[e.lang[0]]):e.lang.length>1&&this.use(lunr.multiLanguage(...e.lang));let s=xe(["trimmer","stopWordFilter","stemmer"],n);for(let o of e.lang.map(a=>a==="en"?lunr:lunr[a]))for(let a of s)this.pipeline.remove(o[a]),this.searchPipeline.remove(o[a]);this.field("title",{boost:1e3}),this.field("text"),this.ref("location");for(let o of r)this.add(o)}):this.index=lunr.Index.load(i)}search(e){if(e)try{let r=this.highlight(e),n=ne(e).filter(s=>s.presence!==lunr.Query.presence.PROHIBITED);return[...this.index.search(`${e}*`).reduce((s,{ref:o,score:a,matchData:u})=>{let c=this.documents.get(o);if(typeof c!="undefined"){let{location:d,title:y,text:p,parent:b}=c,m=ie(n,Object.keys(u.metadata)),Q=+!b+ +Object.values(m).every(f=>f);s.push({location:d,title:r(y),text:r(p),score:a*(1+Q),terms:m})}return s},[]).sort((s,o)=>o.score-s.score).reduce((s,o)=>{let a=this.documents.get(o.location);if(typeof a!="undefined"){let u="parent"in a?a.parent.location:a.location;s.set(u,[...s.get(u)||[],o])}return s},new Map).values()]}catch(r){console.warn(`Invalid query: ${e} \u2013 see https://bit.ly/2s3ChXG`)}return[]}};var R;(function(t){t[t.SETUP=0]="SETUP",t[t.READY=1]="READY",t[t.QUERY=2]="QUERY",t[t.RESULT=3]="RESULT"})(R||(R={}));var H;function Se(t){return z(this,null,function*(){let e="../lunr";if(typeof parent!="undefined"&&"IFrameWorker"in parent){let n=document.querySelector("script[src]"),[i]=n.src.split("/worker");e=e.replace("..",i)}let r=[];for(let n of t.lang)n==="ja"&&r.push(`${e}/tinyseg.js`),n!=="en"&&r.push(`${e}/min/lunr.${n}.min.js`);t.lang.length>1&&r.push(`${e}/min/lunr.multi.min.js`),r.length&&(yield importScripts(`${e}/min/lunr.stemmer.support.min.js`,...r))})}function Qe(t){return z(this,null,function*(){switch(t.type){case R.SETUP:return yield Se(t.data.config),H=new W(t.data),{type:R.READY};case R.QUERY:return{type:R.RESULT,data:H?H.search(t.data):[]};default:throw new TypeError("Invalid message type")}})}self.lunr=se.default;addEventListener("message",t=>z(void 0,null,function*(){postMessage(yield Qe(t.data))}));})(); +/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */ +/*! + * lunr.Builder + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.Index + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.Pipeline + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.Set + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.TokenSet + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.Vector + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.stemmer + * Copyright (C) 2020 Oliver Nightingale + * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt + */ +/*! + * lunr.stopWordFilter + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.tokenizer + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.trimmer + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.utils + * Copyright (C) 2020 Oliver Nightingale + */ +/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 + * Copyright (C) 2020 Oliver Nightingale + * @license MIT + */ +//# sourceMappingURL=search.fe42c31b.min.js.map + diff --git a/assets/javascripts/workers/search.fe42c31b.min.js.map b/assets/javascripts/workers/search.fe42c31b.min.js.map new file mode 100644 index 000000000..3c9fcb619 --- /dev/null +++ b/assets/javascripts/workers/search.fe42c31b.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/lunr/lunr.js", "node_modules/escape-html/index.js", "src/assets/javascripts/integrations/search/worker/main/index.ts", "src/assets/javascripts/integrations/search/document/index.ts", "src/assets/javascripts/integrations/search/highlighter/index.ts", "src/assets/javascripts/integrations/search/query/_/index.ts", "src/assets/javascripts/integrations/search/_/index.ts", "src/assets/javascripts/integrations/search/worker/message/index.ts"], + "sourcesContent": ["/**\n * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9\n * Copyright (C) 2020 Oliver Nightingale\n * @license MIT\n */\n\n;(function(){\n\n/**\n * A convenience function for configuring and constructing\n * a new lunr Index.\n *\n * A lunr.Builder instance is created and the pipeline setup\n * with a trimmer, stop word filter and stemmer.\n *\n * This builder object is yielded to the configuration function\n * that is passed as a parameter, allowing the list of fields\n * and other builder parameters to be customised.\n *\n * All documents _must_ be added within the passed config function.\n *\n * @example\n * var idx = lunr(function () {\n * this.field('title')\n * this.field('body')\n * this.ref('id')\n *\n * documents.forEach(function (doc) {\n * this.add(doc)\n * }, this)\n * })\n *\n * @see {@link lunr.Builder}\n * @see {@link lunr.Pipeline}\n * @see {@link lunr.trimmer}\n * @see {@link lunr.stopWordFilter}\n * @see {@link lunr.stemmer}\n * @namespace {function} lunr\n */\nvar lunr = function (config) {\n var builder = new lunr.Builder\n\n builder.pipeline.add(\n lunr.trimmer,\n lunr.stopWordFilter,\n lunr.stemmer\n )\n\n builder.searchPipeline.add(\n lunr.stemmer\n )\n\n config.call(builder, builder)\n return builder.build()\n}\n\nlunr.version = \"2.3.9\"\n/*!\n * lunr.utils\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A namespace containing utils for the rest of the lunr library\n * @namespace lunr.utils\n */\nlunr.utils = {}\n\n/**\n * Print a warning message to the console.\n *\n * @param {String} message The message to be printed.\n * @memberOf lunr.utils\n * @function\n */\nlunr.utils.warn = (function (global) {\n /* eslint-disable no-console */\n return function (message) {\n if (global.console && console.warn) {\n console.warn(message)\n }\n }\n /* eslint-enable no-console */\n})(this)\n\n/**\n * Convert an object to a string.\n *\n * In the case of `null` and `undefined` the function returns\n * the empty string, in all other cases the result of calling\n * `toString` on the passed object is returned.\n *\n * @param {Any} obj The object to convert to a string.\n * @return {String} string representation of the passed object.\n * @memberOf lunr.utils\n */\nlunr.utils.asString = function (obj) {\n if (obj === void 0 || obj === null) {\n return \"\"\n } else {\n return obj.toString()\n }\n}\n\n/**\n * Clones an object.\n *\n * Will create a copy of an existing object such that any mutations\n * on the copy cannot affect the original.\n *\n * Only shallow objects are supported, passing a nested object to this\n * function will cause a TypeError.\n *\n * Objects with primitives, and arrays of primitives are supported.\n *\n * @param {Object} obj The object to clone.\n * @return {Object} a clone of the passed object.\n * @throws {TypeError} when a nested object is passed.\n * @memberOf Utils\n */\nlunr.utils.clone = function (obj) {\n if (obj === null || obj === undefined) {\n return obj\n }\n\n var clone = Object.create(null),\n keys = Object.keys(obj)\n\n for (var i = 0; i < keys.length; i++) {\n var key = keys[i],\n val = obj[key]\n\n if (Array.isArray(val)) {\n clone[key] = val.slice()\n continue\n }\n\n if (typeof val === 'string' ||\n typeof val === 'number' ||\n typeof val === 'boolean') {\n clone[key] = val\n continue\n }\n\n throw new TypeError(\"clone is not deep and does not support nested objects\")\n }\n\n return clone\n}\nlunr.FieldRef = function (docRef, fieldName, stringValue) {\n this.docRef = docRef\n this.fieldName = fieldName\n this._stringValue = stringValue\n}\n\nlunr.FieldRef.joiner = \"/\"\n\nlunr.FieldRef.fromString = function (s) {\n var n = s.indexOf(lunr.FieldRef.joiner)\n\n if (n === -1) {\n throw \"malformed field ref string\"\n }\n\n var fieldRef = s.slice(0, n),\n docRef = s.slice(n + 1)\n\n return new lunr.FieldRef (docRef, fieldRef, s)\n}\n\nlunr.FieldRef.prototype.toString = function () {\n if (this._stringValue == undefined) {\n this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef\n }\n\n return this._stringValue\n}\n/*!\n * lunr.Set\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A lunr set.\n *\n * @constructor\n */\nlunr.Set = function (elements) {\n this.elements = Object.create(null)\n\n if (elements) {\n this.length = elements.length\n\n for (var i = 0; i < this.length; i++) {\n this.elements[elements[i]] = true\n }\n } else {\n this.length = 0\n }\n}\n\n/**\n * A complete set that contains all elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.complete = {\n intersect: function (other) {\n return other\n },\n\n union: function () {\n return this\n },\n\n contains: function () {\n return true\n }\n}\n\n/**\n * An empty set that contains no elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.empty = {\n intersect: function () {\n return this\n },\n\n union: function (other) {\n return other\n },\n\n contains: function () {\n return false\n }\n}\n\n/**\n * Returns true if this set contains the specified object.\n *\n * @param {object} object - Object whose presence in this set is to be tested.\n * @returns {boolean} - True if this set contains the specified object.\n */\nlunr.Set.prototype.contains = function (object) {\n return !!this.elements[object]\n}\n\n/**\n * Returns a new set containing only the elements that are present in both\n * this set and the specified set.\n *\n * @param {lunr.Set} other - set to intersect with this set.\n * @returns {lunr.Set} a new set that is the intersection of this and the specified set.\n */\n\nlunr.Set.prototype.intersect = function (other) {\n var a, b, elements, intersection = []\n\n if (other === lunr.Set.complete) {\n return this\n }\n\n if (other === lunr.Set.empty) {\n return other\n }\n\n if (this.length < other.length) {\n a = this\n b = other\n } else {\n a = other\n b = this\n }\n\n elements = Object.keys(a.elements)\n\n for (var i = 0; i < elements.length; i++) {\n var element = elements[i]\n if (element in b.elements) {\n intersection.push(element)\n }\n }\n\n return new lunr.Set (intersection)\n}\n\n/**\n * Returns a new set combining the elements of this and the specified set.\n *\n * @param {lunr.Set} other - set to union with this set.\n * @return {lunr.Set} a new set that is the union of this and the specified set.\n */\n\nlunr.Set.prototype.union = function (other) {\n if (other === lunr.Set.complete) {\n return lunr.Set.complete\n }\n\n if (other === lunr.Set.empty) {\n return this\n }\n\n return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements)))\n}\n/**\n * A function to calculate the inverse document frequency for\n * a posting. This is shared between the builder and the index\n *\n * @private\n * @param {object} posting - The posting for a given term\n * @param {number} documentCount - The total number of documents.\n */\nlunr.idf = function (posting, documentCount) {\n var documentsWithTerm = 0\n\n for (var fieldName in posting) {\n if (fieldName == '_index') continue // Ignore the term index, its not a field\n documentsWithTerm += Object.keys(posting[fieldName]).length\n }\n\n var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5)\n\n return Math.log(1 + Math.abs(x))\n}\n\n/**\n * A token wraps a string representation of a token\n * as it is passed through the text processing pipeline.\n *\n * @constructor\n * @param {string} [str=''] - The string token being wrapped.\n * @param {object} [metadata={}] - Metadata associated with this token.\n */\nlunr.Token = function (str, metadata) {\n this.str = str || \"\"\n this.metadata = metadata || {}\n}\n\n/**\n * Returns the token string that is being wrapped by this object.\n *\n * @returns {string}\n */\nlunr.Token.prototype.toString = function () {\n return this.str\n}\n\n/**\n * A token update function is used when updating or optionally\n * when cloning a token.\n *\n * @callback lunr.Token~updateFunction\n * @param {string} str - The string representation of the token.\n * @param {Object} metadata - All metadata associated with this token.\n */\n\n/**\n * Applies the given function to the wrapped string token.\n *\n * @example\n * token.update(function (str, metadata) {\n * return str.toUpperCase()\n * })\n *\n * @param {lunr.Token~updateFunction} fn - A function to apply to the token string.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.update = function (fn) {\n this.str = fn(this.str, this.metadata)\n return this\n}\n\n/**\n * Creates a clone of this token. Optionally a function can be\n * applied to the cloned token.\n *\n * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.clone = function (fn) {\n fn = fn || function (s) { return s }\n return new lunr.Token (fn(this.str, this.metadata), this.metadata)\n}\n/*!\n * lunr.tokenizer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A function for splitting a string into tokens ready to be inserted into\n * the search index. Uses `lunr.tokenizer.separator` to split strings, change\n * the value of this property to change how strings are split into tokens.\n *\n * This tokenizer will convert its parameter to a string by calling `toString` and\n * then will split this string on the character in `lunr.tokenizer.separator`.\n * Arrays will have their elements converted to strings and wrapped in a lunr.Token.\n *\n * Optional metadata can be passed to the tokenizer, this metadata will be cloned and\n * added as metadata to every token that is created from the object to be tokenized.\n *\n * @static\n * @param {?(string|object|object[])} obj - The object to convert into tokens\n * @param {?object} metadata - Optional metadata to associate with every token\n * @returns {lunr.Token[]}\n * @see {@link lunr.Pipeline}\n */\nlunr.tokenizer = function (obj, metadata) {\n if (obj == null || obj == undefined) {\n return []\n }\n\n if (Array.isArray(obj)) {\n return obj.map(function (t) {\n return new lunr.Token(\n lunr.utils.asString(t).toLowerCase(),\n lunr.utils.clone(metadata)\n )\n })\n }\n\n var str = obj.toString().toLowerCase(),\n len = str.length,\n tokens = []\n\n for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) {\n var char = str.charAt(sliceEnd),\n sliceLength = sliceEnd - sliceStart\n\n if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) {\n\n if (sliceLength > 0) {\n var tokenMetadata = lunr.utils.clone(metadata) || {}\n tokenMetadata[\"position\"] = [sliceStart, sliceLength]\n tokenMetadata[\"index\"] = tokens.length\n\n tokens.push(\n new lunr.Token (\n str.slice(sliceStart, sliceEnd),\n tokenMetadata\n )\n )\n }\n\n sliceStart = sliceEnd + 1\n }\n\n }\n\n return tokens\n}\n\n/**\n * The separator used to split a string into tokens. Override this property to change the behaviour of\n * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens.\n *\n * @static\n * @see lunr.tokenizer\n */\nlunr.tokenizer.separator = /[\\s\\-]+/\n/*!\n * lunr.Pipeline\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Pipelines maintain an ordered list of functions to be applied to all\n * tokens in documents entering the search index and queries being ran against\n * the index.\n *\n * An instance of lunr.Index created with the lunr shortcut will contain a\n * pipeline with a stop word filter and an English language stemmer. Extra\n * functions can be added before or after either of these functions or these\n * default functions can be removed.\n *\n * When run the pipeline will call each function in turn, passing a token, the\n * index of that token in the original list of all tokens and finally a list of\n * all the original tokens.\n *\n * The output of functions in the pipeline will be passed to the next function\n * in the pipeline. To exclude a token from entering the index the function\n * should return undefined, the rest of the pipeline will not be called with\n * this token.\n *\n * For serialisation of pipelines to work, all functions used in an instance of\n * a pipeline should be registered with lunr.Pipeline. Registered functions can\n * then be loaded. If trying to load a serialised pipeline that uses functions\n * that are not registered an error will be thrown.\n *\n * If not planning on serialising the pipeline then registering pipeline functions\n * is not necessary.\n *\n * @constructor\n */\nlunr.Pipeline = function () {\n this._stack = []\n}\n\nlunr.Pipeline.registeredFunctions = Object.create(null)\n\n/**\n * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token\n * string as well as all known metadata. A pipeline function can mutate the token string\n * or mutate (or add) metadata for a given token.\n *\n * A pipeline function can indicate that the passed token should be discarded by returning\n * null, undefined or an empty string. This token will not be passed to any downstream pipeline\n * functions and will not be added to the index.\n *\n * Multiple tokens can be returned by returning an array of tokens. Each token will be passed\n * to any downstream pipeline functions and all will returned tokens will be added to the index.\n *\n * Any number of pipeline functions may be chained together using a lunr.Pipeline.\n *\n * @interface lunr.PipelineFunction\n * @param {lunr.Token} token - A token from the document being processed.\n * @param {number} i - The index of this token in the complete list of tokens for this document/field.\n * @param {lunr.Token[]} tokens - All tokens for this document/field.\n * @returns {(?lunr.Token|lunr.Token[])}\n */\n\n/**\n * Register a function with the pipeline.\n *\n * Functions that are used in the pipeline should be registered if the pipeline\n * needs to be serialised, or a serialised pipeline needs to be loaded.\n *\n * Registering a function does not add it to a pipeline, functions must still be\n * added to instances of the pipeline for them to be used when running a pipeline.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @param {String} label - The label to register this function with\n */\nlunr.Pipeline.registerFunction = function (fn, label) {\n if (label in this.registeredFunctions) {\n lunr.utils.warn('Overwriting existing registered function: ' + label)\n }\n\n fn.label = label\n lunr.Pipeline.registeredFunctions[fn.label] = fn\n}\n\n/**\n * Warns if the function is not registered as a Pipeline function.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @private\n */\nlunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {\n var isRegistered = fn.label && (fn.label in this.registeredFunctions)\n\n if (!isRegistered) {\n lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\\n', fn)\n }\n}\n\n/**\n * Loads a previously serialised pipeline.\n *\n * All functions to be loaded must already be registered with lunr.Pipeline.\n * If any function from the serialised data has not been registered then an\n * error will be thrown.\n *\n * @param {Object} serialised - The serialised pipeline to load.\n * @returns {lunr.Pipeline}\n */\nlunr.Pipeline.load = function (serialised) {\n var pipeline = new lunr.Pipeline\n\n serialised.forEach(function (fnName) {\n var fn = lunr.Pipeline.registeredFunctions[fnName]\n\n if (fn) {\n pipeline.add(fn)\n } else {\n throw new Error('Cannot load unregistered function: ' + fnName)\n }\n })\n\n return pipeline\n}\n\n/**\n * Adds new functions to the end of the pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline.\n */\nlunr.Pipeline.prototype.add = function () {\n var fns = Array.prototype.slice.call(arguments)\n\n fns.forEach(function (fn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n this._stack.push(fn)\n }, this)\n}\n\n/**\n * Adds a single function after a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.after = function (existingFn, newFn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n var pos = this._stack.indexOf(existingFn)\n if (pos == -1) {\n throw new Error('Cannot find existingFn')\n }\n\n pos = pos + 1\n this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Adds a single function before a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.before = function (existingFn, newFn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n var pos = this._stack.indexOf(existingFn)\n if (pos == -1) {\n throw new Error('Cannot find existingFn')\n }\n\n this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Removes a function from the pipeline.\n *\n * @param {lunr.PipelineFunction} fn The function to remove from the pipeline.\n */\nlunr.Pipeline.prototype.remove = function (fn) {\n var pos = this._stack.indexOf(fn)\n if (pos == -1) {\n return\n }\n\n this._stack.splice(pos, 1)\n}\n\n/**\n * Runs the current list of functions that make up the pipeline against the\n * passed tokens.\n *\n * @param {Array} tokens The tokens to run through the pipeline.\n * @returns {Array}\n */\nlunr.Pipeline.prototype.run = function (tokens) {\n var stackLength = this._stack.length\n\n for (var i = 0; i < stackLength; i++) {\n var fn = this._stack[i]\n var memo = []\n\n for (var j = 0; j < tokens.length; j++) {\n var result = fn(tokens[j], j, tokens)\n\n if (result === null || result === void 0 || result === '') continue\n\n if (Array.isArray(result)) {\n for (var k = 0; k < result.length; k++) {\n memo.push(result[k])\n }\n } else {\n memo.push(result)\n }\n }\n\n tokens = memo\n }\n\n return tokens\n}\n\n/**\n * Convenience method for passing a string through a pipeline and getting\n * strings out. This method takes care of wrapping the passed string in a\n * token and mapping the resulting tokens back to strings.\n *\n * @param {string} str - The string to pass through the pipeline.\n * @param {?object} metadata - Optional metadata to associate with the token\n * passed to the pipeline.\n * @returns {string[]}\n */\nlunr.Pipeline.prototype.runString = function (str, metadata) {\n var token = new lunr.Token (str, metadata)\n\n return this.run([token]).map(function (t) {\n return t.toString()\n })\n}\n\n/**\n * Resets the pipeline by removing any existing processors.\n *\n */\nlunr.Pipeline.prototype.reset = function () {\n this._stack = []\n}\n\n/**\n * Returns a representation of the pipeline ready for serialisation.\n *\n * Logs a warning if the function has not been registered.\n *\n * @returns {Array}\n */\nlunr.Pipeline.prototype.toJSON = function () {\n return this._stack.map(function (fn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n\n return fn.label\n })\n}\n/*!\n * lunr.Vector\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A vector is used to construct the vector space of documents and queries. These\n * vectors support operations to determine the similarity between two documents or\n * a document and a query.\n *\n * Normally no parameters are required for initializing a vector, but in the case of\n * loading a previously dumped vector the raw elements can be provided to the constructor.\n *\n * For performance reasons vectors are implemented with a flat array, where an elements\n * index is immediately followed by its value. E.g. [index, value, index, value]. This\n * allows the underlying array to be as sparse as possible and still offer decent\n * performance when being used for vector calculations.\n *\n * @constructor\n * @param {Number[]} [elements] - The flat list of element index and element value pairs.\n */\nlunr.Vector = function (elements) {\n this._magnitude = 0\n this.elements = elements || []\n}\n\n\n/**\n * Calculates the position within the vector to insert a given index.\n *\n * This is used internally by insert and upsert. If there are duplicate indexes then\n * the position is returned as if the value for that index were to be updated, but it\n * is the callers responsibility to check whether there is a duplicate at that index\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @returns {Number}\n */\nlunr.Vector.prototype.positionForIndex = function (index) {\n // For an empty vector the tuple can be inserted at the beginning\n if (this.elements.length == 0) {\n return 0\n }\n\n var start = 0,\n end = this.elements.length / 2,\n sliceLength = end - start,\n pivotPoint = Math.floor(sliceLength / 2),\n pivotIndex = this.elements[pivotPoint * 2]\n\n while (sliceLength > 1) {\n if (pivotIndex < index) {\n start = pivotPoint\n }\n\n if (pivotIndex > index) {\n end = pivotPoint\n }\n\n if (pivotIndex == index) {\n break\n }\n\n sliceLength = end - start\n pivotPoint = start + Math.floor(sliceLength / 2)\n pivotIndex = this.elements[pivotPoint * 2]\n }\n\n if (pivotIndex == index) {\n return pivotPoint * 2\n }\n\n if (pivotIndex > index) {\n return pivotPoint * 2\n }\n\n if (pivotIndex < index) {\n return (pivotPoint + 1) * 2\n }\n}\n\n/**\n * Inserts an element at an index within the vector.\n *\n * Does not allow duplicates, will throw an error if there is already an entry\n * for this index.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n */\nlunr.Vector.prototype.insert = function (insertIdx, val) {\n this.upsert(insertIdx, val, function () {\n throw \"duplicate index\"\n })\n}\n\n/**\n * Inserts or updates an existing index within the vector.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n * @param {function} fn - A function that is called for updates, the existing value and the\n * requested value are passed as arguments\n */\nlunr.Vector.prototype.upsert = function (insertIdx, val, fn) {\n this._magnitude = 0\n var position = this.positionForIndex(insertIdx)\n\n if (this.elements[position] == insertIdx) {\n this.elements[position + 1] = fn(this.elements[position + 1], val)\n } else {\n this.elements.splice(position, 0, insertIdx, val)\n }\n}\n\n/**\n * Calculates the magnitude of this vector.\n *\n * @returns {Number}\n */\nlunr.Vector.prototype.magnitude = function () {\n if (this._magnitude) return this._magnitude\n\n var sumOfSquares = 0,\n elementsLength = this.elements.length\n\n for (var i = 1; i < elementsLength; i += 2) {\n var val = this.elements[i]\n sumOfSquares += val * val\n }\n\n return this._magnitude = Math.sqrt(sumOfSquares)\n}\n\n/**\n * Calculates the dot product of this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The vector to compute the dot product with.\n * @returns {Number}\n */\nlunr.Vector.prototype.dot = function (otherVector) {\n var dotProduct = 0,\n a = this.elements, b = otherVector.elements,\n aLen = a.length, bLen = b.length,\n aVal = 0, bVal = 0,\n i = 0, j = 0\n\n while (i < aLen && j < bLen) {\n aVal = a[i], bVal = b[j]\n if (aVal < bVal) {\n i += 2\n } else if (aVal > bVal) {\n j += 2\n } else if (aVal == bVal) {\n dotProduct += a[i + 1] * b[j + 1]\n i += 2\n j += 2\n }\n }\n\n return dotProduct\n}\n\n/**\n * Calculates the similarity between this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The other vector to calculate the\n * similarity with.\n * @returns {Number}\n */\nlunr.Vector.prototype.similarity = function (otherVector) {\n return this.dot(otherVector) / this.magnitude() || 0\n}\n\n/**\n * Converts the vector to an array of the elements within the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toArray = function () {\n var output = new Array (this.elements.length / 2)\n\n for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) {\n output[j] = this.elements[i]\n }\n\n return output\n}\n\n/**\n * A JSON serializable representation of the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toJSON = function () {\n return this.elements\n}\n/* eslint-disable */\n/*!\n * lunr.stemmer\n * Copyright (C) 2020 Oliver Nightingale\n * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt\n */\n\n/**\n * lunr.stemmer is an english language stemmer, this is a JavaScript\n * implementation of the PorterStemmer taken from http://tartarus.org/~martin\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token - The string to stem\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n * @function\n */\nlunr.stemmer = (function(){\n var step2list = {\n \"ational\" : \"ate\",\n \"tional\" : \"tion\",\n \"enci\" : \"ence\",\n \"anci\" : \"ance\",\n \"izer\" : \"ize\",\n \"bli\" : \"ble\",\n \"alli\" : \"al\",\n \"entli\" : \"ent\",\n \"eli\" : \"e\",\n \"ousli\" : \"ous\",\n \"ization\" : \"ize\",\n \"ation\" : \"ate\",\n \"ator\" : \"ate\",\n \"alism\" : \"al\",\n \"iveness\" : \"ive\",\n \"fulness\" : \"ful\",\n \"ousness\" : \"ous\",\n \"aliti\" : \"al\",\n \"iviti\" : \"ive\",\n \"biliti\" : \"ble\",\n \"logi\" : \"log\"\n },\n\n step3list = {\n \"icate\" : \"ic\",\n \"ative\" : \"\",\n \"alize\" : \"al\",\n \"iciti\" : \"ic\",\n \"ical\" : \"ic\",\n \"ful\" : \"\",\n \"ness\" : \"\"\n },\n\n c = \"[^aeiou]\", // consonant\n v = \"[aeiouy]\", // vowel\n C = c + \"[^aeiouy]*\", // consonant sequence\n V = v + \"[aeiou]*\", // vowel sequence\n\n mgr0 = \"^(\" + C + \")?\" + V + C, // [C]VC... is m>0\n meq1 = \"^(\" + C + \")?\" + V + C + \"(\" + V + \")?$\", // [C]VC[V] is m=1\n mgr1 = \"^(\" + C + \")?\" + V + C + V + C, // [C]VCVC... is m>1\n s_v = \"^(\" + C + \")?\" + v; // vowel in stem\n\n var re_mgr0 = new RegExp(mgr0);\n var re_mgr1 = new RegExp(mgr1);\n var re_meq1 = new RegExp(meq1);\n var re_s_v = new RegExp(s_v);\n\n var re_1a = /^(.+?)(ss|i)es$/;\n var re2_1a = /^(.+?)([^s])s$/;\n var re_1b = /^(.+?)eed$/;\n var re2_1b = /^(.+?)(ed|ing)$/;\n var re_1b_2 = /.$/;\n var re2_1b_2 = /(at|bl|iz)$/;\n var re3_1b_2 = new RegExp(\"([^aeiouylsz])\\\\1$\");\n var re4_1b_2 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n var re_1c = /^(.+?[^aeiou])y$/;\n var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;\n\n var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;\n\n var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;\n var re2_4 = /^(.+?)(s|t)(ion)$/;\n\n var re_5 = /^(.+?)e$/;\n var re_5_1 = /ll$/;\n var re3_5 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n var porterStemmer = function porterStemmer(w) {\n var stem,\n suffix,\n firstch,\n re,\n re2,\n re3,\n re4;\n\n if (w.length < 3) { return w; }\n\n firstch = w.substr(0,1);\n if (firstch == \"y\") {\n w = firstch.toUpperCase() + w.substr(1);\n }\n\n // Step 1a\n re = re_1a\n re2 = re2_1a;\n\n if (re.test(w)) { w = w.replace(re,\"$1$2\"); }\n else if (re2.test(w)) { w = w.replace(re2,\"$1$2\"); }\n\n // Step 1b\n re = re_1b;\n re2 = re2_1b;\n if (re.test(w)) {\n var fp = re.exec(w);\n re = re_mgr0;\n if (re.test(fp[1])) {\n re = re_1b_2;\n w = w.replace(re,\"\");\n }\n } else if (re2.test(w)) {\n var fp = re2.exec(w);\n stem = fp[1];\n re2 = re_s_v;\n if (re2.test(stem)) {\n w = stem;\n re2 = re2_1b_2;\n re3 = re3_1b_2;\n re4 = re4_1b_2;\n if (re2.test(w)) { w = w + \"e\"; }\n else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,\"\"); }\n else if (re4.test(w)) { w = w + \"e\"; }\n }\n }\n\n // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say)\n re = re_1c;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n w = stem + \"i\";\n }\n\n // Step 2\n re = re_2;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n suffix = fp[2];\n re = re_mgr0;\n if (re.test(stem)) {\n w = stem + step2list[suffix];\n }\n }\n\n // Step 3\n re = re_3;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n suffix = fp[2];\n re = re_mgr0;\n if (re.test(stem)) {\n w = stem + step3list[suffix];\n }\n }\n\n // Step 4\n re = re_4;\n re2 = re2_4;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n re = re_mgr1;\n if (re.test(stem)) {\n w = stem;\n }\n } else if (re2.test(w)) {\n var fp = re2.exec(w);\n stem = fp[1] + fp[2];\n re2 = re_mgr1;\n if (re2.test(stem)) {\n w = stem;\n }\n }\n\n // Step 5\n re = re_5;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n re = re_mgr1;\n re2 = re_meq1;\n re3 = re3_5;\n if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {\n w = stem;\n }\n }\n\n re = re_5_1;\n re2 = re_mgr1;\n if (re.test(w) && re2.test(w)) {\n re = re_1b_2;\n w = w.replace(re,\"\");\n }\n\n // and turn initial Y back to y\n\n if (firstch == \"y\") {\n w = firstch.toLowerCase() + w.substr(1);\n }\n\n return w;\n };\n\n return function (token) {\n return token.update(porterStemmer);\n }\n})();\n\nlunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')\n/*!\n * lunr.stopWordFilter\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.generateStopWordFilter builds a stopWordFilter function from the provided\n * list of stop words.\n *\n * The built in lunr.stopWordFilter is built using this generator and can be used\n * to generate custom stopWordFilters for applications or non English languages.\n *\n * @function\n * @param {Array} token The token to pass through the filter\n * @returns {lunr.PipelineFunction}\n * @see lunr.Pipeline\n * @see lunr.stopWordFilter\n */\nlunr.generateStopWordFilter = function (stopWords) {\n var words = stopWords.reduce(function (memo, stopWord) {\n memo[stopWord] = stopWord\n return memo\n }, {})\n\n return function (token) {\n if (token && words[token.toString()] !== token.toString()) return token\n }\n}\n\n/**\n * lunr.stopWordFilter is an English language stop word list filter, any words\n * contained in the list will not be passed through the filter.\n *\n * This is intended to be used in the Pipeline. If the token does not pass the\n * filter then undefined will be returned.\n *\n * @function\n * @implements {lunr.PipelineFunction}\n * @params {lunr.Token} token - A token to check for being a stop word.\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n */\nlunr.stopWordFilter = lunr.generateStopWordFilter([\n 'a',\n 'able',\n 'about',\n 'across',\n 'after',\n 'all',\n 'almost',\n 'also',\n 'am',\n 'among',\n 'an',\n 'and',\n 'any',\n 'are',\n 'as',\n 'at',\n 'be',\n 'because',\n 'been',\n 'but',\n 'by',\n 'can',\n 'cannot',\n 'could',\n 'dear',\n 'did',\n 'do',\n 'does',\n 'either',\n 'else',\n 'ever',\n 'every',\n 'for',\n 'from',\n 'get',\n 'got',\n 'had',\n 'has',\n 'have',\n 'he',\n 'her',\n 'hers',\n 'him',\n 'his',\n 'how',\n 'however',\n 'i',\n 'if',\n 'in',\n 'into',\n 'is',\n 'it',\n 'its',\n 'just',\n 'least',\n 'let',\n 'like',\n 'likely',\n 'may',\n 'me',\n 'might',\n 'most',\n 'must',\n 'my',\n 'neither',\n 'no',\n 'nor',\n 'not',\n 'of',\n 'off',\n 'often',\n 'on',\n 'only',\n 'or',\n 'other',\n 'our',\n 'own',\n 'rather',\n 'said',\n 'say',\n 'says',\n 'she',\n 'should',\n 'since',\n 'so',\n 'some',\n 'than',\n 'that',\n 'the',\n 'their',\n 'them',\n 'then',\n 'there',\n 'these',\n 'they',\n 'this',\n 'tis',\n 'to',\n 'too',\n 'twas',\n 'us',\n 'wants',\n 'was',\n 'we',\n 'were',\n 'what',\n 'when',\n 'where',\n 'which',\n 'while',\n 'who',\n 'whom',\n 'why',\n 'will',\n 'with',\n 'would',\n 'yet',\n 'you',\n 'your'\n])\n\nlunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')\n/*!\n * lunr.trimmer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.trimmer is a pipeline function for trimming non word\n * characters from the beginning and end of tokens before they\n * enter the index.\n *\n * This implementation may not work correctly for non latin\n * characters and should either be removed or adapted for use\n * with languages with non-latin characters.\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token The token to pass through the filter\n * @returns {lunr.Token}\n * @see lunr.Pipeline\n */\nlunr.trimmer = function (token) {\n return token.update(function (s) {\n return s.replace(/^\\W+/, '').replace(/\\W+$/, '')\n })\n}\n\nlunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')\n/*!\n * lunr.TokenSet\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A token set is used to store the unique list of all tokens\n * within an index. Token sets are also used to represent an\n * incoming query to the index, this query token set and index\n * token set are then intersected to find which tokens to look\n * up in the inverted index.\n *\n * A token set can hold multiple tokens, as in the case of the\n * index token set, or it can hold a single token as in the\n * case of a simple query token set.\n *\n * Additionally token sets are used to perform wildcard matching.\n * Leading, contained and trailing wildcards are supported, and\n * from this edit distance matching can also be provided.\n *\n * Token sets are implemented as a minimal finite state automata,\n * where both common prefixes and suffixes are shared between tokens.\n * This helps to reduce the space used for storing the token set.\n *\n * @constructor\n */\nlunr.TokenSet = function () {\n this.final = false\n this.edges = {}\n this.id = lunr.TokenSet._nextId\n lunr.TokenSet._nextId += 1\n}\n\n/**\n * Keeps track of the next, auto increment, identifier to assign\n * to a new tokenSet.\n *\n * TokenSets require a unique identifier to be correctly minimised.\n *\n * @private\n */\nlunr.TokenSet._nextId = 1\n\n/**\n * Creates a TokenSet instance from the given sorted array of words.\n *\n * @param {String[]} arr - A sorted array of strings to create the set from.\n * @returns {lunr.TokenSet}\n * @throws Will throw an error if the input array is not sorted.\n */\nlunr.TokenSet.fromArray = function (arr) {\n var builder = new lunr.TokenSet.Builder\n\n for (var i = 0, len = arr.length; i < len; i++) {\n builder.insert(arr[i])\n }\n\n builder.finish()\n return builder.root\n}\n\n/**\n * Creates a token set from a query clause.\n *\n * @private\n * @param {Object} clause - A single clause from lunr.Query.\n * @param {string} clause.term - The query clause term.\n * @param {number} [clause.editDistance] - The optional edit distance for the term.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromClause = function (clause) {\n if ('editDistance' in clause) {\n return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance)\n } else {\n return lunr.TokenSet.fromString(clause.term)\n }\n}\n\n/**\n * Creates a token set representing a single string with a specified\n * edit distance.\n *\n * Insertions, deletions, substitutions and transpositions are each\n * treated as an edit distance of 1.\n *\n * Increasing the allowed edit distance will have a dramatic impact\n * on the performance of both creating and intersecting these TokenSets.\n * It is advised to keep the edit distance less than 3.\n *\n * @param {string} str - The string to create the token set from.\n * @param {number} editDistance - The allowed edit distance to match.\n * @returns {lunr.Vector}\n */\nlunr.TokenSet.fromFuzzyString = function (str, editDistance) {\n var root = new lunr.TokenSet\n\n var stack = [{\n node: root,\n editsRemaining: editDistance,\n str: str\n }]\n\n while (stack.length) {\n var frame = stack.pop()\n\n // no edit\n if (frame.str.length > 0) {\n var char = frame.str.charAt(0),\n noEditNode\n\n if (char in frame.node.edges) {\n noEditNode = frame.node.edges[char]\n } else {\n noEditNode = new lunr.TokenSet\n frame.node.edges[char] = noEditNode\n }\n\n if (frame.str.length == 1) {\n noEditNode.final = true\n }\n\n stack.push({\n node: noEditNode,\n editsRemaining: frame.editsRemaining,\n str: frame.str.slice(1)\n })\n }\n\n if (frame.editsRemaining == 0) {\n continue\n }\n\n // insertion\n if (\"*\" in frame.node.edges) {\n var insertionNode = frame.node.edges[\"*\"]\n } else {\n var insertionNode = new lunr.TokenSet\n frame.node.edges[\"*\"] = insertionNode\n }\n\n if (frame.str.length == 0) {\n insertionNode.final = true\n }\n\n stack.push({\n node: insertionNode,\n editsRemaining: frame.editsRemaining - 1,\n str: frame.str\n })\n\n // deletion\n // can only do a deletion if we have enough edits remaining\n // and if there are characters left to delete in the string\n if (frame.str.length > 1) {\n stack.push({\n node: frame.node,\n editsRemaining: frame.editsRemaining - 1,\n str: frame.str.slice(1)\n })\n }\n\n // deletion\n // just removing the last character from the str\n if (frame.str.length == 1) {\n frame.node.final = true\n }\n\n // substitution\n // can only do a substitution if we have enough edits remaining\n // and if there are characters left to substitute\n if (frame.str.length >= 1) {\n if (\"*\" in frame.node.edges) {\n var substitutionNode = frame.node.edges[\"*\"]\n } else {\n var substitutionNode = new lunr.TokenSet\n frame.node.edges[\"*\"] = substitutionNode\n }\n\n if (frame.str.length == 1) {\n substitutionNode.final = true\n }\n\n stack.push({\n node: substitutionNode,\n editsRemaining: frame.editsRemaining - 1,\n str: frame.str.slice(1)\n })\n }\n\n // transposition\n // can only do a transposition if there are edits remaining\n // and there are enough characters to transpose\n if (frame.str.length > 1) {\n var charA = frame.str.charAt(0),\n charB = frame.str.charAt(1),\n transposeNode\n\n if (charB in frame.node.edges) {\n transposeNode = frame.node.edges[charB]\n } else {\n transposeNode = new lunr.TokenSet\n frame.node.edges[charB] = transposeNode\n }\n\n if (frame.str.length == 1) {\n transposeNode.final = true\n }\n\n stack.push({\n node: transposeNode,\n editsRemaining: frame.editsRemaining - 1,\n str: charA + frame.str.slice(2)\n })\n }\n }\n\n return root\n}\n\n/**\n * Creates a TokenSet from a string.\n *\n * The string may contain one or more wildcard characters (*)\n * that will allow wildcard matching when intersecting with\n * another TokenSet.\n *\n * @param {string} str - The string to create a TokenSet from.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromString = function (str) {\n var node = new lunr.TokenSet,\n root = node\n\n /*\n * Iterates through all characters within the passed string\n * appending a node for each character.\n *\n * When a wildcard character is found then a self\n * referencing edge is introduced to continually match\n * any number of any characters.\n */\n for (var i = 0, len = str.length; i < len; i++) {\n var char = str[i],\n final = (i == len - 1)\n\n if (char == \"*\") {\n node.edges[char] = node\n node.final = final\n\n } else {\n var next = new lunr.TokenSet\n next.final = final\n\n node.edges[char] = next\n node = next\n }\n }\n\n return root\n}\n\n/**\n * Converts this TokenSet into an array of strings\n * contained within the TokenSet.\n *\n * This is not intended to be used on a TokenSet that\n * contains wildcards, in these cases the results are\n * undefined and are likely to cause an infinite loop.\n *\n * @returns {string[]}\n */\nlunr.TokenSet.prototype.toArray = function () {\n var words = []\n\n var stack = [{\n prefix: \"\",\n node: this\n }]\n\n while (stack.length) {\n var frame = stack.pop(),\n edges = Object.keys(frame.node.edges),\n len = edges.length\n\n if (frame.node.final) {\n /* In Safari, at this point the prefix is sometimes corrupted, see:\n * https://github.com/olivernn/lunr.js/issues/279 Calling any\n * String.prototype method forces Safari to \"cast\" this string to what\n * it's supposed to be, fixing the bug. */\n frame.prefix.charAt(0)\n words.push(frame.prefix)\n }\n\n for (var i = 0; i < len; i++) {\n var edge = edges[i]\n\n stack.push({\n prefix: frame.prefix.concat(edge),\n node: frame.node.edges[edge]\n })\n }\n }\n\n return words\n}\n\n/**\n * Generates a string representation of a TokenSet.\n *\n * This is intended to allow TokenSets to be used as keys\n * in objects, largely to aid the construction and minimisation\n * of a TokenSet. As such it is not designed to be a human\n * friendly representation of the TokenSet.\n *\n * @returns {string}\n */\nlunr.TokenSet.prototype.toString = function () {\n // NOTE: Using Object.keys here as this.edges is very likely\n // to enter 'hash-mode' with many keys being added\n //\n // avoiding a for-in loop here as it leads to the function\n // being de-optimised (at least in V8). From some simple\n // benchmarks the performance is comparable, but allowing\n // V8 to optimize may mean easy performance wins in the future.\n\n if (this._str) {\n return this._str\n }\n\n var str = this.final ? '1' : '0',\n labels = Object.keys(this.edges).sort(),\n len = labels.length\n\n for (var i = 0; i < len; i++) {\n var label = labels[i],\n node = this.edges[label]\n\n str = str + label + node.id\n }\n\n return str\n}\n\n/**\n * Returns a new TokenSet that is the intersection of\n * this TokenSet and the passed TokenSet.\n *\n * This intersection will take into account any wildcards\n * contained within the TokenSet.\n *\n * @param {lunr.TokenSet} b - An other TokenSet to intersect with.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.prototype.intersect = function (b) {\n var output = new lunr.TokenSet,\n frame = undefined\n\n var stack = [{\n qNode: b,\n output: output,\n node: this\n }]\n\n while (stack.length) {\n frame = stack.pop()\n\n // NOTE: As with the #toString method, we are using\n // Object.keys and a for loop instead of a for-in loop\n // as both of these objects enter 'hash' mode, causing\n // the function to be de-optimised in V8\n var qEdges = Object.keys(frame.qNode.edges),\n qLen = qEdges.length,\n nEdges = Object.keys(frame.node.edges),\n nLen = nEdges.length\n\n for (var q = 0; q < qLen; q++) {\n var qEdge = qEdges[q]\n\n for (var n = 0; n < nLen; n++) {\n var nEdge = nEdges[n]\n\n if (nEdge == qEdge || qEdge == '*') {\n var node = frame.node.edges[nEdge],\n qNode = frame.qNode.edges[qEdge],\n final = node.final && qNode.final,\n next = undefined\n\n if (nEdge in frame.output.edges) {\n // an edge already exists for this character\n // no need to create a new node, just set the finality\n // bit unless this node is already final\n next = frame.output.edges[nEdge]\n next.final = next.final || final\n\n } else {\n // no edge exists yet, must create one\n // set the finality bit and insert it\n // into the output\n next = new lunr.TokenSet\n next.final = final\n frame.output.edges[nEdge] = next\n }\n\n stack.push({\n qNode: qNode,\n output: next,\n node: node\n })\n }\n }\n }\n }\n\n return output\n}\nlunr.TokenSet.Builder = function () {\n this.previousWord = \"\"\n this.root = new lunr.TokenSet\n this.uncheckedNodes = []\n this.minimizedNodes = {}\n}\n\nlunr.TokenSet.Builder.prototype.insert = function (word) {\n var node,\n commonPrefix = 0\n\n if (word < this.previousWord) {\n throw new Error (\"Out of order word insertion\")\n }\n\n for (var i = 0; i < word.length && i < this.previousWord.length; i++) {\n if (word[i] != this.previousWord[i]) break\n commonPrefix++\n }\n\n this.minimize(commonPrefix)\n\n if (this.uncheckedNodes.length == 0) {\n node = this.root\n } else {\n node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child\n }\n\n for (var i = commonPrefix; i < word.length; i++) {\n var nextNode = new lunr.TokenSet,\n char = word[i]\n\n node.edges[char] = nextNode\n\n this.uncheckedNodes.push({\n parent: node,\n char: char,\n child: nextNode\n })\n\n node = nextNode\n }\n\n node.final = true\n this.previousWord = word\n}\n\nlunr.TokenSet.Builder.prototype.finish = function () {\n this.minimize(0)\n}\n\nlunr.TokenSet.Builder.prototype.minimize = function (downTo) {\n for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) {\n var node = this.uncheckedNodes[i],\n childKey = node.child.toString()\n\n if (childKey in this.minimizedNodes) {\n node.parent.edges[node.char] = this.minimizedNodes[childKey]\n } else {\n // Cache the key for this node since\n // we know it can't change anymore\n node.child._str = childKey\n\n this.minimizedNodes[childKey] = node.child\n }\n\n this.uncheckedNodes.pop()\n }\n}\n/*!\n * lunr.Index\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * An index contains the built index of all documents and provides a query interface\n * to the index.\n *\n * Usually instances of lunr.Index will not be created using this constructor, instead\n * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be\n * used to load previously built and serialized indexes.\n *\n * @constructor\n * @param {Object} attrs - The attributes of the built search index.\n * @param {Object} attrs.invertedIndex - An index of term/field to document reference.\n * @param {Object} attrs.fieldVectors - Field vectors\n * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens.\n * @param {string[]} attrs.fields - The names of indexed document fields.\n * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms.\n */\nlunr.Index = function (attrs) {\n this.invertedIndex = attrs.invertedIndex\n this.fieldVectors = attrs.fieldVectors\n this.tokenSet = attrs.tokenSet\n this.fields = attrs.fields\n this.pipeline = attrs.pipeline\n}\n\n/**\n * A result contains details of a document matching a search query.\n * @typedef {Object} lunr.Index~Result\n * @property {string} ref - The reference of the document this result represents.\n * @property {number} score - A number between 0 and 1 representing how similar this document is to the query.\n * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match.\n */\n\n/**\n * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple\n * query language which itself is parsed into an instance of lunr.Query.\n *\n * For programmatically building queries it is advised to directly use lunr.Query, the query language\n * is best used for human entered text rather than program generated text.\n *\n * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported\n * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello'\n * or 'world', though those that contain both will rank higher in the results.\n *\n * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can\n * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding\n * wildcards will increase the number of documents that will be found but can also have a negative\n * impact on query performance, especially with wildcards at the beginning of a term.\n *\n * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term\n * hello in the title field will match this query. Using a field not present in the index will lead\n * to an error being thrown.\n *\n * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term\n * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported\n * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2.\n * Avoid large values for edit distance to improve query performance.\n *\n * Each term also supports a presence modifier. By default a term's presence in document is optional, however\n * this can be changed to either required or prohibited. For a term's presence to be required in a document the\n * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and\n * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not\n * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'.\n *\n * To escape special characters the backslash character '\\' can be used, this allows searches to include\n * characters that would normally be considered modifiers, e.g. `foo\\~2` will search for a term \"foo~2\" instead\n * of attempting to apply a boost of 2 to the search term \"foo\".\n *\n * @typedef {string} lunr.Index~QueryString\n * @example Simple single term query\n * hello\n * @example Multiple term query\n * hello world\n * @example term scoped to a field\n * title:hello\n * @example term with a boost of 10\n * hello^10\n * @example term with an edit distance of 2\n * hello~2\n * @example terms with presence modifiers\n * -foo +bar baz\n */\n\n/**\n * Performs a search against the index using lunr query syntax.\n *\n * Results will be returned sorted by their score, the most relevant results\n * will be returned first. For details on how the score is calculated, please see\n * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}.\n *\n * For more programmatic querying use lunr.Index#query.\n *\n * @param {lunr.Index~QueryString} queryString - A string containing a lunr query.\n * @throws {lunr.QueryParseError} If the passed query string cannot be parsed.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.search = function (queryString) {\n return this.query(function (query) {\n var parser = new lunr.QueryParser(queryString, query)\n parser.parse()\n })\n}\n\n/**\n * A query builder callback provides a query object to be used to express\n * the query to perform on the index.\n *\n * @callback lunr.Index~queryBuilder\n * @param {lunr.Query} query - The query object to build up.\n * @this lunr.Query\n */\n\n/**\n * Performs a query against the index using the yielded lunr.Query object.\n *\n * If performing programmatic queries against the index, this method is preferred\n * over lunr.Index#search so as to avoid the additional query parsing overhead.\n *\n * A query object is yielded to the supplied function which should be used to\n * express the query to be run against the index.\n *\n * Note that although this function takes a callback parameter it is _not_ an\n * asynchronous operation, the callback is just yielded a query object to be\n * customized.\n *\n * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.query = function (fn) {\n // for each query clause\n // * process terms\n // * expand terms from token set\n // * find matching documents and metadata\n // * get document vectors\n // * score documents\n\n var query = new lunr.Query(this.fields),\n matchingFields = Object.create(null),\n queryVectors = Object.create(null),\n termFieldCache = Object.create(null),\n requiredMatches = Object.create(null),\n prohibitedMatches = Object.create(null)\n\n /*\n * To support field level boosts a query vector is created per\n * field. An empty vector is eagerly created to support negated\n * queries.\n */\n for (var i = 0; i < this.fields.length; i++) {\n queryVectors[this.fields[i]] = new lunr.Vector\n }\n\n fn.call(query, query)\n\n for (var i = 0; i < query.clauses.length; i++) {\n /*\n * Unless the pipeline has been disabled for this term, which is\n * the case for terms with wildcards, we need to pass the clause\n * term through the search pipeline. A pipeline returns an array\n * of processed terms. Pipeline functions may expand the passed\n * term, which means we may end up performing multiple index lookups\n * for a single query term.\n */\n var clause = query.clauses[i],\n terms = null,\n clauseMatches = lunr.Set.empty\n\n if (clause.usePipeline) {\n terms = this.pipeline.runString(clause.term, {\n fields: clause.fields\n })\n } else {\n terms = [clause.term]\n }\n\n for (var m = 0; m < terms.length; m++) {\n var term = terms[m]\n\n /*\n * Each term returned from the pipeline needs to use the same query\n * clause object, e.g. the same boost and or edit distance. The\n * simplest way to do this is to re-use the clause object but mutate\n * its term property.\n */\n clause.term = term\n\n /*\n * From the term in the clause we create a token set which will then\n * be used to intersect the indexes token set to get a list of terms\n * to lookup in the inverted index\n */\n var termTokenSet = lunr.TokenSet.fromClause(clause),\n expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()\n\n /*\n * If a term marked as required does not exist in the tokenSet it is\n * impossible for the search to return any matches. We set all the field\n * scoped required matches set to empty and stop examining any further\n * clauses.\n */\n if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) {\n for (var k = 0; k < clause.fields.length; k++) {\n var field = clause.fields[k]\n requiredMatches[field] = lunr.Set.empty\n }\n\n break\n }\n\n for (var j = 0; j < expandedTerms.length; j++) {\n /*\n * For each term get the posting and termIndex, this is required for\n * building the query vector.\n */\n var expandedTerm = expandedTerms[j],\n posting = this.invertedIndex[expandedTerm],\n termIndex = posting._index\n\n for (var k = 0; k < clause.fields.length; k++) {\n /*\n * For each field that this query term is scoped by (by default\n * all fields are in scope) we need to get all the document refs\n * that have this term in that field.\n *\n * The posting is the entry in the invertedIndex for the matching\n * term from above.\n */\n var field = clause.fields[k],\n fieldPosting = posting[field],\n matchingDocumentRefs = Object.keys(fieldPosting),\n termField = expandedTerm + \"/\" + field,\n matchingDocumentsSet = new lunr.Set(matchingDocumentRefs)\n\n /*\n * if the presence of this term is required ensure that the matching\n * documents are added to the set of required matches for this clause.\n *\n */\n if (clause.presence == lunr.Query.presence.REQUIRED) {\n clauseMatches = clauseMatches.union(matchingDocumentsSet)\n\n if (requiredMatches[field] === undefined) {\n requiredMatches[field] = lunr.Set.complete\n }\n }\n\n /*\n * if the presence of this term is prohibited ensure that the matching\n * documents are added to the set of prohibited matches for this field,\n * creating that set if it does not yet exist.\n */\n if (clause.presence == lunr.Query.presence.PROHIBITED) {\n if (prohibitedMatches[field] === undefined) {\n prohibitedMatches[field] = lunr.Set.empty\n }\n\n prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet)\n\n /*\n * Prohibited matches should not be part of the query vector used for\n * similarity scoring and no metadata should be extracted so we continue\n * to the next field\n */\n continue\n }\n\n /*\n * The query field vector is populated using the termIndex found for\n * the term and a unit value with the appropriate boost applied.\n * Using upsert because there could already be an entry in the vector\n * for the term we are working with. In that case we just add the scores\n * together.\n */\n queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b })\n\n /**\n * If we've already seen this term, field combo then we've already collected\n * the matching documents and metadata, no need to go through all that again\n */\n if (termFieldCache[termField]) {\n continue\n }\n\n for (var l = 0; l < matchingDocumentRefs.length; l++) {\n /*\n * All metadata for this term/field/document triple\n * are then extracted and collected into an instance\n * of lunr.MatchData ready to be returned in the query\n * results\n */\n var matchingDocumentRef = matchingDocumentRefs[l],\n matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field),\n metadata = fieldPosting[matchingDocumentRef],\n fieldMatch\n\n if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) {\n matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata)\n } else {\n fieldMatch.add(expandedTerm, field, metadata)\n }\n\n }\n\n termFieldCache[termField] = true\n }\n }\n }\n\n /**\n * If the presence was required we need to update the requiredMatches field sets.\n * We do this after all fields for the term have collected their matches because\n * the clause terms presence is required in _any_ of the fields not _all_ of the\n * fields.\n */\n if (clause.presence === lunr.Query.presence.REQUIRED) {\n for (var k = 0; k < clause.fields.length; k++) {\n var field = clause.fields[k]\n requiredMatches[field] = requiredMatches[field].intersect(clauseMatches)\n }\n }\n }\n\n /**\n * Need to combine the field scoped required and prohibited\n * matching documents into a global set of required and prohibited\n * matches\n */\n var allRequiredMatches = lunr.Set.complete,\n allProhibitedMatches = lunr.Set.empty\n\n for (var i = 0; i < this.fields.length; i++) {\n var field = this.fields[i]\n\n if (requiredMatches[field]) {\n allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field])\n }\n\n if (prohibitedMatches[field]) {\n allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field])\n }\n }\n\n var matchingFieldRefs = Object.keys(matchingFields),\n results = [],\n matches = Object.create(null)\n\n /*\n * If the query is negated (contains only prohibited terms)\n * we need to get _all_ fieldRefs currently existing in the\n * index. This is only done when we know that the query is\n * entirely prohibited terms to avoid any cost of getting all\n * fieldRefs unnecessarily.\n *\n * Additionally, blank MatchData must be created to correctly\n * populate the results.\n */\n if (query.isNegated()) {\n matchingFieldRefs = Object.keys(this.fieldVectors)\n\n for (var i = 0; i < matchingFieldRefs.length; i++) {\n var matchingFieldRef = matchingFieldRefs[i]\n var fieldRef = lunr.FieldRef.fromString(matchingFieldRef)\n matchingFields[matchingFieldRef] = new lunr.MatchData\n }\n }\n\n for (var i = 0; i < matchingFieldRefs.length; i++) {\n /*\n * Currently we have document fields that match the query, but we\n * need to return documents. The matchData and scores are combined\n * from multiple fields belonging to the same document.\n *\n * Scores are calculated by field, using the query vectors created\n * above, and combined into a final document score using addition.\n */\n var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),\n docRef = fieldRef.docRef\n\n if (!allRequiredMatches.contains(docRef)) {\n continue\n }\n\n if (allProhibitedMatches.contains(docRef)) {\n continue\n }\n\n var fieldVector = this.fieldVectors[fieldRef],\n score = queryVectors[fieldRef.fieldName].similarity(fieldVector),\n docMatch\n\n if ((docMatch = matches[docRef]) !== undefined) {\n docMatch.score += score\n docMatch.matchData.combine(matchingFields[fieldRef])\n } else {\n var match = {\n ref: docRef,\n score: score,\n matchData: matchingFields[fieldRef]\n }\n matches[docRef] = match\n results.push(match)\n }\n }\n\n /*\n * Sort the results objects by score, highest first.\n */\n return results.sort(function (a, b) {\n return b.score - a.score\n })\n}\n\n/**\n * Prepares the index for JSON serialization.\n *\n * The schema for this JSON blob will be described in a\n * separate JSON schema file.\n *\n * @returns {Object}\n */\nlunr.Index.prototype.toJSON = function () {\n var invertedIndex = Object.keys(this.invertedIndex)\n .sort()\n .map(function (term) {\n return [term, this.invertedIndex[term]]\n }, this)\n\n var fieldVectors = Object.keys(this.fieldVectors)\n .map(function (ref) {\n return [ref, this.fieldVectors[ref].toJSON()]\n }, this)\n\n return {\n version: lunr.version,\n fields: this.fields,\n fieldVectors: fieldVectors,\n invertedIndex: invertedIndex,\n pipeline: this.pipeline.toJSON()\n }\n}\n\n/**\n * Loads a previously serialized lunr.Index\n *\n * @param {Object} serializedIndex - A previously serialized lunr.Index\n * @returns {lunr.Index}\n */\nlunr.Index.load = function (serializedIndex) {\n var attrs = {},\n fieldVectors = {},\n serializedVectors = serializedIndex.fieldVectors,\n invertedIndex = Object.create(null),\n serializedInvertedIndex = serializedIndex.invertedIndex,\n tokenSetBuilder = new lunr.TokenSet.Builder,\n pipeline = lunr.Pipeline.load(serializedIndex.pipeline)\n\n if (serializedIndex.version != lunr.version) {\n lunr.utils.warn(\"Version mismatch when loading serialised index. Current version of lunr '\" + lunr.version + \"' does not match serialized index '\" + serializedIndex.version + \"'\")\n }\n\n for (var i = 0; i < serializedVectors.length; i++) {\n var tuple = serializedVectors[i],\n ref = tuple[0],\n elements = tuple[1]\n\n fieldVectors[ref] = new lunr.Vector(elements)\n }\n\n for (var i = 0; i < serializedInvertedIndex.length; i++) {\n var tuple = serializedInvertedIndex[i],\n term = tuple[0],\n posting = tuple[1]\n\n tokenSetBuilder.insert(term)\n invertedIndex[term] = posting\n }\n\n tokenSetBuilder.finish()\n\n attrs.fields = serializedIndex.fields\n\n attrs.fieldVectors = fieldVectors\n attrs.invertedIndex = invertedIndex\n attrs.tokenSet = tokenSetBuilder.root\n attrs.pipeline = pipeline\n\n return new lunr.Index(attrs)\n}\n/*!\n * lunr.Builder\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Builder performs indexing on a set of documents and\n * returns instances of lunr.Index ready for querying.\n *\n * All configuration of the index is done via the builder, the\n * fields to index, the document reference, the text processing\n * pipeline and document scoring parameters are all set on the\n * builder before indexing.\n *\n * @constructor\n * @property {string} _ref - Internal reference to the document reference field.\n * @property {string[]} _fields - Internal reference to the document fields to index.\n * @property {object} invertedIndex - The inverted index maps terms to document fields.\n * @property {object} documentTermFrequencies - Keeps track of document term frequencies.\n * @property {object} documentLengths - Keeps track of the length of documents added to the index.\n * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing.\n * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing.\n * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index.\n * @property {number} documentCount - Keeps track of the total number of documents indexed.\n * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75.\n * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2.\n * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space.\n * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index.\n */\nlunr.Builder = function () {\n this._ref = \"id\"\n this._fields = Object.create(null)\n this._documents = Object.create(null)\n this.invertedIndex = Object.create(null)\n this.fieldTermFrequencies = {}\n this.fieldLengths = {}\n this.tokenizer = lunr.tokenizer\n this.pipeline = new lunr.Pipeline\n this.searchPipeline = new lunr.Pipeline\n this.documentCount = 0\n this._b = 0.75\n this._k1 = 1.2\n this.termIndex = 0\n this.metadataWhitelist = []\n}\n\n/**\n * Sets the document field used as the document reference. Every document must have this field.\n * The type of this field in the document should be a string, if it is not a string it will be\n * coerced into a string by calling toString.\n *\n * The default ref is 'id'.\n *\n * The ref should _not_ be changed during indexing, it should be set before any documents are\n * added to the index. Changing it during indexing can lead to inconsistent results.\n *\n * @param {string} ref - The name of the reference field in the document.\n */\nlunr.Builder.prototype.ref = function (ref) {\n this._ref = ref\n}\n\n/**\n * A function that is used to extract a field from a document.\n *\n * Lunr expects a field to be at the top level of a document, if however the field\n * is deeply nested within a document an extractor function can be used to extract\n * the right field for indexing.\n *\n * @callback fieldExtractor\n * @param {object} doc - The document being added to the index.\n * @returns {?(string|object|object[])} obj - The object that will be indexed for this field.\n * @example Extracting a nested field\n * function (doc) { return doc.nested.field }\n */\n\n/**\n * Adds a field to the list of document fields that will be indexed. Every document being\n * indexed should have this field. Null values for this field in indexed documents will\n * not cause errors but will limit the chance of that document being retrieved by searches.\n *\n * All fields should be added before adding documents to the index. Adding fields after\n * a document has been indexed will have no effect on already indexed documents.\n *\n * Fields can be boosted at build time. This allows terms within that field to have more\n * importance when ranking search results. Use a field boost to specify that matches within\n * one field are more important than other fields.\n *\n * @param {string} fieldName - The name of a field to index in all documents.\n * @param {object} attributes - Optional attributes associated with this field.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this field.\n * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document.\n * @throws {RangeError} fieldName cannot contain unsupported characters '/'\n */\nlunr.Builder.prototype.field = function (fieldName, attributes) {\n if (/\\//.test(fieldName)) {\n throw new RangeError (\"Field '\" + fieldName + \"' contains illegal character '/'\")\n }\n\n this._fields[fieldName] = attributes || {}\n}\n\n/**\n * A parameter to tune the amount of field length normalisation that is applied when\n * calculating relevance scores. A value of 0 will completely disable any normalisation\n * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b\n * will be clamped to the range 0 - 1.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.b = function (number) {\n if (number < 0) {\n this._b = 0\n } else if (number > 1) {\n this._b = 1\n } else {\n this._b = number\n }\n}\n\n/**\n * A parameter that controls the speed at which a rise in term frequency results in term\n * frequency saturation. The default value is 1.2. Setting this to a higher value will give\n * slower saturation levels, a lower value will result in quicker saturation.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.k1 = function (number) {\n this._k1 = number\n}\n\n/**\n * Adds a document to the index.\n *\n * Before adding fields to the index the index should have been fully setup, with the document\n * ref and all fields to index already having been specified.\n *\n * The document must have a field name as specified by the ref (by default this is 'id') and\n * it should have all fields defined for indexing, though null or undefined values will not\n * cause errors.\n *\n * Entire documents can be boosted at build time. Applying a boost to a document indicates that\n * this document should rank higher in search results than other documents.\n *\n * @param {object} doc - The document to add to the index.\n * @param {object} attributes - Optional attributes associated with this document.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this document.\n */\nlunr.Builder.prototype.add = function (doc, attributes) {\n var docRef = doc[this._ref],\n fields = Object.keys(this._fields)\n\n this._documents[docRef] = attributes || {}\n this.documentCount += 1\n\n for (var i = 0; i < fields.length; i++) {\n var fieldName = fields[i],\n extractor = this._fields[fieldName].extractor,\n field = extractor ? extractor(doc) : doc[fieldName],\n tokens = this.tokenizer(field, {\n fields: [fieldName]\n }),\n terms = this.pipeline.run(tokens),\n fieldRef = new lunr.FieldRef (docRef, fieldName),\n fieldTerms = Object.create(null)\n\n this.fieldTermFrequencies[fieldRef] = fieldTerms\n this.fieldLengths[fieldRef] = 0\n\n // store the length of this field for this document\n this.fieldLengths[fieldRef] += terms.length\n\n // calculate term frequencies for this field\n for (var j = 0; j < terms.length; j++) {\n var term = terms[j]\n\n if (fieldTerms[term] == undefined) {\n fieldTerms[term] = 0\n }\n\n fieldTerms[term] += 1\n\n // add to inverted index\n // create an initial posting if one doesn't exist\n if (this.invertedIndex[term] == undefined) {\n var posting = Object.create(null)\n posting[\"_index\"] = this.termIndex\n this.termIndex += 1\n\n for (var k = 0; k < fields.length; k++) {\n posting[fields[k]] = Object.create(null)\n }\n\n this.invertedIndex[term] = posting\n }\n\n // add an entry for this term/fieldName/docRef to the invertedIndex\n if (this.invertedIndex[term][fieldName][docRef] == undefined) {\n this.invertedIndex[term][fieldName][docRef] = Object.create(null)\n }\n\n // store all whitelisted metadata about this token in the\n // inverted index\n for (var l = 0; l < this.metadataWhitelist.length; l++) {\n var metadataKey = this.metadataWhitelist[l],\n metadata = term.metadata[metadataKey]\n\n if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) {\n this.invertedIndex[term][fieldName][docRef][metadataKey] = []\n }\n\n this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata)\n }\n }\n\n }\n}\n\n/**\n * Calculates the average document length for this index\n *\n * @private\n */\nlunr.Builder.prototype.calculateAverageFieldLengths = function () {\n\n var fieldRefs = Object.keys(this.fieldLengths),\n numberOfFields = fieldRefs.length,\n accumulator = {},\n documentsWithField = {}\n\n for (var i = 0; i < numberOfFields; i++) {\n var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n field = fieldRef.fieldName\n\n documentsWithField[field] || (documentsWithField[field] = 0)\n documentsWithField[field] += 1\n\n accumulator[field] || (accumulator[field] = 0)\n accumulator[field] += this.fieldLengths[fieldRef]\n }\n\n var fields = Object.keys(this._fields)\n\n for (var i = 0; i < fields.length; i++) {\n var fieldName = fields[i]\n accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName]\n }\n\n this.averageFieldLength = accumulator\n}\n\n/**\n * Builds a vector space model of every document using lunr.Vector\n *\n * @private\n */\nlunr.Builder.prototype.createFieldVectors = function () {\n var fieldVectors = {},\n fieldRefs = Object.keys(this.fieldTermFrequencies),\n fieldRefsLength = fieldRefs.length,\n termIdfCache = Object.create(null)\n\n for (var i = 0; i < fieldRefsLength; i++) {\n var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n fieldName = fieldRef.fieldName,\n fieldLength = this.fieldLengths[fieldRef],\n fieldVector = new lunr.Vector,\n termFrequencies = this.fieldTermFrequencies[fieldRef],\n terms = Object.keys(termFrequencies),\n termsLength = terms.length\n\n\n var fieldBoost = this._fields[fieldName].boost || 1,\n docBoost = this._documents[fieldRef.docRef].boost || 1\n\n for (var j = 0; j < termsLength; j++) {\n var term = terms[j],\n tf = termFrequencies[term],\n termIndex = this.invertedIndex[term]._index,\n idf, score, scoreWithPrecision\n\n if (termIdfCache[term] === undefined) {\n idf = lunr.idf(this.invertedIndex[term], this.documentCount)\n termIdfCache[term] = idf\n } else {\n idf = termIdfCache[term]\n }\n\n score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf)\n score *= fieldBoost\n score *= docBoost\n scoreWithPrecision = Math.round(score * 1000) / 1000\n // Converts 1.23456789 to 1.234.\n // Reducing the precision so that the vectors take up less\n // space when serialised. Doing it now so that they behave\n // the same before and after serialisation. Also, this is\n // the fastest approach to reducing a number's precision in\n // JavaScript.\n\n fieldVector.insert(termIndex, scoreWithPrecision)\n }\n\n fieldVectors[fieldRef] = fieldVector\n }\n\n this.fieldVectors = fieldVectors\n}\n\n/**\n * Creates a token set of all tokens in the index using lunr.TokenSet\n *\n * @private\n */\nlunr.Builder.prototype.createTokenSet = function () {\n this.tokenSet = lunr.TokenSet.fromArray(\n Object.keys(this.invertedIndex).sort()\n )\n}\n\n/**\n * Builds the index, creating an instance of lunr.Index.\n *\n * This completes the indexing process and should only be called\n * once all documents have been added to the index.\n *\n * @returns {lunr.Index}\n */\nlunr.Builder.prototype.build = function () {\n this.calculateAverageFieldLengths()\n this.createFieldVectors()\n this.createTokenSet()\n\n return new lunr.Index({\n invertedIndex: this.invertedIndex,\n fieldVectors: this.fieldVectors,\n tokenSet: this.tokenSet,\n fields: Object.keys(this._fields),\n pipeline: this.searchPipeline\n })\n}\n\n/**\n * Applies a plugin to the index builder.\n *\n * A plugin is a function that is called with the index builder as its context.\n * Plugins can be used to customise or extend the behaviour of the index\n * in some way. A plugin is just a function, that encapsulated the custom\n * behaviour that should be applied when building the index.\n *\n * The plugin function will be called with the index builder as its argument, additional\n * arguments can also be passed when calling use. The function will be called\n * with the index builder as its context.\n *\n * @param {Function} plugin The plugin to apply.\n */\nlunr.Builder.prototype.use = function (fn) {\n var args = Array.prototype.slice.call(arguments, 1)\n args.unshift(this)\n fn.apply(this, args)\n}\n/**\n * Contains and collects metadata about a matching document.\n * A single instance of lunr.MatchData is returned as part of every\n * lunr.Index~Result.\n *\n * @constructor\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n * @property {object} metadata - A cloned collection of metadata associated with this document.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData = function (term, field, metadata) {\n var clonedMetadata = Object.create(null),\n metadataKeys = Object.keys(metadata || {})\n\n // Cloning the metadata to prevent the original\n // being mutated during match data combination.\n // Metadata is kept in an array within the inverted\n // index so cloning the data can be done with\n // Array#slice\n for (var i = 0; i < metadataKeys.length; i++) {\n var key = metadataKeys[i]\n clonedMetadata[key] = metadata[key].slice()\n }\n\n this.metadata = Object.create(null)\n\n if (term !== undefined) {\n this.metadata[term] = Object.create(null)\n this.metadata[term][field] = clonedMetadata\n }\n}\n\n/**\n * An instance of lunr.MatchData will be created for every term that matches a\n * document. However only one instance is required in a lunr.Index~Result. This\n * method combines metadata from another instance of lunr.MatchData with this\n * objects metadata.\n *\n * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData.prototype.combine = function (otherMatchData) {\n var terms = Object.keys(otherMatchData.metadata)\n\n for (var i = 0; i < terms.length; i++) {\n var term = terms[i],\n fields = Object.keys(otherMatchData.metadata[term])\n\n if (this.metadata[term] == undefined) {\n this.metadata[term] = Object.create(null)\n }\n\n for (var j = 0; j < fields.length; j++) {\n var field = fields[j],\n keys = Object.keys(otherMatchData.metadata[term][field])\n\n if (this.metadata[term][field] == undefined) {\n this.metadata[term][field] = Object.create(null)\n }\n\n for (var k = 0; k < keys.length; k++) {\n var key = keys[k]\n\n if (this.metadata[term][field][key] == undefined) {\n this.metadata[term][field][key] = otherMatchData.metadata[term][field][key]\n } else {\n this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key])\n }\n\n }\n }\n }\n}\n\n/**\n * Add metadata for a term/field pair to this instance of match data.\n *\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n */\nlunr.MatchData.prototype.add = function (term, field, metadata) {\n if (!(term in this.metadata)) {\n this.metadata[term] = Object.create(null)\n this.metadata[term][field] = metadata\n return\n }\n\n if (!(field in this.metadata[term])) {\n this.metadata[term][field] = metadata\n return\n }\n\n var metadataKeys = Object.keys(metadata)\n\n for (var i = 0; i < metadataKeys.length; i++) {\n var key = metadataKeys[i]\n\n if (key in this.metadata[term][field]) {\n this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key])\n } else {\n this.metadata[term][field][key] = metadata[key]\n }\n }\n}\n/**\n * A lunr.Query provides a programmatic way of defining queries to be performed\n * against a {@link lunr.Index}.\n *\n * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method\n * so the query object is pre-initialized with the right index fields.\n *\n * @constructor\n * @property {lunr.Query~Clause[]} clauses - An array of query clauses.\n * @property {string[]} allFields - An array of all available fields in a lunr.Index.\n */\nlunr.Query = function (allFields) {\n this.clauses = []\n this.allFields = allFields\n}\n\n/**\n * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause.\n *\n * This allows wildcards to be added to the beginning and end of a term without having to manually do any string\n * concatenation.\n *\n * The wildcard constants can be bitwise combined to select both leading and trailing wildcards.\n *\n * @constant\n * @default\n * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour\n * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists\n * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example query term with trailing wildcard\n * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING })\n * @example query term with leading and trailing wildcard\n * query.term('foo', {\n * wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING\n * })\n */\n\nlunr.Query.wildcard = new String (\"*\")\nlunr.Query.wildcard.NONE = 0\nlunr.Query.wildcard.LEADING = 1\nlunr.Query.wildcard.TRAILING = 2\n\n/**\n * Constants for indicating what kind of presence a term must have in matching documents.\n *\n * @constant\n * @enum {number}\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example query term with required presence\n * query.term('foo', { presence: lunr.Query.presence.REQUIRED })\n */\nlunr.Query.presence = {\n /**\n * Term's presence in a document is optional, this is the default value.\n */\n OPTIONAL: 1,\n\n /**\n * Term's presence in a document is required, documents that do not contain\n * this term will not be returned.\n */\n REQUIRED: 2,\n\n /**\n * Term's presence in a document is prohibited, documents that do contain\n * this term will not be returned.\n */\n PROHIBITED: 3\n}\n\n/**\n * A single clause in a {@link lunr.Query} contains a term and details on how to\n * match that term against a {@link lunr.Index}.\n *\n * @typedef {Object} lunr.Query~Clause\n * @property {string[]} fields - The fields in an index this clause should be matched against.\n * @property {number} [boost=1] - Any boost that should be applied when matching this clause.\n * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be.\n * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline.\n * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended.\n * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents.\n */\n\n/**\n * Adds a {@link lunr.Query~Clause} to this query.\n *\n * Unless the clause contains the fields to be matched all fields will be matched. In addition\n * a default boost of 1 is applied to the clause.\n *\n * @param {lunr.Query~Clause} clause - The clause to add to this query.\n * @see lunr.Query~Clause\n * @returns {lunr.Query}\n */\nlunr.Query.prototype.clause = function (clause) {\n if (!('fields' in clause)) {\n clause.fields = this.allFields\n }\n\n if (!('boost' in clause)) {\n clause.boost = 1\n }\n\n if (!('usePipeline' in clause)) {\n clause.usePipeline = true\n }\n\n if (!('wildcard' in clause)) {\n clause.wildcard = lunr.Query.wildcard.NONE\n }\n\n if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) {\n clause.term = \"*\" + clause.term\n }\n\n if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) {\n clause.term = \"\" + clause.term + \"*\"\n }\n\n if (!('presence' in clause)) {\n clause.presence = lunr.Query.presence.OPTIONAL\n }\n\n this.clauses.push(clause)\n\n return this\n}\n\n/**\n * A negated query is one in which every clause has a presence of\n * prohibited. These queries require some special processing to return\n * the expected results.\n *\n * @returns boolean\n */\nlunr.Query.prototype.isNegated = function () {\n for (var i = 0; i < this.clauses.length; i++) {\n if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause}\n * to the list of clauses that make up this query.\n *\n * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion\n * to a token or token-like string should be done before calling this method.\n *\n * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an\n * array, each term in the array will share the same options.\n *\n * @param {object|object[]} term - The term(s) to add to the query.\n * @param {object} [options] - Any additional properties to add to the query clause.\n * @returns {lunr.Query}\n * @see lunr.Query#clause\n * @see lunr.Query~Clause\n * @example adding a single term to a query\n * query.term(\"foo\")\n * @example adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard\n * query.term(\"foo\", {\n * fields: [\"title\"],\n * boost: 10,\n * wildcard: lunr.Query.wildcard.TRAILING\n * })\n * @example using lunr.tokenizer to convert a string to tokens before using them as terms\n * query.term(lunr.tokenizer(\"foo bar\"))\n */\nlunr.Query.prototype.term = function (term, options) {\n if (Array.isArray(term)) {\n term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this)\n return this\n }\n\n var clause = options || {}\n clause.term = term.toString()\n\n this.clause(clause)\n\n return this\n}\nlunr.QueryParseError = function (message, start, end) {\n this.name = \"QueryParseError\"\n this.message = message\n this.start = start\n this.end = end\n}\n\nlunr.QueryParseError.prototype = new Error\nlunr.QueryLexer = function (str) {\n this.lexemes = []\n this.str = str\n this.length = str.length\n this.pos = 0\n this.start = 0\n this.escapeCharPositions = []\n}\n\nlunr.QueryLexer.prototype.run = function () {\n var state = lunr.QueryLexer.lexText\n\n while (state) {\n state = state(this)\n }\n}\n\nlunr.QueryLexer.prototype.sliceString = function () {\n var subSlices = [],\n sliceStart = this.start,\n sliceEnd = this.pos\n\n for (var i = 0; i < this.escapeCharPositions.length; i++) {\n sliceEnd = this.escapeCharPositions[i]\n subSlices.push(this.str.slice(sliceStart, sliceEnd))\n sliceStart = sliceEnd + 1\n }\n\n subSlices.push(this.str.slice(sliceStart, this.pos))\n this.escapeCharPositions.length = 0\n\n return subSlices.join('')\n}\n\nlunr.QueryLexer.prototype.emit = function (type) {\n this.lexemes.push({\n type: type,\n str: this.sliceString(),\n start: this.start,\n end: this.pos\n })\n\n this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.escapeCharacter = function () {\n this.escapeCharPositions.push(this.pos - 1)\n this.pos += 1\n}\n\nlunr.QueryLexer.prototype.next = function () {\n if (this.pos >= this.length) {\n return lunr.QueryLexer.EOS\n }\n\n var char = this.str.charAt(this.pos)\n this.pos += 1\n return char\n}\n\nlunr.QueryLexer.prototype.width = function () {\n return this.pos - this.start\n}\n\nlunr.QueryLexer.prototype.ignore = function () {\n if (this.start == this.pos) {\n this.pos += 1\n }\n\n this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.backup = function () {\n this.pos -= 1\n}\n\nlunr.QueryLexer.prototype.acceptDigitRun = function () {\n var char, charCode\n\n do {\n char = this.next()\n charCode = char.charCodeAt(0)\n } while (charCode > 47 && charCode < 58)\n\n if (char != lunr.QueryLexer.EOS) {\n this.backup()\n }\n}\n\nlunr.QueryLexer.prototype.more = function () {\n return this.pos < this.length\n}\n\nlunr.QueryLexer.EOS = 'EOS'\nlunr.QueryLexer.FIELD = 'FIELD'\nlunr.QueryLexer.TERM = 'TERM'\nlunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE'\nlunr.QueryLexer.BOOST = 'BOOST'\nlunr.QueryLexer.PRESENCE = 'PRESENCE'\n\nlunr.QueryLexer.lexField = function (lexer) {\n lexer.backup()\n lexer.emit(lunr.QueryLexer.FIELD)\n lexer.ignore()\n return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexTerm = function (lexer) {\n if (lexer.width() > 1) {\n lexer.backup()\n lexer.emit(lunr.QueryLexer.TERM)\n }\n\n lexer.ignore()\n\n if (lexer.more()) {\n return lunr.QueryLexer.lexText\n }\n}\n\nlunr.QueryLexer.lexEditDistance = function (lexer) {\n lexer.ignore()\n lexer.acceptDigitRun()\n lexer.emit(lunr.QueryLexer.EDIT_DISTANCE)\n return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexBoost = function (lexer) {\n lexer.ignore()\n lexer.acceptDigitRun()\n lexer.emit(lunr.QueryLexer.BOOST)\n return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexEOS = function (lexer) {\n if (lexer.width() > 0) {\n lexer.emit(lunr.QueryLexer.TERM)\n }\n}\n\n// This matches the separator used when tokenising fields\n// within a document. These should match otherwise it is\n// not possible to search for some tokens within a document.\n//\n// It is possible for the user to change the separator on the\n// tokenizer so it _might_ clash with any other of the special\n// characters already used within the search string, e.g. :.\n//\n// This means that it is possible to change the separator in\n// such a way that makes some words unsearchable using a search\n// string.\nlunr.QueryLexer.termSeparator = lunr.tokenizer.separator\n\nlunr.QueryLexer.lexText = function (lexer) {\n while (true) {\n var char = lexer.next()\n\n if (char == lunr.QueryLexer.EOS) {\n return lunr.QueryLexer.lexEOS\n }\n\n // Escape character is '\\'\n if (char.charCodeAt(0) == 92) {\n lexer.escapeCharacter()\n continue\n }\n\n if (char == \":\") {\n return lunr.QueryLexer.lexField\n }\n\n if (char == \"~\") {\n lexer.backup()\n if (lexer.width() > 0) {\n lexer.emit(lunr.QueryLexer.TERM)\n }\n return lunr.QueryLexer.lexEditDistance\n }\n\n if (char == \"^\") {\n lexer.backup()\n if (lexer.width() > 0) {\n lexer.emit(lunr.QueryLexer.TERM)\n }\n return lunr.QueryLexer.lexBoost\n }\n\n // \"+\" indicates term presence is required\n // checking for length to ensure that only\n // leading \"+\" are considered\n if (char == \"+\" && lexer.width() === 1) {\n lexer.emit(lunr.QueryLexer.PRESENCE)\n return lunr.QueryLexer.lexText\n }\n\n // \"-\" indicates term presence is prohibited\n // checking for length to ensure that only\n // leading \"-\" are considered\n if (char == \"-\" && lexer.width() === 1) {\n lexer.emit(lunr.QueryLexer.PRESENCE)\n return lunr.QueryLexer.lexText\n }\n\n if (char.match(lunr.QueryLexer.termSeparator)) {\n return lunr.QueryLexer.lexTerm\n }\n }\n}\n\nlunr.QueryParser = function (str, query) {\n this.lexer = new lunr.QueryLexer (str)\n this.query = query\n this.currentClause = {}\n this.lexemeIdx = 0\n}\n\nlunr.QueryParser.prototype.parse = function () {\n this.lexer.run()\n this.lexemes = this.lexer.lexemes\n\n var state = lunr.QueryParser.parseClause\n\n while (state) {\n state = state(this)\n }\n\n return this.query\n}\n\nlunr.QueryParser.prototype.peekLexeme = function () {\n return this.lexemes[this.lexemeIdx]\n}\n\nlunr.QueryParser.prototype.consumeLexeme = function () {\n var lexeme = this.peekLexeme()\n this.lexemeIdx += 1\n return lexeme\n}\n\nlunr.QueryParser.prototype.nextClause = function () {\n var completedClause = this.currentClause\n this.query.clause(completedClause)\n this.currentClause = {}\n}\n\nlunr.QueryParser.parseClause = function (parser) {\n var lexeme = parser.peekLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n switch (lexeme.type) {\n case lunr.QueryLexer.PRESENCE:\n return lunr.QueryParser.parsePresence\n case lunr.QueryLexer.FIELD:\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.TERM:\n return lunr.QueryParser.parseTerm\n default:\n var errorMessage = \"expected either a field or a term, found \" + lexeme.type\n\n if (lexeme.str.length >= 1) {\n errorMessage += \" with value '\" + lexeme.str + \"'\"\n }\n\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n}\n\nlunr.QueryParser.parsePresence = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n switch (lexeme.str) {\n case \"-\":\n parser.currentClause.presence = lunr.Query.presence.PROHIBITED\n break\n case \"+\":\n parser.currentClause.presence = lunr.Query.presence.REQUIRED\n break\n default:\n var errorMessage = \"unrecognised presence operator'\" + lexeme.str + \"'\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n var errorMessage = \"expecting term or field, found nothing\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.FIELD:\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.TERM:\n return lunr.QueryParser.parseTerm\n default:\n var errorMessage = \"expecting term or field, found '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseField = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n if (parser.query.allFields.indexOf(lexeme.str) == -1) {\n var possibleFields = parser.query.allFields.map(function (f) { return \"'\" + f + \"'\" }).join(', '),\n errorMessage = \"unrecognised field '\" + lexeme.str + \"', possible fields: \" + possibleFields\n\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n parser.currentClause.fields = [lexeme.str]\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n var errorMessage = \"expecting term, found nothing\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n return lunr.QueryParser.parseTerm\n default:\n var errorMessage = \"expecting term, found '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseTerm = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n parser.currentClause.term = lexeme.str.toLowerCase()\n\n if (lexeme.str.indexOf(\"*\") != -1) {\n parser.currentClause.usePipeline = false\n }\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n parser.nextClause()\n return\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n parser.nextClause()\n return lunr.QueryParser.parseTerm\n case lunr.QueryLexer.FIELD:\n parser.nextClause()\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.EDIT_DISTANCE:\n return lunr.QueryParser.parseEditDistance\n case lunr.QueryLexer.BOOST:\n return lunr.QueryParser.parseBoost\n case lunr.QueryLexer.PRESENCE:\n parser.nextClause()\n return lunr.QueryParser.parsePresence\n default:\n var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseEditDistance = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n var editDistance = parseInt(lexeme.str, 10)\n\n if (isNaN(editDistance)) {\n var errorMessage = \"edit distance must be numeric\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n parser.currentClause.editDistance = editDistance\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n parser.nextClause()\n return\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n parser.nextClause()\n return lunr.QueryParser.parseTerm\n case lunr.QueryLexer.FIELD:\n parser.nextClause()\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.EDIT_DISTANCE:\n return lunr.QueryParser.parseEditDistance\n case lunr.QueryLexer.BOOST:\n return lunr.QueryParser.parseBoost\n case lunr.QueryLexer.PRESENCE:\n parser.nextClause()\n return lunr.QueryParser.parsePresence\n default:\n var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseBoost = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n var boost = parseInt(lexeme.str, 10)\n\n if (isNaN(boost)) {\n var errorMessage = \"boost must be numeric\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n parser.currentClause.boost = boost\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n parser.nextClause()\n return\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n parser.nextClause()\n return lunr.QueryParser.parseTerm\n case lunr.QueryLexer.FIELD:\n parser.nextClause()\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.EDIT_DISTANCE:\n return lunr.QueryParser.parseEditDistance\n case lunr.QueryLexer.BOOST:\n return lunr.QueryParser.parseBoost\n case lunr.QueryLexer.PRESENCE:\n parser.nextClause()\n return lunr.QueryParser.parsePresence\n default:\n var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\n /**\n * export the module via AMD, CommonJS or as a browser global\n * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js\n */\n ;(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n // AMD. Register as an anonymous module.\n define(factory)\n } else if (typeof exports === 'object') {\n /**\n * Node. Does not work with strict CommonJS, but\n * only CommonJS-like enviroments that support module.exports,\n * like Node.\n */\n module.exports = factory()\n } else {\n // Browser globals (root is window)\n root.lunr = factory()\n }\n }(this, function () {\n /**\n * Just return a value to define the module export.\n * This example returns an object, but the module\n * can return a function as the exported value.\n */\n return lunr\n }))\n})();\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport lunr from \"lunr\"\n\nimport { Search, SearchIndexConfig } from \"../../_\"\nimport {\n SearchMessage,\n SearchMessageType\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Add support for usage with `iframe-worker` polyfill\n *\n * While `importScripts` is synchronous when executed inside of a web worker,\n * it's not possible to provide a synchronous polyfilled implementation. The\n * cool thing is that awaiting a non-Promise is a noop, so extending the type\n * definition to return a `Promise` shouldn't break anything.\n *\n * @see https://bit.ly/2PjDnXi - GitHub comment\n */\ndeclare global {\n function importScripts(...urls: string[]): Promise | void\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nlet index: Search\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch (= import) multi-language support through `lunr-languages`\n *\n * This function automatically imports the stemmers necessary to process the\n * languages, which are defined through the search index configuration.\n *\n * If the worker runs inside of an `iframe` (when using `iframe-worker` as\n * a shim), the base URL for the stemmers to be loaded must be determined by\n * searching for the first `script` element with a `src` attribute, which will\n * contain the contents of this script.\n *\n * @param config - Search index configuration\n *\n * @returns Promise resolving with no result\n */\nasync function setupSearchLanguages(\n config: SearchIndexConfig\n): Promise {\n let base = \"../lunr\"\n\n /* Detect `iframe-worker` and fix base URL */\n if (typeof parent !== \"undefined\" && \"IFrameWorker\" in parent) {\n const worker = document.querySelector(\"script[src]\")!\n const [path] = worker.src.split(\"/worker\")\n\n /* Prefix base with path */\n base = base.replace(\"..\", path)\n }\n\n /* Add scripts for languages */\n const scripts = []\n for (const lang of config.lang) {\n if (lang === \"ja\") scripts.push(`${base}/tinyseg.js`)\n if (lang !== \"en\") scripts.push(`${base}/min/lunr.${lang}.min.js`)\n }\n\n /* Add multi-language support */\n if (config.lang.length > 1)\n scripts.push(`${base}/min/lunr.multi.min.js`)\n\n /* Load scripts synchronously */\n if (scripts.length)\n await importScripts(\n `${base}/min/lunr.stemmer.support.min.js`,\n ...scripts\n )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Message handler\n *\n * @param message - Source message\n *\n * @returns Target message\n */\nexport async function handler(\n message: SearchMessage\n): Promise {\n switch (message.type) {\n\n /* Search setup message */\n case SearchMessageType.SETUP:\n await setupSearchLanguages(message.data.config)\n index = new Search(message.data)\n return {\n type: SearchMessageType.READY\n }\n\n /* Search query message */\n case SearchMessageType.QUERY:\n return {\n type: SearchMessageType.RESULT,\n data: index ? index.search(message.data) : []\n }\n\n /* All other messages */\n default:\n throw new TypeError(\"Invalid message type\")\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Worker\n * ------------------------------------------------------------------------- */\n\n/* @ts-ignore - expose Lunr.js in global scope, or stemmers will not work */\nself.lunr = lunr\n\n/* Handle messages */\naddEventListener(\"message\", async ev => {\n postMessage(await handler(ev.data))\n})\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexDocument } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search document\n */\nexport interface SearchDocument extends SearchIndexDocument {\n parent?: SearchIndexDocument /* Parent article */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search document mapping\n */\nexport type SearchDocumentMap = Map\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document mapping\n *\n * @param docs - Search index documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n docs: SearchIndexDocument[]\n): SearchDocumentMap {\n const documents = new Map()\n const parents = new Set()\n for (const doc of docs) {\n const [path, hash] = doc.location.split(\"#\")\n\n /* Extract location and title */\n const location = doc.location\n const title = doc.title\n\n /* Escape and cleanup text */\n const text = escapeHTML(doc.text)\n .replace(/\\s+(?=[,.:;!?])/g, \"\")\n .replace(/\\s+/g, \" \")\n\n /* Handle section */\n if (hash) {\n const parent = documents.get(path)!\n\n /* Ignore first section, override article */\n if (!parents.has(parent)) {\n parent.title = doc.title\n parent.text = text\n\n /* Remember that we processed the article */\n parents.add(parent)\n\n /* Add subsequent section */\n } else {\n documents.set(location, {\n location,\n title,\n text,\n parent\n })\n }\n\n /* Add article */\n } else {\n documents.set(location, {\n location,\n title,\n text\n })\n }\n }\n return documents\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndexConfig } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search highlight function\n *\n * @param value - Value\n *\n * @returns Highlighted value\n */\nexport type SearchHighlightFn = (value: string) => string\n\n/**\n * Search highlight factory function\n *\n * @param query - Query value\n *\n * @returns Search highlight function\n */\nexport type SearchHighlightFactoryFn = (query: string) => SearchHighlightFn\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search highlighter\n *\n * @param config - Search index configuration\n *\n * @returns Search highlight factory function\n */\nexport function setupSearchHighlighter(\n config: SearchIndexConfig\n): SearchHighlightFactoryFn {\n const separator = new RegExp(config.separator, \"img\")\n const highlight = (_: unknown, data: string, term: string) => {\n return `${data}${term}`\n }\n\n /* Return factory function */\n return (query: string) => {\n query = query\n .replace(/[\\s*+\\-:~^]+/g, \" \")\n .trim()\n\n /* Create search term match expression */\n const match = new RegExp(`(^|${config.separator})(${\n query\n .replace(/[|\\\\{}()[\\]^$+*?.-]/g, \"\\\\$&\")\n .replace(separator, \"|\")\n })`, \"img\")\n\n /* Highlight string value */\n return value => value\n .replace(match, highlight)\n .replace(/<\\/mark>(\\s+)]*>/img, \"$1\")\n }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query clause\n */\nexport interface SearchQueryClause {\n presence: lunr.Query.presence /* Clause presence */\n term: string /* Clause term */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search query terms\n */\nexport type SearchQueryTerms = Record\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Parse a search query for analysis\n *\n * @param value - Query value\n *\n * @returns Search query clauses\n */\nexport function parseSearchQuery(\n value: string\n): SearchQueryClause[] {\n const query = new (lunr as any).Query([\"title\", \"text\"])\n const parser = new (lunr as any).QueryParser(value, query)\n\n /* Parse and return query clauses */\n parser.parse()\n return query.clauses\n}\n\n/**\n * Analyze the search query clauses in regard to the search terms found\n *\n * @param query - Search query clauses\n * @param terms - Search terms\n *\n * @returns Search query terms\n */\nexport function getSearchQueryTerms(\n query: SearchQueryClause[], terms: string[]\n): SearchQueryTerms {\n const clauses = new Set(query)\n\n /* Match query clauses against terms */\n const result: SearchQueryTerms = {}\n for (let t = 0; t < terms.length; t++)\n for (const clause of clauses)\n if (terms[t].startsWith(clause.term)) {\n result[clause.term] = true\n clauses.delete(clause)\n }\n\n /* Annotate unmatched query clauses */\n for (const clause of clauses)\n result[clause.term] = false\n\n /* Return query terms */\n return result\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n SearchDocument,\n SearchDocumentMap,\n setupSearchDocumentMap\n} from \"../document\"\nimport {\n SearchHighlightFactoryFn,\n setupSearchHighlighter\n} from \"../highlighter\"\nimport {\n SearchQueryTerms,\n getSearchQueryTerms,\n parseSearchQuery\n} from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index configuration\n */\nexport interface SearchIndexConfig {\n lang: string[] /* Search languages */\n separator: string /* Search separator */\n}\n\n/**\n * Search index document\n */\nexport interface SearchIndexDocument {\n location: string /* Document location */\n title: string /* Document title */\n text: string /* Document text */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search index pipeline function\n */\nexport type SearchIndexPipelineFn =\n | \"trimmer\" /* Trimmer */\n | \"stopWordFilter\" /* Stop word filter */\n | \"stemmer\" /* Stemmer */\n\n/**\n * Search index pipeline\n */\nexport type SearchIndexPipeline = SearchIndexPipelineFn[]\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search index\n *\n * This interfaces describes the format of the `search_index.json` file which\n * is automatically built by the MkDocs search plugin.\n */\nexport interface SearchIndex {\n config: SearchIndexConfig /* Search index configuration */\n docs: SearchIndexDocument[] /* Search index documents */\n index?: object /* Prebuilt index */\n pipeline?: SearchIndexPipeline /* Search index pipeline */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search metadata\n */\nexport interface SearchMetadata {\n score: number /* Score (relevance) */\n terms: SearchQueryTerms /* Search query terms */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search result\n */\nexport type SearchResult = Array\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Compute the difference of two lists of strings\n *\n * @param a - 1st list of strings\n * @param b - 2nd list of strings\n *\n * @returns Difference\n */\nfunction difference(a: string[], b: string[]): string[] {\n const [x, y] = [new Set(a), new Set(b)]\n return [\n ...new Set([...x].filter(value => !y.has(value)))\n ]\n}\n\n/* ----------------------------------------------------------------------------\n * Class\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nexport class Search {\n\n /**\n * Search document mapping\n *\n * A mapping of URLs (including hash fragments) to the actual articles and\n * sections of the documentation. The search document mapping must be created\n * regardless of whether the index was prebuilt or not, as Lunr.js itself\n * only stores the actual index.\n */\n protected documents: SearchDocumentMap\n\n /**\n * Search highlight factory function\n */\n protected highlight: SearchHighlightFactoryFn\n\n /**\n * The underlying Lunr.js search index\n */\n protected index: lunr.Index\n\n /**\n * Create the search integration\n *\n * @param data - Search index\n */\n public constructor({ config, docs, pipeline, index }: SearchIndex) {\n this.documents = setupSearchDocumentMap(docs)\n this.highlight = setupSearchHighlighter(config)\n\n /* Set separator for tokenizer */\n lunr.tokenizer.separator = new RegExp(config.separator)\n\n /* If no index was given, create it */\n if (typeof index === \"undefined\") {\n this.index = lunr(function () {\n\n /* Set up multi-language support */\n if (config.lang.length === 1 && config.lang[0] !== \"en\") {\n this.use((lunr as any)[config.lang[0]])\n } else if (config.lang.length > 1) {\n this.use((lunr as any).multiLanguage(...config.lang))\n }\n\n /* Compute functions to be removed from the pipeline */\n const fns = difference([\n \"trimmer\", \"stopWordFilter\", \"stemmer\"\n ], pipeline!)\n\n /* Remove functions from the pipeline for registered languages */\n for (const lang of config.lang.map(language => (\n language === \"en\" ? lunr : (lunr as any)[language]\n ))) {\n for (const fn of fns) {\n this.pipeline.remove(lang[fn])\n this.searchPipeline.remove(lang[fn])\n }\n }\n\n /* Set up fields and reference */\n this.field(\"title\", { boost: 1000 })\n this.field(\"text\")\n this.ref(\"location\")\n\n /* Index documents */\n for (const doc of docs)\n this.add(doc)\n })\n\n /* Handle prebuilt index */\n } else {\n this.index = lunr.Index.load(index)\n }\n }\n\n /**\n * Search for matching documents\n *\n * The search index which MkDocs provides is divided up into articles, which\n * contain the whole content of the individual pages, and sections, which only\n * contain the contents of the subsections obtained by breaking the individual\n * pages up at `h1` ... `h6`. As there may be many sections on different pages\n * with identical titles (for example within this very project, e.g. \"Usage\"\n * or \"Installation\"), they need to be put into the context of the containing\n * page. For this reason, section results are grouped within their respective\n * articles which are the top-level results that are returned.\n *\n * @param query - Query value\n *\n * @returns Search results\n */\n public search(query: string): SearchResult[] {\n if (query) {\n try {\n const highlight = this.highlight(query)\n\n /* Parse query to extract clauses for analysis */\n const clauses = parseSearchQuery(query)\n .filter(clause => (\n clause.presence !== lunr.Query.presence.PROHIBITED\n ))\n\n /* Perform search and post-process results */\n const groups = this.index.search(`${query}*`)\n\n /* Apply post-query boosts based on title and search query terms */\n .reduce((results, { ref, score, matchData }) => {\n const document = this.documents.get(ref)\n if (typeof document !== \"undefined\") {\n const { location, title, text, parent } = document\n\n /* Compute and analyze search query terms */\n const terms = getSearchQueryTerms(\n clauses,\n Object.keys(matchData.metadata)\n )\n\n /* Highlight title and text and apply post-query boosts */\n const boost = +!parent + +Object.values(terms).every(t => t)\n results.push({\n location,\n title: highlight(title),\n text: highlight(text),\n score: score * (1 + boost),\n terms\n })\n }\n return results\n }, [])\n\n /* Sort search results again after applying boosts */\n .sort((a, b) => b.score - a.score)\n\n /* Group search results by page */\n .reduce((results, result) => {\n const document = this.documents.get(result.location)\n if (typeof document !== \"undefined\") {\n const ref = \"parent\" in document\n ? document.parent!.location\n : document.location\n results.set(ref, [...results.get(ref) || [], result])\n }\n return results\n }, new Map())\n\n /* Expand grouped search results */\n return [...groups.values()]\n\n /* Log errors to console (for now) */\n } catch {\n console.warn(`Invalid query: ${query} \u2013 see https://bit.ly/2s3ChXG`)\n }\n }\n\n /* Return nothing in case of error or empty query */\n return []\n }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndex, SearchResult } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search message type\n */\nexport const enum SearchMessageType {\n SETUP, /* Search index setup */\n READY, /* Search index ready */\n QUERY, /* Search query */\n RESULT /* Search results */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message containing the data necessary to setup the search index\n */\nexport interface SearchSetupMessage {\n type: SearchMessageType.SETUP /* Message type */\n data: SearchIndex /* Message data */\n}\n\n/**\n * A message indicating the search index is ready\n */\nexport interface SearchReadyMessage {\n type: SearchMessageType.READY /* Message type */\n}\n\n/**\n * A message containing a search query\n */\nexport interface SearchQueryMessage {\n type: SearchMessageType.QUERY /* Message type */\n data: string /* Message data */\n}\n\n/**\n * A message containing results for a search query\n */\nexport interface SearchResultMessage {\n type: SearchMessageType.RESULT /* Message type */\n data: SearchResult[] /* Message data */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message exchanged with the search worker\n */\nexport type SearchMessage =\n | SearchSetupMessage\n | SearchReadyMessage\n | SearchQueryMessage\n | SearchResultMessage\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Type guard for search setup messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchSetupMessage(\n message: SearchMessage\n): message is SearchSetupMessage {\n return message.type === SearchMessageType.SETUP\n}\n\n/**\n * Type guard for search ready messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchReadyMessage(\n message: SearchMessage\n): message is SearchReadyMessage {\n return message.type === SearchMessageType.READY\n}\n\n/**\n * Type guard for search query messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchQueryMessage(\n message: SearchMessage\n): message is SearchQueryMessage {\n return message.type === SearchMessageType.QUERY\n}\n\n/**\n * Type guard for search result messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchResultMessage(\n message: SearchMessage\n): message is SearchResultMessage {\n return message.type === SearchMessageType.RESULT\n}\n"], + "mappings": "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AAG,GACV,EAAK,EACD,EAAG,KAAK,IACV,GAAI,WAEG,EAAI,KAAK,GAAI,CACtB,GAAI,GAAK,EAAI,KAAK,GAClB,EAAO,EAAG,GAAK,EAAG,GAClB,EAAM,EACF,EAAI,KAAK,IACX,GAAI,GAMR,GADA,EAAK,EACD,EAAG,KAAK,GAAI,CACd,GAAI,GAAK,EAAG,KAAK,GACjB,EAAO,EAAG,GACV,EAAK,EACL,EAAM,EACN,EAAM,EACF,GAAG,KAAK,IAAU,EAAI,KAAK,IAAS,CAAE,EAAI,KAAK,KACjD,GAAI,GAIR,SAAK,EACL,EAAM,EACF,EAAG,KAAK,IAAM,EAAI,KAAK,IACzB,GAAK,EACL,EAAI,EAAE,QAAQ,EAAG,KAKf,GAAW,KACb,GAAI,EAAQ,cAAgB,EAAE,OAAO,IAGhC,GAGT,MAAO,UAAU,EAAO,CACtB,MAAO,GAAM,OAAO,OAIxB,EAAK,SAAS,iBAAiB,EAAK,QAAS,WAC7C,AAkBA,EAAK,uBAAyB,SAAU,EAAW,CACjD,GAAI,GAAQ,EAAU,OAAO,SAAU,EAAM,EAAU,CACrD,SAAK,GAAY,EACV,GACN,IAEH,MAAO,UAAU,EAAO,CACtB,GAAI,GAAS,EAAM,EAAM,cAAgB,EAAM,WAAY,MAAO,KAiBtE,EAAK,eAAiB,EAAK,uBAAuB,CAChD,IACA,OACA,QACA,SACA,QACA,MACA,SACA,OACA,KACA,QACA,KACA,MACA,MACA,MACA,KACA,KACA,KACA,UACA,OACA,MACA,KACA,MACA,SACA,QACA,OACA,MACA,KACA,OACA,SACA,OACA,OACA,QACA,MACA,OACA,MACA,MACA,MACA,MACA,OACA,KACA,MACA,OACA,MACA,MACA,MACA,UACA,IACA,KACA,KACA,OACA,KACA,KACA,MACA,OACA,QACA,MACA,OACA,SACA,MACA,KACA,QACA,OACA,OACA,KACA,UACA,KACA,MACA,MACA,KACA,MACA,QACA,KACA,OACA,KACA,QACA,MACA,MACA,SACA,OACA,MACA,OACA,MACA,SACA,QACA,KACA,OACA,OACA,OACA,MACA,QACA,OACA,OACA,QACA,QACA,OACA,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", + "names": [] +} diff --git a/assets/stylesheets/main.33e2939f.min.css b/assets/stylesheets/main.33e2939f.min.css new file mode 100644 index 000000000..54d216315 --- /dev/null +++ b/assets/stylesheets/main.33e2939f.min.css @@ -0,0 +1,2 @@ +@charset "UTF-8";html{box-sizing:border-box;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;text-size-adjust:none}*,:after,:before{box-sizing:inherit}body{margin:0}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}hr{display:block;box-sizing:initial;height:.05rem;padding:0;overflow:visible;border:0}small{font-size:80%}sub,sup{line-height:1em}img{border-style:none}table{border-collapse:initial;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{margin:0;padding:0;font-size:inherit;background:transparent;border:0}input{border:0;outline:none}:root{--md-default-fg-color:rgba(0,0,0,0.87);--md-default-fg-color--light:rgba(0,0,0,0.54);--md-default-fg-color--lighter:rgba(0,0,0,0.32);--md-default-fg-color--lightest:rgba(0,0,0,0.07);--md-default-bg-color:#fff;--md-default-bg-color--light:hsla(0,0%,100%,0.7);--md-default-bg-color--lighter:hsla(0,0%,100%,0.3);--md-default-bg-color--lightest:hsla(0,0%,100%,0.12);--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7);--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:rgba(83,108,254,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}:root>*{--md-code-fg-color:#36464e;--md-code-bg-color:#f5f5f5;--md-code-hl-color:rgba(255,255,0,0.5);--md-code-hl-number-color:#d52a2a;--md-code-hl-special-color:#db1457;--md-code-hl-function-color:#a846b9;--md-code-hl-constant-color:#6e59d9;--md-code-hl-keyword-color:#3f6ec6;--md-code-hl-string-color:#1c7d4d;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-mark-color:rgba(255,255,0,0.5);--md-typeset-del-color:rgba(245,80,61,0.15);--md-typeset-ins-color:rgba(11,213,112,0.15);--md-typeset-kbd-color:#fafafa;--md-typeset-kbd-accent-color:#fff;--md-typeset-kbd-border-color:#b8b8b8;--md-admonition-fg-color:var(--md-default-fg-color);--md-admonition-bg-color:var(--md-default-bg-color);--md-footer-fg-color:#fff;--md-footer-fg-color--light:hsla(0,0%,100%,0.7);--md-footer-fg-color--lighter:hsla(0,0%,100%,0.3);--md-footer-bg-color:rgba(0,0,0,0.87);--md-footer-bg-color--dark:rgba(0,0,0,0.32)}.md-icon svg{display:block;width:1.2rem;height:1.2rem;fill:currentColor}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,input{font-feature-settings:"kern","liga";font-family:var(--md-text-font-family,_),-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif}body,code,input,kbd,pre{color:var(--md-typeset-color)}code,kbd,pre{font-feature-settings:"kern";font-family:var(--md-code-font-family,_),SFMono-Regular,Consolas,Menlo,monospace}:root{--md-typeset-table--ascending:url("data:image/svg+xml;charset=utf-8,");--md-typeset-table--descending:url("data:image/svg+xml;charset=utf-8,")}.md-typeset{font-size:.8rem;line-height:1.6;-webkit-print-color-adjust:exact;color-adjust:exact}@media print{.md-typeset{font-size:.68rem}}.md-typeset blockquote,.md-typeset dl,.md-typeset figure,.md-typeset ol,.md-typeset pre,.md-typeset ul{display:flow-root;margin:1em 0}.md-typeset h1{margin:0 0 1.25em;color:var(--md-default-fg-color--light);font-size:2em;line-height:1.3}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{margin:1.6em 0 .64em;font-size:1.5625em;line-height:1.4}.md-typeset h3{margin:1.6em 0 .8em;font-weight:400;font-size:1.25em;line-height:1.5;letter-spacing:-.01em}.md-typeset h2+h3{margin-top:.8em}.md-typeset h4{margin:1em 0;font-weight:700;letter-spacing:-.01em}.md-typeset h5,.md-typeset h6{margin:1.25em 0;color:var(--md-default-fg-color--light);font-weight:700;font-size:.8em;letter-spacing:-.01em}.md-typeset h5{text-transform:uppercase}.md-typeset hr{display:flow-root;margin:1.5em 0;border-bottom:.05rem solid var(--md-default-fg-color--lightest)}.md-typeset a{color:var(--md-typeset-a-color);word-break:break-word}.md-typeset a,.md-typeset a:before{transition:color 125ms}.md-typeset a:focus,.md-typeset a:hover{color:var(--md-accent-fg-color)}.md-typeset code,.md-typeset kbd,.md-typeset pre{color:var(--md-code-fg-color);direction:ltr}@media print{.md-typeset code,.md-typeset kbd,.md-typeset pre{white-space:pre-wrap}}.md-typeset code{padding:0 .2941176471em;font-size:.85em;word-break:break-word;background-color:var(--md-code-bg-color);border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset code:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}.md-typeset h1 code,.md-typeset h2 code,.md-typeset h3 code,.md-typeset h4 code,.md-typeset h5 code,.md-typeset h6 code{margin:initial;padding:initial;background-color:initial;box-shadow:none}.md-typeset a code{color:currentColor}.md-typeset pre{position:relative;line-height:1.4}.md-typeset pre>code{display:block;margin:0;padding:.7720588235em 1.1764705882em;overflow:auto;word-break:normal;box-shadow:none;-webkit-box-decoration-break:slice;box-decoration-break:slice;touch-action:auto;scrollbar-width:thin;scrollbar-color:var(--md-default-fg-color--lighter) transparent}.md-typeset pre>code:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-typeset pre>code::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@media screen and (max-width:44.9375em){.md-typeset>pre{margin:1em -.8rem}.md-typeset>pre code{border-radius:0}}.md-typeset kbd{display:inline-block;padding:0 .6666666667em;color:var(--md-default-fg-color);font-size:.75em;vertical-align:text-top;word-break:break-word;background-color:var(--md-typeset-kbd-color);border-radius:.1rem;box-shadow:0 .1rem 0 .05rem var(--md-typeset-kbd-border-color),0 .1rem 0 var(--md-typeset-kbd-border-color),0 -.1rem .2rem var(--md-typeset-kbd-accent-color) inset}.md-typeset mark{color:inherit;word-break:break-word;background-color:var(--md-typeset-mark-color);-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset abbr{text-decoration:none;border-bottom:.05rem dotted var(--md-default-fg-color--light);cursor:help}@media (hover:none){.md-typeset abbr{position:relative}.md-typeset abbr[title]:focus:after,.md-typeset abbr[title]:hover:after{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:absolute;left:0;display:inline-block;width:auto;min-width:-webkit-max-content;min-width:-moz-max-content;min-width:max-content;max-width:80%;margin-top:2em;padding:.2rem .3rem;color:var(--md-default-bg-color);font-size:.7rem;background-color:var(--md-default-fg-color);border-radius:.1rem;content:attr(title)}}.md-typeset small{opacity:.75}.md-typeset sub,.md-typeset sup{margin-left:.078125em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.078125em;margin-left:0}.md-typeset blockquote{padding-left:.6rem;color:var(--md-default-fg-color--light);border-left:.2rem solid var(--md-default-fg-color--lighter)}[dir=rtl] .md-typeset blockquote{padding-right:.6rem;padding-left:0;border-right:.2rem solid var(--md-default-fg-color--lighter);border-left:initial}.md-typeset ul{list-style-type:disc}.md-typeset ol,.md-typeset ul{margin-left:.625em;padding:0}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em;margin-left:0}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em;margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em;margin-left:0}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin:.5em 0 .5em .625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em;margin-left:0}.md-typeset dd{margin:1em 0 1.5em 1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em;margin-left:0}.md-typeset img,.md-typeset svg{max-width:100%;height:auto}.md-typeset img[align=left],.md-typeset svg[align=left]{margin:1em 1em 1em 0}.md-typeset img[align=right],.md-typeset svg[align=right]{margin:1em 0 1em 1em}.md-typeset img[align]:only-child,.md-typeset svg[align]:only-child{margin-top:0}.md-typeset figure{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;max-width:100%;margin:0 auto;text-align:center}.md-typeset figure img{display:block}.md-typeset figcaption{max-width:24rem;margin:1em auto 2em;font-style:italic}.md-typeset iframe{max-width:100%}.md-typeset table:not([class]){display:inline-block;max-width:100%;overflow:auto;font-size:.64rem;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .05rem rgba(0,0,0,.1);touch-action:auto}@media print{.md-typeset table:not([class]){display:table}}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td>:first-child,.md-typeset table:not([class]) th>:first-child{margin-top:0}.md-typeset table:not([class]) td>:last-child,.md-typeset table:not([class]) th>:last-child{margin-bottom:0}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{min-width:5rem;padding:.9375em 1.25em;color:var(--md-default-bg-color);vertical-align:top;background-color:var(--md-default-fg-color--light)}.md-typeset table:not([class]) th a{color:inherit}.md-typeset table:not([class]) td{padding:.9375em 1.25em;vertical-align:top;border-top:.05rem solid var(--md-default-fg-color--lightest)}.md-typeset table:not([class]) tr{transition:background-color 125ms}.md-typeset table:not([class]) tr:hover{background-color:rgba(0,0,0,.035);box-shadow:0 .05rem 0 var(--md-default-bg-color) inset}.md-typeset table:not([class]) tr:first-child td{border-top:0}.md-typeset table:not([class]) a{word-break:normal}.md-typeset table th[role=columnheader]{cursor:pointer}.md-typeset table th[role=columnheader]:after{display:inline-block;width:1.2em;height:1.2em;margin-left:.5em;vertical-align:sub;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-typeset table th[role=columnheader][aria-sort=ascending]:after{background-color:currentColor;-webkit-mask-image:var(--md-typeset-table--ascending);mask-image:var(--md-typeset-table--ascending)}.md-typeset table th[role=columnheader][aria-sort=descending]:after{background-color:currentColor;-webkit-mask-image:var(--md-typeset-table--descending);mask-image:var(--md-typeset-table--descending)}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;touch-action:auto}.md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}@media print{.md-typeset__table{display:block}}html .md-typeset__table table{display:table;width:100%;margin:0;overflow:hidden}html{height:100%;overflow-x:hidden;font-size:125%}@media screen and (min-width:100em){html{font-size:137.5%}}@media screen and (min-width:125em){html{font-size:150%}}body{position:relative;display:flex;flex-direction:column;width:100%;min-height:100%;font-size:.5rem;background-color:var(--md-default-bg-color)}@media print{body{display:block}}@media screen and (max-width:59.9375em){body[data-md-state=lock]{position:fixed}}.md-grid{max-width:61rem;margin-right:auto;margin-left:auto}.md-container{display:flex;flex-direction:column;flex-grow:1}@media print{.md-container{display:block}}.md-main{flex-grow:1}.md-main__inner{display:flex;height:100%;margin-top:1.5rem}.md-ellipsis{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.md-toggle{display:none}.md-option{position:absolute;width:0;height:0;opacity:0}.md-option:checked+label:not([hidden]){display:block}.md-option.focus-visible+label{outline-style:auto}.md-skip{position:fixed;z-index:-1;margin:.5rem;padding:.3rem .5rem;color:var(--md-default-bg-color);font-size:.64rem;background-color:var(--md-default-fg-color);border-radius:.1rem;transform:translateY(.4rem);opacity:0}.md-skip:focus{z-index:10;transform:translateY(0);opacity:1;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 175ms 75ms}@page{margin:25mm}.md-announce{overflow:auto;background-color:var(--md-footer-bg-color)}@media print{.md-announce{display:none}}.md-announce__inner{margin:.6rem auto;padding:0 .8rem;color:var(--md-footer-fg-color);font-size:.7rem}:root{--md-clipboard-icon:url("data:image/svg+xml;charset=utf-8,")}.md-clipboard{position:absolute;top:.5em;right:.5em;z-index:1;width:1.5em;height:1.5em;color:var(--md-default-fg-color--lightest);border-radius:.1rem;cursor:pointer;transition:color .25s}@media print{.md-clipboard{display:none}}.md-clipboard:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}:hover>.md-clipboard{color:var(--md-default-fg-color--light)}.md-clipboard:focus,.md-clipboard:hover{color:var(--md-accent-fg-color)}.md-clipboard:after{display:block;width:1.125em;height:1.125em;margin:0 auto;background-color:currentColor;-webkit-mask-image:var(--md-clipboard-icon);mask-image:var(--md-clipboard-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-clipboard--inline{cursor:pointer}.md-clipboard--inline code{transition:color .25s,background-color .25s}.md-clipboard--inline:focus code,.md-clipboard--inline:hover code{color:var(--md-accent-fg-color);background-color:var(--md-accent-fg-color--transparent)}.md-content{flex-grow:1;overflow:hidden;scroll-padding-top:51.2rem}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}@media screen and (min-width:76.25em){.md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}[dir=rtl] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem;margin-left:.8rem}.md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem}[dir=rtl] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-right:.8rem;margin-left:1.2rem}}.md-content__inner:before{display:block;height:.4rem;content:""}.md-content__inner>:last-child{margin-bottom:0}.md-content__button{float:right;margin:.4rem 0 .4rem .4rem;padding:0}@media print{.md-content__button{display:none}}[dir=rtl] .md-content__button{float:left;margin-right:.4rem;margin-left:0}[dir=rtl] .md-content__button svg{transform:scaleX(-1)}.md-typeset .md-content__button{color:var(--md-default-fg-color--lighter)}.md-content__button svg{display:inline;vertical-align:top}.md-dialog{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:fixed;right:.8rem;bottom:.8rem;left:auto;z-index:2;min-width:11.1rem;padding:.4rem .6rem;background-color:var(--md-default-fg-color);border-radius:.1rem;transform:translateY(100%);opacity:0;transition:transform 0ms .4s,opacity .4s;pointer-events:none}@media print{.md-dialog{display:none}}[dir=rtl] .md-dialog{right:auto;left:.8rem}.md-dialog[data-md-state=open]{transform:translateY(0);opacity:1;transition:transform .4s cubic-bezier(.075,.85,.175,1),opacity .4s;pointer-events:auto}.md-dialog__inner{color:var(--md-default-bg-color);font-size:.7rem}.md-typeset .md-button{display:inline-block;padding:.625em 2em;color:var(--md-primary-fg-color);font-weight:700;border:.1rem solid;border-radius:.1rem;transition:color 125ms,background-color 125ms,border-color 125ms}.md-typeset .md-button--primary{color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color);border-color:var(--md-primary-fg-color)}.md-typeset .md-button:focus,.md-typeset .md-button:hover{color:var(--md-accent-bg-color);background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color)}.md-typeset .md-input{height:1.8rem;padding:0 .6rem;font-size:.8rem;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 .025rem .05rem rgba(0,0,0,.1);transition:box-shadow .25s}.md-typeset .md-input:focus,.md-typeset .md-input:hover{box-shadow:0 .4rem 1rem rgba(0,0,0,.15),0 .025rem .05rem rgba(0,0,0,.15)}.md-typeset .md-input--stretch{width:100%}.md-header{position:-webkit-sticky;position:sticky;top:0;right:0;left:0;z-index:2;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color);box-shadow:0 0 .2rem transparent,0 .2rem .4rem transparent}@media print{.md-header{display:none}}.md-header[data-md-state=shadow]{box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2);transition:transform .25s cubic-bezier(.1,.7,.1,1),box-shadow .25s}.md-header[data-md-state=hidden]{transform:translateY(-100%);transition:transform .25s cubic-bezier(.8,0,.6,1),box-shadow .25s}.md-header .focus-visible{outline-color:currentColor}.md-header__inner{display:flex;align-items:center;padding:0 .2rem}.md-header__button{position:relative;z-index:1;margin:.2rem;padding:.4rem;color:currentColor;vertical-align:middle;cursor:pointer;transition:opacity .25s}.md-header__button:hover{opacity:.7}.md-header__button:not([hidden]){display:inline-block}.md-header__button:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}.md-header__button.md-logo{margin:.2rem;padding:.4rem}@media screen and (max-width:76.1875em){.md-header__button.md-logo{display:none}}.md-header__button.md-logo img,.md-header__button.md-logo svg{display:block;width:1.2rem;height:1.2rem;fill:currentColor}@media screen and (min-width:60em){.md-header__button[for=__search]{display:none}}.no-js .md-header__button[for=__search]{display:none}[dir=rtl] .md-header__button[for=__search] svg{transform:scaleX(-1)}@media screen and (min-width:76.25em){.md-header__button[for=__drawer]{display:none}}.md-header__topic{position:absolute;display:flex;max-width:100%;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-header__topic+.md-header__topic{z-index:-1;transform:translateX(1.25rem);opacity:0;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;pointer-events:none}[dir=rtl] .md-header__topic+.md-header__topic{transform:translateX(-1.25rem)}.md-header__title{flex-grow:1;height:2.4rem;margin-right:.4rem;margin-left:1rem;font-size:.9rem;line-height:2.4rem}.md-header__title[data-md-state=active] .md-header__topic{z-index:-1;transform:translateX(-1.25rem);opacity:0;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;pointer-events:none}[dir=rtl] .md-header__title[data-md-state=active] .md-header__topic{transform:translateX(1.25rem)}.md-header__title[data-md-state=active] .md-header__topic+.md-header__topic{z-index:0;transform:translateX(0);opacity:1;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;pointer-events:auto}.md-header__title>.md-header__ellipsis{position:relative;width:100%;height:100%}.md-header__option{display:flex;flex-shrink:0;max-width:100%;white-space:nowrap;transition:max-width 0ms .25s,opacity .25s .25s}[data-md-toggle=search]:checked~.md-header .md-header__option{max-width:0;opacity:0;transition:max-width 0ms,opacity 0ms}.md-header__source{display:none}@media screen and (min-width:60em){.md-header__source{display:block;width:11.7rem;max-width:11.7rem;margin-left:1rem}[dir=rtl] .md-header__source{margin-right:1rem;margin-left:0}}@media screen and (min-width:76.25em){.md-header__source{margin-left:1.4rem}[dir=rtl] .md-header__source{margin-right:1.4rem}}.md-footer{color:var(--md-footer-fg-color);background-color:var(--md-footer-bg-color)}@media print{.md-footer{display:none}}.md-footer__inner{padding:.2rem;overflow:auto}.md-footer__link{display:flex;padding-top:1.4rem;padding-bottom:.4rem;transition:opacity .25s}@media screen and (min-width:45em){.md-footer__link{width:50%}}.md-footer__link:focus,.md-footer__link:hover{opacity:.7}.md-footer__link--prev{float:left}@media screen and (max-width:44.9375em){.md-footer__link--prev{width:25%}.md-footer__link--prev .md-footer__title{display:none}}[dir=rtl] .md-footer__link--prev{float:right}[dir=rtl] .md-footer__link--prev svg{transform:scaleX(-1)}.md-footer__link--next{float:right;text-align:right}@media screen and (max-width:44.9375em){.md-footer__link--next{width:75%}}[dir=rtl] .md-footer__link--next{float:left;text-align:left}[dir=rtl] .md-footer__link--next svg{transform:scaleX(-1)}.md-footer__title{position:relative;flex-grow:1;max-width:calc(100% - 2.4rem);padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-footer__button{margin:.2rem;padding:.4rem}.md-footer__direction{position:absolute;right:0;left:0;margin-top:-1rem;padding:0 1rem;font-size:.64rem;opacity:.7}.md-footer-meta{background-color:var(--md-footer-bg-color--dark)}.md-footer-meta__inner{display:flex;flex-wrap:wrap;justify-content:space-between;padding:.2rem}html .md-footer-meta.md-typeset a{color:var(--md-footer-fg-color--light)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:var(--md-footer-fg-color)}.md-footer-copyright{width:100%;margin:auto .6rem;padding:.4rem 0;color:var(--md-footer-fg-color--lighter);font-size:.64rem}@media screen and (min-width:45em){.md-footer-copyright{width:auto}}.md-footer-copyright__highlight{color:var(--md-footer-fg-color--light)}.md-footer-social{margin:0 .4rem;padding:.2rem 0 .6rem}@media screen and (min-width:45em){.md-footer-social{padding:.6rem 0}}.md-footer-social__link{display:inline-block;width:1.6rem;height:1.6rem;text-align:center}.md-footer-social__link:before{line-height:1.9}.md-footer-social__link svg{max-height:.8rem;vertical-align:-25%;fill:currentColor}:root{--md-nav-icon--prev:url("data:image/svg+xml;charset=utf-8,");--md-nav-icon--next:url("data:image/svg+xml;charset=utf-8,");--md-toc-icon:url("data:image/svg+xml;charset=utf-8,")}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{display:block;padding:0 .6rem;overflow:hidden;font-weight:700;text-overflow:ellipsis}.md-nav__title .md-nav__button{display:none}.md-nav__title .md-nav__button img{width:auto;height:100%}.md-nav__title .md-nav__button.md-logo img,.md-nav__title .md-nav__button.md-logo svg{display:block;width:2.4rem;height:2.4rem;fill:currentColor}.md-nav__list{margin:0;padding:0;list-style:none}.md-nav__item{padding:0 .6rem}.md-nav__item .md-nav__item{padding-right:0}[dir=rtl] .md-nav__item .md-nav__item{padding-right:.6rem;padding-left:0}.md-nav__link{display:block;margin-top:.625em;overflow:hidden;text-overflow:ellipsis;cursor:pointer;transition:color 125ms;scroll-snap-align:start}.md-nav__link[data-md-state=blur]{color:var(--md-default-fg-color--light)}.md-nav__item .md-nav__link--active{color:var(--md-typeset-a-color)}.md-nav__item--nested>.md-nav__link{color:inherit}.md-nav__link:focus,.md-nav__link:hover{color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link[for=__toc]{display:none}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{display:block;width:100%;height:100%;-webkit-mask-image:var(--md-toc-icon);mask-image:var(--md-toc-icon);background-color:currentColor}.md-nav--primary .md-nav__link[for=__toc]~.md-nav,.md-nav__source{display:none}@media screen and (max-width:76.1875em){.md-nav--primary,.md-nav--primary .md-nav{position:absolute;top:0;right:0;left:0;z-index:1;display:flex;flex-direction:column;height:100%;background-color:var(--md-default-bg-color)}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}.md-nav--primary .md-nav__title{position:relative;height:5.6rem;padding:3rem .8rem .2rem;color:var(--md-default-fg-color--light);font-weight:400;line-height:2.4rem;white-space:nowrap;background-color:var(--md-default-fg-color--lightest);cursor:pointer}.md-nav--primary .md-nav__title .md-nav__icon{position:absolute;top:.4rem;left:.4rem;display:block;width:1.2rem;height:1.2rem;margin:.2rem}[dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon{right:.4rem;left:auto}.md-nav--primary .md-nav__title .md-nav__icon:after{display:block;width:100%;height:100%;background-color:currentColor;-webkit-mask-image:var(--md-nav-icon--prev);mask-image:var(--md-nav-icon--prev);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-nav--primary .md-nav__title~.md-nav__list{overflow-y:auto;background-color:var(--md-default-bg-color);box-shadow:0 .05rem 0 var(--md-default-fg-color--lightest) inset;-ms-scroll-snap-type:y mandatory;scroll-snap-type:y mandatory;touch-action:pan-y}.md-nav--primary .md-nav__title~.md-nav__list>:first-child{border-top:0}.md-nav--primary .md-nav__title[for=__drawer]{color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color)}.md-nav--primary .md-nav__title .md-logo{position:absolute;top:.2rem;left:.2rem;display:block;margin:.2rem;padding:.4rem}[dir=rtl] .md-nav--primary .md-nav__title .md-logo{right:.2rem;left:auto}.md-nav--primary .md-nav__list{flex:1}.md-nav--primary .md-nav__item{padding:0;border-top:.05rem solid var(--md-default-fg-color--lightest)}.md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__item--active>.md-nav__link{color:var(--md-typeset-a-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:focus,.md-nav--primary .md-nav__item--active>.md-nav__link:hover{color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link{position:relative;margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link .md-nav__icon{position:absolute;top:50%;right:.6rem;width:1.2rem;height:1.2rem;margin-top:-.6rem;color:inherit;font-size:1.2rem}[dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon{right:auto;left:.6rem}.md-nav--primary .md-nav__link .md-nav__icon:after{display:block;width:100%;height:100%;background-color:currentColor;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-nav--primary .md-nav__icon:after{transform:scale(-1)}.md-nav--primary .md-nav--secondary .md-nav__link{position:static}.md-nav--primary .md-nav--secondary .md-nav{position:static;background-color:initial}.md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem;padding-left:0}.md-nav--secondary{background-color:initial}.md-nav__toggle~.md-nav{display:flex;transform:translateX(100%);opacity:0;transition:transform .25s cubic-bezier(.8,0,.6,1),opacity 125ms 50ms}[dir=rtl] .md-nav__toggle~.md-nav{transform:translateX(-100%)}.md-nav__toggle:checked~.md-nav{transform:translateX(0);opacity:1;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 125ms 125ms}.md-nav__toggle:checked~.md-nav>.md-nav__list{-webkit-backface-visibility:hidden;backface-visibility:hidden}}@media screen and (max-width:59.9375em){.md-nav--primary .md-nav__link[for=__toc]{display:block;padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__link[for=__toc]{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--primary .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:flex}.md-nav__source{display:block;padding:0 .2rem;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color--dark)}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-nav--integrated .md-nav__link[for=__toc]{display:block;padding-right:2.4rem;scroll-snap-align:none}[dir=rtl] .md-nav--integrated .md-nav__link[for=__toc]{padding-right:.8rem;padding-left:2.4rem}.md-nav--integrated .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--integrated .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:flex}}@media screen and (min-width:60em){.md-nav--secondary .md-nav__title[for=__toc]{scroll-snap-align:start}.md-nav--secondary .md-nav__title .md-nav__icon{display:none}}@media screen and (min-width:76.25em){.md-nav{transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav--primary .md-nav__title[for=__drawer]{scroll-snap-align:start}.md-nav--primary .md-nav__title .md-nav__icon,.md-nav__toggle~.md-nav{display:none}.md-nav__toggle:checked~.md-nav,.md-nav__toggle:indeterminate~.md-nav{display:block}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--section{display:block;margin:1.25em 0}.md-nav__item--section:last-child{margin-bottom:0}.md-nav__item--section>.md-nav__link{display:none}.md-nav__item--section>.md-nav{display:block}.md-nav__item--section>.md-nav>.md-nav__title{display:block;padding:0;pointer-events:none;scroll-snap-align:start}.md-nav__item--section>.md-nav>.md-nav__list>.md-nav__item{padding:0}.md-nav__icon{float:right;width:.9rem;height:.9rem;transition:transform .25s}[dir=rtl] .md-nav__icon{float:left;transform:rotate(180deg)}.md-nav__icon:after{display:inline-block;width:100%;height:100%;vertical-align:-.1rem;background-color:currentColor;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link .md-nav__icon,.md-nav__item--nested .md-nav__toggle:indeterminate~.md-nav__link .md-nav__icon{transform:rotate(90deg)}.md-nav--lifted>.md-nav__list>.md-nav__item,.md-nav--lifted>.md-nav__list>.md-nav__item--nested,.md-nav--lifted>.md-nav__title{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active{display:block;padding:0}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav>.md-nav__title{display:block;padding:0 .6rem;pointer-events:none;scroll-snap-align:start}.md-nav--lifted>.md-nav__list>.md-nav__item>.md-nav__item{padding-right:.6rem}.md-nav--lifted .md-nav[data-md-level="1"]{display:block}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:block;margin-bottom:1.25em;border-left:.05rem solid var(--md-primary-fg-color)}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav>.md-nav__title{display:none}}:root{--md-search-result-icon:url("data:image/svg+xml;charset=utf-8,")}.md-search{position:relative}@media screen and (min-width:60em){.md-search{padding:.2rem 0}}.no-js .md-search{display:none}.md-search__overlay{z-index:1;opacity:0}@media screen and (max-width:59.9375em){.md-search__overlay{position:absolute;top:.2rem;left:-2.2rem;width:2rem;height:2rem;overflow:hidden;background-color:var(--md-default-bg-color);border-radius:1rem;transform-origin:center;transition:transform .3s .1s,opacity .2s .2s;pointer-events:none}[dir=rtl] .md-search__overlay{right:-2.2rem;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{opacity:1;transition:transform .4s,opacity .1s}}@media screen and (min-width:60em){.md-search__overlay{position:fixed;top:0;left:0;width:0;height:0;background-color:rgba(0,0,0,.54);cursor:pointer;transition:width 0ms .25s,height 0ms .25s,opacity .25s}[dir=rtl] .md-search__overlay{right:0;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{width:100%;height:200vh;opacity:1;transition:width 0ms,height 0ms,opacity .25s}}@media screen and (max-width:29.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(45)}}@media screen and (min-width:30em) and (max-width:44.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(60)}}@media screen and (min-width:45em) and (max-width:59.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(75)}}.md-search__inner{-webkit-backface-visibility:hidden;backface-visibility:hidden}@media screen and (max-width:59.9375em){.md-search__inner{position:fixed;top:0;left:100%;z-index:2;width:100%;height:100%;transform:translateX(5%);opacity:0;transition:right 0ms .3s,left 0ms .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s}[data-md-toggle=search]:checked~.md-header .md-search__inner{left:0;transform:translateX(0);opacity:1;transition:right 0ms 0ms,left 0ms 0ms,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s}[dir=rtl] [data-md-toggle=search]:checked~.md-header .md-search__inner{right:0;left:auto}html [dir=rtl] .md-search__inner{right:100%;left:auto;transform:translateX(-5%)}}@media screen and (min-width:60em){.md-search__inner{position:relative;float:right;width:11.7rem;padding:.1rem 0;transition:width .25s cubic-bezier(.1,.7,.1,1)}[dir=rtl] .md-search__inner{float:left}}@media screen and (min-width:60em) and (max-width:76.1875em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}}@media screen and (min-width:76.25em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}}.md-search__form{position:relative}@media screen and (min-width:60em){.md-search__form{border-radius:.1rem}}.md-search__input{position:relative;z-index:2;padding:0 2.2rem 0 3.6rem;text-overflow:ellipsis;background-color:var(--md-default-bg-color);box-shadow:0 0 .6rem transparent;transition:color .25s,background-color .25s,box-shadow .25s}[dir=rtl] .md-search__input{padding:0 3.6rem 0 2.2rem}.md-search__input::-webkit-input-placeholder{-webkit-transition:color .25s;transition:color .25s}.md-search__input::-moz-placeholder{-moz-transition:color .25s;transition:color .25s}.md-search__input::-ms-input-placeholder{-ms-transition:color .25s;transition:color .25s}.md-search__input::placeholder{transition:color .25s}.md-search__input::-webkit-input-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::-moz-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::-ms-input-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:var(--md-default-fg-color--light)}.md-search__input::-ms-clear{display:none}[data-md-toggle=search]:checked~.md-header .md-search__input{box-shadow:0 0 .6rem rgba(0,0,0,.07)}@media screen and (max-width:59.9375em){.md-search__input{width:100%;height:2.4rem;font-size:.9rem}}@media screen and (min-width:60em){.md-search__input{width:100%;height:1.8rem;padding-left:2.2rem;color:inherit;font-size:.8rem;background-color:rgba(0,0,0,.26);border-radius:.1rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input+.md-search__icon{color:var(--md-primary-bg-color)}.md-search__input::-webkit-input-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::-moz-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::-ms-input-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::placeholder{color:var(--md-primary-bg-color--light)}.md-search__input:hover{background-color:hsla(0,0%,100%,.12)}[data-md-toggle=search]:checked~.md-header .md-search__input{color:var(--md-default-fg-color);text-overflow:clip;background-color:var(--md-default-bg-color);border-radius:.1rem .1rem 0 0}[data-md-toggle=search]:checked~.md-header .md-search__input::-webkit-input-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input::-moz-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input::-ms-input-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:var(--md-default-fg-color--light)}}.md-search__icon{position:absolute;z-index:2;width:1.2rem;height:1.2rem;cursor:pointer;transition:color .25s,opacity .25s}.md-search__icon:hover{opacity:.7}.md-search__icon[for=__search]{top:.3rem;left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem;left:auto}[dir=rtl] .md-search__icon[for=__search] svg{transform:scaleX(-1)}@media screen and (max-width:59.9375em){.md-search__icon[for=__search]{top:.6rem;left:.8rem}[dir=rtl] .md-search__icon[for=__search]{right:.8rem;left:auto}.md-search__icon[for=__search] svg:first-child{display:none}}@media screen and (min-width:60em){.md-search__icon[for=__search]{pointer-events:none}.md-search__icon[for=__search] svg:last-child{display:none}}.md-search__icon[type=reset]{top:.3rem;right:.5rem;transform:scale(.75);opacity:0;transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s;pointer-events:none}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.5rem}@media screen and (max-width:59.9375em){.md-search__icon[type=reset]{top:.6rem;right:.8rem}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.8rem}}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]{transform:scale(1);opacity:1;pointer-events:auto}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]:hover{opacity:.7}.md-search__output{position:absolute;z-index:1;width:100%;overflow:hidden;border-radius:0 0 .1rem .1rem}@media screen and (max-width:59.9375em){.md-search__output{top:2.4rem;bottom:0}}@media screen and (min-width:60em){.md-search__output{top:1.9rem;opacity:0;transition:opacity .4s}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.4);opacity:1}}.md-search__scrollwrap{height:100%;overflow-y:auto;background-color:var(--md-default-bg-color);-webkit-backface-visibility:hidden;backface-visibility:hidden;touch-action:pan-y}@media (-webkit-max-device-pixel-ratio:1), (max-resolution:1dppx){.md-search__scrollwrap{transform:translateZ(0)}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-search__scrollwrap{width:23.4rem}}@media screen and (min-width:76.25em){.md-search__scrollwrap{width:34.4rem}}@media screen and (min-width:60em){.md-search__scrollwrap{max-height:0;scrollbar-width:thin;scrollbar-color:var(--md-default-fg-color--lighter) transparent}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-search__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}}.md-search-result{color:var(--md-default-fg-color);word-break:break-word}.md-search-result__meta{padding:0 .8rem;color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.8rem;background-color:var(--md-default-fg-color--lightest);scroll-snap-align:start}@media screen and (min-width:60em){.md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem;padding-left:0}}.md-search-result__list{margin:0;padding:0;list-style:none}.md-search-result__item{box-shadow:0 -.05rem 0 var(--md-default-fg-color--lightest)}.md-search-result__item:first-child{box-shadow:none}.md-search-result__link{display:block;outline:none;transition:background-color .25s;scroll-snap-align:start}.md-search-result__link:focus,.md-search-result__link:hover{background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:last-child p:last-child{margin-bottom:.6rem}.md-search-result__more summary{display:block;padding:.75em .8rem;color:var(--md-typeset-a-color);font-size:.64rem;outline:0;cursor:pointer;transition:color .25s,background-color .25s;scroll-snap-align:start}@media screen and (min-width:60em){.md-search-result__more summary{padding-left:2.2rem}[dir=rtl] .md-search-result__more summary{padding-right:2.2rem;padding-left:.8rem}}.md-search-result__more summary:focus,.md-search-result__more summary:hover{color:var(--md-accent-fg-color);background-color:var(--md-accent-fg-color--transparent)}.md-search-result__more summary::-webkit-details-marker,.md-search-result__more summary::marker{display:none}.md-search-result__more summary~*>*{opacity:.65}.md-search-result__article{position:relative;padding:0 .8rem;overflow:hidden}@media screen and (min-width:60em){.md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem;padding-left:.8rem}}.md-search-result__article--document .md-search-result__title{margin:.55rem 0;font-weight:400;font-size:.8rem;line-height:1.4}.md-search-result__icon{position:absolute;left:0;width:1.2rem;height:1.2rem;margin:.5rem;color:var(--md-default-fg-color--light)}@media screen and (max-width:59.9375em){.md-search-result__icon{display:none}}.md-search-result__icon:after{display:inline-block;width:100%;height:100%;background-color:currentColor;-webkit-mask-image:var(--md-search-result-icon);mask-image:var(--md-search-result-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-search-result__icon{right:0;left:auto}[dir=rtl] .md-search-result__icon:after{transform:scaleX(-1)}.md-search-result__title{margin:.5em 0;font-weight:700;font-size:.64rem;line-height:1.6}.md-search-result__teaser{display:-webkit-box;max-height:2rem;margin:.5em 0;overflow:hidden;color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.6;text-overflow:ellipsis;-webkit-box-orient:vertical;-webkit-line-clamp:2}@media screen and (max-width:44.9375em){.md-search-result__teaser{max-height:3rem;-webkit-line-clamp:3}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-search-result__teaser{max-height:3rem;-webkit-line-clamp:3}}.md-search-result__teaser mark{text-decoration:underline;background-color:initial}.md-search-result__terms{margin:.5em 0;font-size:.64rem;font-style:italic}.md-search-result mark{color:var(--md-accent-fg-color);background-color:initial}.md-select{position:relative;z-index:1}.md-select__inner{position:absolute;top:calc(100% - .2rem);left:50%;max-height:0;margin-top:.2rem;color:var(--md-default-fg-color);background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 0 .05rem rgba(0,0,0,.25);transform:translate3d(-50%,.3rem,0);opacity:0;transition:transform .25s 375ms,opacity .25s .25s,max-height 0ms .5s}.md-select:focus-within .md-select__inner,.md-select:hover .md-select__inner{max-height:10rem;transform:translate3d(-50%,0,0);opacity:1;transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height .25s}.md-select__inner:after{position:absolute;top:0;left:50%;width:0;height:0;margin-top:-.2rem;margin-left:-.2rem;border-left:.2rem solid transparent;border-right:.2rem solid transparent;border-top:0;border-bottom:.2rem solid transparent;border-bottom-color:var(--md-default-bg-color);content:""}.md-select__list{max-height:inherit;margin:0;padding:0;overflow:auto;font-size:.8rem;list-style-type:none;border-radius:.1rem}.md-select__item{line-height:1.8rem}.md-select__link{display:block;width:100%;padding-right:1.2rem;padding-left:.6rem;cursor:pointer;transition:background-color .25s,color .25s;scroll-snap-align:start}[dir=rtl] .md-select__link{padding-right:.6rem;padding-left:1.2rem}.md-select__link:focus,.md-select__link:hover{background-color:var(--md-default-fg-color--lightest)}.md-sidebar{position:-webkit-sticky;position:sticky;top:2.4rem;flex-shrink:0;align-self:flex-start;width:12.1rem;padding:1.2rem 0}@media print{.md-sidebar{display:none}}@media screen and (max-width:76.1875em){.md-sidebar--primary{position:fixed;top:0;left:-12.1rem;z-index:3;display:block;width:12.1rem;height:100%;background-color:var(--md-default-bg-color);transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s}[dir=rtl] .md-sidebar--primary{right:-12.1rem;left:auto}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.4);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{transform:translateX(-12.1rem)}.md-sidebar--primary .md-sidebar__scrollwrap{position:absolute;top:0;right:0;bottom:0;left:0;margin:0;-ms-scroll-snap-type:none;scroll-snap-type:none;overflow:hidden}}@media screen and (min-width:76.25em){.md-sidebar{height:0}.no-js .md-sidebar{height:auto}}.md-sidebar--secondary{display:none;order:2}@media screen and (min-width:60em){.md-sidebar--secondary{height:0}.no-js .md-sidebar--secondary{height:auto}.md-sidebar--secondary:not([hidden]){display:block}.md-sidebar--secondary .md-sidebar__scrollwrap{touch-action:pan-y}}.md-sidebar__scrollwrap{margin:0 .2rem;overflow-y:auto;-webkit-backface-visibility:hidden;backface-visibility:hidden;scrollbar-width:thin;scrollbar-color:var(--md-default-fg-color--lighter) transparent}.md-sidebar__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-sidebar__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@media screen and (max-width:76.1875em){.md-overlay{position:fixed;top:0;z-index:3;width:0;height:0;background-color:rgba(0,0,0,.54);opacity:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s}[data-md-toggle=drawer]:checked~.md-overlay{width:100%;height:100%;opacity:1;transition:width 0ms,height 0ms,opacity .25s}}@-webkit-keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@-webkit-keyframes md-source__fact--done{0%{transform:translateY(100%);opacity:0}50%{opacity:0}to{transform:translateY(0);opacity:1}}@keyframes md-source__fact--done{0%{transform:translateY(100%);opacity:0}50%{opacity:0}to{transform:translateY(0);opacity:1}}:root{--md-source-forks-icon:url("data:image/svg+xml;charset=utf-8,");--md-source-repositories-icon:url("data:image/svg+xml;charset=utf-8,");--md-source-stars-icon:url("data:image/svg+xml;charset=utf-8,");--md-source-version-icon:url("data:image/svg+xml;charset=utf-8,")}.md-source{display:block;font-size:.65rem;line-height:1.2;white-space:nowrap;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:opacity .25s}.md-source:hover{opacity:.7}.md-source__icon{display:inline-block;width:2rem;height:2.4rem;vertical-align:middle}.md-source__icon svg{margin-top:.6rem;margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem;margin-left:0}.md-source__icon+.md-source__repository{margin-left:-2rem;padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2rem;margin-left:0;padding-right:2rem;padding-left:0}.md-source__repository{display:inline-block;max-width:calc(100% - 1.2rem);margin-left:.6rem;overflow:hidden;text-overflow:ellipsis;vertical-align:middle}.md-source__facts{margin:.1rem 0 0;padding:0;overflow:hidden;font-size:.55rem;list-style-type:none;opacity:.75}[data-md-state=done] .md-source__facts{-webkit-animation:md-source__facts--done .25s ease-in;animation:md-source__facts--done .25s ease-in}.md-source__fact{display:inline-block}[data-md-state=done] .md-source__fact{-webkit-animation:md-source__fact--done .4s ease-out;animation:md-source__fact--done .4s ease-out}.md-source__fact:before{display:inline-block;width:.6rem;height:.6rem;margin-right:.1rem;vertical-align:text-top;background-color:currentColor;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-source__fact:nth-child(1n+2):before{margin-left:.4rem}[dir=rtl] .md-source__fact{margin-right:0;margin-left:.1rem}[dir=rtl] .md-source__fact:nth-child(1n+2):before{margin-right:.4rem;margin-left:0}.md-source__fact--version:before{-webkit-mask-image:var(--md-source-version-icon);mask-image:var(--md-source-version-icon)}.md-source__fact--stars:before{-webkit-mask-image:var(--md-source-stars-icon);mask-image:var(--md-source-stars-icon)}.md-source__fact--forks:before{-webkit-mask-image:var(--md-source-forks-icon);mask-image:var(--md-source-forks-icon)}.md-source__fact--repositories:before{-webkit-mask-image:var(--md-source-repositories-icon);mask-image:var(--md-source-repositories-icon)}.md-tabs{width:100%;overflow:auto;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color)}@media print{.md-tabs{display:none}}@media screen and (max-width:76.1875em){.md-tabs{display:none}}.md-tabs[data-md-state=hidden]{pointer-events:none}.md-tabs__list{margin:0 0 0 .2rem;padding:0;white-space:nowrap;list-style:none;contain:content}[dir=rtl] .md-tabs__list{margin-right:.2rem;margin-left:0}.md-tabs__item{display:inline-block;height:2.4rem;padding-right:.6rem;padding-left:.6rem}.md-tabs__link{display:block;margin-top:.8rem;font-size:.7rem;-webkit-backface-visibility:hidden;backface-visibility:hidden;opacity:.7;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s}.md-tabs__link--active,.md-tabs__link:focus,.md-tabs__link:hover{color:inherit;opacity:1}.md-tabs__item:nth-child(2) .md-tabs__link{transition-delay:20ms}.md-tabs__item:nth-child(3) .md-tabs__link{transition-delay:40ms}.md-tabs__item:nth-child(4) .md-tabs__link{transition-delay:60ms}.md-tabs__item:nth-child(5) .md-tabs__link{transition-delay:80ms}.md-tabs__item:nth-child(6) .md-tabs__link{transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{transition-delay:.3s}.md-tabs[data-md-state=hidden] .md-tabs__link{transform:translateY(50%);opacity:0;transition:transform 0ms .1s,opacity .1s}.md-top{position:-webkit-sticky;position:sticky;bottom:.4rem;z-index:1;float:right;margin:-2.8rem .4rem .4rem;padding:.4rem;color:var(--md-primary-bg-color);background:var(--md-primary-fg-color);border-radius:100%;outline:none;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 .025rem .05rem rgba(0,0,0,.1);transform:translateY(0);transition:opacity 125ms,transform 125ms cubic-bezier(.4,0,.2,1),background-color 125ms}[dir=rtl] .md-top{float:left}.md-top[data-md-state=hidden]{transform:translateY(-.2rem);opacity:0}.md-top:focus,.md-top:hover{background:var(--md-accent-fg-color);transform:scale(1.1)}:root{--md-version-icon:url("data:image/svg+xml;charset=utf-8,")}.md-version{flex-shrink:0;height:2.4rem;font-size:.8rem}.md-version__current{position:relative;top:.05rem;margin-right:.4rem;margin-left:1.4rem}[dir=rtl] .md-version__current{margin-right:1.4rem;margin-left:.4rem}.md-version__current:after{display:inline-block;width:.4rem;height:.6rem;margin-left:.4rem;background-color:currentColor;-webkit-mask-image:var(--md-version-icon);mask-image:var(--md-version-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;content:""}[dir=rtl] .md-version__current:after{margin-right:.4rem;margin-left:0}.md-version__list{position:absolute;top:.15rem;z-index:1;max-height:1.8rem;margin:.2rem .8rem;padding:0;overflow:auto;color:var(--md-default-fg-color);list-style-type:none;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 0 .05rem rgba(0,0,0,.25);opacity:0;transition:max-height 0ms .5s,opacity .25s .25s;-ms-scroll-snap-type:y mandatory;scroll-snap-type:y mandatory}.md-version__list:focus-within,.md-version__list:hover{max-height:10rem;opacity:1;transition:max-height .25s,opacity .25s}.md-version__item{line-height:1.8rem}.md-version__link{display:block;width:100%;padding-right:1.2rem;padding-left:.6rem;white-space:nowrap;cursor:pointer;transition:color .25s,background-color .25s;scroll-snap-align:start}[dir=rtl] .md-version__link{padding-right:.6rem;padding-left:1.2rem}.md-version__link:focus,.md-version__link:hover{background-color:var(--md-default-fg-color--lightest)}:root{--md-admonition-icon--note:url("data:image/svg+xml;charset=utf-8,");--md-admonition-icon--abstract:url("data:image/svg+xml;charset=utf-8,");--md-admonition-icon--info:url("data:image/svg+xml;charset=utf-8,");--md-admonition-icon--tip:url("data:image/svg+xml;charset=utf-8,");--md-admonition-icon--success:url("data:image/svg+xml;charset=utf-8,");--md-admonition-icon--question:url("data:image/svg+xml;charset=utf-8,");--md-admonition-icon--warning:url("data:image/svg+xml;charset=utf-8,");--md-admonition-icon--failure:url("data:image/svg+xml;charset=utf-8,");--md-admonition-icon--danger:url("data:image/svg+xml;charset=utf-8,");--md-admonition-icon--bug:url("data:image/svg+xml;charset=utf-8,");--md-admonition-icon--example:url("data:image/svg+xml;charset=utf-8,");--md-admonition-icon--quote:url("data:image/svg+xml;charset=utf-8,")}.md-typeset .admonition,.md-typeset details{margin:1.5625em 0;padding:0 .6rem;overflow:hidden;color:var(--md-admonition-fg-color);font-size:.64rem;page-break-inside:avoid;background-color:var(--md-admonition-bg-color);border-left:.2rem solid #448aff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 .025rem .05rem rgba(0,0,0,.05)}@media print{.md-typeset .admonition,.md-typeset details{box-shadow:none}}[dir=rtl] .md-typeset .admonition,[dir=rtl] .md-typeset details{border-right:.2rem solid #448aff;border-left:none}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin-top:1em;margin-bottom:1em}.md-typeset .admonition .md-typeset__scrollwrap,.md-typeset details .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset .admonition .md-typeset__table,.md-typeset details .md-typeset__table{padding:0 .6rem}.md-typeset .admonition>.tabbed-set:only-child,.md-typeset details>.tabbed-set:only-child{margin-top:0}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}.md-typeset .admonition-title,.md-typeset summary{position:relative;margin:0 -.6rem 0 -.8rem;padding:.4rem .6rem .4rem 2rem;font-weight:700;background-color:rgba(68,138,255,.1);border-left:.2rem solid #448aff}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{margin:0 -.8rem 0 -.6rem;padding:.4rem 2rem .4rem .6rem;border-right:.2rem solid #448aff;border-left:none}html .md-typeset .admonition-title:last-child,html .md-typeset summary:last-child{margin-bottom:0}.md-typeset .admonition-title:before,.md-typeset summary:before{position:absolute;left:.6rem;width:1rem;height:1rem;background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-typeset .admonition-title:before,[dir=rtl] .md-typeset summary:before{right:.6rem;left:auto}.md-typeset .admonition-title+.tabbed-set:last-child,.md-typeset summary+.tabbed-set:last-child{margin-top:0}.md-typeset .admonition.note,.md-typeset details.note{border-color:#448aff}.md-typeset .note>.admonition-title,.md-typeset .note>summary{background-color:rgba(68,138,255,.1);border-color:#448aff}.md-typeset .note>.admonition-title:before,.md-typeset .note>summary:before{background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.abstract,.md-typeset .admonition.summary,.md-typeset .admonition.tldr,.md-typeset details.abstract,.md-typeset details.summary,.md-typeset details.tldr{border-color:#00b0ff}.md-typeset .abstract>.admonition-title,.md-typeset .abstract>summary,.md-typeset .summary>.admonition-title,.md-typeset .summary>summary,.md-typeset .tldr>.admonition-title,.md-typeset .tldr>summary{background-color:rgba(0,176,255,.1);border-color:#00b0ff}.md-typeset .abstract>.admonition-title:before,.md-typeset .abstract>summary:before,.md-typeset .summary>.admonition-title:before,.md-typeset .summary>summary:before,.md-typeset .tldr>.admonition-title:before,.md-typeset .tldr>summary:before{background-color:#00b0ff;-webkit-mask-image:var(--md-admonition-icon--abstract);mask-image:var(--md-admonition-icon--abstract);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.info,.md-typeset .admonition.todo,.md-typeset details.info,.md-typeset details.todo{border-color:#00b8d4}.md-typeset .info>.admonition-title,.md-typeset .info>summary,.md-typeset .todo>.admonition-title,.md-typeset .todo>summary{background-color:rgba(0,184,212,.1);border-color:#00b8d4}.md-typeset .info>.admonition-title:before,.md-typeset .info>summary:before,.md-typeset .todo>.admonition-title:before,.md-typeset .todo>summary:before{background-color:#00b8d4;-webkit-mask-image:var(--md-admonition-icon--info);mask-image:var(--md-admonition-icon--info);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.hint,.md-typeset .admonition.important,.md-typeset .admonition.tip,.md-typeset details.hint,.md-typeset details.important,.md-typeset details.tip{border-color:#00bfa5}.md-typeset .hint>.admonition-title,.md-typeset .hint>summary,.md-typeset .important>.admonition-title,.md-typeset .important>summary,.md-typeset .tip>.admonition-title,.md-typeset .tip>summary{background-color:rgba(0,191,165,.1);border-color:#00bfa5}.md-typeset .hint>.admonition-title:before,.md-typeset .hint>summary:before,.md-typeset .important>.admonition-title:before,.md-typeset .important>summary:before,.md-typeset .tip>.admonition-title:before,.md-typeset .tip>summary:before{background-color:#00bfa5;-webkit-mask-image:var(--md-admonition-icon--tip);mask-image:var(--md-admonition-icon--tip);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.check,.md-typeset .admonition.done,.md-typeset .admonition.success,.md-typeset details.check,.md-typeset details.done,.md-typeset details.success{border-color:#00c853}.md-typeset .check>.admonition-title,.md-typeset .check>summary,.md-typeset .done>.admonition-title,.md-typeset .done>summary,.md-typeset .success>.admonition-title,.md-typeset .success>summary{background-color:rgba(0,200,83,.1);border-color:#00c853}.md-typeset .check>.admonition-title:before,.md-typeset .check>summary:before,.md-typeset .done>.admonition-title:before,.md-typeset .done>summary:before,.md-typeset .success>.admonition-title:before,.md-typeset .success>summary:before{background-color:#00c853;-webkit-mask-image:var(--md-admonition-icon--success);mask-image:var(--md-admonition-icon--success);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.faq,.md-typeset .admonition.help,.md-typeset .admonition.question,.md-typeset details.faq,.md-typeset details.help,.md-typeset details.question{border-color:#64dd17}.md-typeset .faq>.admonition-title,.md-typeset .faq>summary,.md-typeset .help>.admonition-title,.md-typeset .help>summary,.md-typeset .question>.admonition-title,.md-typeset .question>summary{background-color:rgba(100,221,23,.1);border-color:#64dd17}.md-typeset .faq>.admonition-title:before,.md-typeset .faq>summary:before,.md-typeset .help>.admonition-title:before,.md-typeset .help>summary:before,.md-typeset .question>.admonition-title:before,.md-typeset .question>summary:before{background-color:#64dd17;-webkit-mask-image:var(--md-admonition-icon--question);mask-image:var(--md-admonition-icon--question);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.attention,.md-typeset .admonition.caution,.md-typeset .admonition.warning,.md-typeset details.attention,.md-typeset details.caution,.md-typeset details.warning{border-color:#ff9100}.md-typeset .attention>.admonition-title,.md-typeset .attention>summary,.md-typeset .caution>.admonition-title,.md-typeset .caution>summary,.md-typeset .warning>.admonition-title,.md-typeset .warning>summary{background-color:rgba(255,145,0,.1);border-color:#ff9100}.md-typeset .attention>.admonition-title:before,.md-typeset .attention>summary:before,.md-typeset .caution>.admonition-title:before,.md-typeset .caution>summary:before,.md-typeset .warning>.admonition-title:before,.md-typeset .warning>summary:before{background-color:#ff9100;-webkit-mask-image:var(--md-admonition-icon--warning);mask-image:var(--md-admonition-icon--warning);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.fail,.md-typeset .admonition.failure,.md-typeset .admonition.missing,.md-typeset details.fail,.md-typeset details.failure,.md-typeset details.missing{border-color:#ff5252}.md-typeset .fail>.admonition-title,.md-typeset .fail>summary,.md-typeset .failure>.admonition-title,.md-typeset .failure>summary,.md-typeset .missing>.admonition-title,.md-typeset .missing>summary{background-color:rgba(255,82,82,.1);border-color:#ff5252}.md-typeset .fail>.admonition-title:before,.md-typeset .fail>summary:before,.md-typeset .failure>.admonition-title:before,.md-typeset .failure>summary:before,.md-typeset .missing>.admonition-title:before,.md-typeset .missing>summary:before{background-color:#ff5252;-webkit-mask-image:var(--md-admonition-icon--failure);mask-image:var(--md-admonition-icon--failure);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.danger,.md-typeset .admonition.error,.md-typeset details.danger,.md-typeset details.error{border-color:#ff1744}.md-typeset .danger>.admonition-title,.md-typeset .danger>summary,.md-typeset .error>.admonition-title,.md-typeset .error>summary{background-color:rgba(255,23,68,.1);border-color:#ff1744}.md-typeset .danger>.admonition-title:before,.md-typeset .danger>summary:before,.md-typeset .error>.admonition-title:before,.md-typeset .error>summary:before{background-color:#ff1744;-webkit-mask-image:var(--md-admonition-icon--danger);mask-image:var(--md-admonition-icon--danger);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.bug,.md-typeset details.bug{border-color:#f50057}.md-typeset .bug>.admonition-title,.md-typeset .bug>summary{background-color:rgba(245,0,87,.1);border-color:#f50057}.md-typeset .bug>.admonition-title:before,.md-typeset .bug>summary:before{background-color:#f50057;-webkit-mask-image:var(--md-admonition-icon--bug);mask-image:var(--md-admonition-icon--bug);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.example,.md-typeset details.example{border-color:#7c4dff}.md-typeset .example>.admonition-title,.md-typeset .example>summary{background-color:rgba(124,77,255,.1);border-color:#7c4dff}.md-typeset .example>.admonition-title:before,.md-typeset .example>summary:before{background-color:#7c4dff;-webkit-mask-image:var(--md-admonition-icon--example);mask-image:var(--md-admonition-icon--example);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.cite,.md-typeset .admonition.quote,.md-typeset details.cite,.md-typeset details.quote{border-color:#9e9e9e}.md-typeset .cite>.admonition-title,.md-typeset .cite>summary,.md-typeset .quote>.admonition-title,.md-typeset .quote>summary{background-color:hsla(0,0%,62%,.1);border-color:#9e9e9e}.md-typeset .cite>.admonition-title:before,.md-typeset .cite>summary:before,.md-typeset .quote>.admonition-title:before,.md-typeset .quote>summary:before{background-color:#9e9e9e;-webkit-mask-image:var(--md-admonition-icon--quote);mask-image:var(--md-admonition-icon--quote);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}:root{--md-footnotes-icon:url("data:image/svg+xml;charset=utf-8,")}.md-typeset [id^="fnref:"]:target{scroll-margin-top:0;margin-top:-3.4rem;padding-top:3.4rem}.md-typeset [id^="fn:"]:target{scroll-margin-top:0;margin-top:-3.45rem;padding-top:3.45rem}.md-typeset .footnote{color:var(--md-default-fg-color--light);font-size:.64rem}.md-typeset .footnote>ol{margin-left:0}.md-typeset .footnote>ol>li{transition:color 125ms}.md-typeset .footnote>ol>li:target{color:var(--md-default-fg-color)}.md-typeset .footnote>ol>li:hover .footnote-backref,.md-typeset .footnote>ol>li:target .footnote-backref{transform:translateX(0);opacity:1}.md-typeset .footnote>ol>li>:first-child{margin-top:0}.md-typeset .footnote-backref{display:inline-block;color:var(--md-typeset-a-color);font-size:0;vertical-align:text-bottom;transform:translateX(.25rem);opacity:0;transition:color .25s,transform .25s .25s,opacity 125ms .25s}@media print{.md-typeset .footnote-backref{color:var(--md-typeset-a-color);transform:translateX(0);opacity:1}}[dir=rtl] .md-typeset .footnote-backref{transform:translateX(-.25rem)}.md-typeset .footnote-backref:hover{color:var(--md-accent-fg-color)}.md-typeset .footnote-backref:before{display:inline-block;width:.8rem;height:.8rem;background-color:currentColor;-webkit-mask-image:var(--md-footnotes-icon);mask-image:var(--md-footnotes-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-typeset .footnote-backref:before svg{transform:scaleX(-1)}.md-typeset .headerlink{display:inline-block;margin-left:.5rem;color:var(--md-default-fg-color--lighter);opacity:0;transition:color .25s,opacity 125ms}@media print{.md-typeset .headerlink{display:none}}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem;margin-left:0}.md-typeset .headerlink:focus,.md-typeset :hover>.headerlink,.md-typeset :target>.headerlink{opacity:1;transition:color .25s,opacity 125ms}.md-typeset .headerlink:focus,.md-typeset .headerlink:hover,.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset :target{scroll-margin-top:3.6rem}.md-typeset h1:target,.md-typeset h2:target,.md-typeset h3:target{scroll-margin-top:0}.md-typeset h1:target:before,.md-typeset h2:target:before,.md-typeset h3:target:before{display:block;margin-top:-3.4rem;padding-top:3.4rem;content:""}.md-typeset h4:target{scroll-margin-top:0}.md-typeset h4:target:before{display:block;margin-top:-3.45rem;padding-top:3.45rem;content:""}.md-typeset h5:target,.md-typeset h6:target{scroll-margin-top:0}.md-typeset h5:target:before,.md-typeset h6:target:before{display:block;margin-top:-3.6rem;padding-top:3.6rem;content:""}.md-typeset div.arithmatex{overflow:auto}@media screen and (max-width:44.9375em){.md-typeset div.arithmatex{margin:0 -.8rem}}.md-typeset div.arithmatex>*{width:-webkit-min-content;width:-moz-min-content;width:min-content;margin:1em auto!important;padding:0 .8rem;touch-action:auto}.md-typeset .critic.comment,.md-typeset del.critic,.md-typeset ins.critic{-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset del.critic{background-color:var(--md-typeset-del-color)}.md-typeset ins.critic{background-color:var(--md-typeset-ins-color)}.md-typeset .critic.comment{color:var(--md-code-hl-comment-color)}.md-typeset .critic.comment:before{content:"/* "}.md-typeset .critic.comment:after{content:" */"}.md-typeset .critic.block{display:block;margin:1em 0;padding-right:.8rem;padding-left:.8rem;overflow:auto;box-shadow:none}.md-typeset .critic.block>:first-child{margin-top:.5em}.md-typeset .critic.block>:last-child{margin-bottom:.5em}:root{--md-details-icon:url("data:image/svg+xml;charset=utf-8,")}.md-typeset details{display:flow-root;padding-top:0;overflow:visible}.md-typeset details[open]>summary:after{transform:rotate(90deg)}.md-typeset details:not([open]){padding-bottom:0;box-shadow:none}.md-typeset details:not([open])>summary{border-radius:.1rem}.md-typeset details:after{display:table;content:""}.md-typeset summary{display:block;min-height:1rem;padding:.4rem 1.8rem .4rem 2rem;border-top-left-radius:.1rem;border-top-right-radius:.1rem;cursor:pointer}[dir=rtl] .md-typeset summary{padding:.4rem 2.2rem .4rem 1.8rem}.md-typeset summary:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}.md-typeset summary:after{position:absolute;top:.4rem;right:.4rem;width:1rem;height:1rem;background-color:currentColor;-webkit-mask-image:var(--md-details-icon);mask-image:var(--md-details-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transform:rotate(0deg);transition:transform .25s;content:""}[dir=rtl] .md-typeset summary:after{right:auto;left:.4rem;transform:rotate(180deg)}.md-typeset summary::-webkit-details-marker,.md-typeset summary::marker{display:none}.md-typeset .emojione,.md-typeset .gemoji,.md-typeset .twemoji{display:inline-flex;height:1.125em;vertical-align:text-top}.md-typeset .emojione svg,.md-typeset .gemoji svg,.md-typeset .twemoji svg{width:1.125em;max-height:100%;fill:currentColor}.highlight .o,.highlight .ow{color:var(--md-code-hl-operator-color)}.highlight .p{color:var(--md-code-hl-punctuation-color)}.highlight .cpf,.highlight .l,.highlight .s,.highlight .s1,.highlight .s2,.highlight .sb,.highlight .sc,.highlight .si,.highlight .ss{color:var(--md-code-hl-string-color)}.highlight .cp,.highlight .se,.highlight .sh,.highlight .sr,.highlight .sx{color:var(--md-code-hl-special-color)}.highlight .il,.highlight .m,.highlight .mb,.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:var(--md-code-hl-number-color)}.highlight .k,.highlight .kd,.highlight .kn,.highlight .kp,.highlight .kr,.highlight .kt{color:var(--md-code-hl-keyword-color)}.highlight .kc,.highlight .n{color:var(--md-code-hl-name-color)}.highlight .bp,.highlight .nb,.highlight .no{color:var(--md-code-hl-constant-color)}.highlight .nc,.highlight .ne,.highlight .nf,.highlight .nn{color:var(--md-code-hl-function-color)}.highlight .nd,.highlight .ni,.highlight .nl,.highlight .nt{color:var(--md-code-hl-keyword-color)}.highlight .c,.highlight .c1,.highlight .ch,.highlight .cm,.highlight .cs,.highlight .sd{color:var(--md-code-hl-comment-color)}.highlight .na,.highlight .nv,.highlight .vc,.highlight .vg,.highlight .vi{color:var(--md-code-hl-variable-color)}.highlight .ge,.highlight .gh,.highlight .go,.highlight .gp,.highlight .gr,.highlight .gs,.highlight .gt,.highlight .gu{color:var(--md-code-hl-generic-color)}.highlight .gd,.highlight .gi{margin:0 -.125em;padding:0 .125em;border-radius:.1rem}.highlight .gd{background-color:var(--md-typeset-del-color)}.highlight .gi{background-color:var(--md-typeset-ins-color)}.highlight .hll{display:block;margin:0 -1.1764705882em;padding:0 1.1764705882em;background-color:var(--md-code-hl-color)}.highlight [data-linenos]:before{position:-webkit-sticky;position:sticky;left:-1.1764705882em;float:left;margin-right:1.1764705882em;margin-left:-1.1764705882em;padding-left:1.1764705882em;color:var(--md-default-fg-color--light);background-color:var(--md-code-bg-color);box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;content:attr(data-linenos);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.highlighttable{display:flow-root;overflow:hidden}.highlighttable tbody,.highlighttable td{display:block;padding:0}.highlighttable tr{display:flex}.highlighttable pre{margin:0}.highlighttable .linenos{padding:.7720588235em 0 .7720588235em 1.1764705882em;font-size:.85em;background-color:var(--md-code-bg-color);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.highlighttable .linenodiv{padding-right:.5882352941em;box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset}.highlighttable .linenodiv pre{color:var(--md-default-fg-color--light);text-align:right}.highlighttable .code{flex:1;overflow:hidden}.md-typeset .highlighttable{margin:1em 0;direction:ltr;border-radius:.1rem}.md-typeset .highlighttable code{border-radius:0}@media screen and (max-width:44.9375em){.md-typeset>.highlight{margin:1em -.8rem}.md-typeset>.highlight .hll{margin:0 -.8rem;padding:0 .8rem}.md-typeset>.highlight code{border-radius:0}.md-typeset>.highlighttable{margin:1em -.8rem;border-radius:0}.md-typeset>.highlighttable .hll{margin:0 -.8rem;padding:0 .8rem}}.md-typeset .keys kbd:after,.md-typeset .keys kbd:before{position:relative;margin:0;color:inherit;-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial}.md-typeset .keys span{padding:0 .2em;color:var(--md-default-fg-color--light)}.md-typeset .keys .key-alt:before,.md-typeset .keys .key-left-alt:before,.md-typeset .keys .key-right-alt:before{padding-right:.4em;content:"⎇"}.md-typeset .keys .key-command:before,.md-typeset .keys .key-left-command:before,.md-typeset .keys .key-right-command:before{padding-right:.4em;content:"⌘"}.md-typeset .keys .key-control:before,.md-typeset .keys .key-left-control:before,.md-typeset .keys .key-right-control:before{padding-right:.4em;content:"⌃"}.md-typeset .keys .key-left-meta:before,.md-typeset .keys .key-meta:before,.md-typeset .keys .key-right-meta:before{padding-right:.4em;content:"◆"}.md-typeset .keys .key-left-option:before,.md-typeset .keys .key-option:before,.md-typeset .keys .key-right-option:before{padding-right:.4em;content:"⌥"}.md-typeset .keys .key-left-shift:before,.md-typeset .keys .key-right-shift:before,.md-typeset .keys .key-shift:before{padding-right:.4em;content:"⇧"}.md-typeset .keys .key-left-super:before,.md-typeset .keys .key-right-super:before,.md-typeset .keys .key-super:before{padding-right:.4em;content:"❖"}.md-typeset .keys .key-left-windows:before,.md-typeset .keys .key-right-windows:before,.md-typeset .keys .key-windows:before{padding-right:.4em;content:"⊞"}.md-typeset .keys .key-arrow-down:before{padding-right:.4em;content:"↓"}.md-typeset .keys .key-arrow-left:before{padding-right:.4em;content:"←"}.md-typeset .keys .key-arrow-right:before{padding-right:.4em;content:"→"}.md-typeset .keys .key-arrow-up:before{padding-right:.4em;content:"↑"}.md-typeset .keys .key-backspace:before{padding-right:.4em;content:"⌫"}.md-typeset .keys .key-backtab:before{padding-right:.4em;content:"⇤"}.md-typeset .keys .key-caps-lock:before{padding-right:.4em;content:"⇪"}.md-typeset .keys .key-clear:before{padding-right:.4em;content:"⌧"}.md-typeset .keys .key-context-menu:before{padding-right:.4em;content:"☰"}.md-typeset .keys .key-delete:before{padding-right:.4em;content:"⌦"}.md-typeset .keys .key-eject:before{padding-right:.4em;content:"⏏"}.md-typeset .keys .key-end:before{padding-right:.4em;content:"⤓"}.md-typeset .keys .key-escape:before{padding-right:.4em;content:"⎋"}.md-typeset .keys .key-home:before{padding-right:.4em;content:"⤒"}.md-typeset .keys .key-insert:before{padding-right:.4em;content:"⎀"}.md-typeset .keys .key-page-down:before{padding-right:.4em;content:"⇟"}.md-typeset .keys .key-page-up:before{padding-right:.4em;content:"⇞"}.md-typeset .keys .key-print-screen:before{padding-right:.4em;content:"⎙"}.md-typeset .keys .key-tab:after{padding-left:.4em;content:"⇥"}.md-typeset .keys .key-num-enter:after{padding-left:.4em;content:"⌤"}.md-typeset .keys .key-enter:after{padding-left:.4em;content:"⏎"}.md-typeset .tabbed-content{display:none;order:99;width:100%;box-shadow:0 -.05rem var(--md-default-fg-color--lightest)}@media print{.md-typeset .tabbed-content{display:block;order:0}}.md-typeset .tabbed-content>.highlight:only-child pre,.md-typeset .tabbed-content>.highlighttable:only-child,.md-typeset .tabbed-content>pre:only-child{margin:0}.md-typeset .tabbed-content>.highlight:only-child pre>code,.md-typeset .tabbed-content>.highlighttable:only-child>code,.md-typeset .tabbed-content>pre:only-child>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-content>.tabbed-set{margin:0}.md-typeset .tabbed-set{position:relative;display:flex;flex-wrap:wrap;margin:1em 0;border-radius:.1rem}.md-typeset .tabbed-set>input{position:absolute;width:0;height:0;opacity:0}.md-typeset .tabbed-set>input:checked+label{color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color)}.md-typeset .tabbed-set>input:checked+label+.tabbed-content{display:block}.md-typeset .tabbed-set>input:focus+label{outline-style:auto}.md-typeset .tabbed-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.md-typeset .tabbed-set>label{z-index:1;width:auto;padding:.9375em 1.25em .78125em;color:var(--md-default-fg-color--light);font-weight:700;font-size:.64rem;border-bottom:.1rem solid transparent;cursor:pointer;transition:color .25s}.md-typeset .tabbed-set>label:hover{color:var(--md-accent-fg-color)}:root{--md-tasklist-icon:url("data:image/svg+xml;charset=utf-8,");--md-tasklist-icon--checked:url("data:image/svg+xml;charset=utf-8,")}.md-typeset .task-list-item{position:relative;list-style-type:none}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em;left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em;left:auto}.md-typeset .task-list-control [type=checkbox]{z-index:-1;opacity:0}.md-typeset .task-list-indicator:before{position:absolute;top:.15em;left:-1.5em;width:1.25em;height:1.25em;background-color:var(--md-default-fg-color--lightest);-webkit-mask-image:var(--md-tasklist-icon);mask-image:var(--md-tasklist-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-typeset .task-list-indicator:before{right:-1.5em;left:auto}.md-typeset [type=checkbox]:checked+.task-list-indicator:before{background-color:#00e676;-webkit-mask-image:var(--md-tasklist-icon--checked);mask-image:var(--md-tasklist-icon--checked)}@media screen and (min-width:45em){.md-typeset .inline{float:left;width:11.7rem;margin-top:0;margin-right:.8rem;margin-bottom:.8rem}.md-typeset .inline.end,[dir=rtl] .md-typeset .inline{float:right;margin-right:0;margin-left:.8rem}[dir=rtl] .md-typeset .inline.end{float:left;margin-right:.8rem;margin-left:0}} +/*# sourceMappingURL=main.33e2939f.min.css.map */ \ No newline at end of file diff --git a/assets/stylesheets/main.33e2939f.min.css.map b/assets/stylesheets/main.33e2939f.min.css.map new file mode 100644 index 000000000..2b31341f1 --- /dev/null +++ b/assets/stylesheets/main.33e2939f.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["src/assets/stylesheets/main/extensions/pymdownx/_keys.scss","src/assets/stylesheets/main.scss","src/assets/stylesheets/main/_reset.scss","src/assets/stylesheets/main/_colors.scss","src/assets/stylesheets/main/_icons.scss","src/assets/stylesheets/main/_typeset.scss","src/assets/stylesheets/utilities/_break.scss","node_modules/material-shadows/material-shadows.scss","src/assets/stylesheets/main/layout/_base.scss","src/assets/stylesheets/main/layout/_announce.scss","src/assets/stylesheets/main/layout/_clipboard.scss","src/assets/stylesheets/main/layout/_content.scss","src/assets/stylesheets/main/layout/_dialog.scss","src/assets/stylesheets/main/layout/_form.scss","src/assets/stylesheets/main/layout/_header.scss","src/assets/stylesheets/main/layout/_footer.scss","src/assets/stylesheets/main/layout/_nav.scss","src/assets/stylesheets/main/layout/_search.scss","src/assets/stylesheets/main/layout/_select.scss","src/assets/stylesheets/main/layout/_sidebar.scss","src/assets/stylesheets/main/layout/_source.scss","src/assets/stylesheets/main/layout/_tabs.scss","src/assets/stylesheets/main/layout/_top.scss","src/assets/stylesheets/main/layout/_version.scss","src/assets/stylesheets/main/extensions/markdown/_admonition.scss","node_modules/material-design-color/material-color.scss","src/assets/stylesheets/main/extensions/markdown/_footnotes.scss","src/assets/stylesheets/main/extensions/markdown/_toc.scss","src/assets/stylesheets/main/extensions/pymdownx/_arithmatex.scss","src/assets/stylesheets/main/extensions/pymdownx/_critic.scss","src/assets/stylesheets/main/extensions/pymdownx/_details.scss","src/assets/stylesheets/main/extensions/pymdownx/_emoji.scss","src/assets/stylesheets/main/extensions/pymdownx/_highlight.scss","src/assets/stylesheets/main/extensions/pymdownx/_tabbed.scss","src/assets/stylesheets/main/extensions/pymdownx/_tasklist.scss","src/assets/stylesheets/main/_modifiers.scss"],"names":[],"mappings":"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","file":"src/assets/stylesheets/main.scss","sourcesContent":["////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n // Keyboard key\n .keys {\n\n // Keyboard key icon\n kbd::before,\n kbd::after {\n position: relative;\n margin: 0;\n color: inherit;\n -moz-osx-font-smoothing: initial;\n -webkit-font-smoothing: initial;\n }\n\n // Surrounding text\n span {\n padding: 0 px2em(3.2px);\n color: var(--md-default-fg-color--light);\n }\n\n // Define keyboard keys with left icon\n @each $name, $code in (\n\n // Modifiers\n \"alt\": \"\\2387\",\n \"left-alt\": \"\\2387\",\n \"right-alt\": \"\\2387\",\n \"command\": \"\\2318\",\n \"left-command\": \"\\2318\",\n \"right-command\": \"\\2318\",\n \"control\": \"\\2303\",\n \"left-control\": \"\\2303\",\n \"right-control\": \"\\2303\",\n \"meta\": \"\\25C6\",\n \"left-meta\": \"\\25C6\",\n \"right-meta\": \"\\25C6\",\n \"option\": \"\\2325\",\n \"left-option\": \"\\2325\",\n \"right-option\": \"\\2325\",\n \"shift\": \"\\21E7\",\n \"left-shift\": \"\\21E7\",\n \"right-shift\": \"\\21E7\",\n \"super\": \"\\2756\",\n \"left-super\": \"\\2756\",\n \"right-super\": \"\\2756\",\n \"windows\": \"\\229E\",\n \"left-windows\": \"\\229E\",\n \"right-windows\": \"\\229E\",\n\n // Other keys\n \"arrow-down\": \"\\2193\",\n \"arrow-left\": \"\\2190\",\n \"arrow-right\": \"\\2192\",\n \"arrow-up\": \"\\2191\",\n \"backspace\": \"\\232B\",\n \"backtab\": \"\\21E4\",\n \"caps-lock\": \"\\21EA\",\n \"clear\": \"\\2327\",\n \"context-menu\": \"\\2630\",\n \"delete\": \"\\2326\",\n \"eject\": \"\\23CF\",\n \"end\": \"\\2913\",\n \"escape\": \"\\238B\",\n \"home\": \"\\2912\",\n \"insert\": \"\\2380\",\n \"page-down\": \"\\21DF\",\n \"page-up\": \"\\21DE\",\n \"print-screen\": \"\\2399\"\n ) {\n .key-#{$name} {\n &::before {\n padding-right: px2em(6.4px);\n content: $code;\n }\n }\n }\n\n // Define keyboard keys with right icon\n @each $name, $code in (\n \"tab\": \"\\21E5\",\n \"num-enter\": \"\\2324\",\n \"enter\": \"\\23CE\"\n ) {\n .key-#{$name} {\n &::after {\n padding-left: px2em(6.4px);\n content: $code;\n }\n }\n }\n }\n}\n","@charset \"UTF-8\";\nhtml {\n box-sizing: border-box;\n text-size-adjust: none;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: inherit;\n}\n\nbody {\n margin: 0;\n}\n\na,\nbutton,\nlabel,\ninput {\n -webkit-tap-highlight-color: transparent;\n}\n\na {\n color: inherit;\n text-decoration: none;\n}\n\nhr {\n display: block;\n box-sizing: content-box;\n height: 0.05rem;\n padding: 0;\n overflow: visible;\n border: 0;\n}\n\nsmall {\n font-size: 80%;\n}\n\nsub,\nsup {\n line-height: 1em;\n}\n\nimg {\n border-style: none;\n}\n\ntable {\n border-collapse: separate;\n border-spacing: 0;\n}\n\ntd,\nth {\n font-weight: 400;\n vertical-align: top;\n}\n\nbutton {\n margin: 0;\n padding: 0;\n font-size: inherit;\n background: transparent;\n border: 0;\n}\n\ninput {\n border: 0;\n outline: none;\n}\n\n:root {\n --md-default-fg-color: hsla(0, 0%, 0%, 0.87);\n --md-default-fg-color--light: hsla(0, 0%, 0%, 0.54);\n --md-default-fg-color--lighter: hsla(0, 0%, 0%, 0.32);\n --md-default-fg-color--lightest: hsla(0, 0%, 0%, 0.07);\n --md-default-bg-color: hsla(0, 0%, 100%, 1);\n --md-default-bg-color--light: hsla(0, 0%, 100%, 0.7);\n --md-default-bg-color--lighter: hsla(0, 0%, 100%, 0.3);\n --md-default-bg-color--lightest: hsla(0, 0%, 100%, 0.12);\n --md-primary-fg-color: hsla(231, 48%, 48%, 1);\n --md-primary-fg-color--light: hsla(231, 44%, 56%, 1);\n --md-primary-fg-color--dark: hsla(232, 54%, 41%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n --md-accent-fg-color: hsla(231, 99%, 66%, 1);\n --md-accent-fg-color--transparent: hsla(231, 99%, 66%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n:root > * {\n --md-code-fg-color: hsla(200, 18%, 26%, 1);\n --md-code-bg-color: hsla(0, 0%, 96%, 1);\n --md-code-hl-color: hsla(60, 100%, 50%, 0.5);\n --md-code-hl-number-color: hsla(0, 67%, 50%, 1);\n --md-code-hl-special-color: hsla(340, 83%, 47%, 1);\n --md-code-hl-function-color: hsla(291, 45%, 50%, 1);\n --md-code-hl-constant-color: hsla(250, 63%, 60%, 1);\n --md-code-hl-keyword-color: hsla(219, 54%, 51%, 1);\n --md-code-hl-string-color: hsla(150, 63%, 30%, 1);\n --md-code-hl-name-color: var(--md-code-fg-color);\n --md-code-hl-operator-color: var(--md-default-fg-color--light);\n --md-code-hl-punctuation-color: var(--md-default-fg-color--light);\n --md-code-hl-comment-color: var(--md-default-fg-color--light);\n --md-code-hl-generic-color: var(--md-default-fg-color--light);\n --md-code-hl-variable-color: var(--md-default-fg-color--light);\n --md-typeset-color: var(--md-default-fg-color);\n --md-typeset-a-color: var(--md-primary-fg-color);\n --md-typeset-mark-color: hsla(60, 100%, 50%, 0.5);\n --md-typeset-del-color: hsla(6, 90%, 60%, 0.15);\n --md-typeset-ins-color: hsla(150, 90%, 44%, 0.15);\n --md-typeset-kbd-color: hsla(0, 0%, 98%, 1);\n --md-typeset-kbd-accent-color: hsla(0, 100%, 100%, 1);\n --md-typeset-kbd-border-color: hsla(0, 0%, 72%, 1);\n --md-admonition-fg-color: var(--md-default-fg-color);\n --md-admonition-bg-color: var(--md-default-bg-color);\n --md-footer-fg-color: hsla(0, 0%, 100%, 1);\n --md-footer-fg-color--light: hsla(0, 0%, 100%, 0.7);\n --md-footer-fg-color--lighter: hsla(0, 0%, 100%, 0.3);\n --md-footer-bg-color: hsla(0, 0%, 0%, 0.87);\n --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.32);\n}\n\n.md-icon svg {\n display: block;\n width: 1.2rem;\n height: 1.2rem;\n fill: currentColor;\n}\n\nbody {\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\nbody,\ninput {\n color: var(--md-typeset-color);\n font-feature-settings: \"kern\", \"liga\";\n font-family: var(--md-text-font-family, _), -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;\n}\n\ncode,\npre,\nkbd {\n color: var(--md-typeset-color);\n font-feature-settings: \"kern\";\n font-family: var(--md-code-font-family, _), SFMono-Regular, Consolas, Menlo, monospace;\n}\n\n:root {\n --md-typeset-table--ascending: svg-load(\"material/arrow-down.svg\");\n --md-typeset-table--descending: svg-load(\"material/arrow-up.svg\");\n}\n\n.md-typeset {\n font-size: 0.8rem;\n line-height: 1.6;\n color-adjust: exact;\n}\n@media print {\n .md-typeset {\n font-size: 0.68rem;\n }\n}\n.md-typeset ul,\n.md-typeset ol,\n.md-typeset dl,\n.md-typeset figure,\n.md-typeset blockquote,\n.md-typeset pre {\n display: flow-root;\n margin: 1em 0;\n}\n.md-typeset h1 {\n margin: 0 0 1.25em;\n color: var(--md-default-fg-color--light);\n font-weight: 300;\n font-size: 2em;\n line-height: 1.3;\n letter-spacing: -0.01em;\n}\n.md-typeset h2 {\n margin: 1.6em 0 0.64em;\n font-weight: 300;\n font-size: 1.5625em;\n line-height: 1.4;\n letter-spacing: -0.01em;\n}\n.md-typeset h3 {\n margin: 1.6em 0 0.8em;\n font-weight: 400;\n font-size: 1.25em;\n line-height: 1.5;\n letter-spacing: -0.01em;\n}\n.md-typeset h2 + h3 {\n margin-top: 0.8em;\n}\n.md-typeset h4 {\n margin: 1em 0;\n font-weight: 700;\n letter-spacing: -0.01em;\n}\n.md-typeset h5,\n.md-typeset h6 {\n margin: 1.25em 0;\n color: var(--md-default-fg-color--light);\n font-weight: 700;\n font-size: 0.8em;\n letter-spacing: -0.01em;\n}\n.md-typeset h5 {\n text-transform: uppercase;\n}\n.md-typeset hr {\n display: flow-root;\n margin: 1.5em 0;\n border-bottom: 0.05rem solid var(--md-default-fg-color--lightest);\n}\n.md-typeset a {\n color: var(--md-typeset-a-color);\n word-break: break-word;\n}\n.md-typeset a, .md-typeset a::before {\n transition: color 125ms;\n}\n.md-typeset a:focus, .md-typeset a:hover {\n color: var(--md-accent-fg-color);\n}\n.md-typeset code,\n.md-typeset pre,\n.md-typeset kbd {\n color: var(--md-code-fg-color);\n direction: ltr;\n}\n@media print {\n .md-typeset code,\n.md-typeset pre,\n.md-typeset kbd {\n white-space: pre-wrap;\n }\n}\n.md-typeset code {\n padding: 0 0.2941176471em;\n font-size: 0.85em;\n word-break: break-word;\n background-color: var(--md-code-bg-color);\n border-radius: 0.1rem;\n box-decoration-break: clone;\n}\n.md-typeset code:not(.focus-visible) {\n outline: none;\n -webkit-tap-highlight-color: transparent;\n}\n.md-typeset h1 code,\n.md-typeset h2 code,\n.md-typeset h3 code,\n.md-typeset h4 code,\n.md-typeset h5 code,\n.md-typeset h6 code {\n margin: initial;\n padding: initial;\n background-color: transparent;\n box-shadow: none;\n}\n.md-typeset a code {\n color: currentColor;\n}\n.md-typeset pre {\n position: relative;\n line-height: 1.4;\n}\n.md-typeset pre > code {\n display: block;\n margin: 0;\n padding: 0.7720588235em 1.1764705882em;\n overflow: auto;\n word-break: normal;\n box-shadow: none;\n box-decoration-break: slice;\n touch-action: auto;\n scrollbar-width: thin;\n scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n}\n.md-typeset pre > code:hover {\n scrollbar-color: var(--md-accent-fg-color) transparent;\n}\n.md-typeset pre > code::-webkit-scrollbar {\n width: 0.2rem;\n height: 0.2rem;\n}\n.md-typeset pre > code::-webkit-scrollbar-thumb {\n background-color: var(--md-default-fg-color--lighter);\n}\n.md-typeset pre > code::-webkit-scrollbar-thumb:hover {\n background-color: var(--md-accent-fg-color);\n}\n@media screen and (max-width: 44.9375em) {\n .md-typeset > pre {\n margin: 1em -0.8rem;\n }\n .md-typeset > pre code {\n border-radius: 0;\n }\n}\n.md-typeset kbd {\n display: inline-block;\n padding: 0 0.6666666667em;\n color: var(--md-default-fg-color);\n font-size: 0.75em;\n vertical-align: text-top;\n word-break: break-word;\n background-color: var(--md-typeset-kbd-color);\n border-radius: 0.1rem;\n box-shadow: 0 0.1rem 0 0.05rem var(--md-typeset-kbd-border-color), 0 0.1rem 0 var(--md-typeset-kbd-border-color), 0 -0.1rem 0.2rem var(--md-typeset-kbd-accent-color) inset;\n}\n.md-typeset mark {\n color: inherit;\n word-break: break-word;\n background-color: var(--md-typeset-mark-color);\n box-decoration-break: clone;\n}\n.md-typeset abbr {\n text-decoration: none;\n border-bottom: 0.05rem dotted var(--md-default-fg-color--light);\n cursor: help;\n}\n@media (hover: none) {\n .md-typeset abbr {\n position: relative;\n }\n .md-typeset abbr[title]:focus::after, .md-typeset abbr[title]:hover::after {\n box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);\n position: absolute;\n left: 0;\n display: inline-block;\n width: auto;\n min-width: max-content;\n max-width: 80%;\n margin-top: 2em;\n padding: 0.2rem 0.3rem;\n color: var(--md-default-bg-color);\n font-size: 0.7rem;\n background-color: var(--md-default-fg-color);\n border-radius: 0.1rem;\n content: attr(title);\n }\n}\n.md-typeset small {\n opacity: 0.75;\n}\n.md-typeset sup,\n.md-typeset sub {\n margin-left: 0.078125em;\n}\n[dir=rtl] .md-typeset sup,\n[dir=rtl] .md-typeset sub {\n margin-right: 0.078125em;\n margin-left: initial;\n}\n.md-typeset blockquote {\n padding-left: 0.6rem;\n color: var(--md-default-fg-color--light);\n border-left: 0.2rem solid var(--md-default-fg-color--lighter);\n}\n[dir=rtl] .md-typeset blockquote {\n padding-right: 0.6rem;\n padding-left: initial;\n border-right: 0.2rem solid var(--md-default-fg-color--lighter);\n border-left: initial;\n}\n.md-typeset ul {\n list-style-type: disc;\n}\n.md-typeset ul,\n.md-typeset ol {\n margin-left: 0.625em;\n padding: 0;\n}\n[dir=rtl] .md-typeset ul,\n[dir=rtl] .md-typeset ol {\n margin-right: 0.625em;\n margin-left: initial;\n}\n.md-typeset ul ol,\n.md-typeset ol ol {\n list-style-type: lower-alpha;\n}\n.md-typeset ul ol ol,\n.md-typeset ol ol ol {\n list-style-type: lower-roman;\n}\n.md-typeset ul li,\n.md-typeset ol li {\n margin-bottom: 0.5em;\n margin-left: 1.25em;\n}\n[dir=rtl] .md-typeset ul li,\n[dir=rtl] .md-typeset ol li {\n margin-right: 1.25em;\n margin-left: initial;\n}\n.md-typeset ul li p,\n.md-typeset ul li blockquote,\n.md-typeset ol li p,\n.md-typeset ol li blockquote {\n margin: 0.5em 0;\n}\n.md-typeset ul li:last-child,\n.md-typeset ol li:last-child {\n margin-bottom: 0;\n}\n.md-typeset ul li ul,\n.md-typeset ul li ol,\n.md-typeset ol li ul,\n.md-typeset ol li ol {\n margin: 0.5em 0 0.5em 0.625em;\n}\n[dir=rtl] .md-typeset ul li ul,\n[dir=rtl] .md-typeset ul li ol,\n[dir=rtl] .md-typeset ol li ul,\n[dir=rtl] .md-typeset ol li ol {\n margin-right: 0.625em;\n margin-left: initial;\n}\n.md-typeset dd {\n margin: 1em 0 1.5em 1.875em;\n}\n[dir=rtl] .md-typeset dd {\n margin-right: 1.875em;\n margin-left: initial;\n}\n.md-typeset img,\n.md-typeset svg {\n max-width: 100%;\n height: auto;\n}\n.md-typeset img[align=left],\n.md-typeset svg[align=left] {\n margin: 1em;\n margin-left: 0;\n}\n.md-typeset img[align=right],\n.md-typeset svg[align=right] {\n margin: 1em;\n margin-right: 0;\n}\n.md-typeset img[align]:only-child,\n.md-typeset svg[align]:only-child {\n margin-top: 0;\n}\n.md-typeset figure {\n width: fit-content;\n max-width: 100%;\n margin: 0 auto;\n text-align: center;\n}\n.md-typeset figure img {\n display: block;\n}\n.md-typeset figcaption {\n max-width: 24rem;\n margin: 1em auto 2em;\n font-style: italic;\n}\n.md-typeset iframe {\n max-width: 100%;\n}\n.md-typeset table:not([class]) {\n display: inline-block;\n max-width: 100%;\n overflow: auto;\n font-size: 0.64rem;\n background-color: var(--md-default-bg-color);\n border-radius: 0.1rem;\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.05rem rgba(0, 0, 0, 0.1);\n touch-action: auto;\n}\n@media print {\n .md-typeset table:not([class]) {\n display: table;\n }\n}\n.md-typeset table:not([class]) + * {\n margin-top: 1.5em;\n}\n.md-typeset table:not([class]) th > *:first-child,\n.md-typeset table:not([class]) td > *:first-child {\n margin-top: 0;\n}\n.md-typeset table:not([class]) th > *:last-child,\n.md-typeset table:not([class]) td > *:last-child {\n margin-bottom: 0;\n}\n.md-typeset table:not([class]) th:not([align]),\n.md-typeset table:not([class]) td:not([align]) {\n text-align: left;\n}\n[dir=rtl] .md-typeset table:not([class]) th:not([align]),\n[dir=rtl] .md-typeset table:not([class]) td:not([align]) {\n text-align: right;\n}\n.md-typeset table:not([class]) th {\n min-width: 5rem;\n padding: 0.9375em 1.25em;\n color: var(--md-default-bg-color);\n vertical-align: top;\n background-color: var(--md-default-fg-color--light);\n}\n.md-typeset table:not([class]) th a {\n color: inherit;\n}\n.md-typeset table:not([class]) td {\n padding: 0.9375em 1.25em;\n vertical-align: top;\n border-top: 0.05rem solid var(--md-default-fg-color--lightest);\n}\n.md-typeset table:not([class]) tr {\n transition: background-color 125ms;\n}\n.md-typeset table:not([class]) tr:hover {\n background-color: rgba(0, 0, 0, 0.035);\n box-shadow: 0 0.05rem 0 var(--md-default-bg-color) inset;\n}\n.md-typeset table:not([class]) tr:first-child td {\n border-top: 0;\n}\n.md-typeset table:not([class]) a {\n word-break: normal;\n}\n.md-typeset table th[role=columnheader] {\n cursor: pointer;\n}\n.md-typeset table th[role=columnheader]::after {\n display: inline-block;\n width: 1.2em;\n height: 1.2em;\n margin-left: 0.5em;\n vertical-align: sub;\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n}\n.md-typeset table th[role=columnheader][aria-sort=ascending]::after {\n background-color: currentColor;\n mask-image: var(--md-typeset-table--ascending);\n}\n.md-typeset table th[role=columnheader][aria-sort=descending]::after {\n background-color: currentColor;\n mask-image: var(--md-typeset-table--descending);\n}\n.md-typeset__scrollwrap {\n margin: 1em -0.8rem;\n overflow-x: auto;\n touch-action: auto;\n}\n.md-typeset__table {\n display: inline-block;\n margin-bottom: 0.5em;\n padding: 0 0.8rem;\n}\n@media print {\n .md-typeset__table {\n display: block;\n }\n}\nhtml .md-typeset__table table {\n display: table;\n width: 100%;\n margin: 0;\n overflow: hidden;\n}\n\nhtml {\n height: 100%;\n overflow-x: hidden;\n font-size: 125%;\n}\n@media screen and (min-width: 100em) {\n html {\n font-size: 137.5%;\n }\n}\n@media screen and (min-width: 125em) {\n html {\n font-size: 150%;\n }\n}\n\nbody {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n min-height: 100%;\n font-size: 0.5rem;\n background-color: var(--md-default-bg-color);\n}\n@media print {\n body {\n display: block;\n }\n}\n@media screen and (max-width: 59.9375em) {\n body[data-md-state=lock] {\n position: fixed;\n }\n}\n\n.md-grid {\n max-width: 61rem;\n margin-right: auto;\n margin-left: auto;\n}\n\n.md-container {\n display: flex;\n flex-direction: column;\n flex-grow: 1;\n}\n@media print {\n .md-container {\n display: block;\n }\n}\n\n.md-main {\n flex-grow: 1;\n}\n.md-main__inner {\n display: flex;\n height: 100%;\n margin-top: 1.5rem;\n}\n\n.md-ellipsis {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.md-toggle {\n display: none;\n}\n\n.md-option {\n position: absolute;\n width: 0;\n height: 0;\n opacity: 0;\n}\n.md-option:checked + label:not([hidden]) {\n display: block;\n}\n.md-option.focus-visible + label {\n outline-style: auto;\n}\n\n.md-skip {\n position: fixed;\n z-index: -1;\n margin: 0.5rem;\n padding: 0.3rem 0.5rem;\n color: var(--md-default-bg-color);\n font-size: 0.64rem;\n background-color: var(--md-default-fg-color);\n border-radius: 0.1rem;\n transform: translateY(0.4rem);\n opacity: 0;\n}\n.md-skip:focus {\n z-index: 10;\n transform: translateY(0);\n opacity: 1;\n transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), opacity 175ms 75ms;\n}\n\n@page {\n margin: 25mm;\n}\n.md-announce {\n overflow: auto;\n background-color: var(--md-footer-bg-color);\n}\n@media print {\n .md-announce {\n display: none;\n }\n}\n.md-announce__inner {\n margin: 0.6rem auto;\n padding: 0 0.8rem;\n color: var(--md-footer-fg-color);\n font-size: 0.7rem;\n}\n\n:root {\n --md-clipboard-icon: svg-load(\"material/content-copy.svg\");\n}\n\n.md-clipboard {\n position: absolute;\n top: 0.5em;\n right: 0.5em;\n z-index: 1;\n width: 1.5em;\n height: 1.5em;\n color: var(--md-default-fg-color--lightest);\n border-radius: 0.1rem;\n cursor: pointer;\n transition: color 250ms;\n}\n@media print {\n .md-clipboard {\n display: none;\n }\n}\n.md-clipboard:not(.focus-visible) {\n outline: none;\n -webkit-tap-highlight-color: transparent;\n}\n:hover > .md-clipboard {\n color: var(--md-default-fg-color--light);\n}\n.md-clipboard:focus, .md-clipboard:hover {\n color: var(--md-accent-fg-color);\n}\n.md-clipboard::after {\n display: block;\n width: 1.125em;\n height: 1.125em;\n margin: 0 auto;\n background-color: currentColor;\n mask-image: var(--md-clipboard-icon);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n}\n.md-clipboard--inline {\n cursor: pointer;\n}\n.md-clipboard--inline code {\n transition: color 250ms, background-color 250ms;\n}\n.md-clipboard--inline:focus code, .md-clipboard--inline:hover code {\n color: var(--md-accent-fg-color);\n background-color: var(--md-accent-fg-color--transparent);\n}\n\n.md-content {\n flex-grow: 1;\n overflow: hidden;\n scroll-padding-top: 51.2rem;\n}\n.md-content__inner {\n margin: 0 0.8rem 1.2rem;\n padding-top: 0.6rem;\n}\n@media screen and (min-width: 76.25em) {\n .md-sidebar--primary:not([hidden]) ~ .md-content > .md-content__inner {\n margin-left: 1.2rem;\n }\n [dir=rtl] .md-sidebar--primary:not([hidden]) ~ .md-content > .md-content__inner {\n margin-right: 1.2rem;\n margin-left: 0.8rem;\n }\n .md-sidebar--secondary:not([hidden]) ~ .md-content > .md-content__inner {\n margin-right: 1.2rem;\n }\n [dir=rtl] .md-sidebar--secondary:not([hidden]) ~ .md-content > .md-content__inner {\n margin-right: 0.8rem;\n margin-left: 1.2rem;\n }\n}\n.md-content__inner::before {\n display: block;\n height: 0.4rem;\n content: \"\";\n}\n.md-content__inner > :last-child {\n margin-bottom: 0;\n}\n.md-content__button {\n float: right;\n margin: 0.4rem 0;\n margin-left: 0.4rem;\n padding: 0;\n}\n@media print {\n .md-content__button {\n display: none;\n }\n}\n[dir=rtl] .md-content__button {\n float: left;\n margin-right: 0.4rem;\n margin-left: initial;\n}\n[dir=rtl] .md-content__button svg {\n transform: scaleX(-1);\n}\n.md-typeset .md-content__button {\n color: var(--md-default-fg-color--lighter);\n}\n.md-content__button svg {\n display: inline;\n vertical-align: top;\n}\n\n.md-dialog {\n box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);\n position: fixed;\n right: 0.8rem;\n bottom: 0.8rem;\n left: initial;\n z-index: 2;\n min-width: 11.1rem;\n padding: 0.4rem 0.6rem;\n background-color: var(--md-default-fg-color);\n border-radius: 0.1rem;\n transform: translateY(100%);\n opacity: 0;\n transition: transform 0ms 400ms, opacity 400ms;\n pointer-events: none;\n}\n@media print {\n .md-dialog {\n display: none;\n }\n}\n[dir=rtl] .md-dialog {\n right: initial;\n left: 0.8rem;\n}\n.md-dialog[data-md-state=open] {\n transform: translateY(0);\n opacity: 1;\n transition: transform 400ms cubic-bezier(0.075, 0.85, 0.175, 1), opacity 400ms;\n pointer-events: initial;\n}\n.md-dialog__inner {\n color: var(--md-default-bg-color);\n font-size: 0.7rem;\n}\n\n.md-typeset .md-button {\n display: inline-block;\n padding: 0.625em 2em;\n color: var(--md-primary-fg-color);\n font-weight: 700;\n border: 0.1rem solid currentColor;\n border-radius: 0.1rem;\n transition: color 125ms, background-color 125ms, border-color 125ms;\n}\n.md-typeset .md-button--primary {\n color: var(--md-primary-bg-color);\n background-color: var(--md-primary-fg-color);\n border-color: var(--md-primary-fg-color);\n}\n.md-typeset .md-button:focus, .md-typeset .md-button:hover {\n color: var(--md-accent-bg-color);\n background-color: var(--md-accent-fg-color);\n border-color: var(--md-accent-fg-color);\n}\n.md-typeset .md-input {\n height: 1.8rem;\n padding: 0 0.6rem;\n font-size: 0.8rem;\n border-radius: 0.1rem;\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.1);\n transition: box-shadow 250ms;\n}\n.md-typeset .md-input:focus, .md-typeset .md-input:hover {\n box-shadow: 0 0.4rem 1rem rgba(0, 0, 0, 0.15), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.15);\n}\n.md-typeset .md-input--stretch {\n width: 100%;\n}\n\n.md-header {\n position: sticky;\n top: 0;\n right: 0;\n left: 0;\n z-index: 2;\n color: var(--md-primary-bg-color);\n background-color: var(--md-primary-fg-color);\n box-shadow: 0 0 0.2rem rgba(0, 0, 0, 0), 0 0.2rem 0.4rem rgba(0, 0, 0, 0);\n}\n@media print {\n .md-header {\n display: none;\n }\n}\n.md-header[data-md-state=shadow] {\n box-shadow: 0 0 0.2rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.4rem rgba(0, 0, 0, 0.2);\n transition: transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1), box-shadow 250ms;\n}\n.md-header[data-md-state=hidden] {\n transform: translateY(-100%);\n transition: transform 250ms cubic-bezier(0.8, 0, 0.6, 1), box-shadow 250ms;\n}\n.md-header .focus-visible {\n outline-color: currentColor;\n}\n.md-header__inner {\n display: flex;\n align-items: center;\n padding: 0 0.2rem;\n}\n.md-header__button {\n position: relative;\n z-index: 1;\n margin: 0.2rem;\n padding: 0.4rem;\n color: currentColor;\n vertical-align: middle;\n cursor: pointer;\n transition: opacity 250ms;\n}\n.md-header__button:hover {\n opacity: 0.7;\n}\n.md-header__button:not([hidden]) {\n display: inline-block;\n}\n.md-header__button:not(.focus-visible) {\n outline: none;\n -webkit-tap-highlight-color: transparent;\n}\n.md-header__button.md-logo {\n margin: 0.2rem;\n padding: 0.4rem;\n}\n@media screen and (max-width: 76.1875em) {\n .md-header__button.md-logo {\n display: none;\n }\n}\n.md-header__button.md-logo img,\n.md-header__button.md-logo svg {\n display: block;\n width: 1.2rem;\n height: 1.2rem;\n fill: currentColor;\n}\n@media screen and (min-width: 60em) {\n .md-header__button[for=__search] {\n display: none;\n }\n}\n.no-js .md-header__button[for=__search] {\n display: none;\n}\n[dir=rtl] .md-header__button[for=__search] svg {\n transform: scaleX(-1);\n}\n@media screen and (min-width: 76.25em) {\n .md-header__button[for=__drawer] {\n display: none;\n }\n}\n.md-header__topic {\n position: absolute;\n display: flex;\n max-width: 100%;\n transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms;\n}\n.md-header__topic + .md-header__topic {\n z-index: -1;\n transform: translateX(1.25rem);\n opacity: 0;\n transition: transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1), opacity 150ms;\n pointer-events: none;\n}\n[dir=rtl] .md-header__topic + .md-header__topic {\n transform: translateX(-1.25rem);\n}\n.md-header__title {\n flex-grow: 1;\n height: 2.4rem;\n margin-right: 0.4rem;\n margin-left: 1rem;\n font-size: 0.9rem;\n line-height: 2.4rem;\n}\n.md-header__title[data-md-state=active] .md-header__topic {\n z-index: -1;\n transform: translateX(-1.25rem);\n opacity: 0;\n transition: transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1), opacity 150ms;\n pointer-events: none;\n}\n[dir=rtl] .md-header__title[data-md-state=active] .md-header__topic {\n transform: translateX(1.25rem);\n}\n.md-header__title[data-md-state=active] .md-header__topic + .md-header__topic {\n z-index: 0;\n transform: translateX(0);\n opacity: 1;\n transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms;\n pointer-events: initial;\n}\n.md-header__title > .md-header__ellipsis {\n position: relative;\n width: 100%;\n height: 100%;\n}\n.md-header__option {\n display: flex;\n flex-shrink: 0;\n max-width: 100%;\n white-space: nowrap;\n transition: max-width 0ms 250ms, opacity 250ms 250ms;\n}\n[data-md-toggle=search]:checked ~ .md-header .md-header__option {\n max-width: 0;\n opacity: 0;\n transition: max-width 0ms, opacity 0ms;\n}\n.md-header__source {\n display: none;\n}\n@media screen and (min-width: 60em) {\n .md-header__source {\n display: block;\n width: 11.7rem;\n max-width: 11.7rem;\n margin-left: 1rem;\n }\n [dir=rtl] .md-header__source {\n margin-right: 1rem;\n margin-left: initial;\n }\n}\n@media screen and (min-width: 76.25em) {\n .md-header__source {\n margin-left: 1.4rem;\n }\n [dir=rtl] .md-header__source {\n margin-right: 1.4rem;\n }\n}\n\n.md-footer {\n color: var(--md-footer-fg-color);\n background-color: var(--md-footer-bg-color);\n}\n@media print {\n .md-footer {\n display: none;\n }\n}\n.md-footer__inner {\n padding: 0.2rem;\n overflow: auto;\n}\n.md-footer__link {\n display: flex;\n padding-top: 1.4rem;\n padding-bottom: 0.4rem;\n transition: opacity 250ms;\n}\n@media screen and (min-width: 45em) {\n .md-footer__link {\n width: 50%;\n }\n}\n.md-footer__link:focus, .md-footer__link:hover {\n opacity: 0.7;\n}\n.md-footer__link--prev {\n float: left;\n}\n@media screen and (max-width: 44.9375em) {\n .md-footer__link--prev {\n width: 25%;\n }\n .md-footer__link--prev .md-footer__title {\n display: none;\n }\n}\n[dir=rtl] .md-footer__link--prev {\n float: right;\n}\n[dir=rtl] .md-footer__link--prev svg {\n transform: scaleX(-1);\n}\n.md-footer__link--next {\n float: right;\n text-align: right;\n}\n@media screen and (max-width: 44.9375em) {\n .md-footer__link--next {\n width: 75%;\n }\n}\n[dir=rtl] .md-footer__link--next {\n float: left;\n text-align: left;\n}\n[dir=rtl] .md-footer__link--next svg {\n transform: scaleX(-1);\n}\n.md-footer__title {\n position: relative;\n flex-grow: 1;\n max-width: calc(100% - 2.4rem);\n padding: 0 1rem;\n font-size: 0.9rem;\n line-height: 2.4rem;\n}\n.md-footer__button {\n margin: 0.2rem;\n padding: 0.4rem;\n}\n.md-footer__direction {\n position: absolute;\n right: 0;\n left: 0;\n margin-top: -1rem;\n padding: 0 1rem;\n font-size: 0.64rem;\n opacity: 0.7;\n}\n\n.md-footer-meta {\n background-color: var(--md-footer-bg-color--dark);\n}\n.md-footer-meta__inner {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n padding: 0.2rem;\n}\nhtml .md-footer-meta.md-typeset a {\n color: var(--md-footer-fg-color--light);\n}\nhtml .md-footer-meta.md-typeset a:focus, html .md-footer-meta.md-typeset a:hover {\n color: var(--md-footer-fg-color);\n}\n\n.md-footer-copyright {\n width: 100%;\n margin: auto 0.6rem;\n padding: 0.4rem 0;\n color: var(--md-footer-fg-color--lighter);\n font-size: 0.64rem;\n}\n@media screen and (min-width: 45em) {\n .md-footer-copyright {\n width: auto;\n }\n}\n.md-footer-copyright__highlight {\n color: var(--md-footer-fg-color--light);\n}\n\n.md-footer-social {\n margin: 0 0.4rem;\n padding: 0.2rem 0 0.6rem;\n}\n@media screen and (min-width: 45em) {\n .md-footer-social {\n padding: 0.6rem 0;\n }\n}\n.md-footer-social__link {\n display: inline-block;\n width: 1.6rem;\n height: 1.6rem;\n text-align: center;\n}\n.md-footer-social__link::before {\n line-height: 1.9;\n}\n.md-footer-social__link svg {\n max-height: 0.8rem;\n vertical-align: -25%;\n fill: currentColor;\n}\n\n:root {\n --md-nav-icon--prev: svg-load(\"material/arrow-left.svg\");\n --md-nav-icon--next: svg-load(\"material/chevron-right.svg\");\n --md-toc-icon: svg-load(\"material/table-of-contents.svg\");\n}\n\n.md-nav {\n font-size: 0.7rem;\n line-height: 1.3;\n}\n.md-nav__title {\n display: block;\n padding: 0 0.6rem;\n overflow: hidden;\n font-weight: 700;\n text-overflow: ellipsis;\n}\n.md-nav__title .md-nav__button {\n display: none;\n}\n.md-nav__title .md-nav__button img {\n width: auto;\n height: 100%;\n}\n.md-nav__title .md-nav__button.md-logo img,\n.md-nav__title .md-nav__button.md-logo svg {\n display: block;\n width: 2.4rem;\n height: 2.4rem;\n fill: currentColor;\n}\n.md-nav__list {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.md-nav__item {\n padding: 0 0.6rem;\n}\n.md-nav__item .md-nav__item {\n padding-right: 0;\n}\n[dir=rtl] .md-nav__item .md-nav__item {\n padding-right: 0.6rem;\n padding-left: 0;\n}\n.md-nav__link {\n display: block;\n margin-top: 0.625em;\n overflow: hidden;\n text-overflow: ellipsis;\n cursor: pointer;\n transition: color 125ms;\n scroll-snap-align: start;\n}\n.md-nav__link[data-md-state=blur] {\n color: var(--md-default-fg-color--light);\n}\n.md-nav__item .md-nav__link--active {\n color: var(--md-typeset-a-color);\n}\n.md-nav__item--nested > .md-nav__link {\n color: inherit;\n}\n.md-nav__link:focus, .md-nav__link:hover {\n color: var(--md-accent-fg-color);\n}\n.md-nav--primary .md-nav__link[for=__toc] {\n display: none;\n}\n.md-nav--primary .md-nav__link[for=__toc] .md-icon::after {\n display: block;\n width: 100%;\n height: 100%;\n mask-image: var(--md-toc-icon);\n background-color: currentColor;\n}\n.md-nav--primary .md-nav__link[for=__toc] ~ .md-nav {\n display: none;\n}\n.md-nav__source {\n display: none;\n}\n@media screen and (max-width: 76.1875em) {\n .md-nav--primary, .md-nav--primary .md-nav {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1;\n display: flex;\n flex-direction: column;\n height: 100%;\n background-color: var(--md-default-bg-color);\n }\n .md-nav--primary .md-nav__title,\n.md-nav--primary .md-nav__item {\n font-size: 0.8rem;\n line-height: 1.5;\n }\n .md-nav--primary .md-nav__title {\n position: relative;\n height: 5.6rem;\n padding: 3rem 0.8rem 0.2rem;\n color: var(--md-default-fg-color--light);\n font-weight: 400;\n line-height: 2.4rem;\n white-space: nowrap;\n background-color: var(--md-default-fg-color--lightest);\n cursor: pointer;\n }\n .md-nav--primary .md-nav__title .md-nav__icon {\n position: absolute;\n top: 0.4rem;\n left: 0.4rem;\n display: block;\n width: 1.2rem;\n height: 1.2rem;\n margin: 0.2rem;\n }\n [dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon {\n right: 0.4rem;\n left: initial;\n }\n .md-nav--primary .md-nav__title .md-nav__icon::after {\n display: block;\n width: 100%;\n height: 100%;\n background-color: currentColor;\n mask-image: var(--md-nav-icon--prev);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n }\n .md-nav--primary .md-nav__title ~ .md-nav__list {\n overflow-y: auto;\n background-color: var(--md-default-bg-color);\n box-shadow: 0 0.05rem 0 var(--md-default-fg-color--lightest) inset;\n scroll-snap-type: y mandatory;\n touch-action: pan-y;\n }\n .md-nav--primary .md-nav__title ~ .md-nav__list > :first-child {\n border-top: 0;\n }\n .md-nav--primary .md-nav__title[for=__drawer] {\n color: var(--md-primary-bg-color);\n background-color: var(--md-primary-fg-color);\n }\n .md-nav--primary .md-nav__title .md-logo {\n position: absolute;\n top: 0.2rem;\n left: 0.2rem;\n display: block;\n margin: 0.2rem;\n padding: 0.4rem;\n }\n [dir=rtl] .md-nav--primary .md-nav__title .md-logo {\n right: 0.2rem;\n left: initial;\n }\n .md-nav--primary .md-nav__list {\n flex: 1;\n }\n .md-nav--primary .md-nav__item {\n padding: 0;\n border-top: 0.05rem solid var(--md-default-fg-color--lightest);\n }\n .md-nav--primary .md-nav__item--nested > .md-nav__link {\n padding-right: 2.4rem;\n }\n [dir=rtl] .md-nav--primary .md-nav__item--nested > .md-nav__link {\n padding-right: 0.8rem;\n padding-left: 2.4rem;\n }\n .md-nav--primary .md-nav__item--active > .md-nav__link {\n color: var(--md-typeset-a-color);\n }\n .md-nav--primary .md-nav__item--active > .md-nav__link:focus, .md-nav--primary .md-nav__item--active > .md-nav__link:hover {\n color: var(--md-accent-fg-color);\n }\n .md-nav--primary .md-nav__link {\n position: relative;\n margin-top: 0;\n padding: 0.6rem 0.8rem;\n }\n .md-nav--primary .md-nav__link .md-nav__icon {\n position: absolute;\n top: 50%;\n right: 0.6rem;\n width: 1.2rem;\n height: 1.2rem;\n margin-top: -0.6rem;\n color: inherit;\n font-size: 1.2rem;\n }\n [dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon {\n right: initial;\n left: 0.6rem;\n }\n .md-nav--primary .md-nav__link .md-nav__icon::after {\n display: block;\n width: 100%;\n height: 100%;\n background-color: currentColor;\n mask-image: var(--md-nav-icon--next);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n }\n [dir=rtl] .md-nav--primary .md-nav__icon::after {\n transform: scale(-1);\n }\n .md-nav--primary .md-nav--secondary .md-nav__link {\n position: static;\n }\n .md-nav--primary .md-nav--secondary .md-nav {\n position: static;\n background-color: transparent;\n }\n .md-nav--primary .md-nav--secondary .md-nav .md-nav__link {\n padding-left: 1.4rem;\n }\n [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link {\n padding-right: 1.4rem;\n padding-left: initial;\n }\n .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link {\n padding-left: 2rem;\n }\n [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link {\n padding-right: 2rem;\n padding-left: initial;\n }\n .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link {\n padding-left: 2.6rem;\n }\n [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link {\n padding-right: 2.6rem;\n padding-left: initial;\n }\n .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link {\n padding-left: 3.2rem;\n }\n [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link {\n padding-right: 3.2rem;\n padding-left: initial;\n }\n .md-nav--secondary {\n background-color: transparent;\n }\n .md-nav__toggle ~ .md-nav {\n display: flex;\n transform: translateX(100%);\n opacity: 0;\n transition: transform 250ms cubic-bezier(0.8, 0, 0.6, 1), opacity 125ms 50ms;\n }\n [dir=rtl] .md-nav__toggle ~ .md-nav {\n transform: translateX(-100%);\n }\n .md-nav__toggle:checked ~ .md-nav {\n transform: translateX(0);\n opacity: 1;\n transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), opacity 125ms 125ms;\n }\n .md-nav__toggle:checked ~ .md-nav > .md-nav__list {\n backface-visibility: hidden;\n }\n}\n@media screen and (max-width: 59.9375em) {\n .md-nav--primary .md-nav__link[for=__toc] {\n display: block;\n padding-right: 2.4rem;\n }\n [dir=rtl] .md-nav--primary .md-nav__link[for=__toc] {\n padding-right: 0.8rem;\n padding-left: 2.4rem;\n }\n .md-nav--primary .md-nav__link[for=__toc] .md-icon::after {\n content: \"\";\n }\n .md-nav--primary .md-nav__link[for=__toc] + .md-nav__link {\n display: none;\n }\n .md-nav--primary .md-nav__link[for=__toc] ~ .md-nav {\n display: flex;\n }\n .md-nav__source {\n display: block;\n padding: 0 0.2rem;\n color: var(--md-primary-bg-color);\n background-color: var(--md-primary-fg-color--dark);\n }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n .md-nav--integrated .md-nav__link[for=__toc] {\n display: block;\n padding-right: 2.4rem;\n scroll-snap-align: initial;\n }\n [dir=rtl] .md-nav--integrated .md-nav__link[for=__toc] {\n padding-right: 0.8rem;\n padding-left: 2.4rem;\n }\n .md-nav--integrated .md-nav__link[for=__toc] .md-icon::after {\n content: \"\";\n }\n .md-nav--integrated .md-nav__link[for=__toc] + .md-nav__link {\n display: none;\n }\n .md-nav--integrated .md-nav__link[for=__toc] ~ .md-nav {\n display: flex;\n }\n}\n@media screen and (min-width: 60em) {\n .md-nav--secondary .md-nav__title[for=__toc] {\n scroll-snap-align: start;\n }\n .md-nav--secondary .md-nav__title .md-nav__icon {\n display: none;\n }\n}\n@media screen and (min-width: 76.25em) {\n .md-nav {\n transition: max-height 250ms cubic-bezier(0.86, 0, 0.07, 1);\n }\n .md-nav--primary .md-nav__title[for=__drawer] {\n scroll-snap-align: start;\n }\n .md-nav--primary .md-nav__title .md-nav__icon {\n display: none;\n }\n .md-nav__toggle ~ .md-nav {\n display: none;\n }\n .md-nav__toggle:checked ~ .md-nav, .md-nav__toggle:indeterminate ~ .md-nav {\n display: block;\n }\n .md-nav__item--nested > .md-nav > .md-nav__title {\n display: none;\n }\n .md-nav__item--section {\n display: block;\n margin: 1.25em 0;\n }\n .md-nav__item--section:last-child {\n margin-bottom: 0;\n }\n .md-nav__item--section > .md-nav__link {\n display: none;\n }\n .md-nav__item--section > .md-nav {\n display: block;\n }\n .md-nav__item--section > .md-nav > .md-nav__title {\n display: block;\n padding: 0;\n pointer-events: none;\n scroll-snap-align: start;\n }\n .md-nav__item--section > .md-nav > .md-nav__list > .md-nav__item {\n padding: 0;\n }\n .md-nav__icon {\n float: right;\n width: 0.9rem;\n height: 0.9rem;\n transition: transform 250ms;\n }\n [dir=rtl] .md-nav__icon {\n float: left;\n transform: rotate(180deg);\n }\n .md-nav__icon::after {\n display: inline-block;\n width: 100%;\n height: 100%;\n vertical-align: -0.1rem;\n background-color: currentColor;\n mask-image: var(--md-nav-icon--next);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n }\n .md-nav__item--nested .md-nav__toggle:checked ~ .md-nav__link .md-nav__icon, .md-nav__item--nested .md-nav__toggle:indeterminate ~ .md-nav__link .md-nav__icon {\n transform: rotate(90deg);\n }\n .md-nav--lifted > .md-nav__list > .md-nav__item--nested,\n.md-nav--lifted > .md-nav__title {\n display: none;\n }\n .md-nav--lifted > .md-nav__list > .md-nav__item {\n display: none;\n }\n .md-nav--lifted > .md-nav__list > .md-nav__item--active {\n display: block;\n padding: 0;\n }\n .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link {\n display: none;\n }\n .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__title {\n display: block;\n padding: 0 0.6rem;\n pointer-events: none;\n scroll-snap-align: start;\n }\n .md-nav--lifted > .md-nav__list > .md-nav__item > .md-nav__item {\n padding-right: 0.6rem;\n }\n .md-nav--lifted .md-nav[data-md-level=\"1\"] {\n display: block;\n }\n .md-nav--integrated .md-nav__link[for=__toc] ~ .md-nav {\n display: block;\n margin-bottom: 1.25em;\n border-left: 0.05rem solid var(--md-primary-fg-color);\n }\n .md-nav--integrated .md-nav__link[for=__toc] ~ .md-nav > .md-nav__title {\n display: none;\n }\n}\n\n:root {\n --md-search-result-icon: svg-load(\"material/file-search-outline.svg\");\n}\n\n.md-search {\n position: relative;\n}\n@media screen and (min-width: 60em) {\n .md-search {\n padding: 0.2rem 0;\n }\n}\n.no-js .md-search {\n display: none;\n}\n.md-search__overlay {\n z-index: 1;\n opacity: 0;\n}\n@media screen and (max-width: 59.9375em) {\n .md-search__overlay {\n position: absolute;\n top: 0.2rem;\n left: -2.2rem;\n width: 2rem;\n height: 2rem;\n overflow: hidden;\n background-color: var(--md-default-bg-color);\n border-radius: 1rem;\n transform-origin: center;\n transition: transform 300ms 100ms, opacity 200ms 200ms;\n pointer-events: none;\n }\n [dir=rtl] .md-search__overlay {\n right: -2.2rem;\n left: initial;\n }\n [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n opacity: 1;\n transition: transform 400ms, opacity 100ms;\n }\n}\n@media screen and (min-width: 60em) {\n .md-search__overlay {\n position: fixed;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n background-color: rgba(0, 0, 0, 0.54);\n cursor: pointer;\n transition: width 0ms 250ms, height 0ms 250ms, opacity 250ms;\n }\n [dir=rtl] .md-search__overlay {\n right: 0;\n left: initial;\n }\n [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n width: 100%;\n height: 200vh;\n opacity: 1;\n transition: width 0ms, height 0ms, opacity 250ms;\n }\n}\n@media screen and (max-width: 29.9375em) {\n [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n transform: scale(45);\n }\n}\n@media screen and (min-width: 30em) and (max-width: 44.9375em) {\n [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n transform: scale(60);\n }\n}\n@media screen and (min-width: 45em) and (max-width: 59.9375em) {\n [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n transform: scale(75);\n }\n}\n.md-search__inner {\n backface-visibility: hidden;\n}\n@media screen and (max-width: 59.9375em) {\n .md-search__inner {\n position: fixed;\n top: 0;\n left: 100%;\n z-index: 2;\n width: 100%;\n height: 100%;\n transform: translateX(5%);\n opacity: 0;\n transition: right 0ms 300ms, left 0ms 300ms, transform 150ms 150ms cubic-bezier(0.4, 0, 0.2, 1), opacity 150ms 150ms;\n }\n [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n left: 0;\n transform: translateX(0);\n opacity: 1;\n transition: right 0ms 0ms, left 0ms 0ms, transform 150ms 150ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms 150ms;\n }\n [dir=rtl] [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n right: 0;\n left: initial;\n }\n html [dir=rtl] .md-search__inner {\n right: 100%;\n left: initial;\n transform: translateX(-5%);\n }\n}\n@media screen and (min-width: 60em) {\n .md-search__inner {\n position: relative;\n float: right;\n width: 11.7rem;\n padding: 0.1rem 0;\n transition: width 250ms cubic-bezier(0.1, 0.7, 0.1, 1);\n }\n [dir=rtl] .md-search__inner {\n float: left;\n }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n width: 23.4rem;\n }\n}\n@media screen and (min-width: 76.25em) {\n [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n width: 34.4rem;\n }\n}\n.md-search__form {\n position: relative;\n}\n@media screen and (min-width: 60em) {\n .md-search__form {\n border-radius: 0.1rem;\n }\n}\n.md-search__input {\n position: relative;\n z-index: 2;\n padding: 0 2.2rem 0 3.6rem;\n text-overflow: ellipsis;\n background-color: var(--md-default-bg-color);\n box-shadow: 0 0 0.6rem transparent;\n transition: color 250ms, background-color 250ms, box-shadow 250ms;\n}\n[dir=rtl] .md-search__input {\n padding: 0 3.6rem 0 2.2rem;\n}\n.md-search__input::placeholder {\n transition: color 250ms;\n}\n.md-search__input ~ .md-search__icon, .md-search__input::placeholder {\n color: var(--md-default-fg-color--light);\n}\n.md-search__input::-ms-clear {\n display: none;\n}\n[data-md-toggle=search]:checked ~ .md-header .md-search__input {\n box-shadow: 0 0 0.6rem rgba(0, 0, 0, 0.07);\n}\n@media screen and (max-width: 59.9375em) {\n .md-search__input {\n width: 100%;\n height: 2.4rem;\n font-size: 0.9rem;\n }\n}\n@media screen and (min-width: 60em) {\n .md-search__input {\n width: 100%;\n height: 1.8rem;\n padding-left: 2.2rem;\n color: inherit;\n font-size: 0.8rem;\n background-color: rgba(0, 0, 0, 0.26);\n border-radius: 0.1rem;\n }\n [dir=rtl] .md-search__input {\n padding-right: 2.2rem;\n }\n .md-search__input + .md-search__icon {\n color: var(--md-primary-bg-color);\n }\n .md-search__input::placeholder {\n color: var(--md-primary-bg-color--light);\n }\n .md-search__input:hover {\n background-color: rgba(255, 255, 255, 0.12);\n }\n [data-md-toggle=search]:checked ~ .md-header .md-search__input {\n color: var(--md-default-fg-color);\n text-overflow: clip;\n background-color: var(--md-default-bg-color);\n border-radius: 0.1rem 0.1rem 0 0;\n }\n [data-md-toggle=search]:checked ~ .md-header .md-search__input + .md-search__icon, [data-md-toggle=search]:checked ~ .md-header .md-search__input::placeholder {\n color: var(--md-default-fg-color--light);\n }\n}\n.md-search__icon {\n position: absolute;\n z-index: 2;\n width: 1.2rem;\n height: 1.2rem;\n cursor: pointer;\n transition: color 250ms, opacity 250ms;\n}\n.md-search__icon:hover {\n opacity: 0.7;\n}\n.md-search__icon[for=__search] {\n top: 0.3rem;\n left: 0.5rem;\n}\n[dir=rtl] .md-search__icon[for=__search] {\n right: 0.5rem;\n left: initial;\n}\n[dir=rtl] .md-search__icon[for=__search] svg {\n transform: scaleX(-1);\n}\n@media screen and (max-width: 59.9375em) {\n .md-search__icon[for=__search] {\n top: 0.6rem;\n left: 0.8rem;\n }\n [dir=rtl] .md-search__icon[for=__search] {\n right: 0.8rem;\n left: initial;\n }\n .md-search__icon[for=__search] svg:first-child {\n display: none;\n }\n}\n@media screen and (min-width: 60em) {\n .md-search__icon[for=__search] {\n pointer-events: none;\n }\n .md-search__icon[for=__search] svg:last-child {\n display: none;\n }\n}\n.md-search__icon[type=reset] {\n top: 0.3rem;\n right: 0.5rem;\n transform: scale(0.75);\n opacity: 0;\n transition: transform 150ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms;\n pointer-events: none;\n}\n[dir=rtl] .md-search__icon[type=reset] {\n right: initial;\n left: 0.5rem;\n}\n@media screen and (max-width: 59.9375em) {\n .md-search__icon[type=reset] {\n top: 0.6rem;\n right: 0.8rem;\n }\n [dir=rtl] .md-search__icon[type=reset] {\n right: initial;\n left: 0.8rem;\n }\n}\n[data-md-toggle=search]:checked ~ .md-header .md-search__input:valid ~ .md-search__icon[type=reset] {\n transform: scale(1);\n opacity: 1;\n pointer-events: initial;\n}\n[data-md-toggle=search]:checked ~ .md-header .md-search__input:valid ~ .md-search__icon[type=reset]:hover {\n opacity: 0.7;\n}\n.md-search__output {\n position: absolute;\n z-index: 1;\n width: 100%;\n overflow: hidden;\n border-radius: 0 0 0.1rem 0.1rem;\n}\n@media screen and (max-width: 59.9375em) {\n .md-search__output {\n top: 2.4rem;\n bottom: 0;\n }\n}\n@media screen and (min-width: 60em) {\n .md-search__output {\n top: 1.9rem;\n opacity: 0;\n transition: opacity 400ms;\n }\n [data-md-toggle=search]:checked ~ .md-header .md-search__output {\n box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.4);\n opacity: 1;\n }\n}\n.md-search__scrollwrap {\n height: 100%;\n overflow-y: auto;\n background-color: var(--md-default-bg-color);\n backface-visibility: hidden;\n touch-action: pan-y;\n}\n@media (max-resolution: 1dppx) {\n .md-search__scrollwrap {\n transform: translateZ(0);\n }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n .md-search__scrollwrap {\n width: 23.4rem;\n }\n}\n@media screen and (min-width: 76.25em) {\n .md-search__scrollwrap {\n width: 34.4rem;\n }\n}\n@media screen and (min-width: 60em) {\n .md-search__scrollwrap {\n max-height: 0;\n scrollbar-width: thin;\n scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n }\n [data-md-toggle=search]:checked ~ .md-header .md-search__scrollwrap {\n max-height: 75vh;\n }\n .md-search__scrollwrap:hover {\n scrollbar-color: var(--md-accent-fg-color) transparent;\n }\n .md-search__scrollwrap::-webkit-scrollbar {\n width: 0.2rem;\n height: 0.2rem;\n }\n .md-search__scrollwrap::-webkit-scrollbar-thumb {\n background-color: var(--md-default-fg-color--lighter);\n }\n .md-search__scrollwrap::-webkit-scrollbar-thumb:hover {\n background-color: var(--md-accent-fg-color);\n }\n}\n\n.md-search-result {\n color: var(--md-default-fg-color);\n word-break: break-word;\n}\n.md-search-result__meta {\n padding: 0 0.8rem;\n color: var(--md-default-fg-color--light);\n font-size: 0.64rem;\n line-height: 1.8rem;\n background-color: var(--md-default-fg-color--lightest);\n scroll-snap-align: start;\n}\n@media screen and (min-width: 60em) {\n .md-search-result__meta {\n padding-left: 2.2rem;\n }\n [dir=rtl] .md-search-result__meta {\n padding-right: 2.2rem;\n padding-left: initial;\n }\n}\n.md-search-result__list {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.md-search-result__item {\n box-shadow: 0 -0.05rem 0 var(--md-default-fg-color--lightest);\n}\n.md-search-result__item:first-child {\n box-shadow: none;\n}\n.md-search-result__link {\n display: block;\n outline: none;\n transition: background-color 250ms;\n scroll-snap-align: start;\n}\n.md-search-result__link:focus, .md-search-result__link:hover {\n background-color: var(--md-accent-fg-color--transparent);\n}\n.md-search-result__link:last-child p:last-child {\n margin-bottom: 0.6rem;\n}\n.md-search-result__more summary {\n display: block;\n padding: 0.75em 0.8rem;\n color: var(--md-typeset-a-color);\n font-size: 0.64rem;\n outline: 0;\n cursor: pointer;\n transition: color 250ms, background-color 250ms;\n scroll-snap-align: start;\n}\n@media screen and (min-width: 60em) {\n .md-search-result__more summary {\n padding-left: 2.2rem;\n }\n [dir=rtl] .md-search-result__more summary {\n padding-right: 2.2rem;\n padding-left: 0.8rem;\n }\n}\n.md-search-result__more summary:focus, .md-search-result__more summary:hover {\n color: var(--md-accent-fg-color);\n background-color: var(--md-accent-fg-color--transparent);\n}\n.md-search-result__more summary::marker, .md-search-result__more summary::-webkit-details-marker {\n display: none;\n}\n.md-search-result__more summary ~ * > * {\n opacity: 0.65;\n}\n.md-search-result__article {\n position: relative;\n padding: 0 0.8rem;\n overflow: hidden;\n}\n@media screen and (min-width: 60em) {\n .md-search-result__article {\n padding-left: 2.2rem;\n }\n [dir=rtl] .md-search-result__article {\n padding-right: 2.2rem;\n padding-left: 0.8rem;\n }\n}\n.md-search-result__article--document .md-search-result__title {\n margin: 0.55rem 0;\n font-weight: 400;\n font-size: 0.8rem;\n line-height: 1.4;\n}\n.md-search-result__icon {\n position: absolute;\n left: 0;\n width: 1.2rem;\n height: 1.2rem;\n margin: 0.5rem;\n color: var(--md-default-fg-color--light);\n}\n@media screen and (max-width: 59.9375em) {\n .md-search-result__icon {\n display: none;\n }\n}\n.md-search-result__icon::after {\n display: inline-block;\n width: 100%;\n height: 100%;\n background-color: currentColor;\n mask-image: var(--md-search-result-icon);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n}\n[dir=rtl] .md-search-result__icon {\n right: 0;\n left: initial;\n}\n[dir=rtl] .md-search-result__icon::after {\n transform: scaleX(-1);\n}\n.md-search-result__title {\n margin: 0.5em 0;\n font-weight: 700;\n font-size: 0.64rem;\n line-height: 1.6;\n}\n.md-search-result__teaser {\n display: -webkit-box;\n max-height: 2rem;\n margin: 0.5em 0;\n overflow: hidden;\n color: var(--md-default-fg-color--light);\n font-size: 0.64rem;\n line-height: 1.6;\n text-overflow: ellipsis;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n}\n@media screen and (max-width: 44.9375em) {\n .md-search-result__teaser {\n max-height: 3rem;\n -webkit-line-clamp: 3;\n }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n .md-search-result__teaser {\n max-height: 3rem;\n -webkit-line-clamp: 3;\n }\n}\n.md-search-result__teaser mark {\n text-decoration: underline;\n background-color: transparent;\n}\n.md-search-result__terms {\n margin: 0.5em 0;\n font-size: 0.64rem;\n font-style: italic;\n}\n.md-search-result mark {\n color: var(--md-accent-fg-color);\n background-color: transparent;\n}\n\n.md-select {\n position: relative;\n z-index: 1;\n}\n.md-select__inner {\n position: absolute;\n top: calc(100% - 0.2rem);\n left: 50%;\n max-height: 0;\n margin-top: 0.2rem;\n color: var(--md-default-fg-color);\n background-color: var(--md-default-bg-color);\n border-radius: 0.1rem;\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0 0.05rem rgba(0, 0, 0, 0.25);\n transform: translate3d(-50%, 0.3rem, 0);\n opacity: 0;\n transition: transform 250ms 375ms, opacity 250ms 250ms, max-height 0ms 500ms;\n}\n.md-select:focus-within .md-select__inner, .md-select:hover .md-select__inner {\n max-height: 10rem;\n transform: translate3d(-50%, 0, 0);\n opacity: 1;\n transition: transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 250ms, max-height 250ms;\n}\n.md-select__inner::after {\n position: absolute;\n top: 0;\n left: 50%;\n width: 0;\n height: 0;\n margin-top: -0.2rem;\n margin-left: -0.2rem;\n border: 0.2rem solid transparent;\n border-top: 0;\n border-bottom-color: var(--md-default-bg-color);\n content: \"\";\n}\n.md-select__list {\n max-height: inherit;\n margin: 0;\n padding: 0;\n overflow: auto;\n font-size: 0.8rem;\n list-style-type: none;\n border-radius: 0.1rem;\n}\n.md-select__item {\n line-height: 1.8rem;\n}\n.md-select__link {\n display: block;\n width: 100%;\n padding-right: 1.2rem;\n padding-left: 0.6rem;\n cursor: pointer;\n transition: background-color 250ms, color 250ms;\n scroll-snap-align: start;\n}\n[dir=rtl] .md-select__link {\n padding-right: 0.6rem;\n padding-left: 1.2rem;\n}\n.md-select__link:focus, .md-select__link:hover {\n background-color: var(--md-default-fg-color--lightest);\n}\n\n.md-sidebar {\n position: sticky;\n top: 2.4rem;\n flex-shrink: 0;\n align-self: flex-start;\n width: 12.1rem;\n padding: 1.2rem 0;\n}\n@media print {\n .md-sidebar {\n display: none;\n }\n}\n@media screen and (max-width: 76.1875em) {\n .md-sidebar--primary {\n position: fixed;\n top: 0;\n left: -12.1rem;\n z-index: 3;\n display: block;\n width: 12.1rem;\n height: 100%;\n background-color: var(--md-default-bg-color);\n transform: translateX(0);\n transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), box-shadow 250ms;\n }\n [dir=rtl] .md-sidebar--primary {\n right: -12.1rem;\n left: initial;\n }\n [data-md-toggle=drawer]:checked ~ .md-container .md-sidebar--primary {\n box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.4);\n transform: translateX(12.1rem);\n }\n [dir=rtl] [data-md-toggle=drawer]:checked ~ .md-container .md-sidebar--primary {\n transform: translateX(-12.1rem);\n }\n .md-sidebar--primary .md-sidebar__scrollwrap {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n margin: 0;\n scroll-snap-type: none;\n overflow: hidden;\n }\n}\n@media screen and (min-width: 76.25em) {\n .md-sidebar {\n height: 0;\n }\n .no-js .md-sidebar {\n height: auto;\n }\n}\n.md-sidebar--secondary {\n display: none;\n order: 2;\n}\n@media screen and (min-width: 60em) {\n .md-sidebar--secondary {\n height: 0;\n }\n .no-js .md-sidebar--secondary {\n height: auto;\n }\n .md-sidebar--secondary:not([hidden]) {\n display: block;\n }\n .md-sidebar--secondary .md-sidebar__scrollwrap {\n touch-action: pan-y;\n }\n}\n.md-sidebar__scrollwrap {\n margin: 0 0.2rem;\n overflow-y: auto;\n backface-visibility: hidden;\n scrollbar-width: thin;\n scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n}\n.md-sidebar__scrollwrap:hover {\n scrollbar-color: var(--md-accent-fg-color) transparent;\n}\n.md-sidebar__scrollwrap::-webkit-scrollbar {\n width: 0.2rem;\n height: 0.2rem;\n}\n.md-sidebar__scrollwrap::-webkit-scrollbar-thumb {\n background-color: var(--md-default-fg-color--lighter);\n}\n.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover {\n background-color: var(--md-accent-fg-color);\n}\n\n@media screen and (max-width: 76.1875em) {\n .md-overlay {\n position: fixed;\n top: 0;\n z-index: 3;\n width: 0;\n height: 0;\n background-color: rgba(0, 0, 0, 0.54);\n opacity: 0;\n transition: width 0ms 250ms, height 0ms 250ms, opacity 250ms;\n }\n [data-md-toggle=drawer]:checked ~ .md-overlay {\n width: 100%;\n height: 100%;\n opacity: 1;\n transition: width 0ms, height 0ms, opacity 250ms;\n }\n}\n@keyframes md-source__facts--done {\n 0% {\n height: 0;\n }\n 100% {\n height: 0.65rem;\n }\n}\n@keyframes md-source__fact--done {\n 0% {\n transform: translateY(100%);\n opacity: 0;\n }\n 50% {\n opacity: 0;\n }\n 100% {\n transform: translateY(0%);\n opacity: 1;\n }\n}\n:root {\n --md-source-forks-icon: svg-load(\"octicons/repo-forked-16.svg\");\n --md-source-repositories-icon: svg-load(\"octicons/repo-16.svg\");\n --md-source-stars-icon: svg-load(\"octicons/star-16.svg\");\n --md-source-version-icon: svg-load(\"octicons/tag-16.svg\");\n}\n\n.md-source {\n display: block;\n font-size: 0.65rem;\n line-height: 1.2;\n white-space: nowrap;\n backface-visibility: hidden;\n transition: opacity 250ms;\n}\n.md-source:hover {\n opacity: 0.7;\n}\n.md-source__icon {\n display: inline-block;\n width: 2rem;\n height: 2.4rem;\n vertical-align: middle;\n}\n.md-source__icon svg {\n margin-top: 0.6rem;\n margin-left: 0.6rem;\n}\n[dir=rtl] .md-source__icon svg {\n margin-right: 0.6rem;\n margin-left: initial;\n}\n.md-source__icon + .md-source__repository {\n margin-left: -2rem;\n padding-left: 2rem;\n}\n[dir=rtl] .md-source__icon + .md-source__repository {\n margin-right: -2rem;\n margin-left: initial;\n padding-right: 2rem;\n padding-left: initial;\n}\n.md-source__repository {\n display: inline-block;\n max-width: calc(100% - 1.2rem);\n margin-left: 0.6rem;\n overflow: hidden;\n text-overflow: ellipsis;\n vertical-align: middle;\n}\n.md-source__facts {\n margin: 0.1rem 0 0;\n padding: 0;\n overflow: hidden;\n font-size: 0.55rem;\n list-style-type: none;\n opacity: 0.75;\n}\n[data-md-state=done] .md-source__facts {\n animation: md-source__facts--done 250ms ease-in;\n}\n.md-source__fact {\n display: inline-block;\n}\n[data-md-state=done] .md-source__fact {\n animation: md-source__fact--done 400ms ease-out;\n}\n.md-source__fact::before {\n display: inline-block;\n width: 0.6rem;\n height: 0.6rem;\n margin-right: 0.1rem;\n vertical-align: text-top;\n background-color: currentColor;\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n}\n.md-source__fact:nth-child(1n+2)::before {\n margin-left: 0.4rem;\n}\n[dir=rtl] .md-source__fact {\n margin-right: initial;\n margin-left: 0.1rem;\n}\n[dir=rtl] .md-source__fact:nth-child(1n+2)::before {\n margin-right: 0.4rem;\n margin-left: initial;\n}\n.md-source__fact--version::before {\n mask-image: var(--md-source-version-icon);\n}\n.md-source__fact--stars::before {\n mask-image: var(--md-source-stars-icon);\n}\n.md-source__fact--forks::before {\n mask-image: var(--md-source-forks-icon);\n}\n.md-source__fact--repositories::before {\n mask-image: var(--md-source-repositories-icon);\n}\n\n.md-tabs {\n width: 100%;\n overflow: auto;\n color: var(--md-primary-bg-color);\n background-color: var(--md-primary-fg-color);\n}\n@media print {\n .md-tabs {\n display: none;\n }\n}\n@media screen and (max-width: 76.1875em) {\n .md-tabs {\n display: none;\n }\n}\n.md-tabs[data-md-state=hidden] {\n pointer-events: none;\n}\n.md-tabs__list {\n margin: 0;\n margin-left: 0.2rem;\n padding: 0;\n white-space: nowrap;\n list-style: none;\n contain: content;\n}\n[dir=rtl] .md-tabs__list {\n margin-right: 0.2rem;\n margin-left: initial;\n}\n.md-tabs__item {\n display: inline-block;\n height: 2.4rem;\n padding-right: 0.6rem;\n padding-left: 0.6rem;\n}\n.md-tabs__link {\n display: block;\n margin-top: 0.8rem;\n font-size: 0.7rem;\n backface-visibility: hidden;\n opacity: 0.7;\n transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 250ms;\n}\n.md-tabs__link--active, .md-tabs__link:focus, .md-tabs__link:hover {\n color: inherit;\n opacity: 1;\n}\n.md-tabs__item:nth-child(2) .md-tabs__link {\n transition-delay: 20ms;\n}\n.md-tabs__item:nth-child(3) .md-tabs__link {\n transition-delay: 40ms;\n}\n.md-tabs__item:nth-child(4) .md-tabs__link {\n transition-delay: 60ms;\n}\n.md-tabs__item:nth-child(5) .md-tabs__link {\n transition-delay: 80ms;\n}\n.md-tabs__item:nth-child(6) .md-tabs__link {\n transition-delay: 100ms;\n}\n.md-tabs__item:nth-child(7) .md-tabs__link {\n transition-delay: 120ms;\n}\n.md-tabs__item:nth-child(8) .md-tabs__link {\n transition-delay: 140ms;\n}\n.md-tabs__item:nth-child(9) .md-tabs__link {\n transition-delay: 160ms;\n}\n.md-tabs__item:nth-child(10) .md-tabs__link {\n transition-delay: 180ms;\n}\n.md-tabs__item:nth-child(11) .md-tabs__link {\n transition-delay: 200ms;\n}\n.md-tabs__item:nth-child(12) .md-tabs__link {\n transition-delay: 220ms;\n}\n.md-tabs__item:nth-child(13) .md-tabs__link {\n transition-delay: 240ms;\n}\n.md-tabs__item:nth-child(14) .md-tabs__link {\n transition-delay: 260ms;\n}\n.md-tabs__item:nth-child(15) .md-tabs__link {\n transition-delay: 280ms;\n}\n.md-tabs__item:nth-child(16) .md-tabs__link {\n transition-delay: 300ms;\n}\n.md-tabs[data-md-state=hidden] .md-tabs__link {\n transform: translateY(50%);\n opacity: 0;\n transition: transform 0ms 100ms, opacity 100ms;\n}\n\n.md-top {\n position: sticky;\n bottom: 0.4rem;\n z-index: 1;\n float: right;\n margin: -2.8rem 0.4rem 0.4rem;\n padding: 0.4rem;\n color: var(--md-primary-bg-color);\n background: var(--md-primary-fg-color);\n border-radius: 100%;\n outline: none;\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.1);\n transform: translateY(0);\n transition: opacity 125ms, transform 125ms cubic-bezier(0.4, 0, 0.2, 1), background-color 125ms;\n}\n[dir=rtl] .md-top {\n float: left;\n}\n.md-top[data-md-state=hidden] {\n transform: translateY(-0.2rem);\n opacity: 0;\n}\n.md-top:focus, .md-top:hover {\n background: var(--md-accent-fg-color);\n transform: scale(1.1);\n}\n\n:root {\n --md-version-icon: svg-load(\"fontawesome/solid/caret-down.svg\");\n}\n\n.md-version {\n flex-shrink: 0;\n height: 2.4rem;\n font-size: 0.8rem;\n}\n.md-version__current {\n position: relative;\n top: 0.05rem;\n margin-right: 0.4rem;\n margin-left: 1.4rem;\n}\n[dir=rtl] .md-version__current {\n margin-right: 1.4rem;\n margin-left: 0.4rem;\n}\n.md-version__current::after {\n display: inline-block;\n width: 0.4rem;\n height: 0.6rem;\n margin-left: 0.4rem;\n background-color: currentColor;\n mask-image: var(--md-version-icon);\n mask-repeat: no-repeat;\n content: \"\";\n}\n[dir=rtl] .md-version__current::after {\n margin-right: 0.4rem;\n margin-left: initial;\n}\n.md-version__list {\n position: absolute;\n top: 0.15rem;\n z-index: 1;\n max-height: 1.8rem;\n margin: 0.2rem 0.8rem;\n padding: 0;\n overflow: auto;\n color: var(--md-default-fg-color);\n list-style-type: none;\n background-color: var(--md-default-bg-color);\n border-radius: 0.1rem;\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0 0.05rem rgba(0, 0, 0, 0.25);\n opacity: 0;\n transition: max-height 0ms 500ms, opacity 250ms 250ms;\n scroll-snap-type: y mandatory;\n}\n.md-version__list:focus-within, .md-version__list:hover {\n max-height: 10rem;\n opacity: 1;\n transition: max-height 250ms, opacity 250ms;\n}\n.md-version__item {\n line-height: 1.8rem;\n}\n.md-version__link {\n display: block;\n width: 100%;\n padding-right: 1.2rem;\n padding-left: 0.6rem;\n white-space: nowrap;\n cursor: pointer;\n transition: color 250ms, background-color 250ms;\n scroll-snap-align: start;\n}\n[dir=rtl] .md-version__link {\n padding-right: 0.6rem;\n padding-left: 1.2rem;\n}\n.md-version__link:focus, .md-version__link:hover {\n background-color: var(--md-default-fg-color--lightest);\n}\n\n:root {\n --md-admonition-icon--note:\n svg-load(\"material/pencil.svg\");\n --md-admonition-icon--abstract:\n svg-load(\"material/text-subject.svg\");\n --md-admonition-icon--info:\n svg-load(\"material/information.svg\");\n --md-admonition-icon--tip:\n svg-load(\"material/fire.svg\");\n --md-admonition-icon--success:\n svg-load(\"material/check-circle.svg\");\n --md-admonition-icon--question:\n svg-load(\"material/help-circle.svg\");\n --md-admonition-icon--warning:\n svg-load(\"material/alert.svg\");\n --md-admonition-icon--failure:\n svg-load(\"material/close-circle.svg\");\n --md-admonition-icon--danger:\n svg-load(\"material/flash-circle.svg\");\n --md-admonition-icon--bug:\n svg-load(\"material/bug.svg\");\n --md-admonition-icon--example:\n svg-load(\"material/format-list-numbered.svg\");\n --md-admonition-icon--quote:\n svg-load(\"material/format-quote-close.svg\");\n}\n\n.md-typeset .admonition, .md-typeset details {\n margin: 1.5625em 0;\n padding: 0 0.6rem;\n overflow: hidden;\n color: var(--md-admonition-fg-color);\n font-size: 0.64rem;\n page-break-inside: avoid;\n background-color: var(--md-admonition-bg-color);\n border-left: 0.2rem solid #448aff;\n border-radius: 0.1rem;\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.05);\n}\n@media print {\n .md-typeset .admonition, .md-typeset details {\n box-shadow: none;\n }\n}\n[dir=rtl] .md-typeset .admonition, [dir=rtl] .md-typeset details {\n border-right: 0.2rem solid #448aff;\n border-left: none;\n}\n.md-typeset .admonition .admonition, .md-typeset details .admonition, .md-typeset .admonition details, .md-typeset details details {\n margin-top: 1em;\n margin-bottom: 1em;\n}\n.md-typeset .admonition .md-typeset__scrollwrap, .md-typeset details .md-typeset__scrollwrap {\n margin: 1em -0.6rem;\n}\n.md-typeset .admonition .md-typeset__table, .md-typeset details .md-typeset__table {\n padding: 0 0.6rem;\n}\n.md-typeset .admonition > .tabbed-set:only-child, .md-typeset details > .tabbed-set:only-child {\n margin-top: 0;\n}\nhtml .md-typeset .admonition > :last-child, html .md-typeset details > :last-child {\n margin-bottom: 0.6rem;\n}\n.md-typeset .admonition-title, .md-typeset summary {\n position: relative;\n margin: 0 -0.6rem 0 -0.8rem;\n padding: 0.4rem 0.6rem 0.4rem 2rem;\n font-weight: 700;\n background-color: rgba(68, 138, 255, 0.1);\n border-left: 0.2rem solid #448aff;\n}\n[dir=rtl] .md-typeset .admonition-title, [dir=rtl] .md-typeset summary {\n margin: 0 -0.8rem 0 -0.6rem;\n padding: 0.4rem 2rem 0.4rem 0.6rem;\n border-right: 0.2rem solid #448aff;\n border-left: none;\n}\nhtml .md-typeset .admonition-title:last-child, html .md-typeset summary:last-child {\n margin-bottom: 0;\n}\n.md-typeset .admonition-title::before, .md-typeset summary::before {\n position: absolute;\n left: 0.6rem;\n width: 1rem;\n height: 1rem;\n background-color: #448aff;\n mask-image: var(--md-admonition-icon--note);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n}\n[dir=rtl] .md-typeset .admonition-title::before, [dir=rtl] .md-typeset summary::before {\n right: 0.6rem;\n left: initial;\n}\n.md-typeset .admonition-title + .tabbed-set:last-child, .md-typeset summary + .tabbed-set:last-child {\n margin-top: 0;\n}\n\n.md-typeset .admonition.note, .md-typeset details.note {\n border-color: #448aff;\n}\n\n.md-typeset .note > .admonition-title, .md-typeset .note > summary {\n background-color: rgba(68, 138, 255, 0.1);\n border-color: #448aff;\n}\n.md-typeset .note > .admonition-title::before, .md-typeset .note > summary::before {\n background-color: #448aff;\n mask-image: var(--md-admonition-icon--note);\n mask-repeat: no-repeat;\n mask-size: contain;\n}\n\n.md-typeset .admonition.abstract, .md-typeset details.abstract, .md-typeset .admonition.tldr, .md-typeset details.tldr, .md-typeset .admonition.summary, .md-typeset details.summary {\n border-color: #00b0ff;\n}\n\n.md-typeset .abstract > .admonition-title, .md-typeset .abstract > summary, .md-typeset .tldr > .admonition-title, .md-typeset .tldr > summary, .md-typeset .summary > .admonition-title, .md-typeset .summary > summary {\n background-color: rgba(0, 176, 255, 0.1);\n border-color: #00b0ff;\n}\n.md-typeset .abstract > .admonition-title::before, .md-typeset .abstract > summary::before, .md-typeset .tldr > .admonition-title::before, .md-typeset .tldr > summary::before, .md-typeset .summary > .admonition-title::before, .md-typeset .summary > summary::before {\n background-color: #00b0ff;\n mask-image: var(--md-admonition-icon--abstract);\n mask-repeat: no-repeat;\n mask-size: contain;\n}\n\n.md-typeset .admonition.info, .md-typeset details.info, .md-typeset .admonition.todo, .md-typeset details.todo {\n border-color: #00b8d4;\n}\n\n.md-typeset .info > .admonition-title, .md-typeset .info > summary, .md-typeset .todo > .admonition-title, .md-typeset .todo > summary {\n background-color: rgba(0, 184, 212, 0.1);\n border-color: #00b8d4;\n}\n.md-typeset .info > .admonition-title::before, .md-typeset .info > summary::before, .md-typeset .todo > .admonition-title::before, .md-typeset .todo > summary::before {\n background-color: #00b8d4;\n mask-image: var(--md-admonition-icon--info);\n mask-repeat: no-repeat;\n mask-size: contain;\n}\n\n.md-typeset .admonition.tip, .md-typeset details.tip, .md-typeset .admonition.important, .md-typeset details.important, .md-typeset .admonition.hint, .md-typeset details.hint {\n border-color: #00bfa5;\n}\n\n.md-typeset .tip > .admonition-title, .md-typeset .tip > summary, .md-typeset .important > .admonition-title, .md-typeset .important > summary, .md-typeset .hint > .admonition-title, .md-typeset .hint > summary {\n background-color: rgba(0, 191, 165, 0.1);\n border-color: #00bfa5;\n}\n.md-typeset .tip > .admonition-title::before, .md-typeset .tip > summary::before, .md-typeset .important > .admonition-title::before, .md-typeset .important > summary::before, .md-typeset .hint > .admonition-title::before, .md-typeset .hint > summary::before {\n background-color: #00bfa5;\n mask-image: var(--md-admonition-icon--tip);\n mask-repeat: no-repeat;\n mask-size: contain;\n}\n\n.md-typeset .admonition.success, .md-typeset details.success, .md-typeset .admonition.done, .md-typeset details.done, .md-typeset .admonition.check, .md-typeset details.check {\n border-color: #00c853;\n}\n\n.md-typeset .success > .admonition-title, .md-typeset .success > summary, .md-typeset .done > .admonition-title, .md-typeset .done > summary, .md-typeset .check > .admonition-title, .md-typeset .check > summary {\n background-color: rgba(0, 200, 83, 0.1);\n border-color: #00c853;\n}\n.md-typeset .success > .admonition-title::before, .md-typeset .success > summary::before, .md-typeset .done > .admonition-title::before, .md-typeset .done > summary::before, .md-typeset .check > .admonition-title::before, .md-typeset .check > summary::before {\n background-color: #00c853;\n mask-image: var(--md-admonition-icon--success);\n mask-repeat: no-repeat;\n mask-size: contain;\n}\n\n.md-typeset .admonition.question, .md-typeset details.question, .md-typeset .admonition.faq, .md-typeset details.faq, .md-typeset .admonition.help, .md-typeset details.help {\n border-color: #64dd17;\n}\n\n.md-typeset .question > .admonition-title, .md-typeset .question > summary, .md-typeset .faq > .admonition-title, .md-typeset .faq > summary, .md-typeset .help > .admonition-title, .md-typeset .help > summary {\n background-color: rgba(100, 221, 23, 0.1);\n border-color: #64dd17;\n}\n.md-typeset .question > .admonition-title::before, .md-typeset .question > summary::before, .md-typeset .faq > .admonition-title::before, .md-typeset .faq > summary::before, .md-typeset .help > .admonition-title::before, .md-typeset .help > summary::before {\n background-color: #64dd17;\n mask-image: var(--md-admonition-icon--question);\n mask-repeat: no-repeat;\n mask-size: contain;\n}\n\n.md-typeset .admonition.warning, .md-typeset details.warning, .md-typeset .admonition.attention, .md-typeset details.attention, .md-typeset .admonition.caution, .md-typeset details.caution {\n border-color: #ff9100;\n}\n\n.md-typeset .warning > .admonition-title, .md-typeset .warning > summary, .md-typeset .attention > .admonition-title, .md-typeset .attention > summary, .md-typeset .caution > .admonition-title, .md-typeset .caution > summary {\n background-color: rgba(255, 145, 0, 0.1);\n border-color: #ff9100;\n}\n.md-typeset .warning > .admonition-title::before, .md-typeset .warning > summary::before, .md-typeset .attention > .admonition-title::before, .md-typeset .attention > summary::before, .md-typeset .caution > .admonition-title::before, .md-typeset .caution > summary::before {\n background-color: #ff9100;\n mask-image: var(--md-admonition-icon--warning);\n mask-repeat: no-repeat;\n mask-size: contain;\n}\n\n.md-typeset .admonition.failure, .md-typeset details.failure, .md-typeset .admonition.missing, .md-typeset details.missing, .md-typeset .admonition.fail, .md-typeset details.fail {\n border-color: #ff5252;\n}\n\n.md-typeset .failure > .admonition-title, .md-typeset .failure > summary, .md-typeset .missing > .admonition-title, .md-typeset .missing > summary, .md-typeset .fail > .admonition-title, .md-typeset .fail > summary {\n background-color: rgba(255, 82, 82, 0.1);\n border-color: #ff5252;\n}\n.md-typeset .failure > .admonition-title::before, .md-typeset .failure > summary::before, .md-typeset .missing > .admonition-title::before, .md-typeset .missing > summary::before, .md-typeset .fail > .admonition-title::before, .md-typeset .fail > summary::before {\n background-color: #ff5252;\n mask-image: var(--md-admonition-icon--failure);\n mask-repeat: no-repeat;\n mask-size: contain;\n}\n\n.md-typeset .admonition.danger, .md-typeset details.danger, .md-typeset .admonition.error, .md-typeset details.error {\n border-color: #ff1744;\n}\n\n.md-typeset .danger > .admonition-title, .md-typeset .danger > summary, .md-typeset .error > .admonition-title, .md-typeset .error > summary {\n background-color: rgba(255, 23, 68, 0.1);\n border-color: #ff1744;\n}\n.md-typeset .danger > .admonition-title::before, .md-typeset .danger > summary::before, .md-typeset .error > .admonition-title::before, .md-typeset .error > summary::before {\n background-color: #ff1744;\n mask-image: var(--md-admonition-icon--danger);\n mask-repeat: no-repeat;\n mask-size: contain;\n}\n\n.md-typeset .admonition.bug, .md-typeset details.bug {\n border-color: #f50057;\n}\n\n.md-typeset .bug > .admonition-title, .md-typeset .bug > summary {\n background-color: rgba(245, 0, 87, 0.1);\n border-color: #f50057;\n}\n.md-typeset .bug > .admonition-title::before, .md-typeset .bug > summary::before {\n background-color: #f50057;\n mask-image: var(--md-admonition-icon--bug);\n mask-repeat: no-repeat;\n mask-size: contain;\n}\n\n.md-typeset .admonition.example, .md-typeset details.example {\n border-color: #7c4dff;\n}\n\n.md-typeset .example > .admonition-title, .md-typeset .example > summary {\n background-color: rgba(124, 77, 255, 0.1);\n border-color: #7c4dff;\n}\n.md-typeset .example > .admonition-title::before, .md-typeset .example > summary::before {\n background-color: #7c4dff;\n mask-image: var(--md-admonition-icon--example);\n mask-repeat: no-repeat;\n mask-size: contain;\n}\n\n.md-typeset .admonition.quote, .md-typeset details.quote, .md-typeset .admonition.cite, .md-typeset details.cite {\n border-color: #9e9e9e;\n}\n\n.md-typeset .quote > .admonition-title, .md-typeset .quote > summary, .md-typeset .cite > .admonition-title, .md-typeset .cite > summary {\n background-color: rgba(158, 158, 158, 0.1);\n border-color: #9e9e9e;\n}\n.md-typeset .quote > .admonition-title::before, .md-typeset .quote > summary::before, .md-typeset .cite > .admonition-title::before, .md-typeset .cite > summary::before {\n background-color: #9e9e9e;\n mask-image: var(--md-admonition-icon--quote);\n mask-repeat: no-repeat;\n mask-size: contain;\n}\n\n:root {\n --md-footnotes-icon: svg-load(\"material/keyboard-return.svg\");\n}\n\n.md-typeset [id^=\"fnref:\"]:target {\n scroll-margin-top: initial;\n margin-top: -3.4rem;\n padding-top: 3.4rem;\n}\n.md-typeset [id^=\"fn:\"]:target {\n scroll-margin-top: initial;\n margin-top: -3.45rem;\n padding-top: 3.45rem;\n}\n.md-typeset .footnote {\n color: var(--md-default-fg-color--light);\n font-size: 0.64rem;\n}\n.md-typeset .footnote > ol {\n margin-left: 0;\n}\n.md-typeset .footnote > ol > li {\n transition: color 125ms;\n}\n.md-typeset .footnote > ol > li:target {\n color: var(--md-default-fg-color);\n}\n.md-typeset .footnote > ol > li:hover .footnote-backref, .md-typeset .footnote > ol > li:target .footnote-backref {\n transform: translateX(0);\n opacity: 1;\n}\n.md-typeset .footnote > ol > li > :first-child {\n margin-top: 0;\n}\n.md-typeset .footnote-backref {\n display: inline-block;\n color: var(--md-typeset-a-color);\n font-size: 0;\n vertical-align: text-bottom;\n transform: translateX(0.25rem);\n opacity: 0;\n transition: color 250ms, transform 250ms 250ms, opacity 125ms 250ms;\n}\n@media print {\n .md-typeset .footnote-backref {\n color: var(--md-typeset-a-color);\n transform: translateX(0);\n opacity: 1;\n }\n}\n[dir=rtl] .md-typeset .footnote-backref {\n transform: translateX(-0.25rem);\n}\n.md-typeset .footnote-backref:hover {\n color: var(--md-accent-fg-color);\n}\n.md-typeset .footnote-backref::before {\n display: inline-block;\n width: 0.8rem;\n height: 0.8rem;\n background-color: currentColor;\n mask-image: var(--md-footnotes-icon);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n}\n[dir=rtl] .md-typeset .footnote-backref::before svg {\n transform: scaleX(-1);\n}\n\n.md-typeset .headerlink {\n display: inline-block;\n margin-left: 0.5rem;\n color: var(--md-default-fg-color--lighter);\n opacity: 0;\n transition: color 250ms, opacity 125ms;\n}\n@media print {\n .md-typeset .headerlink {\n display: none;\n }\n}\n[dir=rtl] .md-typeset .headerlink {\n margin-right: 0.5rem;\n margin-left: initial;\n}\n.md-typeset :hover > .headerlink,\n.md-typeset :target > .headerlink,\n.md-typeset .headerlink:focus {\n opacity: 1;\n transition: color 250ms, opacity 125ms;\n}\n.md-typeset :target > .headerlink,\n.md-typeset .headerlink:focus,\n.md-typeset .headerlink:hover {\n color: var(--md-accent-fg-color);\n}\n.md-typeset :target {\n scroll-margin-top: 3.6rem;\n}\n.md-typeset h1:target,\n.md-typeset h2:target,\n.md-typeset h3:target {\n scroll-margin-top: initial;\n}\n.md-typeset h1:target::before,\n.md-typeset h2:target::before,\n.md-typeset h3:target::before {\n display: block;\n margin-top: -3.4rem;\n padding-top: 3.4rem;\n content: \"\";\n}\n.md-typeset h4:target {\n scroll-margin-top: initial;\n}\n.md-typeset h4:target::before {\n display: block;\n margin-top: -3.45rem;\n padding-top: 3.45rem;\n content: \"\";\n}\n.md-typeset h5:target,\n.md-typeset h6:target {\n scroll-margin-top: initial;\n}\n.md-typeset h5:target::before,\n.md-typeset h6:target::before {\n display: block;\n margin-top: -3.6rem;\n padding-top: 3.6rem;\n content: \"\";\n}\n\n.md-typeset div.arithmatex {\n overflow: auto;\n}\n@media screen and (max-width: 44.9375em) {\n .md-typeset div.arithmatex {\n margin: 0 -0.8rem;\n }\n}\n.md-typeset div.arithmatex > * {\n width: min-content;\n margin: 1em auto !important;\n padding: 0 0.8rem;\n touch-action: auto;\n}\n\n.md-typeset del.critic,\n.md-typeset ins.critic,\n.md-typeset .critic.comment {\n box-decoration-break: clone;\n}\n.md-typeset del.critic {\n background-color: var(--md-typeset-del-color);\n}\n.md-typeset ins.critic {\n background-color: var(--md-typeset-ins-color);\n}\n.md-typeset .critic.comment {\n color: var(--md-code-hl-comment-color);\n}\n.md-typeset .critic.comment::before {\n content: \"/* \";\n}\n.md-typeset .critic.comment::after {\n content: \" */\";\n}\n.md-typeset .critic.block {\n display: block;\n margin: 1em 0;\n padding-right: 0.8rem;\n padding-left: 0.8rem;\n overflow: auto;\n box-shadow: none;\n}\n.md-typeset .critic.block > :first-child {\n margin-top: 0.5em;\n}\n.md-typeset .critic.block > :last-child {\n margin-bottom: 0.5em;\n}\n\n:root {\n --md-details-icon: svg-load(\"material/chevron-right.svg\");\n}\n\n.md-typeset details {\n display: flow-root;\n padding-top: 0;\n overflow: visible;\n}\n.md-typeset details[open] > summary::after {\n transform: rotate(90deg);\n}\n.md-typeset details:not([open]) {\n padding-bottom: 0;\n box-shadow: none;\n}\n.md-typeset details:not([open]) > summary {\n border-radius: 0.1rem;\n}\n.md-typeset details::after {\n display: table;\n content: \"\";\n}\n.md-typeset summary {\n display: block;\n min-height: 1rem;\n padding: 0.4rem 1.8rem 0.4rem 2rem;\n border-top-left-radius: 0.1rem;\n border-top-right-radius: 0.1rem;\n cursor: pointer;\n}\n[dir=rtl] .md-typeset summary {\n padding: 0.4rem 2.2rem 0.4rem 1.8rem;\n}\n.md-typeset summary:not(.focus-visible) {\n outline: none;\n -webkit-tap-highlight-color: transparent;\n}\n.md-typeset summary::after {\n position: absolute;\n top: 0.4rem;\n right: 0.4rem;\n width: 1rem;\n height: 1rem;\n background-color: currentColor;\n mask-image: var(--md-details-icon);\n mask-repeat: no-repeat;\n mask-size: contain;\n transform: rotate(0deg);\n transition: transform 250ms;\n content: \"\";\n}\n[dir=rtl] .md-typeset summary::after {\n right: initial;\n left: 0.4rem;\n transform: rotate(180deg);\n}\n.md-typeset summary::marker, .md-typeset summary::-webkit-details-marker {\n display: none;\n}\n\n.md-typeset .emojione,\n.md-typeset .twemoji,\n.md-typeset .gemoji {\n display: inline-flex;\n height: 1.125em;\n vertical-align: text-top;\n}\n.md-typeset .emojione svg,\n.md-typeset .twemoji svg,\n.md-typeset .gemoji svg {\n width: 1.125em;\n max-height: 100%;\n fill: currentColor;\n}\n\n.highlight .o,\n.highlight .ow {\n color: var(--md-code-hl-operator-color);\n}\n.highlight .p {\n color: var(--md-code-hl-punctuation-color);\n}\n.highlight .cpf,\n.highlight .l,\n.highlight .s,\n.highlight .sb,\n.highlight .sc,\n.highlight .s2,\n.highlight .si,\n.highlight .s1,\n.highlight .ss {\n color: var(--md-code-hl-string-color);\n}\n.highlight .cp,\n.highlight .se,\n.highlight .sh,\n.highlight .sr,\n.highlight .sx {\n color: var(--md-code-hl-special-color);\n}\n.highlight .m,\n.highlight .mb,\n.highlight .mf,\n.highlight .mh,\n.highlight .mi,\n.highlight .il,\n.highlight .mo {\n color: var(--md-code-hl-number-color);\n}\n.highlight .k,\n.highlight .kd,\n.highlight .kn,\n.highlight .kp,\n.highlight .kr,\n.highlight .kt {\n color: var(--md-code-hl-keyword-color);\n}\n.highlight .kc,\n.highlight .n {\n color: var(--md-code-hl-name-color);\n}\n.highlight .no,\n.highlight .nb,\n.highlight .bp {\n color: var(--md-code-hl-constant-color);\n}\n.highlight .nc,\n.highlight .ne,\n.highlight .nf,\n.highlight .nn {\n color: var(--md-code-hl-function-color);\n}\n.highlight .nd,\n.highlight .ni,\n.highlight .nl,\n.highlight .nt {\n color: var(--md-code-hl-keyword-color);\n}\n.highlight .c,\n.highlight .cm,\n.highlight .c1,\n.highlight .ch,\n.highlight .cs,\n.highlight .sd {\n color: var(--md-code-hl-comment-color);\n}\n.highlight .na,\n.highlight .nv,\n.highlight .vc,\n.highlight .vg,\n.highlight .vi {\n color: var(--md-code-hl-variable-color);\n}\n.highlight .ge,\n.highlight .gr,\n.highlight .gh,\n.highlight .go,\n.highlight .gp,\n.highlight .gs,\n.highlight .gu,\n.highlight .gt {\n color: var(--md-code-hl-generic-color);\n}\n.highlight .gd,\n.highlight .gi {\n margin: 0 -0.125em;\n padding: 0 0.125em;\n border-radius: 0.1rem;\n}\n.highlight .gd {\n background-color: var(--md-typeset-del-color);\n}\n.highlight .gi {\n background-color: var(--md-typeset-ins-color);\n}\n.highlight .hll {\n display: block;\n margin: 0 -1.1764705882em;\n padding: 0 1.1764705882em;\n background-color: var(--md-code-hl-color);\n}\n.highlight [data-linenos]::before {\n position: sticky;\n left: -1.1764705882em;\n float: left;\n margin-right: 1.1764705882em;\n margin-left: -1.1764705882em;\n padding-left: 1.1764705882em;\n color: var(--md-default-fg-color--light);\n background-color: var(--md-code-bg-color);\n box-shadow: -0.05rem 0 var(--md-default-fg-color--lightest) inset;\n content: attr(data-linenos);\n user-select: none;\n}\n\n.highlighttable {\n display: flow-root;\n overflow: hidden;\n}\n.highlighttable tbody,\n.highlighttable td {\n display: block;\n padding: 0;\n}\n.highlighttable tr {\n display: flex;\n}\n.highlighttable pre {\n margin: 0;\n}\n.highlighttable .linenos {\n padding: 0.7720588235em 1.1764705882em;\n padding-right: 0;\n font-size: 0.85em;\n background-color: var(--md-code-bg-color);\n user-select: none;\n}\n.highlighttable .linenodiv {\n padding-right: 0.5882352941em;\n box-shadow: -0.05rem 0 var(--md-default-fg-color--lightest) inset;\n}\n.highlighttable .linenodiv pre {\n color: var(--md-default-fg-color--light);\n text-align: right;\n}\n.highlighttable .code {\n flex: 1;\n overflow: hidden;\n}\n\n.md-typeset .highlighttable {\n margin: 1em 0;\n direction: ltr;\n border-radius: 0.1rem;\n}\n.md-typeset .highlighttable code {\n border-radius: 0;\n}\n@media screen and (max-width: 44.9375em) {\n .md-typeset > .highlight {\n margin: 1em -0.8rem;\n }\n .md-typeset > .highlight .hll {\n margin: 0 -0.8rem;\n padding: 0 0.8rem;\n }\n .md-typeset > .highlight code {\n border-radius: 0;\n }\n .md-typeset > .highlighttable {\n margin: 1em -0.8rem;\n border-radius: 0;\n }\n .md-typeset > .highlighttable .hll {\n margin: 0 -0.8rem;\n padding: 0 0.8rem;\n }\n}\n\n.md-typeset .keys kbd::before,\n.md-typeset .keys kbd::after {\n position: relative;\n margin: 0;\n color: inherit;\n -moz-osx-font-smoothing: initial;\n -webkit-font-smoothing: initial;\n}\n.md-typeset .keys span {\n padding: 0 0.2em;\n color: var(--md-default-fg-color--light);\n}\n.md-typeset .keys .key-alt::before {\n padding-right: 0.4em;\n content: \"⎇\";\n}\n.md-typeset .keys .key-left-alt::before {\n padding-right: 0.4em;\n content: \"⎇\";\n}\n.md-typeset .keys .key-right-alt::before {\n padding-right: 0.4em;\n content: \"⎇\";\n}\n.md-typeset .keys .key-command::before {\n padding-right: 0.4em;\n content: \"⌘\";\n}\n.md-typeset .keys .key-left-command::before {\n padding-right: 0.4em;\n content: \"⌘\";\n}\n.md-typeset .keys .key-right-command::before {\n padding-right: 0.4em;\n content: \"⌘\";\n}\n.md-typeset .keys .key-control::before {\n padding-right: 0.4em;\n content: \"⌃\";\n}\n.md-typeset .keys .key-left-control::before {\n padding-right: 0.4em;\n content: \"⌃\";\n}\n.md-typeset .keys .key-right-control::before {\n padding-right: 0.4em;\n content: \"⌃\";\n}\n.md-typeset .keys .key-meta::before {\n padding-right: 0.4em;\n content: \"◆\";\n}\n.md-typeset .keys .key-left-meta::before {\n padding-right: 0.4em;\n content: \"◆\";\n}\n.md-typeset .keys .key-right-meta::before {\n padding-right: 0.4em;\n content: \"◆\";\n}\n.md-typeset .keys .key-option::before {\n padding-right: 0.4em;\n content: \"⌥\";\n}\n.md-typeset .keys .key-left-option::before {\n padding-right: 0.4em;\n content: \"⌥\";\n}\n.md-typeset .keys .key-right-option::before {\n padding-right: 0.4em;\n content: \"⌥\";\n}\n.md-typeset .keys .key-shift::before {\n padding-right: 0.4em;\n content: \"⇧\";\n}\n.md-typeset .keys .key-left-shift::before {\n padding-right: 0.4em;\n content: \"⇧\";\n}\n.md-typeset .keys .key-right-shift::before {\n padding-right: 0.4em;\n content: \"⇧\";\n}\n.md-typeset .keys .key-super::before {\n padding-right: 0.4em;\n content: \"❖\";\n}\n.md-typeset .keys .key-left-super::before {\n padding-right: 0.4em;\n content: \"❖\";\n}\n.md-typeset .keys .key-right-super::before {\n padding-right: 0.4em;\n content: \"❖\";\n}\n.md-typeset .keys .key-windows::before {\n padding-right: 0.4em;\n content: \"⊞\";\n}\n.md-typeset .keys .key-left-windows::before {\n padding-right: 0.4em;\n content: \"⊞\";\n}\n.md-typeset .keys .key-right-windows::before {\n padding-right: 0.4em;\n content: \"⊞\";\n}\n.md-typeset .keys .key-arrow-down::before {\n padding-right: 0.4em;\n content: \"↓\";\n}\n.md-typeset .keys .key-arrow-left::before {\n padding-right: 0.4em;\n content: \"←\";\n}\n.md-typeset .keys .key-arrow-right::before {\n padding-right: 0.4em;\n content: \"→\";\n}\n.md-typeset .keys .key-arrow-up::before {\n padding-right: 0.4em;\n content: \"↑\";\n}\n.md-typeset .keys .key-backspace::before {\n padding-right: 0.4em;\n content: \"⌫\";\n}\n.md-typeset .keys .key-backtab::before {\n padding-right: 0.4em;\n content: \"⇤\";\n}\n.md-typeset .keys .key-caps-lock::before {\n padding-right: 0.4em;\n content: \"⇪\";\n}\n.md-typeset .keys .key-clear::before {\n padding-right: 0.4em;\n content: \"⌧\";\n}\n.md-typeset .keys .key-context-menu::before {\n padding-right: 0.4em;\n content: \"☰\";\n}\n.md-typeset .keys .key-delete::before {\n padding-right: 0.4em;\n content: \"⌦\";\n}\n.md-typeset .keys .key-eject::before {\n padding-right: 0.4em;\n content: \"⏏\";\n}\n.md-typeset .keys .key-end::before {\n padding-right: 0.4em;\n content: \"⤓\";\n}\n.md-typeset .keys .key-escape::before {\n padding-right: 0.4em;\n content: \"⎋\";\n}\n.md-typeset .keys .key-home::before {\n padding-right: 0.4em;\n content: \"⤒\";\n}\n.md-typeset .keys .key-insert::before {\n padding-right: 0.4em;\n content: \"⎀\";\n}\n.md-typeset .keys .key-page-down::before {\n padding-right: 0.4em;\n content: \"⇟\";\n}\n.md-typeset .keys .key-page-up::before {\n padding-right: 0.4em;\n content: \"⇞\";\n}\n.md-typeset .keys .key-print-screen::before {\n padding-right: 0.4em;\n content: \"⎙\";\n}\n.md-typeset .keys .key-tab::after {\n padding-left: 0.4em;\n content: \"⇥\";\n}\n.md-typeset .keys .key-num-enter::after {\n padding-left: 0.4em;\n content: \"⌤\";\n}\n.md-typeset .keys .key-enter::after {\n padding-left: 0.4em;\n content: \"⏎\";\n}\n\n.md-typeset .tabbed-content {\n display: none;\n order: 99;\n width: 100%;\n box-shadow: 0 -0.05rem var(--md-default-fg-color--lightest);\n}\n@media print {\n .md-typeset .tabbed-content {\n display: block;\n order: initial;\n }\n}\n.md-typeset .tabbed-content > pre:only-child,\n.md-typeset .tabbed-content > .highlight:only-child pre,\n.md-typeset .tabbed-content > .highlighttable:only-child {\n margin: 0;\n}\n.md-typeset .tabbed-content > pre:only-child > code,\n.md-typeset .tabbed-content > .highlight:only-child pre > code,\n.md-typeset .tabbed-content > .highlighttable:only-child > code {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n.md-typeset .tabbed-content > .tabbed-set {\n margin: 0;\n}\n.md-typeset .tabbed-set {\n position: relative;\n display: flex;\n flex-wrap: wrap;\n margin: 1em 0;\n border-radius: 0.1rem;\n}\n.md-typeset .tabbed-set > input {\n position: absolute;\n width: 0;\n height: 0;\n opacity: 0;\n}\n.md-typeset .tabbed-set > input:checked + label {\n color: var(--md-accent-fg-color);\n border-color: var(--md-accent-fg-color);\n}\n.md-typeset .tabbed-set > input:checked + label + .tabbed-content {\n display: block;\n}\n.md-typeset .tabbed-set > input:focus + label {\n outline-style: auto;\n}\n.md-typeset .tabbed-set > input:not(.focus-visible) + label {\n outline: none;\n -webkit-tap-highlight-color: transparent;\n}\n.md-typeset .tabbed-set > label {\n z-index: 1;\n width: auto;\n padding: 0.9375em 1.25em 0.78125em;\n color: var(--md-default-fg-color--light);\n font-weight: 700;\n font-size: 0.64rem;\n border-bottom: 0.1rem solid transparent;\n cursor: pointer;\n transition: color 250ms;\n}\n.md-typeset .tabbed-set > label:hover {\n color: var(--md-accent-fg-color);\n}\n\n:root {\n --md-tasklist-icon:\n svg-load(\"octicons/check-circle-fill-24.svg\");\n --md-tasklist-icon--checked:\n svg-load(\"octicons/check-circle-fill-24.svg\");\n}\n\n.md-typeset .task-list-item {\n position: relative;\n list-style-type: none;\n}\n.md-typeset .task-list-item [type=checkbox] {\n position: absolute;\n top: 0.45em;\n left: -2em;\n}\n[dir=rtl] .md-typeset .task-list-item [type=checkbox] {\n right: -2em;\n left: initial;\n}\n.md-typeset .task-list-control [type=checkbox] {\n z-index: -1;\n opacity: 0;\n}\n.md-typeset .task-list-indicator::before {\n position: absolute;\n top: 0.15em;\n left: -1.5em;\n width: 1.25em;\n height: 1.25em;\n background-color: var(--md-default-fg-color--lightest);\n mask-image: var(--md-tasklist-icon);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n}\n[dir=rtl] .md-typeset .task-list-indicator::before {\n right: -1.5em;\n left: initial;\n}\n.md-typeset [type=checkbox]:checked + .task-list-indicator::before {\n background-color: #00e676;\n mask-image: var(--md-tasklist-icon--checked);\n}\n\n@media screen and (min-width: 45em) {\n .md-typeset .inline {\n float: left;\n width: 11.7rem;\n margin-top: 0;\n margin-right: 0.8rem;\n margin-bottom: 0.8rem;\n }\n [dir=rtl] .md-typeset .inline {\n float: right;\n margin-right: 0;\n margin-left: 0.8rem;\n }\n .md-typeset .inline.end {\n float: right;\n margin-right: 0;\n margin-left: 0.8rem;\n }\n [dir=rtl] .md-typeset .inline.end {\n float: left;\n margin-right: 0.8rem;\n margin-left: 0;\n }\n}\n\n/*# sourceMappingURL=main.css.map */","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Enforce correct box model and prevent adjustments of font size after\n// orientation changes in IE and iOS\nhtml {\n box-sizing: border-box;\n text-size-adjust: none;\n}\n\n// All elements shall inherit the document default\n*,\n*::before,\n*::after {\n box-sizing: inherit;\n}\n\n// Remove margin in all browsers\nbody {\n margin: 0;\n}\n\n// Reset tap outlines on iOS and Android\na,\nbutton,\nlabel,\ninput {\n -webkit-tap-highlight-color: transparent;\n}\n\n// Reset link styles\na {\n color: inherit;\n text-decoration: none;\n}\n\n// Normalize horizontal separator styles\nhr {\n display: block;\n box-sizing: content-box;\n height: px2rem(1px);\n padding: 0;\n overflow: visible;\n border: 0;\n}\n\n// Normalize font-size in all browsers\nsmall {\n font-size: 80%;\n}\n\n// Prevent subscript and superscript from affecting line-height\nsub,\nsup {\n line-height: 1em;\n}\n\n// Remove border on image\nimg {\n border-style: none;\n}\n\n// Reset table styles\ntable {\n border-collapse: separate;\n border-spacing: 0;\n}\n\n// Reset table cell styles\ntd,\nth {\n font-weight: 400;\n vertical-align: top;\n}\n\n// Reset button styles\nbutton {\n margin: 0;\n padding: 0;\n font-size: inherit;\n background: transparent;\n border: 0;\n}\n\n// Reset input styles\ninput {\n border: 0;\n outline: none;\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Color definitions\n:root {\n\n // Default color shades\n --md-default-fg-color: hsla(0, 0%, 0%, 0.87);\n --md-default-fg-color--light: hsla(0, 0%, 0%, 0.54);\n --md-default-fg-color--lighter: hsla(0, 0%, 0%, 0.32);\n --md-default-fg-color--lightest: hsla(0, 0%, 0%, 0.07);\n --md-default-bg-color: hsla(0, 0%, 100%, 1);\n --md-default-bg-color--light: hsla(0, 0%, 100%, 0.7);\n --md-default-bg-color--lighter: hsla(0, 0%, 100%, 0.3);\n --md-default-bg-color--lightest: hsla(0, 0%, 100%, 0.12);\n\n // Primary color shades\n --md-primary-fg-color: hsla(#{hex2hsl($clr-indigo-500)}, 1);\n --md-primary-fg-color--light: hsla(#{hex2hsl($clr-indigo-400)}, 1);\n --md-primary-fg-color--dark: hsla(#{hex2hsl($clr-indigo-700)}, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n\n // Accent color shades\n --md-accent-fg-color: hsla(#{hex2hsl($clr-indigo-a200)}, 1);\n --md-accent-fg-color--transparent: hsla(#{hex2hsl($clr-indigo-a200)}, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n\n // Light theme (default)\n > * {\n\n // Code color shades\n --md-code-fg-color: hsla(200, 18%, 26%, 1);\n --md-code-bg-color: hsla(0, 0%, 96%, 1);\n\n // Code highlighting color shades\n --md-code-hl-color: hsla(#{hex2hsl($clr-yellow-a200)}, 0.5);\n --md-code-hl-number-color: hsla(0, 67%, 50%, 1);\n --md-code-hl-special-color: hsla(340, 83%, 47%, 1);\n --md-code-hl-function-color: hsla(291, 45%, 50%, 1);\n --md-code-hl-constant-color: hsla(250, 63%, 60%, 1);\n --md-code-hl-keyword-color: hsla(219, 54%, 51%, 1);\n --md-code-hl-string-color: hsla(150, 63%, 30%, 1);\n --md-code-hl-name-color: var(--md-code-fg-color);\n --md-code-hl-operator-color: var(--md-default-fg-color--light);\n --md-code-hl-punctuation-color: var(--md-default-fg-color--light);\n --md-code-hl-comment-color: var(--md-default-fg-color--light);\n --md-code-hl-generic-color: var(--md-default-fg-color--light);\n --md-code-hl-variable-color: var(--md-default-fg-color--light);\n\n // Typeset color shades\n --md-typeset-color: var(--md-default-fg-color);\n --md-typeset-a-color: var(--md-primary-fg-color);\n\n // Typeset `mark` color shades\n --md-typeset-mark-color: hsla(#{hex2hsl($clr-yellow-a200)}, 0.5);\n\n // Typeset `del` and `ins` color shades\n --md-typeset-del-color: hsla(6, 90%, 60%, 0.15);\n --md-typeset-ins-color: hsla(150, 90%, 44%, 0.15);\n\n // Typeset `kbd` color shades\n --md-typeset-kbd-color: hsla(0, 0%, 98%, 1);\n --md-typeset-kbd-accent-color: hsla(0, 100%, 100%, 1);\n --md-typeset-kbd-border-color: hsla(0, 0%, 72%, 1);\n\n // Admonition color shades\n --md-admonition-fg-color: var(--md-default-fg-color);\n --md-admonition-bg-color: var(--md-default-bg-color);\n\n // Footer color shades\n --md-footer-fg-color: hsla(0, 0%, 100%, 1);\n --md-footer-fg-color--light: hsla(0, 0%, 100%, 0.7);\n --md-footer-fg-color--lighter: hsla(0, 0%, 100%, 0.3);\n --md-footer-bg-color: hsla(0, 0%, 0%, 0.87);\n --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.32);\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon\n.md-icon {\n\n // SVG defaults\n svg {\n display: block;\n width: px2rem(24px);\n height: px2rem(24px);\n fill: currentColor;\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules: font definitions\n// ----------------------------------------------------------------------------\n\n// Enable font-smoothing in Webkit and FF\nbody {\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Define default fonts\nbody,\ninput {\n color: var(--md-typeset-color);\n font-feature-settings: \"kern\", \"liga\";\n font-family:\n var(--md-text-font-family, _),\n -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;\n}\n\n// Define monospaced fonts\ncode,\npre,\nkbd {\n color: var(--md-typeset-color);\n font-feature-settings: \"kern\";\n font-family:\n var(--md-code-font-family, _),\n SFMono-Regular, Consolas, Menlo, monospace;\n}\n\n// ----------------------------------------------------------------------------\n// Rules: typesetted content\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n --md-typeset-table--ascending: svg-load(\"material/arrow-down.svg\");\n --md-typeset-table--descending: svg-load(\"material/arrow-up.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Content that is typeset - if possible, all margins, paddings and font sizes\n// should be set in ems, so nested blocks (e.g. admonitions) render correctly.\n.md-typeset {\n font-size: px2rem(16px);\n line-height: 1.6;\n color-adjust: exact;\n\n // [print]: We'll use a smaller `font-size` for printing, so code examples\n // don't break too early, and `16px` looks too big anyway.\n @media print {\n font-size: px2rem(13.6px);\n }\n\n // Default spacing\n ul,\n ol,\n dl,\n figure,\n blockquote,\n pre {\n display: flow-root;\n margin: 1em 0;\n }\n\n // Headline on level 1\n h1 {\n margin: 0 0 px2em(40px, 32px);\n color: var(--md-default-fg-color--light);\n font-weight: 300;\n font-size: px2em(32px);\n line-height: 1.3;\n letter-spacing: -0.01em;\n }\n\n // Headline on level 2\n h2 {\n margin: px2em(40px, 25px) 0 px2em(16px, 25px);\n font-weight: 300;\n font-size: px2em(25px);\n line-height: 1.4;\n letter-spacing: -0.01em;\n }\n\n // Headline on level 3\n h3 {\n margin: px2em(32px, 20px) 0 px2em(16px, 20px);\n font-weight: 400;\n font-size: px2em(20px);\n line-height: 1.5;\n letter-spacing: -0.01em;\n }\n\n // Headline on level 3 following level 2\n h2 + h3 {\n margin-top: px2em(16px, 20px);\n }\n\n // Headline on level 4\n h4 {\n margin: px2em(16px) 0;\n font-weight: 700;\n letter-spacing: -0.01em;\n }\n\n // Headline on level 5-6\n h5,\n h6 {\n margin: px2em(16px, 12.8px) 0;\n color: var(--md-default-fg-color--light);\n font-weight: 700;\n font-size: px2em(12.8px);\n letter-spacing: -0.01em;\n }\n\n // Headline on level 5\n h5 {\n text-transform: uppercase;\n }\n\n // Horizontal separator\n hr {\n display: flow-root;\n margin: 1.5em 0;\n border-bottom: px2rem(1px) solid var(--md-default-fg-color--lightest);\n }\n\n // Text link\n a {\n color: var(--md-typeset-a-color);\n word-break: break-word;\n\n // Also enable color transition on pseudo elements\n &,\n &::before {\n transition: color 125ms;\n }\n\n // Text link on focus/hover\n &:focus,\n &:hover {\n color: var(--md-accent-fg-color);\n }\n }\n\n // Code block\n code,\n pre,\n kbd {\n color: var(--md-code-fg-color);\n direction: ltr;\n\n // [print]: Wrap text and hide scollbars\n @media print {\n white-space: pre-wrap;\n }\n }\n\n // Inline code block\n code {\n padding: 0 px2em(4px, 13.6px);\n font-size: px2em(13.6px);\n word-break: break-word;\n background-color: var(--md-code-bg-color);\n border-radius: px2rem(2px);\n box-decoration-break: clone;\n\n // Hide outline for pointer devices\n &:not(.focus-visible) {\n outline: none;\n -webkit-tap-highlight-color: transparent;\n }\n }\n\n // Code block in headline\n h1 code,\n h2 code,\n h3 code,\n h4 code,\n h5 code,\n h6 code {\n margin: initial;\n padding: initial;\n background-color: transparent;\n box-shadow: none;\n }\n\n // Ensure link color in code blocks\n a code {\n color: currentColor;\n }\n\n // Unformatted content\n pre {\n position: relative;\n line-height: 1.4;\n\n // Code block\n > code {\n display: block;\n margin: 0;\n padding: px2em(10.5px, 13.6px) px2em(16px, 13.6px);\n overflow: auto;\n word-break: normal;\n box-shadow: none;\n box-decoration-break: slice;\n touch-action: auto;\n scrollbar-width: thin;\n scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n\n // Code block on hover\n &:hover {\n scrollbar-color: var(--md-accent-fg-color) transparent;\n }\n\n // Webkit scrollbar\n &::-webkit-scrollbar {\n width: px2rem(4px);\n height: px2rem(4px);\n }\n\n // Webkit scrollbar thumb\n &::-webkit-scrollbar-thumb {\n background-color: var(--md-default-fg-color--lighter);\n\n // Webkit scrollbar thumb on hover\n &:hover {\n background-color: var(--md-accent-fg-color);\n }\n }\n }\n }\n\n // [mobile -]: Align with body copy\n @include break-to-device(mobile) {\n\n // Unformatted text\n > pre {\n margin: 1em px2rem(-16px);\n\n // Code block\n code {\n border-radius: 0;\n }\n }\n }\n\n // Keyboard key\n kbd {\n display: inline-block;\n padding: 0 px2em(8px, 12px);\n color: var(--md-default-fg-color);\n font-size: px2em(12px);\n vertical-align: text-top;\n word-break: break-word;\n background-color: var(--md-typeset-kbd-color);\n border-radius: px2rem(2px);\n box-shadow:\n 0 px2rem(2px) 0 px2rem(1px) var(--md-typeset-kbd-border-color),\n 0 px2rem(2px) 0 var(--md-typeset-kbd-border-color),\n 0 px2rem(-2px) px2rem(4px) var(--md-typeset-kbd-accent-color) inset;\n }\n\n // Text highlighting marker\n mark {\n color: inherit;\n word-break: break-word;\n background-color: var(--md-typeset-mark-color);\n box-decoration-break: clone;\n }\n\n // Abbreviation\n abbr {\n text-decoration: none;\n border-bottom: px2rem(1px) dotted var(--md-default-fg-color--light);\n cursor: help;\n\n // Show tooltip for touch devices\n @media (hover: none) {\n position: relative;\n\n // Tooltip\n &[title]:focus::after,\n &[title]:hover::after {\n @include z-depth(2);\n\n position: absolute;\n left: 0;\n display: inline-block;\n width: auto;\n min-width: max-content;\n max-width: 80%;\n margin-top: 2em;\n padding: px2rem(4px) px2rem(6px);\n color: var(--md-default-bg-color);\n font-size: px2rem(14px);\n background-color: var(--md-default-fg-color);\n border-radius: px2rem(2px);\n content: attr(title);\n }\n }\n }\n\n // Small text\n small {\n opacity: 0.75;\n }\n\n // Superscript and subscript\n sup,\n sub {\n margin-left: px2em(1px, 12.8px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: px2em(1px, 12.8px);\n margin-left: initial;\n }\n }\n\n // Blockquotes, possibly nested\n blockquote {\n padding-left: px2rem(12px);\n color: var(--md-default-fg-color--light);\n border-left: px2rem(4px) solid var(--md-default-fg-color--lighter);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding-right: px2rem(12px);\n padding-left: initial;\n border-right: px2rem(4px) solid var(--md-default-fg-color--lighter);\n border-left: initial;\n }\n }\n\n // Unordered list\n ul {\n list-style-type: disc;\n }\n\n // Unordered and ordered list\n ul,\n ol {\n margin-left: px2em(10px);\n padding: 0;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: px2em(10px);\n margin-left: initial;\n }\n\n // Nested ordered list\n ol {\n list-style-type: lower-alpha;\n\n // Triply nested ordered list\n ol {\n list-style-type: lower-roman;\n }\n }\n\n // List element\n li {\n margin-bottom: 0.5em;\n margin-left: px2em(20px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: px2em(20px);\n margin-left: initial;\n }\n\n // Adjust spacing\n p,\n blockquote {\n margin: 0.5em 0;\n }\n\n // Adjust spacing on last child\n &:last-child {\n margin-bottom: 0;\n }\n\n // Nested list\n ul,\n ol {\n margin: 0.5em 0 0.5em px2em(10px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: px2em(10px);\n margin-left: initial;\n }\n }\n }\n }\n\n // Definition list\n dd {\n margin: 1em 0 1.5em px2em(30px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: px2em(30px);\n margin-left: initial;\n }\n }\n\n // Image or icon\n img,\n svg {\n max-width: 100%;\n height: auto;\n\n // Adjust spacing when left-aligned\n &[align=\"left\"] {\n margin: 1em;\n margin-left: 0;\n }\n\n // Adjust spacing when right-aligned\n &[align=\"right\"] {\n margin: 1em;\n margin-right: 0;\n }\n\n // Adjust spacing when sole children\n &[align]:only-child {\n margin-top: 0;\n }\n }\n\n // Figure\n figure {\n width: fit-content;\n max-width: 100%;\n margin: 0 auto;\n text-align: center;\n\n // Figure images\n img {\n display: block;\n }\n }\n\n // Figure caption\n figcaption {\n max-width: px2rem(480px);\n margin: 1em auto 2em;\n font-style: italic;\n }\n\n // Limit width to container\n iframe {\n max-width: 100%;\n }\n\n // Data table\n table:not([class]) {\n display: inline-block;\n max-width: 100%;\n overflow: auto;\n font-size: px2rem(12.8px);\n background-color: var(--md-default-bg-color);\n border-radius: px2rem(2px);\n box-shadow:\n 0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.05),\n 0 0 px2rem(1px) hsla(0, 0%, 0%, 0.1);\n touch-action: auto;\n\n // [print]: Reset display mode so table header wraps when printing\n @media print {\n display: table;\n }\n\n // Due to margin collapse because of the necessary inline-block hack, we\n // cannot increase the bottom margin on the table, so we just increase the\n // top margin on the following element\n + * {\n margin-top: 1.5em;\n }\n\n // Elements in table heading and cell\n th > *,\n td > * {\n\n // Adjust spacing on first child\n &:first-child {\n margin-top: 0;\n }\n\n // Adjust spacing on last child\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n // Table heading and cell\n th:not([align]),\n td:not([align]) {\n text-align: left;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n text-align: right;\n }\n }\n\n // Table heading\n th {\n min-width: px2rem(100px);\n padding: px2em(12px, 12.8px) px2em(16px, 12.8px);\n color: var(--md-default-bg-color);\n vertical-align: top;\n background-color: var(--md-default-fg-color--light);\n\n // Links in table headings\n a {\n color: inherit;\n }\n }\n\n // Table cell\n td {\n padding: px2em(12px, 12.8px) px2em(16px, 12.8px);\n vertical-align: top;\n border-top: px2rem(1px) solid var(--md-default-fg-color--lightest);\n }\n\n // Table row\n tr {\n transition: background-color 125ms;\n\n // Table row on hover\n &:hover {\n background-color: rgba(0, 0, 0, 0.035);\n box-shadow: 0 px2rem(1px) 0 var(--md-default-bg-color) inset;\n }\n\n // Hide border on first table row\n &:first-child td {\n border-top: 0;\n }\n }\n\n // Text link in table\n a {\n word-break: normal;\n }\n }\n\n // Sortable table\n table th[role=\"columnheader\"] {\n cursor: pointer;\n\n // Sort icon\n &::after {\n display: inline-block;\n width: 1.2em;\n height: 1.2em;\n margin-left: 0.5em;\n vertical-align: sub;\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n }\n\n // Sort ascending\n &[aria-sort=\"ascending\"]::after {\n background-color: currentColor;\n mask-image: var(--md-typeset-table--ascending);\n }\n\n // Sort descending\n &[aria-sort=\"descending\"]::after {\n background-color: currentColor;\n mask-image: var(--md-typeset-table--descending);\n }\n }\n\n // Data table scroll wrapper\n &__scrollwrap {\n margin: 1em px2rem(-16px);\n overflow-x: auto;\n touch-action: auto;\n }\n\n // Data table wrapper\n &__table {\n display: inline-block;\n margin-bottom: 0.5em;\n padding: 0 px2rem(16px);\n\n // [print]: Reset display mode so table header wraps when printing\n @media print {\n display: block;\n }\n\n // Data table\n html & table {\n display: table;\n width: 100%;\n margin: 0;\n overflow: hidden;\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Variables\n// ----------------------------------------------------------------------------\n\n///\n/// Device-specific breakpoints\n///\n/// @example\n/// $break-devices: (\n/// mobile: (\n/// portrait: 220px 479px,\n/// landscape: 480px 719px\n/// ),\n/// tablet: (\n/// portrait: 720px 959px,\n/// landscape: 960px 1219px\n/// ),\n/// screen: (\n/// small: 1220px 1599px,\n/// medium: 1600px 1999px,\n/// large: 2000px\n/// )\n/// );\n///\n$break-devices: () !default;\n\n// ----------------------------------------------------------------------------\n// Helpers\n// ----------------------------------------------------------------------------\n\n///\n/// Choose minimum and maximum device widths\n///\n@function break-select-min-max($devices) {\n $min: 1000000;\n $max: 0;\n @each $key, $value in $devices {\n @while type-of($value) == map {\n $value: break-select-min-max($value);\n }\n @if type-of($value) == list {\n @each $number in $value {\n @if type-of($number) == number {\n $min: min($number, $min);\n @if $max {\n $max: max($number, $max);\n }\n } @else {\n @error \"Invalid number: #{$number}\";\n }\n }\n } @else if type-of($value) == number {\n $min: min($value, $min);\n $max: null;\n } @else {\n @error \"Invalid value: #{$value}\";\n }\n }\n @return $min, $max;\n}\n\n///\n/// Select minimum and maximum widths for a device breakpoint\n///\n@function break-select-device($device) {\n $current: $break-devices;\n @for $n from 1 through length($device) {\n @if type-of($current) == map {\n $current: map-get($current, nth($device, $n));\n } @else {\n @error \"Invalid device map: #{$devices}\";\n }\n }\n @if type-of($current) == list or type-of($current) == number {\n $current: (default: $current);\n }\n @return break-select-min-max($current);\n}\n\n// ----------------------------------------------------------------------------\n// Mixins\n// ----------------------------------------------------------------------------\n\n///\n/// A minimum-maximum media query breakpoint\n///\n@mixin break-at($breakpoint) {\n @if type-of($breakpoint) == number {\n @media screen and (min-width: $breakpoint) {\n @content;\n }\n } @else if type-of($breakpoint) == list {\n $min: nth($breakpoint, 1);\n $max: nth($breakpoint, 2);\n @if type-of($min) == number and type-of($max) == number {\n @media screen and (min-width: $min) and (max-width: $max) {\n @content;\n }\n } @else {\n @error \"Invalid breakpoint: #{$breakpoint}\";\n }\n } @else {\n @error \"Invalid breakpoint: #{$breakpoint}\";\n }\n}\n\n///\n/// An orientation media query breakpoint\n///\n@mixin break-at-orientation($breakpoint) {\n @if type-of($breakpoint) == string {\n @media screen and (orientation: $breakpoint) {\n @content;\n }\n } @else {\n @error \"Invalid breakpoint: #{$breakpoint}\";\n }\n}\n\n///\n/// A maximum-aspect-ratio media query breakpoint\n///\n@mixin break-at-ratio($breakpoint) {\n @if type-of($breakpoint) == number {\n @media screen and (max-aspect-ratio: $breakpoint) {\n @content;\n }\n } @else {\n @error \"Invalid breakpoint: #{$breakpoint}\";\n }\n}\n\n///\n/// A minimum-maximum media query device breakpoint\n///\n@mixin break-at-device($device) {\n @if type-of($device) == string {\n $device: $device,;\n }\n @if type-of($device) == list {\n $breakpoint: break-select-device($device);\n @if nth($breakpoint, 2) {\n $min: nth($breakpoint, 1);\n $max: nth($breakpoint, 2);\n\n @media screen and (min-width: $min) and (max-width: $max) {\n @content;\n }\n } @else {\n @error \"Invalid device: #{$device}\";\n }\n } @else {\n @error \"Invalid device: #{$device}\";\n }\n}\n\n///\n/// A minimum media query device breakpoint\n///\n@mixin break-from-device($device) {\n @if type-of($device) == string {\n $device: $device,;\n }\n @if type-of($device) == list {\n $breakpoint: break-select-device($device);\n $min: nth($breakpoint, 1);\n\n @media screen and (min-width: $min) {\n @content;\n }\n } @else {\n @error \"Invalid device: #{$device}\";\n }\n}\n\n///\n/// A maximum media query device breakpoint\n///\n@mixin break-to-device($device) {\n @if type-of($device) == string {\n $device: $device,;\n }\n @if type-of($device) == list {\n $breakpoint: break-select-device($device);\n $max: nth($breakpoint, 2);\n\n @media screen and (max-width: $max) {\n @content;\n }\n } @else {\n @error \"Invalid device: #{$device}\";\n }\n}\n","//\n// Name: Material Shadows\n// Description: Mixins for Material Design Shadows.\n// Version: 3.0.1\n//\n// Author: Denis Malinochkin\n// Git: https://github.com/mrmlnc/material-shadows\n//\n// twitter: @mrmlnc\n//\n// ------------------------------------\n\n\n// Mixins\n// ------------------------------------\n\n@mixin z-depth-transition() {\n transition: box-shadow .28s cubic-bezier(.4, 0, .2, 1);\n}\n\n@mixin z-depth-focus() {\n box-shadow: 0 0 8px rgba(0, 0, 0, .18), 0 8px 16px rgba(0, 0, 0, .36);\n}\n\n@mixin z-depth-2dp() {\n box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .14),\n 0 1px 5px 0 rgba(0, 0, 0, .12),\n 0 3px 1px -2px rgba(0, 0, 0, .2);\n}\n\n@mixin z-depth-3dp() {\n box-shadow: 0 3px 4px 0 rgba(0, 0, 0, .14),\n 0 1px 8px 0 rgba(0, 0, 0, .12),\n 0 3px 3px -2px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-4dp() {\n box-shadow: 0 4px 5px 0 rgba(0, 0, 0, .14),\n 0 1px 10px 0 rgba(0, 0, 0, .12),\n 0 2px 4px -1px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-6dp() {\n box-shadow: 0 6px 10px 0 rgba(0, 0, 0, .14),\n 0 1px 18px 0 rgba(0, 0, 0, .12),\n 0 3px 5px -1px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-8dp() {\n box-shadow: 0 8px 10px 1px rgba(0, 0, 0, .14),\n 0 3px 14px 2px rgba(0, 0, 0, .12),\n 0 5px 5px -3px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-16dp() {\n box-shadow: 0 16px 24px 2px rgba(0, 0, 0, .14),\n 0 6px 30px 5px rgba(0, 0, 0, .12),\n 0 8px 10px -5px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-24dp() {\n box-shadow: 0 9px 46px 8px rgba(0, 0, 0, .14),\n 0 24px 38px 3px rgba(0, 0, 0, .12),\n 0 11px 15px -7px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth($dp: 2) {\n @if $dp == 2 {\n @include z-depth-2dp();\n } @else if $dp == 3 {\n @include z-depth-3dp();\n } @else if $dp == 4 {\n @include z-depth-4dp();\n } @else if $dp == 6 {\n @include z-depth-6dp();\n } @else if $dp == 8 {\n @include z-depth-8dp();\n } @else if $dp == 16 {\n @include z-depth-16dp();\n } @else if $dp == 24 {\n @include z-depth-24dp();\n }\n}\n\n\n// Class generator\n// ------------------------------------\n\n@mixin z-depth-classes($transition: false, $focus: false) {\n @if $transition == true {\n &-transition {\n @include z-depth-transition();\n }\n }\n\n @if $focus == true {\n &-focus {\n @include z-depth-focus();\n }\n }\n\n // The available values for the shadow depth\n @each $depth in 2, 3, 4, 6, 8, 16, 24 {\n &-#{$depth}dp {\n @include z-depth($depth);\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules: base grid and containers\n// ----------------------------------------------------------------------------\n\n// Stretch container to viewport and set base `font-size`\nhtml {\n height: 100%;\n overflow-x: hidden;\n // Hack: normally, we would set the base `font-size` to `62.5%`, so we can\n // base all calculations on `10px`, but Chromium and Chrome define a minimal\n // `font-size` of `12px` if the system language is set to Chinese. For this\n // reason we just double the `font-size` and set it to `20px`.\n //\n // See https://github.com/squidfunk/mkdocs-material/issues/911\n font-size: 125%;\n\n // [screen medium +]: Set base `font-size` to `11px`\n @include break-from-device(screen medium) {\n font-size: 137.5%;\n }\n\n // [screen large +]: Set base `font-size` to `12px`\n @include break-from-device(screen large) {\n font-size: 150%;\n }\n}\n\n// Stretch body to container - flexbox is used, so the footer will always be\n// aligned to the bottom of the viewport\nbody {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n min-height: 100%;\n // Hack: reset `font-size` to `10px`, so the spacing for all inline elements\n // is correct again. Otherwise the spacing would be based on `20px`.\n font-size: px2rem(10px);\n background-color: var(--md-default-bg-color);\n\n // [print]: Omit flexbox layout due to a Firefox bug (https://mzl.la/39DgR3m)\n @media print {\n display: block;\n }\n\n // Body in locked state\n &[data-md-state=\"lock\"] {\n\n // [tablet portrait -]: Omit scroll bubbling\n @include break-to-device(tablet portrait) {\n position: fixed;\n }\n }\n}\n\n// ----------------------------------------------------------------------------\n\n// Grid container - this class is applied to wrapper elements within the\n// header, content area and footer, and makes sure that their width is limited\n// to `1220px`, and they are rendered centered if the screen is larger.\n.md-grid {\n max-width: px2rem(1220px);\n margin-right: auto;\n margin-left: auto;\n}\n\n// Main container\n.md-container {\n display: flex;\n flex-direction: column;\n flex-grow: 1;\n\n // [print]: Omit flexbox layout due to a Firefox bug (https://mzl.la/39DgR3m)\n @media print {\n display: block;\n }\n}\n\n// Main area - stretch to remaining space of container\n.md-main {\n flex-grow: 1;\n\n // Main area wrapper\n &__inner {\n display: flex;\n height: 100%;\n margin-top: px2rem(24px + 6px);\n }\n}\n\n// Add ellipsis in case of overflowing text\n.md-ellipsis {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n// ----------------------------------------------------------------------------\n// Rules: navigational elements\n// ----------------------------------------------------------------------------\n\n// Toggle - this class is applied to checkbox elements, which are used to\n// implement the CSS-only drawer and navigation, as well as the search\n.md-toggle {\n display: none;\n}\n\n// Option - this class is applied to radio elements, which are used to\n// implement the color palette toggle\n.md-option {\n position: absolute;\n width: 0;\n height: 0;\n opacity: 0;\n\n // Option label for checked radio button\n &:checked + label:not([hidden]) {\n display: block;\n }\n\n // Option label on focus\n &.focus-visible + label {\n outline-style: auto;\n }\n}\n\n// Skip link\n.md-skip {\n position: fixed;\n // Hack: if we don't set the negative `z-index`, the skip link will force the\n // creation of new layers when code blocks are near the header on scrolling\n z-index: -1;\n margin: px2rem(10px);\n padding: px2rem(6px) px2rem(10px);\n color: var(--md-default-bg-color);\n font-size: px2rem(12.8px);\n background-color: var(--md-default-fg-color);\n border-radius: px2rem(2px);\n transform: translateY(px2rem(8px));\n opacity: 0;\n\n // Show skip link on focus\n &:focus {\n z-index: 10;\n transform: translateY(0);\n opacity: 1;\n transition:\n transform 250ms cubic-bezier(0.4, 0, 0.2, 1),\n opacity 175ms 75ms;\n }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: print styles\n// ----------------------------------------------------------------------------\n\n// Add margins to page\n@page {\n margin: 25mm;\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Announcement bar\n.md-announce {\n overflow: auto;\n background-color: var(--md-footer-bg-color);\n\n // [print]: Hide announcement bar\n @media print {\n display: none;\n }\n\n // Announcement wrapper\n &__inner {\n margin: px2rem(12px) auto;\n padding: 0 px2rem(16px);\n color: var(--md-footer-fg-color);\n font-size: px2rem(14px);\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n --md-clipboard-icon: svg-load(\"material/content-copy.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Button to copy to clipboard\n.md-clipboard {\n position: absolute;\n top: px2em(8px);\n right: px2em(8px);\n z-index: 1;\n width: px2em(24px);\n height: px2em(24px);\n color: var(--md-default-fg-color--lightest);\n border-radius: px2rem(2px);\n cursor: pointer;\n transition: color 250ms;\n\n // [print]: Hide button\n @media print {\n display: none;\n }\n\n // Hide outline for pointer devices\n &:not(.focus-visible) {\n outline: none;\n -webkit-tap-highlight-color: transparent;\n }\n\n // Darken color on code block hover\n :hover > & {\n color: var(--md-default-fg-color--light);\n }\n\n // Button on focus/hover\n &:focus,\n &:hover {\n color: var(--md-accent-fg-color);\n }\n\n // Button icon - the width and height are defined in `em`, so the size is\n // automatically adjusted for nested code blocks (e.g. in admonitions)\n &::after {\n display: block;\n width: px2em(18px);\n height: px2em(18px);\n margin: 0 auto;\n background-color: currentColor;\n mask-image: var(--md-clipboard-icon);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n }\n\n // Inline button\n &--inline {\n cursor: pointer;\n\n // Code block\n code {\n transition:\n color 250ms,\n background-color 250ms;\n }\n\n // Code block on focus/hover\n &:focus code,\n &:hover code {\n color: var(--md-accent-fg-color);\n background-color: var(--md-accent-fg-color--transparent);\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Content area\n.md-content {\n flex-grow: 1;\n // Hack: we must use `overflow: hidden`, so the content area is capped by\n // the dimensions of its parent. Otherwise, long code blocks might lead to\n // a wider content area which will break everything. This, however, induces\n // margin collapse, which will break scroll margins. Adding a large enough\n // scroll padding seems to do the trick, at least in Chrome and Firefox.\n overflow: hidden;\n scroll-padding-top: px2rem(1024px);\n\n // Content wrapper\n &__inner {\n margin: 0 px2rem(16px) px2rem(24px);\n padding-top: px2rem(12px);\n\n // [screen +]: Adjust spacing between content area and sidebars\n @include break-from-device(screen) {\n\n // Sidebar with navigation is visible\n .md-sidebar--primary:not([hidden]) ~ .md-content > & {\n margin-left: px2rem(24px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: px2rem(24px);\n margin-left: px2rem(16px);\n }\n }\n\n // Sidebar with table of contents is visible\n .md-sidebar--secondary:not([hidden]) ~ .md-content > & {\n margin-right: px2rem(24px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: px2rem(16px);\n margin-left: px2rem(24px);\n }\n }\n }\n\n // Hack: add pseudo element for spacing, as the overflow of the content\n // container may not be hidden due to an imminent offset error on targets\n &::before {\n display: block;\n height: px2rem(8px);\n content: \"\";\n }\n\n // Adjust spacing on last child\n > :last-child {\n margin-bottom: 0;\n }\n }\n\n // Button inside of the content area - these buttons are meant for actions on\n // a document-level, i.e. linking to related source code files, printing etc.\n &__button {\n float: right;\n margin: px2rem(8px) 0;\n margin-left: px2rem(8px);\n padding: 0;\n\n // [print]: Hide buttons\n @media print {\n display: none;\n }\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n float: left;\n margin-right: px2rem(8px);\n margin-left: initial;\n\n // Flip icon vertically\n svg {\n transform: scaleX(-1);\n }\n }\n\n // Adjust default link color for icons\n .md-typeset & {\n color: var(--md-default-fg-color--lighter);\n }\n\n // Align with body copy located next to icon\n svg {\n display: inline;\n vertical-align: top;\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Dialog\n.md-dialog {\n @include z-depth(2);\n\n position: fixed;\n right: px2rem(16px);\n bottom: px2rem(16px);\n left: initial;\n z-index: 2;\n min-width: px2rem(222px);\n padding: px2rem(8px) px2rem(12px);\n background-color: var(--md-default-fg-color);\n border-radius: px2rem(2px);\n transform: translateY(100%);\n opacity: 0;\n transition:\n transform 0ms 400ms,\n opacity 400ms;\n pointer-events: none;\n\n // [print]: Hide dialog\n @media print {\n display: none;\n }\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: initial;\n left: px2rem(16px);\n }\n\n // Dialog in open state\n &[data-md-state=\"open\"] {\n transform: translateY(0);\n opacity: 1;\n transition:\n transform 400ms cubic-bezier(0.075, 0.85, 0.175, 1),\n opacity 400ms;\n pointer-events: initial;\n }\n\n // Dialog wrapper\n &__inner {\n color: var(--md-default-bg-color);\n font-size: px2rem(14px);\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n // Form button\n .md-button {\n display: inline-block;\n padding: px2em(10px) px2em(32px);\n color: var(--md-primary-fg-color);\n font-weight: 700;\n border: px2rem(2px) solid currentColor;\n border-radius: px2rem(2px);\n transition:\n color 125ms,\n background-color 125ms,\n border-color 125ms;\n\n // Primary button\n &--primary {\n color: var(--md-primary-bg-color);\n background-color: var(--md-primary-fg-color);\n border-color: var(--md-primary-fg-color);\n }\n\n // Button on focus/hover\n &:focus,\n &:hover {\n color: var(--md-accent-bg-color);\n background-color: var(--md-accent-fg-color);\n border-color: var(--md-accent-fg-color);\n }\n }\n\n // Form input\n .md-input {\n height: px2rem(36px);\n padding: 0 px2rem(12px);\n font-size: px2rem(16px);\n border-radius: px2rem(2px);\n box-shadow:\n 0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),\n 0 px2rem(0.5px) px2rem(1px) hsla(0, 0%, 0%, 0.1);\n transition: box-shadow 250ms;\n\n // Input on focus/hover\n &:focus,\n &:hover {\n box-shadow:\n 0 px2rem(8px) px2rem(20px) hsla(0, 0%, 0%, 0.15),\n 0 px2rem(0.5px) px2rem(1px) hsla(0, 0%, 0%, 0.15);\n }\n\n // Stretch to full width\n &--stretch {\n width: 100%;\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Header - by default, the header will be sticky and stay always on top of the\n// viewport. If this behavior is not desired, just set `position: static`.\n.md-header {\n position: sticky;\n top: 0;\n right: 0;\n left: 0;\n z-index: 2;\n color: var(--md-primary-bg-color);\n background-color: var(--md-primary-fg-color);\n // Hack: reduce jitter by adding a transparent box shadow of the same size\n // so the size of the layer doesn't change during animation\n box-shadow:\n 0 0 px2rem(4px) rgba(0, 0, 0, 0),\n 0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0);\n\n // [print]: Hide header\n @media print {\n display: none;\n }\n\n // Header in shadow state, i.e. shadow is visible\n &[data-md-state=\"shadow\"] {\n box-shadow:\n 0 0 px2rem(4px) rgba(0, 0, 0, 0.1),\n 0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0.2);\n transition:\n transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1),\n box-shadow 250ms;\n }\n\n // Header in hidden state, i.e. moved out of sight\n &[data-md-state=\"hidden\"] {\n transform: translateY(-100%);\n transition:\n transform 250ms cubic-bezier(0.8, 0, 0.6, 1),\n box-shadow 250ms;\n }\n\n // Link or button on focus\n .focus-visible {\n outline-color: currentColor;\n }\n\n // Header wrapper\n &__inner {\n display: flex;\n align-items: center;\n padding: 0 px2rem(4px);\n }\n\n // Header button\n &__button {\n position: relative;\n z-index: 1;\n margin: px2rem(4px);\n padding: px2rem(8px);\n color: currentColor;\n vertical-align: middle;\n cursor: pointer;\n transition: opacity 250ms;\n\n // Button on hover\n &:hover {\n opacity: 0.7;\n }\n\n // Header button is visible\n &:not([hidden]) {\n display: inline-block;\n }\n\n // Hide outline for pointer devices\n &:not(.focus-visible) {\n outline: none;\n -webkit-tap-highlight-color: transparent;\n }\n\n // Button with logo, pointing to `config.site_url`\n &.md-logo {\n margin: px2rem(4px);\n padding: px2rem(8px);\n\n // [tablet -]: Hide button\n @include break-to-device(tablet) {\n display: none;\n }\n\n // Image or icon\n img,\n svg {\n display: block;\n width: px2rem(24px);\n height: px2rem(24px);\n fill: currentColor;\n }\n }\n\n // Button for search\n &[for=\"__search\"] {\n\n // [tablet landscape +]: Hide button\n @include break-from-device(tablet landscape) {\n display: none;\n }\n\n // [no-js]: Hide button\n .no-js & {\n display: none;\n }\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n\n // Flip icon vertically\n svg {\n transform: scaleX(-1);\n }\n }\n }\n\n // Button for drawer\n &[for=\"__drawer\"] {\n\n // [screen +]: Hide button\n @include break-from-device(screen) {\n display: none;\n }\n }\n }\n\n // Header topic\n &__topic {\n position: absolute;\n display: flex;\n max-width: 100%;\n transition:\n transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),\n opacity 150ms;\n\n // Second header topic - title of the current page\n & + & {\n z-index: -1;\n transform: translateX(px2rem(25px));\n opacity: 0;\n transition:\n transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1),\n opacity 150ms;\n pointer-events: none;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n transform: translateX(px2rem(-25px));\n }\n }\n }\n\n // Header title\n &__title {\n flex-grow: 1;\n height: px2rem(48px);\n margin-right: px2rem(8px);\n margin-left: px2rem(20px);\n font-size: px2rem(18px);\n line-height: px2rem(48px);\n\n // Header title in active state, i.e. page title is visible\n &[data-md-state=\"active\"] .md-header__topic {\n z-index: -1;\n transform: translateX(px2rem(-25px));\n opacity: 0;\n transition:\n transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1),\n opacity 150ms;\n pointer-events: none;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n transform: translateX(px2rem(25px));\n }\n\n // Second header topic - title of the current page\n + .md-header__topic {\n z-index: 0;\n transform: translateX(0);\n opacity: 1;\n transition:\n transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),\n opacity 150ms;\n pointer-events: initial;\n }\n }\n\n // Add ellipsis in case of overflowing text\n > .md-header__ellipsis {\n position: relative;\n width: 100%;\n height: 100%;\n }\n }\n\n // Header option\n &__option {\n display: flex;\n flex-shrink: 0;\n max-width: 100%;\n white-space: nowrap;\n transition:\n max-width 0ms 250ms,\n opacity 250ms 250ms;\n\n // Hide toggle when search is active\n [data-md-toggle=\"search\"]:checked ~ .md-header & {\n max-width: 0;\n opacity: 0;\n transition:\n max-width 0ms,\n opacity 0ms;\n }\n }\n\n // Repository information container\n &__source {\n display: none;\n\n // [tablet landscape +]: Show repository information\n @include break-from-device(tablet landscape) {\n display: block;\n width: px2rem(234px);\n max-width: px2rem(234px);\n margin-left: px2rem(20px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: px2rem(20px);\n margin-left: initial;\n }\n }\n\n // [screen +]: Adjust spacing of search bar\n @include break-from-device(screen) {\n margin-left: px2rem(28px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: px2rem(28px);\n }\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Footer\n.md-footer {\n color: var(--md-footer-fg-color);\n background-color: var(--md-footer-bg-color);\n\n // [print]: Hide footer\n @media print {\n display: none;\n }\n\n // Footer wrapper\n &__inner {\n padding: px2rem(4px);\n overflow: auto;\n }\n\n // Footer link to previous and next page\n &__link {\n display: flex;\n padding-top: px2rem(28px);\n padding-bottom: px2rem(8px);\n transition: opacity 250ms;\n\n // [tablet +]: Adjust width to 50/50\n @include break-from-device(tablet) {\n width: 50%;\n }\n\n // Footer link on focus/hover\n &:focus,\n &:hover {\n opacity: 0.7;\n }\n\n // Footer link to previous page\n &--prev {\n float: left;\n\n // [mobile -]: Adjust width to 25/75 and hide title\n @include break-to-device(mobile) {\n width: 25%;\n\n // Hide footer title\n .md-footer__title {\n display: none;\n }\n }\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n float: right;\n\n // Flip icon vertically\n svg {\n transform: scaleX(-1);\n }\n }\n }\n\n // Footer link to next page\n &--next {\n float: right;\n text-align: right;\n\n // [mobile -]: Adjust width to 25/75\n @include break-to-device(mobile) {\n width: 75%;\n }\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n float: left;\n text-align: left;\n\n // Flip icon vertically\n svg {\n transform: scaleX(-1);\n }\n }\n }\n }\n\n // Footer title\n &__title {\n position: relative;\n flex-grow: 1;\n max-width: calc(100% - #{px2rem(48px)});\n padding: 0 px2rem(20px);\n font-size: px2rem(18px);\n line-height: px2rem(48px);\n }\n\n // Footer link button\n &__button {\n margin: px2rem(4px);\n padding: px2rem(8px);\n }\n\n // Footer link direction (i.e. prev and next)\n &__direction {\n position: absolute;\n right: 0;\n left: 0;\n margin-top: px2rem(-20px);\n padding: 0 px2rem(20px);\n font-size: px2rem(12.8px);\n opacity: 0.7;\n }\n}\n\n// Footer metadata\n.md-footer-meta {\n background-color: var(--md-footer-bg-color--dark);\n\n // Footer metadata wrapper\n &__inner {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n padding: px2rem(4px);\n }\n\n // Lighten color for non-hovered text links\n html &.md-typeset a {\n color: var(--md-footer-fg-color--light);\n\n // Text link on focus/hover\n &:focus,\n &:hover {\n color: var(--md-footer-fg-color);\n }\n }\n}\n\n// Footer copyright and theme information\n.md-footer-copyright {\n width: 100%;\n margin: auto px2rem(12px);\n padding: px2rem(8px) 0;\n color: var(--md-footer-fg-color--lighter);\n font-size: px2rem(12.8px);\n\n // [tablet portrait +]: Show copyright and social links in one line\n @include break-from-device(tablet portrait) {\n width: auto;\n }\n\n // Footer copyright highlight - this is the upper part of the copyright and\n // theme information, which will include a darker color than the theme link\n &__highlight {\n color: var(--md-footer-fg-color--light);\n }\n}\n\n// Footer social links\n.md-footer-social {\n margin: 0 px2rem(8px);\n padding: px2rem(4px) 0 px2rem(12px);\n\n // [tablet portrait +]: Show copyright and social links in one line\n @include break-from-device(tablet portrait) {\n padding: px2rem(12px) 0;\n }\n\n // Footer social link\n &__link {\n display: inline-block;\n width: px2rem(32px);\n height: px2rem(32px);\n text-align: center;\n\n // Adjust line-height to match height for correct alignment\n &::before {\n line-height: 1.9;\n }\n\n // Fill icon with current color\n svg {\n max-height: px2rem(16px);\n vertical-align: -25%;\n fill: currentColor;\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n --md-nav-icon--prev: svg-load(\"material/arrow-left.svg\");\n --md-nav-icon--next: svg-load(\"material/chevron-right.svg\");\n --md-toc-icon: svg-load(\"material/table-of-contents.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Navigation\n.md-nav {\n font-size: px2rem(14px);\n line-height: 1.3;\n\n // Navigation title\n &__title {\n display: block;\n padding: 0 px2rem(12px);\n overflow: hidden;\n font-weight: 700;\n text-overflow: ellipsis;\n\n // Navigaton button\n .md-nav__button {\n display: none;\n\n // Stretch images based on height, as it's the smaller dimension\n img {\n width: auto;\n height: 100%;\n }\n\n // Button with logo, pointing to `config.site_url`\n &.md-logo {\n\n // Image or icon\n img,\n svg {\n display: block;\n width: px2rem(48px);\n height: px2rem(48px);\n fill: currentColor;\n }\n }\n }\n }\n\n // Navigation list\n &__list {\n margin: 0;\n padding: 0;\n list-style: none;\n }\n\n // Navigation item\n &__item {\n padding: 0 px2rem(12px);\n\n // Navigation item on level 2\n & & {\n padding-right: 0;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding-right: px2rem(12px);\n padding-left: 0;\n }\n }\n }\n\n // Navigation link\n &__link {\n display: block;\n margin-top: 0.625em;\n overflow: hidden;\n text-overflow: ellipsis;\n cursor: pointer;\n transition: color 125ms;\n scroll-snap-align: start;\n\n // Link in blurred state\n &[data-md-state=\"blur\"] {\n color: var(--md-default-fg-color--light);\n }\n\n // Active link\n .md-nav__item &--active {\n color: var(--md-typeset-a-color);\n }\n\n // Navigation link in nested list\n .md-nav__item--nested > & {\n color: inherit;\n }\n\n // Navigation link on focus/hover\n &:focus,\n &:hover {\n color: var(--md-accent-fg-color);\n }\n\n // Navigation link to table of contents\n .md-nav--primary &[for=\"__toc\"] {\n display: none;\n\n // Table of contents icon\n .md-icon::after {\n display: block;\n width: 100%;\n height: 100%;\n mask-image: var(--md-toc-icon);\n background-color: currentColor;\n }\n\n // Hide table of contents\n ~ .md-nav {\n display: none;\n }\n }\n }\n\n // Repository information container\n &__source {\n display: none;\n }\n\n // [tablet -]: Layered navigation\n @include break-to-device(tablet) {\n\n // Primary and nested navigation\n &--primary,\n &--primary & {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1;\n display: flex;\n flex-direction: column;\n height: 100%;\n background-color: var(--md-default-bg-color);\n }\n\n // Primary navigation\n &--primary {\n\n // Navigation title and item\n .md-nav__title,\n .md-nav__item {\n font-size: px2rem(16px);\n line-height: 1.5;\n }\n\n // Navigation title\n .md-nav__title {\n position: relative;\n height: px2rem(112px);\n padding: px2rem(60px) px2rem(16px) px2rem(4px);\n color: var(--md-default-fg-color--light);\n font-weight: 400;\n line-height: px2rem(48px);\n white-space: nowrap;\n background-color: var(--md-default-fg-color--lightest);\n cursor: pointer;\n\n // Navigation icon\n .md-nav__icon {\n position: absolute;\n top: px2rem(8px);\n left: px2rem(8px);\n display: block;\n width: px2rem(24px);\n height: px2rem(24px);\n margin: px2rem(4px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: px2rem(8px);\n left: initial;\n }\n\n // Navigation icon in link to previous level\n &::after {\n display: block;\n width: 100%;\n height: 100%;\n background-color: currentColor;\n mask-image: var(--md-nav-icon--prev);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n }\n }\n\n // Navigation list\n ~ .md-nav__list {\n overflow-y: auto;\n background-color: var(--md-default-bg-color);\n box-shadow:\n 0 px2rem(1px) 0 var(--md-default-fg-color--lightest) inset;\n scroll-snap-type: y mandatory;\n touch-action: pan-y;\n\n // Omit border on first child\n > :first-child {\n border-top: 0;\n }\n }\n\n // Top-level navigation title\n &[for=\"__drawer\"] {\n color: var(--md-primary-bg-color);\n background-color: var(--md-primary-fg-color);\n }\n\n // Button with logo, pointing to `config.site_url`\n .md-logo {\n position: absolute;\n top: px2rem(4px);\n left: px2rem(4px);\n display: block;\n margin: px2rem(4px);\n padding: px2rem(8px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: px2rem(4px);\n left: initial;\n }\n }\n }\n\n // Navigation list\n .md-nav__list {\n flex: 1;\n }\n\n // Navigation item\n .md-nav__item {\n padding: 0;\n border-top: px2rem(1px) solid var(--md-default-fg-color--lightest);\n\n // Navigation link in nested navigation\n &--nested > .md-nav__link {\n padding-right: px2rem(48px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding-right: px2rem(16px);\n padding-left: px2rem(48px);\n }\n }\n\n // Navigation link in active navigation\n &--active > .md-nav__link {\n color: var(--md-typeset-a-color);\n\n // Navigation link on focus/hover\n &:focus,\n &:hover {\n color: var(--md-accent-fg-color);\n }\n }\n }\n\n // Navigation link\n .md-nav__link {\n position: relative;\n margin-top: 0;\n padding: px2rem(12px) px2rem(16px);\n\n // Navigation icon\n .md-nav__icon {\n position: absolute;\n top: 50%;\n right: px2rem(12px);\n width: px2rem(24px);\n height: px2rem(24px);\n margin-top: px2rem(-12px);\n color: inherit;\n font-size: px2rem(24px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: initial;\n left: px2rem(12px);\n }\n\n // Navigation icon in link to next level\n &::after {\n display: block;\n width: 100%;\n height: 100%;\n background-color: currentColor;\n mask-image: var(--md-nav-icon--next);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n }\n }\n }\n\n // Flip icon vertically\n .md-nav__icon {\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] &::after {\n transform: scale(-1);\n }\n }\n\n // Table of contents contained in primary navigation\n .md-nav--secondary {\n\n // Navigation link - omit unnecessary layering\n .md-nav__link {\n position: static;\n }\n\n // Navigation on level 2-6\n .md-nav {\n position: static;\n background-color: transparent;\n\n // Navigation link on level 3\n .md-nav__link {\n padding-left: px2rem(28px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding-right: px2rem(28px);\n padding-left: initial;\n }\n }\n\n // Navigation link on level 4\n .md-nav .md-nav__link {\n padding-left: px2rem(40px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding-right: px2rem(40px);\n padding-left: initial;\n }\n }\n\n // Navigation link on level 5\n .md-nav .md-nav .md-nav__link {\n padding-left: px2rem(52px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding-right: px2rem(52px);\n padding-left: initial;\n }\n }\n\n // Navigation link on level 6\n .md-nav .md-nav .md-nav .md-nav__link {\n padding-left: px2rem(64px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding-right: px2rem(64px);\n padding-left: initial;\n }\n }\n }\n }\n }\n\n // Table of contents\n &--secondary {\n background-color: transparent;\n }\n\n // Toggle for nested navigation\n &__toggle ~ & {\n display: flex;\n transform: translateX(100%);\n opacity: 0;\n transition:\n transform 250ms cubic-bezier(0.8, 0, 0.6, 1),\n opacity 125ms 50ms;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n transform: translateX(-100%);\n }\n }\n\n // Show nested navigation when toggle is active\n &__toggle:checked ~ & {\n transform: translateX(0);\n opacity: 1;\n transition:\n transform 250ms cubic-bezier(0.4, 0, 0.2, 1),\n opacity 125ms 125ms;\n\n // Navigation list\n > .md-nav__list {\n // Hack: promote to own layer to reduce jitter\n backface-visibility: hidden;\n }\n }\n }\n\n // [tablet portrait -]: Layered navigation with table of contents\n @include break-to-device(tablet portrait) {\n\n // Show link to table of contents\n &--primary &__link[for=\"__toc\"] {\n display: block;\n padding-right: px2rem(48px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding-right: px2rem(16px);\n padding-left: px2rem(48px);\n }\n\n // Show table of contents icon\n .md-icon::after {\n content: \"\";\n }\n\n // Hide navigation link to current page\n + .md-nav__link {\n display: none;\n }\n\n // Show table of contents\n ~ .md-nav {\n display: flex;\n }\n }\n\n // Repository information container\n &__source {\n display: block;\n padding: 0 px2rem(4px);\n color: var(--md-primary-bg-color);\n background-color: var(--md-primary-fg-color--dark);\n }\n }\n\n // [tablet landscape]: Layered navigation with table of contents\n @include break-at-device(tablet landscape) {\n\n // Show link to integrated table of contents\n &--integrated &__link[for=\"__toc\"] {\n display: block;\n padding-right: px2rem(48px);\n scroll-snap-align: initial;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding-right: px2rem(16px);\n padding-left: px2rem(48px);\n }\n\n // Show table of contents icon\n .md-icon::after {\n content: \"\";\n }\n\n // Hide navigation link to current page\n + .md-nav__link {\n display: none;\n }\n\n // Show table of contents\n ~ .md-nav {\n display: flex;\n }\n }\n }\n\n // [tablet landscape +]: Tree-like table of contents\n @include break-from-device(tablet landscape) {\n\n // Navigation title\n &--secondary &__title {\n\n // Adjust snapping behavior\n &[for=\"__toc\"] {\n scroll-snap-align: start;\n }\n\n // Hide navigation icon\n .md-nav__icon {\n display: none;\n }\n }\n }\n\n // [screen +]: Tree-like navigation\n @include break-from-device(screen) {\n transition: max-height 250ms cubic-bezier(0.86, 0, 0.07, 1);\n\n // Navigation title\n &--primary &__title {\n\n // Adjust snapping behavior\n &[for=\"__drawer\"] {\n scroll-snap-align: start;\n }\n\n // Hide navigation icon\n .md-nav__icon {\n display: none;\n }\n }\n\n // Hide toggle for nested navigation\n &__toggle ~ & {\n display: none;\n }\n\n // Show nested navigation when toggle is active or indeterminate\n &__toggle:checked ~ &,\n &__toggle:indeterminate ~ & {\n display: block;\n }\n\n // Hide navigation title in nested navigation\n &__item--nested > & > &__title {\n display: none;\n }\n\n // Navigation section\n &__item--section {\n display: block;\n margin: 1.25em 0;\n\n // Adjust spacing on last child\n &:last-child {\n margin-bottom: 0;\n }\n\n // Hide navigation link, as sections are always expanded\n > .md-nav__link {\n display: none;\n }\n\n // Navigation\n > .md-nav {\n display: block;\n\n // Navigation title\n > .md-nav__title {\n display: block;\n padding: 0;\n pointer-events: none;\n scroll-snap-align: start;\n }\n\n // Adjust spacing on next level item\n > .md-nav__list > .md-nav__item {\n padding: 0;\n }\n }\n }\n\n // Navigation icon\n &__icon {\n float: right;\n width: px2rem(18px);\n height: px2rem(18px);\n transition: transform 250ms;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n float: left;\n transform: rotate(180deg);\n }\n\n // Navigation icon content\n &::after {\n display: inline-block;\n width: 100%;\n height: 100%;\n vertical-align: px2rem(-2px);\n background-color: currentColor;\n mask-image: var(--md-nav-icon--next);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n }\n\n // Navigation icon - rotate icon when toggle is active or indeterminate\n .md-nav__item--nested .md-nav__toggle:checked ~ .md-nav__link &,\n .md-nav__item--nested .md-nav__toggle:indeterminate ~ .md-nav__link & {\n transform: rotate(90deg);\n }\n }\n\n // Modifier for when navigation tabs are rendered\n &--lifted {\n\n // Hide nested items on level 1 and site title\n > .md-nav__list > .md-nav__item--nested,\n > .md-nav__title {\n display: none;\n }\n\n // Hide level 1 items\n > .md-nav__list > .md-nav__item {\n display: none;\n\n // Active parent navigation item\n &--active {\n display: block;\n padding: 0;\n\n // Hide nested links\n > .md-nav__link {\n display: none;\n }\n\n // Show title and adjust spacing\n > .md-nav > .md-nav__title {\n display: block;\n padding: 0 px2rem(12px);\n pointer-events: none;\n scroll-snap-align: start;\n }\n }\n\n // Adjust spacing for navigation item on level 2\n > .md-nav__item {\n padding-right: px2rem(12px);\n }\n }\n\n // Hack: Always show active navigation tab on breakpoint screen, despite\n // of checkbox being checked or not. Fixes #1655.\n .md-nav[data-md-level=\"1\"] {\n display: block;\n }\n }\n\n // Modifier for when table of contents is rendered in primary navigation\n &--integrated &__link[for=\"__toc\"] ~ .md-nav {\n display: block;\n margin-bottom: 1.25em;\n border-left: px2rem(1px) solid var(--md-primary-fg-color);\n\n // Hide navigation title\n > .md-nav__title {\n display: none;\n }\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n --md-search-result-icon: svg-load(\"material/file-search-outline.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Search\n.md-search {\n position: relative;\n\n // [tablet landscape +]: Header-embedded search\n @include break-from-device(tablet landscape) {\n padding: px2rem(4px) 0;\n }\n\n // [no-js]: Hide search\n .no-js & {\n display: none;\n }\n\n // Search overlay\n &__overlay {\n z-index: 1;\n opacity: 0;\n\n // [tablet portrait -]: Search modal\n @include break-to-device(tablet portrait) {\n position: absolute;\n top: px2rem(4px);\n left: px2rem(-44px);\n width: px2rem(40px);\n height: px2rem(40px);\n overflow: hidden;\n background-color: var(--md-default-bg-color);\n border-radius: px2rem(20px);\n transform-origin: center;\n transition:\n transform 300ms 100ms,\n opacity 200ms 200ms;\n pointer-events: none;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: px2rem(-44px);\n left: initial;\n }\n\n // Show overlay when search is active\n [data-md-toggle=\"search\"]:checked ~ .md-header & {\n opacity: 1;\n transition:\n transform 400ms,\n opacity 100ms;\n }\n }\n\n // [tablet landscape +]: Header-embedded search\n @include break-from-device(tablet landscape) {\n position: fixed;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n background-color: hsla(0, 0%, 0%, 0.54);\n cursor: pointer;\n transition:\n width 0ms 250ms,\n height 0ms 250ms,\n opacity 250ms;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: 0;\n left: initial;\n }\n\n // Show overlay when search is active\n [data-md-toggle=\"search\"]:checked ~ .md-header & {\n width: 100%;\n // Hack: when the header is translated upon scrolling, a new layer is\n // induced, which means that the height will now refer to the height of\n // the header, albeit positioning is fixed. This should be mitigated\n // in all cases when setting the height to 2x the viewport.\n height: 200vh;\n opacity: 1;\n transition:\n width 0ms,\n height 0ms,\n opacity 250ms;\n }\n }\n\n // Adjust appearance when search is active\n [data-md-toggle=\"search\"]:checked ~ .md-header & {\n\n // [mobile portrait -]: Scale up 45 times\n @include break-to-device(mobile portrait) {\n transform: scale(45);\n }\n\n // [mobile landscape]: Scale up 60 times\n @include break-at-device(mobile landscape) {\n transform: scale(60);\n }\n\n // [tablet portrait]: Scale up 75 times\n @include break-at-device(tablet portrait) {\n transform: scale(75);\n }\n }\n }\n\n // Search wrapper\n &__inner {\n // Hack: promote to own layer to reduce jitter\n backface-visibility: hidden;\n\n // [tablet portrait -]: Search modal\n @include break-to-device(tablet portrait) {\n position: fixed;\n top: 0;\n left: 100%;\n z-index: 2;\n width: 100%;\n height: 100%;\n transform: translateX(5%);\n opacity: 0;\n transition:\n right 0ms 300ms,\n left 0ms 300ms,\n transform 150ms 150ms cubic-bezier(0.4, 0, 0.2, 1),\n opacity 150ms 150ms;\n\n // Adjust appearance when search is active\n [data-md-toggle=\"search\"]:checked ~ .md-header & {\n left: 0;\n transform: translateX(0);\n opacity: 1;\n transition:\n right 0ms 0ms,\n left 0ms 0ms,\n transform 150ms 150ms cubic-bezier(0.1, 0.7, 0.1, 1),\n opacity 150ms 150ms;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: 0;\n left: initial;\n }\n }\n\n // Adjust for right-to-left languages\n html [dir=\"rtl\"] & {\n right: 100%;\n left: initial;\n transform: translateX(-5%);\n }\n }\n\n // [tablet landscape +]: Header-embedded search\n @include break-from-device(tablet landscape) {\n position: relative;\n float: right;\n width: px2rem(234px);\n padding: px2rem(2px) 0;\n transition: width 250ms cubic-bezier(0.1, 0.7, 0.1, 1);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n float: left;\n }\n }\n\n // Adjust appearance when search is active\n [data-md-toggle=\"search\"]:checked ~ .md-header & {\n\n // [tablet landscape]: Omit overlaying header title\n @include break-at-device(tablet landscape) {\n width: px2rem(468px);\n }\n\n // [screen +]: Match width of content area\n @include break-from-device(screen) {\n width: px2rem(688px);\n }\n }\n }\n\n // Search form\n &__form {\n position: relative;\n\n // [tablet landscape +]: Header-embedded search\n @include break-from-device(tablet landscape) {\n border-radius: px2rem(2px);\n }\n }\n\n // Search input\n &__input {\n position: relative;\n z-index: 2;\n padding: 0 px2rem(44px) 0 px2rem(72px);\n text-overflow: ellipsis;\n background-color: var(--md-default-bg-color);\n box-shadow: 0 0 px2rem(12px) transparent;\n transition:\n color 250ms,\n background-color 250ms,\n box-shadow 250ms;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding: 0 px2rem(72px) 0 px2rem(44px);\n }\n\n // Search placeholder\n &::placeholder {\n transition: color 250ms;\n }\n\n // Search icon and placeholder\n ~ .md-search__icon,\n &::placeholder {\n color: var(--md-default-fg-color--light);\n }\n\n // Remove the \"x\" rendered by Internet Explorer\n &::-ms-clear {\n display: none;\n }\n\n // Adjust appearance when search is active\n [data-md-toggle=\"search\"]:checked ~ .md-header & {\n box-shadow: 0 0 px2rem(12px) hsla(0, 0%, 0%, 0.07);\n }\n\n // [tablet portrait -]: Search modal\n @include break-to-device(tablet portrait) {\n width: 100%;\n height: px2rem(48px);\n font-size: px2rem(18px);\n }\n\n // [tablet landscape +]: Header-embedded search\n @include break-from-device(tablet landscape) {\n width: 100%;\n height: px2rem(36px);\n padding-left: px2rem(44px);\n color: inherit;\n font-size: px2rem(16px);\n background-color: hsla(0, 0%, 0%, 0.26);\n border-radius: px2rem(2px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding-right: px2rem(44px);\n }\n\n // Search icon\n + .md-search__icon {\n color: var(--md-primary-bg-color);\n }\n\n // Search placeholder\n &::placeholder {\n color: var(--md-primary-bg-color--light);\n }\n\n // Search input on hover\n &:hover {\n background-color: hsla(0, 0%, 100%, 0.12);\n }\n\n // Adjust appearance when search is active\n [data-md-toggle=\"search\"]:checked ~ .md-header & {\n color: var(--md-default-fg-color);\n text-overflow: clip;\n background-color: var(--md-default-bg-color);\n border-radius: px2rem(2px) px2rem(2px) 0 0;\n\n // Search icon and placeholder\n + .md-search__icon,\n &::placeholder {\n color: var(--md-default-fg-color--light);\n }\n }\n }\n }\n\n // Search icon\n &__icon {\n position: absolute;\n z-index: 2;\n width: px2rem(24px);\n height: px2rem(24px);\n cursor: pointer;\n transition:\n color 250ms,\n opacity 250ms;\n\n // Search icon on hover\n &:hover {\n opacity: 0.7;\n }\n\n // Search focus button\n &[for=\"__search\"] {\n top: px2rem(6px);\n left: px2rem(10px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: px2rem(10px);\n left: initial;\n\n // Flip icon vertically\n svg {\n transform: scaleX(-1);\n }\n }\n\n // [tablet portrait -]: Search modal\n @include break-to-device(tablet portrait) {\n top: px2rem(12px);\n left: px2rem(16px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: px2rem(16px);\n left: initial;\n }\n\n // Hide the magnifying glass\n svg:first-child {\n display: none;\n }\n }\n\n // [tablet landscape +]: Header-embedded search\n @include break-from-device(tablet landscape) {\n pointer-events: none;\n\n // Hide the back arrow\n svg:last-child {\n display: none;\n }\n }\n }\n\n // Search reset button\n &[type=\"reset\"] {\n top: px2rem(6px);\n right: px2rem(10px);\n transform: scale(0.75);\n opacity: 0;\n transition:\n transform 150ms cubic-bezier(0.1, 0.7, 0.1, 1),\n opacity 150ms;\n pointer-events: none;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: initial;\n left: px2rem(10px);\n }\n\n // [tablet portrait -]: Search modal\n @include break-to-device(tablet portrait) {\n top: px2rem(12px);\n right: px2rem(16px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: initial;\n left: px2rem(16px);\n }\n }\n\n // Show reset button when search is active and input non-empty\n [data-md-toggle=\"search\"]:checked ~ .md-header\n .md-search__input:valid ~ & {\n transform: scale(1);\n opacity: 1;\n pointer-events: initial;\n\n // Search focus icon\n &:hover {\n opacity: 0.7;\n }\n }\n }\n }\n\n // Search output\n &__output {\n position: absolute;\n z-index: 1;\n width: 100%;\n overflow: hidden;\n border-radius: 0 0 px2rem(2px) px2rem(2px);\n\n // [tablet portrait -]: Search modal\n @include break-to-device(tablet portrait) {\n top: px2rem(48px);\n bottom: 0;\n }\n\n // [tablet landscape +]: Header-embedded search\n @include break-from-device(tablet landscape) {\n top: px2rem(38px);\n opacity: 0;\n transition: opacity 400ms;\n\n // Show output when search is active\n [data-md-toggle=\"search\"]:checked ~ .md-header & {\n @include z-depth(6);\n\n opacity: 1;\n }\n }\n }\n\n // Search scroll wrapper\n &__scrollwrap {\n height: 100%;\n overflow-y: auto;\n background-color: var(--md-default-bg-color);\n // Hack: promote to own layer to reduce jitter\n backface-visibility: hidden;\n // Hack: Chrome 88+ has weird overscroll behavior. Overall, scroll snapping\n // seems to be something that is not ready for prime time on some browsers.\n // scroll-snap-type: y mandatory;\n touch-action: pan-y;\n\n // Mitigiate excessive repaints on non-retina devices\n @media (max-resolution: 1dppx) {\n transform: translateZ(0);\n }\n\n // [tablet landscape]: Set fixed width to omit unnecessary reflow\n @include break-at-device(tablet landscape) {\n width: px2rem(468px);\n }\n\n // [screen +]: Set fixed width to omit unnecessary reflow\n @include break-from-device(screen) {\n width: px2rem(688px);\n }\n\n // [tablet landscape +]: Limit height to viewport\n @include break-from-device(tablet landscape) {\n max-height: 0;\n scrollbar-width: thin;\n scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n\n // Show scroll wrapper when search is active\n [data-md-toggle=\"search\"]:checked ~ .md-header & {\n max-height: 75vh;\n }\n\n // Search scroll wrapper on hover\n &:hover {\n scrollbar-color: var(--md-accent-fg-color) transparent;\n }\n\n // Webkit scrollbar\n &::-webkit-scrollbar {\n width: px2rem(4px);\n height: px2rem(4px);\n }\n\n // Webkit scrollbar thumb\n &::-webkit-scrollbar-thumb {\n background-color: var(--md-default-fg-color--lighter);\n\n // Webkit scrollbar thumb on hover\n &:hover {\n background-color: var(--md-accent-fg-color);\n }\n }\n }\n }\n}\n\n// Search result\n.md-search-result {\n color: var(--md-default-fg-color);\n word-break: break-word;\n\n // Search result metadata\n &__meta {\n padding: 0 px2rem(16px);\n color: var(--md-default-fg-color--light);\n font-size: px2rem(12.8px);\n line-height: px2rem(36px);\n background-color: var(--md-default-fg-color--lightest);\n scroll-snap-align: start;\n\n // [tablet landscape +]: Adjust spacing\n @include break-from-device(tablet landscape) {\n padding-left: px2rem(44px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding-right: px2rem(44px);\n padding-left: initial;\n }\n }\n }\n\n // Search result list\n &__list {\n margin: 0;\n padding: 0;\n list-style: none;\n }\n\n // Search result item\n &__item {\n box-shadow: 0 px2rem(-1px) 0 var(--md-default-fg-color--lightest);\n\n // Omit border on first child\n &:first-child {\n box-shadow: none;\n }\n }\n\n // Search result link\n &__link {\n display: block;\n outline: none;\n transition: background-color 250ms;\n scroll-snap-align: start;\n\n // Search result link on focus/hover\n &:focus,\n &:hover {\n background-color: var(--md-accent-fg-color--transparent);\n }\n\n // Adjust spacing on last child of last link\n &:last-child p:last-child {\n margin-bottom: px2rem(12px);\n }\n }\n\n // Search result more link\n &__more summary {\n display: block;\n padding: px2em(12px) px2rem(16px);\n color: var(--md-typeset-a-color);\n font-size: px2rem(12.8px);\n outline: 0;\n cursor: pointer;\n transition:\n color 250ms,\n background-color 250ms;\n scroll-snap-align: start;\n\n // [tablet landscape +]: Adjust spacing\n @include break-from-device(tablet landscape) {\n padding-left: px2rem(44px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding-right: px2rem(44px);\n padding-left: px2rem(16px);\n }\n }\n\n // Search result more link on focus/hover\n &:focus,\n &:hover {\n color: var(--md-accent-fg-color);\n background-color: var(--md-accent-fg-color--transparent);\n }\n\n // Hide native details marker\n &::marker,\n &::-webkit-details-marker {\n display: none;\n }\n\n // Adjust transparency of less relevant results\n ~ * > * {\n opacity: 0.65;\n }\n }\n\n // Search result article\n &__article {\n position: relative;\n padding: 0 px2rem(16px);\n overflow: hidden;\n\n // [tablet landscape +]: Adjust spacing\n @include break-from-device(tablet landscape) {\n padding-left: px2rem(44px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding-right: px2rem(44px);\n padding-left: px2rem(16px);\n }\n }\n\n // Search result article document\n &--document {\n\n // Search result title\n .md-search-result__title {\n margin: px2rem(11px) 0;\n font-weight: 400;\n font-size: px2rem(16px);\n line-height: 1.4;\n }\n }\n }\n\n // Search result icon\n &__icon {\n position: absolute;\n left: 0;\n width: px2rem(24px);\n height: px2rem(24px);\n margin: px2rem(10px);\n color: var(--md-default-fg-color--light);\n\n // [tablet portrait -]: Hide icon\n @include break-to-device(tablet portrait) {\n display: none;\n }\n\n // Search result icon content\n &::after {\n display: inline-block;\n width: 100%;\n height: 100%;\n background-color: currentColor;\n mask-image: var(--md-search-result-icon);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n }\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: 0;\n left: initial;\n\n // Flip icon vertically\n &::after {\n transform: scaleX(-1);\n }\n }\n }\n\n // Search result title\n &__title {\n margin: 0.5em 0;\n font-weight: 700;\n font-size: px2rem(12.8px);\n line-height: 1.6;\n }\n\n // Search result teaser\n &__teaser {\n display: -webkit-box;\n max-height: px2rem(40px);\n margin: 0.5em 0;\n overflow: hidden;\n color: var(--md-default-fg-color--light);\n font-size: px2rem(12.8px);\n line-height: 1.6;\n text-overflow: ellipsis;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n\n // [mobile -]: Adjust number of lines\n @include break-to-device(mobile) {\n max-height: px2rem(60px);\n -webkit-line-clamp: 3;\n }\n\n // [tablet landscape]: Adjust number of lines\n @include break-at-device(tablet landscape) {\n max-height: px2rem(60px);\n -webkit-line-clamp: 3;\n }\n\n // Search term highlighting\n mark {\n text-decoration: underline;\n background-color: transparent;\n }\n }\n\n // Search result terms\n &__terms {\n margin: 0.5em 0;\n font-size: px2rem(12.8px);\n font-style: italic;\n }\n\n // Search term highlighting\n mark {\n color: var(--md-accent-fg-color);\n background-color: transparent;\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Selection\n.md-select {\n position: relative;\n z-index: 1;\n\n // Selection bubble\n &__inner {\n position: absolute;\n top: calc(100% - #{px2rem(4px)});\n left: 50%;\n max-height: 0;\n margin-top: px2rem(4px);\n color: var(--md-default-fg-color);\n background-color: var(--md-default-bg-color);\n border-radius: px2rem(2px);\n box-shadow:\n 0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),\n 0 0 px2rem(1px) hsla(0, 0%, 0%, 0.25);\n transform: translate3d(-50%, px2rem(6px), 0);\n opacity: 0;\n transition:\n transform 250ms 375ms,\n opacity 250ms 250ms,\n max-height 0ms 500ms;\n\n // Selection bubble on parent focus/hover\n .md-select:focus-within &,\n .md-select:hover & {\n max-height: px2rem(200px);\n transform: translate3d(-50%, 0, 0);\n opacity: 1;\n transition:\n transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1),\n opacity 250ms,\n max-height 250ms;\n }\n\n // Selection bubble handle\n &::after {\n position: absolute;\n top: 0;\n left: 50%;\n width: 0;\n height: 0;\n margin-top: px2rem(-4px);\n margin-left: px2rem(-4px);\n border: px2rem(4px) solid transparent;\n border-top: 0;\n border-bottom-color: var(--md-default-bg-color);\n content: \"\";\n }\n }\n\n // Selection list\n &__list {\n max-height: inherit;\n margin: 0;\n padding: 0;\n overflow: auto;\n font-size: px2rem(16px);\n list-style-type: none;\n border-radius: px2rem(2px);\n }\n\n // Selection item\n &__item {\n line-height: px2rem(36px);\n }\n\n // Selection link\n &__link {\n display: block;\n width: 100%;\n padding-right: px2rem(24px);\n padding-left: px2rem(12px);\n cursor: pointer;\n transition:\n background-color 250ms,\n color 250ms;\n scroll-snap-align: start;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding-right: px2rem(12px);\n padding-left: px2rem(24px);\n }\n\n // Link on focus/hover\n &:focus,\n &:hover {\n background-color: var(--md-default-fg-color--lightest);\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Sidebar\n.md-sidebar {\n position: sticky;\n top: px2rem(48px);\n flex-shrink: 0;\n align-self: flex-start;\n width: px2rem(242px);\n padding: px2rem(24px) 0;\n\n // [print]: Hide sidebar\n @media print {\n display: none;\n }\n\n // [tablet -]: Show navigation as drawer\n @include break-to-device(tablet) {\n\n // Primary sidebar with navigation\n &--primary {\n position: fixed;\n top: 0;\n left: px2rem(-242px);\n z-index: 3;\n display: block;\n width: px2rem(242px);\n height: 100%;\n background-color: var(--md-default-bg-color);\n transform: translateX(0);\n transition:\n transform 250ms cubic-bezier(0.4, 0, 0.2, 1),\n box-shadow 250ms;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: px2rem(-242px);\n left: initial;\n }\n\n // Show sidebar when drawer is active\n [data-md-toggle=\"drawer\"]:checked ~ .md-container & {\n @include z-depth(8);\n\n transform: translateX(px2rem(242px));\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n transform: translateX(px2rem(-242px));\n }\n }\n\n // Stretch scroll wrapper for primary sidebar\n .md-sidebar__scrollwrap {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n margin: 0;\n scroll-snap-type: none;\n overflow: hidden;\n }\n }\n }\n\n // [screen +]: Show navigation as sidebar\n @include break-from-device(screen) {\n height: 0;\n\n // [no-js]: Switch to native sticky behavior\n .no-js & {\n height: auto;\n }\n }\n\n // Secondary sidebar with table of contents\n &--secondary {\n display: none;\n order: 2;\n\n // [tablet landscape +]: Show table of contents as sidebar\n @include break-from-device(tablet landscape) {\n height: 0;\n\n // [no-js]: Switch to native sticky behavior\n .no-js & {\n height: auto;\n }\n\n // Sidebar is visible\n &:not([hidden]) {\n display: block;\n }\n\n // Ensure smooth scrolling on iOS\n .md-sidebar__scrollwrap {\n touch-action: pan-y;\n }\n }\n }\n\n // Sidebar scroll wrapper\n &__scrollwrap {\n margin: 0 px2rem(4px);\n overflow-y: auto;\n // Hack: promote to own layer to reduce jitter\n backface-visibility: hidden;\n // Hack: Chrome 81+ exhibits a strange bug, where it scrolls the container\n // to the bottom if `scroll-snap-type` is set on the initial render. For\n // this reason, we disable scroll snapping until this is resolved (#1667).\n // scroll-snap-type: y mandatory;\n scrollbar-width: thin;\n scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n\n // Sidebar scroll wrapper on hover\n &:hover {\n scrollbar-color: var(--md-accent-fg-color) transparent;\n }\n\n // Webkit scrollbar\n &::-webkit-scrollbar {\n width: px2rem(4px);\n height: px2rem(4px);\n }\n\n // Webkit scrollbar thumb\n &::-webkit-scrollbar-thumb {\n background-color: var(--md-default-fg-color--lighter);\n\n // Webkit scrollbar thumb on hover\n &:hover {\n background-color: var(--md-accent-fg-color);\n }\n }\n }\n}\n\n// [tablet -]: Show overlay on active drawer\n@include break-to-device(tablet) {\n\n // Sidebar overlay\n .md-overlay {\n position: fixed;\n top: 0;\n z-index: 3;\n width: 0;\n height: 0;\n background-color: hsla(0, 0%, 0%, 0.54);\n opacity: 0;\n transition:\n width 0ms 250ms,\n height 0ms 250ms,\n opacity 250ms;\n\n // Show overlay when drawer is active\n [data-md-toggle=\"drawer\"]:checked ~ & {\n width: 100%;\n height: 100%;\n opacity: 1;\n transition:\n width 0ms,\n height 0ms,\n opacity 250ms;\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Keyframes\n// ----------------------------------------------------------------------------\n\n// Show repository facts\n@keyframes md-source__facts--done {\n 0% {\n height: 0;\n }\n\n 100% {\n height: px2rem(13px);\n }\n}\n\n// Show repository fact\n@keyframes md-source__fact--done {\n 0% {\n transform: translateY(100%);\n opacity: 0;\n }\n\n 50% {\n opacity: 0;\n }\n\n 100% {\n transform: translateY(0%);\n opacity: 1;\n }\n}\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n --md-source-forks-icon: svg-load(\"octicons/repo-forked-16.svg\");\n --md-source-repositories-icon: svg-load(\"octicons/repo-16.svg\");\n --md-source-stars-icon: svg-load(\"octicons/star-16.svg\");\n --md-source-version-icon: svg-load(\"octicons/tag-16.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Repository information\n.md-source {\n display: block;\n font-size: px2rem(13px);\n line-height: 1.2;\n white-space: nowrap;\n // Hack: promote to own layer to reduce jitter\n backface-visibility: hidden;\n transition: opacity 250ms;\n\n // Repository information on hover\n &:hover {\n opacity: 0.7;\n }\n\n // Repository icon\n &__icon {\n display: inline-block;\n width: px2rem(40px);\n height: px2rem(48px);\n vertical-align: middle;\n\n // Align with margin only (as opposed to normal button alignment)\n svg {\n margin-top: px2rem(12px);\n margin-left: px2rem(12px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: px2rem(12px);\n margin-left: initial;\n }\n }\n\n // Adjust spacing if icon is present\n + .md-source__repository {\n margin-left: px2rem(-40px);\n padding-left: px2rem(40px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: px2rem(-40px);\n margin-left: initial;\n padding-right: px2rem(40px);\n padding-left: initial;\n }\n }\n }\n\n // Repository name\n &__repository {\n display: inline-block;\n max-width: calc(100% - #{px2rem(24px)});\n margin-left: px2rem(12px);\n overflow: hidden;\n text-overflow: ellipsis;\n vertical-align: middle;\n }\n\n // Repository facts\n &__facts {\n margin: px2rem(2px) 0 0;\n padding: 0;\n overflow: hidden;\n font-size: px2rem(11px);\n list-style-type: none;\n opacity: 0.75;\n\n // Show after the data was loaded\n [data-md-state=\"done\"] & {\n animation: md-source__facts--done 250ms ease-in;\n }\n }\n\n // Repository fact\n &__fact {\n display: inline-block;\n\n // Show after the data was loaded\n [data-md-state=\"done\"] & {\n animation: md-source__fact--done 400ms ease-out;\n }\n\n // Repository fact icon\n &::before {\n display: inline-block;\n width: px2rem(12px);\n height: px2rem(12px);\n margin-right: px2rem(2px);\n vertical-align: text-top;\n background-color: currentColor;\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n }\n\n // Adjust spacing for repository fact icon\n &:nth-child(1n+2)::before {\n margin-left: px2rem(8px);\n }\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: initial;\n margin-left: px2rem(2px);\n\n // Adjust spacing for repository fact icon\n &:nth-child(1n+2)::before {\n margin-right: px2rem(8px);\n margin-left: initial;\n }\n }\n\n // Repository fact: version\n &--version::before {\n mask-image: var(--md-source-version-icon);\n }\n\n // Repository fact: stars\n &--stars::before {\n mask-image: var(--md-source-stars-icon);\n }\n\n // Repository fact: forks\n &--forks::before {\n mask-image: var(--md-source-forks-icon);\n }\n\n // Repository fact: repositories\n &--repositories::before {\n mask-image: var(--md-source-repositories-icon);\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Navigation tabs\n.md-tabs {\n width: 100%;\n overflow: auto;\n color: var(--md-primary-bg-color);\n background-color: var(--md-primary-fg-color);\n\n // [print]: Hide tabs\n @media print {\n display: none;\n }\n\n // [tablet -]: Hide tabs\n @include break-to-device(tablet) {\n display: none;\n }\n\n // Tabs in hidden state, i.e. when scrolling down\n &[data-md-state=\"hidden\"] {\n pointer-events: none;\n }\n\n // Navigation tabs list\n &__list {\n margin: 0;\n margin-left: px2rem(4px);\n padding: 0;\n white-space: nowrap;\n list-style: none;\n contain: content;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: px2rem(4px);\n margin-left: initial;\n }\n }\n\n // Navigation tabs item\n &__item {\n display: inline-block;\n height: px2rem(48px);\n padding-right: px2rem(12px);\n padding-left: px2rem(12px);\n }\n\n // Navigation tabs link - could be defined as block elements and aligned via\n // line height, but this would imply more repaints when scrolling\n &__link {\n display: block;\n margin-top: px2rem(16px);\n font-size: px2rem(14px);\n // Hack: save a repaint when tabs are appearing on scrolling up\n backface-visibility: hidden;\n opacity: 0.7;\n transition:\n transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),\n opacity 250ms;\n\n // Active link and link on focus/hover\n &--active,\n &:focus,\n &:hover {\n color: inherit;\n opacity: 1;\n }\n\n // Delay transitions by a small amount\n @for $i from 2 through 16 {\n .md-tabs__item:nth-child(#{$i}) & {\n transition-delay: 20ms * ($i - 1);\n }\n }\n\n // Hide tabs upon scrolling - disable transition to minimizes repaints\n // while scrolling down, while scrolling up seems to be okay\n .md-tabs[data-md-state=\"hidden\"] & {\n transform: translateY(50%);\n opacity: 0;\n transition:\n transform 0ms 100ms,\n opacity 100ms;\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Back-to-top button\n.md-top {\n position: sticky;\n bottom: px2rem(8px);\n z-index: 1;\n float: right;\n margin: px2rem(-56px) px2rem(8px) px2rem(8px);\n padding: px2rem(8px);\n color: var(--md-primary-bg-color);\n background: var(--md-primary-fg-color);\n border-radius: 100%;\n outline: none;\n box-shadow:\n 0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),\n 0 px2rem(0.5px) px2rem(1px) hsla(0, 0%, 0%, 0.1);\n transform: translateY(0);\n transition:\n opacity 125ms,\n transform 125ms cubic-bezier(0.4, 0, 0.2, 1),\n background-color 125ms;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n float: left;\n }\n\n // Back-to-top button in hidden state\n &[data-md-state=\"hidden\"] {\n transform: translateY(px2rem(-4px));\n opacity: 0;\n }\n\n // Back-to-top button on focus/hover\n &:focus,\n &:hover {\n background: var(--md-accent-fg-color);\n transform: scale(1.1);\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n --md-version-icon: svg-load(\"fontawesome/solid/caret-down.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Version selection\n.md-version {\n flex-shrink: 0;\n height: px2rem(48px);\n font-size: px2rem(16px);\n\n // Current selection\n &__current {\n position: relative;\n // Hack: in general, we would use `vertical-align` to align the version at\n // the bottom with the title, but since the list uses absolute positioning,\n // this won't work consistently. Furthermore, we would need to use inline\n // positioning to align the links, which looks jagged.\n top: px2rem(1px);\n margin-right: px2rem(8px);\n margin-left: px2rem(28px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: px2rem(28px);\n margin-left: px2rem(8px);\n }\n\n // Version selection icon\n &::after {\n display: inline-block;\n width: px2rem(8px);\n height: px2rem(12px);\n margin-left: px2rem(8px);\n background-color: currentColor;\n mask-image: var(--md-version-icon);\n mask-repeat: no-repeat;\n content: \"\";\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: px2rem(8px);\n margin-left: initial;\n }\n }\n }\n\n // Version selection list\n &__list {\n position: absolute;\n top: px2rem(3px);\n z-index: 1;\n max-height: px2rem(36px);\n margin: px2rem(4px) px2rem(16px);\n padding: 0;\n overflow: auto;\n color: var(--md-default-fg-color);\n list-style-type: none;\n background-color: var(--md-default-bg-color);\n border-radius: px2rem(2px);\n box-shadow:\n 0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),\n 0 0 px2rem(1px) hsla(0, 0%, 0%, 0.25);\n opacity: 0;\n transition:\n max-height 0ms 500ms,\n opacity 250ms 250ms;\n scroll-snap-type: y mandatory;\n\n // List on focus/hover\n &:focus-within,\n &:hover {\n max-height: px2rem(200px);\n opacity: 1;\n transition:\n max-height 250ms,\n opacity 250ms;\n }\n }\n\n // Version selection item\n &__item {\n line-height: px2rem(36px);\n }\n\n // Version selection link\n &__link {\n display: block;\n width: 100%;\n padding-right: px2rem(24px);\n padding-left: px2rem(12px);\n white-space: nowrap;\n cursor: pointer;\n transition:\n color 250ms,\n background-color 250ms;\n scroll-snap-align: start;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding-right: px2rem(12px);\n padding-left: px2rem(24px);\n }\n\n // Link on focus/hover\n &:focus,\n &:hover {\n background-color: var(--md-default-fg-color--lightest);\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Variables\n// ----------------------------------------------------------------------------\n\n/// Admonition flavours\n$admonitions: (\n note: pencil $clr-blue-a200,\n abstract summary tldr: text-subject $clr-light-blue-a400,\n info todo: information $clr-cyan-a700,\n tip hint important: fire $clr-teal-a700,\n success check done: check-circle $clr-green-a700,\n question help faq: help-circle $clr-light-green-a700,\n warning caution attention: alert $clr-orange-a400,\n failure fail missing: close-circle $clr-red-a200,\n danger error: flash-circle $clr-red-a400,\n bug: bug $clr-pink-a400,\n example: format-list-numbered $clr-deep-purple-a200,\n quote cite: format-quote-close $clr-grey\n) !default;\n\n// ----------------------------------------------------------------------------\n// Rules: layout\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n @each $names, $props in $admonitions {\n --md-admonition-icon--#{nth($names, 1)}:\n svg-load(\"material/#{nth($props, 1)}.svg\");\n }\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n // Admonition\n .admonition {\n margin: px2em(20px, 12.8px) 0;\n padding: 0 px2rem(12px);\n overflow: hidden;\n color: var(--md-admonition-fg-color);\n font-size: px2rem(12.8px);\n page-break-inside: avoid;\n background-color: var(--md-admonition-bg-color);\n border-left: px2rem(4px) solid $clr-blue-a200;\n border-radius: px2rem(2px);\n box-shadow:\n 0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.05),\n 0 px2rem(0.5px) px2rem(1px) hsla(0, 0%, 0%, 0.05);\n\n // [print]: Omit shadow as it may lead to rendering errors\n @media print {\n box-shadow: none;\n }\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n border-right: px2rem(4px) solid $clr-blue-a200;\n border-left: none;\n }\n\n // Adjust vertical spacing for nested admonitions\n .admonition {\n margin-top: 1em;\n margin-bottom: 1em;\n }\n\n // Adjust spacing for contained table wrappers\n .md-typeset__scrollwrap {\n margin: 1em px2rem(-12px);\n }\n\n // Adjust spacing for contained tables\n .md-typeset__table {\n padding: 0 px2rem(12px);\n }\n\n // Adjust spacing for single-child tabbed block container\n > .tabbed-set:only-child {\n margin-top: 0;\n }\n\n // Adjust spacing on last child\n html & > :last-child {\n margin-bottom: px2rem(12px);\n }\n }\n\n // Admonition title\n .admonition-title {\n position: relative;\n margin: 0 px2rem(-12px) 0 px2rem(-16px);\n padding: px2rem(8px) px2rem(12px) px2rem(8px) px2rem(40px);\n font-weight: 700;\n background-color: transparentize($clr-blue-a200, 0.9);\n border-left: px2rem(4px) solid $clr-blue-a200;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin: 0 px2rem(-16px) 0 px2rem(-12px);\n padding: px2rem(8px) px2rem(40px) px2rem(8px) px2rem(12px);\n border-right: px2rem(4px) solid $clr-blue-a200;\n border-left: none;\n }\n\n // Adjust spacing for title-only admonitions\n html &:last-child {\n margin-bottom: 0;\n }\n\n // Admonition icon\n &::before {\n position: absolute;\n left: px2rem(12px);\n width: px2rem(20px);\n height: px2rem(20px);\n background-color: $clr-blue-a200;\n mask-image: var(--md-admonition-icon--note);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: px2rem(12px);\n left: initial;\n }\n }\n\n // Adjust spacing on last tabbed block container child - if the tabbed\n // block container is the sole child, it looks better to omit the margin\n + .tabbed-set:last-child {\n margin-top: 0;\n }\n }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: flavours\n// ----------------------------------------------------------------------------\n\n@each $names, $props in $admonitions {\n $name: nth($names, 1);\n $tint: nth($props, 2);\n\n // Admonition flavour\n .md-typeset .admonition.#{$name} {\n border-color: $tint;\n }\n\n // Admonition flavour title\n .md-typeset .#{$name} > .admonition-title {\n background-color: transparentize($tint, 0.9);\n border-color: $tint;\n\n // Admonition icon\n &::before {\n background-color: $tint;\n mask-image: var(--md-admonition-icon--#{$name});\n mask-repeat: no-repeat;\n mask-size: contain;\n }\n }\n\n // Define synonyms for flavours\n @if length($names) > 1 {\n @for $n from 2 through length($names) {\n .#{nth($names, $n)} {\n @extend .#{$name};\n }\n }\n }\n}\n","// ==========================================================================\n//\n// Name: UI Color Palette\n// Description: The color palette of material design.\n// Version: 2.3.1\n//\n// Author: Denis Malinochkin\n// Git: https://github.com/mrmlnc/material-color\n//\n// twitter: @mrmlnc\n//\n// ==========================================================================\n\n\n//\n// List of base colors\n//\n\n// $clr-red\n// $clr-pink\n// $clr-purple\n// $clr-deep-purple\n// $clr-indigo\n// $clr-blue\n// $clr-light-blue\n// $clr-cyan\n// $clr-teal\n// $clr-green\n// $clr-light-green\n// $clr-lime\n// $clr-yellow\n// $clr-amber\n// $clr-orange\n// $clr-deep-orange\n// $clr-brown\n// $clr-grey\n// $clr-blue-grey\n// $clr-black\n// $clr-white\n\n\n//\n// Red\n//\n\n$clr-red-list: (\n \"base\": #f44336,\n \"50\": #ffebee,\n \"100\": #ffcdd2,\n \"200\": #ef9a9a,\n \"300\": #e57373,\n \"400\": #ef5350,\n \"500\": #f44336,\n \"600\": #e53935,\n \"700\": #d32f2f,\n \"800\": #c62828,\n \"900\": #b71c1c,\n \"a100\": #ff8a80,\n \"a200\": #ff5252,\n \"a400\": #ff1744,\n \"a700\": #d50000\n);\n\n$clr-red: map-get($clr-red-list, \"base\");\n\n$clr-red-50: map-get($clr-red-list, \"50\");\n$clr-red-100: map-get($clr-red-list, \"100\");\n$clr-red-200: map-get($clr-red-list, \"200\");\n$clr-red-300: map-get($clr-red-list, \"300\");\n$clr-red-400: map-get($clr-red-list, \"400\");\n$clr-red-500: map-get($clr-red-list, \"500\");\n$clr-red-600: map-get($clr-red-list, \"600\");\n$clr-red-700: map-get($clr-red-list, \"700\");\n$clr-red-800: map-get($clr-red-list, \"800\");\n$clr-red-900: map-get($clr-red-list, \"900\");\n$clr-red-a100: map-get($clr-red-list, \"a100\");\n$clr-red-a200: map-get($clr-red-list, \"a200\");\n$clr-red-a400: map-get($clr-red-list, \"a400\");\n$clr-red-a700: map-get($clr-red-list, \"a700\");\n\n\n//\n// Pink\n//\n\n$clr-pink-list: (\n \"base\": #e91e63,\n \"50\": #fce4ec,\n \"100\": #f8bbd0,\n \"200\": #f48fb1,\n \"300\": #f06292,\n \"400\": #ec407a,\n \"500\": #e91e63,\n \"600\": #d81b60,\n \"700\": #c2185b,\n \"800\": #ad1457,\n \"900\": #880e4f,\n \"a100\": #ff80ab,\n \"a200\": #ff4081,\n \"a400\": #f50057,\n \"a700\": #c51162\n);\n\n$clr-pink: map-get($clr-pink-list, \"base\");\n\n$clr-pink-50: map-get($clr-pink-list, \"50\");\n$clr-pink-100: map-get($clr-pink-list, \"100\");\n$clr-pink-200: map-get($clr-pink-list, \"200\");\n$clr-pink-300: map-get($clr-pink-list, \"300\");\n$clr-pink-400: map-get($clr-pink-list, \"400\");\n$clr-pink-500: map-get($clr-pink-list, \"500\");\n$clr-pink-600: map-get($clr-pink-list, \"600\");\n$clr-pink-700: map-get($clr-pink-list, \"700\");\n$clr-pink-800: map-get($clr-pink-list, \"800\");\n$clr-pink-900: map-get($clr-pink-list, \"900\");\n$clr-pink-a100: map-get($clr-pink-list, \"a100\");\n$clr-pink-a200: map-get($clr-pink-list, \"a200\");\n$clr-pink-a400: map-get($clr-pink-list, \"a400\");\n$clr-pink-a700: map-get($clr-pink-list, \"a700\");\n\n\n//\n// Purple\n//\n\n$clr-purple-list: (\n \"base\": #9c27b0,\n \"50\": #f3e5f5,\n \"100\": #e1bee7,\n \"200\": #ce93d8,\n \"300\": #ba68c8,\n \"400\": #ab47bc,\n \"500\": #9c27b0,\n \"600\": #8e24aa,\n \"700\": #7b1fa2,\n \"800\": #6a1b9a,\n \"900\": #4a148c,\n \"a100\": #ea80fc,\n \"a200\": #e040fb,\n \"a400\": #d500f9,\n \"a700\": #aa00ff\n);\n\n$clr-purple: map-get($clr-purple-list, \"base\");\n\n$clr-purple-50: map-get($clr-purple-list, \"50\");\n$clr-purple-100: map-get($clr-purple-list, \"100\");\n$clr-purple-200: map-get($clr-purple-list, \"200\");\n$clr-purple-300: map-get($clr-purple-list, \"300\");\n$clr-purple-400: map-get($clr-purple-list, \"400\");\n$clr-purple-500: map-get($clr-purple-list, \"500\");\n$clr-purple-600: map-get($clr-purple-list, \"600\");\n$clr-purple-700: map-get($clr-purple-list, \"700\");\n$clr-purple-800: map-get($clr-purple-list, \"800\");\n$clr-purple-900: map-get($clr-purple-list, \"900\");\n$clr-purple-a100: map-get($clr-purple-list, \"a100\");\n$clr-purple-a200: map-get($clr-purple-list, \"a200\");\n$clr-purple-a400: map-get($clr-purple-list, \"a400\");\n$clr-purple-a700: map-get($clr-purple-list, \"a700\");\n\n\n//\n// Deep purple\n//\n\n$clr-deep-purple-list: (\n \"base\": #673ab7,\n \"50\": #ede7f6,\n \"100\": #d1c4e9,\n \"200\": #b39ddb,\n \"300\": #9575cd,\n \"400\": #7e57c2,\n \"500\": #673ab7,\n \"600\": #5e35b1,\n \"700\": #512da8,\n \"800\": #4527a0,\n \"900\": #311b92,\n \"a100\": #b388ff,\n \"a200\": #7c4dff,\n \"a400\": #651fff,\n \"a700\": #6200ea\n);\n\n$clr-deep-purple: map-get($clr-deep-purple-list, \"base\");\n\n$clr-deep-purple-50: map-get($clr-deep-purple-list, \"50\");\n$clr-deep-purple-100: map-get($clr-deep-purple-list, \"100\");\n$clr-deep-purple-200: map-get($clr-deep-purple-list, \"200\");\n$clr-deep-purple-300: map-get($clr-deep-purple-list, \"300\");\n$clr-deep-purple-400: map-get($clr-deep-purple-list, \"400\");\n$clr-deep-purple-500: map-get($clr-deep-purple-list, \"500\");\n$clr-deep-purple-600: map-get($clr-deep-purple-list, \"600\");\n$clr-deep-purple-700: map-get($clr-deep-purple-list, \"700\");\n$clr-deep-purple-800: map-get($clr-deep-purple-list, \"800\");\n$clr-deep-purple-900: map-get($clr-deep-purple-list, \"900\");\n$clr-deep-purple-a100: map-get($clr-deep-purple-list, \"a100\");\n$clr-deep-purple-a200: map-get($clr-deep-purple-list, \"a200\");\n$clr-deep-purple-a400: map-get($clr-deep-purple-list, \"a400\");\n$clr-deep-purple-a700: map-get($clr-deep-purple-list, \"a700\");\n\n\n//\n// Indigo\n//\n\n$clr-indigo-list: (\n \"base\": #3f51b5,\n \"50\": #e8eaf6,\n \"100\": #c5cae9,\n \"200\": #9fa8da,\n \"300\": #7986cb,\n \"400\": #5c6bc0,\n \"500\": #3f51b5,\n \"600\": #3949ab,\n \"700\": #303f9f,\n \"800\": #283593,\n \"900\": #1a237e,\n \"a100\": #8c9eff,\n \"a200\": #536dfe,\n \"a400\": #3d5afe,\n \"a700\": #304ffe\n);\n\n$clr-indigo: map-get($clr-indigo-list, \"base\");\n\n$clr-indigo-50: map-get($clr-indigo-list, \"50\");\n$clr-indigo-100: map-get($clr-indigo-list, \"100\");\n$clr-indigo-200: map-get($clr-indigo-list, \"200\");\n$clr-indigo-300: map-get($clr-indigo-list, \"300\");\n$clr-indigo-400: map-get($clr-indigo-list, \"400\");\n$clr-indigo-500: map-get($clr-indigo-list, \"500\");\n$clr-indigo-600: map-get($clr-indigo-list, \"600\");\n$clr-indigo-700: map-get($clr-indigo-list, \"700\");\n$clr-indigo-800: map-get($clr-indigo-list, \"800\");\n$clr-indigo-900: map-get($clr-indigo-list, \"900\");\n$clr-indigo-a100: map-get($clr-indigo-list, \"a100\");\n$clr-indigo-a200: map-get($clr-indigo-list, \"a200\");\n$clr-indigo-a400: map-get($clr-indigo-list, \"a400\");\n$clr-indigo-a700: map-get($clr-indigo-list, \"a700\");\n\n\n//\n// Blue\n//\n\n$clr-blue-list: (\n \"base\": #2196f3,\n \"50\": #e3f2fd,\n \"100\": #bbdefb,\n \"200\": #90caf9,\n \"300\": #64b5f6,\n \"400\": #42a5f5,\n \"500\": #2196f3,\n \"600\": #1e88e5,\n \"700\": #1976d2,\n \"800\": #1565c0,\n \"900\": #0d47a1,\n \"a100\": #82b1ff,\n \"a200\": #448aff,\n \"a400\": #2979ff,\n \"a700\": #2962ff\n);\n\n$clr-blue: map-get($clr-blue-list, \"base\");\n\n$clr-blue-50: map-get($clr-blue-list, \"50\");\n$clr-blue-100: map-get($clr-blue-list, \"100\");\n$clr-blue-200: map-get($clr-blue-list, \"200\");\n$clr-blue-300: map-get($clr-blue-list, \"300\");\n$clr-blue-400: map-get($clr-blue-list, \"400\");\n$clr-blue-500: map-get($clr-blue-list, \"500\");\n$clr-blue-600: map-get($clr-blue-list, \"600\");\n$clr-blue-700: map-get($clr-blue-list, \"700\");\n$clr-blue-800: map-get($clr-blue-list, \"800\");\n$clr-blue-900: map-get($clr-blue-list, \"900\");\n$clr-blue-a100: map-get($clr-blue-list, \"a100\");\n$clr-blue-a200: map-get($clr-blue-list, \"a200\");\n$clr-blue-a400: map-get($clr-blue-list, \"a400\");\n$clr-blue-a700: map-get($clr-blue-list, \"a700\");\n\n\n//\n// Light Blue\n//\n\n$clr-light-blue-list: (\n \"base\": #03a9f4,\n \"50\": #e1f5fe,\n \"100\": #b3e5fc,\n \"200\": #81d4fa,\n \"300\": #4fc3f7,\n \"400\": #29b6f6,\n \"500\": #03a9f4,\n \"600\": #039be5,\n \"700\": #0288d1,\n \"800\": #0277bd,\n \"900\": #01579b,\n \"a100\": #80d8ff,\n \"a200\": #40c4ff,\n \"a400\": #00b0ff,\n \"a700\": #0091ea\n);\n\n$clr-light-blue: map-get($clr-light-blue-list, \"base\");\n\n$clr-light-blue-50: map-get($clr-light-blue-list, \"50\");\n$clr-light-blue-100: map-get($clr-light-blue-list, \"100\");\n$clr-light-blue-200: map-get($clr-light-blue-list, \"200\");\n$clr-light-blue-300: map-get($clr-light-blue-list, \"300\");\n$clr-light-blue-400: map-get($clr-light-blue-list, \"400\");\n$clr-light-blue-500: map-get($clr-light-blue-list, \"500\");\n$clr-light-blue-600: map-get($clr-light-blue-list, \"600\");\n$clr-light-blue-700: map-get($clr-light-blue-list, \"700\");\n$clr-light-blue-800: map-get($clr-light-blue-list, \"800\");\n$clr-light-blue-900: map-get($clr-light-blue-list, \"900\");\n$clr-light-blue-a100: map-get($clr-light-blue-list, \"a100\");\n$clr-light-blue-a200: map-get($clr-light-blue-list, \"a200\");\n$clr-light-blue-a400: map-get($clr-light-blue-list, \"a400\");\n$clr-light-blue-a700: map-get($clr-light-blue-list, \"a700\");\n\n\n//\n// Cyan\n//\n\n$clr-cyan-list: (\n \"base\": #00bcd4,\n \"50\": #e0f7fa,\n \"100\": #b2ebf2,\n \"200\": #80deea,\n \"300\": #4dd0e1,\n \"400\": #26c6da,\n \"500\": #00bcd4,\n \"600\": #00acc1,\n \"700\": #0097a7,\n \"800\": #00838f,\n \"900\": #006064,\n \"a100\": #84ffff,\n \"a200\": #18ffff,\n \"a400\": #00e5ff,\n \"a700\": #00b8d4\n);\n\n$clr-cyan: map-get($clr-cyan-list, \"base\");\n\n$clr-cyan-50: map-get($clr-cyan-list, \"50\");\n$clr-cyan-100: map-get($clr-cyan-list, \"100\");\n$clr-cyan-200: map-get($clr-cyan-list, \"200\");\n$clr-cyan-300: map-get($clr-cyan-list, \"300\");\n$clr-cyan-400: map-get($clr-cyan-list, \"400\");\n$clr-cyan-500: map-get($clr-cyan-list, \"500\");\n$clr-cyan-600: map-get($clr-cyan-list, \"600\");\n$clr-cyan-700: map-get($clr-cyan-list, \"700\");\n$clr-cyan-800: map-get($clr-cyan-list, \"800\");\n$clr-cyan-900: map-get($clr-cyan-list, \"900\");\n$clr-cyan-a100: map-get($clr-cyan-list, \"a100\");\n$clr-cyan-a200: map-get($clr-cyan-list, \"a200\");\n$clr-cyan-a400: map-get($clr-cyan-list, \"a400\");\n$clr-cyan-a700: map-get($clr-cyan-list, \"a700\");\n\n\n//\n// Teal\n//\n\n$clr-teal-list: (\n \"base\": #009688,\n \"50\": #e0f2f1,\n \"100\": #b2dfdb,\n \"200\": #80cbc4,\n \"300\": #4db6ac,\n \"400\": #26a69a,\n \"500\": #009688,\n \"600\": #00897b,\n \"700\": #00796b,\n \"800\": #00695c,\n \"900\": #004d40,\n \"a100\": #a7ffeb,\n \"a200\": #64ffda,\n \"a400\": #1de9b6,\n \"a700\": #00bfa5\n);\n\n$clr-teal: map-get($clr-teal-list, \"base\");\n\n$clr-teal-50: map-get($clr-teal-list, \"50\");\n$clr-teal-100: map-get($clr-teal-list, \"100\");\n$clr-teal-200: map-get($clr-teal-list, \"200\");\n$clr-teal-300: map-get($clr-teal-list, \"300\");\n$clr-teal-400: map-get($clr-teal-list, \"400\");\n$clr-teal-500: map-get($clr-teal-list, \"500\");\n$clr-teal-600: map-get($clr-teal-list, \"600\");\n$clr-teal-700: map-get($clr-teal-list, \"700\");\n$clr-teal-800: map-get($clr-teal-list, \"800\");\n$clr-teal-900: map-get($clr-teal-list, \"900\");\n$clr-teal-a100: map-get($clr-teal-list, \"a100\");\n$clr-teal-a200: map-get($clr-teal-list, \"a200\");\n$clr-teal-a400: map-get($clr-teal-list, \"a400\");\n$clr-teal-a700: map-get($clr-teal-list, \"a700\");\n\n\n//\n// Green\n//\n\n$clr-green-list: (\n \"base\": #4caf50,\n \"50\": #e8f5e9,\n \"100\": #c8e6c9,\n \"200\": #a5d6a7,\n \"300\": #81c784,\n \"400\": #66bb6a,\n \"500\": #4caf50,\n \"600\": #43a047,\n \"700\": #388e3c,\n \"800\": #2e7d32,\n \"900\": #1b5e20,\n \"a100\": #b9f6ca,\n \"a200\": #69f0ae,\n \"a400\": #00e676,\n \"a700\": #00c853\n);\n\n$clr-green: map-get($clr-green-list, \"base\");\n\n$clr-green-50: map-get($clr-green-list, \"50\");\n$clr-green-100: map-get($clr-green-list, \"100\");\n$clr-green-200: map-get($clr-green-list, \"200\");\n$clr-green-300: map-get($clr-green-list, \"300\");\n$clr-green-400: map-get($clr-green-list, \"400\");\n$clr-green-500: map-get($clr-green-list, \"500\");\n$clr-green-600: map-get($clr-green-list, \"600\");\n$clr-green-700: map-get($clr-green-list, \"700\");\n$clr-green-800: map-get($clr-green-list, \"800\");\n$clr-green-900: map-get($clr-green-list, \"900\");\n$clr-green-a100: map-get($clr-green-list, \"a100\");\n$clr-green-a200: map-get($clr-green-list, \"a200\");\n$clr-green-a400: map-get($clr-green-list, \"a400\");\n$clr-green-a700: map-get($clr-green-list, \"a700\");\n\n\n//\n// Light green\n//\n\n$clr-light-green-list: (\n \"base\": #8bc34a,\n \"50\": #f1f8e9,\n \"100\": #dcedc8,\n \"200\": #c5e1a5,\n \"300\": #aed581,\n \"400\": #9ccc65,\n \"500\": #8bc34a,\n \"600\": #7cb342,\n \"700\": #689f38,\n \"800\": #558b2f,\n \"900\": #33691e,\n \"a100\": #ccff90,\n \"a200\": #b2ff59,\n \"a400\": #76ff03,\n \"a700\": #64dd17\n);\n\n$clr-light-green: map-get($clr-light-green-list, \"base\");\n\n$clr-light-green-50: map-get($clr-light-green-list, \"50\");\n$clr-light-green-100: map-get($clr-light-green-list, \"100\");\n$clr-light-green-200: map-get($clr-light-green-list, \"200\");\n$clr-light-green-300: map-get($clr-light-green-list, \"300\");\n$clr-light-green-400: map-get($clr-light-green-list, \"400\");\n$clr-light-green-500: map-get($clr-light-green-list, \"500\");\n$clr-light-green-600: map-get($clr-light-green-list, \"600\");\n$clr-light-green-700: map-get($clr-light-green-list, \"700\");\n$clr-light-green-800: map-get($clr-light-green-list, \"800\");\n$clr-light-green-900: map-get($clr-light-green-list, \"900\");\n$clr-light-green-a100: map-get($clr-light-green-list, \"a100\");\n$clr-light-green-a200: map-get($clr-light-green-list, \"a200\");\n$clr-light-green-a400: map-get($clr-light-green-list, \"a400\");\n$clr-light-green-a700: map-get($clr-light-green-list, \"a700\");\n\n\n//\n// Lime\n//\n\n$clr-lime-list: (\n \"base\": #cddc39,\n \"50\": #f9fbe7,\n \"100\": #f0f4c3,\n \"200\": #e6ee9c,\n \"300\": #dce775,\n \"400\": #d4e157,\n \"500\": #cddc39,\n \"600\": #c0ca33,\n \"700\": #afb42b,\n \"800\": #9e9d24,\n \"900\": #827717,\n \"a100\": #f4ff81,\n \"a200\": #eeff41,\n \"a400\": #c6ff00,\n \"a700\": #aeea00\n);\n\n$clr-lime: map-get($clr-lime-list, \"base\");\n\n$clr-lime-50: map-get($clr-lime-list, \"50\");\n$clr-lime-100: map-get($clr-lime-list, \"100\");\n$clr-lime-200: map-get($clr-lime-list, \"200\");\n$clr-lime-300: map-get($clr-lime-list, \"300\");\n$clr-lime-400: map-get($clr-lime-list, \"400\");\n$clr-lime-500: map-get($clr-lime-list, \"500\");\n$clr-lime-600: map-get($clr-lime-list, \"600\");\n$clr-lime-700: map-get($clr-lime-list, \"700\");\n$clr-lime-800: map-get($clr-lime-list, \"800\");\n$clr-lime-900: map-get($clr-lime-list, \"900\");\n$clr-lime-a100: map-get($clr-lime-list, \"a100\");\n$clr-lime-a200: map-get($clr-lime-list, \"a200\");\n$clr-lime-a400: map-get($clr-lime-list, \"a400\");\n$clr-lime-a700: map-get($clr-lime-list, \"a700\");\n\n\n//\n// Yellow\n//\n\n$clr-yellow-list: (\n \"base\": #ffeb3b,\n \"50\": #fffde7,\n \"100\": #fff9c4,\n \"200\": #fff59d,\n \"300\": #fff176,\n \"400\": #ffee58,\n \"500\": #ffeb3b,\n \"600\": #fdd835,\n \"700\": #fbc02d,\n \"800\": #f9a825,\n \"900\": #f57f17,\n \"a100\": #ffff8d,\n \"a200\": #ffff00,\n \"a400\": #ffea00,\n \"a700\": #ffd600\n);\n\n$clr-yellow: map-get($clr-yellow-list, \"base\");\n\n$clr-yellow-50: map-get($clr-yellow-list, \"50\");\n$clr-yellow-100: map-get($clr-yellow-list, \"100\");\n$clr-yellow-200: map-get($clr-yellow-list, \"200\");\n$clr-yellow-300: map-get($clr-yellow-list, \"300\");\n$clr-yellow-400: map-get($clr-yellow-list, \"400\");\n$clr-yellow-500: map-get($clr-yellow-list, \"500\");\n$clr-yellow-600: map-get($clr-yellow-list, \"600\");\n$clr-yellow-700: map-get($clr-yellow-list, \"700\");\n$clr-yellow-800: map-get($clr-yellow-list, \"800\");\n$clr-yellow-900: map-get($clr-yellow-list, \"900\");\n$clr-yellow-a100: map-get($clr-yellow-list, \"a100\");\n$clr-yellow-a200: map-get($clr-yellow-list, \"a200\");\n$clr-yellow-a400: map-get($clr-yellow-list, \"a400\");\n$clr-yellow-a700: map-get($clr-yellow-list, \"a700\");\n\n\n//\n// amber\n//\n\n$clr-amber-list: (\n \"base\": #ffc107,\n \"50\": #fff8e1,\n \"100\": #ffecb3,\n \"200\": #ffe082,\n \"300\": #ffd54f,\n \"400\": #ffca28,\n \"500\": #ffc107,\n \"600\": #ffb300,\n \"700\": #ffa000,\n \"800\": #ff8f00,\n \"900\": #ff6f00,\n \"a100\": #ffe57f,\n \"a200\": #ffd740,\n \"a400\": #ffc400,\n \"a700\": #ffab00\n);\n\n$clr-amber: map-get($clr-amber-list, \"base\");\n\n$clr-amber-50: map-get($clr-amber-list, \"50\");\n$clr-amber-100: map-get($clr-amber-list, \"100\");\n$clr-amber-200: map-get($clr-amber-list, \"200\");\n$clr-amber-300: map-get($clr-amber-list, \"300\");\n$clr-amber-400: map-get($clr-amber-list, \"400\");\n$clr-amber-500: map-get($clr-amber-list, \"500\");\n$clr-amber-600: map-get($clr-amber-list, \"600\");\n$clr-amber-700: map-get($clr-amber-list, \"700\");\n$clr-amber-800: map-get($clr-amber-list, \"800\");\n$clr-amber-900: map-get($clr-amber-list, \"900\");\n$clr-amber-a100: map-get($clr-amber-list, \"a100\");\n$clr-amber-a200: map-get($clr-amber-list, \"a200\");\n$clr-amber-a400: map-get($clr-amber-list, \"a400\");\n$clr-amber-a700: map-get($clr-amber-list, \"a700\");\n\n\n//\n// Orange\n//\n\n$clr-orange-list: (\n \"base\": #ff9800,\n \"50\": #fff3e0,\n \"100\": #ffe0b2,\n \"200\": #ffcc80,\n \"300\": #ffb74d,\n \"400\": #ffa726,\n \"500\": #ff9800,\n \"600\": #fb8c00,\n \"700\": #f57c00,\n \"800\": #ef6c00,\n \"900\": #e65100,\n \"a100\": #ffd180,\n \"a200\": #ffab40,\n \"a400\": #ff9100,\n \"a700\": #ff6d00\n);\n\n$clr-orange: map-get($clr-orange-list, \"base\");\n\n$clr-orange-50: map-get($clr-orange-list, \"50\");\n$clr-orange-100: map-get($clr-orange-list, \"100\");\n$clr-orange-200: map-get($clr-orange-list, \"200\");\n$clr-orange-300: map-get($clr-orange-list, \"300\");\n$clr-orange-400: map-get($clr-orange-list, \"400\");\n$clr-orange-500: map-get($clr-orange-list, \"500\");\n$clr-orange-600: map-get($clr-orange-list, \"600\");\n$clr-orange-700: map-get($clr-orange-list, \"700\");\n$clr-orange-800: map-get($clr-orange-list, \"800\");\n$clr-orange-900: map-get($clr-orange-list, \"900\");\n$clr-orange-a100: map-get($clr-orange-list, \"a100\");\n$clr-orange-a200: map-get($clr-orange-list, \"a200\");\n$clr-orange-a400: map-get($clr-orange-list, \"a400\");\n$clr-orange-a700: map-get($clr-orange-list, \"a700\");\n\n\n//\n// Deep orange\n//\n\n$clr-deep-orange-list: (\n \"base\": #ff5722,\n \"50\": #fbe9e7,\n \"100\": #ffccbc,\n \"200\": #ffab91,\n \"300\": #ff8a65,\n \"400\": #ff7043,\n \"500\": #ff5722,\n \"600\": #f4511e,\n \"700\": #e64a19,\n \"800\": #d84315,\n \"900\": #bf360c,\n \"a100\": #ff9e80,\n \"a200\": #ff6e40,\n \"a400\": #ff3d00,\n \"a700\": #dd2c00\n);\n\n$clr-deep-orange: map-get($clr-deep-orange-list, \"base\");\n\n$clr-deep-orange-50: map-get($clr-deep-orange-list, \"50\");\n$clr-deep-orange-100: map-get($clr-deep-orange-list, \"100\");\n$clr-deep-orange-200: map-get($clr-deep-orange-list, \"200\");\n$clr-deep-orange-300: map-get($clr-deep-orange-list, \"300\");\n$clr-deep-orange-400: map-get($clr-deep-orange-list, \"400\");\n$clr-deep-orange-500: map-get($clr-deep-orange-list, \"500\");\n$clr-deep-orange-600: map-get($clr-deep-orange-list, \"600\");\n$clr-deep-orange-700: map-get($clr-deep-orange-list, \"700\");\n$clr-deep-orange-800: map-get($clr-deep-orange-list, \"800\");\n$clr-deep-orange-900: map-get($clr-deep-orange-list, \"900\");\n$clr-deep-orange-a100: map-get($clr-deep-orange-list, \"a100\");\n$clr-deep-orange-a200: map-get($clr-deep-orange-list, \"a200\");\n$clr-deep-orange-a400: map-get($clr-deep-orange-list, \"a400\");\n$clr-deep-orange-a700: map-get($clr-deep-orange-list, \"a700\");\n\n\n//\n// Brown\n//\n\n$clr-brown-list: (\n \"base\": #795548,\n \"50\": #efebe9,\n \"100\": #d7ccc8,\n \"200\": #bcaaa4,\n \"300\": #a1887f,\n \"400\": #8d6e63,\n \"500\": #795548,\n \"600\": #6d4c41,\n \"700\": #5d4037,\n \"800\": #4e342e,\n \"900\": #3e2723,\n);\n\n$clr-brown: map-get($clr-brown-list, \"base\");\n\n$clr-brown-50: map-get($clr-brown-list, \"50\");\n$clr-brown-100: map-get($clr-brown-list, \"100\");\n$clr-brown-200: map-get($clr-brown-list, \"200\");\n$clr-brown-300: map-get($clr-brown-list, \"300\");\n$clr-brown-400: map-get($clr-brown-list, \"400\");\n$clr-brown-500: map-get($clr-brown-list, \"500\");\n$clr-brown-600: map-get($clr-brown-list, \"600\");\n$clr-brown-700: map-get($clr-brown-list, \"700\");\n$clr-brown-800: map-get($clr-brown-list, \"800\");\n$clr-brown-900: map-get($clr-brown-list, \"900\");\n\n\n//\n// Grey\n//\n\n$clr-grey-list: (\n \"base\": #9e9e9e,\n \"50\": #fafafa,\n \"100\": #f5f5f5,\n \"200\": #eeeeee,\n \"300\": #e0e0e0,\n \"400\": #bdbdbd,\n \"500\": #9e9e9e,\n \"600\": #757575,\n \"700\": #616161,\n \"800\": #424242,\n \"900\": #212121,\n);\n\n$clr-grey: map-get($clr-grey-list, \"base\");\n\n$clr-grey-50: map-get($clr-grey-list, \"50\");\n$clr-grey-100: map-get($clr-grey-list, \"100\");\n$clr-grey-200: map-get($clr-grey-list, \"200\");\n$clr-grey-300: map-get($clr-grey-list, \"300\");\n$clr-grey-400: map-get($clr-grey-list, \"400\");\n$clr-grey-500: map-get($clr-grey-list, \"500\");\n$clr-grey-600: map-get($clr-grey-list, \"600\");\n$clr-grey-700: map-get($clr-grey-list, \"700\");\n$clr-grey-800: map-get($clr-grey-list, \"800\");\n$clr-grey-900: map-get($clr-grey-list, \"900\");\n\n\n//\n// Blue grey\n//\n\n$clr-blue-grey-list: (\n \"base\": #607d8b,\n \"50\": #eceff1,\n \"100\": #cfd8dc,\n \"200\": #b0bec5,\n \"300\": #90a4ae,\n \"400\": #78909c,\n \"500\": #607d8b,\n \"600\": #546e7a,\n \"700\": #455a64,\n \"800\": #37474f,\n \"900\": #263238,\n);\n\n$clr-blue-grey: map-get($clr-blue-grey-list, \"base\");\n\n$clr-blue-grey-50: map-get($clr-blue-grey-list, \"50\");\n$clr-blue-grey-100: map-get($clr-blue-grey-list, \"100\");\n$clr-blue-grey-200: map-get($clr-blue-grey-list, \"200\");\n$clr-blue-grey-300: map-get($clr-blue-grey-list, \"300\");\n$clr-blue-grey-400: map-get($clr-blue-grey-list, \"400\");\n$clr-blue-grey-500: map-get($clr-blue-grey-list, \"500\");\n$clr-blue-grey-600: map-get($clr-blue-grey-list, \"600\");\n$clr-blue-grey-700: map-get($clr-blue-grey-list, \"700\");\n$clr-blue-grey-800: map-get($clr-blue-grey-list, \"800\");\n$clr-blue-grey-900: map-get($clr-blue-grey-list, \"900\");\n\n\n//\n// Black\n//\n\n$clr-black-list: (\n \"base\": #000\n);\n\n$clr-black: map-get($clr-black-list, \"base\");\n\n\n//\n// White\n//\n\n$clr-white-list: (\n \"base\": #fff\n);\n\n$clr-white: map-get($clr-white-list, \"base\");\n\n\n//\n// List for all Colors for looping\n//\n\n$clr-list-all: (\n \"red\": $clr-red-list,\n \"pink\": $clr-pink-list,\n \"purple\": $clr-purple-list,\n \"deep-purple\": $clr-deep-purple-list,\n \"indigo\": $clr-indigo-list,\n \"blue\": $clr-blue-list,\n \"light-blue\": $clr-light-blue-list,\n \"cyan\": $clr-cyan-list,\n \"teal\": $clr-teal-list,\n \"green\": $clr-green-list,\n \"light-green\": $clr-light-green-list,\n \"lime\": $clr-lime-list,\n \"yellow\": $clr-yellow-list,\n \"amber\": $clr-amber-list,\n \"orange\": $clr-orange-list,\n \"deep-orange\": $clr-deep-orange-list,\n \"brown\": $clr-brown-list,\n \"grey\": $clr-grey-list,\n \"blue-grey\": $clr-blue-grey-list,\n \"black\": $clr-black-list,\n \"white\": $clr-white-list\n);\n\n\n//\n// Typography\n//\n\n$clr-ui-display-4: $clr-grey-600;\n$clr-ui-display-3: $clr-grey-600;\n$clr-ui-display-2: $clr-grey-600;\n$clr-ui-display-1: $clr-grey-600;\n$clr-ui-headline: $clr-grey-900;\n$clr-ui-title: $clr-grey-900;\n$clr-ui-subhead-1: $clr-grey-900;\n$clr-ui-body-2: $clr-grey-900;\n$clr-ui-body-1: $clr-grey-900;\n$clr-ui-caption: $clr-grey-600;\n$clr-ui-menu: $clr-grey-900;\n$clr-ui-button: $clr-grey-900;\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n --md-footnotes-icon: svg-load(\"material/keyboard-return.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n // Footnote reference\n [id^=\"fnref:\"]:target {\n scroll-margin-top: initial;\n margin-top: -1 * px2rem(48px + 24px - 4px);\n padding-top: px2rem(48px + 24px - 4px);\n }\n\n // Footnote\n [id^=\"fn:\"]:target {\n scroll-margin-top: initial;\n margin-top: -1 * px2rem(48px + 24px - 3px);\n padding-top: px2rem(48px + 24px - 3px);\n }\n\n // Footnote container\n .footnote {\n color: var(--md-default-fg-color--light);\n font-size: px2rem(12.8px);\n\n // Footnote list - omit left indentation\n > ol {\n margin-left: 0;\n\n // Footnote item - footnote items can contain lists, so we need to scope\n // the spacing adjustments to the top-level footnote item.\n > li {\n transition: color 125ms;\n\n // Darken color on target\n &:target {\n color: var(--md-default-fg-color);\n }\n\n // Show backreferences on footnote hover\n &:hover .footnote-backref,\n &:target .footnote-backref {\n transform: translateX(0);\n opacity: 1;\n }\n\n // Adjust spacing on first child\n > :first-child {\n margin-top: 0;\n }\n }\n }\n }\n\n // Footnote backreference\n .footnote-backref {\n display: inline-block;\n color: var(--md-typeset-a-color);\n // Hack: omit Unicode arrow for replacement with icon\n font-size: 0;\n vertical-align: text-bottom;\n transform: translateX(px2rem(5px));\n opacity: 0;\n transition:\n color 250ms,\n transform 250ms 250ms,\n opacity 125ms 250ms;\n\n // [print]: Show footnote backreferences\n @media print {\n color: var(--md-typeset-a-color);\n transform: translateX(0);\n opacity: 1;\n }\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n transform: translateX(px2rem(-5px));\n }\n\n // Adjust color on hover\n &:hover {\n color: var(--md-accent-fg-color);\n }\n\n // Footnote backreference icon\n &::before {\n display: inline-block;\n width: px2rem(16px);\n height: px2rem(16px);\n background-color: currentColor;\n mask-image: var(--md-footnotes-icon);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n\n // Flip icon vertically\n svg {\n transform: scaleX(-1);\n }\n }\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n // Headerlink\n .headerlink {\n display: inline-block;\n margin-left: px2rem(10px);\n color: var(--md-default-fg-color--lighter);\n opacity: 0;\n transition:\n color 250ms,\n opacity 125ms;\n\n // [print]: Hide headerlinks\n @media print {\n display: none;\n }\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n margin-right: px2rem(10px);\n margin-left: initial;\n }\n }\n\n // Show headerlinks on parent hover\n :hover > .headerlink,\n :target > .headerlink,\n .headerlink:focus {\n opacity: 1;\n transition:\n color 250ms,\n opacity 125ms;\n }\n\n // Adjust color on parent target or focus/hover\n :target > .headerlink,\n .headerlink:focus,\n .headerlink:hover {\n color: var(--md-accent-fg-color);\n }\n\n // Adjust scroll offset for all elements with `id` attributes - general scroll\n // margin offset for anything that can be targeted. Browser support is pretty\n // decent by now, but Edge <79 and Safari (iOS and macOS) still don't support\n // it properly, so we settle with a cross-browser anchor correction solution.\n :target {\n scroll-margin-top: px2rem(48px + 24px);\n }\n\n // Adjust scroll offset for headlines of level 1-3\n h1:target,\n h2:target,\n h3:target {\n scroll-margin-top: initial;\n\n // Anchor correction hack\n &::before {\n display: block;\n margin-top: -1 * px2rem(48px + 24px - 4px);\n padding-top: px2rem(48px + 24px - 4px);\n content: \"\";\n }\n }\n\n // Adjust scroll offset for headlines of level 4\n h4:target {\n scroll-margin-top: initial;\n\n // Anchor correction hack\n &::before {\n display: block;\n margin-top: -1 * px2rem(48px + 24px - 3px);\n padding-top: px2rem(48px + 24px - 3px);\n content: \"\";\n }\n }\n\n // Adjust scroll offset for headlines of level 5-6\n h5:target,\n h6:target {\n scroll-margin-top: initial;\n\n // Anchor correction hack\n &::before {\n display: block;\n margin-top: -1 * px2rem(48px + 24px);\n padding-top: px2rem(48px + 24px);\n content: \"\";\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n // Arithmatex container\n div.arithmatex {\n overflow: auto;\n\n // [mobile -]: Align with body copy\n @include break-to-device(mobile) {\n margin: 0 px2rem(-16px);\n }\n\n // Arithmatex content\n > * {\n width: min-content;\n // stylelint-disable-next-line declaration-no-important\n margin: 1em auto !important;\n padding: 0 px2rem(16px);\n touch-action: auto;\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n // Deletion, addition or comment\n del.critic,\n ins.critic,\n .critic.comment {\n box-decoration-break: clone;\n }\n\n // Deletion\n del.critic {\n background-color: var(--md-typeset-del-color);\n }\n\n // Addition\n ins.critic {\n background-color: var(--md-typeset-ins-color);\n }\n\n // Comment\n .critic.comment {\n color: var(--md-code-hl-comment-color);\n\n // Comment opening mark\n &::before {\n content: \"/* \";\n }\n\n // Comment closing mark\n &::after {\n content: \" */\";\n }\n }\n\n // Critic block\n .critic.block {\n display: block;\n margin: 1em 0;\n padding-right: px2rem(16px);\n padding-left: px2rem(16px);\n overflow: auto;\n box-shadow: none;\n\n // Adjust spacing on first child\n > :first-child {\n margin-top: 0.5em;\n }\n\n // Adjust spacing on last child\n > :last-child {\n margin-bottom: 0.5em;\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n --md-details-icon: svg-load(\"material/chevron-right.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n // Details\n details {\n @extend .admonition;\n\n display: flow-root;\n padding-top: 0;\n overflow: visible;\n\n // Details title icon - rotate icon on transition to open state\n &[open] > summary::after {\n transform: rotate(90deg);\n }\n\n // Adjust spacing for details in closed state\n &:not([open]) {\n padding-bottom: 0;\n box-shadow: none;\n\n // Hack: we cannot set `overflow: hidden` on the `details` element (which\n // is why we set it to `overflow: visible`, as the outline would not be\n // visible when focusing. Therefore, we must set the border radius on the\n // summary explicitly.\n > summary {\n border-radius: px2rem(2px);\n }\n }\n\n // Hack: omit margin collapse\n &::after {\n display: table;\n content: \"\";\n }\n }\n\n // Details title\n summary {\n @extend .admonition-title;\n\n display: block;\n min-height: px2rem(20px);\n padding: px2rem(8px) px2rem(36px) px2rem(8px) px2rem(40px);\n border-top-left-radius: px2rem(2px);\n border-top-right-radius: px2rem(2px);\n cursor: pointer;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n padding: px2rem(8px) px2rem(44px) px2rem(8px) px2rem(36px);\n }\n\n // Hide outline for pointer devices\n &:not(.focus-visible) {\n outline: none;\n -webkit-tap-highlight-color: transparent;\n }\n\n // Details marker\n &::after {\n position: absolute;\n top: px2rem(8px);\n right: px2rem(8px);\n width: px2rem(20px);\n height: px2rem(20px);\n background-color: currentColor;\n mask-image: var(--md-details-icon);\n mask-repeat: no-repeat;\n mask-size: contain;\n transform: rotate(0deg);\n transition: transform 250ms;\n content: \"\";\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: initial;\n left: px2rem(8px);\n transform: rotate(180deg);\n }\n }\n\n // Hide native details marker\n &::marker,\n &::-webkit-details-marker {\n display: none;\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n // Emoji and icon container\n .emojione,\n .twemoji,\n .gemoji {\n display: inline-flex;\n height: px2em(18px);\n vertical-align: text-top;\n\n // Icon - inlined via mkdocs-material-extensions\n svg {\n width: px2em(18px);\n max-height: 100%;\n fill: currentColor;\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules: syntax highlighting\n// ----------------------------------------------------------------------------\n\n// Code block\n.highlight {\n .o, // Operator\n .ow { // Operator, word\n color: var(--md-code-hl-operator-color);\n }\n\n .p { // Punctuation\n color: var(--md-code-hl-punctuation-color);\n }\n\n .cpf, // Comment, preprocessor file\n .l, // Literal\n .s, // Literal, string\n .sb, // Literal, string backticks\n .sc, // Literal, string char\n .s2, // Literal, string double\n .si, // Literal, string interpol\n .s1, // Literal, string single\n .ss { // Literal, string symbol\n color: var(--md-code-hl-string-color);\n }\n\n .cp, // Comment, pre-processor\n .se, // Literal, string escape\n .sh, // Literal, string heredoc\n .sr, // Literal, string regex\n .sx { // Literal, string other\n color: var(--md-code-hl-special-color);\n }\n\n .m, // Number\n .mb, // Number, binary\n .mf, // Number, float\n .mh, // Number, hex\n .mi, // Number, integer\n .il, // Number, integer long\n .mo { // Number, octal\n color: var(--md-code-hl-number-color);\n }\n\n .k, // Keyword,\n .kd, // Keyword, declaration\n .kn, // Keyword, namespace\n .kp, // Keyword, pseudo\n .kr, // Keyword, reserved\n .kt { // Keyword, type\n color: var(--md-code-hl-keyword-color);\n }\n\n .kc, // Keyword, constant\n .n { // Name\n color: var(--md-code-hl-name-color);\n }\n\n .no, // Name, constant\n .nb, // Name, builtin\n .bp { // Name, builtin pseudo\n color: var(--md-code-hl-constant-color);\n }\n\n .nc, // Name, class\n .ne, // Name, exception\n .nf, // Name, function\n .nn { // Name, namespace\n color: var(--md-code-hl-function-color);\n }\n\n .nd, // Name, decorator\n .ni, // Name, entity\n .nl, // Name, label\n .nt { // Name, tag\n color: var(--md-code-hl-keyword-color);\n }\n\n .c, // Comment\n .cm, // Comment, multiline\n .c1, // Comment, single\n .ch, // Comment, shebang\n .cs, // Comment, special\n .sd { // Literal, string doc\n color: var(--md-code-hl-comment-color);\n }\n\n .na, // Name, attribute\n .nv, // Variable,\n .vc, // Variable, class\n .vg, // Variable, global\n .vi { // Variable, instance\n color: var(--md-code-hl-variable-color);\n }\n\n .ge, // Generic, emph\n .gr, // Generic, error\n .gh, // Generic, heading\n .go, // Generic, output\n .gp, // Generic, prompt\n .gs, // Generic, strong\n .gu, // Generic, subheading\n .gt { // Generic, traceback\n color: var(--md-code-hl-generic-color);\n }\n\n .gd, // Diff, delete\n .gi { // Diff, insert\n margin: 0 px2em(-2px);\n padding: 0 px2em(2px);\n border-radius: px2rem(2px);\n }\n\n .gd { // Diff, delete\n background-color: var(--md-typeset-del-color);\n }\n\n .gi { // Diff, insert\n background-color: var(--md-typeset-ins-color);\n }\n\n // Highlighted line\n .hll {\n display: block;\n margin: 0 px2em(-16px, 13.6px);\n padding: 0 px2em(16px, 13.6px);\n background-color: var(--md-code-hl-color);\n }\n\n // Code block line numbers (inline)\n [data-linenos]::before {\n position: sticky;\n left: px2em(-16px, 13.6px);\n float: left;\n margin-right: px2em(16px, 13.6px);\n margin-left: px2em(-16px, 13.6px);\n padding-left: px2em(16px, 13.6px);\n color: var(--md-default-fg-color--light);\n background-color: var(--md-code-bg-color);\n box-shadow: px2rem(-1px) 0 var(--md-default-fg-color--lightest) inset;\n content: attr(data-linenos);\n user-select: none;\n }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: layout\n// ----------------------------------------------------------------------------\n\n// Code block with line numbers\n.highlighttable {\n display: flow-root;\n overflow: hidden;\n\n // Set table elements to block layout, because otherwise the whole flexbox\n // hacking won't work correctly\n tbody,\n td {\n display: block;\n padding: 0;\n }\n\n // We need to use flexbox layout, because otherwise it's not possible to\n // make the code container scroll while keeping the line numbers static\n tr {\n display: flex;\n }\n\n // The pre tags are nested inside a table, so we need to omit the margin\n // because it collapses below all the overflows\n pre {\n margin: 0;\n }\n\n // Code block line numbers - disable user selection, so code can be easily\n // copied without accidentally also copying the line numbers\n .linenos {\n padding: px2em(10.5px, 13.6px) px2em(16px, 13.6px);\n padding-right: 0;\n font-size: px2em(13.6px);\n background-color: var(--md-code-bg-color);\n user-select: none;\n }\n\n // Code block line numbers container\n .linenodiv {\n padding-right: px2em(8px, 13.6px);\n box-shadow: px2rem(-1px) 0 var(--md-default-fg-color--lightest) inset;\n\n // Adjust colors and alignment\n pre {\n color: var(--md-default-fg-color--light);\n text-align: right;\n }\n }\n\n // Code block container - stretch to remaining space\n .code {\n flex: 1;\n overflow: hidden;\n }\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n // Code block with line numbers\n .highlighttable {\n margin: 1em 0;\n direction: ltr;\n border-radius: px2rem(2px);\n\n // Omit rounded borders on contained code block\n code {\n border-radius: 0;\n }\n }\n\n // [mobile -]: Align with body copy\n @include break-to-device(mobile) {\n\n // Top-level code block\n > .highlight {\n margin: 1em px2rem(-16px);\n\n // Highlighted line\n .hll {\n margin: 0 px2rem(-16px);\n padding: 0 px2rem(16px);\n }\n\n // Omit rounded borders\n code {\n border-radius: 0;\n }\n }\n\n // Top-level code block with line numbers\n > .highlighttable {\n margin: 1em px2rem(-16px);\n border-radius: 0;\n\n // Highlighted line\n .hll {\n margin: 0 px2rem(-16px);\n padding: 0 px2rem(16px);\n }\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n // Tabbed block content\n .tabbed-content {\n display: none;\n order: 99;\n width: 100%;\n box-shadow: 0 px2rem(-1px) var(--md-default-fg-color--lightest);\n\n // [print]: Show all tabs (even hidden ones) when printing\n @media print {\n display: block;\n order: initial;\n }\n\n // Code block is the only child of a tab - remove margin and mirror\n // previous (now deprecated) SuperFences code block grouping behavior\n > pre:only-child,\n > .highlight:only-child pre,\n > .highlighttable:only-child {\n margin: 0;\n\n // Omit rounded borders\n > code {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n }\n }\n\n // Adjust spacing for nested tab\n > .tabbed-set {\n margin: 0;\n }\n }\n\n // Tabbed block container\n .tabbed-set {\n position: relative;\n display: flex;\n flex-wrap: wrap;\n margin: 1em 0;\n border-radius: px2rem(2px);\n\n // Tab radio button - the Tabbed extension will generate radio buttons with\n // labels, so tabs can be triggered without the necessity for JavaScript.\n // This is pretty cool, as it has great accessibility out-of-the box, so\n // we just hide the radio button and toggle the label color for indication.\n > input {\n position: absolute;\n width: 0;\n height: 0;\n opacity: 0;\n\n // Tab label for checked radio button\n &:checked + label {\n color: var(--md-accent-fg-color);\n border-color: var(--md-accent-fg-color);\n\n // Show tabbed block content\n + .tabbed-content {\n display: block;\n }\n }\n\n // Tab label on focus\n &:focus + label {\n outline-style: auto;\n }\n\n // Hide outline for pointer devices\n &:not(.focus-visible) + label {\n outline: none;\n -webkit-tap-highlight-color: transparent;\n }\n }\n\n // Tab label\n > label {\n z-index: 1;\n width: auto;\n padding: px2em(12px, 12.8px) 1.25em px2em(10px, 12.8px);\n color: var(--md-default-fg-color--light);\n font-weight: 700;\n font-size: px2rem(12.8px);\n border-bottom: px2rem(2px) solid transparent;\n cursor: pointer;\n transition: color 250ms;\n\n // Tab label on hover\n &:hover {\n color: var(--md-accent-fg-color);\n }\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n --md-tasklist-icon:\n svg-load(\"octicons/check-circle-fill-24.svg\");\n --md-tasklist-icon--checked:\n svg-load(\"octicons/check-circle-fill-24.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n // Tasklist item\n .task-list-item {\n position: relative;\n list-style-type: none;\n\n // Make checkbox items align with normal list items, but position\n // everything in ems for correct layout at smaller font sizes\n [type=\"checkbox\"] {\n position: absolute;\n top: 0.45em;\n left: -2em;\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: -2em;\n left: initial;\n }\n }\n }\n\n // Hide native checkbox, when custom classes are enabled\n .task-list-control [type=\"checkbox\"] {\n z-index: -1;\n opacity: 0;\n }\n\n // Tasklist indicator in unchecked state\n .task-list-indicator::before {\n position: absolute;\n top: 0.15em;\n left: px2em(-24px);\n width: px2em(20px);\n height: px2em(20px);\n background-color: var(--md-default-fg-color--lightest);\n mask-image: var(--md-tasklist-icon);\n mask-repeat: no-repeat;\n mask-size: contain;\n content: \"\";\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n right: px2em(-24px);\n left: initial;\n }\n }\n\n // Tasklist indicator in checked state\n [type=\"checkbox\"]:checked + .task-list-indicator::before {\n background-color: $clr-green-a400;\n mask-image: var(--md-tasklist-icon--checked);\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n // [tablet +]: Allow for rendering content as sidebars\n @include break-from-device(tablet) {\n\n // Modifier to float block elements\n .inline {\n float: left;\n width: px2rem(234px);\n margin-top: 0;\n margin-right: px2rem(16px);\n margin-bottom: px2rem(16px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n float: right;\n margin-right: 0;\n margin-left: px2rem(16px);\n }\n\n // Modifier to move to end (ltr: right, rtl: left)\n &.end {\n float: right;\n margin-right: 0;\n margin-left: px2rem(16px);\n\n // Adjust for right-to-left languages\n [dir=\"rtl\"] & {\n float: left;\n margin-right: px2rem(16px);\n margin-left: 0;\n }\n }\n }\n }\n}\n"]} \ No newline at end of file diff --git a/assets/stylesheets/palette.ef6f36e2.min.css b/assets/stylesheets/palette.ef6f36e2.min.css new file mode 100644 index 000000000..e0711bd57 --- /dev/null +++ b/assets/stylesheets/palette.ef6f36e2.min.css @@ -0,0 +1,2 @@ +[data-md-color-accent=red]{--md-accent-fg-color:#ff1a47;--md-accent-fg-color--transparent:rgba(255,26,71,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=pink]{--md-accent-fg-color:#f50056;--md-accent-fg-color--transparent:rgba(245,0,86,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=purple]{--md-accent-fg-color:#df41fb;--md-accent-fg-color--transparent:rgba(223,65,251,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=deep-purple]{--md-accent-fg-color:#7c4dff;--md-accent-fg-color--transparent:rgba(124,77,255,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=indigo]{--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:rgba(83,108,254,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=blue]{--md-accent-fg-color:#4287ff;--md-accent-fg-color--transparent:rgba(66,136,255,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=light-blue]{--md-accent-fg-color:#0091eb;--md-accent-fg-color--transparent:rgba(0,145,235,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=cyan]{--md-accent-fg-color:#00bad6;--md-accent-fg-color--transparent:rgba(0,186,214,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=teal]{--md-accent-fg-color:#00bda4;--md-accent-fg-color--transparent:rgba(0,189,164,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=green]{--md-accent-fg-color:#00c753;--md-accent-fg-color--transparent:rgba(0,199,83,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=light-green]{--md-accent-fg-color:#63de17;--md-accent-fg-color--transparent:rgba(99,222,23,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=lime]{--md-accent-fg-color:#b0eb00;--md-accent-fg-color--transparent:rgba(176,235,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=yellow]{--md-accent-fg-color:#ffd500;--md-accent-fg-color--transparent:rgba(255,213,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=amber]{--md-accent-fg-color:#fa0;--md-accent-fg-color--transparent:rgba(255,170,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=orange]{--md-accent-fg-color:#ff9100;--md-accent-fg-color--transparent:rgba(255,145,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=deep-orange]{--md-accent-fg-color:#ff6e42;--md-accent-fg-color--transparent:rgba(255,110,66,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=red]{--md-primary-fg-color:#ef5552;--md-primary-fg-color--light:#e57171;--md-primary-fg-color--dark:#e53734;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=pink]{--md-primary-fg-color:#e92063;--md-primary-fg-color--light:#ec417a;--md-primary-fg-color--dark:#c3185d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=purple]{--md-primary-fg-color:#ab47bd;--md-primary-fg-color--light:#bb69c9;--md-primary-fg-color--dark:#8c24a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=deep-purple]{--md-primary-fg-color:#7e56c2;--md-primary-fg-color--light:#9574cd;--md-primary-fg-color--dark:#673ab6;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=indigo]{--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=blue]{--md-primary-fg-color:#2094f3;--md-primary-fg-color--light:#42a5f5;--md-primary-fg-color--dark:#1975d2;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=light-blue]{--md-primary-fg-color:#02a6f2;--md-primary-fg-color--light:#28b5f6;--md-primary-fg-color--dark:#0287cf;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=cyan]{--md-primary-fg-color:#00bdd6;--md-primary-fg-color--light:#25c5da;--md-primary-fg-color--dark:#0097a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=teal]{--md-primary-fg-color:#009485;--md-primary-fg-color--light:#26a699;--md-primary-fg-color--dark:#007a6c;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=green]{--md-primary-fg-color:#4cae4f;--md-primary-fg-color--light:#68bb6c;--md-primary-fg-color--dark:#398e3d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=light-green]{--md-primary-fg-color:#8bc34b;--md-primary-fg-color--light:#9ccc66;--md-primary-fg-color--dark:#689f38;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=lime]{--md-primary-fg-color:#cbdc38;--md-primary-fg-color--light:#d3e156;--md-primary-fg-color--dark:#b0b52c;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=yellow]{--md-primary-fg-color:#ffec3d;--md-primary-fg-color--light:#ffee57;--md-primary-fg-color--dark:#fbc02d;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=amber]{--md-primary-fg-color:#ffc105;--md-primary-fg-color--light:#ffc929;--md-primary-fg-color--dark:#ffa200;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=orange]{--md-primary-fg-color:#ffa724;--md-primary-fg-color--light:#ffa724;--md-primary-fg-color--dark:#fa8900;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=deep-orange]{--md-primary-fg-color:#ff6e42;--md-primary-fg-color--light:#ff8a66;--md-primary-fg-color--dark:#f4511f;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=brown]{--md-primary-fg-color:#795649;--md-primary-fg-color--light:#8d6e62;--md-primary-fg-color--dark:#5d4037;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=grey]{--md-primary-fg-color:#757575;--md-primary-fg-color--light:#9e9e9e;--md-primary-fg-color--dark:#616161;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=blue-grey]{--md-primary-fg-color:#546d78;--md-primary-fg-color--light:#607c8a;--md-primary-fg-color--dark:#455a63;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=white]{--md-primary-fg-color:#fff;--md-primary-fg-color--light:hsla(0,0%,100%,0.7);--md-primary-fg-color--dark:rgba(0,0,0,0.07);--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54);--md-typeset-a-color:#4051b5}@media screen and (min-width:60em){[data-md-color-primary=white] .md-search__input{background-color:rgba(0,0,0,.07)}[data-md-color-primary=white] .md-search__input+.md-search__icon{color:rgba(0,0,0,.87)}[data-md-color-primary=white] .md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-moz-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input:hover{background-color:rgba(0,0,0,.32)}}@media screen and (min-width:76.25em){[data-md-color-primary=white] .md-tabs{border-bottom:.05rem solid rgba(0,0,0,.07)}}[data-md-color-primary=black]{--md-primary-fg-color:#000;--md-primary-fg-color--light:rgba(0,0,0,0.54);--md-primary-fg-color--dark:#000;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7);--md-typeset-a-color:#4051b5}[data-md-color-primary=black] .md-header{background-color:#000}@media screen and (max-width:59.9375em){[data-md-color-primary=black] .md-nav__source{background-color:rgba(0,0,0,.87)}}@media screen and (min-width:60em){[data-md-color-primary=black] .md-search__input{background-color:hsla(0,0%,100%,.12)}[data-md-color-primary=black] .md-search__input:hover{background-color:hsla(0,0%,100%,.3)}}@media screen and (max-width:76.1875em){html [data-md-color-primary=black] .md-nav--primary .md-nav__title[for=__drawer]{background-color:#000}}@media screen and (min-width:76.25em){[data-md-color-primary=black] .md-tabs{background-color:#000}}@media screen{[data-md-color-scheme=slate]{--md-hue:232;--md-default-fg-color:hsla(var(--md-hue),75%,95%,1);--md-default-fg-color--light:hsla(var(--md-hue),75%,90%,0.62);--md-default-fg-color--lighter:hsla(var(--md-hue),75%,90%,0.32);--md-default-fg-color--lightest:hsla(var(--md-hue),75%,90%,0.12);--md-default-bg-color:hsla(var(--md-hue),15%,21%,1);--md-default-bg-color--light:hsla(var(--md-hue),15%,21%,0.54);--md-default-bg-color--lighter:hsla(var(--md-hue),15%,21%,0.26);--md-default-bg-color--lightest:hsla(var(--md-hue),15%,21%,0.07);--md-code-fg-color:hsla(var(--md-hue),18%,86%,1);--md-code-bg-color:hsla(var(--md-hue),15%,15%,1);--md-code-hl-color:rgba(66,136,255,0.15);--md-code-hl-number-color:#e6695b;--md-code-hl-special-color:#f06090;--md-code-hl-function-color:#c973d9;--md-code-hl-constant-color:#9383e2;--md-code-hl-keyword-color:#6791e0;--md-code-hl-string-color:#2fb170;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-mark-color:rgba(66,136,255,0.3);--md-typeset-kbd-color:hsla(var(--md-hue),15%,94%,0.12);--md-typeset-kbd-accent-color:hsla(var(--md-hue),15%,94%,0.2);--md-typeset-kbd-border-color:hsla(var(--md-hue),15%,14%,1);--md-admonition-bg-color:hsla(var(--md-hue),0%,100%,0.025);--md-footer-bg-color:hsla(var(--md-hue),15%,12%,0.87);--md-footer-bg-color--dark:hsla(var(--md-hue),15%,10%,1)}[data-md-color-scheme=slate][data-md-color-primary=black],[data-md-color-scheme=slate][data-md-color-primary=white]{--md-typeset-a-color:#5d6cc0}} +/*# sourceMappingURL=palette.ef6f36e2.min.css.map */ \ No newline at end of file diff --git a/assets/stylesheets/palette.ef6f36e2.min.css.map b/assets/stylesheets/palette.ef6f36e2.min.css.map new file mode 100644 index 000000000..da0a67b8e --- /dev/null +++ b/assets/stylesheets/palette.ef6f36e2.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["src/assets/stylesheets/palette/_accent.scss","src/assets/stylesheets/palette.scss","src/assets/stylesheets/palette/_primary.scss","src/assets/stylesheets/utilities/_break.scss","src/assets/stylesheets/palette/_scheme.scss"],"names":[],"mappings":"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","file":"src/assets/stylesheets/palette.scss","sourcesContent":["////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n@each $name, $color in (\n \"red\": $clr-red-a400,\n \"pink\": $clr-pink-a400,\n \"purple\": $clr-purple-a200,\n \"deep-purple\": $clr-deep-purple-a200,\n \"indigo\": $clr-indigo-a200,\n \"blue\": $clr-blue-a200,\n \"light-blue\": $clr-light-blue-a700,\n \"cyan\": $clr-cyan-a700,\n \"teal\": $clr-teal-a700,\n \"green\": $clr-green-a700,\n \"light-green\": $clr-light-green-a700,\n \"lime\": $clr-lime-a700,\n \"yellow\": $clr-yellow-a700,\n \"amber\": $clr-amber-a700,\n \"orange\": $clr-orange-a400,\n \"deep-orange\": $clr-deep-orange-a200\n) {\n\n // Color palette\n [data-md-color-accent=\"#{$name}\"] {\n --md-accent-fg-color: hsla(#{hex2hsl($color)}, 1);\n --md-accent-fg-color--transparent: hsla(#{hex2hsl($color)}, 0.1);\n\n // Inverted text for lighter shades\n @if index(\"lime\" \"yellow\" \"amber\" \"orange\", $name) {\n --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n } @else {\n --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n }\n }\n}\n","[data-md-color-accent=red] {\n --md-accent-fg-color: hsla(348, 100%, 55%, 1);\n --md-accent-fg-color--transparent: hsla(348, 100%, 55%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=pink] {\n --md-accent-fg-color: hsla(339, 100%, 48%, 1);\n --md-accent-fg-color--transparent: hsla(339, 100%, 48%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=purple] {\n --md-accent-fg-color: hsla(291, 96%, 62%, 1);\n --md-accent-fg-color--transparent: hsla(291, 96%, 62%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=deep-purple] {\n --md-accent-fg-color: hsla(256, 100%, 65%, 1);\n --md-accent-fg-color--transparent: hsla(256, 100%, 65%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=indigo] {\n --md-accent-fg-color: hsla(231, 99%, 66%, 1);\n --md-accent-fg-color--transparent: hsla(231, 99%, 66%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=blue] {\n --md-accent-fg-color: hsla(218, 100%, 63%, 1);\n --md-accent-fg-color--transparent: hsla(218, 100%, 63%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=light-blue] {\n --md-accent-fg-color: hsla(203, 100%, 46%, 1);\n --md-accent-fg-color--transparent: hsla(203, 100%, 46%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=cyan] {\n --md-accent-fg-color: hsla(188, 100%, 42%, 1);\n --md-accent-fg-color--transparent: hsla(188, 100%, 42%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=teal] {\n --md-accent-fg-color: hsla(172, 100%, 37%, 1);\n --md-accent-fg-color--transparent: hsla(172, 100%, 37%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=green] {\n --md-accent-fg-color: hsla(145, 100%, 39%, 1);\n --md-accent-fg-color--transparent: hsla(145, 100%, 39%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=light-green] {\n --md-accent-fg-color: hsla(97, 81%, 48%, 1);\n --md-accent-fg-color--transparent: hsla(97, 81%, 48%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=lime] {\n --md-accent-fg-color: hsla(75, 100%, 46%, 1);\n --md-accent-fg-color--transparent: hsla(75, 100%, 46%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=yellow] {\n --md-accent-fg-color: hsla(50, 100%, 50%, 1);\n --md-accent-fg-color--transparent: hsla(50, 100%, 50%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=amber] {\n --md-accent-fg-color: hsla(40, 100%, 50%, 1);\n --md-accent-fg-color--transparent: hsla(40, 100%, 50%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=orange] {\n --md-accent-fg-color: hsla(34, 100%, 50%, 1);\n --md-accent-fg-color--transparent: hsla(34, 100%, 50%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=deep-orange] {\n --md-accent-fg-color: hsla(14, 100%, 63%, 1);\n --md-accent-fg-color--transparent: hsla(14, 100%, 63%, 0.1);\n --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=red] {\n --md-primary-fg-color: hsla(1, 83%, 63%, 1);\n --md-primary-fg-color--light: hsla(0, 69%, 67%, 1);\n --md-primary-fg-color--dark: hsla(1, 77%, 55%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=pink] {\n --md-primary-fg-color: hsla(340, 82%, 52%, 1);\n --md-primary-fg-color--light: hsla(340, 82%, 59%, 1);\n --md-primary-fg-color--dark: hsla(336, 78%, 43%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=purple] {\n --md-primary-fg-color: hsla(291, 47%, 51%, 1);\n --md-primary-fg-color--light: hsla(291, 47%, 60%, 1);\n --md-primary-fg-color--dark: hsla(287, 65%, 40%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=deep-purple] {\n --md-primary-fg-color: hsla(262, 47%, 55%, 1);\n --md-primary-fg-color--light: hsla(262, 47%, 63%, 1);\n --md-primary-fg-color--dark: hsla(262, 52%, 47%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=indigo] {\n --md-primary-fg-color: hsla(231, 48%, 48%, 1);\n --md-primary-fg-color--light: hsla(231, 44%, 56%, 1);\n --md-primary-fg-color--dark: hsla(232, 54%, 41%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=blue] {\n --md-primary-fg-color: hsla(207, 90%, 54%, 1);\n --md-primary-fg-color--light: hsla(207, 90%, 61%, 1);\n --md-primary-fg-color--dark: hsla(210, 79%, 46%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=light-blue] {\n --md-primary-fg-color: hsla(199, 98%, 48%, 1);\n --md-primary-fg-color--light: hsla(199, 92%, 56%, 1);\n --md-primary-fg-color--dark: hsla(201, 98%, 41%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=cyan] {\n --md-primary-fg-color: hsla(187, 100%, 42%, 1);\n --md-primary-fg-color--light: hsla(187, 71%, 50%, 1);\n --md-primary-fg-color--dark: hsla(186, 100%, 33%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=teal] {\n --md-primary-fg-color: hsla(174, 100%, 29%, 1);\n --md-primary-fg-color--light: hsla(174, 63%, 40%, 1);\n --md-primary-fg-color--dark: hsla(173, 100%, 24%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=green] {\n --md-primary-fg-color: hsla(122, 39%, 49%, 1);\n --md-primary-fg-color--light: hsla(123, 38%, 57%, 1);\n --md-primary-fg-color--dark: hsla(123, 43%, 39%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=light-green] {\n --md-primary-fg-color: hsla(88, 50%, 53%, 1);\n --md-primary-fg-color--light: hsla(88, 50%, 60%, 1);\n --md-primary-fg-color--dark: hsla(92, 48%, 42%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=lime] {\n --md-primary-fg-color: hsla(66, 70%, 54%, 1);\n --md-primary-fg-color--light: hsla(66, 70%, 61%, 1);\n --md-primary-fg-color--dark: hsla(62, 61%, 44%, 1);\n --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=yellow] {\n --md-primary-fg-color: hsla(54, 100%, 62%, 1);\n --md-primary-fg-color--light: hsla(54, 100%, 67%, 1);\n --md-primary-fg-color--dark: hsla(43, 96%, 58%, 1);\n --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=amber] {\n --md-primary-fg-color: hsla(45, 100%, 51%, 1);\n --md-primary-fg-color--light: hsla(45, 100%, 58%, 1);\n --md-primary-fg-color--dark: hsla(38, 100%, 50%, 1);\n --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=orange] {\n --md-primary-fg-color: hsla(36, 100%, 57%, 1);\n --md-primary-fg-color--light: hsla(36, 100%, 57%, 1);\n --md-primary-fg-color--dark: hsla(33, 100%, 49%, 1);\n --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=deep-orange] {\n --md-primary-fg-color: hsla(14, 100%, 63%, 1);\n --md-primary-fg-color--light: hsla(14, 100%, 70%, 1);\n --md-primary-fg-color--dark: hsla(14, 91%, 54%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=brown] {\n --md-primary-fg-color: hsla(16, 25%, 38%, 1);\n --md-primary-fg-color--light: hsla(16, 18%, 47%, 1);\n --md-primary-fg-color--dark: hsla(14, 26%, 29%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=grey] {\n --md-primary-fg-color: hsla(0, 0%, 46%, 1);\n --md-primary-fg-color--light: hsla(0, 0%, 62%, 1);\n --md-primary-fg-color--dark: hsla(0, 0%, 38%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=blue-grey] {\n --md-primary-fg-color: hsla(199, 18%, 40%, 1);\n --md-primary-fg-color--light: hsla(200, 18%, 46%, 1);\n --md-primary-fg-color--dark: hsla(199, 18%, 33%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=white] {\n --md-primary-fg-color: hsla(0, 0%, 100%, 1);\n --md-primary-fg-color--light: hsla(0, 0%, 100%, 0.7);\n --md-primary-fg-color--dark: hsla(0, 0%, 0%, 0.07);\n --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n --md-typeset-a-color: hsla(231, 48%, 48%, 1);\n}\n@media screen and (min-width: 60em) {\n [data-md-color-primary=white] .md-search__input {\n background-color: rgba(0, 0, 0, 0.07);\n }\n [data-md-color-primary=white] .md-search__input + .md-search__icon {\n color: rgba(0, 0, 0, 0.87);\n }\n [data-md-color-primary=white] .md-search__input::placeholder {\n color: rgba(0, 0, 0, 0.54);\n }\n [data-md-color-primary=white] .md-search__input:hover {\n background-color: rgba(0, 0, 0, 0.32);\n }\n}\n@media screen and (min-width: 76.25em) {\n [data-md-color-primary=white] .md-tabs {\n border-bottom: 0.05rem solid rgba(0, 0, 0, 0.07);\n }\n}\n\n[data-md-color-primary=black] {\n --md-primary-fg-color: hsla(0, 0%, 0%, 1);\n --md-primary-fg-color--light: hsla(0, 0%, 0%, 0.54);\n --md-primary-fg-color--dark: hsla(0, 0%, 0%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n --md-typeset-a-color: hsla(231, 48%, 48%, 1);\n}\n[data-md-color-primary=black] .md-header {\n background-color: black;\n}\n@media screen and (max-width: 59.9375em) {\n [data-md-color-primary=black] .md-nav__source {\n background-color: rgba(0, 0, 0, 0.87);\n }\n}\n@media screen and (min-width: 60em) {\n [data-md-color-primary=black] .md-search__input {\n background-color: rgba(255, 255, 255, 0.12);\n }\n [data-md-color-primary=black] .md-search__input:hover {\n background-color: rgba(255, 255, 255, 0.3);\n }\n}\n@media screen and (max-width: 76.1875em) {\n html [data-md-color-primary=black] .md-nav--primary .md-nav__title[for=__drawer] {\n background-color: black;\n }\n}\n@media screen and (min-width: 76.25em) {\n [data-md-color-primary=black] .md-tabs {\n background-color: black;\n }\n}\n\n@media screen {\n [data-md-color-scheme=slate] {\n --md-hue: 232;\n --md-default-fg-color: hsla(var(--md-hue), 75%, 95%, 1);\n --md-default-fg-color--light: hsla(var(--md-hue), 75%, 90%, 0.62);\n --md-default-fg-color--lighter: hsla(var(--md-hue), 75%, 90%, 0.32);\n --md-default-fg-color--lightest: hsla(var(--md-hue), 75%, 90%, 0.12);\n --md-default-bg-color: hsla(var(--md-hue), 15%, 21%, 1);\n --md-default-bg-color--light: hsla(var(--md-hue), 15%, 21%, 0.54);\n --md-default-bg-color--lighter: hsla(var(--md-hue), 15%, 21%, 0.26);\n --md-default-bg-color--lightest: hsla(var(--md-hue), 15%, 21%, 0.07);\n --md-code-fg-color: hsla(var(--md-hue), 18%, 86%, 1);\n --md-code-bg-color: hsla(var(--md-hue), 15%, 15%, 1);\n --md-code-hl-color: hsla(218, 100%, 63%, 0.15);\n --md-code-hl-number-color: hsla(6, 74%, 63%, 1);\n --md-code-hl-special-color: hsla(340, 83%, 66%, 1);\n --md-code-hl-function-color: hsla(291, 57%, 65%, 1);\n --md-code-hl-constant-color: hsla(250, 62%, 70%, 1);\n --md-code-hl-keyword-color: hsla(219, 66%, 64%, 1);\n --md-code-hl-string-color: hsla(150, 58%, 44%, 1);\n --md-code-hl-name-color: var(--md-code-fg-color);\n --md-code-hl-operator-color: var(--md-default-fg-color--light);\n --md-code-hl-punctuation-color: var(--md-default-fg-color--light);\n --md-code-hl-comment-color: var(--md-default-fg-color--light);\n --md-code-hl-generic-color: var(--md-default-fg-color--light);\n --md-code-hl-variable-color: var(--md-default-fg-color--light);\n --md-typeset-color: var(--md-default-fg-color);\n --md-typeset-a-color: var(--md-primary-fg-color);\n --md-typeset-mark-color: hsla(218, 100%, 63%, 0.3);\n --md-typeset-kbd-color: hsla(var(--md-hue), 15%, 94%, 0.12);\n --md-typeset-kbd-accent-color: hsla(var(--md-hue), 15%, 94%, 0.2);\n --md-typeset-kbd-border-color: hsla(var(--md-hue), 15%, 14%, 1);\n --md-admonition-bg-color: hsla(var(--md-hue), 0%, 100%, 0.025);\n --md-footer-bg-color: hsla(var(--md-hue), 15%, 12%, 0.87);\n --md-footer-bg-color--dark: hsla(var(--md-hue), 15%, 10%, 1);\n }\n [data-md-color-scheme=slate][data-md-color-primary=black], [data-md-color-scheme=slate][data-md-color-primary=white] {\n --md-typeset-a-color: hsla(231, 44%, 56%, 1);\n }\n}\n\n/*# sourceMappingURL=palette.css.map */","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n@each $name, $colors in (\n \"red\": $clr-red-400 $clr-red-300 $clr-red-600,\n \"pink\": $clr-pink-500 $clr-pink-400 $clr-pink-700,\n \"purple\": $clr-purple-400 $clr-purple-300 $clr-purple-600,\n \"deep-purple\": $clr-deep-purple-400 $clr-deep-purple-300 $clr-deep-purple-500,\n \"indigo\": $clr-indigo-500 $clr-indigo-400 $clr-indigo-700,\n \"blue\": $clr-blue-500 $clr-blue-400 $clr-blue-700,\n \"light-blue\": $clr-light-blue-500 $clr-light-blue-400 $clr-light-blue-700,\n \"cyan\": $clr-cyan-500 $clr-cyan-400 $clr-cyan-700,\n \"teal\": $clr-teal-500 $clr-teal-400 $clr-teal-700,\n \"green\": $clr-green-500 $clr-green-400 $clr-green-700,\n \"light-green\": $clr-light-green-500 $clr-light-green-400 $clr-light-green-700,\n \"lime\": $clr-lime-500 $clr-lime-400 $clr-lime-700,\n \"yellow\": $clr-yellow-500 $clr-yellow-400 $clr-yellow-700,\n \"amber\": $clr-amber-500 $clr-amber-400 $clr-amber-700,\n \"orange\": $clr-orange-400 $clr-orange-400 $clr-orange-600,\n \"deep-orange\": $clr-deep-orange-400 $clr-deep-orange-300 $clr-deep-orange-600,\n \"brown\": $clr-brown-500 $clr-brown-400 $clr-brown-700,\n \"grey\": $clr-grey-600 $clr-grey-500 $clr-grey-700,\n \"blue-grey\": $clr-blue-grey-600 $clr-blue-grey-500 $clr-blue-grey-700\n) {\n\n // Color palette\n [data-md-color-primary=\"#{$name}\"] {\n --md-primary-fg-color: hsla(#{hex2hsl(nth($colors, 1))}, 1);\n --md-primary-fg-color--light: hsla(#{hex2hsl(nth($colors, 2))}, 1);\n --md-primary-fg-color--dark: hsla(#{hex2hsl(nth($colors, 3))}, 1);\n\n // Inverted text for lighter shades\n @if index(\"lime\" \"yellow\" \"amber\" \"orange\", $name) {\n --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n } @else {\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n }\n }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: white\n// ----------------------------------------------------------------------------\n\n// Color palette\n[data-md-color-primary=\"white\"] {\n --md-primary-fg-color: hsla(0, 0%, 100%, 1);\n --md-primary-fg-color--light: hsla(0, 0%, 100%, 0.7);\n --md-primary-fg-color--dark: hsla(0, 0%, 0%, 0.07);\n --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n\n // Typeset color shades\n --md-typeset-a-color: hsla(#{hex2hsl($clr-indigo-500)}, 1);\n\n // [tablet portrait +]: Header-embedded search\n @include break-from-device(tablet landscape) {\n\n // Search input\n .md-search__input {\n background-color: hsla(0, 0%, 0%, 0.07);\n\n // Search icon color\n + .md-search__icon {\n color: hsla(0, 0%, 0%, 0.87);\n }\n\n // Placeholder color\n &::placeholder {\n color: hsla(0, 0%, 0%, 0.54);\n }\n\n // Search input on hover\n &:hover {\n background-color: hsla(0, 0%, 0%, 0.32);\n }\n }\n }\n\n // [screen +]: Add bottom border for tabs\n @include break-from-device(screen) {\n\n // Navigation tabs\n .md-tabs {\n border-bottom: px2rem(1px) solid hsla(0, 0%, 0%, 0.07);\n }\n }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: black\n// ----------------------------------------------------------------------------\n\n// Color palette\n[data-md-color-primary=\"black\"] {\n --md-primary-fg-color: hsla(0, 0%, 0%, 1);\n --md-primary-fg-color--light: hsla(0, 0%, 0%, 0.54);\n --md-primary-fg-color--dark: hsla(0, 0%, 0%, 1);\n --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n\n // Text color shades\n --md-typeset-a-color: hsla(#{hex2hsl($clr-indigo-500)}, 1);\n\n // Header\n .md-header {\n background-color: hsla(0, 0%, 0%, 1);\n }\n\n // [tablet portrait -]: Layered navigation\n @include break-to-device(tablet portrait) {\n\n // Repository information container\n .md-nav__source {\n background-color: hsla(0, 0%, 0%, 0.87);\n }\n }\n\n // [tablet landscape +]: Header-embedded search\n @include break-from-device(tablet landscape) {\n\n // Search input\n .md-search__input {\n background-color: hsla(0, 0%, 100%, 0.12);\n\n // Search form on hover\n &:hover {\n background-color: hsla(0, 0%, 100%, 0.3);\n }\n }\n }\n\n // [tablet -]: Layered navigation\n @include break-to-device(tablet) {\n\n // Site title in main navigation\n html & .md-nav--primary .md-nav__title[for=\"__drawer\"] {\n background-color: hsla(0, 0%, 0%, 1);\n }\n }\n\n // [screen +]: Set background color for tabs\n @include break-from-device(screen) {\n\n // Navigation tabs\n .md-tabs {\n background-color: hsla(0, 0%, 0%, 1);\n }\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Variables\n// ----------------------------------------------------------------------------\n\n///\n/// Device-specific breakpoints\n///\n/// @example\n/// $break-devices: (\n/// mobile: (\n/// portrait: 220px 479px,\n/// landscape: 480px 719px\n/// ),\n/// tablet: (\n/// portrait: 720px 959px,\n/// landscape: 960px 1219px\n/// ),\n/// screen: (\n/// small: 1220px 1599px,\n/// medium: 1600px 1999px,\n/// large: 2000px\n/// )\n/// );\n///\n$break-devices: () !default;\n\n// ----------------------------------------------------------------------------\n// Helpers\n// ----------------------------------------------------------------------------\n\n///\n/// Choose minimum and maximum device widths\n///\n@function break-select-min-max($devices) {\n $min: 1000000;\n $max: 0;\n @each $key, $value in $devices {\n @while type-of($value) == map {\n $value: break-select-min-max($value);\n }\n @if type-of($value) == list {\n @each $number in $value {\n @if type-of($number) == number {\n $min: min($number, $min);\n @if $max {\n $max: max($number, $max);\n }\n } @else {\n @error \"Invalid number: #{$number}\";\n }\n }\n } @else if type-of($value) == number {\n $min: min($value, $min);\n $max: null;\n } @else {\n @error \"Invalid value: #{$value}\";\n }\n }\n @return $min, $max;\n}\n\n///\n/// Select minimum and maximum widths for a device breakpoint\n///\n@function break-select-device($device) {\n $current: $break-devices;\n @for $n from 1 through length($device) {\n @if type-of($current) == map {\n $current: map-get($current, nth($device, $n));\n } @else {\n @error \"Invalid device map: #{$devices}\";\n }\n }\n @if type-of($current) == list or type-of($current) == number {\n $current: (default: $current);\n }\n @return break-select-min-max($current);\n}\n\n// ----------------------------------------------------------------------------\n// Mixins\n// ----------------------------------------------------------------------------\n\n///\n/// A minimum-maximum media query breakpoint\n///\n@mixin break-at($breakpoint) {\n @if type-of($breakpoint) == number {\n @media screen and (min-width: $breakpoint) {\n @content;\n }\n } @else if type-of($breakpoint) == list {\n $min: nth($breakpoint, 1);\n $max: nth($breakpoint, 2);\n @if type-of($min) == number and type-of($max) == number {\n @media screen and (min-width: $min) and (max-width: $max) {\n @content;\n }\n } @else {\n @error \"Invalid breakpoint: #{$breakpoint}\";\n }\n } @else {\n @error \"Invalid breakpoint: #{$breakpoint}\";\n }\n}\n\n///\n/// An orientation media query breakpoint\n///\n@mixin break-at-orientation($breakpoint) {\n @if type-of($breakpoint) == string {\n @media screen and (orientation: $breakpoint) {\n @content;\n }\n } @else {\n @error \"Invalid breakpoint: #{$breakpoint}\";\n }\n}\n\n///\n/// A maximum-aspect-ratio media query breakpoint\n///\n@mixin break-at-ratio($breakpoint) {\n @if type-of($breakpoint) == number {\n @media screen and (max-aspect-ratio: $breakpoint) {\n @content;\n }\n } @else {\n @error \"Invalid breakpoint: #{$breakpoint}\";\n }\n}\n\n///\n/// A minimum-maximum media query device breakpoint\n///\n@mixin break-at-device($device) {\n @if type-of($device) == string {\n $device: $device,;\n }\n @if type-of($device) == list {\n $breakpoint: break-select-device($device);\n @if nth($breakpoint, 2) {\n $min: nth($breakpoint, 1);\n $max: nth($breakpoint, 2);\n\n @media screen and (min-width: $min) and (max-width: $max) {\n @content;\n }\n } @else {\n @error \"Invalid device: #{$device}\";\n }\n } @else {\n @error \"Invalid device: #{$device}\";\n }\n}\n\n///\n/// A minimum media query device breakpoint\n///\n@mixin break-from-device($device) {\n @if type-of($device) == string {\n $device: $device,;\n }\n @if type-of($device) == list {\n $breakpoint: break-select-device($device);\n $min: nth($breakpoint, 1);\n\n @media screen and (min-width: $min) {\n @content;\n }\n } @else {\n @error \"Invalid device: #{$device}\";\n }\n}\n\n///\n/// A maximum media query device breakpoint\n///\n@mixin break-to-device($device) {\n @if type-of($device) == string {\n $device: $device,;\n }\n @if type-of($device) == list {\n $breakpoint: break-select-device($device);\n $max: nth($breakpoint, 2);\n\n @media screen and (max-width: $max) {\n @content;\n }\n } @else {\n @error \"Invalid device: #{$device}\";\n }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath \n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Only use dark mode on screens\n@media screen {\n\n // Slate theme, i.e. dark mode\n [data-md-color-scheme=\"slate\"] {\n\n // Slate's hue in the range [0,360] - change this variable to alter the tone\n // of the theme, e.g. to make it more redish or greenish. This is a slate-\n // specific variable, but the same approach may be adapted to custom themes.\n --md-hue: 232;\n\n // Default color shades\n --md-default-fg-color: hsla(var(--md-hue), 75%, 95%, 1);\n --md-default-fg-color--light: hsla(var(--md-hue), 75%, 90%, 0.62);\n --md-default-fg-color--lighter: hsla(var(--md-hue), 75%, 90%, 0.32);\n --md-default-fg-color--lightest: hsla(var(--md-hue), 75%, 90%, 0.12);\n --md-default-bg-color: hsla(var(--md-hue), 15%, 21%, 1);\n --md-default-bg-color--light: hsla(var(--md-hue), 15%, 21%, 0.54);\n --md-default-bg-color--lighter: hsla(var(--md-hue), 15%, 21%, 0.26);\n --md-default-bg-color--lightest: hsla(var(--md-hue), 15%, 21%, 0.07);\n\n // Code color shades\n --md-code-fg-color: hsla(var(--md-hue), 18%, 86%, 1);\n --md-code-bg-color: hsla(var(--md-hue), 15%, 15%, 1);\n\n // Code highlighting color shades\n --md-code-hl-color: hsla(#{hex2hsl($clr-blue-a200)}, 0.15);\n --md-code-hl-number-color: hsla(6, 74%, 63%, 1);\n --md-code-hl-special-color: hsla(340, 83%, 66%, 1);\n --md-code-hl-function-color: hsla(291, 57%, 65%, 1);\n --md-code-hl-constant-color: hsla(250, 62%, 70%, 1);\n --md-code-hl-keyword-color: hsla(219, 66%, 64%, 1);\n --md-code-hl-string-color: hsla(150, 58%, 44%, 1);\n --md-code-hl-name-color: var(--md-code-fg-color);\n --md-code-hl-operator-color: var(--md-default-fg-color--light);\n --md-code-hl-punctuation-color: var(--md-default-fg-color--light);\n --md-code-hl-comment-color: var(--md-default-fg-color--light);\n --md-code-hl-generic-color: var(--md-default-fg-color--light);\n --md-code-hl-variable-color: var(--md-default-fg-color--light);\n\n // Typeset color shades\n --md-typeset-color: var(--md-default-fg-color);\n --md-typeset-a-color: var(--md-primary-fg-color);\n\n // Typeset `mark` color shades\n --md-typeset-mark-color: hsla(#{hex2hsl($clr-blue-a200)}, 0.3);\n\n // Typeset `kbd` color shades\n --md-typeset-kbd-color: hsla(var(--md-hue), 15%, 94%, 0.12);\n --md-typeset-kbd-accent-color: hsla(var(--md-hue), 15%, 94%, 0.2);\n --md-typeset-kbd-border-color: hsla(var(--md-hue), 15%, 14%, 1);\n\n // Admonition color shades\n --md-admonition-bg-color: hsla(var(--md-hue), 0%, 100%, 0.025);\n\n // Footer color shades\n --md-footer-bg-color: hsla(var(--md-hue), 15%, 12%, 0.87);\n --md-footer-bg-color--dark: hsla(var(--md-hue), 15%, 10%, 1);\n\n // Black and white primary colors\n &[data-md-color-primary=\"black\"],\n &[data-md-color-primary=\"white\"] {\n\n // Typeset color shades\n --md-typeset-a-color: hsla(#{hex2hsl($clr-indigo-400)}, 1);\n }\n }\n}\n"]} \ No newline at end of file diff --git a/b_n_r_backup/index.html b/b_n_r_backup/index.html new file mode 100644 index 000000000..dd2a52ce2 --- /dev/null +++ b/b_n_r_backup/index.html @@ -0,0 +1,2013 @@ + + + + + + + + + + + + + + + + + + + Backup - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Backup

    + +

    Backup

    +

    You can use the provided script helper-scripts/backup_and_restore.sh to backup mailcow automatically.

    +

    Please do not copy this script to another location.

    +

    To run a backup, write "backup" as first parameter and either one or more components to backup as following parameters. +You can also use "all" as second parameter to backup all components. Append --delete-days n to delete backups older than n days.

    +
    # Syntax:
    +# ./helper-scripts/backup_and_restore.sh backup (vmail|crypt|redis|rspamd|postfix|mysql|all|--delete-days)
    +
    +# Backup all, delete backups older than 3 days
    +./helper-scripts/backup_and_restore.sh backup all --delete-days 3
    +
    +# Backup vmail, crypt and mysql data, delete backups older than 30 days
    +./helper-scripts/backup_and_restore.sh backup vmail crypt mysql --delete-days 30
    +
    +# Backup vmail
    +./helper-scripts/backup_and_restore.sh backup vmail
    +
    +

    The script will ask you for a backup location. Inside of this location it will create folders in the format "mailcow_DATE". +You should not rename those folders to not break the restore process.

    +

    To run a backup unattended, define MAILCOW_BACKUP_LOCATION as environment variable before starting the script:

    +
    MAILCOW_BACKUP_LOCATION=/opt/backup /opt/mailcow-dockerized/helper-scripts/backup_and_restore.sh backup all
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/b_n_r_restore/index.html b/b_n_r_restore/index.html new file mode 100644 index 000000000..2974f77be --- /dev/null +++ b/b_n_r_restore/index.html @@ -0,0 +1,1998 @@ + + + + + + + + + + + + + + + + + + + Restore - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Restore

    + +

    Restore

    +

    Please do not copy this script to another location.

    +

    To run a restore, start mailcow, use the script with "restore" as first parameter.

    +
    # Syntax:
    +# ./helper-scripts/backup_and_restore.sh restore
    +
    +

    The script will ask you for a backup location containing the mailcow_DATE folders.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/client/client-android/index.html b/client/client-android/index.html new file mode 100644 index 000000000..f91faaece --- /dev/null +++ b/client/client-android/index.html @@ -0,0 +1,1935 @@ + + + + + + + + + + + + + + + + + + + Android - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Android

    + +
      +
    1. Open the Email app.
    2. +
    3. If this is your first email account, tap Add Account; if not, tap More and Settings and then Add account.
    4. +
    5. Select Microsoft Exchange ActiveSync.
    6. +
    7. Enter your email address () and password.
    8. +
    9. Tap Sign in.
    10. +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/client/client-apple/index.html b/client/client-apple/index.html new file mode 100644 index 000000000..d8b428b0b --- /dev/null +++ b/client/client-apple/index.html @@ -0,0 +1,2055 @@ + + + + + + + + + + + + + + + + + + + Apple macOS / iOS - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + +

    Apple macOS / iOS

    + +

    Method 1 via Mobileconfig

    +

    Email, contacts and calendars can be configured automatically on Apple devices by installing a profile. To download a profile you must login to the mailcow UI first.

    +

    Method 1.1: IMAP, SMTP and Cal/CardDAV

    +

    This method configures IMAP, CardDAV and CalDAV.

    +
      +
    1. Download and open the file from https://${MAILCOW_HOSTNAME}/mobileconfig.phpmailcow.mobileconfig.
    2. +
    3. Enter the unlock code (iPhone) or computer password (Mac).
    4. +
    5. Enter your email password three times when prompted.
    6. +
    +

    Method 1.2: IMAP, SMTP (no DAV)

    +

    This method configures IMAP and SMTP only.

    +
      +
    1. Download and open the file from https://${MAILCOW_HOSTNAME}/mobileconfig.php?only_emailmailcow.mobileconfig.
    2. +
    3. Enter the unlock code (iPhone) or computer password (Mac).
    4. +
    5. Enter your email password when prompted.
    6. +
    +

    Method 2 (Exchange ActiveSync emulation)

    +

    On iOS, Exchange ActiveSync is also supported as an alternative to the procedure above. It has the advantage of supporting push email (i.e. you are immediately notified of incoming messages), but has some limitations, e.g. it does not support more than three email addresses per contact in your address book. Follow the steps below if you decide to use Exchange instead.

    +
      +
    1. Open the Settings app, tap Mail, tap Accounts, tap Add Acccount, select Exchange.
    2. +
    3. Enter your email address () and tap Next.
    4. +
    5. Enter your password, tap Next again.
    6. +
    7. Finally, tap Save.
    8. +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/client/client-emclient/index.html b/client/client-emclient/index.html new file mode 100644 index 000000000..102598a58 --- /dev/null +++ b/client/client-emclient/index.html @@ -0,0 +1,1937 @@ + + + + + + + + + + + + + + + + + + + eM Client - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    eM Client

    + +
      +
    1. Launch eM Client.
    2. +
    3. If this is the first time you launched eM Client, it asks you to set up your account. Proceed to step 4.
    4. +
    5. Go to Menu at the top, select Tools and Accounts.
    6. +
    7. Enter your email address () and click Start Now.
    8. +
    9. Enter your password and click Continue.
    10. +
    11. Enter your name () and click Next.
    12. +
    13. Click Finish.
    14. +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/client/client-gajim_xmpp_client/index.html b/client/client-gajim_xmpp_client/index.html new file mode 100644 index 000000000..7944d6096 --- /dev/null +++ b/client/client-gajim_xmpp_client/index.html @@ -0,0 +1,1950 @@ + + + + + + + + + + + + + + + + + + + Gajim XMPP client - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    WIP

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/client/client-kontact/index.html b/client/client-kontact/index.html new file mode 100644 index 000000000..8aef38e23 --- /dev/null +++ b/client/client-kontact/index.html @@ -0,0 +1,1945 @@ + + + + + + + + + + + + + + + + + + + KDE Kontact - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    KDE Kontact

    + +
      +
    1. Launch Kontact.
    2. +
    3. If this is the first time you launched Kontact or KMail, it asks you to set up your account. Proceed to step 4.
    4. +
    5. Go to Mail in the sidebar. Go to the Tools menu and select Account Wizard.
    6. +
    7. Enter your name (), email address () and your password. Click Next.
    8. +
    9. Click Create Account. If prompted, re-enter your password and click OK.
    10. +
    11. Close the window by clicking Finish.
    12. +
    13. Go to Calendar in the sidebar.
    14. +
    15. Go to the Settings menu and select Configure KOrganizer.
    16. +
    17. Go to the Calendars tab and click the Add button.
    18. +
    19. Choose DAV groupware resource and click OK.
    20. +
    21. Enter your email address () and your password. Click Next.
    22. +
    23. Select ScalableOGo from the dropdown menu and click Next.
    24. +
    25. Enter your mailcow hostname into the Host field and click Next.
    26. +
    27. Click Test Connection and then Finish. Finally, click OK twice.
    28. +
    +

    Once you have set up Kontact, you can also use KMail, KOrganizer and KAddressBook individually.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/client/client-manual/index.html b/client/client-manual/index.html new file mode 100644 index 000000000..d90bad6e2 --- /dev/null +++ b/client/client-manual/index.html @@ -0,0 +1,2061 @@ + + + + + + + + + + + + + + + + + + + Manual configuration - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Manual configuration

    + +

    These instructions are valid for unchanged port bindings only!

    +

    Email

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ServiceEncryptionHostPort
    IMAPSTARTTLS mailcow hostname143
    IMAPSSSL mailcow hostname993
    POP3STARTTLS mailcow hostname110
    POP3SSSL mailcow hostname995
    SMTPSTARTTLS mailcow hostname587
    SMTPSSSL mailcow hostname465
    +

    Please use "plain" as authentication mechanisms. Contrary to the assumption no passwords will be transferred plain text, as no authentication is allowed to take place without TLS.

    +

    Contacts and calendars

    +

    SOGos default calendar (CalDAV) and contacts (CardDAV) URLs:

    +
      +
    1. CalDAV - https://mail.example.com/SOGo/dav/user@example.com/Calendar/personal/
    2. +
    3. CardDAV - https://mail.example.com/SOGo/dav/user@example.com/Contacts/personal/
    4. +
    +

    Some applications may require you to use https://mail.example.com/SOGo/dav/ or the full path to your calendar, which can be found and copied from within SOGo.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/client/client-outlook/index.html b/client/client-outlook/index.html new file mode 100644 index 000000000..39b536bf5 --- /dev/null +++ b/client/client-outlook/index.html @@ -0,0 +1,2087 @@ + + + + + + + + + + + + + + + + + + + Microsoft Outlook - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + +

    Microsoft Outlook

    + +
    +

    Outlook 2016 or higher from Office 365 on Windows

    +
    +

    This is only applicable if your server administrator has not disabled EAS for Outlook. If it is disabled, please follow the guide for Outlook 2007 instead.

    +
    +

    Outlook 2016 has an issue with autodiscover. Only Outlook from Office 365 is affected. If you installed Outlook from another source, please follow the guide for Outlook 2013 or higher.

    +

    For EAS you must use the old assistant by launching C:\Program Files (x86)\Microsoft Office\root\Office16\OLCFG.EXE. If this application opens, you can go to step 4 of the guide for Outlook 2013 below.

    +

    If it does not open, you can completely disable the new account creation wizard and follow the guide for Outlook 2013 below.

    +

    Outlook 2013 or higher on Windows

    +
    +

    This is only applicable if your server administrator has not disabled EAS for Outlook. If it is disabled, please follow the guide for Outlook 2007 instead.

    +
    +
      +
    1. Launch Outlook.
    2. +
    3. If this is the first time you launched Outlook, it asks you to set up your account. Proceed to step 4.
    4. +
    5. Go to the File menu and click Add Account.
    6. +
    7. Enter your name (), email address () and your password. Click Next.
    8. +
    9. When prompted, enter your password again, check Remember my credentials and click OK.
    10. +
    11. Click the Allow button.
    12. +
    13. Click Finish.
    14. +
    +

    Outlook 2007 or 2010 on Windows

    +
    +
    +

    Outlook 2007 or higher on Windows

    +
    +
      +
    1. Download and install Outlook CalDav Synchronizer.
    2. +
    3. Launch Outlook.
    4. +
    5. If this is the first time you launched Outlook, it asks you to set up your account. Proceed to step 5.
    6. +
    7. Go to the File menu and click Add Account.
    8. +
    9. Enter your name (), email address () and your password. Click Next.
    10. +
    11. Click Finish.
    12. +
    13. Go to the CalDav Synchronizer ribbon and click Synchronization Profiles.
    14. +
    15. Click the second button at top (Add multiple profiles), select Sogo, click Ok.
    16. +
    17. Click the Get IMAP/POP3 account settings button.
    18. +
    19. Click Discover resources and assign to Outlook folders.
    20. +
    21. In the Select Resource window that pops up, select your main calendar (usually Personal Calendar), click the ... button, assign it to Calendar, and click OK. Go to the Address Books and Tasks tabs and repeat repeat the process accordingly. Do not assign multiple calendars, address books or task lists!
    22. +
    23. Close all windows with the OK buttons.
    24. +
    +

    Outlook 2011 or higher on macOS

    +

    The Mac version of Outlook does not synchronize calendars and contacts and therefore is not supported.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/client/client-thunderbird/index.html b/client/client-thunderbird/index.html new file mode 100644 index 000000000..d34028910 --- /dev/null +++ b/client/client-thunderbird/index.html @@ -0,0 +1,2073 @@ + + + + + + + + + + + + + + + + + + + Mozilla Thunderbird - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + +

    Mozilla Thunderbird

    + +
      +
    1. + Launch Thunderbird. +
    2. +
    3. + If this is the first time you launched Thunderbird, it asks you whether you would like a new email address. Click Skip this and use my existing email and proceed to step 4. +
    4. +
    5. + Go to the Tools menu and select Account Settings. +
    6. +
    7. + Click the Account Actions dropdown menu at the bottom left and select Add Mail Account. +
    8. +
    9. + Enter your name (), email address () and your password. Make sure the Remember password checkbox is selected and click Continue. +
    10. +
    11. + Once the configuration has been automatically detected, click Done. +
    12. +
    13. + If you already had other accounts configured in Thunderbird, select the new one () on the left, click the Account Actions dropdown and select Set as Default. +
    14. +
    15. + Close the account settings window with the OK button. +
    16. +
    17. + In your web browser, download SOGo ConnectorSOGo Connector. +
    18. +
    19. + Back in Thunderbird, go to the Tools menu and select Add-ons. +
    20. +
    21. + Click Extensions on the left and ensure that the Lightning add-on is already installed. It is installed by default in the Windows and macOS versions of Thunderbird, but if you are running Linux and installed Thunderbird through your distribution's package manager, Lightning might be available as a separate package (e.g. xul-ext-lightning on Ubuntu). +
    22. +
    23. + Click Extensions on the left, click the little gear icon at the top and select Install Add-on From File. Select the file you downloaded in step 9, click Open and, after waiting for a few seconds, Install Now. +
    24. +
    25. + Click the Restart Now button at the top that appears. +
    26. +
    27. + Thunderbird briefly shows a message that it is updating extensions, then restarts automatically once more. +
    28. +
    29. + When you are prompted to authenticate for , enter your email address and password, check Use Password Manager and click OK. +
    30. +
    + +
    +

    Automatic configuration of calendars and address books in Thunderbird is not currently supported. + You can ask your server administrator to enable SOGo Connector if you need it.

    +
    +
    +

    Automatic configuration of calendars and address books (from step 9 onward) in Thunderbird is only supported if your server administrator has enabled SOGo Connector.

    +
    +

    Different method of connecting Cal-/CardDAV in Thunderbird with automatic detection of address books and calendars

    +

    Instead of using SOGo Connector you can use a combination of

    + +

    TbSync

    +
      +
    1. You can add new accounts via the CalDAV & CardDAV provider:
    2. +
    +

    TbSync - CalDAV & CardDAV Provider

    +
      +
    1. +

      Choose "Automatic Configuration". Use your mail address as account and username. Use your mail password as DAV password. The server URL is your MAILCOW_HOSTNAME (specifying any protocol is not necessary, just enter the full domain).

      +
    2. +
    3. +

      Now tick the checkbox for "Enable and synchronize this account" in the synchronization status tab:

      +
    4. +
    +

    TbSync - Enable

    +
      +
    1. Several available resources should appear in the same window area now. Tick all checkboxes of the resources (address books and calendars) that you want to sync. Choose a synchronization period (in minutes) in the same window area before clicking on "Synchronize now". If you leave the sync at "0" it will only sync manually so choose at least 30 minutes for periodic synchronization.
    2. +
    +

    If you want to manually synchronize you can find this option under "Account actions" - the dropdown-menu where you added the Cal-/CardDAV account (step 2).

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/client/client-windows/index.html b/client/client-windows/index.html new file mode 100644 index 000000000..2c320086d --- /dev/null +++ b/client/client-windows/index.html @@ -0,0 +1,1938 @@ + + + + + + + + + + + + + + + + + + + Windows Mail - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Windows Mail

    + +

    Windows 8 and higher support email, contacts and calendar via Exchange ActiveSync.

    +
      +
    1. Open the Mail app.
    2. +
    3. If you have not previously used Mail, you can click Add Account in the main window. Proceed to step 4.
    4. +
    5. Click Accounts in the sidebar on the left, then click Add Account on the far right.
    6. +
    7. Select Exchange.
    8. +
    9. Enter your email address () and click Next.
    10. +
    11. Enter your password and click Log in.
    12. +
    +

    Once you have set up the Mail app, you can also use the People and Calendar apps.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/client/client-windowsphone/index.html b/client/client-windowsphone/index.html new file mode 100644 index 000000000..91088ad9c --- /dev/null +++ b/client/client-windowsphone/index.html @@ -0,0 +1,1934 @@ + + + + + + + + + + + + + + + + + + + Windows Phone - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Windows Phone

    + +
      +
    1. Open the Settings app. Select email + accounts and tap add an account.
    2. +
    3. Tap Exchange.
    4. +
    5. Enter your email address () and your password. Tap Sign in.
    6. +
    7. Tap done.
    8. +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/client/index.html b/client/index.html new file mode 100644 index 000000000..9f58c6440 --- /dev/null +++ b/client/index.html @@ -0,0 +1,1953 @@ + + + + + + + + + + + + + + + + + + + Overview - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Overview

    + +

    mailcow supports a variety of email clients, both on desktop computers and on smartphones. +Below, you can find a number of configuration guides that explain how to configure your mailcow account.

    +
    +
    +

    Tip

    +If you access this page by logging into your mailcow server and clicking the "Show configuration guides for email clients and smartphones" link, all of the guides will be personalized with your email address and server name. +
    +
    +
    +
    +

    Success

    +Since you accessed this page after logging into your mailcow server, all of the guides have been personalized with your email address and server name. +
    +
    + + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/clients.js b/clients.js new file mode 100644 index 000000000..cc6cf1257 --- /dev/null +++ b/clients.js @@ -0,0 +1,123 @@ +if (window.location.href.indexOf('/client/') >= 0) { + window.window.addEventListener('load', function () { + function setCookie(name, value) { + document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + "; path=/"; + } + + function getParameterByName(name) { + var match = RegExp('[?#&]' + name + '=([^&]*)').exec(window.location.hash); + return match && decodeURIComponent(match[1].replace(/\+/g, ' ')); + } + + /* Store URL variables in cookies */ + if (getParameterByName('host')) { + setCookie("host", getParameterByName('host')); + } + if (getParameterByName('email')) { + var email = getParameterByName('email'); + setCookie("email", email); + setCookie("domain", email.substring(email.indexOf('@') + 1)); + } + if (getParameterByName('name')) { + setCookie("name", getParameterByName('name')); + } + if (getParameterByName('ui')) { + setCookie("ui", getParameterByName('ui')); + } + if (getParameterByName('port')) { + setCookie("port", getParameterByName('port')); + } + if (getParameterByName('connector')) { + setCookie("connector", getParameterByName('connector')); + } + if (getParameterByName('outlookEAS')) { + setCookie("outlookEAS", getParameterByName('outlookEAS')); + } + }); +} + +if (window.location.href.indexOf('/client') >= 0) { + window.window.addEventListener('load', function () { + function getCookie(cn) { + var fixedcn = encodeURIComponent(cn); + var cs = document.cookie.split(';'); + for (var i = 0; i < cs.length; i++) { + var c = cs[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1); + } + if (c.indexOf(fixedcn + "=") == 0) { + return decodeURIComponent(c.substring(cn.length + 1, c.length)); + } + } + return ""; + } + + /* Hide variable fields if no values are available */ + if (!getCookie('host')) { + Array.prototype.forEach.call(document.getElementsByClassName('client_variables_available'), function(el) { + el.style.display = 'none'; + }); + } else { + Array.prototype.forEach.call(document.getElementsByClassName('client_variables_unavailable'), function(el) { + el.style.display = 'none'; + }); + } + + /* Hide the TOC, which might contain hidden content */ + Array.prototype.forEach.call(document.getElementsByClassName('md-sidebar--secondary'), function(el) { + el.style.display = 'none'; + }); + + /* Substitute variables */ + Array.prototype.forEach.call(document.getElementsByClassName('client_var_host'), function(el) { + el.innerText = getCookie('host'); + }); + Array.prototype.forEach.call(document.getElementsByClassName('client_var_link'), function(el) { + if (!getCookie('ui') && !getCookie('host')) { + el.href = '#'; + } else { + var ui_domain = getCookie('ui') ? getCookie('ui') : getCookie('host'); + if (getCookie('port') != '443') { + el.href = 'https://' + ui_domain + ':' + getCookie('port') + '/' + el.getAttribute("href"); + } else { + el.href = 'https://' + ui_domain + '/' + el.getAttribute("href"); + } + } + }); + Array.prototype.forEach.call(document.getElementsByClassName('client_var_email'), function(el) { + el.innerText = getCookie('email'); + }); + Array.prototype.forEach.call(document.getElementsByClassName('client_var_name'), function(el) { + el.innerText = getCookie('name'); + }); + if (getCookie('port') != '443') { + Array.prototype.forEach.call(document.getElementsByClassName('client_var_port'), function(el) { + el.innerText = ':' + getCookie('port'); + }); + } + + /* Hide those sections that are not applicable because useOutlookForEAS is disabled or SOGo Connector is not available */ + if (getCookie('connector')) { + Array.prototype.forEach.call(document.getElementsByClassName('client_var_connector_link'), function(el) { + el.href = el.href.replace('__DOMAIN__', getCookie('domain')).replace('__VERSION__', getCookie('connector')); + }); + Array.prototype.forEach.call(document.getElementsByClassName('client_connector_disabled'), function(el) { + el.style.display = 'none'; + }); + } else if (getCookie('host')) { + Array.prototype.forEach.call(document.getElementsByClassName('client_connector_enabled'), function(el) { + el.style.display = 'none'; + }); + } + if (getCookie('outlookEAS') || !getCookie('host')) { + Array.prototype.forEach.call(document.getElementsByClassName('client_outlookEAS_disabled'), function(el) { + el.style.display = 'none'; + }); + } else { + Array.prototype.forEach.call(document.getElementsByClassName('client_outlookEAS_enabled'), function(el) { + el.style.display = 'none'; + }); + } + }); +} diff --git a/debug-admin_login_sogo/index.html b/debug-admin_login_sogo/index.html new file mode 100644 index 000000000..9423ac613 --- /dev/null +++ b/debug-admin_login_sogo/index.html @@ -0,0 +1,2053 @@ + + + + + + + + + + + + + + + + + + + Admin login to SOGo - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Admin login to SOGo

    + +

    This is an experimental feature that allows admins and domain admins to directly +log into SOGo as a mailbox user, without knowing the users password.

    +

    For this, an additional link to SOGo is displayed in the mailbox list.

    +

    Multiple concurrent admin-logins to different mailboxes are also possible when using this feature.

    +

    Enabling the feature

    +

    The feature is disabled by default. It can be enabled in the mailcow.conf by setting: +

    ALLOW_ADMIN_EMAIL_LOGIN=y
    +
    +and recreating the affected containers with +
    docker-compose up -d
    +

    +

    Drawbacks when enabled

    +
      +
    • Each SOGo page-load and each Active-Sync request will cause an additional execution of an internal PHP script. +This might impact load-times of SOGo / EAS. +In most cases, this should not be noticeable but should be kept in mind if you face any performance issues.
    • +
    • SOGo will not display a logout link for admin-logins, to login normally one has to logout from the mailcow UI so the PHP session is destroyed.
    • +
    +

    Technical details

    +

    SOGoTrustProxyAuthentication option is set to YES which makes SOGo trust the x-webobjects-remote-user header.

    +

    Dovecot will receive a random master-password which is valid for all mailboxes when used by the SOGo container.

    +

    Clicking on the SOGo button in the mailbox list will open sogo-auth.php which checks permissions, sets session variables and redirects to the SOGo mailbox.

    +

    Each SOGo, CardDAV, CalDAV and EAS http request will cause an additional, nginx internal auth_request call to sogo-auth.php with the following behavior:

    +
      +
    • +

      If a basic_auth header is present, the script will validate the credentials in place of SOGo and provide the following headers: +x-webobjects-remote-user, Authorization and x-webobjects-auth-type.

      +
    • +
    • +

      If no basic_auth header is present, the script will check for an active mailcow admin session for the requested email user and provide the same headers but with the dovecot master password used in the Authorization header.

      +
    • +
    • +

      If both fails the headers will be set empty, which makes SOGo use its standard authentication methods.

      +
    • +
    +

    All of these options / behaviors are disabled if the ALLOW_ADMIN_EMAIL_LOGIN is not enabled in the config.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/debug-attach_service/index.html b/debug-attach_service/index.html new file mode 100644 index 000000000..995f8ea4c --- /dev/null +++ b/debug-attach_service/index.html @@ -0,0 +1,2158 @@ + + + + + + + + + + + + + + + + + + + Attach a Container - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Attach a Container

    + +

    Attaching a Container to your Shell

    +

    To attach a container to your shell you can simply run

    +
    docker-compose exec $Service_Name /bin/bash
    +
    +

    Connecting to Services

    +

    If you want to connect to a service / application directly it is always a good idea to source mailcow.conf to get all relevant variables into your environment.

    +

    MySQL

    +
    source mailcow.conf
    +docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME}
    +
    +

    Redis

    +
    docker-compose exec redis-mailcow redis-cli
    +
    +

    Service Descriptions

    +

    Here is a brief overview of what container / service does what:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Service NameService Descriptions
    unbound-mailcowLocal (DNSSEC) DNS Resolver
    mysql-mailcowStores SOGo's and most of mailcow's settings
    postfix-mailcowReceives and sends mails
    dovecot-mailcowUser logins and sieve filter
    redis-mailcowStorage back-end for DKIM keys and Rspamd
    rspamd-mailcowMail filtering system. Used for av handling, dkim signing, spam handling
    clamd-mailcowScans attachments for viruses
    olefy-mailcowScans attached office documents for macro-viruses
    solr-mailcowProvides full-text search in Dovecot
    sogo-mailcowWebmail client that handles Microsoft ActiveSync and Cal- / CardDav
    nginx-mailcowNginx remote proxy that handles all mailcow related HTTP / HTTPS requests
    acme-mailcowAutomates HTTPS (SSL/TLS) certificate deployment
    memcached-mailcowInternal caching system for mailcow services
    watchdog-mailcowAllows the monitoring of docker containers / services
    php-fpm-mailcowPowers the mailcow web UI
    netfilter-mailcowFail2Ban like integration
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/debug-common_problems/index.html b/debug-common_problems/index.html new file mode 100644 index 000000000..0142e6df7 --- /dev/null +++ b/debug-common_problems/index.html @@ -0,0 +1,2139 @@ + + + + + + + + + + + + + + + + + + + Common Problems - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + +

    Common Problems

    + +

    Here we list common problems and possible solutions:

    +

    Mail loops back to myself

    +

    Please check in your mailcow UI if you made the domain a backup MX:

    +

    Check your MX Backup settings

    +

    I can receive but not send mails

    +

    There are a lot of things that could prevent you from sending mail:

    +
      +
    • Check if your IP address is on any blacklists. You could use dnsbl.info or any other similar service to check for your IP address.
    • +
    • There are some consumer ISP routers out there, that block mail ports for non whitelisted domains. Please check if you can reach your server on the ports 465 or 587:
    • +
    +
    # telnet 74.125.133.27 465
    +Trying 74.125.133.27...
    +Connected to 74.125.133.27.
    +Escape character is '^]'.
    +
    +

    My mails are identified as Spam

    +

    Please read our guide on DNS configuration.

    +

    docker-compose throws weird errors

    +

    ... like:

    +
      +
    • ERROR: Invalid interpolation format ...
    • +
    • AttributeError: 'NoneType' object has no attribute 'keys'.
    • +
    • ERROR: In file './docker-compose.yml' service 'version' doesn't have any configuration options.
    • +
    +

    When you encounter one or similar messages while trying to run mailcow: dockerized please check if you have the latest version of Docker and docker-compose

    +

    Container XY is unhealthy

    +

    This error tries to tell you that one of the (health) conditions for a certain container are not met. Therefore it can't be started. This can have several reasons, the most common one is an updated git clone but old docker image or vice versa.

    +

    A wrong configured firewall could also cause such a failure. The containers need to be able to talk to each other over the network 172.22.1.1/24.

    +

    It might also be wrongly linked file (i.e. SSL certificate) that prevents a crucial container (nginx) from starting, so always check your logs to get an idea where your problem is coming from.

    +

    Address already in use

    +

    If you get an error message like:

    +
    ERROR: for postfix-mailcow  Cannot start service postfix-mailcow: driver failed programming external     connectivity on endpoint mailcowdockerized_postfix-mailcow_1: Error starting userland proxy: listen tcp 0.0.0.0:25: bind: address already in use
    +
    +

    while trying to start / install mailcow: dockerized, make sure you've followed our section on the prerequisites.

    +

    XYZ can't connect to ...

    +

    Please check your local firewall! +Docker and iptables-based firewalls sometimes create conflicting rules, so disable the firewall on your host to determine whether your connection issues are caused by such conflicts. If they are, you need to manually create appropriate rules in your host firewall to permit the necessary connections.

    +

    If you experience connection problems from home, please check your ISP router's firewall too, some of them block mail traffic on the SMTP (587) or SMTPS (465) ports. It could also be, that your ISP is blocking the ports for SUBMISSION (25).

    +

    While Linux users can chose from a variety of tools1 to check if a port is open, the Windows user has only the command telnet host port available by default (and it has to be activated since Windows Vista).

    +

    To enable telnet on a Windows after Vista please check this guide or enter the following command in an terminal with administrator privileges:

    +
    dism /online /Enable-Feature /FeatureName:TelnetClient
    +
    +

    Inotify instance limit for user 5000 (UID vmail) exceeded (see #453)

    +

    Docker containers use the Docker hosts inotify limits. Setting them on your Docker host will pass them to the container.

    +
    +
    +
      +
    1. +

      netcat, nmap, openssl, telnet, etc. 

      +
    2. +
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/debug-logs/index.html b/debug-logs/index.html new file mode 100644 index 000000000..142dcdd14 --- /dev/null +++ b/debug-logs/index.html @@ -0,0 +1,1944 @@ + + + + + + + + + + + + + + + + + + + Logs - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Logs

    + +
    +

    Warning

    +

    This section only applies for Dockers default logging driver (JSON).

    +
    +

    To view the logs of all mailcow: dockerized related containers, you can use docker-compose logs inside your mailcow-dockerized folder that contains your mailcow.conf. This is usually a bit much, but you could trim the output with --tail=100 to the last 100 lines per container, or add a -f to follow the live output of all your services.

    +

    To view the logs of a specific service you can use docker-compose logs [options] $service_name

    +
    +

    Info

    +

    The available options for the command docker-compose logs are:

    +
      +
    • --no-color: Produce monochrome output.
    • +
    • -f: Follow the log output.
    • +
    • -t: Show timestamps.
    • +
    • --tail="all": Number of lines to show from the end of the logs for each container.
    • +
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/debug-mysql_upgrade/index.html b/debug-mysql_upgrade/index.html new file mode 100644 index 000000000..4f20e5263 --- /dev/null +++ b/debug-mysql_upgrade/index.html @@ -0,0 +1,1998 @@ + + + + + + + + + + + + + + + + + + + Manual MySQL upgrade - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Manual MySQL upgrade

    + +

    Run a manual mysql_upgrade

    +

    This step is usually not necessary.

    +
    docker-compose stop mysql-mailcow watchdog-mailcow
    +docker-compose run --rm --entrypoint '/bin/sh -c "gosu mysql mysqld --skip-grant-tables & sleep 10 && bash && exit 0"' mysql-mailcow
    +
    +

    As soon as the SQL shell spawned, run mysql_upgrade and exit the container:

    +
    mysql_upgrade
    +exit
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/debug-reset_pw/index.html b/debug-reset_pw/index.html new file mode 100644 index 000000000..a23d47803 --- /dev/null +++ b/debug-reset_pw/index.html @@ -0,0 +1,2159 @@ + + + + + + + + + + + + + + + + + + + Reset Passwords (incl. SQL) - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + +

    Reset Passwords (incl. SQL)

    + +

    mailcow Admin Account

    +

    Reset mailcow admin to admin:moohoo. Older mailcow: dockerized installations may find mailcow-reset-admin.sh in their mailcow root directory (mailcow_path).

    +
    cd mailcow_path
    +./helper-scripts/mailcow-reset-admin.sh
    +
    +

    Reset MySQL Passwords

    +

    Stop the stack by running docker-compose stop.

    +

    When the containers came to a stop, run this command:

    +
    docker-compose run --rm --entrypoint '/bin/sh -c "gosu mysql mysqld --skip-grant-tables & sleep 10 && mysql -hlocalhost -uroot && exit 0"' mysql-mailcow
    +
    +

    1. Find database name

    +
    # source mailcow.conf
    +# docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME}
    +MariaDB [(none)]> show databases;
    ++--------------------+
    +| Database           |
    ++--------------------+
    +| information_schema |
    +| mailcow_database   | <=====
    +| mysql              |
    +| performance_schema |
    ++--------------------+
    +4 rows in set (0.00 sec)
    +
    +

    2. Reset one or more users

    +

    2.1 Maria DB < 10.4 (older mailcow installations)

    +

    Both "password" and "authentication_string" exist. Currently "password" is used, but better set both.

    +
    MariaDB [(none)]> SELECT user FROM mysql.user;
    ++--------------+
    +| user         |
    ++--------------+
    +| mailcow      | <=====
    +| root         |
    ++--------------+
    +2 rows in set (0.00 sec)
    +
    +MariaDB [(none)]> FLUSH PRIVILEGES;
    +MariaDB [(none)]> UPDATE mysql.user SET authentication_string = PASSWORD('gotr00t'), password = PASSWORD('gotr00t') WHERE User = 'root';
    +MariaDB [(none)]> UPDATE mysql.user SET authentication_string = PASSWORD('mookuh'), password = PASSWORD('mookuh') WHERE User = 'mailcow' AND Host = '%';
    +MariaDB [(none)]> FLUSH PRIVILEGES;
    +
    +

    2.2 Maria DB >= 10.4 (current mailcows)

    +
    MariaDB [(none)]> SELECT user FROM mysql.user;
    ++--------------+
    +| user         |
    ++--------------+
    +| mailcow      | <=====
    +| root         |
    ++--------------+
    +2 rows in set (0.00 sec)
    +
    +MariaDB [(none)]> FLUSH PRIVILEGES;
    +MariaDB [(none)]> ALTER USER 'mailcow'@'%' IDENTIFIED BY 'mookuh';
    +MariaDB [(none)]> ALTER USER 'root'@'%' IDENTIFIED BY 'gotr00t';
    +MariaDB [(none)]> ALTER USER 'root'@'localhost' IDENTIFIED BY 'gotr00t';
    +MariaDB [(none)]> FLUSH PRIVILEGES;
    +
    +

    Remove Two-Factor Authentication

    +

    This works similar to resetting a MySQL password, now we do it from the host without connecting to the MySQL CLI:

    +
    source mailcow.conf
    +docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM tfa WHERE username='YOUR_USERNAME';"
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/debug-rm_volumes/index.html b/debug-rm_volumes/index.html new file mode 100644 index 000000000..638c7152c --- /dev/null +++ b/debug-rm_volumes/index.html @@ -0,0 +1,1941 @@ + + + + + + + + + + + + + + + + + + + Remove Persistent Data - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Remove Persistent Data

    + +

    You may want to remove a set of persistent data to resolve a conflict or to start over.

    +

    mailcowdockerized can vary and depends on your compose project name (if it's unchanged, mailcowdockerized is the correct value). If you are unsure about volume names, run docker volume ls for a full list.

    +

    Delete a single volume:

    +
    docker volume rm mailcowdockerized_${VOLUME_NAME}
    +
    +
      +
    • Remove volume mysql-vol-1 to remove all MySQL data.
    • +
    • Remove volume redis-vol-1 to remove all Redis data.
    • +
    • Remove volume vmail-vol-1 to remove all contents of /var/vmail mounted to dovecot-mailcow.
    • +
    • Remove volume rspamd-vol-1 to remove all Rspamd data.
    • +
    • Remove volume crypt-vol-1 to remove all crypto data. This will render all mails unreadable.
    • +
    +

    Alternatively, running docker-compose down -v will destroy all mailcow: dockerized volumes and delete any related containers and networks.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/debug/index.html b/debug/index.html new file mode 100644 index 000000000..8ccf6ddd1 --- /dev/null +++ b/debug/index.html @@ -0,0 +1,1938 @@ + + + + + + + + + + + + + + + + + + + Introduction - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Introduction

    + +

    When a problem occurs, then always for a reason! What you want to do in such a case is:

    +
      +
    1. Read your logs; follow them to see what the reason for your problem is.
    2. +
    3. Follow the leads given to you in your logfiles and start investigating.
    4. +
    5. Restarting the troubled service or the whole stack to see if the problem persists.
    6. +
    7. Read the documentation of the troubled service and search it's bugtracker for your problem.
    8. +
    9. Search our issues for your problem.
    10. +
    11. Create an issue over at our GitHub repository if you think your problem might be a bug or a missing feature you badly need. But please make sure, that you include all the logs and a full description to your problem. Please do not ask for support on Git.
    12. +
    13. Join our Telegram community or find the official support packages at Servercow.
    14. +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/download/build-thunderbird-plugins.sh b/download/build-thunderbird-plugins.sh new file mode 100644 index 000000000..35ff84a26 --- /dev/null +++ b/download/build-thunderbird-plugins.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +set -e + +MAILHOST=$1 +if [ "$MAILHOST" = "" ]; then + echo "Usage: echo example.com example.org | $0 mailcow.example.com" + exit 1 +fi + +cd $(dirname $0) + +# we have to use the master branch, because there is no tag or release at the moment +wget -O connector.zip https://github.com/inverse-inc/sogo-connector/archive/master.zip +unzip connector.zip + +# build custom connector +while read DOMAINS; do + for DOMAIN in $DOMAINS; do + echo "Building SOGo Connector for $DOMAIN hosted on $MAILHOST" + cd sogo-connector-master + mkdir -p custom/${DOMAIN} + cp -r custom/sogo-demo/* custom/${DOMAIN}/ + sed -i "s/https:\/\/demo\.sogo\.nu/https:\/\/${MAILHOST}/g" custom/${DOMAIN}/chrome/content/sogo-connector/general/custom-preferences.js + sed -i "s/plugins\/updates\.php[?]/thunderbird-plugins.php?domain=${DOMAIN}\&/g" chrome/content/sogo-connector/global/extensions.rdf + # adjust sogo-connector.autocomplete.server.urlid + sed -i "s/\"public\"/\"${MAILHOST}\"/g" custom/${DOMAIN}/chrome/content/sogo-connector/general/custom-preferences.js + # remove wrong timezone setting + sed -i 's/char_pref(\"calendar\.timezone\.local\", \"\/mozilla\.org\/20070129_1\/America\/Montreal\");//g' custom/${DOMAIN}/chrome/content/sogo-connector/general/custom-preferences.js + + echo 'bool_pref("mail.collect_email_address_outgoing", false);' >> custom/${DOMAIN}/chrome/content/sogo-connector/general/custom-preferences.js + make build=${DOMAIN} + CONNECTOR_VER=$(grep \"version\" manifest.json | awk -F '"' '{print $4}') + CONNECTOR_MIN_VER=$(grep strict_min_version manifest.json | grep -Eo '[0-9\.]+' | head -n 1) + mv sogo-connector-*.xpi ../sogo-connector-${CONNECTOR_VER}-${DOMAIN}.xpi + cd .. + done +done + +# if you add any other plugins below, you need to add them into extensions.rdf as in the line commented out above + +# # download Sieve plugin +# SIEVE_RELEASES=$(wget --header="Accept: application/vnd.github.v3+json" -qO - https://api.github.com/repos/thsmi/sieve/releases) +# SIEVE_VER=$(echo "$SIEVE_RELEASES" | grep -o '"tag_name": *"[^"]*"' | head -n 1 | awk -F '"' '{print $4}') +# SIEVE_URL=$(echo "$SIEVE_RELEASES" | grep -o '"browser_download_url": *"[^"]*"' | head -n 1 | awk -F '"' '{print $4}') +# wget -O sieve-${SIEVE_VER}.xpi ${SIEVE_URL} +# unset SIEVE_RELEASES +# +# # download ACL plugin +# IMAP_ACL_RELEASES=$(wget -qO - 'https://addons.thunderbird.net/en-US/thunderbird/addon/imap-acl-extension/') +# IMAP_ACL_VER=$(echo "$IMAP_ACL_RELEASES" | grep version-number | awk -F '[<>]' '{print $3}' | head -n 1) +# IMAP_ACL_URL=$(echo "$IMAP_ACL_RELEASES" | grep -o 'https://.*\.xpi' | head -n 1) +# wget -O imap_acl_extension-${IMAP_ACL_VER}-tb.xpi ${IMAP_ACL_URL} +# unset IMAP_ACL_RELEASES + +# update version file +echo "sogo-connector@inverse.ca;${CONNECTOR_VER};sogo-connector-${CONNECTOR_VER}-__DOMAIN__.xpi;${CONNECTOR_MIN_VER}" > version.csv +# echo "sieve@mozdev.org;${SIEVE_VER};sieve-${SIEVE_VER}.xpi" >> version.csv +# echo "imap-acl@sirphreak.com;${IMAP_ACL_VER};imap_acl_extension-${IMAP_ACL_VER}-tb.xpi" >> version.csv + +rm -rf sogo-connector-master *.zip diff --git a/download/thunderbird-plugins.php b/download/thunderbird-plugins.php new file mode 100644 index 000000000..889cdf35d --- /dev/null +++ b/download/thunderbird-plugins.php @@ -0,0 +1,118 @@ + 'thunderbird', + 'version' => $row[1], + 'filename' => str_replace('__DOMAIN__', $_GET["domain"], $row[2]), + ); + if (count($row) > 3) + { + $plugins[$row[0]]['min_version'] = $row[3]; + } + else + { + $plugins[$row[0]]['min_version'] = '30.0'; + } + } + fclose($fh); + } +} + +$applications += array( "thunderbird" => "{3550f703-e582-4d05-9a08-453d09bdfdc6} + __MIN_VERSION__ + 99.*" ); + +$pluginname = $_GET["plugin"]; +$plugin =& $plugins[$pluginname]; +$application =& $applications[$plugin["application"]]; + +if ( $plugin ) { + $platform = $_GET["platform"]; + if ( $platform + && file_exists( $platform . "/" . $plugin["filename"] ) ) { + $plugin["filename"] = $platform . "/" . $plugin["filename"]; + } + elseif ( !file_exists( $plugin["filename"] ) ) { + $plugin = false; + } +} + +if (preg_match('/Thunderbird\/([0-9\.]+)/', $_SERVER['HTTP_USER_AGENT'], $client_ver)) +{ + $client_ver = $client_ver[1]; +} +else +{ + $client_ver = $plugin['min_version']; +} + +if ( $plugin ) { + if (version_compare($client_ver, $plugin['min_version'], '<')) { + header("Content-type: text/plain; charset=utf-8", true, 404); + echo( 'Plugin not compatible with client version' ); + exit; + } + header("Content-type: text/xml; charset=utf-8"); + echo ('' . "\n"); +?> + + + + + +
  • + + + + + + + + + + +
  • +
    +
    +
    +
    + diff --git a/extra.css b/extra.css new file mode 100644 index 000000000..9a9a9bc02 --- /dev/null +++ b/extra.css @@ -0,0 +1,17 @@ +@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,700,700i'); +.md-nav { + font-size: 14px; + line-height: 1.4; +} +.md-typeset { + font-size: 14px; + line-height: 1.5; +} +body, input { + font-family: "Source Sans Pro", "Roboto","Helvetica Neue",Helvetica,Arial,sans-serif; + color: black; +} +code { + display: inline-block; + white-space: pre-wrap; +} diff --git a/firststeps-disable_ipv6/index.html b/firststeps-disable_ipv6/index.html new file mode 100644 index 000000000..60922b76a --- /dev/null +++ b/firststeps-disable_ipv6/index.html @@ -0,0 +1,1978 @@ + + + + + + + + + + + + + + + + + + + Disable IPv6 - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Disable IPv6

    + +

    This is ONLY recommended if you do not have an IPv6 enabled network on your host!

    +

    If you really need to, you can disable the usage of IPv6 in the compose file. +Additionally, you can also disable the startup of container "ipv6nat-mailcow", as it's not needed if you won't use IPv6.

    +

    Instead of editing docker-compose.yml directly, it is preferable to create an override file for it +and implement your changes to the service there. Unfortunately, this right now only seems to work for services, not for network settings.

    +

    To disable IPv6 on the mailcow network, open docker-compose.yml with your favourite text editor and search for the network section (it's near the bottom of the file).

    +

    1. Modify docker-compose.yml

    +

    Change enable_ipv6: true to enable_ipv6: false:

    +
    networks:
    +  mailcow-network:
    +    [...]
    +    enable_ipv6: true # <<< set to false
    +    [...]
    +
    +

    2. Disable ipv6nat-mailcow

    +

    To disable the ipv6nat-mailcow container as well, go to your mailcow directory and create a new file called "docker-compose.override.yml":

    +

    NOTE: If you already have an override file, of course don't recreate it, but merge the lines below into your existing one accordingly!

    +
    # cd /opt/mailcow-dockerized
    +# touch docker-compose.override.yml
    +
    +

    Open the file in your favourite text editor and fill in the following:

    +
    version: '2.1'
    +services:
    +
    +    ipv6nat-mailcow:
    +      restart: "no"
    +      entrypoint: ["echo", "ipv6nat disabled in compose.override.yml"]
    +
    +

    For these changes to be effective, you need to fully stop and then restart the stack, so containers and networks are recreated:

    +
    docker-compose down
    +docker-compose up -d
    +
    +

    3. Disable IPv6 in unbound-mailcow

    +

    Edit data/conf/unbound/unbound.conf and set do-ip6 to "no":

    +
    server:
    +  [...]
    +  do-ip6: no
    +  [...]
    +
    +

    Restart Unbound:

    +
    docker-compose restart unbound-mailcow
    +
    +

    4. Disable IPv6 in postfix-mailcow

    +

    Create data/conf/postfix/extra.cf and set smtp_address_preference to ipv4:

    +
    smtp_address_preference = ipv4
    +inet_protocols = ipv4
    +
    +

    Restart Postfix:

    +
    docker-compose restart postfix-mailcow
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/firststeps-ip_bindings/index.html b/firststeps-ip_bindings/index.html new file mode 100644 index 000000000..3de8dff84 --- /dev/null +++ b/firststeps-ip_bindings/index.html @@ -0,0 +1,2064 @@ + + + + + + + + + + + + + + + + + + + IP bindings - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    IP bindings

    + +
    +

    Warning

    +

    Changing the binding does not affect source NAT. See SNAT for required steps.

    +
    +

    IPv4 binding

    +

    To adjust one or multiple IPv4 bindings, open mailcow.conf and edit one, multiple or all variables as per your needs:

    +
    # For technical reasons, http bindings are a bit different from other service bindings.
    +# You will find the following variables, separated by a bind address and its port:
    +# Example: HTTP_BIND=1.2.3.4
    +
    +HTTP_PORT=80
    +HTTP_BIND=
    +HTTPS_PORT=443
    +HTTPS_BIND=
    +
    +# Other services are bound by using the following format:
    +# SMTP_PORT=1.2.3.4:25 will bind SMTP to the IP 1.2.3.4 on port 25
    +# Important! Specifying an IPv4 address will skip all IPv6 bindings since Docker 20.x.
    +# doveadm, SQL as well as Solr are bound to local ports only, please do not change that, unless you know what you are doing.
    +
    +SMTP_PORT=25
    +SMTPS_PORT=465
    +SUBMISSION_PORT=587
    +IMAP_PORT=143
    +IMAPS_PORT=993
    +POP_PORT=110
    +POPS_PORT=995
    +SIEVE_PORT=4190
    +DOVEADM_PORT=127.0.0.1:19991
    +SQL_PORT=127.0.0.1:13306
    +SOLR_PORT=127.0.0.1:18983
    +
    +

    To apply your changes, run docker-compose down followed by docker-compose up -d.

    +

    IPv6 binding

    +

    Changing IPv6 bindings is different from IPv4. Again, this has a technical background.

    +

    A docker-compose.override.yml file will be used instead of editing the docker-compose.yml file directly. This is to maintain updatability, as the docker-compose.yml file gets updated regularly and your changes will most likely be overwritten.

    +

    Edit to create a file docker-compose.override.yml with the following content. Its content will be merged with the productive docker-compose.yml file.

    +

    An imaginary IPv6 2a00:dead:beef::abc is given. The first suffix :PORT1 defines the external port, while the second suffix :PORT2 routes to the corresponding port inside the container and must not be changed.

    +
    version: '2.1'
    +services:
    +
    +    dovecot-mailcow:
    +      ports:
    +        - '2a00:dead:beef::abc:143:143'
    +        - '2a00:dead:beef::abc:993:993'
    +        - '2a00:dead:beef::abc:110:110'
    +        - '2a00:dead:beef::abc:995:995'
    +        - '2a00:dead:beef::abc:4190:4190'
    +
    +    postfix-mailcow:
    +      ports:
    +        - '2a00:dead:beef::abc:25:25'
    +        - '2a00:dead:beef::abc:465:465'
    +        - '2a00:dead:beef::abc:587:587'
    +
    +    nginx-mailcow:
    +      ports:
    +        - '2a00:dead:beef::abc:80:80'
    +        - '2a00:dead:beef::abc:443:443'
    +
    +

    To apply your changes, run docker-compose down followed by docker-compose up -d.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/firststeps-local_mta/index.html b/firststeps-local_mta/index.html new file mode 100644 index 000000000..b1f411a4e --- /dev/null +++ b/firststeps-local_mta/index.html @@ -0,0 +1,1943 @@ + + + + + + + + + + + + + + + + + + + Local MTA on Docker host - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Local MTA on Docker host

    + +

    The easiest option would be to disable the listener on port 25/tcp.

    +

    Postfix users disable the listener by commenting the following line (starting with smtp or 25) in /etc/postfix/master.cf: +

    #smtp      inet  n       -       -       -       -       smtpd
    +

    +

    Furthermore, to relay over a dockerized mailcow, you may want to add 172.22.1.1 as relayhost and remove the Docker interface from "inet_interfaces":

    +
    postconf -e 'relayhost = 172.22.1.1'
    +postconf -e "mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128"
    +postconf -e "inet_interfaces = loopback-only"
    +postconf -e "relay_transport = relay"
    +postconf -e "default_transport = smtp"
    +
    +

    Now it is important to not have the same FQDN in myhostname as you use for your dockerized mailcow. Check your local (non-Docker) Postfix' main.cf for myhostname and set it to something different, for example local.my.fqdn.tld.

    +

    "172.22.1.1" is the mailcow created network gateway in Docker. +Relaying over this interface is necessary (instead of - for example - relaying directly over ${MAILCOW_HOSTNAME}) to relay over a known internal network.

    +

    Restart Postfix after applying your changes.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/firststeps-logging/index.html b/firststeps-logging/index.html new file mode 100644 index 000000000..7cd267299 --- /dev/null +++ b/firststeps-logging/index.html @@ -0,0 +1,2124 @@ + + + + + + + + + + + + + + + + + + + Logging - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Logging

    + +

    Logging in mailcow: dockerized consists of multiple stages, but is, after all, much more flexible and easier to integrate into a logging daemon than before.

    +

    In Docker the containerized application (PID 1) writes its output to stdout. For real one-application containers this works just fine. +Run docker-compose logs --help to learn more.

    +

    Some containers log or stream to multiple destinations.

    +

    No container will keep persistent logs in it. Containers are transient items!

    +

    In the end, every line of logs will reach the Docker daemon - unfiltered.

    +

    The default logging driver is "json".

    +

    Filtered logs

    +

    Some logs are filtered and written to Redis keys but also streamed to a Redis channel.

    +

    The Redis channel is used to stream logs with failed authentication attempts to be read by netfilter-mailcow.

    +

    The Redis keys are persistent and will keep 10000 lines of logs for the web UI.

    +

    This mechanism makes it possible to use whatever Docker logging driver you want to, without losing +the ability to read logs from the UI or ban suspicious clients with netfilter-mailcow.

    +

    Redis keys will only hold logs from applications and filter out system messages (think of cron etc.).

    +

    Logging drivers

    +

    Via docker-compose.override.yml

    +

    Here is the good news: Since Docker has some great logging drivers, you can integrate mailcow: dockerized into your existing logging environment with ease.

    +

    Create a docker-compose.override.yml and add, for example, this block to use the "gelf" logging plugin for postfix-mailcow:

    +
    version: '2.1'
    +services:
    +  postfix-mailcow: # or any other
    +    logging:
    +      driver: "gelf"
    +      options:
    +        gelf-address: "udp://graylog:12201"  
    +
    +

    Another example for Syslog:

    +
    version: '2.1'
    +services:
    +
    +  postfix-mailcow: # or any other
    +    logging:
    +      driver: "syslog"
    +      options:
    +        syslog-address: "udp://127.0.0.1:514"
    +        syslog-facility: "local3"
    +
    +  dovecot-mailcow: # or any other
    +    logging:
    +      driver: "syslog"
    +      options:
    +        syslog-address: "udp://127.0.0.1:514"
    +        syslog-facility: "local3"
    +
    +  rspamd-mailcow: # or any other
    +    logging:
    +      driver: "syslog"
    +      options:
    +        syslog-address: "udp://127.0.0.1:514"
    +        syslog-facility: "local3"
    +
    +# For Rsyslog only:
    +# To move local3 input to /var/log/mailcow.log and stop processing, create a file "/etc/rsyslog.d/docker.conf":
    +
    +local3.*        /var/log/mailcow.logs
    +& ~
    +
    +# Restart rsyslog afterwards.
    +
    +

    via daemon.json (globally)

    +

    If you want to change the logging driver globally, edit Dockers daemon configuration file /etc/docker/daemon.json and restart the Docker service:

    +
    {
    +...
    +  "log-driver": "gelf",
    +  "log-opts": {
    +    "gelf-address": "udp://graylog:12201"
    +  }
    +...
    +}
    +
    +

    For Syslog:

    +
    {
    +...
    +  "log-driver": "syslog",
    +  "log-opts": {
    +    "syslog-address": "udp://1.2.3.4:514"
    +  }
    +...
    +}
    +
    +

    Restart the Docker daemon and run docker-compose down && docker-compose up -d to recreate the containers with the new logging driver.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/firststeps-relayhost/index.html b/firststeps-relayhost/index.html new file mode 100644 index 000000000..20fb0a3d1 --- /dev/null +++ b/firststeps-relayhost/index.html @@ -0,0 +1,2051 @@ + + + + + + + + + + + + + + + + + + + Relayhosts - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Relayhosts

    + +

    As of September 12, 2018 you can setup relayhosts as admin by using the mailcow UI.

    +

    This is useful if you want to relay outgoing emails for a specific domain to a third-party spam filter or a service like Mailgun or Sendgrid. This is also known as a smarthost.

    +

    Add a new relayhost

    +

    Go to the Routing tab of the Configuration and Details section of the admin UI. +Here you will see a list of relayhosts currently setup.

    +

    Scroll to the Add sender-dependent transport section.

    +

    Under Host, add the host you want to relay to.
    +Example: if you want to use Mailgun to send emails instead of your server IP, enter smtp.mailgun.org

    +

    If the relay host requires a username and password to authenticate, enter them in the respective fields.
    +Keep in mind the credentials will be stored in plain text.

    +

    Test a relayhost

    +

    To test that connectivity to the host works, click on Test from the list of relayhosts and enter a From: address. Then, run the test.

    +

    You will then see the results of the SMTP transmission. If all went well, you should see +SERVER -> CLIENT: 250 2.0.0 Ok: queued as A093B401D4 as one of the last lines.

    +

    If not, review the error provided and resolve it.

    +

    Note: Some hosts, especially those who do not require authentication, will deny connections from servers that have not been added to their system beforehand. Make sure you read the documentation of the relayhost to make sure you've added your domain and/or the server IP to their system.

    +

    Tip: You can change the default test To: address the test uses from null@mailcow.email to any email address you choose by modifying the $RELAY_TO variable on the vars.inc.php file under /opt/mailcow-dockerized/data/web/inc
    This way you can check that the relay worked by checking the destination mailbox.

    +

    Set the relayhost for a domain

    +

    Go to the Domains tab of the Mail setup section of the admin UI.

    +

    Edit the desired domain.

    +

    Select the newly added host on the Sender-dependent transports dropdown and save changes.

    +

    Send an email from a mailbox on that domain and you should see postfix handing the message over to the relayhost in the logs.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/firststeps-rfc-1918/index.html b/firststeps-rfc-1918/index.html new file mode 100644 index 000000000..73debca41 --- /dev/null +++ b/firststeps-rfc-1918/index.html @@ -0,0 +1,1934 @@ + + + + + + + + + + + + + + + + + + + Untrust RFC 1918 - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Untrust RFC 1918

    + +

    Per default, mailcow considers all private RFC1918 networks (i.e. 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) as trusted. Though it is reasonable in most cases, you may want to restrict this setting under certain circumstances. In particular, if you are using some kind of reverse proxy for SMTP TCP ports. If your reverse proxy host is located in a private net, mailcow will consider all traffic from it as trusted, which may result in an open relay.

    +

    To change this behaviour override the default value of mynetworks parameter through the data/conf/postfix/extra.cf configuration file.

    +

    Important: Do not remove the networks listed as IPV4_NETWORK and IPV6_NETWORK in your mailcow.conf. You should also keep local addresses.

    +

    The default values for those variables - 172.22.1.0/24 and fd4d:6169:6c63:6f77::/64 - would result in the following, minimal configuration:

    +
    mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 [fe80::]/10 172.22.1.0/24 [fd4d:6169:6c63:6f77::]/64
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/firststeps-rp/index.html b/firststeps-rp/index.html new file mode 100644 index 000000000..927fc07b6 --- /dev/null +++ b/firststeps-rp/index.html @@ -0,0 +1,2281 @@ + + + + + + + + + + + + + + + + + + + Reverse Proxy - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + +

    Reverse Proxy

    + +

    You don't need to change the Nginx site that comes with mailcow: dockerized. +mailcow: dockerized trusts the default gateway IP 172.22.1.1 as proxy.

    +

    1. Make sure you change HTTP_BIND and HTTPS_BIND in mailcow.conf to a local address and set the ports accordingly, for example: +

    HTTP_BIND=127.0.0.1
    +HTTP_PORT=8080
    +HTTPS_BIND=127.0.0.1
    +HTTPS_PORT=8443
    +

    +

    This will also change the bindings inside the Nginx container! This is important, if you decide to use a proxy within Docker.

    +

    IMPORTANT: Do not use port 8081, 9081 or 65510!

    +

    Recreate affected containers by running docker-compose up -d.

    +
    +

    Warning

    +

    Make sure you run generate_config.sh before you enable any site configuration examples below. +The script generate_config.sh copies snake-oil certificates to the correct location, so the services will not fail to start due to missing files.

    +
    +
    +

    Warning

    +

    If you enable TLS SNI (ENABLE_TLS_SNI in mailcow.conf), the certificate paths in your reverse proxy must match the correct paths in data/assets/ssl/{hostname}. The certificates will be split into data/assets/ssl/{hostname1,hostname2,etc} and therefore will not work when you copy the examples from below pointing to data/assets/ssl/cert.pem etc.

    +
    +
    +

    Info

    +

    Using the site configs below will forward ACME requests to mailcow and let it handle certificates itself. +The downside of using mailcow as ACME client behind a reverse proxy is, that you will need to reload your webserver after acme-mailcow changed/renewed/created the certificate. You can either reload your webserver daily or write a script to watch the file for changes. +On many servers logrotate will reload the webserver daily anyway.

    +

    If you want to use a local certbot installation, you will need to change the SSL certificate parameters accordingly. +Make sure you run a post-hook script when you decide to use external ACME clients. You will find an example at the bottom of this page.

    +
    +

    2. Configure your local webserver as reverse proxy:

    +

    Apache 2.4

    +

    Required modules: +

    a2enmod rewrite proxy proxy_http headers ssl
    +

    +

    Let's Encrypt will follow our rewrite, certificate requests in mailcow will work fine.

    +

    Take care of highlighted lines.

    +
    <VirtualHost *:80>
    +  ServerName CHANGE_TO_MAILCOW_HOSTNAME
    +  ServerAlias autodiscover.*
    +  ServerAlias autoconfig.*
    +  ServerAlias xmpp_prefix_if_any.domain
    +  ServerAlias *.xmpp_prefix_if_any.domain
    +  RewriteEngine on
    +
    +  RewriteCond %{HTTPS} off
    +  RewriteRule ^/?(.*) https://%{HTTP_HOST}/$1 [R=301,L]
    +
    +  ProxyPass / http://127.0.0.1:8080/
    +  ProxyPassReverse / http://127.0.0.1:8080/
    +  ProxyPreserveHost On
    +  ProxyAddHeaders On
    +  RequestHeader set X-Forwarded-Proto "http"
    +</VirtualHost>
    +<VirtualHost *:443>
    +  ServerName CHANGE_TO_MAILCOW_HOSTNAME
    +  ServerAlias autodiscover.*
    +  ServerAlias autoconfig.*
    +  ServerAlias xmpp_prefix_if_any.domain
    +  ServerAlias *.xmpp_prefix_if_any.domain
    +
    +  # You should proxy to a plain HTTP session to offload SSL processing
    +  ProxyPass /Microsoft-Server-ActiveSync http://127.0.0.1:8080/Microsoft-Server-ActiveSync connectiontimeout=4000
    +  ProxyPassReverse /Microsoft-Server-ActiveSync http://127.0.0.1:8080/Microsoft-Server-ActiveSync
    +  ProxyPass / http://127.0.0.1:8080/
    +  ProxyPassReverse / http://127.0.0.1:8080/
    +  ProxyPreserveHost On
    +  ProxyAddHeaders On
    +  RequestHeader set X-Forwarded-Proto "https"
    +
    +  SSLCertificateFile MAILCOW_PATH/data/assets/ssl/cert.pem
    +  SSLCertificateKeyFile MAILCOW_PATH/data/assets/ssl/key.pem
    +
    +  # If you plan to proxy to a HTTPS host:
    +  #SSLProxyEngine On
    +
    +  # If you plan to proxy to an untrusted HTTPS host:
    +  #SSLProxyVerify none
    +  #SSLProxyCheckPeerCN off
    +  #SSLProxyCheckPeerName off
    +  #SSLProxyCheckPeerExpire off
    +</VirtualHost>
    +
    +

    Nginx

    +

    Let's Encrypt will follow our rewrite, certificate requests will work fine.

    +

    Take care of highlighted lines.

    +
    server {
    +  listen 80 default_server;
    +  listen [::]:80 default_server;
    +  server_name CHANGE_TO_MAILCOW_HOSTNAME autodiscover.* autoconfig.* xmpp_prefix_if_any.domain *.xmpp_prefix_if_any.domain;
    +  return 301 https://$host$request_uri;
    +}
    +server {
    +  listen 443 ssl http2;
    +  listen [::]:443 ssl http2;
    +  server_name CHANGE_TO_MAILCOW_HOSTNAME autodiscover.* autoconfig.* xmpp_prefix_if_any.domain *.xmpp_prefix_if_any.domain;
    +
    +  ssl_certificate MAILCOW_PATH/data/assets/ssl/cert.pem;
    +  ssl_certificate_key MAILCOW_PATH/data/assets/ssl/key.pem;
    +  ssl_session_timeout 1d;
    +  ssl_session_cache shared:SSL:50m;
    +  ssl_session_tickets off;
    +
    +  # See https://ssl-config.mozilla.org/#server=nginx for the latest ssl settings recommendations
    +  # An example config is given below
    +  ssl_protocols TLSv1.2;
    +  ssl_ciphers HIGH:!aNULL:!MD5:!SHA1:!kRSA;
    +  ssl_prefer_server_ciphers off;
    +
    +  location /Microsoft-Server-ActiveSync {
    +    proxy_pass http://127.0.0.1:8080/Microsoft-Server-ActiveSync;
    +    proxy_set_header Host $http_host;
    +    proxy_set_header X-Real-IP $remote_addr;
    +    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    +    proxy_set_header X-Forwarded-Proto $scheme;
    +    proxy_connect_timeout 75;
    +    proxy_send_timeout 3650;
    +    proxy_read_timeout 3650;
    +    proxy_buffers 64 256k;
    +    client_body_buffer_size 512k;
    +    client_max_body_size 0;
    +  }
    +
    +  location / {
    +    proxy_pass http://127.0.0.1:8080/;
    +    proxy_set_header Host $http_host;
    +    proxy_set_header X-Real-IP $remote_addr;
    +    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    +    proxy_set_header X-Forwarded-Proto $scheme;
    +    client_max_body_size 0;
    +  }
    +}
    +
    +

    HAProxy (community supported)

    +
    +

    Warning

    +

    This is an unsupported community contribution. Feel free to provide fixes.

    +
    +

    Important/Fixme: This example only forwards HTTPS traffic and does not use mailcows built-in ACME client.

    +
    frontend https-in
    +  bind :::443 v4v6 ssl crt mailcow.pem
    +  default_backend mailcow
    +
    +backend mailcow
    +  option forwardfor
    +  http-request set-header X-Forwarded-Proto https if { ssl_fc }
    +  http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
    +  server mailcow 127.0.0.1:8080 check
    +
    +

    Traefik v2 (community supported)

    +
    +

    Warning

    +

    This is an unsupported community contribution. Feel free to provide fixes.

    +
    +

    Important: This config only covers the "reverseproxing" of the webpannel (nginx-mailcow) using Traefik v2, if you also want to reverseproxy the mail services such as dovecot, postfix... you'll just need to adapt the following config to each container and create an EntryPoint on your traefik.toml or traefik.yml (depending which config you use) for each port.

    +

    For this section we'll assume you have your Traefik 2 [certificatesresolvers] properly configured on your traefik configuration file, and also using acme, also, the following example uses Lets Encrypt, but feel free to change it to your own cert resolver. You can find a basic Traefik 2 toml config file with all the above implemented which can be used for this example here traefik.toml if you need one, or a hint on how to adapt your config.

    +

    So, first of all, we are going to disable the acme-mailcow container since we'll use the certs that traefik will provide us. +For this we'll have to set SKIP_LETS_ENCRYPT=y on our mailcow.conf, and run docker-compose up -d to apply the changes.

    +

    Then we'll create a docker-compose.override.yml file in order to override the main docker-compose.yml found in your mailcow root folder.

    +
    version: '2.1'
    +
    +services:
    +    nginx-mailcow:
    +      networks:
    +        # add Traefik's network
    +        web:
    +      labels:
    +        - traefik.enable=true
    +        # Creates a router called "moo" for the container, and sets up a rule to link the container to certain rule,
    +        #   in this case, a Host rule with our MAILCOW_HOSTNAME var.
    +        - traefik.http.routers.moo.rule=Host(`${MAILCOW_HOSTNAME}`)
    +        # Enables tls over the router we created before.
    +        - traefik.http.routers.moo.tls=true
    +        # Specifies which kind of cert resolver we'll use, in this case le (Lets Encrypt).
    +        - traefik.http.routers.moo.tls.certresolver=le
    +        # Creates a service called "moo" for the container, and specifies which internal port of the container
    +        #   should traefik route the incoming data to.
    +        - traefik.http.services.moo.loadbalancer.server.port=80
    +        # Specifies which entrypoint (external port) should traefik listen to, for this container.
    +        #   websecure being port 443, check the traefik.toml file liked above.
    +        - traefik.http.routers.moo.entrypoints=secure
    +        # Make sure traefik uses the web network, not the mailcowdockerized_mailcow-network
    +        - traefik.docker.network=web
    +
    +    certdumper:
    +        image: humenius/traefik-certs-dumper
    +        container_name: traefik_certdumper
    +        network_mode: none
    +        volumes:
    +          # mount the folder which contains Traefik's `acme.json' file
    +          #   in this case Traefik is started from its own docker-compose in ../traefik
    +          - ../traefik/data:/traefik:ro
    +          # mount mailcow's SSL folder
    +          - ./data/assets/ssl/:/output:rw
    +        environment:
    +          # only change this, if you're using another domain for mailcow's web frontend compared to the standard config
    +          - DOMAIN=${MAILCOW_HOSTNAME}
    +
    +networks:
    +  web:
    +    external: true
    +
    +

    Start the new containers with docker-compose up -d.

    +

    Now, there's only one thing left to do, which is setup the certs so that the mail services can use them as well, since Traefik 2 uses an acme v2 format to save ALL the license from all the domains we have, we'll need to find a way to dump the certs, lucky we have this tiny container which grabs the acme.json file trough a volume, and a variable DOMAIN=example.org, and with these, the container will output the cert.pem and key.pem files, for this we'll simply run the traefik-certs-dumper container binding the /traefik volume to the folder where our acme.json is saved, bind the /output volume to our mailcow data/assets/ssl/ folder, and set up the DOMAIN=example.org variable to the domain we want the certs dumped from.

    +

    This container will watch over the acme.json file for any changes, and regenerate the cert.pem and key.pem files directly into data/assets/ssl/ being the path binded to the container's /output path.

    +

    You can use the command line to run it, or use the docker-compose shown here.

    +

    After we have the certs dumped, we'll have to reload the configs from our postfix and dovecot containers, and check the certs, you can see how here.

    +

    Aaand that should be it 😊, you can check if the Traefik router works fine trough Traefik's dashboard / traefik logs / accessing the setted domain trough https, or / and check HTTPS, SMTP and IMAP trough the commands shown on the page linked before.

    +

    Optional: Post-hook script for non-mailcow ACME clients

    +

    Using a local certbot (or any other ACME client) requires to restart some containers, you can do this with a post-hook script. +Make sure you change the paths accordingly: +

    #!/bin/bash
    +cp /etc/letsencrypt/live/my.domain.tld/fullchain.pem /opt/mailcow-dockerized/data/assets/ssl/cert.pem
    +cp /etc/letsencrypt/live/my.domain.tld/privkey.pem /opt/mailcow-dockerized/data/assets/ssl/key.pem
    +postfix_c=$(docker ps -qaf name=postfix-mailcow)
    +dovecot_c=$(docker ps -qaf name=dovecot-mailcow)
    +nginx_c=$(docker ps -qaf name=nginx-mailcow)
    +docker restart ${postfix_c} ${dovecot_c} ${nginx_c}
    +

    +

    Adding additional server names for mailcow UI

    +

    If you plan to use a server name that is not MAILCOW_HOSTNAME in your reverse proxy, make sure to populate that name in mailcow.conf via ADDITIONAL_SERVER_NAMES first. Names must be separated by commas and must not contain spaces. If you skip this step, mailcow may respond to your reverse proxy with an incorrect site.

    +
    ADDITIONAL_SERVER_NAMES=webmail.domain.tld,other.example.tld
    +
    +

    Run docker-compose up -d to apply.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/firststeps-rspamd_ui/index.html b/firststeps-rspamd_ui/index.html new file mode 100644 index 000000000..14363e8c9 --- /dev/null +++ b/firststeps-rspamd_ui/index.html @@ -0,0 +1,1936 @@ + + + + + + + + + + + + + + + + + + + Rspamd UI - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Rspamd UI

    + +

    Rspamd is an easy to use spam filtering tool presently installed with mailcow.

    +
      +
    1. Go to the mailcow web admin interface
    2. +
    3. Navigate to the Access tab. (Configuration > Administration > Access)
    4. +
    5. Modify the Rspamd UI password
    6. +
    7. Go to https://${MAILCOW_HOSTNAME}/rspamd in a browser and log in!
    8. +
    +

    Additional configuration options and documentation can be found here : https://rspamd.com/webui/

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/firststeps-snat/index.html b/firststeps-snat/index.html new file mode 100644 index 000000000..e3ecbd7e5 --- /dev/null +++ b/firststeps-snat/index.html @@ -0,0 +1,1940 @@ + + + + + + + + + + + + + + + + + + + SNAT - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    SNAT

    + +

    SNAT is used to change the source address of the packets sent by mailcow. +It can be used to change the outgoing IP address on systems with multiple IP addresses.

    +

    Open mailcow.conf, set either or both of the following parameters:

    +
    # Use this IPv4 for outgoing connections (SNAT)
    +SNAT_TO_SOURCE=1.2.3.4
    +
    +# Use this IPv6 for outgoing connections (SNAT)
    +SNAT6_TO_SOURCE=dead:beef
    +
    +

    Run docker-compose up -d.

    +

    The values are read by netfilter-mailcow. netfilter-mailcow will make sure, the post-routing rules are on position 1 in the netfilter table. It does automatically delete and re-create them if they are found on another position than 1.

    +

    Check the output of docker-compose logs --tail=200 netfilter-mailcow to ensure the SNAT settings have been applied.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/firststeps-ssl/index.html b/firststeps-ssl/index.html new file mode 100644 index 000000000..7cb6fb9c5 --- /dev/null +++ b/firststeps-ssl/index.html @@ -0,0 +1,2277 @@ + + + + + + + + + + + + + + + + + + + Advanced SSL - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + +

    Advanced SSL

    + +

    Let's Encrypt (out-of-the-box)

    +

    The "acme-mailcow" container will try to obtain a LE certificate for ${MAILCOW_HOSTNAME}, autodiscover.ADDED_MAIL_DOMAIN and autoconfig.ADDED_MAIL_DOMAIN.

    +
    +

    Warning

    +

    mailcow must be available on port 80 for the acme-client to work. Our reverse proxy example configurations do cover that. You can also use any external ACME client (certbot for example) to obtain certificates, but you will need to make sure, that they are copied to the correct location and a post-hook reloads affected containers. See more in the Reverse Proxy documentation.

    +
    +

    By default, which means 0 domains are added to mailcow, it will try to obtain a certificate for ${MAILCOW_HOSTNAME}.

    +

    For each domain you add, it will try to resolve autodiscover.ADDED_MAIL_DOMAIN and autoconfig.ADDED_MAIL_DOMAIN to its IPv6 address or - if IPv6 is not configured in your domain - IPv4 address. If it succeeds, a name will be added as SAN to the certificate request.

    +

    Only names that can be validated, will be added as SAN.

    +

    For every domain you remove, the certificate will be moved and a new certificate will be requested. It is not possible to keep domains in a certificate, when we are not able validate the challenge for those.

    +

    If you want to re-run the ACME client, use docker-compose restart acme-mailcow and monitor its logs with docker-compose logs --tail=200 -f acme-mailcow.

    +

    Additional domain names

    +

    Edit "mailcow.conf" and add a parameter ADDITIONAL_SAN like this:

    +

    Do not use quotes (") and do not use spaces between the names!

    +
    ADDITIONAL_SAN=smtp.*,cert1.example.com,cert2.example.org,whatever.*
    +
    +

    Each name will be validated against its IPv6 address or - if IPv6 is not configured in your domain - IPv4 address.

    +

    A wildcard name like smtp.* will try to obtain a smtp.DOMAIN_NAME SAN for each domain added to mailcow.

    +

    Run docker-compose up -d to recreate affected containers automatically.

    +
    +

    Info

    +

    Using names other name MAILCOW_HOSTNAME to access the mailcow UI may need further configuration.

    +
    +

    If you plan to use a server name that is not MAILCOW_HOSTNAME to access the mailcow UI (for example by adding mail.* to ADDITIONAL_SAN make sure to populate that name in mailcow.conf via ADDITIONAL_SERVER_NAMES. Names must be separated by commas and must not contain spaces. If you skip this step, mailcow may respond with an incorrect site.

    +
    ADDITIONAL_SERVER_NAMES=webmail.domain.tld,other.example.tld
    +
    +

    Run docker-compose up -d to apply.

    +

    Force renewal

    +

    To force a renewal, you need to create a file named force_renew and restart the acme-mailcow container:

    +
    cd /opt/mailcow-dockerized
    +touch data/assets/ssl/force_renew
    +docker-compose restart acme-mailcow
    +# Now check the logs for a renewal
    +docker-compose logs --tail=200 -f acme-mailcow
    +
    +

    The file will be deleted automatically.

    +

    Validation errors and how to skip validation

    +

    You can skip the IP verification by setting SKIP_IP_CHECK=y in mailcow.conf (no quotes). Be warned that a misconfiguration will get you ratelimited by Let's Encrypt! This is primarily useful for multi-IP setups where the IP check would return the incorrect source IP address. Due to using dynamic IPs for acme-mailcow, source NAT is not consistent over restarts.

    +

    If you encounter problems with "HTTP validation", but your IP address confirmation succeeds, you are most likely using firewalld, ufw or any other firewall, that disallows connections from br-mailcow to your external interface. Both firewalld and ufw disallow this by default. It is often not enough to just stop these firewall services. You'd need to stop mailcow (docker-compose down), stop the firewall service, flush the chains and restart Docker.

    +

    You can also skip this validation method by setting SKIP_HTTP_VERIFICATION=y in "mailcow.conf". Be warned that this is discouraged. In most cases, the HTTP verification is skipped to workaround unknown NAT reflection issues, which are not resolved by ignoring this specific network misconfiguration. If you encounter problems generating TLSA records in the DNS overview within mailcow, you are most likely having issues with NAT reflection you should fix.

    +

    If you changed a SKIP_* parameter, run docker-compose up -d to apply your changes.

    +

    Disable Let's Encrypt

    +

    Disable Let's Encrypt completely

    +

    Set SKIP_LETS_ENCRYPT=y in "mailcow.conf" and recreate "acme-mailcow" by running docker-compose up -d.

    +

    Skip all names but ${MAILCOW_HOSTNAME}

    +

    Add ONLY_MAILCOW_HOSTNAME=y to "mailcow.conf" and recreate "acme-mailcow" by running docker-compose up -d.

    +

    The Let's Encrypt subjectAltName limit of 100 domains

    +

    Let's Encrypt currently has a limit of 100 Domain Names per Certificate.

    +

    By default, "acme-mailcow" will create a single SAN certificate for all validated domains +(see the first section and Additional domain names). +This provides best compatibility but means the Let's Encrypt limit exceeds if you add too many domains to a single mailcow installation.

    +

    To solve this, you can configure ENABLE_SSL_SNI to generate:

    +
      +
    • A main server certificate with MAILCOW_HOSTNAME and all fully qualified domain names in the ADDITIONAL_SAN config
    • +
    • One additional certificate for each domain found in the database with autodiscover., autoconfig. and any other ADDITIONAL_SAN configured in this format (subdomain.*).
    • +
    • Limitations: A certificate name ADDITIONAL_SAN=test.example.com will be added as SAN to the main certificate. A separate certificate/key pair will not be generated for this format.
    • +
    +

    Postfix, Dovecot and Nginx will then serve these certificates with SNI.

    +

    Set ENABLE_SSL_SNI=y in "mailcow.conf" and recreate "acme-mailcow" by running docker-compose up -d.

    +
    +

    Warning

    +

    Not all clients support SNI, see Dovecot documentation or Wikipedia. +You should make sure these clients use the MAILCOW_HOSTNAME for secure connections if you enable this feature.

    +
    +

    Here is an example:

    +
      +
    • MAILCOW_HOSTNAME=server.email.tld
    • +
    • ADDITIONAL_SAN=webmail.email.tld,mail.*
    • +
    • Mailcow email domains: "domain1.tld" and "domain2.tld"
    • +
    +

    The following certificates will be generated:

    +
      +
    • server.email.tld, webmail.email.tld -> this is the default certificate, all clients can connect with these domains
    • +
    • mail.domain1.tld, autoconfig.domain1.tld, autodiscover.domain1.tld -> individual certificate for domain1.tld, cannot be used by clients without SNI support
    • +
    • mail.domain2.tld, autoconfig.domain2.tld, autodiscover.domain2.tld -> individual certificate for domain2.tld, cannot be used by clients without SNI support
    • +
    +

    How to use your own certificate

    +

    Make sure you disable mailcows internal LE client (see above).

    +

    To use your own certificates, just save the combined certificate (containing the certificate and intermediate CA/CA if any) to data/assets/ssl/cert.pem and the corresponding key to data/assets/ssl/key.pem.

    +

    IMPORTANT: Do not use symbolic links! Make sure you copy the certificates and do not link them to data/assets/ssl.

    +

    Restart affected services afterwards:

    +
    docker restart $(docker ps -qaf name=postfix-mailcow)
    +docker restart $(docker ps -qaf name=nginx-mailcow)
    +docker restart $(docker ps -qaf name=dovecot-mailcow)
    +
    +

    See Post-hook script for non-mailcow ACME clients for a full example script.

    +

    Test against staging ACME directory

    +

    Edit mailcow.conf and add LE_STAGING=y.

    +

    Run docker-compose up -d to activate your changes.

    +

    Custom directory URL

    +

    Edit mailcow.conf and add the corresponding directory URL to the new variable DIRECTORY_URL:

    +
    DIRECTORY_URL=https://acme-custom-v9000.api.letsencrypt.org/directory
    +
    +

    You cannot use LE_STAGING with DIRECTORY_URL. If both are set, only LE_STAGING is used.

    +

    Run docker-compose up -d to activate your changes.

    +

    Check your configuration

    +

    Run docker-compose logs acme-mailcow to find out why a validation fails.

    +

    To check if nginx serves the correct certificate, simply use a browser of your choice and check the displayed certificate.

    +

    To check the certificate served by Postfix, Dovecot and Nginx we will use openssl:

    +
    # Connect via SMTP (587)
    +echo "Q" | openssl s_client -starttls smtp -crlf -connect mx.mailcow.email:587
    +# Connect via IMAP (143)
    +echo "Q" | openssl s_client -starttls imap -showcerts -connect mx.mailcow.email:143
    +# Connect via HTTPS (443)
    +echo "Q" | openssl s_client -connect mx.mailcow.email:443
    +
    +

    To validate the expiry dates as returned by openssl against MAILCOW_HOSTNAME, you are able to use our helper script:

    +
    cd /opt/mailcow-dockerized
    +bash helper-scripts/expiry-dates.sh
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/firststeps-sync_jobs_migration/index.html b/firststeps-sync_jobs_migration/index.html new file mode 100644 index 000000000..d8a7a3ffd --- /dev/null +++ b/firststeps-sync_jobs_migration/index.html @@ -0,0 +1,2022 @@ + + + + + + + + + + + + + + + + + + + Sync job migration - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Sync job migration

    + +

    Sync jobs are used to copy or move existing emails from an external IMAP server or within mailcow's existing mailboxes.

    +
    +

    Info

    +

    Depending on your mailbox's ACL you may not have the option to add a sync job. Please contact your domain administrator if so.

    +
    +

    Setup a Sync Job

    +
      +
    1. +

      In the "Mail Setup" or "User Settings" interface, create a new sync job.

      +
    2. +
    3. +

      If you are an administrator, select the username of the downstream mailcow mailbox in the "Username" dropdown.

      +
    4. +
    5. +

      Fill in the "Host" and "Port" fields with their respective correct values from the upstream IMAP server.

      +
    6. +
    7. +

      In the "Username" and "Password" fields, supply the correct access credentials from the upstream IMAP server.

      +
    8. +
    9. +

      Select the "Encryption Method". If the upstream IMAP server uses port 143, it is likely that the encryption method is TLS and SSL for port 993. Nevertheless, you can use PLAIN authentication, but it is stongly discouraged.

      +
    10. +
    11. +

      For all ther other fields, you can leave them as is or modify them as desired.

      +
    12. +
    13. +

      Make sure to tick "Active" and click "Add".

      +
    14. +
    +
    +

    Info

    +

    Once Completed, log into the mailbox and check if all emails are imported correctly. If all goes well, all your mails shall end up in your new mailbox. And don't forget to delete or deactivate the sync job after it is used.

    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/i_u_m_install/index.html b/i_u_m_install/index.html new file mode 100644 index 000000000..9d1e35082 --- /dev/null +++ b/i_u_m_install/index.html @@ -0,0 +1,2021 @@ + + + + + + + + + + + + + + + + + + + Installation - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Installation

    + +
    +

    Warning

    +

    Make sure you've read "Prepare Your System" before proceeding! +We do not recommend CentOS 8 anymore.

    +
    +

    You need Docker and Docker Compose.

    +

    1. Learn how to install Docker and Docker Compose.

    +

    Quick installation for most operation systems:

    +
      +
    • +

      Docker +

      curl -sSL https://get.docker.com/ | CHANNEL=stable sh
      +# After the installation process is finished, you may need to enable the service and make sure it is started (e.g. CentOS 7)
      +systemctl enable docker.service
      +systemctl start docker.service
      +

      +
    • +
    • +

      Docker-Compose

      +
    • +
    +
    +

    Warning

    +

    mailcow requires the latest version of docker-compose. It is highly recommended to use the commands below to install docker-compose. Package managers (e.g. apt, yum) likely won't give you the latest version. +Note: This command downloads docker-compose from the official Docker Github repository and is a safe method. The snippet will determine the latest supported version by mailcow. In almost all cases this is the latest version available (exceptions are broken releases or major changes not yet supported by mailcow).

    +
    +
    curl -L https://github.com/docker/compose/releases/download/$(curl -Ls https://www.servercow.de/docker-compose/latest.php)/docker-compose-$(uname -s)-$(uname -m) > /usr/local/bin/docker-compose
    +chmod +x /usr/local/bin/docker-compose
    +
    +

    Please use the latest Docker engine available and do not use the engine that ships with your distros repository.

    +

    1.1. On SELinux enabled systems, e.g. CentOS 7:

    +
      +
    • Check if "container-selinux" package is present on your system:
    • +
    +
    rpm -qa | grep container-selinux
    +
    +

    If the above command returns an empty or no output, you should install it via your package manager.

    +
      +
    • Check if docker has SELinux support enabled:
    • +
    +
    docker info | grep selinux
    +
    +

    If the above command returns an empty or no output, create or edit /etc/docker/daemon.json and add "selinux-enabled": true. Example file content:

    +
    {
    +  "selinux-enabled": true
    +}
    +
    +

    Restart the docker daemon and verify SELinux is now enabled.

    +

    This step is required to make sure mailcows volumes are properly labeled as declared in the compose file. +If you are interested in how this works, you can check out the readme of https://github.com/containers/container-selinux which links to a lot of useful information on that topic.

    +

    2. Clone the master branch of the repository, make sure your umask equals 0022. Please clone the repository as root user and also control the stack as root. We will modify attributes - if necessary - while boostrapping the containers automatically and make sure everything is secured. The update.sh script must therefore also be run as root. It might be necessary to change ownership and other attributes of files you will otherwise not have access to. We drop permissions for every exposed application and will not run an exposed service as root! Controlling the Docker daemon as non-root user does not give you additional security. The unprivileged user will spawn the containers as root likewise. The behaviour of the stack is identical.

    +
    $ su
    +# umask
    +0022 # <- Verify it is 0022
    +# cd /opt
    +# git clone https://github.com/mailcow/mailcow-dockerized
    +# cd mailcow-dockerized
    +
    +

    3. Generate a configuration file. Use a FQDN (host.domain.tld) as hostname when asked. +

    ./generate_config.sh
    +

    +

    4. Change configuration if you want or need to. +

    nano mailcow.conf
    +
    +If you plan to use a reverse proxy, you can, for example, bind HTTPS to 127.0.0.1 on port 8443 and HTTP to 127.0.0.1 on port 8080.

    +

    You may need to stop an existing pre-installed MTA which blocks port 25/tcp. See this chapter to learn how to reconfigure Postfix to run besides mailcow after a successful installation.

    +

    Some updates modify mailcow.conf and add new parameters. It is hard to keep track of them in the documentation. Please check their description and, if unsure, ask at the known channels for advise.

    +

    4.1. Users with a MTU not equal to 1500 (e.g. OpenStack):

    +

    Whenever you run into trouble and strange phenomena, please check your MTU.

    +

    Edit docker-compose.yml and change the network settings according to your MTU. +Add the new driver_opts parameter like this: +

    networks:
    +  mailcow-network:
    +    ...
    +    driver_opts:
    +      com.docker.network.driver.mtu: 1450
    +    ...
    +

    +

    4.2. Users without an IPv6 enabled network on their host system:

    +

    Enable IPv6. Finally.

    +

    If you do not have an IPv6 enabled network on your host and you don't care for a better internet (thehe), it is recommended to disable IPv6 for the mailcow network to prevent unforeseen issues.

    +

    5. Pull the images and run the compose file. The parameter -d will start mailcow: dockerized detached: +

    docker-compose pull
    +docker-compose up -d
    +

    +

    Done!

    +

    You can now access https://${MAILCOW_HOSTNAME} with the default credentials admin + password moohoo.

    +
    +

    Info

    +

    If you are not using mailcow behind a reverse proxy, you should redirect all HTTP requests to HTTPS.

    +
    +

    The database will be initialized right after a connection to MySQL can be established.

    +

    Your data will persist in multiple Docker volumes, that are not deleted when you recreate or delete containers. Run docker volume ls to see a list of all volumes. You can safely run docker-compose down without removing persistent data.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/i_u_m_migration/index.html b/i_u_m_migration/index.html new file mode 100644 index 000000000..4c22d6f50 --- /dev/null +++ b/i_u_m_migration/index.html @@ -0,0 +1,1980 @@ + + + + + + + + + + + + + + + + + + + Migration - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Migration

    + +
    +

    Warning

    +

    This guide assumes you intend to migrate an existing mailcow server (source) over to a brand new, empty server (target). It takes no care about preserving any existing data on your target server and will erase anything within /var/lib/docker/volumes and thus any Docker volumes you may have already set up.

    +
    +
    +

    Tip

    +

    Alternatively, you can use the ./helper-scripts/backup_and_restore.sh script to create a full backup on the source machine, then install mailcow on the target machine as usual, copy over your mailcow.conf and use the same script to restore your backup to the target machine.

    +
    +

    1. +Install Docker and Docker Compose on your new server.

    +

    Quick installation for most operation systems:

    +
      +
    • +

      Docker +

      curl -sSL https://get.docker.com/ | CHANNEL=stable sh
      +# After the installation process is finished, you may need to enable the service and make sure it is started (e.g. CentOS 7)
      +systemctl enable docker.service
      +

      +
    • +
    • +

      docker-compose +

      curl -L https://github.com/docker/compose/releases/download/$(curl -Ls https://www.servercow.de/docker-compose/latest.php)/docker-compose-$(uname -s)-$(uname -m) > /usr/local/bin/docker-compose
      +chmod +x /usr/local/bin/docker-compose
      +

      +
    • +
    +

    Please use the latest Docker engine available and do not use the engine that ships with your distros repository.

    +

    2. Stop Docker and assure Docker has stopped: +

    systemctl stop docker.service
    +systemctl status docker.service
    +

    +

    3. Run the following commands on the source machine (take care of adding the trailing slashes in the first path parameter as shown below!) - WARNING: This command will erase anything that may already exist under /var/lib/docker/volumes on the target machine: +

    rsync -aHhP --numeric-ids --delete /opt/mailcow-dockerized/ root@target-machine.example.com:/opt/mailcow-dockerized
    +rsync -aHhP --numeric-ids --delete /var/lib/docker/volumes/ root@target-machine.example.com:/var/lib/docker/volumes
    +

    +

    4. Shut down mailcow and stop Docker on the source machine. +

    cd /opt/mailcow-dockerized
    +docker-compose down
    +systemctl stop docker.service
    +

    +

    5. Repeat step 3 with the same commands. This will be much quicker than the first time.

    +

    6. Switch over to the target machine and start Docker. +

    systemctl start docker.service
    +

    +

    7. Now pull the mailcow Docker images on the target machine. +

    cd /opt/mailcow-dockerized
    +docker-compose pull
    +

    +

    8. Start the whole mailcow stack and everything should be done! +

    docker-compose up -d
    +

    +

    9. Finally, change your DNS settings to point to the target server.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/i_u_m_update/index.html b/i_u_m_update/index.html new file mode 100644 index 000000000..b2603eed5 --- /dev/null +++ b/i_u_m_update/index.html @@ -0,0 +1,2110 @@ + + + + + + + + + + + + + + + + + + + Update - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Update

    + +

    Automatic update

    +

    An update script in your mailcow-dockerized directory will take care of updates.

    +

    But use it with caution! If you think you made a lot of changes to the mailcow code, you should use the manual update guide below.

    +

    Run the update script: +

    ./update.sh
    +

    +

    If it needs to, it will ask you how you wish to proceed. +Merge errors will be reported. +Some minor conflicts will be auto-corrected (in favour for the mailcow: dockerized repository code).

    +

    Options

    +
    # Options can be combined
    +
    +# - Check for updates and show changes
    +./update.sh --check
    +
    +# Do not try to update docker-compose, **make sure to use the latest docker-compose available**
    +./update.sh --no-update-compose
    +
    +# - Do not start mailcow after applying an update
    +./update.sh --skip-start
    +
    +# - Force update (unattended, but unsupported, use at own risk)
    +./update.sh --force
    +
    +# - Run garbage collector to cleanup old image tags and exit
    +./update.sh --gc
    +
    +# - Update with merge strategy option "ours" instead of "theirs"
    +#   This will **solve conflicts** when merging in favor for your local changes and should be avoided. Local changes will always be kept, unless we changed file XY, too.
    +./update.sh --ours
    +
    +# - Don't update, but prefetch images and exit
    +./update.sh --prefetch
    +
    +

    I forgot what I changed before running update.sh

    +

    See git log --pretty=oneline | grep -i "before update", you will have an output similar to this:

    +
    22cd00b5e28893ef9ddef3c2b5436453cc5223ab Before update on 2020-09-28_19_25_45
    +dacd4fb9b51e9e1c8a37d84485b92ffaf6c59353 Before update on 2020-08-07_13_31_31
    +
    +

    Run git diff 22cd00b5e28893ef9ddef3c2b5436453cc5223ab to see what changed.

    +

    Can I roll back?

    +

    Yes.

    +

    See the topic above, instead of a diff, you run checkout:

    +
    docker-compose down
    +# Replace commit ID 22cd00b5e28893ef9ddef3c2b5436453cc5223ab by your ID
    +git checkout 22cd00b5e28893ef9ddef3c2b5436453cc5223ab
    +docker-compose pull
    +docker-compose up -d
    +
    +

    Footnotes

    +
      +
    • There is no release cycle regarding updates.
    • +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/images/favicon.png b/images/favicon.png new file mode 100644 index 000000000..6390041d6 Binary files /dev/null and b/images/favicon.png differ diff --git a/images/logo.svg b/images/logo.svg new file mode 100644 index 000000000..ea3b2796b --- /dev/null +++ b/images/logo.svg @@ -0,0 +1,179 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/images/mailcow-backupmx.png b/images/mailcow-backupmx.png new file mode 100644 index 000000000..889b2f7a1 Binary files /dev/null and b/images/mailcow-backupmx.png differ diff --git a/images/mailcow-bl_wl.png b/images/mailcow-bl_wl.png new file mode 100644 index 000000000..3619868f5 Binary files /dev/null and b/images/mailcow-bl_wl.png differ diff --git a/images/mailcow-spamalias.png b/images/mailcow-spamalias.png new file mode 100644 index 000000000..a0e30ccf7 Binary files /dev/null and b/images/mailcow-spamalias.png differ diff --git a/images/mailcow-spamfilter.png b/images/mailcow-spamfilter.png new file mode 100644 index 000000000..dc1d7c3c6 Binary files /dev/null and b/images/mailcow-spamfilter.png differ diff --git a/images/mailcow-tagging.png b/images/mailcow-tagging.png new file mode 100644 index 000000000..20591d514 Binary files /dev/null and b/images/mailcow-tagging.png differ diff --git a/images/thunderbird-tbsync-caldav.png b/images/thunderbird-tbsync-caldav.png new file mode 100644 index 000000000..b9f60023b Binary files /dev/null and b/images/thunderbird-tbsync-caldav.png differ diff --git a/images/thunderbird-tbsync-enable.png b/images/thunderbird-tbsync-enable.png new file mode 100644 index 000000000..f45298d86 Binary files /dev/null and b/images/thunderbird-tbsync-enable.png differ diff --git a/images/thunderbird-tbsync.png b/images/thunderbird-tbsync.png new file mode 100644 index 000000000..0c8680360 Binary files /dev/null and b/images/thunderbird-tbsync.png differ diff --git a/index.html b/index.html new file mode 100644 index 000000000..a7f9a9ee7 --- /dev/null +++ b/index.html @@ -0,0 +1,2143 @@ + + + + + + + + + + + + + + + + + + + mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    🐮 + 🐋 = 💕

    +

    Help mailcow

    +

    Please consider a support contract for a small monthly fee at Servercow EN/Servercow DE to support further development. We support you while you support us. :)

    +

    If you are super awesome and would like to support without a contract, you can get a SAL license that confirms your awesomeness (a flexible one-time payment) at Servercow EN/Servercow DE.

    +

    Get support

    +

    There are two ways to achieve support for your mailcow installation.

    +

    Commercial support

    +

    For professional and prioritized commercial support you can sign a basic support subscription at Servercow EN/Servercow DE. For custom inquiries or questions please contact us at info@servercow.de instead.

    +

    Furthermore we do also provide a fully featured and managed mailcow here. This way we take care about the technical magic underneath and you can enjoy your whole mail experience in a hassle-free way.

    +

    Community support and chat

    +

    The other alternative is our free community-support on our various channels below. Please notice, that this support is driven by our awesome community around mailcow. This kind of support is best-effort, voluntary and there is no guarantee for anything.

    + +

    Telegram desktop clients are available for multiple platforms. You can search the groups history for keywords.

    +

    For bug tracking, feature requests and code contributions only:

    + +

    Demo

    +

    You can find a demo at demo.mailcow.email, use the following credentials to login:

    +
      +
    • Administrator: admin / moohoo
    • +
    • Domain administrator: department / moohoo
    • +
    • Mailbox: demo@440044.xyz / moohoo
    • +
    +

    Overview

    +

    The integrated mailcow UI allows administrative work on your mail server instance as well as separated domain administrator and mailbox user access:

    +
      +
    • DKIM and ARC support
    • +
    • Black- and whitelists per domain and per user
    • +
    • Spam score management per-user (reject spam, mark spam, greylist)
    • +
    • Allow mailbox users to create temporary spam aliases
    • +
    • Prepend mail tags to subject or move mail to sub folder (per-user)
    • +
    • Allow mailbox users to toggle incoming and outgoing TLS enforcement
    • +
    • Allow users to reset SOGo ActiveSync device caches
    • +
    • imapsync to migrate or pull remote mailboxes regularly
    • +
    • TFA: Yubikey OTP and U2F USB (Google Chrome and derivatives only), TOTP
    • +
    • Add domains, mailboxes, aliases, domain aliases and SOGo resources
    • +
    • Add whitelisted hosts to forward mail to mailcow
    • +
    • Fail2ban-like integration
    • +
    • Quarantine system
    • +
    • Antivirus scanning incl. macro scanning in office documents
    • +
    • Integrated basic monitoring
    • +
    • A lot more...
    • +
    +

    mailcow: dockerized comes with multiple containers linked in one bridged network. +Each container represents a single application.

    + +

    Docker volumes to keep dynamic data - take care of them!

    +
      +
    • vmail-vol-1
    • +
    • solr-vol-1
    • +
    • redis-vol-1
    • +
    • mysql-vol-1
    • +
    • rspamd-vol-1
    • +
    • postfix-vol-1
    • +
    • crypt-vol-1
    • +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/model-acl/index.html b/model-acl/index.html new file mode 100644 index 000000000..c1d3d508f --- /dev/null +++ b/model-acl/index.html @@ -0,0 +1,1947 @@ + + + + + + + + + + + + + + + + + + + ACL - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    ACL

    + +

    Editing a domain administrator or a mailbox user allows to set restrictions to that account.

    +

    Important: For overlapping modules like sync jobs, which both domain administrators and mailbox users can be granted access to, the domain administrators permissions are inherited, when logging in as mailbox user.

    +

    Some examples:

    +

    1.

    +
      +
    • A domain administror has not access to sync jobs but can login as mailbox user
    • +
    • When logging in as mailbox user, he does not gain access to sync jobs, even if the given mailbox user has access when logging in directly
    • +
    +

    2.

    +
      +
    • A domain administror has access to sync jobs and can login as mailbox user
    • +
    • The mailbox user he tries to login as has not access to sync jobs
    • +
    • The domain administrator, now logged in as mailbox user, inherits its permission to the mailbox user and can access sync jobs
    • +
    +

    3.

    +
      +
    • A domain administrator logs in as mailbox user
    • +
    • Every permission, that does not exist in a domain administrators ACL, is automatically granted (example: time-limited alias, TLS policy etc.)
    • +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/model-passwd/index.html b/model-passwd/index.html new file mode 100644 index 000000000..60b23f135 --- /dev/null +++ b/model-passwd/index.html @@ -0,0 +1,2047 @@ + + + + + + + + + + + + + + + + + + + Password hashing - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Password hashing

    + +

    Fully supported hashing methods

    +

    The most current mailcow fully supports the following hashing methods. +The default hashing method is written in bold:

    +
      +
    • BLF-CRYPT
    • +
    • SSHA
    • +
    • SSHA256
    • +
    • SSHA512
    • +
    +

    The methods above can be used in mailcow.conf as MAILCOW_PASS_SCHEME value.

    +

    Read-only hashing methods

    +

    The following methods are supported read only. +If you plan to use SOGo (as per default), you need a SOGo compatible hashing method. Please see the note at the bottom of this page how to update the view if necessary. +With SOGo disabled, all hashing methods below will be able to be read by mailcow and Dovecot.

    +
      +
    • ARGON2I (SOGo compatible)
    • +
    • ARGON2ID (SOGo compatible)
    • +
    • CLEAR
    • +
    • CLEARTEXT
    • +
    • CRYPT (SOGo compatible)
    • +
    • DES-CRYPT
    • +
    • LDAP-MD5 (SOGo compatible)
    • +
    • MD5 (SOGo compatible)
    • +
    • MD5-CRYPT (SOGo compatible)
    • +
    • PBKDF2 (SOGo compatible)
    • +
    • PLAIN (SOGo compatible)
    • +
    • PLAIN-MD4
    • +
    • PLAIN-MD5
    • +
    • PLAIN-TRUNC
    • +
    • SHA (SOGo compatible)
    • +
    • SHA1 (SOGo compatible)
    • +
    • SHA256 (SOGo compatible)
    • +
    • SHA256-CRYPT (SOGo compatible)
    • +
    • SHA512 (SOGo compatible)
    • +
    • SHA512-CRYPT (SOGo compatible)
    • +
    • SMD5 (SOGo compatible)
    • +
    +

    That means mailcow is able to verify users with a hash like {MD5}1a1dc91c907325c69271ddf0c944bc72 from the database.

    +

    The value of MAILCOW_PASS_SCHEME will always be used to encrypt new passwords.

    +
    +
    +

    I changed the password hashes in the "mailbox" SQL table and cannot login.

    +
    +

    A "view" needs to be updated. You can trigger this by restarting sogo-mailcow: docker-compose restart sogo-mailcow

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/model-sender_rcv/index.html b/model-sender_rcv/index.html new file mode 100644 index 000000000..104caac05 --- /dev/null +++ b/model-sender_rcv/index.html @@ -0,0 +1,2025 @@ + + + + + + + + + + + + + + + + + + + Sender and receiver model - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Sender and receiver model

    + +

    When a mailbox is created, a user is allowed to send mail from and receive mail for his own mailbox address.

    +
    Mailbox me@example.org is created. example.org is a primary domain.
    +Note: a mailbox cannot be created in an alias domain.
    +
    +me@example.org is only known as me@example.org.
    +me@example.org is allowed to send as me@example.org.
    +
    + +

    We can add an alias domain for example.org:

    +
    Alias domain alias.com is added and assigned to primary domain example.org.
    +me@example.org is now known as me@example.org and me@alias.com.
    +me@example.org is now allowed to send as me@example.org and me@alias.com.
    +
    + +

    We can add aliases for a mailbox to receive mail for and to send from this new address.

    +

    It is important to know, that you are not able to receive mail for my-alias@my-alias-domain.tld. You would need to create this particular alias.

    +
    me@example.org is assigned the alias alias@example.org
    +me@example.org is now known as me@example.org, me@alias.com, alias@example.org
    +
    +me@example.org is NOT known as alias@alias.com.
    +
    + +

    Please note that this does not apply to catch-all aliases:

    +
    Alias domain alias.com is added and assigned to primary domain example.org
    +me@example.org is assigned the catch-all alias @example.org
    +me@example.org is still just known as me@example.org, which is the only available send-as option
    +
    +Any email send to alias.com will match the catch-all alias for example.org
    +
    + +

    Administrators and domain administrators can edit mailboxes to allow specific users to send as other mailbox users ("delegate" them).

    +

    You can choose between mailbox users or completely disable the sender check for domains.

    +

    SOGo "mail from" addresses

    +

    Mailbox users can, obviously, select their own mailbox address, as well as all alias addresses and aliases that exist through alias domains.

    +

    If you want to select another existing mailbox user as your "mail from" address, this user has to delegate you access through SOGo (see SOGo documentation). Moreover a mailcow (domain) administrator +needs to grant you access as described above.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/prerequisite-dns/index.html b/prerequisite-dns/index.html new file mode 100644 index 000000000..1b816fdc7 --- /dev/null +++ b/prerequisite-dns/index.html @@ -0,0 +1,2228 @@ + + + + + + + + + + + + + + + + + + + DNS setup - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + +

    DNS setup

    + +

    Below you can find a list of recommended DNS records. While some are mandatory for a mail server (A, MX), others are recommended to build a good reputation score (TXT/SPF) or used for auto-configuration of mail clients (SRV).

    +

    References

    + +

    Reverse DNS of your IP address

    +

    Make sure that the PTR record of your IP address matches the FQDN of your mailcow host: ${MAILCOW_HOSTNAME} 1. This record is usually set at the provider you leased the IP address (server) from.

    +

    The minimal DNS configuration

    +

    This example shows you a set of records for one domain managed by mailcow. Each domain that is added to mailcow needs at least this set of records to function correctly.

    +
    # Name              Type       Value
    +mail                IN A       1.2.3.4
    +autodiscover        IN CNAME   mail.example.org. (your ${MAILCOW_HOSTNAME})
    +autoconfig          IN CNAME   mail.example.org. (your ${MAILCOW_HOSTNAME})
    +@                   IN MX 10   mail.example.org. (your ${MAILCOW_HOSTNAME})
    +
    +

    DKIM, SPF and DMARC

    +

    In the example DNS zone file snippet below, a simple SPF TXT record is used to only allow THIS server (the MX) to send mail for your domain. Every other server is disallowed but able to ("~all"). Please refer to SPF Project for further reading.

    +
    # Name              Type       Value
    +@                   IN TXT     "v=spf1 mx a -all"
    +
    +

    It is highly recommended to create a DKIM TXT record in your mailcow UI and set the corresponding TXT record in your DNS records. Please refer to OpenDKIM for further reading.

    +
    # Name              Type       Value
    +dkim._domainkey     IN TXT     "v=DKIM1; k=rsa; t=s; s=email; p=..."
    +
    +

    The last step in protecting yourself and others is the implementation of a DMARC TXT record, for example by using the DMARC Assistant (check).

    +
    # Name              Type       Value
    +_dmarc              IN TXT     "v=DMARC1; p=reject; rua=mailto:mailauth-reports@example.org"
    +
    +

    The advanced DNS configuration

    +

    SRV records specify the server(s) for a specific protocol on your domain. If you want to explicitly announce a service as not provided, give "." as the target address (instead of "mail.example.org."). Please refer to RFC 2782.

    +
    # Name              Type       Priority Weight Port    Value
    +_autodiscover._tcp  IN SRV     0        1      443      mail.example.org. (your ${MAILCOW_HOSTNAME})
    +_caldavs._tcp       IN SRV     0        1      443      mail.example.org. (your ${MAILCOW_HOSTNAME})
    +_caldavs._tcp       IN TXT                              "path=/SOGo/dav/"
    +_carddavs._tcp      IN SRV     0        1      443      Mail.example.org. (your ${MAILCOW_HOSTNAME})
    +_carddavs._tcp      IN TXT                              "path=/SOGo/dav/"
    +_imap._tcp          IN SRV     0        1      143      mail.example.org. (your ${MAILCOW_HOSTNAME})
    +_imaps._tcp         IN SRV     0        1      993      mail.example.org. (your ${MAILCOW_HOSTNAME})
    +_pop3._tcp          IN SRV     0        1      110      mail.example.org. (your ${MAILCOW_HOSTNAME})
    +_pop3s._tcp         IN SRV     0        1      995      mail.example.org. (your ${MAILCOW_HOSTNAME})
    +_sieve._tcp         IN SRV     0        1      4190     mail.example.org. (your ${MAILCOW_HOSTNAME})
    +_smtps._tcp         IN SRV     0        1      465      mail.example.org. (your ${MAILCOW_HOSTNAME})
    +_submission._tcp    IN SRV     0        1      587      mail.example.org. (your ${MAILCOW_HOSTNAME})
    +
    +

    Testing

    +

    Here are some tools you can use to verify your DNS configuration:

    + +

    Misc

    +

    Optional DMARC Statistics

    +

    If you are interested in statistics, you can additionally register with some of the many below DMARC statistic services, or self-host your own.

    +

    NOTE: It is worth considering that if you request DMARC statistic reports to your mailcow server, if there are issues with that domain you may not get accurate results. You can consider using an alternative email domain for recieving DMARC reports.

    +

    It is worth mentioning, that the following suggestions are not a comprehensive list of all services and tools avaialble, but only a small few of the many choices.

    + +

    NOTE: The services may provide you with a TXT record, which you would insert into your DNS records as the provider specifies. This record will give you details about spam-classified mails by your domain. However, please ensure to read the providers documentation from the service you choose, as this process may vary and not all providers may use a TXT record.

    +

    Email Test for SPF, DKIM and DMARC:

    +

    To test send an email to the email below and wait for a reply:

    +

    check-auth@verifier.port25.com

    +

    You will get a report back that looks like the following:

    +

    ==========================================================
    +Summary of Results
    +==========================================================
    +SPF check:          pass
    +"iprev" check:      pass
    +DKIM check:         pass
    +DKIM check:         pass
    +SpamAssassin check: ham
    +
    +==========================================================
    +Details:
    +==========================================================
    +....
    +
    +The full report will contain more technical details this is just the first section, we found this to be quite usful for testing both outgoing mail and spam scores.

    +

    Fully Qualified Domain Name (FQDN)

    +
    +
    +
      +
    1. +

      A Fully Qualified Domain Name (FQDN) is the complete (absolute) domain name for a specific computer or host, on the Internet. The FQDN consists of at least three parts divided by a dot: the hostname (myhost), the domain name (mydomain) and the top level domain in short tld (com). In the example of mx.mailcow.email the hostname would be mx, the domain name mailcow and the tld email

      +
    2. +
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/prerequisite-system/index.html b/prerequisite-system/index.html new file mode 100644 index 000000000..3aaf1b794 --- /dev/null +++ b/prerequisite-system/index.html @@ -0,0 +1,2344 @@ + + + + + + + + + + + + + + + + + + + Prepare your system - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Prepare your system

    + +

    Before you run mailcow: dockerized, there are a few requirements that you should check:

    +
    +

    Warning

    +

    Do not try to install mailcow on a Synology/QNAP device (any NAS), OpenVZ, LXC or other container platforms. KVM, ESX, Hyper-V and other full virtualization platforms are supported. +We do not recommend to use CentOS 8 anymore!

    +
    +
    +

    Info

    +
      +
    • mailcow: dockerized requires some ports to be open for incoming connections, so make sure that your firewall is not blocking these.
    • +
    • Make sure that no other application is interfering with mailcow's configuration, such as another mail service
    • +
    • A correct DNS setup is crucial to every good mailserver setup, so please make sure you got at least the basics covered before you begin!
    • +
    • Make sure that your system has a correct date and time setup. This is crucial for various components like two factor TOTP authentication.
    • +
    +
    +

    Minimum System Resources

    +

    OpenVZ, Virtuozzo and LXC are not supported.

    +

    Please make sure that your system has at least the following resources:

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Resourcemailcow: dockerized
    CPU1 GHz
    RAM                    Minimum 6 GiB + 1 GiB swap (default config)
    Disk20 GiB (without emails)
    System Typex86_64
    +

    We recommend using any distribution listed as supported by Docker CE (check https://docs.docker.com/install/). We test on CentOS 7, Debian 9/10 and Ubuntu 18.04/20.04.

    +

    ClamAV and Solr can be greedy with RAM. You may disable them in mailcow.conf by settings SKIP_CLAMD=y and SKIP_SOLR=y.

    +

    Info: We are aware that a pure MTA can run on 128 MiB RAM. mailcow is a full-grown and ready-to-use groupware with many extras making life easier. mailcow comes with a webserver, webmailer, ActiveSync (MS), antivirus, antispam, indexing (Solr), document scanner (Oletools), SQL (MariaDB), Cache (Redis), MDA, MTA, various web services etc.

    +

    A single SOGo worker can acquire ~350 MiB RAM before it gets purged. The more ActiveSync connections you plan to use, the more RAM you will need. A default configuration spawns 20 workers.

    +

    Usage examples

    +

    A company with 15 phones (EAS enabled) and about 50 concurrent IMAP connections should plan 16 GiB RAM.

    +

    6 GiB RAM + 1 GiB swap are fine for most private installations while 8 GiB RAM are recommended for ~5 to 10 users.

    +

    We can help to correctly plan your setup as part of our support.

    +

    Firewall & Ports

    +

    Please check if any of mailcow's standard ports are open and not in use by other applications:

    +
    ss -tlpn | grep -E -w '25|80|110|143|443|465|587|993|995|4190|5222|5269|5443'
    +# or:
    +netstat -tulpn | grep -E -w '25|80|110|143|443|465|587|993|995|4190|5222|5269|5443'
    +
    +
    +

    Warning

    +

    There are several problems with running mailcow on a firewalld/ufw enabled system. You should disable it (if possible) and move your ruleset to the DOCKER-USER chain, which is not cleared by a Docker service restart, instead. See this (blog.donnex.net) or this (unrouted.io) guide for information about how to use iptables-persistent with the DOCKER-USER chain. +As mailcow runs dockerized, INPUT rules have no effect on restricting access to mailcow. Use the FORWARD chain instead.

    +
    +

    If this command returns any results please remove or stop the application running on that port. You may also adjust mailcows ports via the mailcow.conf configuration file.

    +

    Default Ports

    +

    If you have a firewall in front of mailcow, please make sure that these ports are open for incoming connections:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ServiceProtocolPortContainerVariable
    Postfix SMTPTCP25postfix-mailcow${SMTP_PORT}
    Postfix SMTPSTCP465postfix-mailcow${SMTPS_PORT}
    Postfix SubmissionTCP587postfix-mailcow${SUBMISSION_PORT}
    Dovecot IMAPTCP143dovecot-mailcow${IMAP_PORT}
    Dovecot IMAPSTCP993dovecot-mailcow${IMAPS_PORT}
    Dovecot POP3TCP110dovecot-mailcow${POP_PORT}
    Dovecot POP3STCP995dovecot-mailcow${POPS_PORT}
    Dovecot ManageSieveTCP4190dovecot-mailcow${SIEVE_PORT}
    HTTP(S)TCP80/443nginx-mailcow${HTTP_PORT} / ${HTTPS_PORT}
    XMPP (c2s)TCP5222ejabberd-mailcow${XMPP_C2S_PORT}
    XMPP (s2s)TCP5269ejabberd-mailcow${XMPP_C2S_PORT}
    XMPP (upload)TCP5443ejabberd-mailcow${XMPP_HTTPS_PORT}
    +

    To bind a service to an IP address, you can prepend the IP like this: SMTP_PORT=1.2.3.4:25

    +

    Important: You cannot use IP:PORT bindings in HTTP_PORT and HTTPS_PORT. Please use HTTP_PORT=1234 and HTTP_BIND=1.2.3.4 instead.

    +

    Important for Hetzner firewalls

    +

    Quoting https://github.com/chermsen via https://github.com/mailcow/mailcow-dockerized/issues/497#issuecomment-469847380 (THANK YOU!):

    +

    For all who are struggling with the Hetzner firewall:

    +

    Port 53 unimportant for the firewall configuration in this case. According to the documentation unbound uses the port range 1024-65535 for outgoing requests. +Since the Hetzner Robot Firewall is a static firewall (each incoming packet is checked isolated) - the following rules must be applied:

    +

    For TCP +

    SRC-IP:       ---
    +DST IP:       ---
    +SRC Port:    ---
    +DST Port:    1024-65535
    +Protocol:    tcp
    +TCP flags:   ack
    +Action:      Accept
    +

    +

    For UDP +

    SRC-IP:       ---
    +DST IP:       ---
    +SRC Port:    ---
    +DST Port:    1024-65535
    +Protocol:    udp
    +Action:      Accept
    +

    +

    If you want to apply a more restrictive port range you have to change the config of unbound first (after installation):

    +

    {mailcow-dockerized}/data/conf/unbound/unbound.conf: +

    outgoing-port-avoid: 0-32767
    +

    +

    Now the firewall rules can be adjusted as follows:

    +
    [...]
    +DST Port:  32768-65535
    +[...]
    +
    +

    Date and Time

    +

    To ensure that you have the correct date and time setup on your system, please check the output of timedatectl status:

    +
    $ timedatectl status
    +      Local time: Sat 2017-05-06 02:12:33 CEST
    +  Universal time: Sat 2017-05-06 00:12:33 UTC
    +        RTC time: Sat 2017-05-06 00:12:32
    +       Time zone: Europe/Berlin (CEST, +0200)
    +     NTP enabled: yes
    +NTP synchronized: yes
    + RTC in local TZ: no
    +      DST active: yes
    + Last DST change: DST began at
    +                  Sun 2017-03-26 01:59:59 CET
    +                  Sun 2017-03-26 03:00:00 CEST
    + Next DST change: DST ends (the clock jumps one hour backwards) at
    +                  Sun 2017-10-29 02:59:59 CEST
    +                  Sun 2017-10-29 02:00:00 CET
    +
    +

    The lines NTP enabled: yes and NTP synchronized: yes indicate whether you have NTP enabled and if it's synchronized.

    +

    To enable NTP you need to run the command timedatectl set-ntp true. You also need to edit your /etc/systemd/timesyncd.conf:

    +
    # vim /etc/systemd/timesyncd.conf
    +[Time]
    +Servers=0.pool.ntp.org 1.pool.ntp.org 2.pool.ntp.org 3.pool.ntp.org
    +
    +

    Hetzner Cloud (and probably others)

    +

    Check /etc/network/interfaces.d/50-cloud-init.cfg and change the IPv6 interface from eth0:0 to eth0:

    +
    # Wrong:
    +auto eth0:0
    +iface eth0:0 inet6 static
    +# Right:
    +auto eth0
    +iface eth0 inet6 static
    +
    +

    Reboot or restart the interface. +You may want to disable cloud-init network changes.

    +

    MTU

    +

    Especially relevant for OpenStack users: Check your MTU and set it accordingly in docker-compose.yml. See 4.1 in our installation docs.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/restrictions_ip_accss/index.html b/restrictions_ip_accss/index.html new file mode 100644 index 000000000..6289261fa --- /dev/null +++ b/restrictions_ip_accss/index.html @@ -0,0 +1,1935 @@ + + + + + + + + + + + + + + + + + + + Restrictions ip accss - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    WIP

    +

    Protocol restrictions and IP access

    +

    Denied access will be shown to the user as failed login attempts.

    +

    Protocol restrictions in Dovecot

    +

    Protocol restrictions work by filtering the passdb query for IMAP and POP3 as well as reading the JSON value for %s_access where %s reflects the protocol seen by Dovecot.

    +

    In the future we may use virtual colums in SQL to add an index on these values.

    +

    Protocol restrictions in Postfix

    +

    Filtering SMTP protocol access works by using a check_sasl_map in the smtpd_recipient_restrictions.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 000000000..ac0f0d2db --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"\ud83d\udc2e + \ud83d\udc0b = \ud83d\udc95 \u00b6 Help mailcow \u00b6 Please consider a support contract for a small monthly fee at Servercow EN / Servercow DE to support further development. We support you while you support us . :) If you are super awesome and would like to support without a contract, you can get a SAL license that confirms your awesomeness (a flexible one-time payment) at Servercow EN / Servercow DE . Get support \u00b6 There are two ways to achieve support for your mailcow installation. Commercial support \u00b6 For professional and prioritized commercial support you can sign a basic support subscription at Servercow EN / Servercow DE . For custom inquiries or questions please contact us at info@servercow.de instead. Furthermore we do also provide a fully featured and managed mailcow here . This way we take care about the technical magic underneath and you can enjoy your whole mail experience in a hassle-free way. Community support and chat \u00b6 The other alternative is our free community-support on our various channels below. Please notice, that this support is driven by our awesome community around mailcow. This kind of support is best-effort, voluntary and there is no guarantee for anything. Our mailcow community @ community.mailcow.email Telegram @ t.me/mailcow . Telegram @ t.me/mailcowOfftopic . Telegram desktop clients are available for multiple platforms . You can search the groups history for keywords. For bug tracking, feature requests and code contributions only: GitHub @ mailcow/mailcow-dockerized Demo \u00b6 You can find a demo at demo.mailcow.email , use the following credentials to login: Administrator : admin / moohoo Domain administrator : department / moohoo Mailbox : demo@440044.xyz / moohoo Overview \u00b6 The integrated mailcow UI allows administrative work on your mail server instance as well as separated domain administrator and mailbox user access: DKIM and ARC support Black- and whitelists per domain and per user Spam score management per-user (reject spam, mark spam, greylist) Allow mailbox users to create temporary spam aliases Prepend mail tags to subject or move mail to sub folder (per-user) Allow mailbox users to toggle incoming and outgoing TLS enforcement Allow users to reset SOGo ActiveSync device caches imapsync to migrate or pull remote mailboxes regularly TFA: Yubikey OTP and U2F USB (Google Chrome and derivatives only), TOTP Add domains, mailboxes, aliases, domain aliases and SOGo resources Add whitelisted hosts to forward mail to mailcow Fail2ban-like integration Quarantine system Antivirus scanning incl. macro scanning in office documents Integrated basic monitoring A lot more... mailcow: dockerized comes with multiple containers linked in one bridged network. Each container represents a single application. Dovecot ClamAV (optional) Solr (optional) Oletools via Olefy Memcached Redis MariaDB Unbound PHP Postfix ACME Nginx Rspamd SOGo Netfilter (Fail2ban-like integration by @mkuron ) A Watchdog to provide basic monitoring Docker volumes to keep dynamic data - take care of them! vmail-vol-1 solr-vol-1 redis-vol-1 mysql-vol-1 rspamd-vol-1 postfix-vol-1 crypt-vol-1","title":"Information & Support"},{"location":"#_1","text":"","title":"\ud83d\udc2e + \ud83d\udc0b = \ud83d\udc95"},{"location":"#help-mailcow","text":"Please consider a support contract for a small monthly fee at Servercow EN / Servercow DE to support further development. We support you while you support us . :) If you are super awesome and would like to support without a contract, you can get a SAL license that confirms your awesomeness (a flexible one-time payment) at Servercow EN / Servercow DE .","title":"Help mailcow"},{"location":"#get-support","text":"There are two ways to achieve support for your mailcow installation.","title":"Get support"},{"location":"#commercial-support","text":"For professional and prioritized commercial support you can sign a basic support subscription at Servercow EN / Servercow DE . For custom inquiries or questions please contact us at info@servercow.de instead. Furthermore we do also provide a fully featured and managed mailcow here . This way we take care about the technical magic underneath and you can enjoy your whole mail experience in a hassle-free way.","title":"Commercial support"},{"location":"#community-support-and-chat","text":"The other alternative is our free community-support on our various channels below. Please notice, that this support is driven by our awesome community around mailcow. This kind of support is best-effort, voluntary and there is no guarantee for anything. Our mailcow community @ community.mailcow.email Telegram @ t.me/mailcow . Telegram @ t.me/mailcowOfftopic . Telegram desktop clients are available for multiple platforms . You can search the groups history for keywords. For bug tracking, feature requests and code contributions only: GitHub @ mailcow/mailcow-dockerized","title":"Community support and chat"},{"location":"#demo","text":"You can find a demo at demo.mailcow.email , use the following credentials to login: Administrator : admin / moohoo Domain administrator : department / moohoo Mailbox : demo@440044.xyz / moohoo","title":"Demo"},{"location":"#overview","text":"The integrated mailcow UI allows administrative work on your mail server instance as well as separated domain administrator and mailbox user access: DKIM and ARC support Black- and whitelists per domain and per user Spam score management per-user (reject spam, mark spam, greylist) Allow mailbox users to create temporary spam aliases Prepend mail tags to subject or move mail to sub folder (per-user) Allow mailbox users to toggle incoming and outgoing TLS enforcement Allow users to reset SOGo ActiveSync device caches imapsync to migrate or pull remote mailboxes regularly TFA: Yubikey OTP and U2F USB (Google Chrome and derivatives only), TOTP Add domains, mailboxes, aliases, domain aliases and SOGo resources Add whitelisted hosts to forward mail to mailcow Fail2ban-like integration Quarantine system Antivirus scanning incl. macro scanning in office documents Integrated basic monitoring A lot more... mailcow: dockerized comes with multiple containers linked in one bridged network. Each container represents a single application. Dovecot ClamAV (optional) Solr (optional) Oletools via Olefy Memcached Redis MariaDB Unbound PHP Postfix ACME Nginx Rspamd SOGo Netfilter (Fail2ban-like integration by @mkuron ) A Watchdog to provide basic monitoring Docker volumes to keep dynamic data - take care of them! vmail-vol-1 solr-vol-1 redis-vol-1 mysql-vol-1 rspamd-vol-1 postfix-vol-1 crypt-vol-1","title":"Overview"},{"location":"b_n_r_backup/","text":"Backup \u00b6 You can use the provided script helper-scripts/backup_and_restore.sh to backup mailcow automatically. Please do not copy this script to another location. To run a backup, write \"backup\" as first parameter and either one or more components to backup as following parameters. You can also use \"all\" as second parameter to backup all components. Append --delete-days n to delete backups older than n days. # Syntax: # ./helper-scripts/backup_and_restore.sh backup (vmail|crypt|redis|rspamd|postfix|mysql|all|--delete-days) # Backup all, delete backups older than 3 days ./helper-scripts/backup_and_restore.sh backup all --delete-days 3 # Backup vmail, crypt and mysql data, delete backups older than 30 days ./helper-scripts/backup_and_restore.sh backup vmail crypt mysql --delete-days 30 # Backup vmail ./helper-scripts/backup_and_restore.sh backup vmail The script will ask you for a backup location. Inside of this location it will create folders in the format \"mailcow_DATE\". You should not rename those folders to not break the restore process. To run a backup unattended, define MAILCOW_BACKUP_LOCATION as environment variable before starting the script: MAILCOW_BACKUP_LOCATION=/opt/backup /opt/mailcow-dockerized/helper-scripts/backup_and_restore.sh backup all","title":"Backup"},{"location":"b_n_r_backup/#backup","text":"You can use the provided script helper-scripts/backup_and_restore.sh to backup mailcow automatically. Please do not copy this script to another location. To run a backup, write \"backup\" as first parameter and either one or more components to backup as following parameters. You can also use \"all\" as second parameter to backup all components. Append --delete-days n to delete backups older than n days. # Syntax: # ./helper-scripts/backup_and_restore.sh backup (vmail|crypt|redis|rspamd|postfix|mysql|all|--delete-days) # Backup all, delete backups older than 3 days ./helper-scripts/backup_and_restore.sh backup all --delete-days 3 # Backup vmail, crypt and mysql data, delete backups older than 30 days ./helper-scripts/backup_and_restore.sh backup vmail crypt mysql --delete-days 30 # Backup vmail ./helper-scripts/backup_and_restore.sh backup vmail The script will ask you for a backup location. Inside of this location it will create folders in the format \"mailcow_DATE\". You should not rename those folders to not break the restore process. To run a backup unattended, define MAILCOW_BACKUP_LOCATION as environment variable before starting the script: MAILCOW_BACKUP_LOCATION=/opt/backup /opt/mailcow-dockerized/helper-scripts/backup_and_restore.sh backup all","title":"Backup"},{"location":"b_n_r_restore/","text":"Restore \u00b6 Please do not copy this script to another location. To run a restore, start mailcow , use the script with \"restore\" as first parameter. # Syntax: # ./helper-scripts/backup_and_restore.sh restore The script will ask you for a backup location containing the mailcow_DATE folders.","title":"Restore"},{"location":"b_n_r_restore/#restore","text":"Please do not copy this script to another location. To run a restore, start mailcow , use the script with \"restore\" as first parameter. # Syntax: # ./helper-scripts/backup_and_restore.sh restore The script will ask you for a backup location containing the mailcow_DATE folders.","title":"Restore"},{"location":"client/","text":"mailcow supports a variety of email clients, both on desktop computers and on smartphones. Below, you can find a number of configuration guides that explain how to configure your mailcow account. Tip If you access this page by logging into your mailcow server and clicking the \"Show configuration guides for email clients and smartphones\" link, all of the guides will be personalized with your email address and server name. Success Since you accessed this page after logging into your mailcow server, all of the guides have been personalized with your email address and server name. Android Apple iOS / macOS eM Client KDE Kontact / KMail Microsoft Outlook Mozilla Thunderbird Windows Mail Windows Phone Manual configuration","title":"Overview"},{"location":"debug-admin_login_sogo/","text":"This is an experimental feature that allows admins and domain admins to directly log into SOGo as a mailbox user, without knowing the users password. For this, an additional link to SOGo is displayed in the mailbox list. Multiple concurrent admin-logins to different mailboxes are also possible when using this feature. Enabling the feature \u00b6 The feature is disabled by default. It can be enabled in the mailcow.conf by setting: ALLOW_ADMIN_EMAIL_LOGIN=y and recreating the affected containers with docker-compose up -d Drawbacks when enabled \u00b6 Each SOGo page-load and each Active-Sync request will cause an additional execution of an internal PHP script. This might impact load-times of SOGo / EAS. In most cases, this should not be noticeable but should be kept in mind if you face any performance issues. SOGo will not display a logout link for admin-logins, to login normally one has to logout from the mailcow UI so the PHP session is destroyed. Technical details \u00b6 SOGoTrustProxyAuthentication option is set to YES which makes SOGo trust the x-webobjects-remote-user header. Dovecot will receive a random master-password which is valid for all mailboxes when used by the SOGo container. Clicking on the SOGo button in the mailbox list will open sogo-auth.php which checks permissions, sets session variables and redirects to the SOGo mailbox. Each SOGo, CardDAV, CalDAV and EAS http request will cause an additional, nginx internal auth_request call to sogo-auth.php with the following behavior: If a basic_auth header is present, the script will validate the credentials in place of SOGo and provide the following headers: x-webobjects-remote-user , Authorization and x-webobjects-auth-type . If no basic_auth header is present, the script will check for an active mailcow admin session for the requested email user and provide the same headers but with the dovecot master password used in the Authorization header. If both fails the headers will be set empty, which makes SOGo use its standard authentication methods. All of these options / behaviors are disabled if the ALLOW_ADMIN_EMAIL_LOGIN is not enabled in the config.","title":"Admin login to SOGo"},{"location":"debug-admin_login_sogo/#enabling-the-feature","text":"The feature is disabled by default. It can be enabled in the mailcow.conf by setting: ALLOW_ADMIN_EMAIL_LOGIN=y and recreating the affected containers with docker-compose up -d","title":"Enabling the feature"},{"location":"debug-admin_login_sogo/#drawbacks-when-enabled","text":"Each SOGo page-load and each Active-Sync request will cause an additional execution of an internal PHP script. This might impact load-times of SOGo / EAS. In most cases, this should not be noticeable but should be kept in mind if you face any performance issues. SOGo will not display a logout link for admin-logins, to login normally one has to logout from the mailcow UI so the PHP session is destroyed.","title":"Drawbacks when enabled"},{"location":"debug-admin_login_sogo/#technical-details","text":"SOGoTrustProxyAuthentication option is set to YES which makes SOGo trust the x-webobjects-remote-user header. Dovecot will receive a random master-password which is valid for all mailboxes when used by the SOGo container. Clicking on the SOGo button in the mailbox list will open sogo-auth.php which checks permissions, sets session variables and redirects to the SOGo mailbox. Each SOGo, CardDAV, CalDAV and EAS http request will cause an additional, nginx internal auth_request call to sogo-auth.php with the following behavior: If a basic_auth header is present, the script will validate the credentials in place of SOGo and provide the following headers: x-webobjects-remote-user , Authorization and x-webobjects-auth-type . If no basic_auth header is present, the script will check for an active mailcow admin session for the requested email user and provide the same headers but with the dovecot master password used in the Authorization header. If both fails the headers will be set empty, which makes SOGo use its standard authentication methods. All of these options / behaviors are disabled if the ALLOW_ADMIN_EMAIL_LOGIN is not enabled in the config.","title":"Technical details"},{"location":"debug-attach_service/","text":"Attaching a Container to your Shell \u00b6 To attach a container to your shell you can simply run docker-compose exec $Service_Name /bin/bash Connecting to Services \u00b6 If you want to connect to a service / application directly it is always a good idea to source mailcow.conf to get all relevant variables into your environment. MySQL \u00b6 source mailcow.conf docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} Redis \u00b6 docker-compose exec redis-mailcow redis-cli Service Descriptions \u00b6 Here is a brief overview of what container / service does what: Service Name Service Descriptions unbound-mailcow Local (DNSSEC) DNS Resolver mysql-mailcow Stores SOGo's and most of mailcow's settings postfix-mailcow Receives and sends mails dovecot-mailcow User logins and sieve filter redis-mailcow Storage back-end for DKIM keys and Rspamd rspamd-mailcow Mail filtering system. Used for av handling, dkim signing, spam handling clamd-mailcow Scans attachments for viruses olefy-mailcow Scans attached office documents for macro-viruses solr-mailcow Provides full-text search in Dovecot sogo-mailcow Webmail client that handles Microsoft ActiveSync and Cal- / CardDav nginx-mailcow Nginx remote proxy that handles all mailcow related HTTP / HTTPS requests acme-mailcow Automates HTTPS (SSL/TLS) certificate deployment memcached-mailcow Internal caching system for mailcow services watchdog-mailcow Allows the monitoring of docker containers / services php-fpm-mailcow Powers the mailcow web UI netfilter-mailcow Fail2Ban like integration","title":"Attach a Container"},{"location":"debug-attach_service/#attaching-a-container-to-your-shell","text":"To attach a container to your shell you can simply run docker-compose exec $Service_Name /bin/bash","title":"Attaching a Container to your Shell"},{"location":"debug-attach_service/#connecting-to-services","text":"If you want to connect to a service / application directly it is always a good idea to source mailcow.conf to get all relevant variables into your environment.","title":"Connecting to Services"},{"location":"debug-attach_service/#mysql","text":"source mailcow.conf docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME}","title":"MySQL"},{"location":"debug-attach_service/#redis","text":"docker-compose exec redis-mailcow redis-cli","title":"Redis"},{"location":"debug-attach_service/#service-descriptions","text":"Here is a brief overview of what container / service does what: Service Name Service Descriptions unbound-mailcow Local (DNSSEC) DNS Resolver mysql-mailcow Stores SOGo's and most of mailcow's settings postfix-mailcow Receives and sends mails dovecot-mailcow User logins and sieve filter redis-mailcow Storage back-end for DKIM keys and Rspamd rspamd-mailcow Mail filtering system. Used for av handling, dkim signing, spam handling clamd-mailcow Scans attachments for viruses olefy-mailcow Scans attached office documents for macro-viruses solr-mailcow Provides full-text search in Dovecot sogo-mailcow Webmail client that handles Microsoft ActiveSync and Cal- / CardDav nginx-mailcow Nginx remote proxy that handles all mailcow related HTTP / HTTPS requests acme-mailcow Automates HTTPS (SSL/TLS) certificate deployment memcached-mailcow Internal caching system for mailcow services watchdog-mailcow Allows the monitoring of docker containers / services php-fpm-mailcow Powers the mailcow web UI netfilter-mailcow Fail2Ban like integration","title":"Service Descriptions"},{"location":"debug-common_problems/","text":"Here we list common problems and possible solutions: Mail loops back to myself \u00b6 Please check in your mailcow UI if you made the domain a backup MX : I can receive but not send mails \u00b6 There are a lot of things that could prevent you from sending mail: Check if your IP address is on any blacklists. You could use dnsbl.info or any other similar service to check for your IP address. There are some consumer ISP routers out there, that block mail ports for non whitelisted domains. Please check if you can reach your server on the ports 465 or 587 : # telnet 74.125.133.27 465 Trying 74.125.133.27... Connected to 74.125.133.27. Escape character is '^]'. My mails are identified as Spam \u00b6 Please read our guide on DNS configuration . docker-compose throws weird errors \u00b6 ... like: ERROR: Invalid interpolation format ... AttributeError: 'NoneType' object has no attribute 'keys' . ERROR: In file './docker-compose.yml' service 'version' doesn't have any configuration options . When you encounter one or similar messages while trying to run mailcow: dockerized please check if you have the latest version of Docker and docker-compose Container XY is unhealthy \u00b6 This error tries to tell you that one of the (health) conditions for a certain container are not met. Therefore it can't be started. This can have several reasons, the most common one is an updated git clone but old docker image or vice versa. A wrong configured firewall could also cause such a failure. The containers need to be able to talk to each other over the network 172.22.1.1/24. It might also be wrongly linked file (i.e. SSL certificate) that prevents a crucial container (nginx) from starting, so always check your logs to get an idea where your problem is coming from. Address already in use \u00b6 If you get an error message like: ERROR: for postfix-mailcow Cannot start service postfix-mailcow: driver failed programming external connectivity on endpoint mailcowdockerized_postfix-mailcow_1: Error starting userland proxy: listen tcp 0.0.0.0:25: bind: address already in use while trying to start / install mailcow: dockerized, make sure you've followed our section on the prerequisites . XYZ can't connect to ... \u00b6 Please check your local firewall! Docker and iptables-based firewalls sometimes create conflicting rules, so disable the firewall on your host to determine whether your connection issues are caused by such conflicts. If they are, you need to manually create appropriate rules in your host firewall to permit the necessary connections. If you experience connection problems from home, please check your ISP router's firewall too, some of them block mail traffic on the SMTP (587) or SMTPS (465) ports. It could also be, that your ISP is blocking the ports for SUBMISSION (25). While Linux users can chose from a variety of tools 1 to check if a port is open, the Windows user has only the command telnet host port available by default (and it has to be activated since Windows Vista). To enable telnet on a Windows after Vista please check this guide or enter the following command in an terminal with administrator privileges : dism /online /Enable-Feature /FeatureName:TelnetClient Inotify instance limit for user 5000 (UID vmail) exceeded ( see #453 ) \u00b6 Docker containers use the Docker hosts inotify limits. Setting them on your Docker host will pass them to the container. netcat , nmap , openssl , telnet , etc. \u21a9","title":"Common Problems"},{"location":"debug-common_problems/#mail-loops-back-to-myself","text":"Please check in your mailcow UI if you made the domain a backup MX :","title":"Mail loops back to myself"},{"location":"debug-common_problems/#i-can-receive-but-not-send-mails","text":"There are a lot of things that could prevent you from sending mail: Check if your IP address is on any blacklists. You could use dnsbl.info or any other similar service to check for your IP address. There are some consumer ISP routers out there, that block mail ports for non whitelisted domains. Please check if you can reach your server on the ports 465 or 587 : # telnet 74.125.133.27 465 Trying 74.125.133.27... Connected to 74.125.133.27. Escape character is '^]'.","title":"I can receive but not send mails"},{"location":"debug-common_problems/#my-mails-are-identified-as-spam","text":"Please read our guide on DNS configuration .","title":"My mails are identified as Spam"},{"location":"debug-common_problems/#docker-compose-throws-weird-errors","text":"... like: ERROR: Invalid interpolation format ... AttributeError: 'NoneType' object has no attribute 'keys' . ERROR: In file './docker-compose.yml' service 'version' doesn't have any configuration options . When you encounter one or similar messages while trying to run mailcow: dockerized please check if you have the latest version of Docker and docker-compose","title":"docker-compose throws weird errors"},{"location":"debug-common_problems/#container-xy-is-unhealthy","text":"This error tries to tell you that one of the (health) conditions for a certain container are not met. Therefore it can't be started. This can have several reasons, the most common one is an updated git clone but old docker image or vice versa. A wrong configured firewall could also cause such a failure. The containers need to be able to talk to each other over the network 172.22.1.1/24. It might also be wrongly linked file (i.e. SSL certificate) that prevents a crucial container (nginx) from starting, so always check your logs to get an idea where your problem is coming from.","title":"Container XY is unhealthy"},{"location":"debug-common_problems/#address-already-in-use","text":"If you get an error message like: ERROR: for postfix-mailcow Cannot start service postfix-mailcow: driver failed programming external connectivity on endpoint mailcowdockerized_postfix-mailcow_1: Error starting userland proxy: listen tcp 0.0.0.0:25: bind: address already in use while trying to start / install mailcow: dockerized, make sure you've followed our section on the prerequisites .","title":"Address already in use"},{"location":"debug-common_problems/#xyz-cant-connect-to","text":"Please check your local firewall! Docker and iptables-based firewalls sometimes create conflicting rules, so disable the firewall on your host to determine whether your connection issues are caused by such conflicts. If they are, you need to manually create appropriate rules in your host firewall to permit the necessary connections. If you experience connection problems from home, please check your ISP router's firewall too, some of them block mail traffic on the SMTP (587) or SMTPS (465) ports. It could also be, that your ISP is blocking the ports for SUBMISSION (25). While Linux users can chose from a variety of tools 1 to check if a port is open, the Windows user has only the command telnet host port available by default (and it has to be activated since Windows Vista). To enable telnet on a Windows after Vista please check this guide or enter the following command in an terminal with administrator privileges : dism /online /Enable-Feature /FeatureName:TelnetClient","title":"XYZ can't connect to ..."},{"location":"debug-common_problems/#inotify-instance-limit-for-user-5000-uid-vmail-exceeded-see-453","text":"Docker containers use the Docker hosts inotify limits. Setting them on your Docker host will pass them to the container. netcat , nmap , openssl , telnet , etc. \u21a9","title":"Inotify instance limit for user 5000 (UID vmail) exceeded (see #453)"},{"location":"debug-logs/","text":"Warning This section only applies for Dockers default logging driver (JSON). To view the logs of all mailcow: dockerized related containers, you can use docker-compose logs inside your mailcow-dockerized folder that contains your mailcow.conf . This is usually a bit much, but you could trim the output with --tail=100 to the last 100 lines per container, or add a -f to follow the live output of all your services. To view the logs of a specific service you can use docker-compose logs [options] $service_name Info The available options for the command docker-compose logs are: --no-color : Produce monochrome output. -f : Follow the log output. -t : Show timestamps. --tail=\"all\" : Number of lines to show from the end of the logs for each container.","title":"Logs"},{"location":"debug-mysql_upgrade/","text":"Run a manual mysql_upgrade \u00b6 This step is usually not necessary. docker-compose stop mysql-mailcow watchdog-mailcow docker-compose run --rm --entrypoint '/bin/sh -c \"gosu mysql mysqld --skip-grant-tables & sleep 10 && bash && exit 0\"' mysql-mailcow As soon as the SQL shell spawned, run mysql_upgrade and exit the container: mysql_upgrade exit","title":"Manual MySQL upgrade"},{"location":"debug-mysql_upgrade/#run-a-manual-mysql_upgrade","text":"This step is usually not necessary. docker-compose stop mysql-mailcow watchdog-mailcow docker-compose run --rm --entrypoint '/bin/sh -c \"gosu mysql mysqld --skip-grant-tables & sleep 10 && bash && exit 0\"' mysql-mailcow As soon as the SQL shell spawned, run mysql_upgrade and exit the container: mysql_upgrade exit","title":"Run a manual mysql_upgrade"},{"location":"debug-reset_pw/","text":"mailcow Admin Account \u00b6 Reset mailcow admin to admin:moohoo . Older mailcow: dockerized installations may find mailcow-reset-admin.sh in their mailcow root directory (mailcow_path). cd mailcow_path ./helper-scripts/mailcow-reset-admin.sh Reset MySQL Passwords \u00b6 Stop the stack by running docker-compose stop . When the containers came to a stop, run this command: docker-compose run --rm --entrypoint '/bin/sh -c \"gosu mysql mysqld --skip-grant-tables & sleep 10 && mysql -hlocalhost -uroot && exit 0\"' mysql-mailcow 1. Find database name \u00b6 # source mailcow.conf # docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} MariaDB [(none)]> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mailcow_database | <===== | mysql | | performance_schema | +--------------------+ 4 rows in set (0.00 sec) 2. Reset one or more users \u00b6 2.1 Maria DB < 10.4 (older mailcow installations) \u00b6 Both \"password\" and \"authentication_string\" exist. Currently \"password\" is used, but better set both. MariaDB [(none)]> SELECT user FROM mysql.user; +--------------+ | user | +--------------+ | mailcow | <===== | root | +--------------+ 2 rows in set (0.00 sec) MariaDB [(none)]> FLUSH PRIVILEGES; MariaDB [(none)]> UPDATE mysql.user SET authentication_string = PASSWORD('gotr00t'), password = PASSWORD('gotr00t') WHERE User = 'root'; MariaDB [(none)]> UPDATE mysql.user SET authentication_string = PASSWORD('mookuh'), password = PASSWORD('mookuh') WHERE User = 'mailcow' AND Host = '%'; MariaDB [(none)]> FLUSH PRIVILEGES; 2.2 Maria DB >= 10.4 (current mailcows) \u00b6 MariaDB [(none)]> SELECT user FROM mysql.user; +--------------+ | user | +--------------+ | mailcow | <===== | root | +--------------+ 2 rows in set (0.00 sec) MariaDB [(none)]> FLUSH PRIVILEGES; MariaDB [(none)]> ALTER USER 'mailcow'@'%' IDENTIFIED BY 'mookuh'; MariaDB [(none)]> ALTER USER 'root'@'%' IDENTIFIED BY 'gotr00t'; MariaDB [(none)]> ALTER USER 'root'@'localhost' IDENTIFIED BY 'gotr00t'; MariaDB [(none)]> FLUSH PRIVILEGES; Remove Two-Factor Authentication \u00b6 This works similar to resetting a MySQL password, now we do it from the host without connecting to the MySQL CLI: source mailcow.conf docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e \"DELETE FROM tfa WHERE username='YOUR_USERNAME';\"","title":"Reset Passwords (incl. SQL)"},{"location":"debug-reset_pw/#mailcow-admin-account","text":"Reset mailcow admin to admin:moohoo . Older mailcow: dockerized installations may find mailcow-reset-admin.sh in their mailcow root directory (mailcow_path). cd mailcow_path ./helper-scripts/mailcow-reset-admin.sh","title":"mailcow Admin Account"},{"location":"debug-reset_pw/#reset-mysql-passwords","text":"Stop the stack by running docker-compose stop . When the containers came to a stop, run this command: docker-compose run --rm --entrypoint '/bin/sh -c \"gosu mysql mysqld --skip-grant-tables & sleep 10 && mysql -hlocalhost -uroot && exit 0\"' mysql-mailcow","title":"Reset MySQL Passwords"},{"location":"debug-reset_pw/#1-find-database-name","text":"# source mailcow.conf # docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} MariaDB [(none)]> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mailcow_database | <===== | mysql | | performance_schema | +--------------------+ 4 rows in set (0.00 sec)","title":"1. Find database name"},{"location":"debug-reset_pw/#2-reset-one-or-more-users","text":"","title":"2. Reset one or more users"},{"location":"debug-reset_pw/#21-maria-db-104-older-mailcow-installations","text":"Both \"password\" and \"authentication_string\" exist. Currently \"password\" is used, but better set both. MariaDB [(none)]> SELECT user FROM mysql.user; +--------------+ | user | +--------------+ | mailcow | <===== | root | +--------------+ 2 rows in set (0.00 sec) MariaDB [(none)]> FLUSH PRIVILEGES; MariaDB [(none)]> UPDATE mysql.user SET authentication_string = PASSWORD('gotr00t'), password = PASSWORD('gotr00t') WHERE User = 'root'; MariaDB [(none)]> UPDATE mysql.user SET authentication_string = PASSWORD('mookuh'), password = PASSWORD('mookuh') WHERE User = 'mailcow' AND Host = '%'; MariaDB [(none)]> FLUSH PRIVILEGES;","title":"2.1 Maria DB < 10.4 (older mailcow installations)"},{"location":"debug-reset_pw/#22-maria-db-104-current-mailcows","text":"MariaDB [(none)]> SELECT user FROM mysql.user; +--------------+ | user | +--------------+ | mailcow | <===== | root | +--------------+ 2 rows in set (0.00 sec) MariaDB [(none)]> FLUSH PRIVILEGES; MariaDB [(none)]> ALTER USER 'mailcow'@'%' IDENTIFIED BY 'mookuh'; MariaDB [(none)]> ALTER USER 'root'@'%' IDENTIFIED BY 'gotr00t'; MariaDB [(none)]> ALTER USER 'root'@'localhost' IDENTIFIED BY 'gotr00t'; MariaDB [(none)]> FLUSH PRIVILEGES;","title":"2.2 Maria DB >= 10.4 (current mailcows)"},{"location":"debug-reset_pw/#remove-two-factor-authentication","text":"This works similar to resetting a MySQL password, now we do it from the host without connecting to the MySQL CLI: source mailcow.conf docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e \"DELETE FROM tfa WHERE username='YOUR_USERNAME';\"","title":"Remove Two-Factor Authentication"},{"location":"debug-rm_volumes/","text":"You may want to remove a set of persistent data to resolve a conflict or to start over. mailcowdockerized can vary and depends on your compose project name (if it's unchanged, mailcowdockerized is the correct value). If you are unsure about volume names, run docker volume ls for a full list. Delete a single volume: docker volume rm mailcowdockerized_${VOLUME_NAME} Remove volume mysql-vol-1 to remove all MySQL data. Remove volume redis-vol-1 to remove all Redis data. Remove volume vmail-vol-1 to remove all contents of /var/vmail mounted to dovecot-mailcow . Remove volume rspamd-vol-1 to remove all Rspamd data. Remove volume crypt-vol-1 to remove all crypto data. This will render all mails unreadable. Alternatively, running docker-compose down -v will destroy all mailcow: dockerized volumes and delete any related containers and networks.","title":"Remove Persistent Data"},{"location":"debug/","text":"When a problem occurs, then always for a reason! What you want to do in such a case is: Read your logs; follow them to see what the reason for your problem is. Follow the leads given to you in your logfiles and start investigating. Restarting the troubled service or the whole stack to see if the problem persists. Read the documentation of the troubled service and search it's bugtracker for your problem. Search our issues for your problem. Create an issue over at our GitHub repository if you think your problem might be a bug or a missing feature you badly need. But please make sure, that you include all the logs and a full description to your problem. Please do not ask for support on Git. Join our Telegram community or find the official support packages at Servercow .","title":"Introduction"},{"location":"firststeps-disable_ipv6/","text":"This is ONLY recommended if you do not have an IPv6 enabled network on your host! If you really need to, you can disable the usage of IPv6 in the compose file. Additionally, you can also disable the startup of container \"ipv6nat-mailcow\", as it's not needed if you won't use IPv6. Instead of editing docker-compose.yml directly, it is preferable to create an override file for it and implement your changes to the service there. Unfortunately, this right now only seems to work for services, not for network settings. To disable IPv6 on the mailcow network, open docker-compose.yml with your favourite text editor and search for the network section (it's near the bottom of the file). 1. Modify docker-compose.yml Change enable_ipv6: true to enable_ipv6: false : networks: mailcow-network: [...] enable_ipv6: true # <<< set to false [...] 2. Disable ipv6nat-mailcow To disable the ipv6nat-mailcow container as well, go to your mailcow directory and create a new file called \"docker-compose.override.yml\": NOTE: If you already have an override file, of course don't recreate it, but merge the lines below into your existing one accordingly! # cd /opt/mailcow-dockerized # touch docker-compose.override.yml Open the file in your favourite text editor and fill in the following: version: '2.1' services: ipv6nat-mailcow: restart: \"no\" entrypoint: [\"echo\", \"ipv6nat disabled in compose.override.yml\"] For these changes to be effective, you need to fully stop and then restart the stack, so containers and networks are recreated: docker-compose down docker-compose up -d 3. Disable IPv6 in unbound-mailcow Edit data/conf/unbound/unbound.conf and set do-ip6 to \"no\": server: [...] do-ip6: no [...] Restart Unbound: docker-compose restart unbound-mailcow 4. Disable IPv6 in postfix-mailcow Create data/conf/postfix/extra.cf and set smtp_address_preference to ipv4 : smtp_address_preference = ipv4 inet_protocols = ipv4 Restart Postfix: docker-compose restart postfix-mailcow","title":"Disable IPv6"},{"location":"firststeps-ip_bindings/","text":"Warning Changing the binding does not affect source NAT. See SNAT for required steps. IPv4 binding \u00b6 To adjust one or multiple IPv4 bindings, open mailcow.conf and edit one, multiple or all variables as per your needs: # For technical reasons, http bindings are a bit different from other service bindings. # You will find the following variables, separated by a bind address and its port: # Example: HTTP_BIND=1.2.3.4 HTTP_PORT=80 HTTP_BIND= HTTPS_PORT=443 HTTPS_BIND= # Other services are bound by using the following format: # SMTP_PORT=1.2.3.4:25 will bind SMTP to the IP 1.2.3.4 on port 25 # Important! Specifying an IPv4 address will skip all IPv6 bindings since Docker 20.x. # doveadm, SQL as well as Solr are bound to local ports only, please do not change that, unless you know what you are doing. SMTP_PORT=25 SMTPS_PORT=465 SUBMISSION_PORT=587 IMAP_PORT=143 IMAPS_PORT=993 POP_PORT=110 POPS_PORT=995 SIEVE_PORT=4190 DOVEADM_PORT=127.0.0.1:19991 SQL_PORT=127.0.0.1:13306 SOLR_PORT=127.0.0.1:18983 To apply your changes, run docker-compose down followed by docker-compose up -d . IPv6 binding \u00b6 Changing IPv6 bindings is different from IPv4. Again, this has a technical background. A docker-compose.override.yml file will be used instead of editing the docker-compose.yml file directly. This is to maintain updatability, as the docker-compose.yml file gets updated regularly and your changes will most likely be overwritten. Edit to create a file docker-compose.override.yml with the following content. Its content will be merged with the productive docker-compose.yml file. An imaginary IPv6 2a00:dead:beef::abc is given. The first suffix :PORT1 defines the external port, while the second suffix :PORT2 routes to the corresponding port inside the container and must not be changed. version: '2.1' services: dovecot-mailcow: ports: - '2a00:dead:beef::abc:143:143' - '2a00:dead:beef::abc:993:993' - '2a00:dead:beef::abc:110:110' - '2a00:dead:beef::abc:995:995' - '2a00:dead:beef::abc:4190:4190' postfix-mailcow: ports: - '2a00:dead:beef::abc:25:25' - '2a00:dead:beef::abc:465:465' - '2a00:dead:beef::abc:587:587' nginx-mailcow: ports: - '2a00:dead:beef::abc:80:80' - '2a00:dead:beef::abc:443:443' To apply your changes, run docker-compose down followed by docker-compose up -d .","title":"IP bindings"},{"location":"firststeps-ip_bindings/#ipv4-binding","text":"To adjust one or multiple IPv4 bindings, open mailcow.conf and edit one, multiple or all variables as per your needs: # For technical reasons, http bindings are a bit different from other service bindings. # You will find the following variables, separated by a bind address and its port: # Example: HTTP_BIND=1.2.3.4 HTTP_PORT=80 HTTP_BIND= HTTPS_PORT=443 HTTPS_BIND= # Other services are bound by using the following format: # SMTP_PORT=1.2.3.4:25 will bind SMTP to the IP 1.2.3.4 on port 25 # Important! Specifying an IPv4 address will skip all IPv6 bindings since Docker 20.x. # doveadm, SQL as well as Solr are bound to local ports only, please do not change that, unless you know what you are doing. SMTP_PORT=25 SMTPS_PORT=465 SUBMISSION_PORT=587 IMAP_PORT=143 IMAPS_PORT=993 POP_PORT=110 POPS_PORT=995 SIEVE_PORT=4190 DOVEADM_PORT=127.0.0.1:19991 SQL_PORT=127.0.0.1:13306 SOLR_PORT=127.0.0.1:18983 To apply your changes, run docker-compose down followed by docker-compose up -d .","title":"IPv4 binding"},{"location":"firststeps-ip_bindings/#ipv6-binding","text":"Changing IPv6 bindings is different from IPv4. Again, this has a technical background. A docker-compose.override.yml file will be used instead of editing the docker-compose.yml file directly. This is to maintain updatability, as the docker-compose.yml file gets updated regularly and your changes will most likely be overwritten. Edit to create a file docker-compose.override.yml with the following content. Its content will be merged with the productive docker-compose.yml file. An imaginary IPv6 2a00:dead:beef::abc is given. The first suffix :PORT1 defines the external port, while the second suffix :PORT2 routes to the corresponding port inside the container and must not be changed. version: '2.1' services: dovecot-mailcow: ports: - '2a00:dead:beef::abc:143:143' - '2a00:dead:beef::abc:993:993' - '2a00:dead:beef::abc:110:110' - '2a00:dead:beef::abc:995:995' - '2a00:dead:beef::abc:4190:4190' postfix-mailcow: ports: - '2a00:dead:beef::abc:25:25' - '2a00:dead:beef::abc:465:465' - '2a00:dead:beef::abc:587:587' nginx-mailcow: ports: - '2a00:dead:beef::abc:80:80' - '2a00:dead:beef::abc:443:443' To apply your changes, run docker-compose down followed by docker-compose up -d .","title":"IPv6 binding"},{"location":"firststeps-local_mta/","text":"The easiest option would be to disable the listener on port 25/tcp. Postfix users disable the listener by commenting the following line (starting with smtp or 25 ) in /etc/postfix/master.cf : #smtp inet n - - - - smtpd Furthermore, to relay over a dockerized mailcow, you may want to add 172.22.1.1 as relayhost and remove the Docker interface from \"inet_interfaces\": postconf -e 'relayhost = 172.22.1.1' postconf -e \"mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128\" postconf -e \"inet_interfaces = loopback-only\" postconf -e \"relay_transport = relay\" postconf -e \"default_transport = smtp\" Now it is important to not have the same FQDN in myhostname as you use for your dockerized mailcow. Check your local (non-Docker) Postfix' main.cf for myhostname and set it to something different, for example local.my.fqdn.tld . \"172.22.1.1\" is the mailcow created network gateway in Docker. Relaying over this interface is necessary (instead of - for example - relaying directly over ${MAILCOW_HOSTNAME}) to relay over a known internal network. Restart Postfix after applying your changes.","title":"Local MTA on Docker host"},{"location":"firststeps-logging/","text":"Logging in mailcow: dockerized consists of multiple stages, but is, after all, much more flexible and easier to integrate into a logging daemon than before. In Docker the containerized application (PID 1) writes its output to stdout. For real one-application containers this works just fine. Run docker-compose logs --help to learn more. Some containers log or stream to multiple destinations. No container will keep persistent logs in it. Containers are transient items! In the end, every line of logs will reach the Docker daemon - unfiltered. The default logging driver is \"json\" . Filtered logs \u00b6 Some logs are filtered and written to Redis keys but also streamed to a Redis channel. The Redis channel is used to stream logs with failed authentication attempts to be read by netfilter-mailcow. The Redis keys are persistent and will keep 10000 lines of logs for the web UI. This mechanism makes it possible to use whatever Docker logging driver you want to, without losing the ability to read logs from the UI or ban suspicious clients with netfilter-mailcow. Redis keys will only hold logs from applications and filter out system messages (think of cron etc.). Logging drivers \u00b6 Via docker-compose.override.yml \u00b6 Here is the good news: Since Docker has some great logging drivers, you can integrate mailcow: dockerized into your existing logging environment with ease. Create a docker-compose.override.yml and add, for example, this block to use the \"gelf\" logging plugin for postfix-mailcow : version: '2.1' services: postfix-mailcow: # or any other logging: driver: \"gelf\" options: gelf-address: \"udp://graylog:12201\" Another example for Syslog : version: '2.1' services: postfix-mailcow: # or any other logging: driver: \"syslog\" options: syslog-address: \"udp://127.0.0.1:514\" syslog-facility: \"local3\" dovecot-mailcow: # or any other logging: driver: \"syslog\" options: syslog-address: \"udp://127.0.0.1:514\" syslog-facility: \"local3\" rspamd-mailcow: # or any other logging: driver: \"syslog\" options: syslog-address: \"udp://127.0.0.1:514\" syslog-facility: \"local3\" # For Rsyslog only: # To move local3 input to /var/log/mailcow.log and stop processing, create a file \"/etc/rsyslog.d/docker.conf\": local3.* /var/log/mailcow.logs & ~ # Restart rsyslog afterwards. via daemon.json (globally) \u00b6 If you want to change the logging driver globally , edit Dockers daemon configuration file /etc/docker/daemon.json and restart the Docker service: { ... \"log-driver\": \"gelf\", \"log-opts\": { \"gelf-address\": \"udp://graylog:12201\" } ... } For Syslog: { ... \"log-driver\": \"syslog\", \"log-opts\": { \"syslog-address\": \"udp://1.2.3.4:514\" } ... } Restart the Docker daemon and run docker-compose down && docker-compose up -d to recreate the containers with the new logging driver.","title":"Logging"},{"location":"firststeps-logging/#filtered-logs","text":"Some logs are filtered and written to Redis keys but also streamed to a Redis channel. The Redis channel is used to stream logs with failed authentication attempts to be read by netfilter-mailcow. The Redis keys are persistent and will keep 10000 lines of logs for the web UI. This mechanism makes it possible to use whatever Docker logging driver you want to, without losing the ability to read logs from the UI or ban suspicious clients with netfilter-mailcow. Redis keys will only hold logs from applications and filter out system messages (think of cron etc.).","title":"Filtered logs"},{"location":"firststeps-logging/#logging-drivers","text":"","title":"Logging drivers"},{"location":"firststeps-logging/#via-docker-composeoverrideyml","text":"Here is the good news: Since Docker has some great logging drivers, you can integrate mailcow: dockerized into your existing logging environment with ease. Create a docker-compose.override.yml and add, for example, this block to use the \"gelf\" logging plugin for postfix-mailcow : version: '2.1' services: postfix-mailcow: # or any other logging: driver: \"gelf\" options: gelf-address: \"udp://graylog:12201\" Another example for Syslog : version: '2.1' services: postfix-mailcow: # or any other logging: driver: \"syslog\" options: syslog-address: \"udp://127.0.0.1:514\" syslog-facility: \"local3\" dovecot-mailcow: # or any other logging: driver: \"syslog\" options: syslog-address: \"udp://127.0.0.1:514\" syslog-facility: \"local3\" rspamd-mailcow: # or any other logging: driver: \"syslog\" options: syslog-address: \"udp://127.0.0.1:514\" syslog-facility: \"local3\" # For Rsyslog only: # To move local3 input to /var/log/mailcow.log and stop processing, create a file \"/etc/rsyslog.d/docker.conf\": local3.* /var/log/mailcow.logs & ~ # Restart rsyslog afterwards.","title":"Via docker-compose.override.yml"},{"location":"firststeps-logging/#via-daemonjson-globally","text":"If you want to change the logging driver globally , edit Dockers daemon configuration file /etc/docker/daemon.json and restart the Docker service: { ... \"log-driver\": \"gelf\", \"log-opts\": { \"gelf-address\": \"udp://graylog:12201\" } ... } For Syslog: { ... \"log-driver\": \"syslog\", \"log-opts\": { \"syslog-address\": \"udp://1.2.3.4:514\" } ... } Restart the Docker daemon and run docker-compose down && docker-compose up -d to recreate the containers with the new logging driver.","title":"via daemon.json (globally)"},{"location":"firststeps-relayhost/","text":"As of September 12, 2018 you can setup relayhosts as admin by using the mailcow UI. This is useful if you want to relay outgoing emails for a specific domain to a third-party spam filter or a service like Mailgun or Sendgrid. This is also known as a smarthost . Add a new relayhost \u00b6 Go to the Routing tab of the Configuration and Details section of the admin UI. Here you will see a list of relayhosts currently setup. Scroll to the Add sender-dependent transport section. Under Host , add the host you want to relay to. Example: if you want to use Mailgun to send emails instead of your server IP, enter smtp.mailgun.org If the relay host requires a username and password to authenticate, enter them in the respective fields. Keep in mind the credentials will be stored in plain text. Test a relayhost \u00b6 To test that connectivity to the host works, click on Test from the list of relayhosts and enter a From: address. Then, run the test. You will then see the results of the SMTP transmission. If all went well, you should see SERVER -> CLIENT: 250 2.0.0 Ok: queued as A093B401D4 as one of the last lines. If not, review the error provided and resolve it. Note: Some hosts, especially those who do not require authentication, will deny connections from servers that have not been added to their system beforehand. Make sure you read the documentation of the relayhost to make sure you've added your domain and/or the server IP to their system. Tip: You can change the default test To: address the test uses from null@mailcow.email to any email address you choose by modifying the $RELAY_TO variable on the vars.inc.php file under /opt/mailcow-dockerized/data/web/inc This way you can check that the relay worked by checking the destination mailbox. Set the relayhost for a domain \u00b6 Go to the Domains tab of the Mail setup section of the admin UI. Edit the desired domain. Select the newly added host on the Sender-dependent transports dropdown and save changes. Send an email from a mailbox on that domain and you should see postfix handing the message over to the relayhost in the logs.","title":"Relayhosts"},{"location":"firststeps-relayhost/#add-a-new-relayhost","text":"Go to the Routing tab of the Configuration and Details section of the admin UI. Here you will see a list of relayhosts currently setup. Scroll to the Add sender-dependent transport section. Under Host , add the host you want to relay to. Example: if you want to use Mailgun to send emails instead of your server IP, enter smtp.mailgun.org If the relay host requires a username and password to authenticate, enter them in the respective fields. Keep in mind the credentials will be stored in plain text.","title":"Add a new relayhost"},{"location":"firststeps-relayhost/#test-a-relayhost","text":"To test that connectivity to the host works, click on Test from the list of relayhosts and enter a From: address. Then, run the test. You will then see the results of the SMTP transmission. If all went well, you should see SERVER -> CLIENT: 250 2.0.0 Ok: queued as A093B401D4 as one of the last lines. If not, review the error provided and resolve it. Note: Some hosts, especially those who do not require authentication, will deny connections from servers that have not been added to their system beforehand. Make sure you read the documentation of the relayhost to make sure you've added your domain and/or the server IP to their system. Tip: You can change the default test To: address the test uses from null@mailcow.email to any email address you choose by modifying the $RELAY_TO variable on the vars.inc.php file under /opt/mailcow-dockerized/data/web/inc This way you can check that the relay worked by checking the destination mailbox.","title":"Test a relayhost"},{"location":"firststeps-relayhost/#set-the-relayhost-for-a-domain","text":"Go to the Domains tab of the Mail setup section of the admin UI. Edit the desired domain. Select the newly added host on the Sender-dependent transports dropdown and save changes. Send an email from a mailbox on that domain and you should see postfix handing the message over to the relayhost in the logs.","title":"Set the relayhost for a domain"},{"location":"firststeps-rfc-1918/","text":"Per default, mailcow considers all private RFC1918 networks (i.e. 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) as trusted. Though it is reasonable in most cases, you may want to restrict this setting under certain circumstances. In particular, if you are using some kind of reverse proxy for SMTP TCP ports. If your reverse proxy host is located in a private net, mailcow will consider all traffic from it as trusted, which may result in an open relay. To change this behaviour override the default value of mynetworks parameter through the data/conf/postfix/extra.cf configuration file. Important : Do not remove the networks listed as IPV4_NETWORK and IPV6_NETWORK in your mailcow.conf. You should also keep local addresses. The default values for those variables - 172.22.1.0/24 and fd4d:6169:6c63:6f77::/64 - would result in the following, minimal configuration: mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 [fe80::]/10 172.22.1.0/24 [fd4d:6169:6c63:6f77::]/64","title":"Untrust RFC 1918"},{"location":"firststeps-rp/","text":"You don't need to change the Nginx site that comes with mailcow: dockerized. mailcow: dockerized trusts the default gateway IP 172.22.1.1 as proxy. 1. Make sure you change HTTP_BIND and HTTPS_BIND in mailcow.conf to a local address and set the ports accordingly, for example: HTTP_BIND = 127 .0.0.1 HTTP_PORT = 8080 HTTPS_BIND = 127 .0.0.1 HTTPS_PORT = 8443 This will also change the bindings inside the Nginx container! This is important, if you decide to use a proxy within Docker. IMPORTANT: Do not use port 8081, 9081 or 65510! Recreate affected containers by running docker-compose up -d . Warning Make sure you run generate_config.sh before you enable any site configuration examples below. The script generate_config.sh copies snake-oil certificates to the correct location, so the services will not fail to start due to missing files. Warning If you enable TLS SNI ( ENABLE_TLS_SNI in mailcow.conf), the certificate paths in your reverse proxy must match the correct paths in data/assets/ssl/{hostname}. The certificates will be split into data/assets/ssl/{hostname1,hostname2,etc} and therefore will not work when you copy the examples from below pointing to data/assets/ssl/cert.pem etc. Info Using the site configs below will forward ACME requests to mailcow and let it handle certificates itself. The downside of using mailcow as ACME client behind a reverse proxy is, that you will need to reload your webserver after acme-mailcow changed/renewed/created the certificate. You can either reload your webserver daily or write a script to watch the file for changes. On many servers logrotate will reload the webserver daily anyway. If you want to use a local certbot installation, you will need to change the SSL certificate parameters accordingly. Make sure you run a post-hook script when you decide to use external ACME clients. You will find an example at the bottom of this page. 2. Configure your local webserver as reverse proxy: Apache 2.4 \u00b6 Required modules: a2enmod rewrite proxy proxy_http headers ssl Let's Encrypt will follow our rewrite, certificate requests in mailcow will work fine. Take care of highlighted lines. ServerName CHANGE_TO_MAILCOW_HOSTNAME ServerAlias autodiscover.* ServerAlias autoconfig.* ServerAlias xmpp_prefix_if_any.domain ServerAlias *.xmpp_prefix_if_any.domain RewriteEngine on RewriteCond %{HTTPS} off RewriteRule ^/?(.*) https://%{HTTP_HOST}/$1 [R=301,L] ProxyPass / http://127.0.0.1:8080/ ProxyPassReverse / http://127.0.0.1:8080/ ProxyPreserveHost On ProxyAddHeaders On RequestHeader set X-Forwarded-Proto \"http\" ServerName CHANGE_TO_MAILCOW_HOSTNAME ServerAlias autodiscover.* ServerAlias autoconfig.* ServerAlias xmpp_prefix_if_any.domain ServerAlias *.xmpp_prefix_if_any.domain # You should proxy to a plain HTTP session to offload SSL processing ProxyPass /Microsoft-Server-ActiveSync http://127.0.0.1:8080/Microsoft-Server-ActiveSync connectiontimeout=4000 ProxyPassReverse /Microsoft-Server-ActiveSync http://127.0.0.1:8080/Microsoft-Server-ActiveSync ProxyPass / http://127.0.0.1:8080/ ProxyPassReverse / http://127.0.0.1:8080/ ProxyPreserveHost On ProxyAddHeaders On RequestHeader set X-Forwarded-Proto \"https\" SSLCertificateFile MAILCOW_PATH/data/assets/ssl/cert.pem SSLCertificateKeyFile MAILCOW_PATH/data/assets/ssl/key.pem # If you plan to proxy to a HTTPS host: #SSLProxyEngine On # If you plan to proxy to an untrusted HTTPS host: #SSLProxyVerify none #SSLProxyCheckPeerCN off #SSLProxyCheckPeerName off #SSLProxyCheckPeerExpire off Nginx \u00b6 Let's Encrypt will follow our rewrite, certificate requests will work fine. Take care of highlighted lines. server { listen 80 default_server; listen [::]:80 default_server; server_name CHANGE_TO_MAILCOW_HOSTNAME autodiscover.* autoconfig.* xmpp_prefix_if_any.domain *.xmpp_prefix_if_any.domain; return 301 https://$host$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name CHANGE_TO_MAILCOW_HOSTNAME autodiscover.* autoconfig.* xmpp_prefix_if_any.domain *.xmpp_prefix_if_any.domain; ssl_certificate MAILCOW_PATH/data/assets/ssl/cert.pem; ssl_certificate_key MAILCOW_PATH/data/assets/ssl/key.pem; ssl_session_timeout 1d; ssl_session_cache shared:SSL:50m; ssl_session_tickets off; # See https://ssl-config.mozilla.org/#server=nginx for the latest ssl settings recommendations # An example config is given below ssl_protocols TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5:!SHA1:!kRSA; ssl_prefer_server_ciphers off; location /Microsoft-Server-ActiveSync { proxy_pass http://127.0.0.1:8080/Microsoft-Server-ActiveSync; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 75; proxy_send_timeout 3650; proxy_read_timeout 3650; proxy_buffers 64 256k; client_body_buffer_size 512k; client_max_body_size 0; } location / { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; client_max_body_size 0; } } HAProxy (community supported) \u00b6 Warning This is an unsupported community contribution. Feel free to provide fixes. Important/Fixme : This example only forwards HTTPS traffic and does not use mailcows built-in ACME client. frontend https-in bind :::443 v4v6 ssl crt mailcow.pem default_backend mailcow backend mailcow option forwardfor http-request set-header X-Forwarded-Proto https if { ssl_fc } http-request set-header X-Forwarded-Proto http if !{ ssl_fc } server mailcow 127.0.0.1:8080 check Traefik v2 (community supported) \u00b6 Warning This is an unsupported community contribution. Feel free to provide fixes. Important : This config only covers the \"reverseproxing\" of the webpannel (nginx-mailcow) using Traefik v2, if you also want to reverseproxy the mail services such as dovecot, postfix... you'll just need to adapt the following config to each container and create an EntryPoint on your traefik.toml or traefik.yml (depending which config you use) for each port. For this section we'll assume you have your Traefik 2 [certificatesresolvers] properly configured on your traefik configuration file, and also using acme, also, the following example uses Lets Encrypt, but feel free to change it to your own cert resolver. You can find a basic Traefik 2 toml config file with all the above implemented which can be used for this example here traefik.toml if you need one, or a hint on how to adapt your config. So, first of all, we are going to disable the acme-mailcow container since we'll use the certs that traefik will provide us. For this we'll have to set SKIP_LETS_ENCRYPT=y on our mailcow.conf , and run docker-compose up -d to apply the changes. Then we'll create a docker-compose.override.yml file in order to override the main docker-compose.yml found in your mailcow root folder. version: '2.1' services: nginx-mailcow: networks: # add Traefik's network web: labels: - traefik.enable=true # Creates a router called \"moo\" for the container, and sets up a rule to link the container to certain rule, # in this case, a Host rule with our MAILCOW_HOSTNAME var. - traefik.http.routers.moo.rule=Host(`${MAILCOW_HOSTNAME}`) # Enables tls over the router we created before. - traefik.http.routers.moo.tls=true # Specifies which kind of cert resolver we'll use, in this case le (Lets Encrypt). - traefik.http.routers.moo.tls.certresolver=le # Creates a service called \"moo\" for the container, and specifies which internal port of the container # should traefik route the incoming data to. - traefik.http.services.moo.loadbalancer.server.port=80 # Specifies which entrypoint (external port) should traefik listen to, for this container. # websecure being port 443, check the traefik.toml file liked above. - traefik.http.routers.moo.entrypoints=secure # Make sure traefik uses the web network, not the mailcowdockerized_mailcow-network - traefik.docker.network=web certdumper: image: humenius/traefik-certs-dumper container_name: traefik_certdumper network_mode: none volumes: # mount the folder which contains Traefik's `acme.json' file # in this case Traefik is started from its own docker-compose in ../traefik - ../traefik/data:/traefik:ro # mount mailcow's SSL folder - ./data/assets/ssl/:/output:rw environment: # only change this, if you're using another domain for mailcow's web frontend compared to the standard config - DOMAIN=${MAILCOW_HOSTNAME} networks: web: external: true Start the new containers with docker-compose up -d . Now, there's only one thing left to do, which is setup the certs so that the mail services can use them as well, since Traefik 2 uses an acme v2 format to save ALL the license from all the domains we have, we'll need to find a way to dump the certs, lucky we have this tiny container which grabs the acme.json file trough a volume, and a variable DOMAIN=example.org , and with these, the container will output the cert.pem and key.pem files, for this we'll simply run the traefik-certs-dumper container binding the /traefik volume to the folder where our acme.json is saved, bind the /output volume to our mailcow data/assets/ssl/ folder, and set up the DOMAIN=example.org variable to the domain we want the certs dumped from. This container will watch over the acme.json file for any changes, and regenerate the cert.pem and key.pem files directly into data/assets/ssl/ being the path binded to the container's /output path. You can use the command line to run it, or use the docker-compose shown here . After we have the certs dumped, we'll have to reload the configs from our postfix and dovecot containers, and check the certs, you can see how here . Aaand that should be it \ud83d\ude0a, you can check if the Traefik router works fine trough Traefik's dashboard / traefik logs / accessing the setted domain trough https, or / and check HTTPS, SMTP and IMAP trough the commands shown on the page linked before. Optional: Post-hook script for non-mailcow ACME clients \u00b6 Using a local certbot (or any other ACME client) requires to restart some containers, you can do this with a post-hook script. Make sure you change the paths accordingly: #!/bin/bash cp /etc/letsencrypt/live/my.domain.tld/fullchain.pem /opt/mailcow-dockerized/data/assets/ssl/cert.pem cp /etc/letsencrypt/live/my.domain.tld/privkey.pem /opt/mailcow-dockerized/data/assets/ssl/key.pem postfix_c=$(docker ps -qaf name=postfix-mailcow) dovecot_c=$(docker ps -qaf name=dovecot-mailcow) nginx_c=$(docker ps -qaf name=nginx-mailcow) docker restart ${postfix_c} ${dovecot_c} ${nginx_c} Adding additional server names for mailcow UI \u00b6 If you plan to use a server name that is not MAILCOW_HOSTNAME in your reverse proxy, make sure to populate that name in mailcow.conf via ADDITIONAL_SERVER_NAMES first. Names must be separated by commas and must not contain spaces. If you skip this step, mailcow may respond to your reverse proxy with an incorrect site. ADDITIONAL_SERVER_NAMES=webmail.domain.tld,other.example.tld Run docker-compose up -d to apply.","title":"Reverse Proxy"},{"location":"firststeps-rp/#apache-24","text":"Required modules: a2enmod rewrite proxy proxy_http headers ssl Let's Encrypt will follow our rewrite, certificate requests in mailcow will work fine. Take care of highlighted lines. ServerName CHANGE_TO_MAILCOW_HOSTNAME ServerAlias autodiscover.* ServerAlias autoconfig.* ServerAlias xmpp_prefix_if_any.domain ServerAlias *.xmpp_prefix_if_any.domain RewriteEngine on RewriteCond %{HTTPS} off RewriteRule ^/?(.*) https://%{HTTP_HOST}/$1 [R=301,L] ProxyPass / http://127.0.0.1:8080/ ProxyPassReverse / http://127.0.0.1:8080/ ProxyPreserveHost On ProxyAddHeaders On RequestHeader set X-Forwarded-Proto \"http\" ServerName CHANGE_TO_MAILCOW_HOSTNAME ServerAlias autodiscover.* ServerAlias autoconfig.* ServerAlias xmpp_prefix_if_any.domain ServerAlias *.xmpp_prefix_if_any.domain # You should proxy to a plain HTTP session to offload SSL processing ProxyPass /Microsoft-Server-ActiveSync http://127.0.0.1:8080/Microsoft-Server-ActiveSync connectiontimeout=4000 ProxyPassReverse /Microsoft-Server-ActiveSync http://127.0.0.1:8080/Microsoft-Server-ActiveSync ProxyPass / http://127.0.0.1:8080/ ProxyPassReverse / http://127.0.0.1:8080/ ProxyPreserveHost On ProxyAddHeaders On RequestHeader set X-Forwarded-Proto \"https\" SSLCertificateFile MAILCOW_PATH/data/assets/ssl/cert.pem SSLCertificateKeyFile MAILCOW_PATH/data/assets/ssl/key.pem # If you plan to proxy to a HTTPS host: #SSLProxyEngine On # If you plan to proxy to an untrusted HTTPS host: #SSLProxyVerify none #SSLProxyCheckPeerCN off #SSLProxyCheckPeerName off #SSLProxyCheckPeerExpire off ","title":"Apache 2.4"},{"location":"firststeps-rp/#nginx","text":"Let's Encrypt will follow our rewrite, certificate requests will work fine. Take care of highlighted lines. server { listen 80 default_server; listen [::]:80 default_server; server_name CHANGE_TO_MAILCOW_HOSTNAME autodiscover.* autoconfig.* xmpp_prefix_if_any.domain *.xmpp_prefix_if_any.domain; return 301 https://$host$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name CHANGE_TO_MAILCOW_HOSTNAME autodiscover.* autoconfig.* xmpp_prefix_if_any.domain *.xmpp_prefix_if_any.domain; ssl_certificate MAILCOW_PATH/data/assets/ssl/cert.pem; ssl_certificate_key MAILCOW_PATH/data/assets/ssl/key.pem; ssl_session_timeout 1d; ssl_session_cache shared:SSL:50m; ssl_session_tickets off; # See https://ssl-config.mozilla.org/#server=nginx for the latest ssl settings recommendations # An example config is given below ssl_protocols TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5:!SHA1:!kRSA; ssl_prefer_server_ciphers off; location /Microsoft-Server-ActiveSync { proxy_pass http://127.0.0.1:8080/Microsoft-Server-ActiveSync; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 75; proxy_send_timeout 3650; proxy_read_timeout 3650; proxy_buffers 64 256k; client_body_buffer_size 512k; client_max_body_size 0; } location / { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; client_max_body_size 0; } }","title":"Nginx"},{"location":"firststeps-rp/#haproxy-community-supported","text":"Warning This is an unsupported community contribution. Feel free to provide fixes. Important/Fixme : This example only forwards HTTPS traffic and does not use mailcows built-in ACME client. frontend https-in bind :::443 v4v6 ssl crt mailcow.pem default_backend mailcow backend mailcow option forwardfor http-request set-header X-Forwarded-Proto https if { ssl_fc } http-request set-header X-Forwarded-Proto http if !{ ssl_fc } server mailcow 127.0.0.1:8080 check","title":"HAProxy (community supported)"},{"location":"firststeps-rp/#traefik-v2-community-supported","text":"Warning This is an unsupported community contribution. Feel free to provide fixes. Important : This config only covers the \"reverseproxing\" of the webpannel (nginx-mailcow) using Traefik v2, if you also want to reverseproxy the mail services such as dovecot, postfix... you'll just need to adapt the following config to each container and create an EntryPoint on your traefik.toml or traefik.yml (depending which config you use) for each port. For this section we'll assume you have your Traefik 2 [certificatesresolvers] properly configured on your traefik configuration file, and also using acme, also, the following example uses Lets Encrypt, but feel free to change it to your own cert resolver. You can find a basic Traefik 2 toml config file with all the above implemented which can be used for this example here traefik.toml if you need one, or a hint on how to adapt your config. So, first of all, we are going to disable the acme-mailcow container since we'll use the certs that traefik will provide us. For this we'll have to set SKIP_LETS_ENCRYPT=y on our mailcow.conf , and run docker-compose up -d to apply the changes. Then we'll create a docker-compose.override.yml file in order to override the main docker-compose.yml found in your mailcow root folder. version: '2.1' services: nginx-mailcow: networks: # add Traefik's network web: labels: - traefik.enable=true # Creates a router called \"moo\" for the container, and sets up a rule to link the container to certain rule, # in this case, a Host rule with our MAILCOW_HOSTNAME var. - traefik.http.routers.moo.rule=Host(`${MAILCOW_HOSTNAME}`) # Enables tls over the router we created before. - traefik.http.routers.moo.tls=true # Specifies which kind of cert resolver we'll use, in this case le (Lets Encrypt). - traefik.http.routers.moo.tls.certresolver=le # Creates a service called \"moo\" for the container, and specifies which internal port of the container # should traefik route the incoming data to. - traefik.http.services.moo.loadbalancer.server.port=80 # Specifies which entrypoint (external port) should traefik listen to, for this container. # websecure being port 443, check the traefik.toml file liked above. - traefik.http.routers.moo.entrypoints=secure # Make sure traefik uses the web network, not the mailcowdockerized_mailcow-network - traefik.docker.network=web certdumper: image: humenius/traefik-certs-dumper container_name: traefik_certdumper network_mode: none volumes: # mount the folder which contains Traefik's `acme.json' file # in this case Traefik is started from its own docker-compose in ../traefik - ../traefik/data:/traefik:ro # mount mailcow's SSL folder - ./data/assets/ssl/:/output:rw environment: # only change this, if you're using another domain for mailcow's web frontend compared to the standard config - DOMAIN=${MAILCOW_HOSTNAME} networks: web: external: true Start the new containers with docker-compose up -d . Now, there's only one thing left to do, which is setup the certs so that the mail services can use them as well, since Traefik 2 uses an acme v2 format to save ALL the license from all the domains we have, we'll need to find a way to dump the certs, lucky we have this tiny container which grabs the acme.json file trough a volume, and a variable DOMAIN=example.org , and with these, the container will output the cert.pem and key.pem files, for this we'll simply run the traefik-certs-dumper container binding the /traefik volume to the folder where our acme.json is saved, bind the /output volume to our mailcow data/assets/ssl/ folder, and set up the DOMAIN=example.org variable to the domain we want the certs dumped from. This container will watch over the acme.json file for any changes, and regenerate the cert.pem and key.pem files directly into data/assets/ssl/ being the path binded to the container's /output path. You can use the command line to run it, or use the docker-compose shown here . After we have the certs dumped, we'll have to reload the configs from our postfix and dovecot containers, and check the certs, you can see how here . Aaand that should be it \ud83d\ude0a, you can check if the Traefik router works fine trough Traefik's dashboard / traefik logs / accessing the setted domain trough https, or / and check HTTPS, SMTP and IMAP trough the commands shown on the page linked before.","title":"Traefik v2 (community supported)"},{"location":"firststeps-rp/#optional-post-hook-script-for-non-mailcow-acme-clients","text":"Using a local certbot (or any other ACME client) requires to restart some containers, you can do this with a post-hook script. Make sure you change the paths accordingly: #!/bin/bash cp /etc/letsencrypt/live/my.domain.tld/fullchain.pem /opt/mailcow-dockerized/data/assets/ssl/cert.pem cp /etc/letsencrypt/live/my.domain.tld/privkey.pem /opt/mailcow-dockerized/data/assets/ssl/key.pem postfix_c=$(docker ps -qaf name=postfix-mailcow) dovecot_c=$(docker ps -qaf name=dovecot-mailcow) nginx_c=$(docker ps -qaf name=nginx-mailcow) docker restart ${postfix_c} ${dovecot_c} ${nginx_c}","title":"Optional: Post-hook script for non-mailcow ACME clients"},{"location":"firststeps-rp/#adding-additional-server-names-for-mailcow-ui","text":"If you plan to use a server name that is not MAILCOW_HOSTNAME in your reverse proxy, make sure to populate that name in mailcow.conf via ADDITIONAL_SERVER_NAMES first. Names must be separated by commas and must not contain spaces. If you skip this step, mailcow may respond to your reverse proxy with an incorrect site. ADDITIONAL_SERVER_NAMES=webmail.domain.tld,other.example.tld Run docker-compose up -d to apply.","title":"Adding additional server names for mailcow UI"},{"location":"firststeps-rspamd_ui/","text":"Rspamd is an easy to use spam filtering tool presently installed with mailcow. Go to the mailcow web admin interface Navigate to the Access tab. (Configuration > Administration > Access) Modify the Rspamd UI password Go to https://${MAILCOW_HOSTNAME}/rspamd in a browser and log in! Additional configuration options and documentation can be found here : https://rspamd.com/webui/","title":"Rspamd UI"},{"location":"firststeps-snat/","text":"SNAT is used to change the source address of the packets sent by mailcow. It can be used to change the outgoing IP address on systems with multiple IP addresses. Open mailcow.conf , set either or both of the following parameters: # Use this IPv4 for outgoing connections (SNAT) SNAT_TO_SOURCE=1.2.3.4 # Use this IPv6 for outgoing connections (SNAT) SNAT6_TO_SOURCE=dead:beef Run docker-compose up -d . The values are read by netfilter-mailcow. netfilter-mailcow will make sure, the post-routing rules are on position 1 in the netfilter table. It does automatically delete and re-create them if they are found on another position than 1. Check the output of docker-compose logs --tail=200 netfilter-mailcow to ensure the SNAT settings have been applied.","title":"SNAT"},{"location":"firststeps-ssl/","text":"Let's Encrypt (out-of-the-box) \u00b6 The \"acme-mailcow\" container will try to obtain a LE certificate for ${MAILCOW_HOSTNAME} , autodiscover.ADDED_MAIL_DOMAIN and autoconfig.ADDED_MAIL_DOMAIN . Warning mailcow must be available on port 80 for the acme-client to work. Our reverse proxy example configurations do cover that. You can also use any external ACME client (certbot for example) to obtain certificates, but you will need to make sure, that they are copied to the correct location and a post-hook reloads affected containers. See more in the Reverse Proxy documentation. By default, which means 0 domains are added to mailcow, it will try to obtain a certificate for ${MAILCOW_HOSTNAME} . For each domain you add, it will try to resolve autodiscover.ADDED_MAIL_DOMAIN and autoconfig.ADDED_MAIL_DOMAIN to its IPv6 address or - if IPv6 is not configured in your domain - IPv4 address. If it succeeds, a name will be added as SAN to the certificate request. Only names that can be validated, will be added as SAN. For every domain you remove, the certificate will be moved and a new certificate will be requested. It is not possible to keep domains in a certificate, when we are not able validate the challenge for those. If you want to re-run the ACME client, use docker-compose restart acme-mailcow and monitor its logs with docker-compose logs --tail=200 -f acme-mailcow . Additional domain names \u00b6 Edit \"mailcow.conf\" and add a parameter ADDITIONAL_SAN like this: Do not use quotes ( \" ) and do not use spaces between the names! ADDITIONAL_SAN=smtp.*,cert1.example.com,cert2.example.org,whatever.* Each name will be validated against its IPv6 address or - if IPv6 is not configured in your domain - IPv4 address. A wildcard name like smtp.* will try to obtain a smtp.DOMAIN_NAME SAN for each domain added to mailcow. Run docker-compose up -d to recreate affected containers automatically. Info Using names other name MAILCOW_HOSTNAME to access the mailcow UI may need further configuration. If you plan to use a server name that is not MAILCOW_HOSTNAME to access the mailcow UI (for example by adding mail.* to ADDITIONAL_SAN make sure to populate that name in mailcow.conf via ADDITIONAL_SERVER_NAMES . Names must be separated by commas and must not contain spaces. If you skip this step, mailcow may respond with an incorrect site. ADDITIONAL_SERVER_NAMES=webmail.domain.tld,other.example.tld Run docker-compose up -d to apply. Force renewal \u00b6 To force a renewal, you need to create a file named force_renew and restart the acme-mailcow container: cd /opt/mailcow-dockerized touch data/assets/ssl/force_renew docker-compose restart acme-mailcow # Now check the logs for a renewal docker-compose logs --tail=200 -f acme-mailcow The file will be deleted automatically. Validation errors and how to skip validation \u00b6 You can skip the IP verification by setting SKIP_IP_CHECK=y in mailcow.conf (no quotes). Be warned that a misconfiguration will get you ratelimited by Let's Encrypt! This is primarily useful for multi-IP setups where the IP check would return the incorrect source IP address. Due to using dynamic IPs for acme-mailcow, source NAT is not consistent over restarts. If you encounter problems with \"HTTP validation\", but your IP address confirmation succeeds, you are most likely using firewalld, ufw or any other firewall, that disallows connections from br-mailcow to your external interface. Both firewalld and ufw disallow this by default. It is often not enough to just stop these firewall services. You'd need to stop mailcow ( docker-compose down ), stop the firewall service, flush the chains and restart Docker. You can also skip this validation method by setting SKIP_HTTP_VERIFICATION=y in \"mailcow.conf\". Be warned that this is discouraged. In most cases, the HTTP verification is skipped to workaround unknown NAT reflection issues, which are not resolved by ignoring this specific network misconfiguration. If you encounter problems generating TLSA records in the DNS overview within mailcow, you are most likely having issues with NAT reflection you should fix. If you changed a SKIP_* parameter, run docker-compose up -d to apply your changes. Disable Let's Encrypt \u00b6 Disable Let's Encrypt completely \u00b6 Set SKIP_LETS_ENCRYPT=y in \"mailcow.conf\" and recreate \"acme-mailcow\" by running docker-compose up -d . Skip all names but ${MAILCOW_HOSTNAME} \u00b6 Add ONLY_MAILCOW_HOSTNAME=y to \"mailcow.conf\" and recreate \"acme-mailcow\" by running docker-compose up -d . The Let's Encrypt subjectAltName limit of 100 domains \u00b6 Let's Encrypt currently has a limit of 100 Domain Names per Certificate . By default, \"acme-mailcow\" will create a single SAN certificate for all validated domains (see the first section and Additional domain names ). This provides best compatibility but means the Let's Encrypt limit exceeds if you add too many domains to a single mailcow installation. To solve this, you can configure ENABLE_SSL_SNI to generate: A main server certificate with MAILCOW_HOSTNAME and all fully qualified domain names in the ADDITIONAL_SAN config One additional certificate for each domain found in the database with autodiscover. , autoconfig. and any other ADDITIONAL_SAN configured in this format (subdomain.*). Limitations: A certificate name ADDITIONAL_SAN=test.example.com will be added as SAN to the main certificate. A separate certificate/key pair will not be generated for this format. Postfix, Dovecot and Nginx will then serve these certificates with SNI. Set ENABLE_SSL_SNI=y in \"mailcow.conf\" and recreate \"acme-mailcow\" by running docker-compose up -d . Warning Not all clients support SNI, see Dovecot documentation or Wikipedia . You should make sure these clients use the MAILCOW_HOSTNAME for secure connections if you enable this feature. Here is an example: MAILCOW_HOSTNAME=server.email.tld ADDITIONAL_SAN=webmail.email.tld,mail.* Mailcow email domains: \"domain1.tld\" and \"domain2.tld\" The following certificates will be generated: server.email.tld, webmail.email.tld -> this is the default certificate, all clients can connect with these domains mail.domain1.tld, autoconfig.domain1.tld, autodiscover.domain1.tld -> individual certificate for domain1.tld, cannot be used by clients without SNI support mail.domain2.tld, autoconfig.domain2.tld, autodiscover.domain2.tld -> individual certificate for domain2.tld, cannot be used by clients without SNI support How to use your own certificate \u00b6 Make sure you disable mailcows internal LE client (see above). To use your own certificates, just save the combined certificate (containing the certificate and intermediate CA/CA if any) to data/assets/ssl/cert.pem and the corresponding key to data/assets/ssl/key.pem . IMPORTANT: Do not use symbolic links! Make sure you copy the certificates and do not link them to data/assets/ssl . Restart affected services afterwards: docker restart $(docker ps -qaf name=postfix-mailcow) docker restart $(docker ps -qaf name=nginx-mailcow) docker restart $(docker ps -qaf name=dovecot-mailcow) See Post-hook script for non-mailcow ACME clients for a full example script. Test against staging ACME directory \u00b6 Edit mailcow.conf and add LE_STAGING=y . Run docker-compose up -d to activate your changes. Custom directory URL \u00b6 Edit mailcow.conf and add the corresponding directory URL to the new variable DIRECTORY_URL : DIRECTORY_URL=https://acme-custom-v9000.api.letsencrypt.org/directory You cannot use LE_STAGING with DIRECTORY_URL . If both are set, only LE_STAGING is used. Run docker-compose up -d to activate your changes. Check your configuration \u00b6 Run docker-compose logs acme-mailcow to find out why a validation fails. To check if nginx serves the correct certificate, simply use a browser of your choice and check the displayed certificate. To check the certificate served by Postfix, Dovecot and Nginx we will use openssl : # Connect via SMTP (587) echo \"Q\" | openssl s_client -starttls smtp -crlf -connect mx.mailcow.email:587 # Connect via IMAP (143) echo \"Q\" | openssl s_client -starttls imap -showcerts -connect mx.mailcow.email:143 # Connect via HTTPS (443) echo \"Q\" | openssl s_client -connect mx.mailcow.email:443 To validate the expiry dates as returned by openssl against MAILCOW_HOSTNAME, you are able to use our helper script: cd /opt/mailcow-dockerized bash helper-scripts/expiry-dates.sh","title":"Advanced SSL"},{"location":"firststeps-ssl/#lets-encrypt-out-of-the-box","text":"The \"acme-mailcow\" container will try to obtain a LE certificate for ${MAILCOW_HOSTNAME} , autodiscover.ADDED_MAIL_DOMAIN and autoconfig.ADDED_MAIL_DOMAIN . Warning mailcow must be available on port 80 for the acme-client to work. Our reverse proxy example configurations do cover that. You can also use any external ACME client (certbot for example) to obtain certificates, but you will need to make sure, that they are copied to the correct location and a post-hook reloads affected containers. See more in the Reverse Proxy documentation. By default, which means 0 domains are added to mailcow, it will try to obtain a certificate for ${MAILCOW_HOSTNAME} . For each domain you add, it will try to resolve autodiscover.ADDED_MAIL_DOMAIN and autoconfig.ADDED_MAIL_DOMAIN to its IPv6 address or - if IPv6 is not configured in your domain - IPv4 address. If it succeeds, a name will be added as SAN to the certificate request. Only names that can be validated, will be added as SAN. For every domain you remove, the certificate will be moved and a new certificate will be requested. It is not possible to keep domains in a certificate, when we are not able validate the challenge for those. If you want to re-run the ACME client, use docker-compose restart acme-mailcow and monitor its logs with docker-compose logs --tail=200 -f acme-mailcow .","title":"Let's Encrypt (out-of-the-box)"},{"location":"firststeps-ssl/#additional-domain-names","text":"Edit \"mailcow.conf\" and add a parameter ADDITIONAL_SAN like this: Do not use quotes ( \" ) and do not use spaces between the names! ADDITIONAL_SAN=smtp.*,cert1.example.com,cert2.example.org,whatever.* Each name will be validated against its IPv6 address or - if IPv6 is not configured in your domain - IPv4 address. A wildcard name like smtp.* will try to obtain a smtp.DOMAIN_NAME SAN for each domain added to mailcow. Run docker-compose up -d to recreate affected containers automatically. Info Using names other name MAILCOW_HOSTNAME to access the mailcow UI may need further configuration. If you plan to use a server name that is not MAILCOW_HOSTNAME to access the mailcow UI (for example by adding mail.* to ADDITIONAL_SAN make sure to populate that name in mailcow.conf via ADDITIONAL_SERVER_NAMES . Names must be separated by commas and must not contain spaces. If you skip this step, mailcow may respond with an incorrect site. ADDITIONAL_SERVER_NAMES=webmail.domain.tld,other.example.tld Run docker-compose up -d to apply.","title":"Additional domain names"},{"location":"firststeps-ssl/#force-renewal","text":"To force a renewal, you need to create a file named force_renew and restart the acme-mailcow container: cd /opt/mailcow-dockerized touch data/assets/ssl/force_renew docker-compose restart acme-mailcow # Now check the logs for a renewal docker-compose logs --tail=200 -f acme-mailcow The file will be deleted automatically.","title":"Force renewal"},{"location":"firststeps-ssl/#validation-errors-and-how-to-skip-validation","text":"You can skip the IP verification by setting SKIP_IP_CHECK=y in mailcow.conf (no quotes). Be warned that a misconfiguration will get you ratelimited by Let's Encrypt! This is primarily useful for multi-IP setups where the IP check would return the incorrect source IP address. Due to using dynamic IPs for acme-mailcow, source NAT is not consistent over restarts. If you encounter problems with \"HTTP validation\", but your IP address confirmation succeeds, you are most likely using firewalld, ufw or any other firewall, that disallows connections from br-mailcow to your external interface. Both firewalld and ufw disallow this by default. It is often not enough to just stop these firewall services. You'd need to stop mailcow ( docker-compose down ), stop the firewall service, flush the chains and restart Docker. You can also skip this validation method by setting SKIP_HTTP_VERIFICATION=y in \"mailcow.conf\". Be warned that this is discouraged. In most cases, the HTTP verification is skipped to workaround unknown NAT reflection issues, which are not resolved by ignoring this specific network misconfiguration. If you encounter problems generating TLSA records in the DNS overview within mailcow, you are most likely having issues with NAT reflection you should fix. If you changed a SKIP_* parameter, run docker-compose up -d to apply your changes.","title":"Validation errors and how to skip validation"},{"location":"firststeps-ssl/#disable-lets-encrypt","text":"","title":"Disable Let's Encrypt"},{"location":"firststeps-ssl/#disable-lets-encrypt-completely","text":"Set SKIP_LETS_ENCRYPT=y in \"mailcow.conf\" and recreate \"acme-mailcow\" by running docker-compose up -d .","title":"Disable Let's Encrypt completely"},{"location":"firststeps-ssl/#skip-all-names-but-mailcow_hostname","text":"Add ONLY_MAILCOW_HOSTNAME=y to \"mailcow.conf\" and recreate \"acme-mailcow\" by running docker-compose up -d .","title":"Skip all names but ${MAILCOW_HOSTNAME}"},{"location":"firststeps-ssl/#the-lets-encrypt-subjectaltname-limit-of-100-domains","text":"Let's Encrypt currently has a limit of 100 Domain Names per Certificate . By default, \"acme-mailcow\" will create a single SAN certificate for all validated domains (see the first section and Additional domain names ). This provides best compatibility but means the Let's Encrypt limit exceeds if you add too many domains to a single mailcow installation. To solve this, you can configure ENABLE_SSL_SNI to generate: A main server certificate with MAILCOW_HOSTNAME and all fully qualified domain names in the ADDITIONAL_SAN config One additional certificate for each domain found in the database with autodiscover. , autoconfig. and any other ADDITIONAL_SAN configured in this format (subdomain.*). Limitations: A certificate name ADDITIONAL_SAN=test.example.com will be added as SAN to the main certificate. A separate certificate/key pair will not be generated for this format. Postfix, Dovecot and Nginx will then serve these certificates with SNI. Set ENABLE_SSL_SNI=y in \"mailcow.conf\" and recreate \"acme-mailcow\" by running docker-compose up -d . Warning Not all clients support SNI, see Dovecot documentation or Wikipedia . You should make sure these clients use the MAILCOW_HOSTNAME for secure connections if you enable this feature. Here is an example: MAILCOW_HOSTNAME=server.email.tld ADDITIONAL_SAN=webmail.email.tld,mail.* Mailcow email domains: \"domain1.tld\" and \"domain2.tld\" The following certificates will be generated: server.email.tld, webmail.email.tld -> this is the default certificate, all clients can connect with these domains mail.domain1.tld, autoconfig.domain1.tld, autodiscover.domain1.tld -> individual certificate for domain1.tld, cannot be used by clients without SNI support mail.domain2.tld, autoconfig.domain2.tld, autodiscover.domain2.tld -> individual certificate for domain2.tld, cannot be used by clients without SNI support","title":"The Let's Encrypt subjectAltName limit of 100 domains"},{"location":"firststeps-ssl/#how-to-use-your-own-certificate","text":"Make sure you disable mailcows internal LE client (see above). To use your own certificates, just save the combined certificate (containing the certificate and intermediate CA/CA if any) to data/assets/ssl/cert.pem and the corresponding key to data/assets/ssl/key.pem . IMPORTANT: Do not use symbolic links! Make sure you copy the certificates and do not link them to data/assets/ssl . Restart affected services afterwards: docker restart $(docker ps -qaf name=postfix-mailcow) docker restart $(docker ps -qaf name=nginx-mailcow) docker restart $(docker ps -qaf name=dovecot-mailcow) See Post-hook script for non-mailcow ACME clients for a full example script.","title":"How to use your own certificate"},{"location":"firststeps-ssl/#test-against-staging-acme-directory","text":"Edit mailcow.conf and add LE_STAGING=y . Run docker-compose up -d to activate your changes.","title":"Test against staging ACME directory"},{"location":"firststeps-ssl/#custom-directory-url","text":"Edit mailcow.conf and add the corresponding directory URL to the new variable DIRECTORY_URL : DIRECTORY_URL=https://acme-custom-v9000.api.letsencrypt.org/directory You cannot use LE_STAGING with DIRECTORY_URL . If both are set, only LE_STAGING is used. Run docker-compose up -d to activate your changes.","title":"Custom directory URL"},{"location":"firststeps-ssl/#check-your-configuration","text":"Run docker-compose logs acme-mailcow to find out why a validation fails. To check if nginx serves the correct certificate, simply use a browser of your choice and check the displayed certificate. To check the certificate served by Postfix, Dovecot and Nginx we will use openssl : # Connect via SMTP (587) echo \"Q\" | openssl s_client -starttls smtp -crlf -connect mx.mailcow.email:587 # Connect via IMAP (143) echo \"Q\" | openssl s_client -starttls imap -showcerts -connect mx.mailcow.email:143 # Connect via HTTPS (443) echo \"Q\" | openssl s_client -connect mx.mailcow.email:443 To validate the expiry dates as returned by openssl against MAILCOW_HOSTNAME, you are able to use our helper script: cd /opt/mailcow-dockerized bash helper-scripts/expiry-dates.sh","title":"Check your configuration"},{"location":"firststeps-sync_jobs_migration/","text":"Sync jobs are used to copy or move existing emails from an external IMAP server or within mailcow's existing mailboxes. Info Depending on your mailbox's ACL you may not have the option to add a sync job. Please contact your domain administrator if so. Setup a Sync Job \u00b6 In the \"Mail Setup\" or \"User Settings\" interface, create a new sync job. If you are an administrator, select the username of the downstream mailcow mailbox in the \"Username\" dropdown. Fill in the \"Host\" and \"Port\" fields with their respective correct values from the upstream IMAP server. In the \"Username\" and \"Password\" fields, supply the correct access credentials from the upstream IMAP server. Select the \"Encryption Method\". If the upstream IMAP server uses port 143, it is likely that the encryption method is TLS and SSL for port 993. Nevertheless, you can use PLAIN authentication, but it is stongly discouraged. For all ther other fields, you can leave them as is or modify them as desired. Make sure to tick \"Active\" and click \"Add\". Info Once Completed, log into the mailbox and check if all emails are imported correctly. If all goes well, all your mails shall end up in your new mailbox. And don't forget to delete or deactivate the sync job after it is used.","title":"Sync job migration"},{"location":"firststeps-sync_jobs_migration/#setup-a-sync-job","text":"In the \"Mail Setup\" or \"User Settings\" interface, create a new sync job. If you are an administrator, select the username of the downstream mailcow mailbox in the \"Username\" dropdown. Fill in the \"Host\" and \"Port\" fields with their respective correct values from the upstream IMAP server. In the \"Username\" and \"Password\" fields, supply the correct access credentials from the upstream IMAP server. Select the \"Encryption Method\". If the upstream IMAP server uses port 143, it is likely that the encryption method is TLS and SSL for port 993. Nevertheless, you can use PLAIN authentication, but it is stongly discouraged. For all ther other fields, you can leave them as is or modify them as desired. Make sure to tick \"Active\" and click \"Add\". Info Once Completed, log into the mailbox and check if all emails are imported correctly. If all goes well, all your mails shall end up in your new mailbox. And don't forget to delete or deactivate the sync job after it is used.","title":"Setup a Sync Job"},{"location":"i_u_m_install/","text":"Warning Make sure you've read \"Prepare Your System\" before proceeding! We do not recommend CentOS 8 anymore. You need Docker and Docker Compose. 1. Learn how to install Docker and Docker Compose . Quick installation for most operation systems: Docker curl -sSL https://get.docker.com/ | CHANNEL=stable sh # After the installation process is finished, you may need to enable the service and make sure it is started (e.g. CentOS 7) systemctl enable docker.service systemctl start docker.service Docker-Compose Warning mailcow requires the latest version of docker-compose. It is highly recommended to use the commands below to install docker-compose . Package managers (e.g. apt , yum ) likely won't give you the latest version. Note: This command downloads docker-compose from the official Docker Github repository and is a safe method. The snippet will determine the latest supported version by mailcow. In almost all cases this is the latest version available (exceptions are broken releases or major changes not yet supported by mailcow). curl -L https://github.com/docker/compose/releases/download/$(curl -Ls https://www.servercow.de/docker-compose/latest.php)/docker-compose-$(uname -s)-$(uname -m) > /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose Please use the latest Docker engine available and do not use the engine that ships with your distros repository. 1.1. On SELinux enabled systems, e.g. CentOS 7: Check if \"container-selinux\" package is present on your system: rpm -qa | grep container-selinux If the above command returns an empty or no output, you should install it via your package manager. Check if docker has SELinux support enabled: docker info | grep selinux If the above command returns an empty or no output, create or edit /etc/docker/daemon.json and add \"selinux-enabled\": true . Example file content: { \"selinux-enabled\": true } Restart the docker daemon and verify SELinux is now enabled. This step is required to make sure mailcows volumes are properly labeled as declared in the compose file. If you are interested in how this works, you can check out the readme of https://github.com/containers/container-selinux which links to a lot of useful information on that topic. 2. Clone the master branch of the repository, make sure your umask equals 0022. Please clone the repository as root user and also control the stack as root. We will modify attributes - if necessary - while boostrapping the containers automatically and make sure everything is secured. The update.sh script must therefore also be run as root. It might be necessary to change ownership and other attributes of files you will otherwise not have access to. We drop permissions for every exposed application and will not run an exposed service as root! Controlling the Docker daemon as non-root user does not give you additional security. The unprivileged user will spawn the containers as root likewise. The behaviour of the stack is identical. $ su # umask 0022 # <- Verify it is 0022 # cd /opt # git clone https://github.com/mailcow/mailcow-dockerized # cd mailcow-dockerized 3. Generate a configuration file. Use a FQDN ( host.domain.tld ) as hostname when asked. ./generate_config.sh 4. Change configuration if you want or need to. nano mailcow.conf If you plan to use a reverse proxy, you can, for example, bind HTTPS to 127.0.0.1 on port 8443 and HTTP to 127.0.0.1 on port 8080. You may need to stop an existing pre-installed MTA which blocks port 25/tcp. See this chapter to learn how to reconfigure Postfix to run besides mailcow after a successful installation. Some updates modify mailcow.conf and add new parameters. It is hard to keep track of them in the documentation. Please check their description and, if unsure, ask at the known channels for advise. 4.1. Users with a MTU not equal to 1500 (e.g. OpenStack): Whenever you run into trouble and strange phenomena, please check your MTU. Edit docker-compose.yml and change the network settings according to your MTU. Add the new driver_opts parameter like this: networks: mailcow-network: ... driver_opts: com.docker.network.driver.mtu: 1450 ... 4.2. Users without an IPv6 enabled network on their host system: Enable IPv6. Finally. If you do not have an IPv6 enabled network on your host and you don't care for a better internet (thehe), it is recommended to disable IPv6 for the mailcow network to prevent unforeseen issues. 5. Pull the images and run the compose file. The parameter -d will start mailcow: dockerized detached: docker-compose pull docker-compose up -d Done! You can now access https://${MAILCOW_HOSTNAME} with the default credentials admin + password moohoo . Info If you are not using mailcow behind a reverse proxy, you should redirect all HTTP requests to HTTPS . The database will be initialized right after a connection to MySQL can be established. Your data will persist in multiple Docker volumes, that are not deleted when you recreate or delete containers. Run docker volume ls to see a list of all volumes. You can safely run docker-compose down without removing persistent data.","title":"Installation"},{"location":"i_u_m_migration/","text":"Warning This guide assumes you intend to migrate an existing mailcow server (source) over to a brand new, empty server (target). It takes no care about preserving any existing data on your target server and will erase anything within /var/lib/docker/volumes and thus any Docker volumes you may have already set up. Tip Alternatively, you can use the ./helper-scripts/backup_and_restore.sh script to create a full backup on the source machine, then install mailcow on the target machine as usual, copy over your mailcow.conf and use the same script to restore your backup to the target machine. 1. Install Docker and Docker Compose on your new server. Quick installation for most operation systems: Docker curl -sSL https://get.docker.com/ | CHANNEL=stable sh # After the installation process is finished, you may need to enable the service and make sure it is started (e.g. CentOS 7) systemctl enable docker.service docker-compose curl -L https://github.com/docker/compose/releases/download/$(curl -Ls https://www.servercow.de/docker-compose/latest.php)/docker-compose-$(uname -s)-$(uname -m) > /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose Please use the latest Docker engine available and do not use the engine that ships with your distros repository. 2. Stop Docker and assure Docker has stopped: systemctl stop docker.service systemctl status docker.service 3. Run the following commands on the source machine (take care of adding the trailing slashes in the first path parameter as shown below!) - WARNING: This command will erase anything that may already exist under /var/lib/docker/volumes on the target machine : rsync -aHhP --numeric-ids --delete /opt/mailcow-dockerized/ root@target-machine.example.com:/opt/mailcow-dockerized rsync -aHhP --numeric-ids --delete /var/lib/docker/volumes/ root@target-machine.example.com:/var/lib/docker/volumes 4. Shut down mailcow and stop Docker on the source machine. cd /opt/mailcow-dockerized docker-compose down systemctl stop docker.service 5. Repeat step 3 with the same commands. This will be much quicker than the first time. 6. Switch over to the target machine and start Docker. systemctl start docker.service 7. Now pull the mailcow Docker images on the target machine. cd /opt/mailcow-dockerized docker-compose pull 8. Start the whole mailcow stack and everything should be done! docker-compose up -d 9. Finally, change your DNS settings to point to the target server.","title":"Migration"},{"location":"i_u_m_update/","text":"Automatic update \u00b6 An update script in your mailcow-dockerized directory will take care of updates. But use it with caution! If you think you made a lot of changes to the mailcow code, you should use the manual update guide below. Run the update script: ./update.sh If it needs to, it will ask you how you wish to proceed. Merge errors will be reported. Some minor conflicts will be auto-corrected (in favour for the mailcow: dockerized repository code). Options \u00b6 # Options can be combined # - Check for updates and show changes ./update.sh --check # Do not try to update docker-compose, **make sure to use the latest docker-compose available** ./update.sh --no-update-compose # - Do not start mailcow after applying an update ./update.sh --skip-start # - Force update (unattended, but unsupported, use at own risk) ./update.sh --force # - Run garbage collector to cleanup old image tags and exit ./update.sh --gc # - Update with merge strategy option \"ours\" instead of \"theirs\" # This will **solve conflicts** when merging in favor for your local changes and should be avoided. Local changes will always be kept, unless we changed file XY, too. ./update.sh --ours # - Don't update, but prefetch images and exit ./update.sh --prefetch I forgot what I changed before running update.sh \u00b6 See git log --pretty=oneline | grep -i \"before update\" , you will have an output similar to this: 22cd00b5e28893ef9ddef3c2b5436453cc5223ab Before update on 2020-09-28_19_25_45 dacd4fb9b51e9e1c8a37d84485b92ffaf6c59353 Before update on 2020-08-07_13_31_31 Run git diff 22cd00b5e28893ef9ddef3c2b5436453cc5223ab to see what changed. Can I roll back? \u00b6 Yes. See the topic above, instead of a diff, you run checkout: docker-compose down # Replace commit ID 22cd00b5e28893ef9ddef3c2b5436453cc5223ab by your ID git checkout 22cd00b5e28893ef9ddef3c2b5436453cc5223ab docker-compose pull docker-compose up -d Footnotes \u00b6 There is no release cycle regarding updates.","title":"Update"},{"location":"i_u_m_update/#automatic-update","text":"An update script in your mailcow-dockerized directory will take care of updates. But use it with caution! If you think you made a lot of changes to the mailcow code, you should use the manual update guide below. Run the update script: ./update.sh If it needs to, it will ask you how you wish to proceed. Merge errors will be reported. Some minor conflicts will be auto-corrected (in favour for the mailcow: dockerized repository code).","title":"Automatic update"},{"location":"i_u_m_update/#options","text":"# Options can be combined # - Check for updates and show changes ./update.sh --check # Do not try to update docker-compose, **make sure to use the latest docker-compose available** ./update.sh --no-update-compose # - Do not start mailcow after applying an update ./update.sh --skip-start # - Force update (unattended, but unsupported, use at own risk) ./update.sh --force # - Run garbage collector to cleanup old image tags and exit ./update.sh --gc # - Update with merge strategy option \"ours\" instead of \"theirs\" # This will **solve conflicts** when merging in favor for your local changes and should be avoided. Local changes will always be kept, unless we changed file XY, too. ./update.sh --ours # - Don't update, but prefetch images and exit ./update.sh --prefetch","title":"Options"},{"location":"i_u_m_update/#i-forgot-what-i-changed-before-running-updatesh","text":"See git log --pretty=oneline | grep -i \"before update\" , you will have an output similar to this: 22cd00b5e28893ef9ddef3c2b5436453cc5223ab Before update on 2020-09-28_19_25_45 dacd4fb9b51e9e1c8a37d84485b92ffaf6c59353 Before update on 2020-08-07_13_31_31 Run git diff 22cd00b5e28893ef9ddef3c2b5436453cc5223ab to see what changed.","title":"I forgot what I changed before running update.sh"},{"location":"i_u_m_update/#can-i-roll-back","text":"Yes. See the topic above, instead of a diff, you run checkout: docker-compose down # Replace commit ID 22cd00b5e28893ef9ddef3c2b5436453cc5223ab by your ID git checkout 22cd00b5e28893ef9ddef3c2b5436453cc5223ab docker-compose pull docker-compose up -d","title":"Can I roll back?"},{"location":"i_u_m_update/#footnotes","text":"There is no release cycle regarding updates.","title":"Footnotes"},{"location":"model-acl/","text":"Editing a domain administrator or a mailbox user allows to set restrictions to that account. Important : For overlapping modules like sync jobs, which both domain administrators and mailbox users can be granted access to, the domain administrators permissions are inherited, when logging in as mailbox user. Some examples: 1. A domain administror has not access to sync jobs but can login as mailbox user When logging in as mailbox user, he does not gain access to sync jobs, even if the given mailbox user has access when logging in directly 2. A domain administror has access to sync jobs and can login as mailbox user The mailbox user he tries to login as has not access to sync jobs The domain administrator, now logged in as mailbox user, inherits its permission to the mailbox user and can access sync jobs 3. A domain administrator logs in as mailbox user Every permission, that does not exist in a domain administrators ACL, is automatically granted (example: time-limited alias, TLS policy etc.)","title":"ACL"},{"location":"model-passwd/","text":"Fully supported hashing methods \u00b6 The most current mailcow fully supports the following hashing methods. The default hashing method is written in bold: BLF-CRYPT SSHA SSHA256 SSHA512 The methods above can be used in mailcow.conf as MAILCOW_PASS_SCHEME value. Read-only hashing methods \u00b6 The following methods are supported read only . If you plan to use SOGo (as per default), you need a SOGo compatible hashing method. Please see the note at the bottom of this page how to update the view if necessary. With SOGo disabled, all hashing methods below will be able to be read by mailcow and Dovecot. ARGON2I (SOGo compatible) ARGON2ID (SOGo compatible) CLEAR CLEARTEXT CRYPT (SOGo compatible) DES-CRYPT LDAP-MD5 (SOGo compatible) MD5 (SOGo compatible) MD5-CRYPT (SOGo compatible) PBKDF2 (SOGo compatible) PLAIN (SOGo compatible) PLAIN-MD4 PLAIN-MD5 PLAIN-TRUNC SHA (SOGo compatible) SHA1 (SOGo compatible) SHA256 (SOGo compatible) SHA256-CRYPT (SOGo compatible) SHA512 (SOGo compatible) SHA512-CRYPT (SOGo compatible) SMD5 (SOGo compatible) That means mailcow is able to verify users with a hash like {MD5}1a1dc91c907325c69271ddf0c944bc72 from the database. The value of MAILCOW_PASS_SCHEME will always be used to encrypt new passwords. I changed the password hashes in the \"mailbox\" SQL table and cannot login. A \"view\" needs to be updated. You can trigger this by restarting sogo-mailcow: docker-compose restart sogo-mailcow","title":"Password hashing"},{"location":"model-passwd/#fully-supported-hashing-methods","text":"The most current mailcow fully supports the following hashing methods. The default hashing method is written in bold: BLF-CRYPT SSHA SSHA256 SSHA512 The methods above can be used in mailcow.conf as MAILCOW_PASS_SCHEME value.","title":"Fully supported hashing methods"},{"location":"model-passwd/#read-only-hashing-methods","text":"The following methods are supported read only . If you plan to use SOGo (as per default), you need a SOGo compatible hashing method. Please see the note at the bottom of this page how to update the view if necessary. With SOGo disabled, all hashing methods below will be able to be read by mailcow and Dovecot. ARGON2I (SOGo compatible) ARGON2ID (SOGo compatible) CLEAR CLEARTEXT CRYPT (SOGo compatible) DES-CRYPT LDAP-MD5 (SOGo compatible) MD5 (SOGo compatible) MD5-CRYPT (SOGo compatible) PBKDF2 (SOGo compatible) PLAIN (SOGo compatible) PLAIN-MD4 PLAIN-MD5 PLAIN-TRUNC SHA (SOGo compatible) SHA1 (SOGo compatible) SHA256 (SOGo compatible) SHA256-CRYPT (SOGo compatible) SHA512 (SOGo compatible) SHA512-CRYPT (SOGo compatible) SMD5 (SOGo compatible) That means mailcow is able to verify users with a hash like {MD5}1a1dc91c907325c69271ddf0c944bc72 from the database. The value of MAILCOW_PASS_SCHEME will always be used to encrypt new passwords. I changed the password hashes in the \"mailbox\" SQL table and cannot login. A \"view\" needs to be updated. You can trigger this by restarting sogo-mailcow: docker-compose restart sogo-mailcow","title":"Read-only hashing methods"},{"location":"model-sender_rcv/","text":"When a mailbox is created, a user is allowed to send mail from and receive mail for his own mailbox address. Mailbox me @example . org is created . example . org is a primary domain . Note : a mailbox cannot be created in an alias domain . me @example . org is only known as me @example . org . me @example . org is allowed to send as me @example . org . We can add an alias domain for example.org: Alias domain alias . com is added and assigned to primary domain example . org . me @example . org is now known as me @example . org and me @alias . com . me @example . org is now allowed to send as me @example . org and me @alias . com . We can add aliases for a mailbox to receive mail for and to send from this new address. It is important to know, that you are not able to receive mail for my-alias@my-alias-domain.tld . You would need to create this particular alias. me @example . org is assigned the alias alias @example . org me @example . org is now known as me @example . org , me @alias . com , alias @example . org me @example . org is NOT known as alias @alias . com . Please note that this does not apply to catch-all aliases: Alias domain alias . com is added and assigned to primary domain example . org me @example . org is assigned the catch - all alias @example . org me @example . org is still just known as me @example . org , which is the only available send - as option Any email send to alias . com will match the catch - all alias for example . org Administrators and domain administrators can edit mailboxes to allow specific users to send as other mailbox users (\"delegate\" them). You can choose between mailbox users or completely disable the sender check for domains. SOGo \"mail from\" addresses \u00b6 Mailbox users can, obviously, select their own mailbox address, as well as all alias addresses and aliases that exist through alias domains. If you want to select another existing mailbox user as your \"mail from\" address, this user has to delegate you access through SOGo (see SOGo documentation). Moreover a mailcow (domain) administrator needs to grant you access as described above.","title":"Sender and receiver model"},{"location":"model-sender_rcv/#sogo-mail-from-addresses","text":"Mailbox users can, obviously, select their own mailbox address, as well as all alias addresses and aliases that exist through alias domains. If you want to select another existing mailbox user as your \"mail from\" address, this user has to delegate you access through SOGo (see SOGo documentation). Moreover a mailcow (domain) administrator needs to grant you access as described above.","title":"SOGo \"mail from\" addresses"},{"location":"prerequisite-dns/","text":"Below you can find a list of recommended DNS records . While some are mandatory for a mail server (A, MX), others are recommended to build a good reputation score (TXT/SPF) or used for auto-configuration of mail clients (SRV). References \u00b6 A good article covering all relevant topics: \"3 DNS Records Every Email Marketer Must Know\" Another great one, but Zimbra as an example platform: \"Best Practices on Email Protection: SPF, DKIM and DMARC\" An in-depth discussion of SPF, DKIM and DMARC: \"How to eliminate spam and protect your name with DMARC\" A thorough guide on understanding DMARC: \"Demystifying DMARC: A guide to preventing email spoofing\" Reverse DNS of your IP address \u00b6 Make sure that the PTR record of your IP address matches the FQDN of your mailcow host: ${MAILCOW_HOSTNAME} 1 . This record is usually set at the provider you leased the IP address (server) from. The minimal DNS configuration \u00b6 This example shows you a set of records for one domain managed by mailcow. Each domain that is added to mailcow needs at least this set of records to function correctly. # Name Type Value mail IN A 1.2.3.4 autodiscover IN CNAME mail.example.org. (your ${MAILCOW_HOSTNAME}) autoconfig IN CNAME mail.example.org. (your ${MAILCOW_HOSTNAME}) @ IN MX 10 mail.example.org. (your ${MAILCOW_HOSTNAME}) DKIM, SPF and DMARC \u00b6 In the example DNS zone file snippet below, a simple SPF TXT record is used to only allow THIS server (the MX) to send mail for your domain. Every other server is disallowed but able to (\" ~all \"). Please refer to SPF Project for further reading. # Name Type Value @ IN TXT \"v=spf1 mx a -all\" It is highly recommended to create a DKIM TXT record in your mailcow UI and set the corresponding TXT record in your DNS records. Please refer to OpenDKIM for further reading. # Name Type Value dkim._domainkey IN TXT \"v=DKIM1; k=rsa; t=s; s=email; p=...\" The last step in protecting yourself and others is the implementation of a DMARC TXT record, for example by using the DMARC Assistant ( check ). # Name Type Value _dmarc IN TXT \"v=DMARC1; p=reject; rua=mailto:mailauth-reports@example.org\" The advanced DNS configuration \u00b6 SRV records specify the server(s) for a specific protocol on your domain. If you want to explicitly announce a service as not provided, give \".\" as the target address (instead of \"mail.example.org.\"). Please refer to RFC 2782 . # Name Type Priority Weight Port Value _autodiscover._tcp IN SRV 0 1 443 mail.example.org. (your ${MAILCOW_HOSTNAME}) _caldavs._tcp IN SRV 0 1 443 mail.example.org. (your ${MAILCOW_HOSTNAME}) _caldavs._tcp IN TXT \"path=/SOGo/dav/\" _carddavs._tcp IN SRV 0 1 443 Mail.example.org. (your ${MAILCOW_HOSTNAME}) _carddavs._tcp IN TXT \"path=/SOGo/dav/\" _imap._tcp IN SRV 0 1 143 mail.example.org. (your ${MAILCOW_HOSTNAME}) _imaps._tcp IN SRV 0 1 993 mail.example.org. (your ${MAILCOW_HOSTNAME}) _pop3._tcp IN SRV 0 1 110 mail.example.org. (your ${MAILCOW_HOSTNAME}) _pop3s._tcp IN SRV 0 1 995 mail.example.org. (your ${MAILCOW_HOSTNAME}) _sieve._tcp IN SRV 0 1 4190 mail.example.org. (your ${MAILCOW_HOSTNAME}) _smtps._tcp IN SRV 0 1 465 mail.example.org. (your ${MAILCOW_HOSTNAME}) _submission._tcp IN SRV 0 1 587 mail.example.org. (your ${MAILCOW_HOSTNAME}) Testing \u00b6 Here are some tools you can use to verify your DNS configuration: MX Toolbox (DNS, SMTP, RBL) port25.com (DKIM, SPF) Mail-tester (DKIM, DMARC, SPF) DMARC Analyzer (DMARC, SPF) MultiRBL.valli.org (DNSBL, RBL, FCrDNS) Misc \u00b6 Optional DMARC Statistics \u00b6 If you are interested in statistics, you can additionally register with some of the many below DMARC statistic services, or self-host your own. NOTE: It is worth considering that if you request DMARC statistic reports to your mailcow server, if there are issues with that domain you may not get accurate results. You can consider using an alternative email domain for recieving DMARC reports. It is worth mentioning, that the following suggestions are not a comprehensive list of all services and tools avaialble, but only a small few of the many choices. Postmaster Tool parsedmarc (self-hosted) Fraudmarc Postmark Dmarcian NOTE: The services may provide you with a TXT record, which you would insert into your DNS records as the provider specifies. This record will give you details about spam-classified mails by your domain. However, please ensure to read the providers documentation from the service you choose, as this process may vary and not all providers may use a TXT record. Email Test for SPF, DKIM and DMARC: \u00b6 To test send an email to the email below and wait for a reply: check-auth@verifier.port25.com You will get a report back that looks like the following: ========================================================== Summary of Results ========================================================== SPF check: pass \"iprev\" check: pass DKIM check: pass DKIM check: pass SpamAssassin check: ham ========================================================== Details: ========================================================== .... The full report will contain more technical details this is just the first section, we found this to be quite usful for testing both outgoing mail and spam scores. Fully Qualified Domain Name (FQDN) \u00b6 A Fully Qualified Domain Name ( FQDN ) is the complete (absolute) domain name for a specific computer or host, on the Internet. The FQDN consists of at least three parts divided by a dot: the hostname (myhost), the domain name (mydomain) and the top level domain in short tld (com). In the example of mx.mailcow.email the hostname would be mx , the domain name mailcow and the tld email . \u21a9","title":"DNS setup"},{"location":"prerequisite-dns/#references","text":"A good article covering all relevant topics: \"3 DNS Records Every Email Marketer Must Know\" Another great one, but Zimbra as an example platform: \"Best Practices on Email Protection: SPF, DKIM and DMARC\" An in-depth discussion of SPF, DKIM and DMARC: \"How to eliminate spam and protect your name with DMARC\" A thorough guide on understanding DMARC: \"Demystifying DMARC: A guide to preventing email spoofing\"","title":"References"},{"location":"prerequisite-dns/#reverse-dns-of-your-ip-address","text":"Make sure that the PTR record of your IP address matches the FQDN of your mailcow host: ${MAILCOW_HOSTNAME} 1 . This record is usually set at the provider you leased the IP address (server) from.","title":"Reverse DNS of your IP address"},{"location":"prerequisite-dns/#the-minimal-dns-configuration","text":"This example shows you a set of records for one domain managed by mailcow. Each domain that is added to mailcow needs at least this set of records to function correctly. # Name Type Value mail IN A 1.2.3.4 autodiscover IN CNAME mail.example.org. (your ${MAILCOW_HOSTNAME}) autoconfig IN CNAME mail.example.org. (your ${MAILCOW_HOSTNAME}) @ IN MX 10 mail.example.org. (your ${MAILCOW_HOSTNAME})","title":"The minimal DNS configuration"},{"location":"prerequisite-dns/#dkim-spf-and-dmarc","text":"In the example DNS zone file snippet below, a simple SPF TXT record is used to only allow THIS server (the MX) to send mail for your domain. Every other server is disallowed but able to (\" ~all \"). Please refer to SPF Project for further reading. # Name Type Value @ IN TXT \"v=spf1 mx a -all\" It is highly recommended to create a DKIM TXT record in your mailcow UI and set the corresponding TXT record in your DNS records. Please refer to OpenDKIM for further reading. # Name Type Value dkim._domainkey IN TXT \"v=DKIM1; k=rsa; t=s; s=email; p=...\" The last step in protecting yourself and others is the implementation of a DMARC TXT record, for example by using the DMARC Assistant ( check ). # Name Type Value _dmarc IN TXT \"v=DMARC1; p=reject; rua=mailto:mailauth-reports@example.org\"","title":"DKIM, SPF and DMARC"},{"location":"prerequisite-dns/#the-advanced-dns-configuration","text":"SRV records specify the server(s) for a specific protocol on your domain. If you want to explicitly announce a service as not provided, give \".\" as the target address (instead of \"mail.example.org.\"). Please refer to RFC 2782 . # Name Type Priority Weight Port Value _autodiscover._tcp IN SRV 0 1 443 mail.example.org. (your ${MAILCOW_HOSTNAME}) _caldavs._tcp IN SRV 0 1 443 mail.example.org. (your ${MAILCOW_HOSTNAME}) _caldavs._tcp IN TXT \"path=/SOGo/dav/\" _carddavs._tcp IN SRV 0 1 443 Mail.example.org. (your ${MAILCOW_HOSTNAME}) _carddavs._tcp IN TXT \"path=/SOGo/dav/\" _imap._tcp IN SRV 0 1 143 mail.example.org. (your ${MAILCOW_HOSTNAME}) _imaps._tcp IN SRV 0 1 993 mail.example.org. (your ${MAILCOW_HOSTNAME}) _pop3._tcp IN SRV 0 1 110 mail.example.org. (your ${MAILCOW_HOSTNAME}) _pop3s._tcp IN SRV 0 1 995 mail.example.org. (your ${MAILCOW_HOSTNAME}) _sieve._tcp IN SRV 0 1 4190 mail.example.org. (your ${MAILCOW_HOSTNAME}) _smtps._tcp IN SRV 0 1 465 mail.example.org. (your ${MAILCOW_HOSTNAME}) _submission._tcp IN SRV 0 1 587 mail.example.org. (your ${MAILCOW_HOSTNAME})","title":"The advanced DNS configuration"},{"location":"prerequisite-dns/#testing","text":"Here are some tools you can use to verify your DNS configuration: MX Toolbox (DNS, SMTP, RBL) port25.com (DKIM, SPF) Mail-tester (DKIM, DMARC, SPF) DMARC Analyzer (DMARC, SPF) MultiRBL.valli.org (DNSBL, RBL, FCrDNS)","title":"Testing"},{"location":"prerequisite-dns/#misc","text":"","title":"Misc"},{"location":"prerequisite-dns/#optional-dmarc-statistics","text":"If you are interested in statistics, you can additionally register with some of the many below DMARC statistic services, or self-host your own. NOTE: It is worth considering that if you request DMARC statistic reports to your mailcow server, if there are issues with that domain you may not get accurate results. You can consider using an alternative email domain for recieving DMARC reports. It is worth mentioning, that the following suggestions are not a comprehensive list of all services and tools avaialble, but only a small few of the many choices. Postmaster Tool parsedmarc (self-hosted) Fraudmarc Postmark Dmarcian NOTE: The services may provide you with a TXT record, which you would insert into your DNS records as the provider specifies. This record will give you details about spam-classified mails by your domain. However, please ensure to read the providers documentation from the service you choose, as this process may vary and not all providers may use a TXT record.","title":"Optional DMARC Statistics"},{"location":"prerequisite-dns/#email-test-for-spf-dkim-and-dmarc","text":"To test send an email to the email below and wait for a reply: check-auth@verifier.port25.com You will get a report back that looks like the following: ========================================================== Summary of Results ========================================================== SPF check: pass \"iprev\" check: pass DKIM check: pass DKIM check: pass SpamAssassin check: ham ========================================================== Details: ========================================================== .... The full report will contain more technical details this is just the first section, we found this to be quite usful for testing both outgoing mail and spam scores.","title":"Email Test for SPF, DKIM and DMARC:"},{"location":"prerequisite-dns/#fully-qualified-domain-name-fqdn","text":"A Fully Qualified Domain Name ( FQDN ) is the complete (absolute) domain name for a specific computer or host, on the Internet. The FQDN consists of at least three parts divided by a dot: the hostname (myhost), the domain name (mydomain) and the top level domain in short tld (com). In the example of mx.mailcow.email the hostname would be mx , the domain name mailcow and the tld email . \u21a9","title":"Fully Qualified Domain Name (FQDN)"},{"location":"prerequisite-system/","text":"Before you run mailcow: dockerized , there are a few requirements that you should check: Warning Do not try to install mailcow on a Synology/QNAP device (any NAS), OpenVZ, LXC or other container platforms. KVM, ESX, Hyper-V and other full virtualization platforms are supported. We do not recommend to use CentOS 8 anymore! Info mailcow: dockerized requires some ports to be open for incoming connections, so make sure that your firewall is not blocking these. Make sure that no other application is interfering with mailcow's configuration, such as another mail service A correct DNS setup is crucial to every good mailserver setup, so please make sure you got at least the basics covered before you begin! Make sure that your system has a correct date and time setup . This is crucial for various components like two factor TOTP authentication. Minimum System Resources \u00b6 OpenVZ, Virtuozzo and LXC are not supported . Please make sure that your system has at least the following resources: Resource mailcow: dockerized CPU 1 GHz RAM Minimum 6 GiB + 1 GiB swap (default config) Disk 20 GiB (without emails) System Type x86_64 We recommend using any distribution listed as supported by Docker CE (check https://docs.docker.com/install/ ). We test on CentOS 7, Debian 9/10 and Ubuntu 18.04/20.04. ClamAV and Solr can be greedy with RAM. You may disable them in mailcow.conf by settings SKIP_CLAMD=y and SKIP_SOLR=y . Info : We are aware that a pure MTA can run on 128 MiB RAM. mailcow is a full-grown and ready-to-use groupware with many extras making life easier. mailcow comes with a webserver, webmailer, ActiveSync (MS), antivirus, antispam, indexing (Solr), document scanner (Oletools), SQL (MariaDB), Cache (Redis), MDA, MTA, various web services etc. A single SOGo worker can acquire ~350 MiB RAM before it gets purged. The more ActiveSync connections you plan to use, the more RAM you will need. A default configuration spawns 20 workers. Usage examples \u00b6 A company with 15 phones (EAS enabled) and about 50 concurrent IMAP connections should plan 16 GiB RAM. 6 GiB RAM + 1 GiB swap are fine for most private installations while 8 GiB RAM are recommended for ~5 to 10 users. We can help to correctly plan your setup as part of our support. Firewall & Ports \u00b6 Please check if any of mailcow's standard ports are open and not in use by other applications: ss -tlpn | grep -E -w '25|80|110|143|443|465|587|993|995|4190|5222|5269|5443' # or: netstat -tulpn | grep -E -w '25|80|110|143|443|465|587|993|995|4190|5222|5269|5443' Warning There are several problems with running mailcow on a firewalld/ufw enabled system. You should disable it (if possible) and move your ruleset to the DOCKER-USER chain, which is not cleared by a Docker service restart, instead. See this (blog.donnex.net) or this (unrouted.io) guide for information about how to use iptables-persistent with the DOCKER-USER chain. As mailcow runs dockerized, INPUT rules have no effect on restricting access to mailcow. Use the FORWARD chain instead. If this command returns any results please remove or stop the application running on that port. You may also adjust mailcows ports via the mailcow.conf configuration file. Default Ports \u00b6 If you have a firewall in front of mailcow, please make sure that these ports are open for incoming connections: Service Protocol Port Container Variable Postfix SMTP TCP 25 postfix-mailcow ${SMTP_PORT} Postfix SMTPS TCP 465 postfix-mailcow ${SMTPS_PORT} Postfix Submission TCP 587 postfix-mailcow ${SUBMISSION_PORT} Dovecot IMAP TCP 143 dovecot-mailcow ${IMAP_PORT} Dovecot IMAPS TCP 993 dovecot-mailcow ${IMAPS_PORT} Dovecot POP3 TCP 110 dovecot-mailcow ${POP_PORT} Dovecot POP3S TCP 995 dovecot-mailcow ${POPS_PORT} Dovecot ManageSieve TCP 4190 dovecot-mailcow ${SIEVE_PORT} HTTP(S) TCP 80/443 nginx-mailcow ${HTTP_PORT} / ${HTTPS_PORT} XMPP (c2s) TCP 5222 ejabberd-mailcow ${XMPP_C2S_PORT} XMPP (s2s) TCP 5269 ejabberd-mailcow ${XMPP_C2S_PORT} XMPP (upload) TCP 5443 ejabberd-mailcow ${XMPP_HTTPS_PORT} To bind a service to an IP address, you can prepend the IP like this: SMTP_PORT=1.2.3.4:25 Important : You cannot use IP:PORT bindings in HTTP_PORT and HTTPS_PORT. Please use HTTP_PORT=1234 and HTTP_BIND=1.2.3.4 instead. Important for Hetzner firewalls \u00b6 Quoting https://github.com/chermsen via https://github.com/mailcow/mailcow-dockerized/issues/497#issuecomment-469847380 (THANK YOU!): For all who are struggling with the Hetzner firewall: Port 53 unimportant for the firewall configuration in this case. According to the documentation unbound uses the port range 1024-65535 for outgoing requests. Since the Hetzner Robot Firewall is a static firewall (each incoming packet is checked isolated) - the following rules must be applied: For TCP SRC-IP: --- DST IP: --- SRC Port: --- DST Port: 1024-65535 Protocol: tcp TCP flags: ack Action: Accept For UDP SRC-IP: --- DST IP: --- SRC Port: --- DST Port: 1024-65535 Protocol: udp Action: Accept If you want to apply a more restrictive port range you have to change the config of unbound first (after installation): {mailcow-dockerized}/data/conf/unbound/unbound.conf: outgoing-port-avoid: 0-32767 Now the firewall rules can be adjusted as follows: [...] DST Port: 32768-65535 [...] Date and Time \u00b6 To ensure that you have the correct date and time setup on your system, please check the output of timedatectl status : $ timedatectl status Local time: Sat 2017-05-06 02:12:33 CEST Universal time: Sat 2017-05-06 00:12:33 UTC RTC time: Sat 2017-05-06 00:12:32 Time zone: Europe/Berlin (CEST, +0200) NTP enabled: yes NTP synchronized: yes RTC in local TZ: no DST active: yes Last DST change: DST began at Sun 2017-03-26 01:59:59 CET Sun 2017-03-26 03:00:00 CEST Next DST change: DST ends (the clock jumps one hour backwards) at Sun 2017-10-29 02:59:59 CEST Sun 2017-10-29 02:00:00 CET The lines NTP enabled: yes and NTP synchronized: yes indicate whether you have NTP enabled and if it's synchronized. To enable NTP you need to run the command timedatectl set-ntp true . You also need to edit your /etc/systemd/timesyncd.conf : # vim /etc/systemd/timesyncd.conf [Time] Servers=0.pool.ntp.org 1.pool.ntp.org 2.pool.ntp.org 3.pool.ntp.org Hetzner Cloud (and probably others) \u00b6 Check /etc/network/interfaces.d/50-cloud-init.cfg and change the IPv6 interface from eth0:0 to eth0: # Wrong: auto eth0:0 iface eth0:0 inet6 static # Right: auto eth0 iface eth0 inet6 static Reboot or restart the interface. You may want to disable cloud-init network changes. MTU \u00b6 Especially relevant for OpenStack users: Check your MTU and set it accordingly in docker-compose.yml. See 4.1 in our installation docs .","title":"Prepare your system"},{"location":"prerequisite-system/#minimum-system-resources","text":"OpenVZ, Virtuozzo and LXC are not supported . Please make sure that your system has at least the following resources: Resource mailcow: dockerized CPU 1 GHz RAM Minimum 6 GiB + 1 GiB swap (default config) Disk 20 GiB (without emails) System Type x86_64 We recommend using any distribution listed as supported by Docker CE (check https://docs.docker.com/install/ ). We test on CentOS 7, Debian 9/10 and Ubuntu 18.04/20.04. ClamAV and Solr can be greedy with RAM. You may disable them in mailcow.conf by settings SKIP_CLAMD=y and SKIP_SOLR=y . Info : We are aware that a pure MTA can run on 128 MiB RAM. mailcow is a full-grown and ready-to-use groupware with many extras making life easier. mailcow comes with a webserver, webmailer, ActiveSync (MS), antivirus, antispam, indexing (Solr), document scanner (Oletools), SQL (MariaDB), Cache (Redis), MDA, MTA, various web services etc. A single SOGo worker can acquire ~350 MiB RAM before it gets purged. The more ActiveSync connections you plan to use, the more RAM you will need. A default configuration spawns 20 workers.","title":"Minimum System Resources"},{"location":"prerequisite-system/#usage-examples","text":"A company with 15 phones (EAS enabled) and about 50 concurrent IMAP connections should plan 16 GiB RAM. 6 GiB RAM + 1 GiB swap are fine for most private installations while 8 GiB RAM are recommended for ~5 to 10 users. We can help to correctly plan your setup as part of our support.","title":"Usage examples"},{"location":"prerequisite-system/#firewall-ports","text":"Please check if any of mailcow's standard ports are open and not in use by other applications: ss -tlpn | grep -E -w '25|80|110|143|443|465|587|993|995|4190|5222|5269|5443' # or: netstat -tulpn | grep -E -w '25|80|110|143|443|465|587|993|995|4190|5222|5269|5443' Warning There are several problems with running mailcow on a firewalld/ufw enabled system. You should disable it (if possible) and move your ruleset to the DOCKER-USER chain, which is not cleared by a Docker service restart, instead. See this (blog.donnex.net) or this (unrouted.io) guide for information about how to use iptables-persistent with the DOCKER-USER chain. As mailcow runs dockerized, INPUT rules have no effect on restricting access to mailcow. Use the FORWARD chain instead. If this command returns any results please remove or stop the application running on that port. You may also adjust mailcows ports via the mailcow.conf configuration file.","title":"Firewall & Ports"},{"location":"prerequisite-system/#default-ports","text":"If you have a firewall in front of mailcow, please make sure that these ports are open for incoming connections: Service Protocol Port Container Variable Postfix SMTP TCP 25 postfix-mailcow ${SMTP_PORT} Postfix SMTPS TCP 465 postfix-mailcow ${SMTPS_PORT} Postfix Submission TCP 587 postfix-mailcow ${SUBMISSION_PORT} Dovecot IMAP TCP 143 dovecot-mailcow ${IMAP_PORT} Dovecot IMAPS TCP 993 dovecot-mailcow ${IMAPS_PORT} Dovecot POP3 TCP 110 dovecot-mailcow ${POP_PORT} Dovecot POP3S TCP 995 dovecot-mailcow ${POPS_PORT} Dovecot ManageSieve TCP 4190 dovecot-mailcow ${SIEVE_PORT} HTTP(S) TCP 80/443 nginx-mailcow ${HTTP_PORT} / ${HTTPS_PORT} XMPP (c2s) TCP 5222 ejabberd-mailcow ${XMPP_C2S_PORT} XMPP (s2s) TCP 5269 ejabberd-mailcow ${XMPP_C2S_PORT} XMPP (upload) TCP 5443 ejabberd-mailcow ${XMPP_HTTPS_PORT} To bind a service to an IP address, you can prepend the IP like this: SMTP_PORT=1.2.3.4:25 Important : You cannot use IP:PORT bindings in HTTP_PORT and HTTPS_PORT. Please use HTTP_PORT=1234 and HTTP_BIND=1.2.3.4 instead.","title":"Default Ports"},{"location":"prerequisite-system/#important-for-hetzner-firewalls","text":"Quoting https://github.com/chermsen via https://github.com/mailcow/mailcow-dockerized/issues/497#issuecomment-469847380 (THANK YOU!): For all who are struggling with the Hetzner firewall: Port 53 unimportant for the firewall configuration in this case. According to the documentation unbound uses the port range 1024-65535 for outgoing requests. Since the Hetzner Robot Firewall is a static firewall (each incoming packet is checked isolated) - the following rules must be applied: For TCP SRC-IP: --- DST IP: --- SRC Port: --- DST Port: 1024-65535 Protocol: tcp TCP flags: ack Action: Accept For UDP SRC-IP: --- DST IP: --- SRC Port: --- DST Port: 1024-65535 Protocol: udp Action: Accept If you want to apply a more restrictive port range you have to change the config of unbound first (after installation): {mailcow-dockerized}/data/conf/unbound/unbound.conf: outgoing-port-avoid: 0-32767 Now the firewall rules can be adjusted as follows: [...] DST Port: 32768-65535 [...]","title":"Important for Hetzner firewalls"},{"location":"prerequisite-system/#date-and-time","text":"To ensure that you have the correct date and time setup on your system, please check the output of timedatectl status : $ timedatectl status Local time: Sat 2017-05-06 02:12:33 CEST Universal time: Sat 2017-05-06 00:12:33 UTC RTC time: Sat 2017-05-06 00:12:32 Time zone: Europe/Berlin (CEST, +0200) NTP enabled: yes NTP synchronized: yes RTC in local TZ: no DST active: yes Last DST change: DST began at Sun 2017-03-26 01:59:59 CET Sun 2017-03-26 03:00:00 CEST Next DST change: DST ends (the clock jumps one hour backwards) at Sun 2017-10-29 02:59:59 CEST Sun 2017-10-29 02:00:00 CET The lines NTP enabled: yes and NTP synchronized: yes indicate whether you have NTP enabled and if it's synchronized. To enable NTP you need to run the command timedatectl set-ntp true . You also need to edit your /etc/systemd/timesyncd.conf : # vim /etc/systemd/timesyncd.conf [Time] Servers=0.pool.ntp.org 1.pool.ntp.org 2.pool.ntp.org 3.pool.ntp.org","title":"Date and Time"},{"location":"prerequisite-system/#hetzner-cloud-and-probably-others","text":"Check /etc/network/interfaces.d/50-cloud-init.cfg and change the IPv6 interface from eth0:0 to eth0: # Wrong: auto eth0:0 iface eth0:0 inet6 static # Right: auto eth0 iface eth0 inet6 static Reboot or restart the interface. You may want to disable cloud-init network changes.","title":"Hetzner Cloud (and probably others)"},{"location":"prerequisite-system/#mtu","text":"Especially relevant for OpenStack users: Check your MTU and set it accordingly in docker-compose.yml. See 4.1 in our installation docs .","title":"MTU"},{"location":"restrictions_ip_accss/","text":"WIP Protocol restrictions and IP access \u00b6 Denied access will be shown to the user as failed login attempts. Protocol restrictions in Dovecot \u00b6 Protocol restrictions work by filtering the passdb query for IMAP and POP3 as well as reading the JSON value for %s_access where %s reflects the protocol seen by Dovecot. In the future we may use virtual colums in SQL to add an index on these values. Protocol restrictions in Postfix \u00b6 Filtering SMTP protocol access works by using a check_sasl_map in the smtpd_recipient_restrictions.","title":"Restrictions ip accss"},{"location":"restrictions_ip_accss/#protocol-restrictions-and-ip-access","text":"Denied access will be shown to the user as failed login attempts.","title":"Protocol restrictions and IP access"},{"location":"restrictions_ip_accss/#protocol-restrictions-in-dovecot","text":"Protocol restrictions work by filtering the passdb query for IMAP and POP3 as well as reading the JSON value for %s_access where %s reflects the protocol seen by Dovecot. In the future we may use virtual colums in SQL to add an index on these values.","title":"Protocol restrictions in Dovecot"},{"location":"restrictions_ip_accss/#protocol-restrictions-in-postfix","text":"Filtering SMTP protocol access works by using a check_sasl_map in the smtpd_recipient_restrictions.","title":"Protocol restrictions in Postfix"},{"location":"third_party-exchange_onprem/","text":"Using Microsoft Exchange in a hybrid setup is possible with mailcow. With this setup you can add mailboxes on your mailcow and still use Exchange Online Protection . All mailboxes setup in Exchange will receive their mails as usual , while with the hybrid approach additional Mailboxes can be setup in mailcow without any further configuration. This setup becomes very handy if you have enabled the Office 365 security defaults and third party applications can no longer login into your mailboxes by any of the supported methods . Requirements \u00b6 The mx Record of your domain needs to point at the Exchange mail service. Log into your Admin center and look out for the dns settings of your domain to find your personalized gateway domain. It should look like this contoso-com.mail.protection.outlook.com . Contact your domain registrant to get further information on how to change mx record. The domain you want to have additional mailboxes for must be setup as internal relay domain in Exchange. Log in to your Exchange Admin Center Select the mail flow pane and click on accepted domains Select the domain and switch it from authorative to internal relay Set up the mailcow \u00b6 Your mailcow needs to relay all mails to your personalized Exchange Host. It is the same host address we already looked up for the mx Record. Add the domain to your mailcow Add your personalized Exchange Host address as relayhost Add your personalized Exchange Host address as forwarding host to unconditionally accepted all relayed mails from Exchange. (Admin > Configuration & Details > Configuration Dropdown > Forwarding Hosts) Go to the domain settings and select the newly added host on the Sender-dependent transports dropdown. Enable relaying by ticking the Relay this domain , Relay all recipients and the Relay non-existing mailboxes only. checkboxes Info From now on your mailcow will accept all mails relayed from Exchange. The inbound filtering and so the neural learning of your cow will no longer work . Because all mails are routed through Exchange the filtering process is handled there . Set up Connectors in Exchange \u00b6 All mail traffic now goes through Exchange. At this point the Exchange Online Protection already filters all incoming and outgoing mails. Now we need to set up two connectors to relay incoming mails from our Exchange Service to the mailcow and another one to allow mails relayed from the mailcow to our exchange service. You can follow the official guide from Microsoft . Warning For the connector that handles mails from your mailcow to Exchange Microsoft offers two ways of authenticating it. The recommended way is to use a tls certificate configured with a subject name that matches an accepted domain in Exchange. Otherwise you need to choose authentication with the static ip address of your mailcow. Validating \u00b6 The easiest way to validate the hybrid setup is by sending a mail from the internet to a mailbox that only exists on the mailcow and vice versa. Common Issues \u00b6 The connector validation from Exchange to your mailcow failed with 550 5.1.10 RESOLVER.ADR.RecipientNotFound; Recipient test@contoso.com not found by SMTP address lookup Possible Solution: Your domain is not set up as internal relay . Exchange therefore cannot find the recipient Mails sent from the mailcow to a mailbox in the internet cannot be sent. Non Delivery Report with error 550 5.7.64 TenantAttribution; Relay Access Denied Possible Solution: The authentication method failed. Make sure the certificate subject matches an accepted domain in Exchange. Try authenticating by static ip instead. Microsoft Guide for the connector setup and additional requirements: https://docs.microsoft.com/exchange/mail-flow-best-practices/use-connectors-to-configure-mail-flow/set-up-connectors-to-route-mail#prerequisites-for-your-on-premises-email-environment","title":"Exchange Hybrid Setup"},{"location":"third_party-exchange_onprem/#requirements","text":"The mx Record of your domain needs to point at the Exchange mail service. Log into your Admin center and look out for the dns settings of your domain to find your personalized gateway domain. It should look like this contoso-com.mail.protection.outlook.com . Contact your domain registrant to get further information on how to change mx record. The domain you want to have additional mailboxes for must be setup as internal relay domain in Exchange. Log in to your Exchange Admin Center Select the mail flow pane and click on accepted domains Select the domain and switch it from authorative to internal relay","title":"Requirements"},{"location":"third_party-exchange_onprem/#set-up-the-mailcow","text":"Your mailcow needs to relay all mails to your personalized Exchange Host. It is the same host address we already looked up for the mx Record. Add the domain to your mailcow Add your personalized Exchange Host address as relayhost Add your personalized Exchange Host address as forwarding host to unconditionally accepted all relayed mails from Exchange. (Admin > Configuration & Details > Configuration Dropdown > Forwarding Hosts) Go to the domain settings and select the newly added host on the Sender-dependent transports dropdown. Enable relaying by ticking the Relay this domain , Relay all recipients and the Relay non-existing mailboxes only. checkboxes Info From now on your mailcow will accept all mails relayed from Exchange. The inbound filtering and so the neural learning of your cow will no longer work . Because all mails are routed through Exchange the filtering process is handled there .","title":"Set up the mailcow"},{"location":"third_party-exchange_onprem/#set-up-connectors-in-exchange","text":"All mail traffic now goes through Exchange. At this point the Exchange Online Protection already filters all incoming and outgoing mails. Now we need to set up two connectors to relay incoming mails from our Exchange Service to the mailcow and another one to allow mails relayed from the mailcow to our exchange service. You can follow the official guide from Microsoft . Warning For the connector that handles mails from your mailcow to Exchange Microsoft offers two ways of authenticating it. The recommended way is to use a tls certificate configured with a subject name that matches an accepted domain in Exchange. Otherwise you need to choose authentication with the static ip address of your mailcow.","title":"Set up Connectors in Exchange"},{"location":"third_party-exchange_onprem/#validating","text":"The easiest way to validate the hybrid setup is by sending a mail from the internet to a mailbox that only exists on the mailcow and vice versa.","title":"Validating"},{"location":"third_party-exchange_onprem/#common-issues","text":"The connector validation from Exchange to your mailcow failed with 550 5.1.10 RESOLVER.ADR.RecipientNotFound; Recipient test@contoso.com not found by SMTP address lookup Possible Solution: Your domain is not set up as internal relay . Exchange therefore cannot find the recipient Mails sent from the mailcow to a mailbox in the internet cannot be sent. Non Delivery Report with error 550 5.7.64 TenantAttribution; Relay Access Denied Possible Solution: The authentication method failed. Make sure the certificate subject matches an accepted domain in Exchange. Try authenticating by static ip instead. Microsoft Guide for the connector setup and additional requirements: https://docs.microsoft.com/exchange/mail-flow-best-practices/use-connectors-to-configure-mail-flow/set-up-connectors-to-route-mail#prerequisites-for-your-on-premises-email-environment","title":"Common Issues"},{"location":"third_party-gitea/","text":"With Gitea' ability to authenticate over SMTP it is trivial to integrate it with mailcow. Few changes are needed: 1. Open docker-compose.override.yml and add gitea: version: '2.1' services: gitea-mailcow: image: gitea/gitea:1 volumes: - ./data/gitea:/data networks: mailcow-network: aliases: - gitea ports: - \"${GITEA_SSH_PORT:-127.0.0.1:4000}:22\" 2. Create data/conf/nginx/site.gitea.custom , add: location /gitea/ { proxy_pass http://gitea:3000/; } 3. Open mailcow.conf and define the binding you want gitea to use for SSH. Example: GITEA_SSH_PORT=127.0.0.1:4000 5. Run docker-compose up -d to bring up the gitea container and run docker-compose restart nginx-mailcow afterwards. 6. If you forced mailcow to https, execute step 9 and restart gitea with docker-compose restart gitea-mailcow . Go head with step 7 (Remember to use https instead of http, https://mx.example.org/gitea/ 7. Open http://${MAILCOW_HOSTNAME}/gitea/ , for example http://mx.example.org/gitea/ . For database details set mysql as database host. Use the value of DBNAME found in mailcow.conf as database name, DBUSER as database user and DBPASS as database password. 8. Once the installation is complete, login as admin and set \"settings\" -> \"authorization\" -> \"enable SMTP\". SMTP Host should be postfix with port 587 , set Skip TLS Verify as we are using an unlisted SAN (\"postfix\" is most likely not part of your certificate). 9. Create data/gitea/gitea/conf/app.ini and set following values. You can consult gitea cheat sheet for their meaning and other possible values. [server] SSH_LISTEN_PORT = 22 # For GITEA_SSH_PORT=127.0.0.1:4000 in mailcow.conf, set: SSH_DOMAIN = 127.0.0.1 SSH_PORT = 4000 # For MAILCOW_HOSTNAME=mx.example.org in mailcow.conf (and default ports for HTTPS), set: ROOT_URL = https://mx.example.org/gitea/ 10. Restart gitea with docker-compose restart gitea-mailcow . Your users should be able to login with mailcow managed accounts.","title":"Gitea"},{"location":"third_party-gogs/","text":"With Gogs' ability to authenticate over SMTP it is trivial to integrate it with mailcow. Few changes are needed: 1. Open docker-compose.override.yml and add Gogs: version: '2.1' services: gogs-mailcow: image: gogs/gogs volumes: - ./data/gogs:/data networks: mailcow-network: aliases: - gogs ports: - \"${GOGS_SSH_PORT:-127.0.0.1:4000}:22\" 2. Create data/conf/nginx/site.gogs.custom , add: location /gogs/ { proxy_pass http://gogs:3000/; } 3. Open mailcow.conf and define the binding you want Gogs to use for SSH. Example: GOGS_SSH_PORT=127.0.0.1:4000 5. Run docker-compose up -d to bring up the Gogs container and run docker-compose restart nginx-mailcow afterwards. 6. Open http://${MAILCOW_HOSTNAME}/gogs/ , for example http://mx.example.org/gogs/ . For database details set mysql as database host. Use the value of DBNAME found in mailcow.conf as database name, DBUSER as database user and DBPASS as database password. 7. Once the installation is complete, login as admin and set \"settings\" -> \"authorization\" -> \"enable SMTP\". SMTP Host should be postfix with port 587 , set Skip TLS Verify as we are using an unlisted SAN (\"postfix\" is most likely not part of your certificate). 8. Create data/gogs/gogs/conf/app.ini and set following values. You can consult Gogs cheat sheet for their meaning and other possible values. [server] SSH_LISTEN_PORT = 22 # For GOGS_SSH_PORT=127.0.0.1:4000 in mailcow.conf, set: SSH_DOMAIN = 127.0.0.1 SSH_PORT = 4000 # For MAILCOW_HOSTNAME=mx.example.org in mailcow.conf (and default ports for HTTPS), set: ROOT_URL = https://mx.example.org/gogs/ 9. Restart Gogs with docker-compose restart gogs-mailcow . Your users should be able to login with mailcow managed accounts.","title":"Gogs"},{"location":"third_party-mailpiler_integration/","text":"This is a simple integration of mailcow aliases and the mailbox name into mailpiler when using IMAP authentication. Disclaimer : This is not officially maintained nor supported by the mailcow project nor its contributors. No warranty or support is being provided, however you're free to open issues on GitHub for filing a bug or provide further ideas. GitHub repo can be found here . Info Support for domain wildcards were implemented in Piler 1.3.10 which was released on 03.01.2021. Prior versions basically do work, but after logging in you won't see emails sent from or to the domain alias. (e.g. when @example.com is an alias for admin@example.com ) The problem to solve \u00b6 mailpiler offers the authentication based on IMAP, for example: $config['ENABLE_IMAP_AUTH'] = 1; $config['IMAP_HOST'] = 'mail.example.com'; $config['IMAP_PORT'] = 993; $config['IMAP_SSL'] = true; So when you log in using patrik@example.com , you will only see delivered emails sent from or to this specific email address. When additional aliases are defined in mailcow, like team@example.com , you won't see emails sent to or from this email address even the fact you're a recipient of mails sent to this alias address. By hooking into the authentication process of mailpiler, we are able to get required data via the mailcow API during login. This fires API requests to the mailcow API (requiring read-only API access) to read out the aliases your email address participates and also the \"Name\" of the mailbox specified to display it on the top-right of mailpiler after login. Permitted email addresses can be seen in the mailpiler settings top-right after logging in. Info This is only pulled once during the authentication process. The authorized aliases and the realname are valid for the whole duration of the user session as mailpiler sets them in the session data. If user is removed from specific alias, this will only take effect after next login. The solution \u00b6 Note: File paths might vary depending on your setup. Requirements \u00b6 A working mailcow instance A working mailpiler instance ( You can find an installation guide here , check supported versions here ) An mailcow API key (read-only works just fine): Configuration & Details - Access - Read-Only Access . Don't forget to allow API access from your mailpiler IP. Warning As mailpiler authenticates against mailcow, our IMAP server, failed logins of users or bots might trigger a block for your mailpiler instance. Therefore you might want to consider whitelisting the IP address of the mailpiler instance within mailcow: Configuration & Details - Configuration - Fail2ban parameters - Whitelisted networks/hosts . Setup \u00b6 Set the custom query function of mailpiler and append this to /usr/local/etc/piler/config-site.php : $config['MAILCOW_API_KEY'] = 'YOUR_READONLY_API_KEY'; $config['MAILCOW_SET_REALNAME'] = true; // when not specified, then default is false $config['CUSTOM_EMAIL_QUERY_FUNCTION'] = 'query_mailcow_for_email_access'; include('auth-mailcow.php'); You can also change the mailcow hostname, if required: $config['MAILCOW_HOST'] = 'mail.domain.tld'; // defaults to $config['IMAP_HOST'] Download the PHP file with the functions from the GitHub repo : curl -o /usr/local/etc/piler/auth-mailcow.php https://raw.githubusercontent.com/patschi/mailpiler-mailcow-integration/master/auth-mailcow.php Done! Make sure to re-login with your IMAP credentials for changes to take effect. If it doesn't work, most likely something's wrong with the API query itself. Consider debugging by sending manual API requests to the API. (Tip: Open https://mail.domain.tld/api on your instance)","title":"Mailpiler Integration"},{"location":"third_party-mailpiler_integration/#the-problem-to-solve","text":"mailpiler offers the authentication based on IMAP, for example: $config['ENABLE_IMAP_AUTH'] = 1; $config['IMAP_HOST'] = 'mail.example.com'; $config['IMAP_PORT'] = 993; $config['IMAP_SSL'] = true; So when you log in using patrik@example.com , you will only see delivered emails sent from or to this specific email address. When additional aliases are defined in mailcow, like team@example.com , you won't see emails sent to or from this email address even the fact you're a recipient of mails sent to this alias address. By hooking into the authentication process of mailpiler, we are able to get required data via the mailcow API during login. This fires API requests to the mailcow API (requiring read-only API access) to read out the aliases your email address participates and also the \"Name\" of the mailbox specified to display it on the top-right of mailpiler after login. Permitted email addresses can be seen in the mailpiler settings top-right after logging in. Info This is only pulled once during the authentication process. The authorized aliases and the realname are valid for the whole duration of the user session as mailpiler sets them in the session data. If user is removed from specific alias, this will only take effect after next login.","title":"The problem to solve"},{"location":"third_party-mailpiler_integration/#the-solution","text":"Note: File paths might vary depending on your setup.","title":"The solution"},{"location":"third_party-mailpiler_integration/#requirements","text":"A working mailcow instance A working mailpiler instance ( You can find an installation guide here , check supported versions here ) An mailcow API key (read-only works just fine): Configuration & Details - Access - Read-Only Access . Don't forget to allow API access from your mailpiler IP. Warning As mailpiler authenticates against mailcow, our IMAP server, failed logins of users or bots might trigger a block for your mailpiler instance. Therefore you might want to consider whitelisting the IP address of the mailpiler instance within mailcow: Configuration & Details - Configuration - Fail2ban parameters - Whitelisted networks/hosts .","title":"Requirements"},{"location":"third_party-mailpiler_integration/#setup","text":"Set the custom query function of mailpiler and append this to /usr/local/etc/piler/config-site.php : $config['MAILCOW_API_KEY'] = 'YOUR_READONLY_API_KEY'; $config['MAILCOW_SET_REALNAME'] = true; // when not specified, then default is false $config['CUSTOM_EMAIL_QUERY_FUNCTION'] = 'query_mailcow_for_email_access'; include('auth-mailcow.php'); You can also change the mailcow hostname, if required: $config['MAILCOW_HOST'] = 'mail.domain.tld'; // defaults to $config['IMAP_HOST'] Download the PHP file with the functions from the GitHub repo : curl -o /usr/local/etc/piler/auth-mailcow.php https://raw.githubusercontent.com/patschi/mailpiler-mailcow-integration/master/auth-mailcow.php Done! Make sure to re-login with your IMAP credentials for changes to take effect. If it doesn't work, most likely something's wrong with the API query itself. Consider debugging by sending manual API requests to the API. (Tip: Open https://mail.domain.tld/api on your instance)","title":"Setup"},{"location":"third_party-nextcloud/","text":"Manage Nextcloud using the helper script \u00b6 Nextcloud can be set up (parameter -i ) and removed (parameter -p ) with the helper script included with mailcow. In order to install Nextcloud simply navigate to your mailcow-dockerized root folder and run the helper script as follows: ./helper-scripts/nextcloud.sh -i In case you have forgotten the password (e.g. for admin) and can't request a new one via the password reset link on the login screen calling the helper script with -r as parameter allows you to set a new password. Only use this option if your Nextcloud isn't configured to use mailcow for authentication as described in the next section. Configure Nextcloud to use mailcow for authentication \u00b6 The following describes how set up authentication via mailcow using the OAuth2 protocol. We will only assume that you have already set up Nextcloud at cloud.example.com and that your mailcow is running at mail.example.com . It does not matter if your Nextcloud is running on a different server, you can still use mailcow for authentication. 1. Log into mailcow as administrator. 2. Scroll down to OAuth2 Apps and click the Add button. Specify the redirect URI as https://cloud.example.com/index.php/apps/sociallogin/custom_oauth2/Mailcow and click Add . Save the client ID and secret for later. Info Some installations, including those setup using the helper script of mailcow, need to remove index.php/ from the URL to get a successful redirect: https://cloud.example.com/apps/sociallogin/custom_oauth2/Mailcow 3. Log into Nextcloud as administrator. 4. Click the button in the top right corner and select Apps . Click the search button in the toolbar, search for the Social Login plugin and click Download and enable next to it. 5. Click the button in the top right corner and select Settings . Scroll down to the Administration section on the left and click Social login . 6. Uncheck the following items: \"Disable auto create new users\" \"Allow users to connect social logins with their accounts\" \"Do not prune not available user groups on login\" \"Automatically create groups if they do not exists\" \"Restrict login for users without mapped groups\" 7. Check the following items: \"Prevent creating an account if the email address exists in another account\" \"Update user profile every login\" \"Disable notify admins about new users\" Click the Save button. 8. Scroll down to Custom OAuth2 and click the + button. 9. Configure the parameters as follows: Internal name: Mailcow Title: Mailcow API Base URL: https://mail.example.com Authorize URL: https://mail.example.com/oauth/authorize Token URL: https://mail.example.com/oauth/token Profile URL: https://mail.example.com/oauth/profile Logout URL: (leave blank) Client ID: (what you obtained in step 1) Client Secret: (what you obtained in step 1) Scope: profile Click the Save button at the very bottom of the page. If you have previously used Nextcloud with mailcow authentication via user_external/IMAP, you need to perform some additional steps to link your existing user accounts with OAuth2. 1. Click the button in the top right corner and select Apps . Scroll down to the External user authentication app and click Remove next to it. 2. Run the following queries in your Nextcloud database (if you set up Nextcloud using mailcow's script, you can run source mailcow.conf && docker-compose exec mysql-mailcow mysql -u$DBUSER -p$DBPASS $DBNAME ): INSERT INTO nc_users (uid, uid_lower) SELECT DISTINCT uid, LOWER(uid) FROM nc_users_external; INSERT INTO nc_sociallogin_connect (uid, identifier) SELECT DISTINCT uid, CONCAT(\"Mailcow-\", uid) FROM nc_users_external; If you have previously used Nextcloud without mailcow authentication, but with the same usernames as mailcow, you can also link your existing user accounts with OAuth2. 1. Run the following queries in your Nextcloud database (if you set up Nextcloud using mailcow's script, you can run source mailcow.conf && docker-compose exec mysql-mailcow mysql -u$DBUSER -p$DBPASS $DBNAME ): INSERT INTO nc_sociallogin_connect (uid, identifier) SELECT DISTINCT uid, CONCAT(\"Mailcow-\", uid) FROM nc_users; Update \u00b6 The Nextcloud instance can be updated easily with the web update mechanism. In the case of larger updates, there may be further changes to be made after the update. After the Nextcloud instance has been checked, problems are shown. This can be e.g. missing indices in the DB or similar. It shows which commands have to be executed, these have to be placed in the php-fpm-mailcow container. As an an example run the following command to add the missing indices. docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c \"php /web/nextcloud/occ db:add-missing-indices\" Debugging & Troubleshooting \u00b6 It may happen that you cannot reach the Nextcloud instance from your network. This may be due to the fact that the entry of your subnet in the array 'trusted_proxies' is missing. You can make changes in the Nextcloud config.php in data/web/nextcloud/config/* . 'trusted_proxies' => array ( 0 => 'fd4d:6169:6c63:6f77::/64', 1 => '172.22.1.0/24', 2 => 'NewSubnet/24', ), After the changes have been made, the nginx container must be restarted. docker-compose restart nginx-mailcow","title":"Nextcloud"},{"location":"third_party-nextcloud/#manage-nextcloud-using-the-helper-script","text":"Nextcloud can be set up (parameter -i ) and removed (parameter -p ) with the helper script included with mailcow. In order to install Nextcloud simply navigate to your mailcow-dockerized root folder and run the helper script as follows: ./helper-scripts/nextcloud.sh -i In case you have forgotten the password (e.g. for admin) and can't request a new one via the password reset link on the login screen calling the helper script with -r as parameter allows you to set a new password. Only use this option if your Nextcloud isn't configured to use mailcow for authentication as described in the next section.","title":"Manage Nextcloud using the helper script"},{"location":"third_party-nextcloud/#configure-nextcloud-to-use-mailcow-for-authentication","text":"The following describes how set up authentication via mailcow using the OAuth2 protocol. We will only assume that you have already set up Nextcloud at cloud.example.com and that your mailcow is running at mail.example.com . It does not matter if your Nextcloud is running on a different server, you can still use mailcow for authentication. 1. Log into mailcow as administrator. 2. Scroll down to OAuth2 Apps and click the Add button. Specify the redirect URI as https://cloud.example.com/index.php/apps/sociallogin/custom_oauth2/Mailcow and click Add . Save the client ID and secret for later. Info Some installations, including those setup using the helper script of mailcow, need to remove index.php/ from the URL to get a successful redirect: https://cloud.example.com/apps/sociallogin/custom_oauth2/Mailcow 3. Log into Nextcloud as administrator. 4. Click the button in the top right corner and select Apps . Click the search button in the toolbar, search for the Social Login plugin and click Download and enable next to it. 5. Click the button in the top right corner and select Settings . Scroll down to the Administration section on the left and click Social login . 6. Uncheck the following items: \"Disable auto create new users\" \"Allow users to connect social logins with their accounts\" \"Do not prune not available user groups on login\" \"Automatically create groups if they do not exists\" \"Restrict login for users without mapped groups\" 7. Check the following items: \"Prevent creating an account if the email address exists in another account\" \"Update user profile every login\" \"Disable notify admins about new users\" Click the Save button. 8. Scroll down to Custom OAuth2 and click the + button. 9. Configure the parameters as follows: Internal name: Mailcow Title: Mailcow API Base URL: https://mail.example.com Authorize URL: https://mail.example.com/oauth/authorize Token URL: https://mail.example.com/oauth/token Profile URL: https://mail.example.com/oauth/profile Logout URL: (leave blank) Client ID: (what you obtained in step 1) Client Secret: (what you obtained in step 1) Scope: profile Click the Save button at the very bottom of the page. If you have previously used Nextcloud with mailcow authentication via user_external/IMAP, you need to perform some additional steps to link your existing user accounts with OAuth2. 1. Click the button in the top right corner and select Apps . Scroll down to the External user authentication app and click Remove next to it. 2. Run the following queries in your Nextcloud database (if you set up Nextcloud using mailcow's script, you can run source mailcow.conf && docker-compose exec mysql-mailcow mysql -u$DBUSER -p$DBPASS $DBNAME ): INSERT INTO nc_users (uid, uid_lower) SELECT DISTINCT uid, LOWER(uid) FROM nc_users_external; INSERT INTO nc_sociallogin_connect (uid, identifier) SELECT DISTINCT uid, CONCAT(\"Mailcow-\", uid) FROM nc_users_external; If you have previously used Nextcloud without mailcow authentication, but with the same usernames as mailcow, you can also link your existing user accounts with OAuth2. 1. Run the following queries in your Nextcloud database (if you set up Nextcloud using mailcow's script, you can run source mailcow.conf && docker-compose exec mysql-mailcow mysql -u$DBUSER -p$DBPASS $DBNAME ): INSERT INTO nc_sociallogin_connect (uid, identifier) SELECT DISTINCT uid, CONCAT(\"Mailcow-\", uid) FROM nc_users;","title":"Configure Nextcloud to use mailcow for authentication"},{"location":"third_party-nextcloud/#update","text":"The Nextcloud instance can be updated easily with the web update mechanism. In the case of larger updates, there may be further changes to be made after the update. After the Nextcloud instance has been checked, problems are shown. This can be e.g. missing indices in the DB or similar. It shows which commands have to be executed, these have to be placed in the php-fpm-mailcow container. As an an example run the following command to add the missing indices. docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c \"php /web/nextcloud/occ db:add-missing-indices\"","title":"Update"},{"location":"third_party-nextcloud/#debugging-troubleshooting","text":"It may happen that you cannot reach the Nextcloud instance from your network. This may be due to the fact that the entry of your subnet in the array 'trusted_proxies' is missing. You can make changes in the Nextcloud config.php in data/web/nextcloud/config/* . 'trusted_proxies' => array ( 0 => 'fd4d:6169:6c63:6f77::/64', 1 => '172.22.1.0/24', 2 => 'NewSubnet/24', ), After the changes have been made, the nginx container must be restarted. docker-compose restart nginx-mailcow","title":"Debugging & Troubleshooting"},{"location":"third_party-portainer/","text":"In order to enable Portainer, the docker-compose.yml and site.conf for Nginx must be modified. 1. Create a new file docker-compose.override.yml in the mailcow-dockerized root folder and insert the following configuration version: '2.1' services: portainer-mailcow: image: portainer/portainer-ce volumes: - /var/run/docker.sock:/var/run/docker.sock - ./data/conf/portainer:/data restart: always dns: - 172.22.1.254 dns_search: mailcow-network networks: mailcow-network: aliases: - portainer 2a. Create data/conf/nginx/portainer.conf : upstream portainer { server portainer-mailcow:9000; } map $http_upgrade $connection_upgrade { default upgrade; '' close; } 2b. Insert a new location to the default mailcow site by creating the file data/conf/nginx/site.portainer.custom : location /portainer/ { proxy_http_version 1.1; proxy_set_header Host $http_host; # required for docker client's sake proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 900; proxy_set_header Connection \"\"; proxy_buffers 32 4k; proxy_pass http://portainer/; } location /portainer/api/websocket/ { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_pass http://portainer/api/websocket/; } 3. Apply your changes: docker-compose up -d && docker-compose restart nginx-mailcow Now you can simply navigate to https://${MAILCOW_HOSTNAME}/portainer/ to view your Portainer container monitoring page. You\u2019ll then be prompted to specify a new password for the admin account. After specifying your password, you\u2019ll then be able to connect to the Portainer UI.","title":"Portainer"},{"location":"third_party-roundcube/","text":"Download Roundcube 1.4.x to the web htdocs directory and extract it (here rc/ ): # Check for a newer release! cd data/web wget -O - https://github.com/roundcube/roundcubemail/releases/download/1.4.9/roundcubemail-1.4.9-complete.tar.gz | tar xfvz - # Change folder name mv roundcubemail-1.4.9 rc # Change permissions chown -R root: rc/ Create a file data/web/rc/config/config.inc.php with the following content. Change the des_key parameter to a random value. It is used to temporarily store your IMAP password. The \"db_prefix\" is optional but recommended. array('verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true) ); $config['enable_installer'] = true; $config['smtp_conn_options'] = array( 'ssl' => array('verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true) ); $config['db_prefix'] = 'mailcow_rc1'; Point your browser to https://myserver/rc/installer and follow the instructions. Initialize the database and leave the installer. Delete the directory data/web/rc/installer after a successful installation! Configure ManageSieve filtering \u00b6 Open data/web/rc/plugins/managesieve/config.inc.php and change the following parameters (or add them at the bottom of that file): $config['managesieve_port'] = 4190; $config['managesieve_host'] = 'tls://dovecot'; $config['managesieve_conn_options'] = array( 'ssl' => array('verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true) ); // Enables separate management interface for vacation responses (out-of-office) // 0 - no separate section (default), // 1 - add Vacation section, // 2 - add Vacation section, but hide Filters section $config['managesieve_vacation'] = 1; Enable change password function in Roundcube \u00b6 Open data/web/rc/config/config.inc.php and enable the password plugin: ... $config['plugins'] = array( 'archive', 'password', ); ... Open data/web/rc/plugins/password/password.php , search for case 'ssha': and add above: case 'ssha256': $salt = rcube_utils::random_bytes(8); $crypted = base64_encode( hash('sha256', $password . $salt, TRUE ) . $salt ); $prefix = '{SSHA256}'; break; Open data/web/rc/plugins/password/config.inc.php and change the following parameters (or add them at the bottom of that file): $config['password_driver'] = 'sql'; $config['password_algorithm'] = 'ssha256'; $config['password_algorithm_prefix'] = '{SSHA256}'; $config['password_query'] = \"UPDATE mailbox SET password = %P WHERE username = %u\"; Integrate CardDAV addressbooks in Roundcube \u00b6 Download the latest release of RCMCardDAV to the Roundcube plugin directory and extract it (here rc/plugins ): cd data/web/rc/plugins wget -O - https://github.com/blind-coder/rcmcarddav/releases/download/v3.0.3/carddav-3.0.3.tar.bz2 | tar xfvj - chown -R root: carddav/ Copy the file config.inc.php.dist to config.inc.php (here in rc/plugins/carddav ) and append the following preset to the end of the file - don't forget to replace mx.example.org with your own hostname: $prefs['SOGo'] = array( 'name' => 'SOGo', 'username' => '%u', 'password' => '%p', 'url' => 'https://mx.example.org/SOGo/dav/%u/', 'carddav_name_only' => true, 'use_categories' => true, 'active' => true, 'readonly' => false, 'refresh_time' => '02:00:00', 'fixed' => array( 'active', 'name', 'username', 'password', 'refresh_time' ), 'hide' => false, ); Please note, that this preset only integrates the default addressbook (the one that's named \"Personal Address Book\" and can't be deleted). Additional addressbooks are currently not automatically detected but can be manually added within the roundecube settings. Enable the plugin by adding carddav to $config['plugins'] in rc/config/config.inc.php . If you want to remove the default addressbooks (stored in the Roundcube database), so that only the CardDAV addressbooks are accessible, append $config['address_book_type'] = ''; to the config file data/web/rc/config/config.inc.php . Optionally, you can add Roundcube's link to the mailcow Apps list. To do this, open or create data/web/inc/vars.local.inc.php and add the following code-block: NOTE: Don't forget to add the 'SOGo', 'link' => '/SOGo/' ), array( 'name' => 'Roundcube', 'link' => '/rc/' ) ); ...","title":"Roundcube"},{"location":"third_party-roundcube/#configure-managesieve-filtering","text":"Open data/web/rc/plugins/managesieve/config.inc.php and change the following parameters (or add them at the bottom of that file): $config['managesieve_port'] = 4190; $config['managesieve_host'] = 'tls://dovecot'; $config['managesieve_conn_options'] = array( 'ssl' => array('verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true) ); // Enables separate management interface for vacation responses (out-of-office) // 0 - no separate section (default), // 1 - add Vacation section, // 2 - add Vacation section, but hide Filters section $config['managesieve_vacation'] = 1;","title":"Configure ManageSieve filtering"},{"location":"third_party-roundcube/#enable-change-password-function-in-roundcube","text":"Open data/web/rc/config/config.inc.php and enable the password plugin: ... $config['plugins'] = array( 'archive', 'password', ); ... Open data/web/rc/plugins/password/password.php , search for case 'ssha': and add above: case 'ssha256': $salt = rcube_utils::random_bytes(8); $crypted = base64_encode( hash('sha256', $password . $salt, TRUE ) . $salt ); $prefix = '{SSHA256}'; break; Open data/web/rc/plugins/password/config.inc.php and change the following parameters (or add them at the bottom of that file): $config['password_driver'] = 'sql'; $config['password_algorithm'] = 'ssha256'; $config['password_algorithm_prefix'] = '{SSHA256}'; $config['password_query'] = \"UPDATE mailbox SET password = %P WHERE username = %u\";","title":"Enable change password function in Roundcube"},{"location":"third_party-roundcube/#integrate-carddav-addressbooks-in-roundcube","text":"Download the latest release of RCMCardDAV to the Roundcube plugin directory and extract it (here rc/plugins ): cd data/web/rc/plugins wget -O - https://github.com/blind-coder/rcmcarddav/releases/download/v3.0.3/carddav-3.0.3.tar.bz2 | tar xfvj - chown -R root: carddav/ Copy the file config.inc.php.dist to config.inc.php (here in rc/plugins/carddav ) and append the following preset to the end of the file - don't forget to replace mx.example.org with your own hostname: $prefs['SOGo'] = array( 'name' => 'SOGo', 'username' => '%u', 'password' => '%p', 'url' => 'https://mx.example.org/SOGo/dav/%u/', 'carddav_name_only' => true, 'use_categories' => true, 'active' => true, 'readonly' => false, 'refresh_time' => '02:00:00', 'fixed' => array( 'active', 'name', 'username', 'password', 'refresh_time' ), 'hide' => false, ); Please note, that this preset only integrates the default addressbook (the one that's named \"Personal Address Book\" and can't be deleted). Additional addressbooks are currently not automatically detected but can be manually added within the roundecube settings. Enable the plugin by adding carddav to $config['plugins'] in rc/config/config.inc.php . If you want to remove the default addressbooks (stored in the Roundcube database), so that only the CardDAV addressbooks are accessible, append $config['address_book_type'] = ''; to the config file data/web/rc/config/config.inc.php . Optionally, you can add Roundcube's link to the mailcow Apps list. To do this, open or create data/web/inc/vars.local.inc.php and add the following code-block: NOTE: Don't forget to add the 'SOGo', 'link' => '/SOGo/' ), array( 'name' => 'Roundcube', 'link' => '/rc/' ) ); ...","title":"Integrate CardDAV addressbooks in Roundcube"},{"location":"third_party-thunderbird/","text":"Build the SOGo Connector plugin \u00b6 Install GNU Make, tar, and ZIP if you don't already have them installed. On Debian/Ubuntu, this can be done using apt-get install make tar zip Next, go to data/web inside mailcow-dockerized. Place the file thunderbird-plugins.php into that directory. Create a new directory thunderbird-plugins and place the script build-plugins.sh into it. Finally, execute the script with your hostname as an argument and piping it the names of all domains that mailcow handles. All of this can be done using the following commands: cd data/web curl -LO https://github.com/mailcow/mailcow-dockerized-docs/raw/master/docs/download/thunderbird-plugins.php mkdir thunderbird-plugins cd thunderbird-plugins curl -Lo build-plugins.sh https://github.com/mailcow/mailcow-dockerized-docs/raw/master/docs/download/build-thunderbird-plugins.sh chmod +x build-plugins.sh echo example.com example.org | ./build-plugins.sh mailcow.example.com Install it in Thunderbird \u00b6 After you have set up your mailcow IMAP account in Thunderbird, download the SOGo Connector plugin for your domain, e.g. https://mailcow.example.com/thunderbird-plugins/sogo-connector-68.0.1-example.com.xpi (see data/web/thunderbird-plugins ), and install it into Thunderbird. All your address books and calendars will be configured automatically.","title":"SOGo Connector for Thunderbird"},{"location":"third_party-thunderbird/#build-the-sogo-connector-plugin","text":"Install GNU Make, tar, and ZIP if you don't already have them installed. On Debian/Ubuntu, this can be done using apt-get install make tar zip Next, go to data/web inside mailcow-dockerized. Place the file thunderbird-plugins.php into that directory. Create a new directory thunderbird-plugins and place the script build-plugins.sh into it. Finally, execute the script with your hostname as an argument and piping it the names of all domains that mailcow handles. All of this can be done using the following commands: cd data/web curl -LO https://github.com/mailcow/mailcow-dockerized-docs/raw/master/docs/download/thunderbird-plugins.php mkdir thunderbird-plugins cd thunderbird-plugins curl -Lo build-plugins.sh https://github.com/mailcow/mailcow-dockerized-docs/raw/master/docs/download/build-thunderbird-plugins.sh chmod +x build-plugins.sh echo example.com example.org | ./build-plugins.sh mailcow.example.com","title":"Build the SOGo Connector plugin"},{"location":"third_party-thunderbird/#install-it-in-thunderbird","text":"After you have set up your mailcow IMAP account in Thunderbird, download the SOGo Connector plugin for your domain, e.g. https://mailcow.example.com/thunderbird-plugins/sogo-connector-68.0.1-example.com.xpi (see data/web/thunderbird-plugins ), and install it into Thunderbird. All your address books and calendars will be configured automatically.","title":"Install it in Thunderbird"},{"location":"u_e-80_to_443/","text":"Since February the 28th 2017 mailcow does come with port 80 and 443 enabled. Do not use the config below for reverse proxy setups , please see our reverse proxy guide for this, which includes a redirect from HTTP to HTTPS. Open mailcow.conf and set HTTP_BIND= - if not already set. Create a new file data/conf/nginx/redirect.conf and add the following server config to the file: server { root /web; listen 80 default_server; listen [::]:80 default_server; include /etc/nginx/conf.d/server_name.active; if ( $request_uri ~* \"%0A|%0D\" ) { return 403; } location ^~ /.well-known/acme-challenge/ { allow all; default_type \"text/plain\"; } location / { return 301 https://$host$uri$is_args$args; } } In case you changed the HTTP_BIND parameter, recreate the container: docker-compose up -d Otherwise restart Nginx: docker-compose restart nginx-mailcow","title":"Redirect HTTP to HTTPS"},{"location":"u_e-autodiscover_config/","text":"You do not need to change or create this file, autodiscover works out of the box . This guide is only meant for customizations to the autodiscover or autoconfig process. Newer Outlook clients (especially those delivered with O365) will not autodiscover mail profiles. Keep in mind, that ActiveSync should NOT be used with a desktop client . Open/create data/web/inc/vars.local.inc.php and add your changes to the configuration array. Changes will be merged with \"$autodiscover_config\" in data/web/inc/vars.inc.php ): 'activesync', // If autodiscoverType => activesync, also use ActiveSync (EAS) for Outlook desktop clients (>= Outlook 2013 on Windows) // Outlook for Mac does not support ActiveSync 'useEASforOutlook' => 'yes', // Please don't use STARTTLS-enabled service ports in the \"port\" variable. // The autodiscover service will always point to SMTPS and IMAPS (TLS-wrapped services). // The autoconfig service will additionally announce the STARTTLS-enabled ports, specified in the \"tlsport\" variable. 'imap' => array( 'server' => $mailcow_hostname, 'port' => array_pop(explode(':', getenv('IMAPS_PORT'))), 'tlsport' => array_pop(explode(':', getenv('IMAP_PORT'))), ), 'pop3' => array( 'server' => $mailcow_hostname, 'port' => array_pop(explode(':', getenv('POPS_PORT'))), 'tlsport' => array_pop(explode(':', getenv('POP_PORT'))), ), 'smtp' => array( 'server' => $mailcow_hostname, 'port' => array_pop(explode(':', getenv('SMTPS_PORT'))), 'tlsport' => array_pop(explode(':', getenv('SUBMISSION_PORT'))), ), 'activesync' => array( 'url' => 'https://'.$mailcow_hostname.($https_port == 443 ? '' : ':'.$https_port).'/Microsoft-Server-ActiveSync', ), 'caldav' => array( 'server' => $mailcow_hostname, 'port' => $https_port, ), 'carddav' => array( 'server' => $mailcow_hostname, 'port' => $https_port, ), ); To always use IMAP and SMTP instead of EAS, set 'autodiscoverType' => 'imap' . Disable ActiveSync for Outlook desktop clients by setting \"useEASforOutlook\" to \"no\".","title":"Autodiscover / Autoconfig"},{"location":"u_e-backup_restore-maildir/","text":"Backup \u00b6 This line backups the vmail directory to a file backup_vmail.tar.gz in the mailcow root directory: cd /path/to/mailcow-dockerized docker run --rm -i -v $(docker inspect --format '{{ range .Mounts }}{{ if eq .Destination \"/var/vmail\" }}{{ .Name }}{{ end }}{{ end }}' $(docker-compose ps -q dovecot-mailcow)):/vmail -v ${PWD}:/backup debian:stretch-slim tar cvfz /backup/backup_vmail.tar.gz /vmail You can change the path by adjusting ${PWD} (which equals to the current directory) to any path you have write-access to. Set the filename backup_vmail.tar.gz to any custom name, but leave the path as it is. Example: [...] tar cvfz /backup/my_own_filename_.tar.gz Restore \u00b6 cd /path/to/mailcow-dockerized docker run --rm -it -v $(docker inspect --format '{{ range .Mounts }}{{ if eq .Destination \"/var/vmail\" }}{{ .Name }}{{ end }}{{ end }}' $(docker-compose ps -q dovecot-mailcow)):/vmail -v ${PWD}:/backup debian:stretch-slim tar xvfz /backup/backup_vmail.tar.gz","title":"Maildir"},{"location":"u_e-backup_restore-maildir/#backup","text":"This line backups the vmail directory to a file backup_vmail.tar.gz in the mailcow root directory: cd /path/to/mailcow-dockerized docker run --rm -i -v $(docker inspect --format '{{ range .Mounts }}{{ if eq .Destination \"/var/vmail\" }}{{ .Name }}{{ end }}{{ end }}' $(docker-compose ps -q dovecot-mailcow)):/vmail -v ${PWD}:/backup debian:stretch-slim tar cvfz /backup/backup_vmail.tar.gz /vmail You can change the path by adjusting ${PWD} (which equals to the current directory) to any path you have write-access to. Set the filename backup_vmail.tar.gz to any custom name, but leave the path as it is. Example: [...] tar cvfz /backup/my_own_filename_.tar.gz","title":"Backup"},{"location":"u_e-backup_restore-maildir/#restore","text":"cd /path/to/mailcow-dockerized docker run --rm -it -v $(docker inspect --format '{{ range .Mounts }}{{ if eq .Destination \"/var/vmail\" }}{{ .Name }}{{ end }}{{ end }}' $(docker-compose ps -q dovecot-mailcow)):/vmail -v ${PWD}:/backup debian:stretch-slim tar xvfz /backup/backup_vmail.tar.gz","title":"Restore"},{"location":"u_e-backup_restore-mysql/","text":"Backup \u00b6 cd /path/to/mailcow-dockerized source mailcow.conf DATE=$(date +\"%Y%m%d_%H%M%S\") docker-compose exec -T mysql-mailcow mysqldump --default-character-set=utf8mb4 -u${DBUSER} -p${DBPASS} ${DBNAME} > backup_${DBNAME}_${DATE}.sql Restore \u00b6 Warning You should redirect the SQL dump without docker-compose to prevent parsing errors. cd /path/to/mailcow-dockerized source mailcow.conf docker exec -i $(docker-compose ps -q mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} < backup_file.sql","title":"MySQL"},{"location":"u_e-backup_restore-mysql/#backup","text":"cd /path/to/mailcow-dockerized source mailcow.conf DATE=$(date +\"%Y%m%d_%H%M%S\") docker-compose exec -T mysql-mailcow mysqldump --default-character-set=utf8mb4 -u${DBUSER} -p${DBPASS} ${DBNAME} > backup_${DBNAME}_${DATE}.sql","title":"Backup"},{"location":"u_e-backup_restore-mysql/#restore","text":"Warning You should redirect the SQL dump without docker-compose to prevent parsing errors. cd /path/to/mailcow-dockerized source mailcow.conf docker exec -i $(docker-compose ps -q mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} < backup_file.sql","title":"Restore"},{"location":"u_e-change_config/","text":"The most important configuration files are mounted from the host into the related containers: data/conf \u251c\u2500\u2500 unbound \u2502 \u2514\u2500\u2500 unbound.conf \u251c\u2500\u2500 dovecot \u2502 \u251c\u2500\u2500 dovecot.conf \u2502 \u251c\u2500\u2500 dovecot-master.passwd \u2502 \u251c\u2500\u2500 sieve_after \u2502 \u2514\u2500\u2500 sql \u2502 \u251c\u2500\u2500 dovecot-dict-sql.conf \u2502 \u2514\u2500\u2500 dovecot-mysql.conf \u251c\u2500\u2500 mysql \u2502 \u2514\u2500\u2500 my.cnf \u251c\u2500\u2500 nginx \u2502 \u251c\u2500\u2500 dynmaps.conf \u2502 \u251c\u2500\u2500 site.conf \u2502 \u2514\u2500\u2500 templates \u2502 \u251c\u2500\u2500 listen_plain.template \u2502 \u251c\u2500\u2500 listen_ssl.template \u2502 \u2514\u2500\u2500 server_name.template \u251c\u2500\u2500 postfix \u2502 \u251c\u2500\u2500 main.cf \u2502 \u251c\u2500\u2500 master.cf \u2502 \u251c\u2500\u2500 postscreen_access.cidr \u2502 \u251c\u2500\u2500 smtp_dsn_filter \u2502 \u2514\u2500\u2500 sql \u2502 \u251c\u2500\u2500 mysql_relay_recipient_maps.cf \u2502 \u251c\u2500\u2500 mysql_tls_enforce_in_policy.cf \u2502 \u251c\u2500\u2500 mysql_tls_enforce_out_policy.cf \u2502 \u251c\u2500\u2500 mysql_virtual_alias_domain_catchall_maps.cf \u2502 \u251c\u2500\u2500 mysql_virtual_alias_domain_maps.cf \u2502 \u251c\u2500\u2500 mysql_virtual_alias_maps.cf \u2502 \u251c\u2500\u2500 mysql_virtual_domains_maps.cf \u2502 \u251c\u2500\u2500 mysql_virtual_mailbox_maps.cf \u2502 \u251c\u2500\u2500 mysql_virtual_relay_domain_maps.cf \u2502 \u251c\u2500\u2500 mysql_virtual_sender_acl.cf \u2502 \u2514\u2500\u2500 mysql_virtual_spamalias_maps.cf \u251c\u2500\u2500 rspamd \u2502 \u251c\u2500\u2500 dynmaps \u2502 \u2502 \u251c\u2500\u2500 authoritative.php \u2502 \u2502 \u251c\u2500\u2500 settings.php \u2502 \u2502 \u251c\u2500\u2500 tags.php \u2502 \u2502 \u2514\u2500\u2500 vars.inc.php -> ../../../web/inc/vars.inc.php \u2502 \u251c\u2500\u2500 local.d \u2502 \u2502 \u251c\u2500\u2500 dkim.conf \u2502 \u2502 \u251c\u2500\u2500 metrics.conf \u2502 \u2502 \u251c\u2500\u2500 options.inc \u2502 \u2502 \u251c\u2500\u2500 redis.conf \u2502 \u2502 \u251c\u2500\u2500 rspamd.conf.local \u2502 \u2502 \u2514\u2500\u2500 statistic.conf \u2502 \u251c\u2500\u2500 lua \u2502 \u2502 \u2514\u2500\u2500 rspamd.local.lua \u2502 \u2514\u2500\u2500 override.d \u2502 \u251c\u2500\u2500 logging.inc \u2502 \u251c\u2500\u2500 worker-controller.inc \u2502 \u2514\u2500\u2500 worker-normal.inc \u2514\u2500\u2500 sogo \u251c\u2500\u2500 sieve.creds \u2514\u2500\u2500 sogo.conf Just change the according configuration file on the host and restart the related service: docker-compose restart service-mailcow","title":"Adjust Service Configurations"},{"location":"u_e-deinstall/","text":"To remove mailcow: dockerized with all it's volumes, images and containers do: docker-compose down -v --rmi all --remove-orphans Info -v Remove named volumes declared in the volumes section of the Compose file and anonymous volumes attached to containers. --rmi Remove images. Type must be one of: all : Remove all images used by any service. local : Remove only images that don't have a custom tag set by the image field. --remove-orphans Remove containers for services not defined in the compose file. By default docker-compose down only removes currently active containers and networks defined in the docker-compose.yml .","title":"Deinstall"},{"location":"u_e-docker-cust_dockerfiles/","text":"You need to copy the override file with corresponding build tags to the mailcow: dockerized root folder (i.e. /opt/mailcow-dockerized ): cp helper-scripts/docker-compose.override.yml.d/BUILD_FLAGS/docker-compose.override.yml docker-compose.override.yml Make your changes in data/Dockerfiles/$service and build the image locally: docker build data/Dockerfiles/service -t mailcow/$service Now auto-recreate modified containers: docker-compose up -d","title":"Customize Dockerfiles"},{"location":"u_e-docker-dc_bash_compl/","text":"To get some sexy bash completion inside your containers simply execute the following: curl -L https://raw.githubusercontent.com/docker/compose/$(docker-compose version --short)/contrib/completion/bash/docker-compose -o /etc/bash_completion.d/docker-compose","title":"Docker Compose Bash Completion"},{"location":"u_e-dovecot-any_acl/","text":"On August the 17th, we disabled the possibility to share with \"any\" or \"all authenticated users\" by default. This function can be re-enabled by setting ACL_ANYONE to allow in mailcow.conf: ACL_ANYONE=allow Apply the changes by running docker-compose up -d .","title":"Enable \"any\" ACL settings"},{"location":"u_e-dovecot-expunge/","text":"If you want to delete old mails out of the .Junk or .Trash folders or maybe delete all read mails that are older than a certain amount of time you may use dovecot's tool doveadm man doveadm-expunge . The manual way \u00b6 That said, let's dive in: Delete a user's mails inside the junk folder that are read and older than 4 hours docker-compose exec dovecot-mailcow doveadm expunge -u 'mailbox@example.com' mailbox 'Junk' SEEN not SINCE 4h Delete all user's mails in the junk folder that are older than 7 days docker-compose exec dovecot-mailcow doveadm expunge -A mailbox 'Junk' savedbefore 7d Delete all mails (of all users) in all folders that are older than 52 weeks (internal date of the mail, not the date it was saved on the system => before instead of savedbefore ). Useful for deleting very old mails on all users and folders (thus especially useful for GDPR-compliance). docker-compose exec dovecot-mailcow doveadm expunge -A mailbox % before 52w Delete mails inside a custom folder inside a user's inbox that are not flagged and older than 2 weeks docker-compose exec dovecot-mailcow doveadm expunge -u 'mailbox@example.com' mailbox 'INBOX/custom-folder' not FLAGGED not SINCE 2w Info For possible time spans or search keys have a look at man doveadm-search-query Job scheduler \u00b6 via the host system cron \u00b6 If you want to automate such a task you can create a cron job on your host that calls a script like the one below: #!/bin/bash # Path to mailcow-dockerized, e.g. /opt/mailcow-dockerized cd /path/to/your/mailcow-dockerized /usr/local/bin/docker-compose exec -T dovecot-mailcow doveadm expunge -A mailbox 'Junk' savedbefore 2w /usr/local/bin/docker-compose exec -T dovecot-mailcow doveadm expunge -A mailbox 'Junk' SEEN not SINCE 12h [...] To create a cron job you may execute crontab -e and insert something like the following to execute a script: # Execute everyday at 04:00 A.M. 0 4 * * * /path/to/your/expunge_mailboxes.sh via Docker job scheduler \u00b6 To archive this with a docker job scheduler use this docker-compose.override.yml with your mailcow: version: '2.1' services: ofelia: image: mcuadros/ofelia:latest restart: always command: daemon --docker volumes: - /var/run/docker.sock:/var/run/docker.sock:ro network_mode: none dovecot-mailcow: labels: - \"ofelia.enabled=true\" - \"ofelia.job-exec.dovecot-expunge-trash.schedule=0 4 * * *\" - \"ofelia.job-exec.dovecot-expunge-trash.command=doveadm expunge -A mailbox 'Junk' savedbefore 2w\" - \"ofelia.job-exec.dovecot-expunge-trash.tty=false\" The job controller just need access to the docker control socket to be able to emulate the behavior of \"exec\". Then we add a few label to our dovecot-container to activate the job scheduler and tell him in a cron compatible scheduling format when to run. If you struggle with that schedule string you can use crontab guru . This docker-compose.override.yml deletes all mails older then 2 weeks from the \"Junk\" folder every day at 4 am. To see if things ran proper, you can not only see in your mailbox but also check Ofelia's docker log if it looks something like this: common.go:124 \u25b6 NOTICE [Job \"dovecot-expunge-trash\" (8759567efa66)] Started - doveadm expunge -A mailbox 'Junk' savedbefore 2w, common.go:124 \u25b6 NOTICE [Job \"dovecot-expunge-trash\" (8759567efa66)] Finished in \"285.032291ms\", failed: false, skipped: false, error: none, If it failed it will say so and give you the output of the doveadm in the log to make it easy on you to debug. In case you want to add more jobs, ensure you change the \"dovecot-expunge-trash\" part after \"ofelia.job-exec.\" to something else, it defines the name of the job. Syntax of the labels you find at mcuadros/ofelia .","title":"Expunge a Users mails"},{"location":"u_e-dovecot-expunge/#the-manual-way","text":"That said, let's dive in: Delete a user's mails inside the junk folder that are read and older than 4 hours docker-compose exec dovecot-mailcow doveadm expunge -u 'mailbox@example.com' mailbox 'Junk' SEEN not SINCE 4h Delete all user's mails in the junk folder that are older than 7 days docker-compose exec dovecot-mailcow doveadm expunge -A mailbox 'Junk' savedbefore 7d Delete all mails (of all users) in all folders that are older than 52 weeks (internal date of the mail, not the date it was saved on the system => before instead of savedbefore ). Useful for deleting very old mails on all users and folders (thus especially useful for GDPR-compliance). docker-compose exec dovecot-mailcow doveadm expunge -A mailbox % before 52w Delete mails inside a custom folder inside a user's inbox that are not flagged and older than 2 weeks docker-compose exec dovecot-mailcow doveadm expunge -u 'mailbox@example.com' mailbox 'INBOX/custom-folder' not FLAGGED not SINCE 2w Info For possible time spans or search keys have a look at man doveadm-search-query","title":"The manual way"},{"location":"u_e-dovecot-expunge/#job-scheduler","text":"","title":"Job scheduler"},{"location":"u_e-dovecot-expunge/#via-the-host-system-cron","text":"If you want to automate such a task you can create a cron job on your host that calls a script like the one below: #!/bin/bash # Path to mailcow-dockerized, e.g. /opt/mailcow-dockerized cd /path/to/your/mailcow-dockerized /usr/local/bin/docker-compose exec -T dovecot-mailcow doveadm expunge -A mailbox 'Junk' savedbefore 2w /usr/local/bin/docker-compose exec -T dovecot-mailcow doveadm expunge -A mailbox 'Junk' SEEN not SINCE 12h [...] To create a cron job you may execute crontab -e and insert something like the following to execute a script: # Execute everyday at 04:00 A.M. 0 4 * * * /path/to/your/expunge_mailboxes.sh","title":"via the host system cron"},{"location":"u_e-dovecot-expunge/#via-docker-job-scheduler","text":"To archive this with a docker job scheduler use this docker-compose.override.yml with your mailcow: version: '2.1' services: ofelia: image: mcuadros/ofelia:latest restart: always command: daemon --docker volumes: - /var/run/docker.sock:/var/run/docker.sock:ro network_mode: none dovecot-mailcow: labels: - \"ofelia.enabled=true\" - \"ofelia.job-exec.dovecot-expunge-trash.schedule=0 4 * * *\" - \"ofelia.job-exec.dovecot-expunge-trash.command=doveadm expunge -A mailbox 'Junk' savedbefore 2w\" - \"ofelia.job-exec.dovecot-expunge-trash.tty=false\" The job controller just need access to the docker control socket to be able to emulate the behavior of \"exec\". Then we add a few label to our dovecot-container to activate the job scheduler and tell him in a cron compatible scheduling format when to run. If you struggle with that schedule string you can use crontab guru . This docker-compose.override.yml deletes all mails older then 2 weeks from the \"Junk\" folder every day at 4 am. To see if things ran proper, you can not only see in your mailbox but also check Ofelia's docker log if it looks something like this: common.go:124 \u25b6 NOTICE [Job \"dovecot-expunge-trash\" (8759567efa66)] Started - doveadm expunge -A mailbox 'Junk' savedbefore 2w, common.go:124 \u25b6 NOTICE [Job \"dovecot-expunge-trash\" (8759567efa66)] Finished in \"285.032291ms\", failed: false, skipped: false, error: none, If it failed it will say so and give you the output of the doveadm in the log to make it easy on you to debug. In case you want to add more jobs, ensure you change the \"dovecot-expunge-trash\" part after \"ofelia.job-exec.\" to something else, it defines the name of the job. Syntax of the labels you find at mcuadros/ofelia .","title":"via Docker job scheduler"},{"location":"u_e-dovecot-extra_conf/","text":"Create a file data/conf/dovecot/extra.conf - if missing - and add your additional content here. Restart dovecot-mailcow to apply your changes: docker-compose restart dovecot-mailcow","title":"Customize/Expand dovecot.conf"},{"location":"u_e-dovecot-fts/","text":"Solr is used for setups with memory >= 3.5 GiB to provide full-text search in Dovecot. Please be aware that applications like Solr may need maintenance from time to time. Besides that, Solr will eat a lot of RAM, depending on the usage of your server. Please avoid it on machines with less than 3 GB RAM. The default heap size (1024 M) is defined in mailcow.conf. Since we run in Docker and create our containers with the \"restart: always\" flag, a oom situation will at least only trigger a restart of the container. FTS related Dovecot commands \u00b6 # single user docker-compose exec dovecot-mailcow doveadm fts rescan -u user@domain # all users docker-compose exec dovecot-mailcow doveadm fts rescan -A Dovecot Wiki: \"Scan what mails exist in the full text search index and compare those to what actually exist in mailboxes. This removes mails from the index that have already been expunged and makes sure that the next doveadm index will index all the missing mails (if any).\" This does not re-index a mailbox. It basically repairs a given index. If you want to re-index data immediately, you can run the followig command, where '*' can also be a mailbox mask like 'Sent'. You do not need to run these commands, but it will speed things up a bit: # single user docker-compose exec dovecot-mailcow doveadm index -u user@domain '*' # all users, but obviously slower and more dangerous docker-compose exec dovecot-mailcow doveadm index -A '*' This will take some time depending on your machine and Solr can run oom, monitor it! Because re-indexing is very sensible, we did not include it to mailcow UI. You will need to take care of any errors while re-indexing a mailbox. Delete mailbox data \u00b6 mailcow will purge index data of a user when deleting a mailbox.","title":"FTS (Solr)"},{"location":"u_e-dovecot-fts/#fts-related-dovecot-commands","text":"# single user docker-compose exec dovecot-mailcow doveadm fts rescan -u user@domain # all users docker-compose exec dovecot-mailcow doveadm fts rescan -A Dovecot Wiki: \"Scan what mails exist in the full text search index and compare those to what actually exist in mailboxes. This removes mails from the index that have already been expunged and makes sure that the next doveadm index will index all the missing mails (if any).\" This does not re-index a mailbox. It basically repairs a given index. If you want to re-index data immediately, you can run the followig command, where '*' can also be a mailbox mask like 'Sent'. You do not need to run these commands, but it will speed things up a bit: # single user docker-compose exec dovecot-mailcow doveadm index -u user@domain '*' # all users, but obviously slower and more dangerous docker-compose exec dovecot-mailcow doveadm index -A '*' This will take some time depending on your machine and Solr can run oom, monitor it! Because re-indexing is very sensible, we did not include it to mailcow UI. You will need to take care of any errors while re-indexing a mailbox.","title":"FTS related Dovecot commands"},{"location":"u_e-dovecot-fts/#delete-mailbox-data","text":"mailcow will purge index data of a user when deleting a mailbox.","title":"Delete mailbox data"},{"location":"u_e-dovecot-idle_interval/","text":"Changing the IMAP IDLE interval \u00b6 What is the IDLE interval? \u00b6 Per default, Dovecot sends a \"I'm still here\" notification to every client that has an open connection with Dovecot to get mails as quickly as possible without manually polling it (IMAP PUSH). This notification is controlled by the setting imap_idle_notify_interval , which defaults to 2 minutes. A short interval results in the client getting a lot of messages for this connection, which is bad for mobile devices, because every time the device receives this message, the mailing app has to wake up. This can result in unnecessary battery drain. Edit the value \u00b6 Change configuration \u00b6 Create a new file data/conf/dovecot/extra.conf (or edit it if it already exists). Insert the setting followed by the new value. For example, to set the interval to 5 minutes you could type: imap_idle_notify_interval = 5 mins 29 minutes is the maximum value allowed by the corresponding RFC . Warning This isn't a default setting in mailcow because we don't know how this setting changes the behavior of other clients. Be careful if you change this and monitor different behavior. Reload Dovecot \u00b6 Now reload Dovecot: docker-compose exec dovecot-mailcow dovecot reload Info You can check the value of this setting with docker-compose exec dovecot-mailcow dovecot -a | grep \"imap_idle_notify_interval\" If you didn't change it, it should be at 2m. If you did change it, you should see your new value.","title":"IMAP IDLE interval"},{"location":"u_e-dovecot-idle_interval/#changing-the-imap-idle-interval","text":"","title":"Changing the IMAP IDLE interval"},{"location":"u_e-dovecot-idle_interval/#what-is-the-idle-interval","text":"Per default, Dovecot sends a \"I'm still here\" notification to every client that has an open connection with Dovecot to get mails as quickly as possible without manually polling it (IMAP PUSH). This notification is controlled by the setting imap_idle_notify_interval , which defaults to 2 minutes. A short interval results in the client getting a lot of messages for this connection, which is bad for mobile devices, because every time the device receives this message, the mailing app has to wake up. This can result in unnecessary battery drain.","title":"What is the IDLE interval?"},{"location":"u_e-dovecot-idle_interval/#edit-the-value","text":"","title":"Edit the value"},{"location":"u_e-dovecot-idle_interval/#change-configuration","text":"Create a new file data/conf/dovecot/extra.conf (or edit it if it already exists). Insert the setting followed by the new value. For example, to set the interval to 5 minutes you could type: imap_idle_notify_interval = 5 mins 29 minutes is the maximum value allowed by the corresponding RFC . Warning This isn't a default setting in mailcow because we don't know how this setting changes the behavior of other clients. Be careful if you change this and monitor different behavior.","title":"Change configuration"},{"location":"u_e-dovecot-idle_interval/#reload-dovecot","text":"Now reload Dovecot: docker-compose exec dovecot-mailcow dovecot reload Info You can check the value of this setting with docker-compose exec dovecot-mailcow dovecot -a | grep \"imap_idle_notify_interval\" If you didn't change it, it should be at 2m. If you did change it, you should see your new value.","title":"Reload Dovecot"},{"location":"u_e-dovecot-mail-crypt/","text":"Mails are stored compressed (lz4) and encrypted. The key pair can be found in crypt-vol-1. If you want to decode/encode existing maildir files, you can use the following script at your own risk: Enter Dovecot by running docker-compose exec dovecot-mailcow /bin/bash in the mailcow-dockerized location. # Decrypt /var/vmail find /var/vmail/ -type f -regextype egrep -regex '.*S=.*W=.*' | while read -r file; do if [[ $(head -c7 \"$file\") == \"CRYPTED\" ]]; then doveadm fs get compress lz4:0:crypt:private_key_path=/mail_crypt/ecprivkey.pem:public_key_path=/mail_crypt/ecpubkey.pem:posix:prefix=/ \\ \"$file\" > \"/tmp/$(basename \"$file\")\" if [[ -s \"/tmp/$(basename \"$file\")\" ]]; then chmod 600 \"/tmp/$(basename \"$file\")\" chown 5000:5000 \"/tmp/$(basename \"$file\")\" mv \"/tmp/$(basename \"$file\")\" \"$file\" else rm \"/tmp/$(basename \"$file\")\" fi fi done # Encrypt /var/vmail find /var/vmail/ -type f -regextype egrep -regex '.*S=.*W=.*' | while read -r file; do if [[ $(head -c7 \"$file\") != \"CRYPTED\" ]]; then doveadm fs put crypt private_key_path=/mail_crypt/ecprivkey.pem:public_key_path=/mail_crypt/ecpubkey.pem:posix:prefix=/ \\ \"$file\" \"$file\" chmod 600 \"$file\" chown 5000:5000 \"$file\" fi done","title":"Mail crypt"},{"location":"u_e-dovecot-more/","text":"Here is just an unsorted list of useful doveadm commands that could be useful. doveadm quota \u00b6 The quota get and quota recalc 1 commands are used to display or recalculate the current user's quota usage. The reported values are in kilobytes . To list the current quota status for a user / mailbox, do: doveadm quota get -u 'mailbox@example.org' To list the quota storage value for all users, do: doveadm quota get -A |grep \"STORAGE\" Recalculate a single user's quota usage: doveadm quota recalc -u 'mailbox@example.org' doveadm search \u00b6 The doveadm search 2 command is used to find messages matching your query. It can return the username, mailbox-GUID / -UID and message-GUIDs / -UIDs. To view the number of messages, by user, in their .Trash folder: doveadm search -A mailbox 'Trash' | awk '{print $1}' | sort | uniq -c Show all messages in a user's inbox older then 90 days: doveadm search -u 'mailbox@example.org' mailbox 'INBOX' savedbefore 90d Show all messages in any folder that are older then 30 days for mailbox@example.org : doveadm search -u 'mailbox@example.org' mailbox \"*\" savedbefore 30d https://wiki.dovecot.org/Tools/Doveadm/Quota \u21a9 https://wiki.dovecot.org/Tools/Doveadm/Search \u21a9","title":"More Examples with DOVEADM"},{"location":"u_e-dovecot-more/#doveadm-quota","text":"The quota get and quota recalc 1 commands are used to display or recalculate the current user's quota usage. The reported values are in kilobytes . To list the current quota status for a user / mailbox, do: doveadm quota get -u 'mailbox@example.org' To list the quota storage value for all users, do: doveadm quota get -A |grep \"STORAGE\" Recalculate a single user's quota usage: doveadm quota recalc -u 'mailbox@example.org'","title":"doveadm quota"},{"location":"u_e-dovecot-more/#doveadm-search","text":"The doveadm search 2 command is used to find messages matching your query. It can return the username, mailbox-GUID / -UID and message-GUIDs / -UIDs. To view the number of messages, by user, in their .Trash folder: doveadm search -A mailbox 'Trash' | awk '{print $1}' | sort | uniq -c Show all messages in a user's inbox older then 90 days: doveadm search -u 'mailbox@example.org' mailbox 'INBOX' savedbefore 90d Show all messages in any folder that are older then 30 days for mailbox@example.org : doveadm search -u 'mailbox@example.org' mailbox \"*\" savedbefore 30d https://wiki.dovecot.org/Tools/Doveadm/Quota \u21a9 https://wiki.dovecot.org/Tools/Doveadm/Search \u21a9","title":"doveadm search"},{"location":"u_e-dovecot-public_folder/","text":"Create a new public namespace \"Public\" and a mailbox \"Develcow\" inside that namespace: Edit or create data/conf/dovecot/extra.conf , add: namespace { type = public separator = / prefix = Public/ location = maildir:/var/vmail/public:INDEXPVT=~/public subscriptions = yes mailbox \"Develcow\" { auto = subscribe } } :INDEXPVT=~/public can be omitted if per-user seen flags are not wanted. The new mailbox in the public namespace will be auto-subscribed by users. To allow all authenticated users access full to that new mailbox (not the whole namespace), run: docker-compose exec dovecot-mailcow doveadm acl set -A \"Public/Develcow\" \"authenticated\" lookup read write write-seen write-deleted insert post delete expunge create Adjust the command to your needs if you like to assign more granular rights per user (use -u user@domain instead of -A for example). Allow authenticated users access to the whole public namespace \u00b6 To allow all authenticated users access full access to the whole public namespace and its subfolders, create a new dovecot-acl file in the namespace root directory: Open/edit/create /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data/public/dovecot-acl (adjust the path accordingly) to create the global ACL file with the following content: authenticated kxeilprwts kxeilprwts equals to lookup read write write-seen write-deleted insert post delete expunge create . You can use doveadm acl set -u user@domain \"Public/Develcow\" user=user@domain lookup read to limit access for a single user. You may also turn it around to limit access for all users to \"lr\" and grant only some users full access. See Dovecot ACL for further information about ACL.","title":"Public folders"},{"location":"u_e-dovecot-public_folder/#allow-authenticated-users-access-to-the-whole-public-namespace","text":"To allow all authenticated users access full access to the whole public namespace and its subfolders, create a new dovecot-acl file in the namespace root directory: Open/edit/create /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data/public/dovecot-acl (adjust the path accordingly) to create the global ACL file with the following content: authenticated kxeilprwts kxeilprwts equals to lookup read write write-seen write-deleted insert post delete expunge create . You can use doveadm acl set -u user@domain \"Public/Develcow\" user=user@domain lookup read to limit access for a single user. You may also turn it around to limit access for all users to \"lr\" and grant only some users full access. See Dovecot ACL for further information about ACL.","title":"Allow authenticated users access to the whole public namespace"},{"location":"u_e-dovecot-static_master/","text":"Random master usernames and passwords are automatically created on every restart of dovecot-mailcow. That's recommended and should not be changed. If you need the user to be static anyway, please specify two variables in mailcow.conf . Both parameters must not be empty! DOVECOT_MASTER_USER=mymasteruser DOVECOT_MASTER_PASS=mysecretpass Run docker-compose up -d to apply your changes. The static master username will be expanded to DOVECOT_MASTER_USER@mailcow.local . To login as test@example.org this would equal to test@example.org*mymasteruser@mailcow.local with the specified password above. A login to SOGo is not possible with this username. A click-to-login function for SOGo is available for admins as described here No master user is required.","title":"Static master user"},{"location":"u_e-dovecot-vmail-volume/","text":"The \"new\" way \u00b6 WARNING : Newer Docker versions seem to complain about existing volumes. You can fix this temporarily by removing the existing volume and start mailcow with the override file. But it seems to be problematic after a reboot (needs to be confirmed). An easy, dirty, yet stable workaround is to stop mailcow ( docker-compose down ), remove /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data and create a new link to your remote filesystem location, for example: mv /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data_backup ln -s /mnt/volume-xy/vmail_data /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data Start mailcow afterwards. The \"old\" way \u00b6 If you want to use another folder for the vmail-volume, you can create a docker-compose.override.yml file and add the following content: version: '2.1' volumes: vmail-vol-1: driver_opts: type: none device: /data/mailcow/vmail o: bind Moving an existing vmail folder: \u00b6 Locate the current vmail folder by its \"Mountpoint\" attribute: docker volume inspect mailcowdockerized_vmail-vol-1 [ { \"CreatedAt\": \"2019-06-16T22:08:34+02:00\", \"Driver\": \"local\", \"Labels\": { \"com.docker.compose.project\": \"mailcowdockerized\", \"com.docker.compose.version\": \"1.23.2\", \"com.docker.compose.volume\": \"vmail-vol-1\" }, \"Mountpoint\": \"/var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data\", \"Name\": \"mailcowdockerized_vmail-vol-1\", \"Options\": null, \"Scope\": \"local\" } ] Copy the content of the Mountpoint folder to the new location (e.g. /data/mailcow/vmail ) using cp -a , rsync -a or a similar non strcuture breaking copy command Stop mailcow by executing docker-compose down from within your mailcow root folder (e.g. /opt/mailcow-dockerized ) Create the file docker-compose.override.yml , edit the device path accordingly Delete the current vmail folder: docker volume rm mailcowdockerized_vmail-vol-1 Start mailcow by executing docker-compose up -d from within your mailcow root folder (e.g. /opt/mailcow-dockerized )","title":"Move Maildir (vmail)"},{"location":"u_e-dovecot-vmail-volume/#the-new-way","text":"WARNING : Newer Docker versions seem to complain about existing volumes. You can fix this temporarily by removing the existing volume and start mailcow with the override file. But it seems to be problematic after a reboot (needs to be confirmed). An easy, dirty, yet stable workaround is to stop mailcow ( docker-compose down ), remove /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data and create a new link to your remote filesystem location, for example: mv /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data_backup ln -s /mnt/volume-xy/vmail_data /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data Start mailcow afterwards.","title":"The \"new\" way"},{"location":"u_e-dovecot-vmail-volume/#the-old-way","text":"If you want to use another folder for the vmail-volume, you can create a docker-compose.override.yml file and add the following content: version: '2.1' volumes: vmail-vol-1: driver_opts: type: none device: /data/mailcow/vmail o: bind","title":"The \"old\" way"},{"location":"u_e-dovecot-vmail-volume/#moving-an-existing-vmail-folder","text":"Locate the current vmail folder by its \"Mountpoint\" attribute: docker volume inspect mailcowdockerized_vmail-vol-1 [ { \"CreatedAt\": \"2019-06-16T22:08:34+02:00\", \"Driver\": \"local\", \"Labels\": { \"com.docker.compose.project\": \"mailcowdockerized\", \"com.docker.compose.version\": \"1.23.2\", \"com.docker.compose.volume\": \"vmail-vol-1\" }, \"Mountpoint\": \"/var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data\", \"Name\": \"mailcowdockerized_vmail-vol-1\", \"Options\": null, \"Scope\": \"local\" } ] Copy the content of the Mountpoint folder to the new location (e.g. /data/mailcow/vmail ) using cp -a , rsync -a or a similar non strcuture breaking copy command Stop mailcow by executing docker-compose down from within your mailcow root folder (e.g. /opt/mailcow-dockerized ) Create the file docker-compose.override.yml , edit the device path accordingly Delete the current vmail folder: docker volume rm mailcowdockerized_vmail-vol-1 Start mailcow by executing docker-compose up -d from within your mailcow root folder (e.g. /opt/mailcow-dockerized )","title":"Moving an existing vmail folder:"},{"location":"u_e-fido2/","text":"How is UV handled in mailcow? \u00b6 The UV flag (as in \"user verification\") enforces WebAuthn to verify the user before it allows access to the key (think of a PIN). We don't enforce UV to allow logins via iOS and NFC (YubiKey). Login and key processing \u00b6 mailcow uses client-side key processing . We ask the authenticator (i.e. YubiKey) to save the registration in its memory. A user does not need to enter a username. The available credentials - if any - will be shown to the user when selecting the \"key login\" via mailcow UI login. When calling the login process, the authenticator is not given any credential IDs. This will force it to lookup credentials in its own memory. Who can use WebAuthn to login to mailcow? \u00b6 As of today, only administrators and domain administrators are able to setup WebAuthn/FIDO2.","title":"WebAuthn / FIDO2"},{"location":"u_e-fido2/#how-is-uv-handled-in-mailcow","text":"The UV flag (as in \"user verification\") enforces WebAuthn to verify the user before it allows access to the key (think of a PIN). We don't enforce UV to allow logins via iOS and NFC (YubiKey).","title":"How is UV handled in mailcow?"},{"location":"u_e-fido2/#login-and-key-processing","text":"mailcow uses client-side key processing . We ask the authenticator (i.e. YubiKey) to save the registration in its memory. A user does not need to enter a username. The available credentials - if any - will be shown to the user when selecting the \"key login\" via mailcow UI login. When calling the login process, the authenticator is not given any credential IDs. This will force it to lookup credentials in its own memory.","title":"Login and key processing"},{"location":"u_e-fido2/#who-can-use-webauthn-to-login-to-mailcow","text":"As of today, only administrators and domain administrators are able to setup WebAuthn/FIDO2.","title":"Who can use WebAuthn to login to mailcow?"},{"location":"u_e-mailcow_ui-bl_wl/","text":"To add or edit an entry to your domain wide filter table, login to your mailcow UI as (domain) administrator. Info Be aware that a user may override this setting by setting his own black- and whitelist! There is also a global filter table in /admin to configure a server-wide filter for multiple Regex maps (Todo: Screenshots).","title":"Blacklist / Whitelist"},{"location":"u_e-mailcow_ui-config/","text":"Several configuration parameters of the mailcow UI can be changed by creating a file data/web/inc/vars.local.inc.php which overrides defaults settings found in data/web/inc/vars.inc.php . The local configuration file is persistent over updates of mailcow. Try not to change values inside data/web/inc/vars.inc.php , but use them as template for the local override. mailcow UI configuration parameters can be used to... ...change the default language 1 ...change the default bootstrap theme ...set a password complexity regex ...enable DKIM private key visibility ...set a pagination trigger size ...set default mailbox attributes ...change session lifetimes ...create fixed app menus (which cannot be changed in mailcow UI) ...set a default \"To\" field for relayhost tests ...set a timeout for Docker API requests ...toggle IP anonymization To change SOGos default language, you will need to edit data/conf/sogo/sogo.conf and replace \"English\" by your preferred language. \u21a9","title":"Configuration"},{"location":"u_e-mailcow_ui-pushover/","text":"Info Pushover makes it easy to get real-time notifications on your Android, iPhone, iPad, and Desktop You can use Pushover to get a push notification on every mail you receive for each mailbox where you enabled this feature. 1. As admin open your mailbox' settings and scroll down to the Pushover settings 2. Register yourself on Pushover 3. Put your 'User Key' in the 'User/Group Key' field in your mailbox settings 4. Create an Applications to get the API Token/Key which you also need to put in your mailbox settings 5. Optional you can edit the notification title/text and define certain sender email addresses where a push notification is triggered 6. Save everything and then you can verify your credentials If everything is done you can test sending a mail and you will receive a push message on your phone","title":"Pushover"},{"location":"u_e-mailcow_ui-spamalias/","text":"These temporary email aliases are mostly used for places where we need to provide an email address but don't want future correspondence with. They are also called spam alias. To create, delete or extend a temporary email aliases you need to login to mailcow's UI as a mailbox user and navigate to the tab Temporary email aliases :","title":"Temporary email aliases"},{"location":"u_e-mailcow_ui-spamfilter/","text":"A mailbox user may adjust the spam filter and black- / whitelist settings for his mailbox individually by navigating to the Spam filter tab in the users mailcow UI. Info For global adjustments on your spam filter please check our section on Rspamd . For a domain wide black- and whitelist please check our guide on Black / Whitelist","title":"Spamfilter"},{"location":"u_e-mailcow_ui-tagging/","text":"Mailbox users can tag their mail address like in me+facebook@example.org . They can control the tag handling in the users mailcow UI panel. Available Actions \u00b6 1. Move this message to a sub folder \"facebook\" (will be created lower case if not existing) 2. Prepend the tag to the subject: \"[facebook] Subject\" Please note: Uppercase tags are converted to lowercase except for the first letter. If you want to keep the tag as it is, please apply the following diff and restart mailcow: diff --git a/data/conf/dovecot/global_sieve_after b/data/conf/dovecot/global_sieve_after index e047136e..933c4137 100644 --- a/data/conf/dovecot/global_sieve_after +++ b/data/conf/dovecot/global_sieve_after @@ -15,7 +15,7 @@ if allof ( envelope :detail :matches \"to\" \"*\", header :contains \"X-Moo-Tag\" \"YES\" ) { - set :lower :upperfirst \"tag\" \"${1}\"; + set \"tag\" \"${1}\"; if mailboxexists \"INBOX/${1}\" { fileinto \"INBOX/${1}\"; } else {","title":"Tagging"},{"location":"u_e-mailcow_ui-tagging/#available-actions","text":"1. Move this message to a sub folder \"facebook\" (will be created lower case if not existing) 2. Prepend the tag to the subject: \"[facebook] Subject\" Please note: Uppercase tags are converted to lowercase except for the first letter. If you want to keep the tag as it is, please apply the following diff and restart mailcow: diff --git a/data/conf/dovecot/global_sieve_after b/data/conf/dovecot/global_sieve_after index e047136e..933c4137 100644 --- a/data/conf/dovecot/global_sieve_after +++ b/data/conf/dovecot/global_sieve_after @@ -15,7 +15,7 @@ if allof ( envelope :detail :matches \"to\" \"*\", header :contains \"X-Moo-Tag\" \"YES\" ) { - set :lower :upperfirst \"tag\" \"${1}\"; + set \"tag\" \"${1}\"; if mailboxexists \"INBOX/${1}\" { fileinto \"INBOX/${1}\"; } else {","title":"Available Actions"},{"location":"u_e-mailcow_ui-tfa/","text":"So far three methods for Two-Factor Authentication are implemented: U2F, Yubi OTP, and TOTP For U2F to work, you need an encrypted connection to the server (HTTPS) as well as a FIDO security key. Both U2F and Yubi OTP work well with the fantastic Yubikey . While Yubi OTP needs an active internet connection and an API ID + key, U2F will work with any FIDO U2F USB key out of the box, but can only be used when mailcow is accessed over HTTPS. U2F and Yubi OTP support multiple keys per user. As the third TFA method mailcow uses TOTP: time-based one-time passwords. Those passwords can be generated with apps like \"Google Authenticator\" after initially scanning a QR code or entering the given secret manually. As administrator you are able to temporary disable a domain administrators TFA login until they successfully logged in. The key used to login will be displayed in green, while other keys remain grey. Information on how to remove 2FA can be found here . Yubi OTP \u00b6 The Yubi API ID and Key will be checked against the Yubico Cloud API. When setting up TFA you will be asked for your personal API account for this key. The API ID, API key and the first 12 characters (your YubiKeys ID in modhex) are stored in the MySQL table as secret. U2F \u00b6 Only Google Chrome (+derivatives) and Opera support U2F authentication to this day natively. Since version 67 Mozilla Firefox can handle U2F natively. ( Source ) U2F works without an internet connection. TOTP \u00b6 The best known TFA method mostly used with a smartphone.","title":"Two-Factor Authentication"},{"location":"u_e-mailcow_ui-tfa/#yubi-otp","text":"The Yubi API ID and Key will be checked against the Yubico Cloud API. When setting up TFA you will be asked for your personal API account for this key. The API ID, API key and the first 12 characters (your YubiKeys ID in modhex) are stored in the MySQL table as secret.","title":"Yubi OTP"},{"location":"u_e-mailcow_ui-tfa/#u2f","text":"Only Google Chrome (+derivatives) and Opera support U2F authentication to this day natively. Since version 67 Mozilla Firefox can handle U2F natively. ( Source ) U2F works without an internet connection.","title":"U2F"},{"location":"u_e-mailcow_ui-tfa/#totp","text":"The best known TFA method mostly used with a smartphone.","title":"TOTP"},{"location":"u_e-nginx/","text":"To create persistent (over updates) sites hosted by mailcow: dockerized, a new site configuration must be placed inside data/conf/nginx/ : nano data/conf/nginx/my_custom_site.conf A good template to begin with: server { ssl_certificate /etc/ssl/mail/cert.pem; ssl_certificate_key /etc/ssl/mail/key.pem; index index.php index.html; client_max_body_size 0; root /web; include /etc/nginx/conf.d/listen_plain.active; include /etc/nginx/conf.d/listen_ssl.active; server_name mysite.example.org; location ^~ /.well-known/acme-challenge/ { allow all; default_type \"text/plain\"; } if ($scheme = http) { return 301 https://$server_name$request_uri; } } Another example with a reverse proxy configuration: server { ssl_certificate /etc/ssl/mail/cert.pem; ssl_certificate_key /etc/ssl/mail/key.pem; index index.php index.html; client_max_body_size 0; root /web; include /etc/nginx/conf.d/listen_plain.active; include /etc/nginx/conf.d/listen_ssl.active; server_name example.domain.tld; location ^~ /.well-known/acme-challenge/ { allow all; default_type \"text/plain\"; } if ($scheme = http) { return 301 https://$host$request_uri; } location / { proxy_pass http://service:3000/; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; client_max_body_size 0; } } The filename is not important, as long as the filename carries a .conf extension. It is also possible to extend the configuration of the default file site.conf file: nano data/conf/nginx/site.my_content.custom This filename does not need to have a \".conf\" extension, but follows the pattern site.*.custom , where * is a custom name. If PHP is to be included in a custom site, please use the PHP-FPM listener on phpfpm:9002 or create a new listener in data/conf/phpfpm/php-fpm.d/pools.conf . Restart Nginx (and PHP-FPM, if a new listener was created): docker-compose restart nginx-mailcow docker-compose restart php-fpm-mailcow","title":"Custom sites"},{"location":"u_e-postfix-attachment_size/","text":"Open data/conf/postfix/main.cf and set the message_size_limit accordingly in bytes. Restart Postfix: docker-compose restart postfix-mailcow","title":"Max. message size (attachment size)"},{"location":"u_e-postfix-custom_transport/","text":"For transport maps other than those to be configured in mailcow UI, please use data/conf/postfix/custom_transport.pcre to prevent existing maps or settings from being overwritten by updates. In most cases using this file is not necessary. Please make sure mailcow UI is not able to route your desired traffic properly before using that file. The file needs valid PCRE content and can break Postfix, if configured incorrectly.","title":"Custom transport maps"},{"location":"u_e-postfix-disable_sender_verification/","text":"New guide \u00b6 Edit a mailbox and select \"Allow to send as *\". For historical reasons we kept the old and deprecated guide below: Deprecated guide (DO NOT USE ON NEWER MAILCOWS!) \u00b6 This option is not best-practice and should only be implemented when there is no other option available to achieve whatever you are trying to do. Simply create a file data/conf/postfix/check_sasl_access and enter the following content. This user must exist in your installation and needs to authenticate before sending mail. user-to-allow-everything@example.com OK Open data/conf/postfix/main.cf and find smtpd_sender_restrictions . Prepend check_sasl_access hash:/opt/postfix/conf/check_sasl_access like this: smtpd_sender_restrictions = check_sasl_access hash:/opt/postfix/conf/check_sasl_access reject_authenticated_sender_login_mismatch [...] Run postmap on check_sasl_access: docker-compose exec postfix-mailcow postmap /opt/postfix/conf/check_sasl_access Restart the Postfix container.","title":"Disable Sender Addresses Verification"},{"location":"u_e-postfix-disable_sender_verification/#new-guide","text":"Edit a mailbox and select \"Allow to send as *\". For historical reasons we kept the old and deprecated guide below:","title":"New guide"},{"location":"u_e-postfix-disable_sender_verification/#deprecated-guide-do-not-use-on-newer-mailcows","text":"This option is not best-practice and should only be implemented when there is no other option available to achieve whatever you are trying to do. Simply create a file data/conf/postfix/check_sasl_access and enter the following content. This user must exist in your installation and needs to authenticate before sending mail. user-to-allow-everything@example.com OK Open data/conf/postfix/main.cf and find smtpd_sender_restrictions . Prepend check_sasl_access hash:/opt/postfix/conf/check_sasl_access like this: smtpd_sender_restrictions = check_sasl_access hash:/opt/postfix/conf/check_sasl_access reject_authenticated_sender_login_mismatch [...] Run postmap on check_sasl_access: docker-compose exec postfix-mailcow postmap /opt/postfix/conf/check_sasl_access Restart the Postfix container.","title":"Deprecated guide (DO NOT USE ON NEWER MAILCOWS!)"},{"location":"u_e-postfix-extra_cf/","text":"Please create a new file data/conf/postfix/extra.cf for overrides or additional content to main.cf . Postfix will complain about duplicate values once after starting postfix-mailcow, this is intended. Syslog-ng was configured to hide those warnings while Postfix is running, to not spam the log files with unnecessary information every time a service is used. Restart postfix-mailcow to apply your changes: docker-compose restart postfix-mailcow","title":"Customize/Expand main.cf"},{"location":"u_e-postfix-pflogsumm/","text":"To use pflogsumm with the default logging driver, we need to query postfix-mailcow via docker logs and pipe the output to pflogsumm: docker logs --since 24h $(docker ps -qf name=postfix-mailcow) | pflogsumm The above log output is limited to the past 24 hours. It's also possible to create a daily pflogsumm report via cron. Create the file /etc/cron.d/pflogsumm with the following content: SHELL=/bin/bash 59 23 * * * root docker logs --since 24h $(docker ps -qf name=postfix-mailcow) | /usr/sbin/pflogsumm -d today | mail -s \"Postfix Report of $(date)\" postmaster@example.net Based on the last 24h postfix logs, this example sends every day at 23:59:00 a pflogsumm report to postmaster@example.net .","title":"Statistics with pflogsumm"},{"location":"u_e-postfix-postscreen_whitelist/","text":"IPs can be removed from Postscreen and therefore also from RBL checks in data/conf/postfix/custom_postscreen_whitelist.cidr . Postscreen does multiple checks to identify malicious senders. In most cases you want to whitelist an IP to exclude it from blacklist lookups. The format of the file is as follows: CIDR ACTION Where CIDR is a single IP address or IP range in CIDR notation, and action is either \"permit\" or \"reject\". Example: # Rules are evaluated in the order as specified. # Blacklist 192.168.* except 192.168.0.1. 192.168.0.1 permit 192.168.0.0/16 reject The file is reloaded on the fly, postfix restart is not required.","title":"Whitelist IP in Postscreen"},{"location":"u_e-redis/","text":"Redis is used as a key-value store for rspamd's and (some of) mailcow's settings and data. If you are unfamiliar with redis please read the introduction to redis and maybe visit this wonderful guide on how to use it. Client \u00b6 To connect to the redis cli execute: docker-compose exec redis-mailcow redis-cli Debugging \u00b6 Here are some useful commands for the redis-cli for debugging: MONITOR \u00b6 Listens for all requests received by the server in real time: # docker-compose exec redis-mailcow redis-cli 127.0.0.1:6379> monitor OK 1494077286.401963 [0 172.22.1.253:41228] \"SMEMBERS\" \"BAYES_SPAM_keys\" 1494077288.292970 [0 172.22.1.253:41229] \"SMEMBERS\" \"BAYES_SPAM_keys\" [...] KEYS \u00b6 Get all keys matching your pattern: KEYS * PING \u00b6 Test a connection: 127.0.0.1:6379> PING PONG If you want to know more, here is a cheat sheet .","title":"Redis"},{"location":"u_e-redis/#client","text":"To connect to the redis cli execute: docker-compose exec redis-mailcow redis-cli","title":"Client"},{"location":"u_e-redis/#debugging","text":"Here are some useful commands for the redis-cli for debugging:","title":"Debugging"},{"location":"u_e-redis/#monitor","text":"Listens for all requests received by the server in real time: # docker-compose exec redis-mailcow redis-cli 127.0.0.1:6379> monitor OK 1494077286.401963 [0 172.22.1.253:41228] \"SMEMBERS\" \"BAYES_SPAM_keys\" 1494077288.292970 [0 172.22.1.253:41229] \"SMEMBERS\" \"BAYES_SPAM_keys\" [...]","title":"MONITOR"},{"location":"u_e-redis/#keys","text":"Get all keys matching your pattern: KEYS *","title":"KEYS"},{"location":"u_e-redis/#ping","text":"Test a connection: 127.0.0.1:6379> PING PONG If you want to know more, here is a cheat sheet .","title":"PING"},{"location":"u_e-reeanble-weak-protocols/","text":"On February the 12th 2020 we disabled the deprecated protocols TLS 1.0 and 1.1 in Dovecot (POP3, POP3S, IMAP, IMAPS) and Postfix (SMTPS, SUBMISSION). Unauthenticated mail via SMTP on port 25/tcp does still accept >= TLS 1.0 . It is better to accept a weak encryption than none at all. How to re-enable weak protocols? Edit data/conf/postfix/extra.cf : submission_smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3 smtps_smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3 Edit data/conf/dovecot/extra.conf : ssl_min_protocol = TLSv1 Restart the affected services: docker-compose restart postfix-mailcow dovecot-mailcow Hint: You can enable TLS 1.2 in Windows 7.","title":"Re-enable TLS 1.0 and TLS 1.1"},{"location":"u_e-rspamd/","text":"Rspamd is used for AV handling, DKIM signing and SPAM handling. It's a powerful and fast filter system. For a more in-depth documentation on Rspamd please visit its own documentation . Learn Spam & Ham \u00b6 Rspamd learns mail as spam or ham when you move a message in or out of the junk folder to any mailbox besides trash. This is achieved by using the Sieve plugin \"sieve_imapsieve\" and parser scripts. Rspamd also auto-learns mail when a high or low score is detected (see https://rspamd.com/doc/configuration/statistic.html#autolearning ). We configured the plugin to keep a sane ratio between spam and ham learns. The bayes statistics are written to Redis as keys BAYES_HAM and BAYES_SPAM . Besides bayes, a local fuzzy storage is used to learn recurring patterns in text or images that indicate ham or spam. You can also use Rspamd's web UI to learn ham and / or spam or to adjust certain settings of Rspamd. Learn Spam or Ham from existing directory \u00b6 You can use a one-liner to learn mail in plain-text (uncompressed) format: # Ham for file in /my/folder/cur/* ; do docker exec -i $( docker-compose ps -q rspamd-mailcow ) rspamc learn_ham < $file ; done # Spam for file in /my/folder/.Junk/cur/* ; do docker exec -i $( docker-compose ps -q rspamd-mailcow ) rspamc learn_spam < $file ; done Consider attaching a local folder as new volume to rspamd-mailcow in docker-compose.yml and learn given files inside the container. This can be used as workaround to parse compressed data with zcat. Example: for file in /data/old_mail/.Junk/cur/* ; do rspamc learn_spam < zcat $file ; done Reset learned data (Bayes, Neural) \u00b6 You need to delete keys in Redis to reset learned data, so create a copy of your Redis database now: Backup database # It is better to stop Redis before you copy the file. cp /var/lib/docker/volumes/mailcowdockerized_redis-vol-1/_data/dump.rdb /root/ Reset Bayes data docker-compose exec redis-mailcow sh -c 'redis-cli --scan --pattern BAYES_* | xargs redis-cli del' docker-compose exec redis-mailcow sh -c 'redis-cli --scan --pattern RS* | xargs redis-cli del' Reset Neural data docker-compose exec redis-mailcow sh -c 'redis-cli --scan --pattern rn_* | xargs redis-cli del' Reset Fuzzy data # We need to enter the redis-cli first: docker-compose exec redis-mailcow redis-cli # In redis-cli: 127 .0.0.1:6379> EVAL \"for i, name in ipairs(redis.call('KEYS', ARGV[1])) do redis.call('DEL', name); end\" 0 fuzzy* Info If redis-cli complains about... (error) ERR wrong number of arguments for 'del' command ...the key pattern was not found and thus no data is available to delete - it is fine. CLI tools \u00b6 docker-compose exec rspamd-mailcow rspamc --help docker-compose exec rspamd-mailcow rspamadm --help Disable Greylisting \u00b6 Only messages with a higher score will be considered to be greylisted (soft rejected). It is bad practice to disable greylisting. You can disable greylisting server-wide by editing: {mailcow-dir}/data/conf/rspamd/local.d/greylist.conf Add the line: enabled = false ; Save the file and restart \"rspamd-mailcow\": docker-compose restart rspamd-mailcow Spam filter thresholds (global) \u00b6 Each user is able to change their spam rating individually . To define a new server-wide limit, edit data/conf/rspamd/local.d/actions.conf : reject = 15 ; add_header = 8 ; greylist = 7 ; Save the file and restart \"rspamd-mailcow\": docker-compose restart rspamd-mailcow Existing settings of users will not be overwritten! To reset custom defined thresholds, run: source mailcow.conf docker-compose exec mysql-mailcow mysql -umailcow -p$DBPASS mailcow -e \"delete from filterconf where option = 'highspamlevel' or option = 'lowspamlevel';\" # or: # docker-compose exec mysql-mailcow mysql -umailcow -p$DBPASS mailcow -e \"delete from filterconf where option = 'highspamlevel' or option = 'lowspamlevel' and object = 'only-this-mailbox@example.org';\" Custom reject messages \u00b6 The default spam reject message can be changed by adding a new file data/conf/rspamd/override.d/worker-proxy.custom.inc with the following content: reject_message = \"My custom reject message\"; Save the file and restart Rspamd: docker-compose restart rspamd-mailcow . While the above works for rejected mails with a high spam score, prefilter reject actions will ignore this setting. For these maps, the multimap module in Rspamd needs to be adjusted: Find prefilet reject symbol for which you want change message, to do it run: grep -R \"SYMBOL_YOU_WANT_TO_ADJUST\" /opt/mailcow-dockerized/data/conf/rspamd/ Add your custom message as new line: GLOBAL_RCPT_BL { type = \"rcpt\"; map = \"${LOCAL_CONFDIR}/custom/global_rcpt_blacklist.map\"; regexp = true; prefilter = true; action = \"reject\"; message = \"Sending mail to this recipient is prohibited by postmaster@your.domain\"; } Save the file and restart Rspamd: docker-compose restart rspamd-mailcow . Whitelist specific ClamAV signatures \u00b6 You may find that legitimate (clean) mail is being blocked by ClamAV (Rspamd will flag the mail with VIRUS_FOUND ). For instance, interactive PDF form attachments are blocked by default because the embedded Javascript code may be used for nefarious purposes. Confirm by looking at the clamd logs, e.g.: docker-compose logs clamd-mailcow | grep \"FOUND\" This line confirms that such was identified: clamd-mailcow_1 | Sat Sep 28 07:43:24 2019 -> instream(local): PUA.Pdf.Trojan.EmbeddedJavaScript-1(e887d2ac324ce90750768b86b63d0749:363325) FOUND To whitelist this particular signature (and enable sending this type of file attached), add it to the ClamAV signature whitelist file: echo 'PUA.Pdf.Trojan.EmbeddedJavaScript-1' >> data/conf/clamav/whitelist.ign2 Then restart the clamd-mailcow service container in the mailcow UI or using docker-compose: docker-compose restart clamd-mailcow Cleanup cached ClamAV results in Redis: # docker-compose exec redis-mailcow /bin/sh /data # redis-cli KEYS rs_cl* | xargs redis-cli DEL /data # exit Discard instead of reject \u00b6 If you want to silently drop a message, create or edit the file data/conf/rspamd/override.d/worker-proxy.custom.inc and add the following content: discard_on_reject = true; Restart Rspamd: docker-compose restart rspamd-mailcow Wipe all ratelimit keys \u00b6 If you don't want to use the UI and instead wipe all keys in the Redis database, you can use redis-cli for that task: docker-compose exec redis-mailcow sh # Unlink (available in Redis >=4.) will delete in the backgronud redis-cli --scan --pattern RL* | xargs redis-cli unlink Restart Rspamd: docker-compose exec redis-mailcow sh Trigger a resend of quarantine notifications \u00b6 Should be used for debugging only! docker-compose exec dovecot-mailcow bash mysql -umailcow -p$DBPASS mailcow -e \"update quarantine set notified = 0;\" redis-cli -h redis DEL Q_LAST_NOTIFIED quarantine_notify.py","title":"Rspamd"},{"location":"u_e-rspamd/#learn-spam-ham","text":"Rspamd learns mail as spam or ham when you move a message in or out of the junk folder to any mailbox besides trash. This is achieved by using the Sieve plugin \"sieve_imapsieve\" and parser scripts. Rspamd also auto-learns mail when a high or low score is detected (see https://rspamd.com/doc/configuration/statistic.html#autolearning ). We configured the plugin to keep a sane ratio between spam and ham learns. The bayes statistics are written to Redis as keys BAYES_HAM and BAYES_SPAM . Besides bayes, a local fuzzy storage is used to learn recurring patterns in text or images that indicate ham or spam. You can also use Rspamd's web UI to learn ham and / or spam or to adjust certain settings of Rspamd.","title":"Learn Spam & Ham"},{"location":"u_e-rspamd/#learn-spam-or-ham-from-existing-directory","text":"You can use a one-liner to learn mail in plain-text (uncompressed) format: # Ham for file in /my/folder/cur/* ; do docker exec -i $( docker-compose ps -q rspamd-mailcow ) rspamc learn_ham < $file ; done # Spam for file in /my/folder/.Junk/cur/* ; do docker exec -i $( docker-compose ps -q rspamd-mailcow ) rspamc learn_spam < $file ; done Consider attaching a local folder as new volume to rspamd-mailcow in docker-compose.yml and learn given files inside the container. This can be used as workaround to parse compressed data with zcat. Example: for file in /data/old_mail/.Junk/cur/* ; do rspamc learn_spam < zcat $file ; done","title":"Learn Spam or Ham from existing directory"},{"location":"u_e-rspamd/#reset-learned-data-bayes-neural","text":"You need to delete keys in Redis to reset learned data, so create a copy of your Redis database now: Backup database # It is better to stop Redis before you copy the file. cp /var/lib/docker/volumes/mailcowdockerized_redis-vol-1/_data/dump.rdb /root/ Reset Bayes data docker-compose exec redis-mailcow sh -c 'redis-cli --scan --pattern BAYES_* | xargs redis-cli del' docker-compose exec redis-mailcow sh -c 'redis-cli --scan --pattern RS* | xargs redis-cli del' Reset Neural data docker-compose exec redis-mailcow sh -c 'redis-cli --scan --pattern rn_* | xargs redis-cli del' Reset Fuzzy data # We need to enter the redis-cli first: docker-compose exec redis-mailcow redis-cli # In redis-cli: 127 .0.0.1:6379> EVAL \"for i, name in ipairs(redis.call('KEYS', ARGV[1])) do redis.call('DEL', name); end\" 0 fuzzy* Info If redis-cli complains about... (error) ERR wrong number of arguments for 'del' command ...the key pattern was not found and thus no data is available to delete - it is fine.","title":"Reset learned data (Bayes, Neural)"},{"location":"u_e-rspamd/#cli-tools","text":"docker-compose exec rspamd-mailcow rspamc --help docker-compose exec rspamd-mailcow rspamadm --help","title":"CLI tools"},{"location":"u_e-rspamd/#disable-greylisting","text":"Only messages with a higher score will be considered to be greylisted (soft rejected). It is bad practice to disable greylisting. You can disable greylisting server-wide by editing: {mailcow-dir}/data/conf/rspamd/local.d/greylist.conf Add the line: enabled = false ; Save the file and restart \"rspamd-mailcow\": docker-compose restart rspamd-mailcow","title":"Disable Greylisting"},{"location":"u_e-rspamd/#spam-filter-thresholds-global","text":"Each user is able to change their spam rating individually . To define a new server-wide limit, edit data/conf/rspamd/local.d/actions.conf : reject = 15 ; add_header = 8 ; greylist = 7 ; Save the file and restart \"rspamd-mailcow\": docker-compose restart rspamd-mailcow Existing settings of users will not be overwritten! To reset custom defined thresholds, run: source mailcow.conf docker-compose exec mysql-mailcow mysql -umailcow -p$DBPASS mailcow -e \"delete from filterconf where option = 'highspamlevel' or option = 'lowspamlevel';\" # or: # docker-compose exec mysql-mailcow mysql -umailcow -p$DBPASS mailcow -e \"delete from filterconf where option = 'highspamlevel' or option = 'lowspamlevel' and object = 'only-this-mailbox@example.org';\"","title":"Spam filter thresholds (global)"},{"location":"u_e-rspamd/#custom-reject-messages","text":"The default spam reject message can be changed by adding a new file data/conf/rspamd/override.d/worker-proxy.custom.inc with the following content: reject_message = \"My custom reject message\"; Save the file and restart Rspamd: docker-compose restart rspamd-mailcow . While the above works for rejected mails with a high spam score, prefilter reject actions will ignore this setting. For these maps, the multimap module in Rspamd needs to be adjusted: Find prefilet reject symbol for which you want change message, to do it run: grep -R \"SYMBOL_YOU_WANT_TO_ADJUST\" /opt/mailcow-dockerized/data/conf/rspamd/ Add your custom message as new line: GLOBAL_RCPT_BL { type = \"rcpt\"; map = \"${LOCAL_CONFDIR}/custom/global_rcpt_blacklist.map\"; regexp = true; prefilter = true; action = \"reject\"; message = \"Sending mail to this recipient is prohibited by postmaster@your.domain\"; } Save the file and restart Rspamd: docker-compose restart rspamd-mailcow .","title":"Custom reject messages"},{"location":"u_e-rspamd/#whitelist-specific-clamav-signatures","text":"You may find that legitimate (clean) mail is being blocked by ClamAV (Rspamd will flag the mail with VIRUS_FOUND ). For instance, interactive PDF form attachments are blocked by default because the embedded Javascript code may be used for nefarious purposes. Confirm by looking at the clamd logs, e.g.: docker-compose logs clamd-mailcow | grep \"FOUND\" This line confirms that such was identified: clamd-mailcow_1 | Sat Sep 28 07:43:24 2019 -> instream(local): PUA.Pdf.Trojan.EmbeddedJavaScript-1(e887d2ac324ce90750768b86b63d0749:363325) FOUND To whitelist this particular signature (and enable sending this type of file attached), add it to the ClamAV signature whitelist file: echo 'PUA.Pdf.Trojan.EmbeddedJavaScript-1' >> data/conf/clamav/whitelist.ign2 Then restart the clamd-mailcow service container in the mailcow UI or using docker-compose: docker-compose restart clamd-mailcow Cleanup cached ClamAV results in Redis: # docker-compose exec redis-mailcow /bin/sh /data # redis-cli KEYS rs_cl* | xargs redis-cli DEL /data # exit","title":"Whitelist specific ClamAV signatures"},{"location":"u_e-rspamd/#discard-instead-of-reject","text":"If you want to silently drop a message, create or edit the file data/conf/rspamd/override.d/worker-proxy.custom.inc and add the following content: discard_on_reject = true; Restart Rspamd: docker-compose restart rspamd-mailcow","title":"Discard instead of reject"},{"location":"u_e-rspamd/#wipe-all-ratelimit-keys","text":"If you don't want to use the UI and instead wipe all keys in the Redis database, you can use redis-cli for that task: docker-compose exec redis-mailcow sh # Unlink (available in Redis >=4.) will delete in the backgronud redis-cli --scan --pattern RL* | xargs redis-cli unlink Restart Rspamd: docker-compose exec redis-mailcow sh","title":"Wipe all ratelimit keys"},{"location":"u_e-rspamd/#trigger-a-resend-of-quarantine-notifications","text":"Should be used for debugging only! docker-compose exec dovecot-mailcow bash mysql -umailcow -p$DBPASS mailcow -e \"update quarantine set notified = 0;\" redis-cli -h redis DEL Q_LAST_NOTIFIED quarantine_notify.py","title":"Trigger a resend of quarantine notifications"},{"location":"u_e-sogo/","text":"SOGo is used for accessing your mails via a webbrowser, adding and sharing your contacts or calendars. For a more in-depth documentation on SOGo please visit its own documentation . Change theme \u00b6 mailcow builds after 28 January 2021 can change SOGo's theme by editing data/conf/sogo/custom-theme.js . Please check the AngularJS Material intro and documentation as well as the material style guideline to learn how this works. After you updated said file you need to restart SOGo and Memcached containers by executing docker-compose restart memcached-mailcow sogo-mailcow . Reset to SOGo default theme \u00b6 Checkout data/conf/sogo/custom-theme.js by executing git fetch ; git checkout origin/master data/conf/sogo/custom-theme.js data/conf/sogo/custom-theme.js Find in data/conf/sogo/custom-theme.js : // Apply new palettes to the default theme, remap some of the hues $mdThemingProvider.theme('default') .primaryPalette('green-cow', { 'default': '400', // background color of top toolbars 'hue-1': '400', 'hue-2': '600', // background color of sidebar toolbar 'hue-3': 'A700' }) .accentPalette('green', { 'default': '600', // background color of fab buttons 'hue-1': '300', // background color of center list toolbar 'hue-2': '300', 'hue-3': 'A700' }) .backgroundPalette('frost-grey'); and replace with: $mdThemingProvider.theme('default'); Change favicon \u00b6 mailcow builds after 31 January 2021 can change SOGo's favicon by replacing data/conf/sogo/custom-favicon.ico for SOGo and data/web/favicon.png for mailcow UI. Note : You can use .png favicons for SOGo by renaming them to custom-favicon.ico . For both SOGo and mailcow UI favicons you need use one of the standard dimensions: 16x16, 32x32, 64x64, 128x128 and 256x256. After you replaced said file you need to restart SOGo and Memcached containers by executing docker-compose restart memcached-mailcow sogo-mailcow . Change logo \u00b6 mailcow builds after 21 December 2018 can change SOGo's logo by replacing or creating (if missing) data/conf/sogo/sogo-full.svg . After you replaced said file you need to restart SOGo and Memcached containers by executing docker-compose restart memcached-mailcow sogo-mailcow . Connect domains \u00b6 Domains are usually isolated from eachother. You can change that by modifying data/conf/sogo/sogo.conf : Search... // SOGoDomainsVisibility = ( // (domain1.tld, domain5.tld), // (domain3.tld, domain2.tld) // ); ...and replace it by - for example: SOGoDomainsVisibility = ( (example.org, example.com, example.net) ); Restart SOGo: docker-compose restart sogo-mailcow Disable password changing \u00b6 Edit data/conf/sogo/sogo.conf and change SOGoPasswordChangeEnabled to NO . Please do not add a new parameter. Run docker-compose restart memcached-mailcow sogo-mailcow to activate the changes.","title":"SOGo"},{"location":"u_e-sogo/#change-theme","text":"mailcow builds after 28 January 2021 can change SOGo's theme by editing data/conf/sogo/custom-theme.js . Please check the AngularJS Material intro and documentation as well as the material style guideline to learn how this works. After you updated said file you need to restart SOGo and Memcached containers by executing docker-compose restart memcached-mailcow sogo-mailcow .","title":"Change theme"},{"location":"u_e-sogo/#reset-to-sogo-default-theme","text":"Checkout data/conf/sogo/custom-theme.js by executing git fetch ; git checkout origin/master data/conf/sogo/custom-theme.js data/conf/sogo/custom-theme.js Find in data/conf/sogo/custom-theme.js : // Apply new palettes to the default theme, remap some of the hues $mdThemingProvider.theme('default') .primaryPalette('green-cow', { 'default': '400', // background color of top toolbars 'hue-1': '400', 'hue-2': '600', // background color of sidebar toolbar 'hue-3': 'A700' }) .accentPalette('green', { 'default': '600', // background color of fab buttons 'hue-1': '300', // background color of center list toolbar 'hue-2': '300', 'hue-3': 'A700' }) .backgroundPalette('frost-grey'); and replace with: $mdThemingProvider.theme('default');","title":"Reset to SOGo default theme"},{"location":"u_e-sogo/#change-favicon","text":"mailcow builds after 31 January 2021 can change SOGo's favicon by replacing data/conf/sogo/custom-favicon.ico for SOGo and data/web/favicon.png for mailcow UI. Note : You can use .png favicons for SOGo by renaming them to custom-favicon.ico . For both SOGo and mailcow UI favicons you need use one of the standard dimensions: 16x16, 32x32, 64x64, 128x128 and 256x256. After you replaced said file you need to restart SOGo and Memcached containers by executing docker-compose restart memcached-mailcow sogo-mailcow .","title":"Change favicon"},{"location":"u_e-sogo/#change-logo","text":"mailcow builds after 21 December 2018 can change SOGo's logo by replacing or creating (if missing) data/conf/sogo/sogo-full.svg . After you replaced said file you need to restart SOGo and Memcached containers by executing docker-compose restart memcached-mailcow sogo-mailcow .","title":"Change logo"},{"location":"u_e-sogo/#connect-domains","text":"Domains are usually isolated from eachother. You can change that by modifying data/conf/sogo/sogo.conf : Search... // SOGoDomainsVisibility = ( // (domain1.tld, domain5.tld), // (domain3.tld, domain2.tld) // ); ...and replace it by - for example: SOGoDomainsVisibility = ( (example.org, example.com, example.net) ); Restart SOGo: docker-compose restart sogo-mailcow","title":"Connect domains"},{"location":"u_e-sogo/#disable-password-changing","text":"Edit data/conf/sogo/sogo.conf and change SOGoPasswordChangeEnabled to NO . Please do not add a new parameter. Run docker-compose restart memcached-mailcow sogo-mailcow to activate the changes.","title":"Disable password changing"},{"location":"u_e-unbound-fwd/","text":"If you want or have to use an external DNS service, you can either set a forwarder in Unbound or copy an override file to define external DNS servers: !!! warning Please do not use a public resolver like we did in the example above. Many - if not all - blacklist lookups will fail with public resolvers. Important : Only DNSSEC validating DNS services will work. Method A, Unbound \u00b6 Edit data/conf/unbound/unbound.conf and append the following parameters: forward-zone: name: \".\" forward-addr: 8.8.8.8 # DO NOT USE PUBLIC DNS SERVERS - JUST AN EXAMPLE forward-addr: 8.8.4.4 # DO NOT USE PUBLIC DNS SERVERS - JUST AN EXAMPLE Restart Unbound: docker-compose restart unbound-mailcow Method B, Override file \u00b6 cd /opt/mailcow-dockerized cp helper-scripts/docker-compose.override.yml.d/EXTERNAL_DNS/docker-compose.override.yml . Edit docker-compose.override.yml and adjust the IP. Run docker-compose down ; docker-compose up -d .","title":"Using an external DNS service"},{"location":"u_e-unbound-fwd/#method-a-unbound","text":"Edit data/conf/unbound/unbound.conf and append the following parameters: forward-zone: name: \".\" forward-addr: 8.8.8.8 # DO NOT USE PUBLIC DNS SERVERS - JUST AN EXAMPLE forward-addr: 8.8.4.4 # DO NOT USE PUBLIC DNS SERVERS - JUST AN EXAMPLE Restart Unbound: docker-compose restart unbound-mailcow","title":"Method A, Unbound"},{"location":"u_e-unbound-fwd/#method-b-override-file","text":"cd /opt/mailcow-dockerized cp helper-scripts/docker-compose.override.yml.d/EXTERNAL_DNS/docker-compose.override.yml . Edit docker-compose.override.yml and adjust the IP. Run docker-compose down ; docker-compose up -d .","title":"Method B, Override file"},{"location":"u_e-webmail-site/","text":"IMPORTANT : This guide only applies to non SNI enabled configurations. The certificate path needs to be adjusted if SNI is enabled. Something like ssl_certificate,key /etc/ssl/mail/webmail.example.org/cert.pem,key.pem; will do. But : The certificate should be acquired first and only after the certificate exists a site config should be created. Nginx will fail to start if it cannot find the certificate and key. To create a subdomain webmail.example.org and redirect it to SOGo, you need to create a new Nginx site. Take care of \"CHANGE_TO_MAILCOW_HOSTNAME\"! nano data/conf/nginx/webmail.conf server { ssl_certificate /etc/ssl/mail/cert.pem; ssl_certificate_key /etc/ssl/mail/key.pem; index index.php index.html; client_max_body_size 0; root /web; include /etc/nginx/conf.d/listen_plain.active; include /etc/nginx/conf.d/listen_ssl.active; server_name webmail.example.org; location ^~ /.well-known/acme-challenge/ { allow all; default_type \"text/plain\"; } location / { return 301 https://CHANGE_TO_MAILCOW_HOSTNAME/SOGo; } } Save and restart Nginx: docker-compose restart nginx-mailcow . Now open mailcow.conf and find ADDITIONAL_SAN . Add webmail.example.org to this array, don't use quotes! ADDITIONAL_SAN=webmail.example.org Run docker-compose up -d . See \"acme-mailcow\" and \"nginx-mailcow\" logs if anything fails.","title":"Create subdomain webmail.example.org"},{"location":"u_e-why_unbound/","text":"For DNS blacklist lookups and DNSSEC. Most systems use either a public or a local caching DNS resolver. That's a very bad idea when it comes to filter spam using DNS-based black hole lists (DNSBL) or similar technics. Most if not all providers apply a rate limit based on the DNS resolver that is used to query their service. Using a public resolver like Googles 4x8, OpenDNS or any other shared DNS resolver like your ISPs will hit that limit very soon.","title":"Why unbound?"},{"location":"u_e-xmpp-enable/","text":"XMPP is provided by ejabberd, which describes itself as robust, scalable and extensible XMPP Server. So first of all, thanks to ejabberd and its contributers! Enable XMPP in mailcow \u00b6 To enable XMPP for a domain, you need to edit the given domain in mailcow UI: The chosen prefix will be used to derive your XMPP login. A prefix xmpp_prefix for the mailbox user cowboy@develcow.de would equal to the JID cowboy@xmpp_prefix.develcow.de . Info The login passwords for mail and XMPP are the same. XMPP users are authenticated against mailcow. Before enabling XMPP for a domain, you should create two CNAME records in DNS: # CNAMES # Name Type Value xmpp_prefix IN CNAME mail.example.org. (your ${MAILCOW_HOSTNAME}) *.xmpp_prefix IN CNAME mail.example.org. (your ${MAILCOW_HOSTNAME}) These two CNAMEs are essential for acquiring a certificate. Please do not add \"xmpp_prefix.domain.tld\" as name to ADDITIONAL_SAN . Make sure your CNAMEs are correct. Enable XMPP for your domain now. If you enabled XMPP first and then added your DNS records there is no need to worry. You will just need to wait for ejabberd to automatically acquire the certificates or simply restart ejabberd-mailcow to trigger the process immediately: docker-compose restart ejabberd-mailcow . Once ejabberd is enabled, you may want to re-run the DNS check in the mailcow UI where you will find two more SRV records: # SRV records # Name Type Value _xmpp-client._tcp.xmpp_prefix IN SRV 10 1 5222 mail.example.org. (your ${MAILCOW_HOSTNAME}) _xmpp-server._tcp.xmpp_prefix IN SRV 10 1 5269 mail.example.org. (your ${MAILCOW_HOSTNAME}) There is no need to restart ejabberd, add these SRV records whenever you like. These records are crucial for autoconfiguration of XMPP clients and server-to-server connections. ACL \u00b6 A domain administrator can be given the right to toggle XMPP access for domains and mailboxes, promoting users to XMPP administrators (WIP) and to change the prefix: Verify certificates \u00b6 Once everything is setup, make sure ejabberd was able to acquire certificates: If you see a message similar to... ejabberd-mailcow_1 | 2021-02-13 14:40:19.507956+01:00 [error] Failed to request certificate for im.example.org, pubsub.im.example.org and 3 more hosts: Challenge failed for domain conference.im.example.org: ACME server reported: DNS problem: NXDOMAIN looking up A for conference.im.example.org - check that a DNS record exists for this domain (error type: dns) ...you may need to recheck your DNS configuration or restart ejabberd-mailcow to restart the process in case of slow DNS propagation. Opening https://xmpp_prefix.domain.tld:5443/upload should point you to a 404 page with a valid certificate. Why can't we use no prefix? \u00b6 It does not matter which server name we point our SRV to, Jabber will always rely on the domain given in a JID. We would need to acquire a certificate for the SLD domain.tld , which hardly anyone wants to point to its mail system. We are sorry for this circumstance. As soon as we implemented Servercows DNS API, this may be reconsidered. My reverse proxy does not work anymore \u00b6 If your reverse proxy is configured to point to a site like webmail.domain.tld which mailcow is not aware of (as in MAILCOW_HOSTNAME does not match webmail.domain.tld ), you may now be redirected to the default ejabberd Nginx site. That's because mailcow does not know it should respond to webmail.domain.tld with mailcow UI. Method 1 \u00b6 A more simple approach is defining ADDITIONAL_SERVER_NAMES in mailcow.conf : ADDITIONAL_SERVER_NAMES=webmail.domain.tld Run docker-compose up -d to apply. Method 2 \u00b6 In your reverse proxy configuration, make sure you set a \"Host\" header that mailcow actually services, similar to this (Nginx example): proxy_set_header Host MAILCOW_HOSTNAME; # Instead of proxy_set_header Host $http_host; Now you can use whatever name you like, as long mailcow receives a known \"Host\" header.","title":"Enable XMPP"},{"location":"u_e-xmpp-enable/#enable-xmpp-in-mailcow","text":"To enable XMPP for a domain, you need to edit the given domain in mailcow UI: The chosen prefix will be used to derive your XMPP login. A prefix xmpp_prefix for the mailbox user cowboy@develcow.de would equal to the JID cowboy@xmpp_prefix.develcow.de . Info The login passwords for mail and XMPP are the same. XMPP users are authenticated against mailcow. Before enabling XMPP for a domain, you should create two CNAME records in DNS: # CNAMES # Name Type Value xmpp_prefix IN CNAME mail.example.org. (your ${MAILCOW_HOSTNAME}) *.xmpp_prefix IN CNAME mail.example.org. (your ${MAILCOW_HOSTNAME}) These two CNAMEs are essential for acquiring a certificate. Please do not add \"xmpp_prefix.domain.tld\" as name to ADDITIONAL_SAN . Make sure your CNAMEs are correct. Enable XMPP for your domain now. If you enabled XMPP first and then added your DNS records there is no need to worry. You will just need to wait for ejabberd to automatically acquire the certificates or simply restart ejabberd-mailcow to trigger the process immediately: docker-compose restart ejabberd-mailcow . Once ejabberd is enabled, you may want to re-run the DNS check in the mailcow UI where you will find two more SRV records: # SRV records # Name Type Value _xmpp-client._tcp.xmpp_prefix IN SRV 10 1 5222 mail.example.org. (your ${MAILCOW_HOSTNAME}) _xmpp-server._tcp.xmpp_prefix IN SRV 10 1 5269 mail.example.org. (your ${MAILCOW_HOSTNAME}) There is no need to restart ejabberd, add these SRV records whenever you like. These records are crucial for autoconfiguration of XMPP clients and server-to-server connections.","title":"Enable XMPP in mailcow"},{"location":"u_e-xmpp-enable/#acl","text":"A domain administrator can be given the right to toggle XMPP access for domains and mailboxes, promoting users to XMPP administrators (WIP) and to change the prefix:","title":"ACL"},{"location":"u_e-xmpp-enable/#verify-certificates","text":"Once everything is setup, make sure ejabberd was able to acquire certificates: If you see a message similar to... ejabberd-mailcow_1 | 2021-02-13 14:40:19.507956+01:00 [error] Failed to request certificate for im.example.org, pubsub.im.example.org and 3 more hosts: Challenge failed for domain conference.im.example.org: ACME server reported: DNS problem: NXDOMAIN looking up A for conference.im.example.org - check that a DNS record exists for this domain (error type: dns) ...you may need to recheck your DNS configuration or restart ejabberd-mailcow to restart the process in case of slow DNS propagation. Opening https://xmpp_prefix.domain.tld:5443/upload should point you to a 404 page with a valid certificate.","title":"Verify certificates"},{"location":"u_e-xmpp-enable/#why-cant-we-use-no-prefix","text":"It does not matter which server name we point our SRV to, Jabber will always rely on the domain given in a JID. We would need to acquire a certificate for the SLD domain.tld , which hardly anyone wants to point to its mail system. We are sorry for this circumstance. As soon as we implemented Servercows DNS API, this may be reconsidered.","title":"Why can't we use no prefix?"},{"location":"u_e-xmpp-enable/#my-reverse-proxy-does-not-work-anymore","text":"If your reverse proxy is configured to point to a site like webmail.domain.tld which mailcow is not aware of (as in MAILCOW_HOSTNAME does not match webmail.domain.tld ), you may now be redirected to the default ejabberd Nginx site. That's because mailcow does not know it should respond to webmail.domain.tld with mailcow UI.","title":"My reverse proxy does not work anymore"},{"location":"u_e-xmpp-enable/#method-1","text":"A more simple approach is defining ADDITIONAL_SERVER_NAMES in mailcow.conf : ADDITIONAL_SERVER_NAMES=webmail.domain.tld Run docker-compose up -d to apply.","title":"Method 1"},{"location":"u_e-xmpp-enable/#method-2","text":"In your reverse proxy configuration, make sure you set a \"Host\" header that mailcow actually services, similar to this (Nginx example): proxy_set_header Host MAILCOW_HOSTNAME; # Instead of proxy_set_header Host $http_host; Now you can use whatever name you like, as long mailcow receives a known \"Host\" header.","title":"Method 2"},{"location":"u_e-xmpp-faq/","text":"FAQ \u00b6 Please find the most frequently asked questions with their corresponding configuration in data/conf/ejabberd/ejabberd.yml (if any). I do not want to run ejabberd, is there a SKIP_XMPP variable? No, there is not. But you don't need one either. The xmppd behaves the same way as SOGo or Solr do when disabled. A shell will be idling and ejabberd will not be started. As soon as a domain is enabled for XMPP, the container will be restarted and ejabberd bootstrapped. ejabberd is very light on resources, you may want to give it a try. Are messages stored on the server? Not by default. The default setting is to disable the message archive via mod_mam but allow users to enable the function if they want to: mod_mam: clear_archive_on_room_destroy: true default: never compress_xml: true request_activates_archiving: true Are uploaded files stored on the server? Yes, uploaded files are stored in the volume xmpp-uploads-vol-1 . The retention policy saves them for 30 days: mod_http_upload_quota: max_days: 30 Are messages stored when a JID is offline? Yes, up to 1000 messages are stored for \"normal\" users and administrators: shaper_rules: max_user_offline_messages: 1000: admin 1000: all Are messages written in group chats stored? No, messages are not stored: mod_muc: default_room_options: mam: false Are group chats persistent when the last participant leaves? No, they will vanish: mod_muc: default_room_options: persistent: false How many client sessions can be open at the same time? 10 sessions are allowed per session. shaper_rules: max_user_sessions: 10","title":"FAQ"},{"location":"u_e-xmpp-faq/#faq","text":"Please find the most frequently asked questions with their corresponding configuration in data/conf/ejabberd/ejabberd.yml (if any). I do not want to run ejabberd, is there a SKIP_XMPP variable? No, there is not. But you don't need one either. The xmppd behaves the same way as SOGo or Solr do when disabled. A shell will be idling and ejabberd will not be started. As soon as a domain is enabled for XMPP, the container will be restarted and ejabberd bootstrapped. ejabberd is very light on resources, you may want to give it a try. Are messages stored on the server? Not by default. The default setting is to disable the message archive via mod_mam but allow users to enable the function if they want to: mod_mam: clear_archive_on_room_destroy: true default: never compress_xml: true request_activates_archiving: true Are uploaded files stored on the server? Yes, uploaded files are stored in the volume xmpp-uploads-vol-1 . The retention policy saves them for 30 days: mod_http_upload_quota: max_days: 30 Are messages stored when a JID is offline? Yes, up to 1000 messages are stored for \"normal\" users and administrators: shaper_rules: max_user_offline_messages: 1000: admin 1000: all Are messages written in group chats stored? No, messages are not stored: mod_muc: default_room_options: mam: false Are group chats persistent when the last participant leaves? No, they will vanish: mod_muc: default_room_options: persistent: false How many client sessions can be open at the same time? 10 sessions are allowed per session. shaper_rules: max_user_sessions: 10","title":"FAQ"},{"location":"client/client-android/","text":"Open the Email app. If this is your first email account, tap Add Account ; if not, tap More and Settings and then Add account . Select Microsoft Exchange ActiveSync . Enter your email address ( ) and password. Tap Sign in .","title":"Android"},{"location":"client/client-apple/","text":"Method 1 via Mobileconfig \u00b6 Email, contacts and calendars can be configured automatically on Apple devices by installing a profile. To download a profile you must login to the mailcow UI first. Method 1.1: IMAP, SMTP and Cal/CardDAV \u00b6 This method configures IMAP, CardDAV and CalDAV. Download and open the file from https://${MAILCOW_HOSTNAME}/mobileconfig.php mailcow.mobileconfig . Enter the unlock code (iPhone) or computer password (Mac). Enter your email password three times when prompted. Method 1.2: IMAP, SMTP (no DAV) \u00b6 This method configures IMAP and SMTP only. Download and open the file from https://${MAILCOW_HOSTNAME}/mobileconfig.php?only_email mailcow.mobileconfig . Enter the unlock code (iPhone) or computer password (Mac). Enter your email password when prompted. Method 2 (Exchange ActiveSync emulation) \u00b6 On iOS, Exchange ActiveSync is also supported as an alternative to the procedure above. It has the advantage of supporting push email (i.e. you are immediately notified of incoming messages), but has some limitations, e.g. it does not support more than three email addresses per contact in your address book. Follow the steps below if you decide to use Exchange instead. Open the Settings app, tap Mail , tap Accounts , tap Add Acccount , select Exchange . Enter your email address ( ) and tap Next . Enter your password, tap Next again. Finally, tap Save .","title":"Apple macOS / iOS"},{"location":"client/client-apple/#method-1-via-mobileconfig","text":"Email, contacts and calendars can be configured automatically on Apple devices by installing a profile. To download a profile you must login to the mailcow UI first.","title":"Method 1 via Mobileconfig"},{"location":"client/client-apple/#method-11-imap-smtp-and-calcarddav","text":"This method configures IMAP, CardDAV and CalDAV. Download and open the file from https://${MAILCOW_HOSTNAME}/mobileconfig.php mailcow.mobileconfig . Enter the unlock code (iPhone) or computer password (Mac). Enter your email password three times when prompted.","title":"Method 1.1: IMAP, SMTP and Cal/CardDAV"},{"location":"client/client-apple/#method-12-imap-smtp-no-dav","text":"This method configures IMAP and SMTP only. Download and open the file from https://${MAILCOW_HOSTNAME}/mobileconfig.php?only_email mailcow.mobileconfig . Enter the unlock code (iPhone) or computer password (Mac). Enter your email password when prompted.","title":"Method 1.2: IMAP, SMTP (no DAV)"},{"location":"client/client-apple/#method-2-exchange-activesync-emulation","text":"On iOS, Exchange ActiveSync is also supported as an alternative to the procedure above. It has the advantage of supporting push email (i.e. you are immediately notified of incoming messages), but has some limitations, e.g. it does not support more than three email addresses per contact in your address book. Follow the steps below if you decide to use Exchange instead. Open the Settings app, tap Mail , tap Accounts , tap Add Acccount , select Exchange . Enter your email address ( ) and tap Next . Enter your password, tap Next again. Finally, tap Save .","title":"Method 2 (Exchange ActiveSync emulation)"},{"location":"client/client-emclient/","text":"Launch eM Client. If this is the first time you launched eM Client, it asks you to set up your account. Proceed to step 4. Go to Menu at the top, select Tools and Accounts . Enter your email address ( ) and click Start Now . Enter your password and click Continue . Enter your name ( ) and click Next . Click Finish .","title":"eM Client"},{"location":"client/client-gajim_xmpp_client/","text":"WIP \u00b6","title":"Gajim XMPP client"},{"location":"client/client-gajim_xmpp_client/#wip","text":"","title":"WIP"},{"location":"client/client-kontact/","text":"Launch Kontact. If this is the first time you launched Kontact or KMail, it asks you to set up your account. Proceed to step 4. Go to Mail in the sidebar. Go to the Tools menu and select Account Wizard . Enter your name ( ) , email address ( ) and your password. Click Next . Click Create Account . If prompted, re-enter your password and click OK . Close the window by clicking Finish . Go to Calendar in the sidebar. Go to the Settings menu and select Configure KOrganizer . Go to the Calendars tab and click the Add button. Choose DAV groupware resource and click OK . Enter your email address ( ) and your password. Click Next . Select ScalableOGo from the dropdown menu and click Next . Enter your mailcow hostname into the Host field and click Next . Click Test Connection and then Finish . Finally, click OK twice. Once you have set up Kontact, you can also use KMail, KOrganizer and KAddressBook individually.","title":"KDE Kontact"},{"location":"client/client-manual/","text":"These instructions are valid for unchanged port bindings only! Email \u00b6 Service Encryption Host Port IMAP STARTTLS mailcow hostname 143 IMAPS SSL mailcow hostname 993 POP3 STARTTLS mailcow hostname 110 POP3S SSL mailcow hostname 995 SMTP STARTTLS mailcow hostname 587 SMTPS SSL mailcow hostname 465 Please use \"plain\" as authentication mechanisms. Contrary to the assumption no passwords will be transferred plain text, as no authentication is allowed to take place without TLS. Contacts and calendars \u00b6 SOGos default calendar (CalDAV) and contacts (CardDAV) URLs: CalDAV - https://mail.example.com/SOGo/dav/user@example.com/Calendar/personal/ CardDAV - https://mail.example.com/SOGo/dav/user@example.com/Contacts/personal/ Some applications may require you to use https://mail.example.com/SOGo/dav/ or the full path to your calendar, which can be found and copied from within SOGo.","title":"Manual configuration"},{"location":"client/client-manual/#email","text":"Service Encryption Host Port IMAP STARTTLS mailcow hostname 143 IMAPS SSL mailcow hostname 993 POP3 STARTTLS mailcow hostname 110 POP3S SSL mailcow hostname 995 SMTP STARTTLS mailcow hostname 587 SMTPS SSL mailcow hostname 465 Please use \"plain\" as authentication mechanisms. Contrary to the assumption no passwords will be transferred plain text, as no authentication is allowed to take place without TLS.","title":"Email"},{"location":"client/client-manual/#contacts-and-calendars","text":"SOGos default calendar (CalDAV) and contacts (CardDAV) URLs: CalDAV - https://mail.example.com/SOGo/dav/user@example.com/Calendar/personal/ CardDAV - https://mail.example.com/SOGo/dav/user@example.com/Contacts/personal/ Some applications may require you to use https://mail.example.com/SOGo/dav/ or the full path to your calendar, which can be found and copied from within SOGo.","title":"Contacts and calendars"},{"location":"client/client-outlook/","text":"Outlook 2016 or higher from Office 365 on Windows \u00b6 This is only applicable if your server administrator has not disabled EAS for Outlook. If it is disabled, please follow the guide for Outlook 2007 instead. Outlook 2016 has an issue with autodiscover . Only Outlook from Office 365 is affected. If you installed Outlook from another source, please follow the guide for Outlook 2013 or higher. For EAS you must use the old assistant by launching C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\OLCFG.EXE . If this application opens, you can go to step 4 of the guide for Outlook 2013 below. If it does not open, you can completely disable the new account creation wizard and follow the guide for Outlook 2013 below. Outlook 2013 or higher on Windows \u00b6 This is only applicable if your server administrator has not disabled EAS for Outlook. If it is disabled, please follow the guide for Outlook 2007 instead. Launch Outlook. If this is the first time you launched Outlook, it asks you to set up your account. Proceed to step 4. Go to the File menu and click Add Account . Enter your name ( ) , email address ( ) and your password. Click Next . When prompted, enter your password again, check Remember my credentials and click OK . Click the Allow button. Click Finish . Outlook 2007 or 2010 on Windows \u00b6 Outlook 2007 or higher on Windows \u00b6 Download and install Outlook CalDav Synchronizer . Launch Outlook. If this is the first time you launched Outlook, it asks you to set up your account. Proceed to step 5. Go to the File menu and click Add Account . Enter your name ( ) , email address ( ) and your password. Click Next . Click Finish . Go to the CalDav Synchronizer ribbon and click Synchronization Profiles . Click the second button at top ( Add multiple profiles ), select Sogo , click Ok . Click the Get IMAP/POP3 account settings button. Click Discover resources and assign to Outlook folders . In the Select Resource window that pops up, select your main calendar (usually Personal Calendar ), click the ... button, assign it to Calendar , and click OK . Go to the Address Books and Tasks tabs and repeat repeat the process accordingly. Do not assign multiple calendars, address books or task lists! Close all windows with the OK buttons. Outlook 2011 or higher on macOS \u00b6 The Mac version of Outlook does not synchronize calendars and contacts and therefore is not supported.","title":"Microsoft Outlook"},{"location":"client/client-outlook/#outlook-2016-or-higher-from-office-365-on-windows","text":"This is only applicable if your server administrator has not disabled EAS for Outlook. If it is disabled, please follow the guide for Outlook 2007 instead. Outlook 2016 has an issue with autodiscover . Only Outlook from Office 365 is affected. If you installed Outlook from another source, please follow the guide for Outlook 2013 or higher. For EAS you must use the old assistant by launching C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\OLCFG.EXE . If this application opens, you can go to step 4 of the guide for Outlook 2013 below. If it does not open, you can completely disable the new account creation wizard and follow the guide for Outlook 2013 below.","title":"Outlook 2016 or higher from Office 365 on Windows"},{"location":"client/client-outlook/#outlook-2013-or-higher-on-windows","text":"This is only applicable if your server administrator has not disabled EAS for Outlook. If it is disabled, please follow the guide for Outlook 2007 instead. Launch Outlook. If this is the first time you launched Outlook, it asks you to set up your account. Proceed to step 4. Go to the File menu and click Add Account . Enter your name ( ) , email address ( ) and your password. Click Next . When prompted, enter your password again, check Remember my credentials and click OK . Click the Allow button. Click Finish .","title":"Outlook 2013 or higher on Windows"},{"location":"client/client-outlook/#outlook-2007-or-2010-on-windows","text":"","title":"Outlook 2007 or 2010 on Windows"},{"location":"client/client-outlook/#outlook-2007-or-higher-on-windows","text":"Download and install Outlook CalDav Synchronizer . Launch Outlook. If this is the first time you launched Outlook, it asks you to set up your account. Proceed to step 5. Go to the File menu and click Add Account . Enter your name ( ) , email address ( ) and your password. Click Next . Click Finish . Go to the CalDav Synchronizer ribbon and click Synchronization Profiles . Click the second button at top ( Add multiple profiles ), select Sogo , click Ok . Click the Get IMAP/POP3 account settings button. Click Discover resources and assign to Outlook folders . In the Select Resource window that pops up, select your main calendar (usually Personal Calendar ), click the ... button, assign it to Calendar , and click OK . Go to the Address Books and Tasks tabs and repeat repeat the process accordingly. Do not assign multiple calendars, address books or task lists! Close all windows with the OK buttons.","title":"Outlook 2007 or higher on Windows"},{"location":"client/client-outlook/#outlook-2011-or-higher-on-macos","text":"The Mac version of Outlook does not synchronize calendars and contacts and therefore is not supported.","title":"Outlook 2011 or higher on macOS"},{"location":"client/client-thunderbird/","text":"Launch Thunderbird. If this is the first time you launched Thunderbird, it asks you whether you would like a new email address. Click Skip this and use my existing email and proceed to step 4. Go to the Tools menu and select Account Settings . Click the Account Actions dropdown menu at the bottom left and select Add Mail Account . Enter your name ( ) , email address ( ) and your password. Make sure the Remember password checkbox is selected and click Continue . Once the configuration has been automatically detected, click Done . If you already had other accounts configured in Thunderbird, select the new one ( ) on the left, click the Account Actions dropdown and select Set as Default . Close the account settings window with the OK button. In your web browser, download SOGo Connector SOGo Connector . Back in Thunderbird, go to the Tools menu and select Add-ons . Click Extensions on the left and ensure that the Lightning add-on is already installed. It is installed by default in the Windows and macOS versions of Thunderbird, but if you are running Linux and installed Thunderbird through your distribution's package manager, Lightning might be available as a separate package (e.g. xul-ext-lightning on Ubuntu). Click Extensions on the left, click the little gear icon at the top and select Install Add-on From File . Select the file you downloaded in step 9, click Open and, after waiting for a few seconds, Install Now . Click the Restart Now button at the top that appears. Thunderbird briefly shows a message that it is updating extensions, then restarts automatically once more. When you are prompted to authenticate for , enter your email address and password, check Use Password Manager and click OK . Automatic configuration of calendars and address books in Thunderbird is not currently supported. You can ask your server administrator to enable SOGo Connector if you need it. Automatic configuration of calendars and address books (from step 9 onward) in Thunderbird is only supported if your server administrator has enabled SOGo Connector . Different method of connecting Cal-/CardDAV in Thunderbird with automatic detection of address books and calendars \u00b6 Instead of using SOGo Connector you can use a combination of https://addons.thunderbird.net/de/thunderbird/addon/tbsync/ and https://addons.thunderbird.net/de/thunderbird/addon/dav-4-tbsync/ To add your Cal-/CardDAV accounts go to Tools and find TbSync You can add new accounts via the CalDAV & CardDAV provider: Choose \"Automatic Configuration\". Use your mail address as account and username. Use your mail password as DAV password. The server URL is your MAILCOW_HOSTNAME (specifying any protocol is not necessary, just enter the full domain). Now tick the checkbox for \"Enable and synchronize this account\" in the synchronization status tab: Several available resources should appear in the same window area now. Tick all checkboxes of the resources (address books and calendars) that you want to sync. Choose a synchronization period (in minutes) in the same window area before clicking on \"Synchronize now\". If you leave the sync at \"0\" it will only sync manually so choose at least 30 minutes for periodic synchronization. If you want to manually synchronize you can find this option under \"Account actions\" - the dropdown-menu where you added the Cal-/CardDAV account (step 2).","title":"Mozilla Thunderbird"},{"location":"client/client-thunderbird/#different-method-of-connecting-cal-carddav-in-thunderbird-with-automatic-detection-of-address-books-and-calendars","text":"Instead of using SOGo Connector you can use a combination of https://addons.thunderbird.net/de/thunderbird/addon/tbsync/ and https://addons.thunderbird.net/de/thunderbird/addon/dav-4-tbsync/ To add your Cal-/CardDAV accounts go to Tools and find TbSync You can add new accounts via the CalDAV & CardDAV provider: Choose \"Automatic Configuration\". Use your mail address as account and username. Use your mail password as DAV password. The server URL is your MAILCOW_HOSTNAME (specifying any protocol is not necessary, just enter the full domain). Now tick the checkbox for \"Enable and synchronize this account\" in the synchronization status tab: Several available resources should appear in the same window area now. Tick all checkboxes of the resources (address books and calendars) that you want to sync. Choose a synchronization period (in minutes) in the same window area before clicking on \"Synchronize now\". If you leave the sync at \"0\" it will only sync manually so choose at least 30 minutes for periodic synchronization. If you want to manually synchronize you can find this option under \"Account actions\" - the dropdown-menu where you added the Cal-/CardDAV account (step 2).","title":"Different method of connecting Cal-/CardDAV in Thunderbird with automatic detection of address books and calendars"},{"location":"client/client-windows/","text":"Windows 8 and higher support email, contacts and calendar via Exchange ActiveSync. Open the Mail app. If you have not previously used Mail, you can click Add Account in the main window. Proceed to step 4. Click Accounts in the sidebar on the left, then click Add Account on the far right. Select Exchange . Enter your email address ( ) and click Next . Enter your password and click Log in . Once you have set up the Mail app, you can also use the People and Calendar apps.","title":"Windows Mail"},{"location":"client/client-windowsphone/","text":"Open the Settings app. Select email + accounts and tap add an account . Tap Exchange . Enter your email address ( ) and your password. Tap Sign in . Tap done .","title":"Windows Phone"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 000000000..424992083 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,367 @@ + + + https://mailcow.github.io/mailcow-dockerized-docs/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/prerequisite-system/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/prerequisite-dns/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/i_u_m_install/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/i_u_m_update/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/i_u_m_migration/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/firststeps-ssl/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/firststeps-disable_ipv6/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/firststeps-ip_bindings/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/firststeps-local_mta/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/firststeps-logging/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/firststeps-relayhost/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/firststeps-rp/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/firststeps-rspamd_ui/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/firststeps-snat/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/firststeps-sync_jobs_migration/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/firststeps-rfc-1918/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/model-acl/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/model-passwd/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/model-sender_rcv/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/debug/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/debug-logs/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/debug-attach_service/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/debug-reset_pw/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/debug-mysql_upgrade/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/debug-rm_volumes/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/debug-common_problems/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/debug-admin_login_sogo/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/b_n_r_backup/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/b_n_r_restore/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-backup_restore-maildir/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-backup_restore-mysql/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-mailcow_ui-config/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-mailcow_ui-bl_wl/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-mailcow_ui-pushover/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-mailcow_ui-spamfilter/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-mailcow_ui-spamalias/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-mailcow_ui-tagging/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-mailcow_ui-tfa/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-fido2/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-postfix-custom_transport/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-postfix-postscreen_whitelist/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-postfix-extra_cf/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-postfix-disable_sender_verification/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-postfix-attachment_size/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-postfix-pflogsumm/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-unbound-fwd/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-dovecot-any_acl/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-dovecot-expunge/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-dovecot-extra_conf/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-dovecot-fts/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-dovecot-idle_interval/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-dovecot-mail-crypt/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-dovecot-more/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-dovecot-vmail-volume/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-dovecot-public_folder/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-dovecot-static_master/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-nginx/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-webmail-site/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-redis/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-rspamd/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-sogo/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-xmpp-faq/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-xmpp-enable/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-docker-cust_dockerfiles/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-docker-dc_bash_compl/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-why_unbound/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-autodiscover_config/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-80_to_443/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-change_config/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-deinstall/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/u_e-reeanble-weak-protocols/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/client/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/client/client-android/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/client/client-apple/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/client/client-emclient/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/client/client-gajim_xmpp_client/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/client/client-kontact/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/client/client-outlook/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/client/client-thunderbird/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/client/client-windows/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/client/client-windowsphone/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/client/client-manual/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/third_party-exchange_onprem/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/third_party-gitea/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/third_party-gogs/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/third_party-mailpiler_integration/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/third_party-nextcloud/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/third_party-portainer/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/third_party-roundcube/ + 2021-04-01 + daily + + https://mailcow.github.io/mailcow-dockerized-docs/third_party-thunderbird/ + 2021-04-01 + daily + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 000000000..660b86858 Binary files /dev/null and b/sitemap.xml.gz differ diff --git a/third_party-exchange_onprem/index.html b/third_party-exchange_onprem/index.html new file mode 100644 index 000000000..eba6df65c --- /dev/null +++ b/third_party-exchange_onprem/index.html @@ -0,0 +1,2098 @@ + + + + + + + + + + + + + + + + + + + Exchange Hybrid Setup - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Exchange Hybrid Setup

    + +

    Using Microsoft Exchange in a hybrid setup is possible with mailcow. With this setup you can add mailboxes on your mailcow and still use Exchange Online Protection. +All mailboxes setup in Exchange will receive their mails as usual, while with the hybrid approach additional Mailboxes can be setup in mailcow without any further configuration.

    +

    This setup becomes very handy if you have enabled the Office 365 security defaults and third party applications can no longer login into your mailboxes by any of the supported methods.

    +

    Requirements

    +
      +
    • The mx Record of your domain needs to point at the Exchange mail service. Log into your Admin center and look out for the dns settings of your domain to find your personalized gateway domain. It should look like this contoso-com.mail.protection.outlook.com. Contact your domain registrant to get further information on how to change mx record.
    • +
    • The domain you want to have additional mailboxes for must be setup as internal relay domain in Exchange.
        +
      1. Log in to your Exchange Admin Center
      2. +
      3. Select the mail flow pane and click on accepted domains
      4. +
      5. Select the domain and switch it from authorative to internal relay
      6. +
      +
    • +
    +

    Set up the mailcow

    +

    Your mailcow needs to relay all mails to your personalized Exchange Host. It is the same host address we already looked up for the mx Record.

    +
      +
    1. Add the domain to your mailcow
    2. +
    3. Add your personalized Exchange Host address as relayhost
    4. +
    5. Add your personalized Exchange Host address as forwarding host to unconditionally accepted all relayed mails from Exchange. (Admin > Configuration & Details > Configuration Dropdown > Forwarding Hosts)
    6. +
    7. Go to the domain settings and select the newly added host on the Sender-dependent transports dropdown. Enable relaying by ticking the Relay this domain, Relay all recipients and the Relay non-existing mailboxes only. checkboxes
    8. +
    +
    +

    Info

    +

    From now on your mailcow will accept all mails relayed from Exchange. The inbound filtering and so the neural learning of your cow will no longer work. Because all mails are routed through Exchange the filtering process is handled there.

    +
    +

    Set up Connectors in Exchange

    +

    All mail traffic now goes through Exchange. At this point the Exchange Online Protection already filters all incoming and outgoing mails. Now we need to set up two connectors to relay incoming mails from our Exchange Service to the mailcow and another one to allow mails relayed from the mailcow to our exchange service. You can follow the official guide from Microsoft.

    +
    +

    Warning

    +

    For the connector that handles mails from your mailcow to Exchange Microsoft offers two ways of authenticating it. The recommended way is to use a tls certificate configured with a subject name that matches an accepted domain in Exchange. Otherwise you need to choose authentication with the static ip address of your mailcow.

    +
    +

    Validating

    +

    The easiest way to validate the hybrid setup is by sending a mail from the internet to a mailbox that only exists on the mailcow and vice versa.

    +

    Common Issues

    +
      +
    • The connector validation from Exchange to your mailcow failed with 550 5.1.10 RESOLVER.ADR.RecipientNotFound; Recipient test@contoso.com not found by SMTP address lookup
      +Possible Solution: Your domain is not set up as internal relay. Exchange therefore cannot find the recipient
    • +
    • Mails sent from the mailcow to a mailbox in the internet cannot be sent. Non Delivery Report with error 550 5.7.64 TenantAttribution; Relay Access Denied
      +Possible Solution: The authentication method failed. Make sure the certificate subject matches an accepted domain in Exchange. Try authenticating by static ip instead.
    • +
    +

    Microsoft Guide for the connector setup and additional requirements: https://docs.microsoft.com/exchange/mail-flow-best-practices/use-connectors-to-configure-mail-flow/set-up-connectors-to-route-mail#prerequisites-for-your-on-premises-email-environment

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/third_party-gitea/index.html b/third_party-gitea/index.html new file mode 100644 index 000000000..423286ef9 --- /dev/null +++ b/third_party-gitea/index.html @@ -0,0 +1,1966 @@ + + + + + + + + + + + + + + + + + + + Gitea - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Gitea

    + +

    With Gitea' ability to authenticate over SMTP it is trivial to integrate it with mailcow. Few changes are needed:

    +

    1. Open docker-compose.override.yml and add gitea:

    +
    version: '2.1'
    +services:
    +
    +        gitea-mailcow:
    +            image: gitea/gitea:1
    +            volumes:
    +                - ./data/gitea:/data
    +            networks:
    +                mailcow-network:
    +                    aliases:
    +                        - gitea
    +            ports:
    +                - "${GITEA_SSH_PORT:-127.0.0.1:4000}:22"
    +
    +

    2. Create data/conf/nginx/site.gitea.custom, add: +

    location /gitea/ {
    +        proxy_pass http://gitea:3000/;
    +}
    +

    +

    3. Open mailcow.conf and define the binding you want gitea to use for SSH. Example:

    +
    GITEA_SSH_PORT=127.0.0.1:4000
    +
    +

    5. Run docker-compose up -d to bring up the gitea container and run docker-compose restart nginx-mailcow afterwards.

    +

    6. If you forced mailcow to https, execute step 9 and restart gitea with docker-compose restart gitea-mailcow . Go head with step 7 (Remember to use https instead of http, https://mx.example.org/gitea/

    +

    7. Open http://${MAILCOW_HOSTNAME}/gitea/, for example http://mx.example.org/gitea/. For database details set mysql as database host. Use the value of DBNAME found in mailcow.conf as database name, DBUSER as database user and DBPASS as database password.

    +

    8. Once the installation is complete, login as admin and set "settings" -> "authorization" -> "enable SMTP". SMTP Host should be postfix with port 587, set Skip TLS Verify as we are using an unlisted SAN ("postfix" is most likely not part of your certificate).

    +

    9. Create data/gitea/gitea/conf/app.ini and set following values. You can consult gitea cheat sheet for their meaning and other possible values.

    +
    [server]
    +SSH_LISTEN_PORT = 22
    +# For GITEA_SSH_PORT=127.0.0.1:4000 in mailcow.conf, set:
    +SSH_DOMAIN = 127.0.0.1
    +SSH_PORT = 4000
    +# For MAILCOW_HOSTNAME=mx.example.org in mailcow.conf (and default ports for HTTPS), set:
    +ROOT_URL = https://mx.example.org/gitea/
    +
    +

    10. Restart gitea with docker-compose restart gitea-mailcow. Your users should be able to login with mailcow managed accounts.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/third_party-gogs/index.html b/third_party-gogs/index.html new file mode 100644 index 000000000..7581ad8cd --- /dev/null +++ b/third_party-gogs/index.html @@ -0,0 +1,1965 @@ + + + + + + + + + + + + + + + + + + + Gogs - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Gogs

    + +

    With Gogs' ability to authenticate over SMTP it is trivial to integrate it with mailcow. Few changes are needed:

    +

    1. Open docker-compose.override.yml and add Gogs:

    +
    version: '2.1'
    +services:
    +
    +    gogs-mailcow:
    +      image: gogs/gogs
    +      volumes:
    +        - ./data/gogs:/data
    +      networks:
    +        mailcow-network:
    +          aliases:
    +            - gogs
    +      ports:
    +        - "${GOGS_SSH_PORT:-127.0.0.1:4000}:22"
    +
    +

    2. Create data/conf/nginx/site.gogs.custom, add: +

    location /gogs/ {
    +    proxy_pass http://gogs:3000/;
    +}
    +

    +

    3. Open mailcow.conf and define the binding you want Gogs to use for SSH. Example:

    +
    GOGS_SSH_PORT=127.0.0.1:4000
    +
    +

    5. Run docker-compose up -d to bring up the Gogs container and run docker-compose restart nginx-mailcow afterwards.

    +

    6. Open http://${MAILCOW_HOSTNAME}/gogs/, for example http://mx.example.org/gogs/. For database details set mysql as database host. Use the value of DBNAME found in mailcow.conf as database name, DBUSER as database user and DBPASS as database password.

    +

    7. Once the installation is complete, login as admin and set "settings" -> "authorization" -> "enable SMTP". SMTP Host should be postfix with port 587, set Skip TLS Verify as we are using an unlisted SAN ("postfix" is most likely not part of your certificate).

    +

    8. Create data/gogs/gogs/conf/app.ini and set following values. You can consult Gogs cheat sheet for their meaning and other possible values.

    +
    [server]
    +SSH_LISTEN_PORT = 22
    +# For GOGS_SSH_PORT=127.0.0.1:4000 in mailcow.conf, set:
    +SSH_DOMAIN = 127.0.0.1
    +SSH_PORT = 4000
    +# For MAILCOW_HOSTNAME=mx.example.org in mailcow.conf (and default ports for HTTPS), set:
    +ROOT_URL = https://mx.example.org/gogs/
    +
    +

    9. Restart Gogs with docker-compose restart gogs-mailcow. Your users should be able to login with mailcow managed accounts.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/third_party-mailpiler_integration/index.html b/third_party-mailpiler_integration/index.html new file mode 100644 index 000000000..3441e4a0f --- /dev/null +++ b/third_party-mailpiler_integration/index.html @@ -0,0 +1,2102 @@ + + + + + + + + + + + + + + + + + + + Mailpiler Integration - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Mailpiler Integration

    + +

    This is a simple integration of mailcow aliases and the mailbox name into mailpiler when using IMAP authentication.

    +

    Disclaimer: This is not officially maintained nor supported by the mailcow project nor its contributors. No warranty or support is being provided, however you're free to open issues on GitHub for filing a bug or provide further ideas. GitHub repo can be found here.

    +
    +

    Info

    +

    Support for domain wildcards were implemented in Piler 1.3.10 which was released on 03.01.2021. Prior versions basically do work, but after logging in you won't see emails sent from or to the domain alias. (e.g. when @example.com is an alias for admin@example.com)

    +
    +

    The problem to solve

    +

    mailpiler offers the authentication based on IMAP, for example:

    +
    $config['ENABLE_IMAP_AUTH'] = 1;
    +$config['IMAP_HOST'] = 'mail.example.com';
    +$config['IMAP_PORT'] =  993;
    +$config['IMAP_SSL'] = true;
    +
    +
      +
    • So when you log in using patrik@example.com, you will only see delivered emails sent from or to this specific email address.
    • +
    • When additional aliases are defined in mailcow, like team@example.com, you won't see emails sent to or from this email address even the fact you're a recipient of mails sent to this alias address.
    • +
    +

    By hooking into the authentication process of mailpiler, we are able to get required data via the mailcow API during login. This fires API requests to the mailcow API (requiring read-only API access) to read out the aliases your email address participates and also the "Name" of the mailbox specified to display it on the top-right of mailpiler after login.

    +

    Permitted email addresses can be seen in the mailpiler settings top-right after logging in.

    +
    +

    Info

    +

    This is only pulled once during the authentication process. The authorized aliases and the realname are valid for the whole duration of the user session as mailpiler sets them in the session data. If user is removed from specific alias, this will only take effect after next login.

    +
    +

    The solution

    +

    Note: File paths might vary depending on your setup.

    +

    Requirements

    + +
    +

    Warning

    +

    As mailpiler authenticates against mailcow, our IMAP server, failed logins of users or bots might trigger a block for your mailpiler instance. Therefore you might want to consider whitelisting the IP address of the mailpiler instance within mailcow: Configuration & Details - Configuration - Fail2ban parameters - Whitelisted networks/hosts.

    +
    +

    Setup

    +
      +
    1. +

      Set the custom query function of mailpiler and append this to /usr/local/etc/piler/config-site.php:

      +
      $config['MAILCOW_API_KEY'] = 'YOUR_READONLY_API_KEY';
      +$config['MAILCOW_SET_REALNAME'] = true; // when not specified, then default is false
      +$config['CUSTOM_EMAIL_QUERY_FUNCTION'] = 'query_mailcow_for_email_access';
      +include('auth-mailcow.php');
      +
      +

      You can also change the mailcow hostname, if required: +

      $config['MAILCOW_HOST'] = 'mail.domain.tld'; // defaults to $config['IMAP_HOST']
      +

      +
    2. +
    3. +

      Download the PHP file with the functions from the GitHub repo:

      +
      curl -o /usr/local/etc/piler/auth-mailcow.php https://raw.githubusercontent.com/patschi/mailpiler-mailcow-integration/master/auth-mailcow.php
      +
      +
    4. +
    5. +

      Done!

      +
    6. +
    +

    Make sure to re-login with your IMAP credentials for changes to take effect.

    +

    If it doesn't work, most likely something's wrong with the API query itself. Consider debugging by sending manual API requests to the API. (Tip: Open https://mail.domain.tld/api on your instance)

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/third_party-nextcloud/index.html b/third_party-nextcloud/index.html new file mode 100644 index 000000000..9b8d4a3d6 --- /dev/null +++ b/third_party-nextcloud/index.html @@ -0,0 +1,2106 @@ + + + + + + + + + + + + + + + + + + + Nextcloud - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + +

    Nextcloud

    + +

    Manage Nextcloud using the helper script

    +

    Nextcloud can be set up (parameter -i) and removed (parameter -p) with the helper script included with mailcow. In order to install Nextcloud simply navigate to your mailcow-dockerized root folder and run the helper script as follows:

    +

    ./helper-scripts/nextcloud.sh -i

    +

    In case you have forgotten the password (e.g. for admin) and can't request a new one via the password reset link on the login screen calling the helper script with -r as parameter allows you to set a new password. Only use this option if your Nextcloud isn't configured to use mailcow for authentication as described in the next section.

    +

    Configure Nextcloud to use mailcow for authentication

    +

    The following describes how set up authentication via mailcow using the OAuth2 protocol. We will only assume that you have already set up Nextcloud at cloud.example.com and that your mailcow is running at mail.example.com. It does not matter if your Nextcloud is running on a different server, you can still use mailcow for authentication.

    +

    1. Log into mailcow as administrator.

    +

    2. Scroll down to OAuth2 Apps and click the Add button. Specify the redirect URI as https://cloud.example.com/index.php/apps/sociallogin/custom_oauth2/Mailcow and click Add. Save the client ID and secret for later.

    +
    +

    Info

    +

    Some installations, including those setup using the helper script of mailcow, need to remove index.php/ from the URL to get a successful redirect: https://cloud.example.com/apps/sociallogin/custom_oauth2/Mailcow

    +
    +

    3. Log into Nextcloud as administrator.

    +

    4. Click the button in the top right corner and select Apps. Click the search button in the toolbar, search for the Social Login plugin and click Download and enable next to it.

    +

    5. Click the button in the top right corner and select Settings. Scroll down to the Administration section on the left and click Social login.

    +

    6. Uncheck the following items:

    +
      +
    • "Disable auto create new users"
    • +
    • "Allow users to connect social logins with their accounts"
    • +
    • "Do not prune not available user groups on login"
    • +
    • "Automatically create groups if they do not exists"
    • +
    • "Restrict login for users without mapped groups"
    • +
    +

    7. Check the following items:

    +
      +
    • "Prevent creating an account if the email address exists in another account"
    • +
    • "Update user profile every login"
    • +
    • "Disable notify admins about new users"
    • +
    +

    Click the Save button.

    +

    8. Scroll down to Custom OAuth2 and click the + button. +9. Configure the parameters as follows:

    +
      +
    • Internal name: Mailcow
    • +
    • Title: Mailcow
    • +
    • API Base URL: https://mail.example.com
    • +
    • Authorize URL: https://mail.example.com/oauth/authorize
    • +
    • Token URL: https://mail.example.com/oauth/token
    • +
    • Profile URL: https://mail.example.com/oauth/profile
    • +
    • Logout URL: (leave blank)
    • +
    • Client ID: (what you obtained in step 1)
    • +
    • Client Secret: (what you obtained in step 1)
    • +
    • Scope: profile
    • +
    +

    Click the Save button at the very bottom of the page.

    +
    +

    If you have previously used Nextcloud with mailcow authentication via user_external/IMAP, you need to perform some additional steps to link your existing user accounts with OAuth2.

    +

    1. Click the button in the top right corner and select Apps. Scroll down to the External user authentication app and click Remove next to it. +2. Run the following queries in your Nextcloud database (if you set up Nextcloud using mailcow's script, you can run source mailcow.conf && docker-compose exec mysql-mailcow mysql -u$DBUSER -p$DBPASS $DBNAME): +

    INSERT INTO nc_users (uid, uid_lower) SELECT DISTINCT uid, LOWER(uid) FROM nc_users_external;
    +INSERT INTO nc_sociallogin_connect (uid, identifier) SELECT DISTINCT uid, CONCAT("Mailcow-", uid) FROM nc_users_external;
    +

    +
    +

    If you have previously used Nextcloud without mailcow authentication, but with the same usernames as mailcow, you can also link your existing user accounts with OAuth2.

    +

    1. Run the following queries in your Nextcloud database (if you set up Nextcloud using mailcow's script, you can run source mailcow.conf && docker-compose exec mysql-mailcow mysql -u$DBUSER -p$DBPASS $DBNAME): +

    INSERT INTO nc_sociallogin_connect (uid, identifier) SELECT DISTINCT uid, CONCAT("Mailcow-", uid) FROM nc_users;
    +

    +
    +

    Update

    +

    The Nextcloud instance can be updated easily with the web update mechanism. In the case of larger updates, there may be further changes to be made after the update. After the Nextcloud instance has been checked, problems are shown. This can be e.g. missing indices in the DB or similar. +It shows which commands have to be executed, these have to be placed in the php-fpm-mailcow container.

    +

    As an an example run the following command to add the missing indices. +docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "php /web/nextcloud/occ db:add-missing-indices"

    +
    +

    Debugging & Troubleshooting

    +

    It may happen that you cannot reach the Nextcloud instance from your network. This may be due to the fact that the entry of your subnet in the array 'trusted_proxies' is missing. You can make changes in the Nextcloud config.php in data/web/nextcloud/config/*.

    +
    'trusted_proxies' =>
    +  array (
    +    0 => 'fd4d:6169:6c63:6f77::/64',
    +    1 => '172.22.1.0/24',
    +    2 => 'NewSubnet/24',
    +  ),
    +
    +

    After the changes have been made, the nginx container must be restarted. +docker-compose restart nginx-mailcow

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/third_party-portainer/index.html b/third_party-portainer/index.html new file mode 100644 index 000000000..00a341149 --- /dev/null +++ b/third_party-portainer/index.html @@ -0,0 +1,1981 @@ + + + + + + + + + + + + + + + + + + + Portainer - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Portainer

    + +

    In order to enable Portainer, the docker-compose.yml and site.conf for Nginx must be modified.

    +

    1. Create a new file docker-compose.override.yml in the mailcow-dockerized root folder and insert the following configuration +

    version: '2.1'
    +services:
    +    portainer-mailcow:
    +      image: portainer/portainer-ce
    +      volumes:
    +        - /var/run/docker.sock:/var/run/docker.sock
    +        - ./data/conf/portainer:/data
    +      restart: always
    +      dns:
    +        - 172.22.1.254
    +      dns_search: mailcow-network
    +      networks:
    +        mailcow-network:
    +          aliases:
    +            - portainer
    +
    +2a. Create data/conf/nginx/portainer.conf: +
    upstream portainer {
    +  server portainer-mailcow:9000;
    +}
    +
    +map $http_upgrade $connection_upgrade {
    +  default upgrade;
    +  '' close;
    +}
    +

    +

    2b. Insert a new location to the default mailcow site by creating the file data/conf/nginx/site.portainer.custom: +

      location /portainer/ {
    +    proxy_http_version 1.1;
    +    proxy_set_header Host              $http_host;   # required for docker client's sake
    +    proxy_set_header X-Real-IP         $remote_addr; # pass on real client's IP
    +    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    +    proxy_set_header X-Forwarded-Proto $scheme;
    +    proxy_read_timeout                 900;
    +
    +    proxy_set_header Connection "";
    +    proxy_buffers 32 4k;
    +    proxy_pass http://portainer/;
    +  }
    +
    +  location /portainer/api/websocket/ {
    +    proxy_http_version 1.1;
    +    proxy_set_header Upgrade $http_upgrade;
    +    proxy_set_header Connection $connection_upgrade;
    +    proxy_pass http://portainer/api/websocket/;
    +  }
    +

    +

    3. Apply your changes: +

    docker-compose up -d && docker-compose restart nginx-mailcow
    +

    +

    Now you can simply navigate to https://${MAILCOW_HOSTNAME}/portainer/ to view your Portainer container monitoring page. You’ll then be prompted to specify a new password for the admin account. After specifying your password, you’ll then be able to connect to the Portainer UI.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/third_party-roundcube/index.html b/third_party-roundcube/index.html new file mode 100644 index 000000000..3666bc0c5 --- /dev/null +++ b/third_party-roundcube/index.html @@ -0,0 +1,2139 @@ + + + + + + + + + + + + + + + + + + + Roundcube - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + +

    Roundcube

    + +

    Download Roundcube 1.4.x to the web htdocs directory and extract it (here rc/): +

    # Check for a newer release!
    +cd data/web
    +wget -O - https://github.com/roundcube/roundcubemail/releases/download/1.4.9/roundcubemail-1.4.9-complete.tar.gz | tar xfvz -
    +# Change folder name
    +mv roundcubemail-1.4.9 rc
    +# Change permissions
    +chown -R root: rc/
    +

    +

    Create a file data/web/rc/config/config.inc.php with the following content.

    +

    Change the des_key parameter to a random value. It is used to temporarily store your IMAP password. The "db_prefix" is optional but recommended.

    +
    <?php
    +error_reporting(0);
    +if (!file_exists('/tmp/mime.types')) {
    +file_put_contents("/tmp/mime.types", fopen("http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types", 'r'));
    +}
    +$config = array();
    +$config['db_dsnw'] = 'mysql://' . getenv('DBUSER') . ':' . getenv('DBPASS') . '@mysql/' . getenv('DBNAME');
    +$config['default_host'] = 'tls://dovecot';
    +$config['default_port'] = '143';
    +$config['smtp_server'] = 'tls://postfix';
    +$config['smtp_port'] = 587;
    +$config['smtp_user'] = '%u';
    +$config['smtp_pass'] = '%p';
    +$config['support_url'] = '';
    +$config['product_name'] = 'Roundcube Webmail';
    +$config['des_key'] = 'yourrandomstring_changeme';
    +$config['log_dir'] = '/dev/null';
    +$config['temp_dir'] = '/tmp';
    +$config['plugins'] = array(
    +  'archive',
    +  'managesieve'
    +);
    +$config['mime_types'] = '/tmp/mime.types';
    +$config['imap_conn_options'] = array(
    +  'ssl' => array('verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true)
    +);
    +$config['enable_installer'] = true;
    +$config['smtp_conn_options'] = array(
    +  'ssl' => array('verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true)
    +);
    +$config['db_prefix'] = 'mailcow_rc1';
    +
    +

    Point your browser to https://myserver/rc/installer and follow the instructions. +Initialize the database and leave the installer.

    +

    Delete the directory data/web/rc/installer after a successful installation!

    +

    Configure ManageSieve filtering

    +

    Open data/web/rc/plugins/managesieve/config.inc.php and change the following parameters (or add them at the bottom of that file): +

    $config['managesieve_port'] = 4190;
    +$config['managesieve_host'] = 'tls://dovecot';
    +$config['managesieve_conn_options'] = array(
    +  'ssl' => array('verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true)
    +);
    +// Enables separate management interface for vacation responses (out-of-office)
    +// 0 - no separate section (default),
    +// 1 - add Vacation section,
    +// 2 - add Vacation section, but hide Filters section
    +$config['managesieve_vacation'] = 1;
    +

    +

    Enable change password function in Roundcube

    +

    Open data/web/rc/config/config.inc.php and enable the password plugin:

    +
    ...
    +$config['plugins'] = array(
    +    'archive',
    +    'password',
    +);
    +...
    +
    +

    Open data/web/rc/plugins/password/password.php, search for case 'ssha': and add above:

    +
            case 'ssha256':
    +            $salt = rcube_utils::random_bytes(8);
    +            $crypted = base64_encode( hash('sha256', $password . $salt, TRUE ) . $salt );
    +            $prefix  = '{SSHA256}';
    +            break;
    +
    +

    Open data/web/rc/plugins/password/config.inc.php and change the following parameters (or add them at the bottom of that file):

    +
    $config['password_driver'] = 'sql';
    +$config['password_algorithm'] = 'ssha256';
    +$config['password_algorithm_prefix'] = '{SSHA256}';
    +$config['password_query'] = "UPDATE mailbox SET password = %P WHERE username = %u";
    +
    +

    Integrate CardDAV addressbooks in Roundcube

    +

    Download the latest release of RCMCardDAV to the Roundcube plugin directory and extract it (here rc/plugins): +

    cd data/web/rc/plugins
    +wget -O - https://github.com/blind-coder/rcmcarddav/releases/download/v3.0.3/carddav-3.0.3.tar.bz2 | tar xfvj -
    +chown -R root: carddav/
    +

    +

    Copy the file config.inc.php.dist to config.inc.php (here in rc/plugins/carddav) and append the following preset to the end of the file - don't forget to replace mx.example.org with your own hostname: +

    $prefs['SOGo'] = array(
    +    'name'         =>  'SOGo',
    +    'username'     =>  '%u',
    +    'password'     =>  '%p',
    +    'url'          =>  'https://mx.example.org/SOGo/dav/%u/',
    +    'carddav_name_only' => true,
    +    'use_categories' => true,
    +    'active'       =>  true,
    +    'readonly'     =>  false,
    +    'refresh_time' => '02:00:00',
    +    'fixed'        =>  array( 'active', 'name', 'username', 'password', 'refresh_time' ),
    +    'hide'        =>  false,
    +);
    +
    +Please note, that this preset only integrates the default addressbook (the one that's named "Personal Address Book" and can't be deleted). Additional addressbooks are currently not automatically detected but can be manually added within the roundecube settings.

    +

    Enable the plugin by adding carddav to $config['plugins'] in rc/config/config.inc.php.

    +

    If you want to remove the default addressbooks (stored in the Roundcube database), so that only the CardDAV addressbooks are accessible, append $config['address_book_type'] = ''; to the config file data/web/rc/config/config.inc.php.

    +
    +

    Optionally, you can add Roundcube's link to the mailcow Apps list. +To do this, open or create data/web/inc/vars.local.inc.php and add the following code-block:

    +

    NOTE: Don't forget to add the <?php delimiter on the first line

    +
    ...
    +$MAILCOW_APPS = array(
    +  array(
    +    'name' => 'SOGo',
    +    'link' => '/SOGo/'
    +  ),
    +  array(
    +    'name' => 'Roundcube',
    +    'link' => '/rc/'
    +   )
    +);
    +...
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/third_party-thunderbird/index.html b/third_party-thunderbird/index.html new file mode 100644 index 000000000..3b65ed2ed --- /dev/null +++ b/third_party-thunderbird/index.html @@ -0,0 +1,1955 @@ + + + + + + + + + + + + + + + + + + + SOGo Connector for Thunderbird - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Build the SOGo Connector plugin

    +

    Install GNU Make, tar, and ZIP if you don't already have them installed. On Debian/Ubuntu, this can be done using

    +
    apt-get install make tar zip
    +
    +

    Next, go to data/web inside mailcow-dockerized. +Place the file thunderbird-plugins.php into that directory. +Create a new directory thunderbird-plugins and place the script build-plugins.sh into it. +Finally, execute the script with your hostname as an argument and piping it the names of all domains that mailcow handles. +All of this can be done using the following commands:

    +
    cd data/web
    +curl -LO https://github.com/mailcow/mailcow-dockerized-docs/raw/master/docs/download/thunderbird-plugins.php
    +mkdir thunderbird-plugins
    +cd thunderbird-plugins
    +curl -Lo build-plugins.sh https://github.com/mailcow/mailcow-dockerized-docs/raw/master/docs/download/build-thunderbird-plugins.sh
    +chmod +x build-plugins.sh
    +echo example.com example.org | ./build-plugins.sh mailcow.example.com
    +
    +

    Install it in Thunderbird

    +

    After you have set up your mailcow IMAP account in Thunderbird, download the SOGo Connector plugin for your domain, e.g. https://mailcow.example.com/thunderbird-plugins/sogo-connector-68.0.1-example.com.xpi (see data/web/thunderbird-plugins), and install it into Thunderbird. +All your address books and calendars will be configured automatically.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-80_to_443/index.html b/u_e-80_to_443/index.html new file mode 100644 index 000000000..4dbdac84e --- /dev/null +++ b/u_e-80_to_443/index.html @@ -0,0 +1,1953 @@ + + + + + + + + + + + + + + + + + + + Redirect HTTP to HTTPS - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Redirect HTTP to HTTPS

    + +

    Since February the 28th 2017 mailcow does come with port 80 and 443 enabled.

    +

    Do not use the config below for reverse proxy setups, please see our reverse proxy guide for this, which includes a redirect from HTTP to HTTPS.

    +

    Open mailcow.conf and set HTTP_BIND= - if not already set.

    +

    Create a new file data/conf/nginx/redirect.conf and add the following server config to the file:

    +
    server {
    +  root /web;
    +  listen 80 default_server;
    +  listen [::]:80 default_server;
    +  include /etc/nginx/conf.d/server_name.active;
    +  if ( $request_uri ~* "%0A|%0D" ) { return 403; }
    +  location ^~ /.well-known/acme-challenge/ {
    +    allow all;
    +    default_type "text/plain";
    +  }
    +  location / {
    +    return 301 https://$host$uri$is_args$args;
    +  }
    +}
    +
    +

    In case you changed the HTTP_BIND parameter, recreate the container:

    +
    docker-compose up -d
    +
    +

    Otherwise restart Nginx:

    +
    docker-compose restart nginx-mailcow
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-autodiscover_config/index.html b/u_e-autodiscover_config/index.html new file mode 100644 index 000000000..906ce4cda --- /dev/null +++ b/u_e-autodiscover_config/index.html @@ -0,0 +1,1974 @@ + + + + + + + + + + + + + + + + + + + Autodiscover / Autoconfig - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Autodiscover / Autoconfig

    + +

    You do not need to change or create this file, autodiscover works out of the box. This guide is only meant for customizations to the autodiscover or autoconfig process.

    +

    Newer Outlook clients (especially those delivered with O365) will not autodiscover mail profiles. +Keep in mind, that ActiveSync should NOT be used with a desktop client.

    +

    Open/create data/web/inc/vars.local.inc.php and add your changes to the configuration array.

    +

    Changes will be merged with "$autodiscover_config" in data/web/inc/vars.inc.php):

    +
    <?php
    +$autodiscover_config = array(
    +  // General autodiscover service type: "activesync" or "imap"
    +  // emClient uses autodiscover, but does not support ActiveSync. mailcow excludes emClient from ActiveSync.
    +  'autodiscoverType' => 'activesync',
    +  // If autodiscoverType => activesync, also use ActiveSync (EAS) for Outlook desktop clients (>= Outlook 2013 on Windows)
    +  // Outlook for Mac does not support ActiveSync
    +  'useEASforOutlook' => 'yes',
    +  // Please don't use STARTTLS-enabled service ports in the "port" variable.
    +  // The autodiscover service will always point to SMTPS and IMAPS (TLS-wrapped services).
    +  // The autoconfig service will additionally announce the STARTTLS-enabled ports, specified in the "tlsport" variable.
    +  'imap' => array(
    +    'server' => $mailcow_hostname,
    +    'port' => array_pop(explode(':', getenv('IMAPS_PORT'))),
    +    'tlsport' => array_pop(explode(':', getenv('IMAP_PORT'))),
    +  ),
    +  'pop3' => array(
    +    'server' => $mailcow_hostname,
    +    'port' => array_pop(explode(':', getenv('POPS_PORT'))),
    +    'tlsport' => array_pop(explode(':', getenv('POP_PORT'))),
    +  ),
    +  'smtp' => array(
    +    'server' => $mailcow_hostname,
    +    'port' => array_pop(explode(':', getenv('SMTPS_PORT'))),
    +    'tlsport' => array_pop(explode(':', getenv('SUBMISSION_PORT'))),
    +  ),
    +  'activesync' => array(
    +    'url' => 'https://'.$mailcow_hostname.($https_port == 443 ? '' : ':'.$https_port).'/Microsoft-Server-ActiveSync',
    +  ),
    +  'caldav' => array(
    +    'server' => $mailcow_hostname,
    +    'port' => $https_port,
    +  ),
    +  'carddav' => array(
    +    'server' => $mailcow_hostname,
    +    'port' => $https_port,
    +  ),
    +);
    +
    +

    To always use IMAP and SMTP instead of EAS, set 'autodiscoverType' => 'imap'.

    +

    Disable ActiveSync for Outlook desktop clients by setting "useEASforOutlook" to "no".

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-backup_restore-maildir/index.html b/u_e-backup_restore-maildir/index.html new file mode 100644 index 000000000..9500cc20a --- /dev/null +++ b/u_e-backup_restore-maildir/index.html @@ -0,0 +1,2016 @@ + + + + + + + + + + + + + + + + + + + Maildir - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Maildir

    + +

    Backup

    +

    This line backups the vmail directory to a file backup_vmail.tar.gz in the mailcow root directory: +

    cd /path/to/mailcow-dockerized
    +docker run --rm -i -v $(docker inspect --format '{{ range .Mounts }}{{ if eq .Destination "/var/vmail" }}{{ .Name }}{{ end }}{{ end }}' $(docker-compose ps -q dovecot-mailcow)):/vmail -v ${PWD}:/backup debian:stretch-slim tar cvfz /backup/backup_vmail.tar.gz /vmail
    +

    +

    You can change the path by adjusting ${PWD} (which equals to the current directory) to any path you have write-access to. +Set the filename backup_vmail.tar.gz to any custom name, but leave the path as it is. Example: [...] tar cvfz /backup/my_own_filename_.tar.gz

    +

    Restore

    +
    cd /path/to/mailcow-dockerized
    +docker run --rm -it -v $(docker inspect --format '{{ range .Mounts }}{{ if eq .Destination "/var/vmail" }}{{ .Name }}{{ end }}{{ end }}' $(docker-compose ps -q dovecot-mailcow)):/vmail -v ${PWD}:/backup debian:stretch-slim tar xvfz /backup/backup_vmail.tar.gz
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-backup_restore-mysql/index.html b/u_e-backup_restore-mysql/index.html new file mode 100644 index 000000000..b4de044f5 --- /dev/null +++ b/u_e-backup_restore-mysql/index.html @@ -0,0 +1,2020 @@ + + + + + + + + + + + + + + + + + + + MySQL - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    MySQL

    + +

    Backup

    +
    cd /path/to/mailcow-dockerized
    +source mailcow.conf
    +DATE=$(date +"%Y%m%d_%H%M%S")
    +docker-compose exec -T mysql-mailcow mysqldump --default-character-set=utf8mb4 -u${DBUSER} -p${DBPASS} ${DBNAME} > backup_${DBNAME}_${DATE}.sql
    +
    +

    Restore

    +
    +

    Warning

    +

    You should redirect the SQL dump without docker-compose to prevent parsing errors.

    +
    +
    cd /path/to/mailcow-dockerized
    +source mailcow.conf
    +docker exec -i $(docker-compose ps -q mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} < backup_file.sql
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-change_config/index.html b/u_e-change_config/index.html new file mode 100644 index 000000000..b632d09dd --- /dev/null +++ b/u_e-change_config/index.html @@ -0,0 +1,1991 @@ + + + + + + + + + + + + + + + + + + + Adjust Service Configurations - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Adjust Service Configurations

    + +

    The most important configuration files are mounted from the host into the related containers:

    +
    data/conf
    +├── unbound
    +│   └── unbound.conf
    +├── dovecot
    +│   ├── dovecot.conf
    +│   ├── dovecot-master.passwd
    +│   ├── sieve_after
    +│   └── sql
    +│       ├── dovecot-dict-sql.conf
    +│       └── dovecot-mysql.conf
    +├── mysql
    +│   └── my.cnf
    +├── nginx
    +│   ├── dynmaps.conf
    +│   ├── site.conf
    +│   └── templates
    +│       ├── listen_plain.template
    +│       ├── listen_ssl.template
    +│       └── server_name.template
    +├── postfix
    +│   ├── main.cf
    +│   ├── master.cf
    +│   ├── postscreen_access.cidr
    +│   ├── smtp_dsn_filter
    +│   └── sql
    +│       ├── mysql_relay_recipient_maps.cf
    +│       ├── mysql_tls_enforce_in_policy.cf
    +│       ├── mysql_tls_enforce_out_policy.cf
    +│       ├── mysql_virtual_alias_domain_catchall_maps.cf
    +│       ├── mysql_virtual_alias_domain_maps.cf
    +│       ├── mysql_virtual_alias_maps.cf
    +│       ├── mysql_virtual_domains_maps.cf
    +│       ├── mysql_virtual_mailbox_maps.cf
    +│       ├── mysql_virtual_relay_domain_maps.cf
    +│       ├── mysql_virtual_sender_acl.cf
    +│       └── mysql_virtual_spamalias_maps.cf
    +├── rspamd
    +│   ├── dynmaps
    +│   │   ├── authoritative.php
    +│   │   ├── settings.php
    +│   │   ├── tags.php
    +│   │   └── vars.inc.php -> ../../../web/inc/vars.inc.php
    +│   ├── local.d
    +│   │   ├── dkim.conf
    +│   │   ├── metrics.conf
    +│   │   ├── options.inc
    +│   │   ├── redis.conf
    +│   │   ├── rspamd.conf.local
    +│   │   └── statistic.conf
    +│   ├── lua
    +│   │   └── rspamd.local.lua
    +│   └── override.d
    +│       ├── logging.inc
    +│       ├── worker-controller.inc
    +│       └── worker-normal.inc
    +└── sogo
    +    ├── sieve.creds
    +    └── sogo.conf
    +
    +

    Just change the according configuration file on the host and restart the related service: +

    docker-compose restart service-mailcow
    +

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-deinstall/index.html b/u_e-deinstall/index.html new file mode 100644 index 000000000..5a4e34669 --- /dev/null +++ b/u_e-deinstall/index.html @@ -0,0 +1,1940 @@ + + + + + + + + + + + + + + + + + + + Deinstall - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Deinstall

    + +

    To remove mailcow: dockerized with all it's volumes, images and containers do:

    +
    docker-compose down -v --rmi all --remove-orphans
    +
    +
    +

    Info

    +
      +
    • -v Remove named volumes declared in the volumes section of the Compose file and anonymous volumes attached to containers.
    • +
    • --rmi Remove images. Type must be one of: all: Remove all images used by any service. local: Remove only images that don't have a custom tag set by the image field.
    • +
    • --remove-orphans Remove containers for services not defined in the compose file.
    • +
    • By default docker-compose down only removes currently active containers and networks defined in the docker-compose.yml.
    • +
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-docker-cust_dockerfiles/index.html b/u_e-docker-cust_dockerfiles/index.html new file mode 100644 index 000000000..3cbc4c8ff --- /dev/null +++ b/u_e-docker-cust_dockerfiles/index.html @@ -0,0 +1,1939 @@ + + + + + + + + + + + + + + + + + + + Customize Dockerfiles - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Customize Dockerfiles

    + +

    You need to copy the override file with corresponding build tags to the mailcow: dockerized root folder (i.e. /opt/mailcow-dockerized):

    +
    cp helper-scripts/docker-compose.override.yml.d/BUILD_FLAGS/docker-compose.override.yml docker-compose.override.yml
    +
    +

    Make your changes in data/Dockerfiles/$service and build the image locally:

    +
    docker build data/Dockerfiles/service -t mailcow/$service
    +
    +

    Now auto-recreate modified containers:

    +
    docker-compose up -d
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-docker-dc_bash_compl/index.html b/u_e-docker-dc_bash_compl/index.html new file mode 100644 index 000000000..b1dd32247 --- /dev/null +++ b/u_e-docker-dc_bash_compl/index.html @@ -0,0 +1,1933 @@ + + + + + + + + + + + + + + + + + + + Docker Compose Bash Completion - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Docker Compose Bash Completion

    + +

    To get some sexy bash completion inside your containers simply execute the following:

    +
    curl -L https://raw.githubusercontent.com/docker/compose/$(docker-compose version --short)/contrib/completion/bash/docker-compose -o /etc/bash_completion.d/docker-compose
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-dovecot-any_acl/index.html b/u_e-dovecot-any_acl/index.html new file mode 100644 index 000000000..e0461d3cd --- /dev/null +++ b/u_e-dovecot-any_acl/index.html @@ -0,0 +1,1935 @@ + + + + + + + + + + + + + + + + + + + Enable "any" ACL settings - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Enable "any" ACL settings

    + +

    On August the 17th, we disabled the possibility to share with "any" or "all authenticated users" by default.

    +

    This function can be re-enabled by setting ACL_ANYONE to allow in mailcow.conf:

    +
    ACL_ANYONE=allow
    +
    +

    Apply the changes by running docker-compose up -d.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-dovecot-expunge/index.html b/u_e-dovecot-expunge/index.html new file mode 100644 index 000000000..83e9767e9 --- /dev/null +++ b/u_e-dovecot-expunge/index.html @@ -0,0 +1,2107 @@ + + + + + + + + + + + + + + + + + + + Expunge a Users mails - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Expunge a Users mails

    + +

    If you want to delete old mails out of the .Junk or .Trash folders or maybe delete all read mails that are older than a certain amount of time you may use dovecot's tool doveadm man doveadm-expunge.

    +

    The manual way

    +

    That said, let's dive in:

    +

    Delete a user's mails inside the junk folder that are read and older than 4 hours

    +
    docker-compose exec dovecot-mailcow doveadm expunge -u 'mailbox@example.com' mailbox 'Junk' SEEN not SINCE 4h
    +
    +

    Delete all user's mails in the junk folder that are older than 7 days

    +
    docker-compose exec dovecot-mailcow doveadm expunge -A mailbox 'Junk' savedbefore 7d
    +
    +

    Delete all mails (of all users) in all folders that are older than 52 weeks (internal date of the mail, not the date it was saved on the system => before instead of savedbefore). Useful for deleting very old mails on all users and folders (thus especially useful for GDPR-compliance).

    +
    docker-compose exec dovecot-mailcow doveadm expunge -A mailbox % before 52w
    +
    +

    Delete mails inside a custom folder inside a user's inbox that are not flagged and older than 2 weeks

    +
    docker-compose exec dovecot-mailcow doveadm expunge -u 'mailbox@example.com' mailbox 'INBOX/custom-folder' not FLAGGED not SINCE 2w
    +
    +
    +

    Info

    +

    For possible time spans or search keys have a look at man doveadm-search-query

    +
    +

    Job scheduler

    +

    via the host system cron

    +

    If you want to automate such a task you can create a cron job on your host that calls a script like the one below:

    +
    #!/bin/bash
    +# Path to mailcow-dockerized, e.g. /opt/mailcow-dockerized
    +cd /path/to/your/mailcow-dockerized
    +
    +/usr/local/bin/docker-compose exec -T dovecot-mailcow doveadm expunge -A mailbox 'Junk' savedbefore 2w
    +/usr/local/bin/docker-compose exec -T dovecot-mailcow doveadm expunge -A mailbox 'Junk' SEEN not SINCE 12h
    +[...]
    +
    +

    To create a cron job you may execute crontab -e and insert something like the following to execute a script:

    +
    # Execute everyday at 04:00 A.M.
    +0 4 * * * /path/to/your/expunge_mailboxes.sh
    +
    +

    via Docker job scheduler

    +

    To archive this with a docker job scheduler use this docker-compose.override.yml with your mailcow:

    +
    version: '2.1'
    +
    +services:
    +
    +  ofelia:
    +    image: mcuadros/ofelia:latest
    +    restart: always
    +    command: daemon --docker
    +    volumes:
    +      - /var/run/docker.sock:/var/run/docker.sock:ro   
    +    network_mode: none
    +
    +  dovecot-mailcow:
    +    labels:
    +      - "ofelia.enabled=true"
    +      - "ofelia.job-exec.dovecot-expunge-trash.schedule=0 4 * * *"
    +      - "ofelia.job-exec.dovecot-expunge-trash.command=doveadm expunge -A mailbox 'Junk' savedbefore 2w"
    +      - "ofelia.job-exec.dovecot-expunge-trash.tty=false"
    +
    +

    The job controller just need access to the docker control socket to be able to emulate the behavior of "exec". Then we add a few label to our dovecot-container to activate the job scheduler and tell him in a cron compatible scheduling format when to run. If you struggle with that schedule string you can use crontab guru. +This docker-compose.override.yml deletes all mails older then 2 weeks from the "Junk" folder every day at 4 am. To see if things ran proper, you can not only see in your mailbox but also check Ofelia's docker log if it looks something like this:

    +
    common.go:124 ▶ NOTICE [Job "dovecot-expunge-trash" (8759567efa66)] Started - doveadm expunge -A mailbox 'Junk' savedbefore 2w,
    +common.go:124 ▶ NOTICE [Job "dovecot-expunge-trash" (8759567efa66)] Finished in "285.032291ms", failed: false, skipped: false, error: none,
    +
    +

    If it failed it will say so and give you the output of the doveadm in the log to make it easy on you to debug.

    +

    In case you want to add more jobs, ensure you change the "dovecot-expunge-trash" part after "ofelia.job-exec." to something else, it defines the name of the job. Syntax of the labels you find at mcuadros/ofelia.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-dovecot-extra_conf/index.html b/u_e-dovecot-extra_conf/index.html new file mode 100644 index 000000000..70cd12312 --- /dev/null +++ b/u_e-dovecot-extra_conf/index.html @@ -0,0 +1,1934 @@ + + + + + + + + + + + + + + + + + + + Customize/Expand dovecot.conf - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Customize/Expand dovecot.conf

    + +

    Create a file data/conf/dovecot/extra.conf - if missing - and add your additional content here.

    +

    Restart dovecot-mailcow to apply your changes:

    +
    docker-compose restart dovecot-mailcow
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-dovecot-fts/index.html b/u_e-dovecot-fts/index.html new file mode 100644 index 000000000..cb4faa58e --- /dev/null +++ b/u_e-dovecot-fts/index.html @@ -0,0 +1,2028 @@ + + + + + + + + + + + + + + + + + + + FTS (Solr) - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    FTS (Solr)

    + +

    Solr is used for setups with memory >= 3.5 GiB to provide full-text search in Dovecot.

    +

    Please be aware that applications like Solr may need maintenance from time to time.

    +

    Besides that, Solr will eat a lot of RAM, depending on the usage of your server. Please avoid it on machines with less than 3 GB RAM.

    +

    The default heap size (1024 M) is defined in mailcow.conf.

    +

    Since we run in Docker and create our containers with the "restart: always" flag, a oom situation will at least only trigger a restart of the container.

    + +
    # single user
    +docker-compose exec dovecot-mailcow doveadm fts rescan -u user@domain
    +# all users
    +docker-compose exec dovecot-mailcow doveadm fts rescan -A
    +
    +

    Dovecot Wiki: "Scan what mails exist in the full text search index and compare those to what actually exist in mailboxes. This removes mails from the index that have already been expunged and makes sure that the next doveadm index will index all the missing mails (if any)."

    +

    This does not re-index a mailbox. It basically repairs a given index.

    +

    If you want to re-index data immediately, you can run the followig command, where '*' can also be a mailbox mask like 'Sent'. You do not need to run these commands, but it will speed things up a bit:

    +
    # single user
    +docker-compose exec dovecot-mailcow doveadm index -u user@domain '*'
    +# all users, but obviously slower and more dangerous
    +docker-compose exec dovecot-mailcow doveadm index -A '*'
    +
    +

    This will take some time depending on your machine and Solr can run oom, monitor it!

    +

    Because re-indexing is very sensible, we did not include it to mailcow UI. You will need to take care of any errors while re-indexing a mailbox.

    +

    Delete mailbox data

    +

    mailcow will purge index data of a user when deleting a mailbox.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-dovecot-idle_interval/index.html b/u_e-dovecot-idle_interval/index.html new file mode 100644 index 000000000..f0b3db65d --- /dev/null +++ b/u_e-dovecot-idle_interval/index.html @@ -0,0 +1,2075 @@ + + + + + + + + + + + + + + + + + + + IMAP IDLE interval - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Changing the IMAP IDLE interval

    +

    What is the IDLE interval?

    +

    Per default, Dovecot sends a "I'm still here" notification to every client that has an open connection with Dovecot to get mails as quickly as possible without manually polling it (IMAP PUSH). This notification is controlled by the setting imap_idle_notify_interval, which defaults to 2 minutes.

    +

    A short interval results in the client getting a lot of messages for this connection, which is bad for mobile devices, because every time the device receives this message, the mailing app has to wake up. This can result in unnecessary battery drain.

    +

    Edit the value

    +

    Change configuration

    +

    Create a new file data/conf/dovecot/extra.conf (or edit it if it already exists). +Insert the setting followed by the new value. For example, to set the interval to 5 minutes you could type:

    +
    imap_idle_notify_interval = 5 mins
    +
    +

    29 minutes is the maximum value allowed by the corresponding RFC.

    +
    +

    Warning

    +

    This isn't a default setting in mailcow because we don't know how this setting changes the behavior of other clients. Be careful if you change this and monitor different behavior.

    +
    +

    Reload Dovecot

    +

    Now reload Dovecot: +

    docker-compose exec dovecot-mailcow dovecot reload
    +

    +
    +

    Info

    +

    You can check the value of this setting with +

    docker-compose exec dovecot-mailcow dovecot -a | grep "imap_idle_notify_interval"
    +
    +If you didn't change it, it should be at 2m. If you did change it, you should see your new value.

    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-dovecot-mail-crypt/index.html b/u_e-dovecot-mail-crypt/index.html new file mode 100644 index 000000000..a91ddbd03 --- /dev/null +++ b/u_e-dovecot-mail-crypt/index.html @@ -0,0 +1,1958 @@ + + + + + + + + + + + + + + + + + + + Mail crypt - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Mail crypt

    + +

    Mails are stored compressed (lz4) and encrypted. The key pair can be found in crypt-vol-1.

    +

    If you want to decode/encode existing maildir files, you can use the following script at your own risk:

    +

    Enter Dovecot by running docker-compose exec dovecot-mailcow /bin/bash in the mailcow-dockerized location.

    +
    # Decrypt /var/vmail
    +find /var/vmail/ -type f -regextype egrep -regex '.*S=.*W=.*' | while read -r file; do
    +if [[ $(head -c7 "$file") == "CRYPTED" ]]; then
    +doveadm fs get compress lz4:0:crypt:private_key_path=/mail_crypt/ecprivkey.pem:public_key_path=/mail_crypt/ecpubkey.pem:posix:prefix=/ \
    +  "$file" > "/tmp/$(basename "$file")"
    +  if [[ -s "/tmp/$(basename "$file")" ]]; then
    +    chmod 600 "/tmp/$(basename "$file")"
    +    chown 5000:5000 "/tmp/$(basename "$file")"
    +    mv "/tmp/$(basename "$file")" "$file"
    +  else
    +    rm "/tmp/$(basename "$file")"
    +  fi
    +fi
    +done
    +
    +# Encrypt /var/vmail
    +find /var/vmail/ -type f -regextype egrep -regex '.*S=.*W=.*' | while read -r file; do
    +if [[ $(head -c7 "$file") != "CRYPTED" ]]; then
    +doveadm fs put crypt private_key_path=/mail_crypt/ecprivkey.pem:public_key_path=/mail_crypt/ecpubkey.pem:posix:prefix=/ \
    +  "$file" "$file"
    +  chmod 600 "$file"
    +  chown 5000:5000 "$file"
    +fi
    +done
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-dovecot-more/index.html b/u_e-dovecot-more/index.html new file mode 100644 index 000000000..65da38583 --- /dev/null +++ b/u_e-dovecot-more/index.html @@ -0,0 +1,2039 @@ + + + + + + + + + + + + + + + + + + + More Examples with DOVEADM - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    More Examples with DOVEADM

    + +

    Here is just an unsorted list of useful doveadm commands that could be useful.

    +

    doveadm quota

    +

    The quota get and quota recalc1 commands are used to display or recalculate the current user's quota usage. The reported values are in kilobytes.

    +

    To list the current quota status for a user / mailbox, do:

    +
    doveadm quota get -u 'mailbox@example.org'
    +
    +

    To list the quota storage value for all users, do:

    +
    doveadm quota get -A |grep "STORAGE"
    +
    +

    Recalculate a single user's quota usage:

    +
    doveadm quota recalc -u 'mailbox@example.org'
    +
    + +

    The doveadm search2 command is used to find messages matching your query. It can return the username, mailbox-GUID / -UID and message-GUIDs / -UIDs.

    +

    To view the number of messages, by user, in their .Trash folder:

    +
    doveadm search -A mailbox 'Trash' | awk '{print $1}' | sort | uniq -c
    +
    +

    Show all messages in a user's inbox older then 90 days:

    +
    doveadm search -u 'mailbox@example.org' mailbox 'INBOX' savedbefore 90d
    +
    +

    Show all messages in any folder that are older then 30 days for mailbox@example.org:

    +
    doveadm search -u 'mailbox@example.org' mailbox "*" savedbefore 30d
    +
    + + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-dovecot-public_folder/index.html b/u_e-dovecot-public_folder/index.html new file mode 100644 index 000000000..afbf49234 --- /dev/null +++ b/u_e-dovecot-public_folder/index.html @@ -0,0 +1,2018 @@ + + + + + + + + + + + + + + + + + + + Public folders - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Public folders

    + +

    Create a new public namespace "Public" and a mailbox "Develcow" inside that namespace:

    +

    Edit or create data/conf/dovecot/extra.conf, add:

    +
    namespace {
    +  type = public
    +  separator = /
    +  prefix = Public/
    +  location = maildir:/var/vmail/public:INDEXPVT=~/public
    +  subscriptions = yes
    +  mailbox "Develcow" {
    +    auto = subscribe
    +  }
    +}
    +
    +

    :INDEXPVT=~/public can be omitted if per-user seen flags are not wanted.

    +

    The new mailbox in the public namespace will be auto-subscribed by users.

    +

    To allow all authenticated users access full to that new mailbox (not the whole namespace), run:

    +
    docker-compose exec dovecot-mailcow doveadm acl set -A "Public/Develcow" "authenticated" lookup read write write-seen write-deleted insert post delete expunge create
    +
    +

    Adjust the command to your needs if you like to assign more granular rights per user (use -u user@domain instead of -A for example).

    +

    Allow authenticated users access to the whole public namespace

    +

    To allow all authenticated users access full access to the whole public namespace and its subfolders, create a new dovecot-acl file in the namespace root directory:

    +

    Open/edit/create /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data/public/dovecot-acl (adjust the path accordingly) to create the global ACL file with the following content:

    +
    authenticated kxeilprwts
    +
    +

    kxeilprwts equals to lookup read write write-seen write-deleted insert post delete expunge create.

    +

    You can use doveadm acl set -u user@domain "Public/Develcow" user=user@domain lookup read to limit access for a single user. You may also turn it around to limit access for all users to "lr" and grant only some users full access.

    +

    See Dovecot ACL for further information about ACL.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-dovecot-static_master/index.html b/u_e-dovecot-static_master/index.html new file mode 100644 index 000000000..b843e5cb8 --- /dev/null +++ b/u_e-dovecot-static_master/index.html @@ -0,0 +1,1942 @@ + + + + + + + + + + + + + + + + + + + Static master user - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Static master user

    + +

    Random master usernames and passwords are automatically created on every restart of dovecot-mailcow.

    +

    That's recommended and should not be changed.

    +

    If you need the user to be static anyway, please specify two variables in mailcow.conf.

    +

    Both parameters must not be empty!

    +
    DOVECOT_MASTER_USER=mymasteruser
    +DOVECOT_MASTER_PASS=mysecretpass
    +
    +

    Run docker-compose up -d to apply your changes.

    +

    The static master username will be expanded to DOVECOT_MASTER_USER@mailcow.local.

    +

    To login as test@example.org this would equal to test@example.org*mymasteruser@mailcow.local with the specified password above.

    +

    A login to SOGo is not possible with this username. A click-to-login function for SOGo is available for admins as described here +No master user is required.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-dovecot-vmail-volume/index.html b/u_e-dovecot-vmail-volume/index.html new file mode 100644 index 000000000..0e12ffb24 --- /dev/null +++ b/u_e-dovecot-vmail-volume/index.html @@ -0,0 +1,2076 @@ + + + + + + + + + + + + + + + + + + + Move Maildir (vmail) - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Move Maildir (vmail)

    + +

    The "new" way

    +

    WARNING: Newer Docker versions seem to complain about existing volumes. You can fix this temporarily by removing the existing volume and start mailcow with the override file. But it seems to be problematic after a reboot (needs to be confirmed).

    +

    An easy, dirty, yet stable workaround is to stop mailcow (docker-compose down), remove /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data and create a new link to your remote filesystem location, for example:

    +
    mv /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data_backup
    +ln -s /mnt/volume-xy/vmail_data /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data
    +
    +

    Start mailcow afterwards.

    +
    +

    The "old" way

    +

    If you want to use another folder for the vmail-volume, you can create a docker-compose.override.yml file and add the following content:

    +
    version: '2.1'
    +volumes:
    +  vmail-vol-1:
    +    driver_opts:
    +      type: none
    +      device: /data/mailcow/vmail   
    +      o: bind
    +
    +

    Moving an existing vmail folder:

    +
      +
    • Locate the current vmail folder by its "Mountpoint" attribute: docker volume inspect mailcowdockerized_vmail-vol-1
    • +
    +
    [
    +    {
    +        "CreatedAt": "2019-06-16T22:08:34+02:00",
    +        "Driver": "local",
    +        "Labels": {
    +            "com.docker.compose.project": "mailcowdockerized",
    +            "com.docker.compose.version": "1.23.2",
    +            "com.docker.compose.volume": "vmail-vol-1"
    +        },
    +        "Mountpoint": "/var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data",
    +        "Name": "mailcowdockerized_vmail-vol-1",
    +        "Options": null,
    +        "Scope": "local"
    +    }
    +]
    +
    +
      +
    • Copy the content of the Mountpoint folder to the new location (e.g. /data/mailcow/vmail) using cp -a, rsync -a or a similar non strcuture breaking copy command
    • +
    • Stop mailcow by executing docker-compose down from within your mailcow root folder (e.g. /opt/mailcow-dockerized)
    • +
    • Create the file docker-compose.override.yml, edit the device path accordingly
    • +
    • Delete the current vmail folder: docker volume rm mailcowdockerized_vmail-vol-1
    • +
    • Start mailcow by executing docker-compose up -d from within your mailcow root folder (e.g. /opt/mailcow-dockerized)
    • +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-fido2/index.html b/u_e-fido2/index.html new file mode 100644 index 000000000..4d8a5a460 --- /dev/null +++ b/u_e-fido2/index.html @@ -0,0 +1,2027 @@ + + + + + + + + + + + + + + + + + + + WebAuthn / FIDO2 - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    WebAuthn / FIDO2

    + +

    How is UV handled in mailcow?

    +

    The UV flag (as in "user verification") enforces WebAuthn to verify the user before it allows access to the key (think of a PIN). We don't enforce UV to allow logins via iOS and NFC (YubiKey).

    +

    Login and key processing

    +

    mailcow uses client-side key processing. We ask the authenticator (i.e. YubiKey) to save the registration in its memory.

    +

    A user does not need to enter a username. The available credentials - if any - will be shown to the user when selecting the "key login" via mailcow UI login.

    +

    When calling the login process, the authenticator is not given any credential IDs. This will force it to lookup credentials in its own memory.

    +

    Who can use WebAuthn to login to mailcow?

    +

    As of today, only administrators and domain administrators are able to setup WebAuthn/FIDO2.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-mailcow_ui-bl_wl/index.html b/u_e-mailcow_ui-bl_wl/index.html new file mode 100644 index 000000000..10ddc3b7b --- /dev/null +++ b/u_e-mailcow_ui-bl_wl/index.html @@ -0,0 +1,1937 @@ + + + + + + + + + + + + + + + + + + + Blacklist / Whitelist - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Blacklist / Whitelist

    + +

    To add or edit an entry to your domain wide filter table, login to your mailcow UI as (domain) administrator.

    +

    Black- and Whitelist configuration

    +
    +

    Info

    +

    Be aware that a user may override this setting by setting his own black- and whitelist!

    +
    +

    There is also a global filter table in /admin to configure a server-wide filter for multiple Regex maps (Todo: Screenshots).

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-mailcow_ui-config/index.html b/u_e-mailcow_ui-config/index.html new file mode 100644 index 000000000..707f65b08 --- /dev/null +++ b/u_e-mailcow_ui-config/index.html @@ -0,0 +1,1954 @@ + + + + + + + + + + + + + + + + + + + Configuration - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Configuration

    + +

    Several configuration parameters of the mailcow UI can be changed by creating a file data/web/inc/vars.local.inc.php which overrides defaults settings found in data/web/inc/vars.inc.php.

    +

    The local configuration file is persistent over updates of mailcow. Try not to change values inside data/web/inc/vars.inc.php, but use them as template for the local override.

    +

    mailcow UI configuration parameters can be used to...

    +
      +
    • ...change the default language1
    • +
    • ...change the default bootstrap theme
    • +
    • ...set a password complexity regex
    • +
    • ...enable DKIM private key visibility
    • +
    • ...set a pagination trigger size
    • +
    • ...set default mailbox attributes
    • +
    • ...change session lifetimes
    • +
    • ...create fixed app menus (which cannot be changed in mailcow UI)
    • +
    • ...set a default "To" field for relayhost tests
    • +
    • ...set a timeout for Docker API requests
    • +
    • ...toggle IP anonymization
    • +
    +
    +
    +
      +
    1. +

      To change SOGos default language, you will need to edit data/conf/sogo/sogo.conf and replace "English" by your preferred language. 

      +
    2. +
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-mailcow_ui-pushover/index.html b/u_e-mailcow_ui-pushover/index.html new file mode 100644 index 000000000..d251ad858 --- /dev/null +++ b/u_e-mailcow_ui-pushover/index.html @@ -0,0 +1,1942 @@ + + + + + + + + + + + + + + + + + + + Pushover - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Pushover

    + +
    +

    Info

    +

    Pushover makes it easy to get real-time notifications on your Android, iPhone, iPad, and Desktop

    +
    +

    You can use Pushover to get a push notification on every mail you receive for each mailbox where you enabled this feature.

    +

    1. As admin open your mailbox' settings and scroll down to the Pushover settings

    +

    2. Register yourself on Pushover

    +

    3. Put your 'User Key' in the 'User/Group Key' field in your mailbox settings

    +

    4. Create an Applications to get the API Token/Key which you also need to put in your mailbox settings

    +

    5. Optional you can edit the notification title/text and define certain sender email addresses where a push notification is triggered

    +

    6. Save everything and then you can verify your credentials

    +

    If everything is done you can test sending a mail and you will receive a push message on your phone

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-mailcow_ui-spamalias/index.html b/u_e-mailcow_ui-spamalias/index.html new file mode 100644 index 000000000..eea1bca47 --- /dev/null +++ b/u_e-mailcow_ui-spamalias/index.html @@ -0,0 +1,1933 @@ + + + + + + + + + + + + + + + + + + + Temporary email aliases - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Temporary email aliases

    + +

    These temporary email aliases are mostly used for places where we need to provide an email address but don't want future correspondence with. They are also called spam alias.

    +

    To create, delete or extend a temporary email aliases you need to login to mailcow's UI as a mailbox user and navigate to the tab Temporary email aliases:

    +

    How to set spam- or temporary email aliases in mailcow

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-mailcow_ui-spamfilter/index.html b/u_e-mailcow_ui-spamfilter/index.html new file mode 100644 index 000000000..9a93f4698 --- /dev/null +++ b/u_e-mailcow_ui-spamfilter/index.html @@ -0,0 +1,1937 @@ + + + + + + + + + + + + + + + + + + + Spamfilter - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Spamfilter

    + +

    A mailbox user may adjust the spam filter and black- / whitelist settings for his mailbox individually by navigating to the Spam filter tab in the users mailcow UI.

    +

    Where to adjust the users spam, black- and whitelist settings

    +
    +

    Info

    +

    For global adjustments on your spam filter please check our section on Rspamd. +For a domain wide black- and whitelist please check our guide on Black / Whitelist

    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-mailcow_ui-tagging/index.html b/u_e-mailcow_ui-tagging/index.html new file mode 100644 index 000000000..d09575a86 --- /dev/null +++ b/u_e-mailcow_ui-tagging/index.html @@ -0,0 +1,2011 @@ + + + + + + + + + + + + + + + + + + + Tagging - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Tagging

    + +

    Mailbox users can tag their mail address like in me+facebook@example.org. They can control the tag handling in the users mailcow UI panel. +mailcow mail tagging settings

    +

    Available Actions

    +

    1. Move this message to a sub folder "facebook" (will be created lower case if not existing)

    +

    2. Prepend the tag to the subject: "[facebook] Subject"

    +

    Please note: Uppercase tags are converted to lowercase except for the first letter. If you want to keep the tag as it is, please apply the following diff and restart mailcow: +

    diff --git a/data/conf/dovecot/global_sieve_after b/data/conf/dovecot/global_sieve_after
    +index e047136e..933c4137 100644
    +--- a/data/conf/dovecot/global_sieve_after
    ++++ b/data/conf/dovecot/global_sieve_after
    +@@ -15,7 +15,7 @@ if allof (
    +   envelope :detail :matches "to" "*",
    +   header :contains "X-Moo-Tag" "YES"
    +   ) {
    +-  set :lower :upperfirst "tag" "${1}";
    ++  set "tag" "${1}";
    +   if mailboxexists "INBOX/${1}" {
    +     fileinto "INBOX/${1}";
    +   } else {
    +

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-mailcow_ui-tfa/index.html b/u_e-mailcow_ui-tfa/index.html new file mode 100644 index 000000000..27e6b3962 --- /dev/null +++ b/u_e-mailcow_ui-tfa/index.html @@ -0,0 +1,2039 @@ + + + + + + + + + + + + + + + + + + + Two-Factor Authentication - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Two-Factor Authentication

    + +

    So far three methods for Two-Factor Authentication are implemented: U2F, Yubi OTP, and TOTP

    +
      +
    • For U2F to work, you need an encrypted connection to the server (HTTPS) as well as a FIDO security key.
    • +
    • Both U2F and Yubi OTP work well with the fantastic Yubikey.
    • +
    • While Yubi OTP needs an active internet connection and an API ID + key, U2F will work with any FIDO U2F USB key out of the box, but can only be used when mailcow is accessed over HTTPS.
    • +
    • U2F and Yubi OTP support multiple keys per user.
    • +
    • As the third TFA method mailcow uses TOTP: time-based one-time passwords. Those passwords can be generated with apps like "Google Authenticator" after initially scanning a QR code or entering the given secret manually.
    • +
    +

    As administrator you are able to temporary disable a domain administrators TFA login until they successfully logged in.

    +

    The key used to login will be displayed in green, while other keys remain grey.

    +

    Information on how to remove 2FA can be found here.

    +

    Yubi OTP

    +

    The Yubi API ID and Key will be checked against the Yubico Cloud API. When setting up TFA you will be asked for your personal API account for this key. +The API ID, API key and the first 12 characters (your YubiKeys ID in modhex) are stored in the MySQL table as secret.

    +

    U2F

    +

    Only Google Chrome (+derivatives) and Opera support U2F authentication to this day natively. +Since version 67 Mozilla Firefox can handle U2F natively. (Source)

    +

    U2F works without an internet connection.

    +

    TOTP

    +

    The best known TFA method mostly used with a smartphone.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-mailpiler-integration/index.html b/u_e-mailpiler-integration/index.html new file mode 100644 index 000000000..c0fbd7284 --- /dev/null +++ b/u_e-mailpiler-integration/index.html @@ -0,0 +1,15 @@ + + + + + + Redirecting... + + + + + + +Redirecting... + + diff --git a/u_e-nginx/index.html b/u_e-nginx/index.html new file mode 100644 index 000000000..68f8b006e --- /dev/null +++ b/u_e-nginx/index.html @@ -0,0 +1,1994 @@ + + + + + + + + + + + + + + + + + + + Custom sites - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Custom sites

    + +

    To create persistent (over updates) sites hosted by mailcow: dockerized, a new site configuration must be placed inside data/conf/nginx/:

    +
    nano data/conf/nginx/my_custom_site.conf
    +
    +

    A good template to begin with:

    +
    server {
    +  ssl_certificate /etc/ssl/mail/cert.pem;
    +  ssl_certificate_key /etc/ssl/mail/key.pem;
    +  index index.php index.html;
    +  client_max_body_size 0;
    +  root /web;
    +  include /etc/nginx/conf.d/listen_plain.active;
    +  include /etc/nginx/conf.d/listen_ssl.active;
    +  server_name mysite.example.org;
    +
    +  location ^~ /.well-known/acme-challenge/ {
    +    allow all;
    +    default_type "text/plain";
    +  }
    +
    +  if ($scheme = http) {
    +    return 301 https://$server_name$request_uri;
    +  }
    +}
    +
    +

    Another example with a reverse proxy configuration:

    +
    server {
    +  ssl_certificate /etc/ssl/mail/cert.pem;
    +  ssl_certificate_key /etc/ssl/mail/key.pem;
    +  index index.php index.html;
    +  client_max_body_size 0;
    +  root /web;
    +  include /etc/nginx/conf.d/listen_plain.active;
    +  include /etc/nginx/conf.d/listen_ssl.active;
    +  server_name example.domain.tld;
    +
    +  location ^~ /.well-known/acme-challenge/ {
    +    allow all;
    +    default_type "text/plain";
    +  }
    +
    +  if ($scheme = http) {
    +    return 301 https://$host$request_uri;
    +  }
    +
    +  location / {
    +    proxy_pass http://service:3000/;
    +    proxy_set_header Host $http_host;
    +    proxy_set_header X-Real-IP $remote_addr;
    +    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    +    proxy_set_header X-Forwarded-Proto $scheme;
    +    client_max_body_size 0;
    +  }
    +}
    +
    +

    The filename is not important, as long as the filename carries a .conf extension.

    +

    It is also possible to extend the configuration of the default file site.conf file:

    +
    nano data/conf/nginx/site.my_content.custom
    +
    +

    This filename does not need to have a ".conf" extension, but follows the pattern site.*.custom, where * is a custom name.

    +

    If PHP is to be included in a custom site, please use the PHP-FPM listener on phpfpm:9002 or create a new listener in data/conf/phpfpm/php-fpm.d/pools.conf.

    +

    Restart Nginx (and PHP-FPM, if a new listener was created):

    +
    docker-compose restart nginx-mailcow
    +docker-compose restart php-fpm-mailcow
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-postfix-attachment_size/index.html b/u_e-postfix-attachment_size/index.html new file mode 100644 index 000000000..b6a858e81 --- /dev/null +++ b/u_e-postfix-attachment_size/index.html @@ -0,0 +1,1934 @@ + + + + + + + + + + + + + + + + + + + Max. message size (attachment size) - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Max. message size (attachment size)

    + +

    Open data/conf/postfix/main.cf and set the message_size_limit accordingly in bytes.

    +

    Restart Postfix:

    +
    docker-compose restart postfix-mailcow
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-postfix-custom_transport/index.html b/u_e-postfix-custom_transport/index.html new file mode 100644 index 000000000..ec915e604 --- /dev/null +++ b/u_e-postfix-custom_transport/index.html @@ -0,0 +1,1933 @@ + + + + + + + + + + + + + + + + + + + Custom transport maps - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Custom transport maps

    + +

    For transport maps other than those to be configured in mailcow UI, please use data/conf/postfix/custom_transport.pcre to prevent existing maps or settings from being overwritten by updates.

    +

    In most cases using this file is not necessary. Please make sure mailcow UI is not able to route your desired traffic properly before using that file.

    +

    The file needs valid PCRE content and can break Postfix, if configured incorrectly.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-postfix-disable_sender_verification/index.html b/u_e-postfix-disable_sender_verification/index.html new file mode 100644 index 000000000..8b017174f --- /dev/null +++ b/u_e-postfix-disable_sender_verification/index.html @@ -0,0 +1,2020 @@ + + + + + + + + + + + + + + + + + + + Disable Sender Addresses Verification - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Disable Sender Addresses Verification

    + +

    New guide

    +

    Edit a mailbox and select "Allow to send as *".

    +

    For historical reasons we kept the old and deprecated guide below:

    +

    Deprecated guide (DO NOT USE ON NEWER MAILCOWS!)

    +

    This option is not best-practice and should only be implemented when there is no other option available to achieve whatever you are trying to do.

    +

    Simply create a file data/conf/postfix/check_sasl_access and enter the following content. This user must exist in your installation and needs to authenticate before sending mail. +

    user-to-allow-everything@example.com OK
    +

    +

    Open data/conf/postfix/main.cf and find smtpd_sender_restrictions. Prepend check_sasl_access hash:/opt/postfix/conf/check_sasl_access like this: +

    smtpd_sender_restrictions = check_sasl_access hash:/opt/postfix/conf/check_sasl_access reject_authenticated_sender_login_mismatch [...]
    +

    +

    Run postmap on check_sasl_access:

    +
    docker-compose exec postfix-mailcow postmap /opt/postfix/conf/check_sasl_access
    +
    +

    Restart the Postfix container.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-postfix-extra_cf/index.html b/u_e-postfix-extra_cf/index.html new file mode 100644 index 000000000..c406569e7 --- /dev/null +++ b/u_e-postfix-extra_cf/index.html @@ -0,0 +1,1936 @@ + + + + + + + + + + + + + + + + + + + Customize/Expand main.cf - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Customize/Expand main.cf

    + +

    Please create a new file data/conf/postfix/extra.cf for overrides or additional content to main.cf.

    +

    Postfix will complain about duplicate values once after starting postfix-mailcow, this is intended.

    +

    Syslog-ng was configured to hide those warnings while Postfix is running, to not spam the log files with unnecessary information every time a service is used.

    +

    Restart postfix-mailcow to apply your changes:

    +
    docker-compose restart postfix-mailcow
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-postfix-pflogsumm/index.html b/u_e-postfix-pflogsumm/index.html new file mode 100644 index 000000000..b9e19517a --- /dev/null +++ b/u_e-postfix-pflogsumm/index.html @@ -0,0 +1,1939 @@ + + + + + + + + + + + + + + + + + + + Statistics with pflogsumm - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Statistics with pflogsumm

    + +

    To use pflogsumm with the default logging driver, we need to query postfix-mailcow via docker logs and pipe the output to pflogsumm:

    +
    docker logs --since 24h $(docker ps -qf name=postfix-mailcow) | pflogsumm
    +
    +

    The above log output is limited to the past 24 hours.

    +

    It's also possible to create a daily pflogsumm report via cron. Create the file /etc/cron.d/pflogsumm with the following content:

    +
    SHELL=/bin/bash
    +59 23 * * * root docker logs --since 24h $(docker ps -qf name=postfix-mailcow) | /usr/sbin/pflogsumm -d today | mail -s "Postfix Report of $(date)" postmaster@example.net
    +
    +

    Based on the last 24h postfix logs, this example sends every day at 23:59:00 a pflogsumm report to postmaster@example.net.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-postfix-postscreen_whitelist/index.html b/u_e-postfix-postscreen_whitelist/index.html new file mode 100644 index 000000000..a26d3e243 --- /dev/null +++ b/u_e-postfix-postscreen_whitelist/index.html @@ -0,0 +1,1942 @@ + + + + + + + + + + + + + + + + + + + Whitelist IP in Postscreen - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Whitelist IP in Postscreen

    + +

    IPs can be removed from Postscreen and therefore also from RBL checks in data/conf/postfix/custom_postscreen_whitelist.cidr.

    +

    Postscreen does multiple checks to identify malicious senders. In most cases you want to whitelist an IP to exclude it from blacklist lookups.

    +

    The format of the file is as follows:

    +

    CIDR ACTION

    +

    Where CIDR is a single IP address or IP range in CIDR notation, and action is either "permit" or "reject".

    +

    Example:

    +
    # Rules are evaluated in the order as specified.
    +# Blacklist 192.168.* except 192.168.0.1.
    +192.168.0.1          permit
    +192.168.0.0/16       reject
    +
    +

    The file is reloaded on the fly, postfix restart is not required.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-redis/index.html b/u_e-redis/index.html new file mode 100644 index 000000000..7f465165f --- /dev/null +++ b/u_e-redis/index.html @@ -0,0 +1,2095 @@ + + + + + + + + + + + + + + + + + + + Redis - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Redis

    + +

    Redis is used as a key-value store for rspamd's and (some of) mailcow's settings and data. If you are unfamiliar with redis please read the introduction to redis and maybe visit this wonderful guide on how to use it.

    +

    Client

    +

    To connect to the redis cli execute:

    +
    docker-compose exec redis-mailcow redis-cli
    +
    +

    Debugging

    +

    Here are some useful commands for the redis-cli for debugging:

    +
    MONITOR
    +

    Listens for all requests received by the server in real time:

    +
    # docker-compose exec redis-mailcow redis-cli
    +127.0.0.1:6379> monitor
    +OK
    +1494077286.401963 [0 172.22.1.253:41228] "SMEMBERS" "BAYES_SPAM_keys"
    +1494077288.292970 [0 172.22.1.253:41229] "SMEMBERS" "BAYES_SPAM_keys"
    +[...]
    +
    +
    KEYS
    +

    Get all keys matching your pattern:

    +
    KEYS *
    +
    +
    PING
    +

    Test a connection:

    +
    127.0.0.1:6379> PING
    +PONG
    +
    +

    If you want to know more, here is a cheat sheet.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-reeanble-weak-protocols/index.html b/u_e-reeanble-weak-protocols/index.html new file mode 100644 index 000000000..d8cd7298d --- /dev/null +++ b/u_e-reeanble-weak-protocols/index.html @@ -0,0 +1,1942 @@ + + + + + + + + + + + + + + + + + + + Re-enable TLS 1.0 and TLS 1.1 - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Re-enable TLS 1.0 and TLS 1.1

    + +

    On February the 12th 2020 we disabled the deprecated protocols TLS 1.0 and 1.1 in Dovecot (POP3, POP3S, IMAP, IMAPS) and Postfix (SMTPS, SUBMISSION).

    +

    Unauthenticated mail via SMTP on port 25/tcp does still accept >= TLS 1.0 . It is better to accept a weak encryption than none at all.

    +

    How to re-enable weak protocols?

    +

    Edit data/conf/postfix/extra.cf:

    +
    submission_smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3
    +smtps_smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3
    +
    +

    Edit data/conf/dovecot/extra.conf:

    +
    ssl_min_protocol = TLSv1
    +
    +

    Restart the affected services:

    +
    docker-compose restart postfix-mailcow dovecot-mailcow
    +
    +

    Hint: You can enable TLS 1.2 in Windows 7.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-rspamd/index.html b/u_e-rspamd/index.html new file mode 100644 index 000000000..0a401a178 --- /dev/null +++ b/u_e-rspamd/index.html @@ -0,0 +1,2276 @@ + + + + + + + + + + + + + + + + + + + Rspamd - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + +

    Rspamd

    + +

    Rspamd is used for AV handling, DKIM signing and SPAM handling. It's a powerful and fast filter system. For a more in-depth documentation on Rspamd please visit its own documentation.

    +

    Learn Spam & Ham

    +

    Rspamd learns mail as spam or ham when you move a message in or out of the junk folder to any mailbox besides trash. +This is achieved by using the Sieve plugin "sieve_imapsieve" and parser scripts.

    +

    Rspamd also auto-learns mail when a high or low score is detected (see https://rspamd.com/doc/configuration/statistic.html#autolearning). We configured the plugin to keep a sane ratio between spam and ham learns.

    +

    The bayes statistics are written to Redis as keys BAYES_HAM and BAYES_SPAM.

    +

    Besides bayes, a local fuzzy storage is used to learn recurring patterns in text or images that indicate ham or spam.

    +

    You can also use Rspamd's web UI to learn ham and / or spam or to adjust certain settings of Rspamd.

    +

    Learn Spam or Ham from existing directory

    +

    You can use a one-liner to learn mail in plain-text (uncompressed) format:

    +
    # Ham
    +for file in /my/folder/cur/*; do docker exec -i $(docker-compose ps -q rspamd-mailcow) rspamc learn_ham < $file; done
    +# Spam
    +for file in /my/folder/.Junk/cur/*; do docker exec -i $(docker-compose ps -q rspamd-mailcow) rspamc learn_spam < $file; done
    +
    +

    Consider attaching a local folder as new volume to rspamd-mailcow in docker-compose.yml and learn given files inside the container. This can be used as workaround to parse compressed data with zcat. Example:

    +
    for file in /data/old_mail/.Junk/cur/*; do rspamc learn_spam < zcat $file; done
    +
    +

    Reset learned data (Bayes, Neural)

    +

    You need to delete keys in Redis to reset learned data, so create a copy of your Redis database now:

    +

    Backup database

    +
    # It is better to stop Redis before you copy the file.
    +cp /var/lib/docker/volumes/mailcowdockerized_redis-vol-1/_data/dump.rdb /root/
    +
    +

    Reset Bayes data

    +
    docker-compose exec redis-mailcow sh -c 'redis-cli --scan --pattern BAYES_* | xargs redis-cli del'
    +docker-compose exec redis-mailcow sh -c 'redis-cli --scan --pattern RS* | xargs redis-cli del'
    +
    +

    Reset Neural data

    +
    docker-compose exec redis-mailcow sh -c 'redis-cli --scan --pattern rn_* | xargs redis-cli del'
    +
    +

    Reset Fuzzy data

    +
    # We need to enter the redis-cli first:
    +docker-compose exec redis-mailcow redis-cli
    +# In redis-cli:
    +127.0.0.1:6379> EVAL "for i, name in ipairs(redis.call('KEYS', ARGV[1])) do redis.call('DEL', name); end" 0 fuzzy*
    +
    +

    Info

    +

    If redis-cli complains about...

    +
    (error) ERR wrong number of arguments for 'del' command
    +
    +

    ...the key pattern was not found and thus no data is available to delete - it is fine.

    +

    CLI tools

    +
    docker-compose exec rspamd-mailcow rspamc --help
    +docker-compose exec rspamd-mailcow rspamadm --help
    +
    +

    Disable Greylisting

    +

    Only messages with a higher score will be considered to be greylisted (soft rejected). It is bad practice to disable greylisting.

    +

    You can disable greylisting server-wide by editing:

    +

    {mailcow-dir}/data/conf/rspamd/local.d/greylist.conf

    +

    Add the line:

    +
    enabled = false;
    +
    +

    Save the file and restart "rspamd-mailcow": docker-compose restart rspamd-mailcow

    +

    Spam filter thresholds (global)

    +

    Each user is able to change their spam rating individually. To define a new server-wide limit, edit data/conf/rspamd/local.d/actions.conf:

    +
    reject = 15;
    +add_header = 8;
    +greylist = 7;
    +
    +

    Save the file and restart "rspamd-mailcow": docker-compose restart rspamd-mailcow

    +

    Existing settings of users will not be overwritten!

    +

    To reset custom defined thresholds, run:

    +
    source mailcow.conf
    +docker-compose exec mysql-mailcow mysql -umailcow -p$DBPASS mailcow -e "delete from filterconf where option = 'highspamlevel' or option = 'lowspamlevel';"
    +# or:
    +# docker-compose exec mysql-mailcow mysql -umailcow -p$DBPASS mailcow -e "delete from filterconf where option = 'highspamlevel' or option = 'lowspamlevel' and object = 'only-this-mailbox@example.org';"
    +
    +

    Custom reject messages

    +

    The default spam reject message can be changed by adding a new file data/conf/rspamd/override.d/worker-proxy.custom.inc with the following content:

    +
    reject_message = "My custom reject message";
    +
    +

    Save the file and restart Rspamd: docker-compose restart rspamd-mailcow.

    +

    While the above works for rejected mails with a high spam score, prefilter reject actions will ignore this setting. For these maps, the multimap module in Rspamd needs to be adjusted:

    +
      +
    1. +

      Find prefilet reject symbol for which you want change message, to do it run: grep -R "SYMBOL_YOU_WANT_TO_ADJUST" /opt/mailcow-dockerized/data/conf/rspamd/

      +
    2. +
    3. +

      Add your custom message as new line:

      +
    4. +
    +
    GLOBAL_RCPT_BL {
    +  type = "rcpt";
    +  map = "${LOCAL_CONFDIR}/custom/global_rcpt_blacklist.map";
    +  regexp = true;
    +  prefilter = true;
    +  action = "reject";
    +  message = "Sending mail to this recipient is prohibited by postmaster@your.domain";
    +}
    +
    +
      +
    1. Save the file and restart Rspamd: docker-compose restart rspamd-mailcow.
    2. +
    +

    Whitelist specific ClamAV signatures

    +

    You may find that legitimate (clean) mail is being blocked by ClamAV (Rspamd will flag the mail with VIRUS_FOUND). For instance, interactive PDF form attachments are blocked by default because the embedded Javascript code may be used for nefarious purposes. Confirm by looking at the clamd logs, e.g.:

    +
    docker-compose logs clamd-mailcow | grep "FOUND"
    +
    +

    This line confirms that such was identified:

    +
    clamd-mailcow_1      | Sat Sep 28 07:43:24 2019 -> instream(local): PUA.Pdf.Trojan.EmbeddedJavaScript-1(e887d2ac324ce90750768b86b63d0749:363325) FOUND
    +
    +

    To whitelist this particular signature (and enable sending this type of file attached), add it to the ClamAV signature whitelist file:

    +
    echo 'PUA.Pdf.Trojan.EmbeddedJavaScript-1' >> data/conf/clamav/whitelist.ign2
    +
    +

    Then restart the clamd-mailcow service container in the mailcow UI or using docker-compose:

    +
    docker-compose restart clamd-mailcow
    +
    +

    Cleanup cached ClamAV results in Redis:

    +
    # docker-compose exec redis-mailcow  /bin/sh
    +/data # redis-cli KEYS rs_cl* | xargs redis-cli DEL
    +/data # exit
    +
    +

    Discard instead of reject

    +

    If you want to silently drop a message, create or edit the file data/conf/rspamd/override.d/worker-proxy.custom.inc and add the following content:

    +
    discard_on_reject = true;
    +
    +

    Restart Rspamd:

    +
    docker-compose restart rspamd-mailcow
    +
    +

    Wipe all ratelimit keys

    +

    If you don't want to use the UI and instead wipe all keys in the Redis database, you can use redis-cli for that task:

    +
    docker-compose exec redis-mailcow sh
    +# Unlink (available in Redis >=4.) will delete in the backgronud
    +redis-cli --scan --pattern RL* | xargs redis-cli unlink
    +
    +

    Restart Rspamd:

    +
    docker-compose exec redis-mailcow sh
    +
    +

    Trigger a resend of quarantine notifications

    +

    Should be used for debugging only!

    +
    docker-compose exec dovecot-mailcow bash
    +mysql -umailcow -p$DBPASS mailcow -e "update quarantine set notified = 0;"
    +redis-cli -h redis DEL Q_LAST_NOTIFIED
    +quarantine_notify.py
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-sogo/index.html b/u_e-sogo/index.html new file mode 100644 index 000000000..56894b019 --- /dev/null +++ b/u_e-sogo/index.html @@ -0,0 +1,2112 @@ + + + + + + + + + + + + + + + + + + + SOGo - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    SOGo

    + +

    SOGo is used for accessing your mails via a webbrowser, adding and sharing your contacts or calendars. For a more in-depth documentation on SOGo please visit its own documentation.

    +

    Change theme

    +

    mailcow builds after 28 January 2021 can change SOGo's theme by editing data/conf/sogo/custom-theme.js. +Please check the AngularJS Material intro and documentation as well as the material style guideline to learn how this works. +After you updated said file you need to restart SOGo and Memcached containers by executing docker-compose restart memcached-mailcow sogo-mailcow.

    +

    Reset to SOGo default theme

    +

    Checkout data/conf/sogo/custom-theme.js by executing git fetch ; git checkout origin/master data/conf/sogo/custom-theme.js data/conf/sogo/custom-theme.js +Find in data/conf/sogo/custom-theme.js: +

    // Apply new palettes to the default theme, remap some of the hues
    +    $mdThemingProvider.theme('default')
    +      .primaryPalette('green-cow', {
    +        'default': '400',  // background color of top toolbars
    +        'hue-1': '400',
    +        'hue-2': '600',    // background color of sidebar toolbar
    +        'hue-3': 'A700'
    +      })
    +      .accentPalette('green', {
    +        'default': '600',  // background color of fab buttons
    +        'hue-1': '300',    // background color of center list toolbar
    +        'hue-2': '300',
    +        'hue-3': 'A700'
    +      })
    +      .backgroundPalette('frost-grey');
    +
    +and replace with: +
        $mdThemingProvider.theme('default');
    +

    +

    Change favicon

    +

    mailcow builds after 31 January 2021 can change SOGo's favicon by replacing data/conf/sogo/custom-favicon.ico for SOGo and data/web/favicon.png for mailcow UI. +Note: You can use .png favicons for SOGo by renaming them to custom-favicon.ico. +For both SOGo and mailcow UI favicons you need use one of the standard dimensions: 16x16, 32x32, 64x64, 128x128 and 256x256. +After you replaced said file you need to restart SOGo and Memcached containers by executing docker-compose restart memcached-mailcow sogo-mailcow.

    + +

    mailcow builds after 21 December 2018 can change SOGo's logo by replacing or creating (if missing) data/conf/sogo/sogo-full.svg. +After you replaced said file you need to restart SOGo and Memcached containers by executing docker-compose restart memcached-mailcow sogo-mailcow.

    +

    Connect domains

    +

    Domains are usually isolated from eachother.

    +

    You can change that by modifying data/conf/sogo/sogo.conf:

    +

    Search... +

       // SOGoDomainsVisibility = (
    +    //  (domain1.tld, domain5.tld),
    +    //  (domain3.tld, domain2.tld)
    +    // );
    +
    +...and replace it by - for example:

    +
        SOGoDomainsVisibility = (
    +      (example.org, example.com, example.net)
    +    );
    +
    +

    Restart SOGo: docker-compose restart sogo-mailcow

    +

    Disable password changing

    +

    Edit data/conf/sogo/sogo.conf and change SOGoPasswordChangeEnabled to NO. Please do not add a new parameter.

    +

    Run docker-compose restart memcached-mailcow sogo-mailcow to activate the changes.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-unbound-fwd/index.html b/u_e-unbound-fwd/index.html new file mode 100644 index 000000000..0e6395259 --- /dev/null +++ b/u_e-unbound-fwd/index.html @@ -0,0 +1,2025 @@ + + + + + + + + + + + + + + + + + + + Using an external DNS service - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Using an external DNS service

    + +

    If you want or have to use an external DNS service, you can either set a forwarder in Unbound or copy an override file to define external DNS servers:

    +

    !!! warning + Please do not use a public resolver like we did in the example above. Many - if not all - blacklist lookups will fail with public resolvers. + Important: Only DNSSEC validating DNS services will work.

    +

    Method A, Unbound

    +

    Edit data/conf/unbound/unbound.conf and append the following parameters:

    +
    forward-zone:
    +  name: "."
    +  forward-addr: 8.8.8.8 # DO NOT USE PUBLIC DNS SERVERS - JUST AN EXAMPLE
    +  forward-addr: 8.8.4.4 # DO NOT USE PUBLIC DNS SERVERS - JUST AN EXAMPLE
    +
    +

    Restart Unbound:

    +
    docker-compose restart unbound-mailcow
    +
    +

    Method B, Override file

    +
    cd /opt/mailcow-dockerized
    +cp helper-scripts/docker-compose.override.yml.d/EXTERNAL_DNS/docker-compose.override.yml .
    +
    +

    Edit docker-compose.override.yml and adjust the IP.

    +

    Run docker-compose down ; docker-compose up -d.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-webmail-site/index.html b/u_e-webmail-site/index.html new file mode 100644 index 000000000..e721c61bd --- /dev/null +++ b/u_e-webmail-site/index.html @@ -0,0 +1,1959 @@ + + + + + + + + + + + + + + + + + + + Create subdomain webmail.example.org - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Create subdomain webmail.example.org

    + +

    IMPORTANT: This guide only applies to non SNI enabled configurations. The certificate path needs to be adjusted if SNI is enabled. Something like ssl_certificate,key /etc/ssl/mail/webmail.example.org/cert.pem,key.pem; will do. But: The certificate should be acquired first and only after the certificate exists a site config should be created. Nginx will fail to start if it cannot find the certificate and key.

    +

    To create a subdomain webmail.example.org and redirect it to SOGo, you need to create a new Nginx site. Take care of "CHANGE_TO_MAILCOW_HOSTNAME"!

    +

    nano data/conf/nginx/webmail.conf

    +
    server {
    +  ssl_certificate /etc/ssl/mail/cert.pem;
    +  ssl_certificate_key /etc/ssl/mail/key.pem;
    +  index index.php index.html;
    +  client_max_body_size 0;
    +  root /web;
    +  include /etc/nginx/conf.d/listen_plain.active;
    +  include /etc/nginx/conf.d/listen_ssl.active;
    +  server_name webmail.example.org;
    +
    +  location ^~ /.well-known/acme-challenge/ {
    +    allow all;
    +    default_type "text/plain";
    +  }
    +
    +  location / {
    +    return 301 https://CHANGE_TO_MAILCOW_HOSTNAME/SOGo;
    +  }
    +}
    +
    +

    Save and restart Nginx: docker-compose restart nginx-mailcow.

    +

    Now open mailcow.conf and find ADDITIONAL_SAN. +Add webmail.example.org to this array, don't use quotes!

    +
    ADDITIONAL_SAN=webmail.example.org
    +
    +

    Run docker-compose up -d. See "acme-mailcow" and "nginx-mailcow" logs if anything fails.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-why_unbound/index.html b/u_e-why_unbound/index.html new file mode 100644 index 000000000..0c93dbdf3 --- /dev/null +++ b/u_e-why_unbound/index.html @@ -0,0 +1,1933 @@ + + + + + + + + + + + + + + + + + + + Why unbound? - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Why unbound?

    + +

    For DNS blacklist lookups and DNSSEC.

    +

    Most systems use either a public or a local caching DNS resolver. +That's a very bad idea when it comes to filter spam using DNS-based black hole lists (DNSBL) or similar technics. +Most if not all providers apply a rate limit based on the DNS resolver that is used to query their service. +Using a public resolver like Googles 4x8, OpenDNS or any other shared DNS resolver like your ISPs will hit that limit very soon.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-xmpp-enable/index.html b/u_e-xmpp-enable/index.html new file mode 100644 index 000000000..c0a7ea5ea --- /dev/null +++ b/u_e-xmpp-enable/index.html @@ -0,0 +1,2143 @@ + + + + + + + + + + + + + + + + + + + Enable XMPP - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    Enable XMPP

    + +

    XMPP is provided by ejabberd, which describes itself as robust, scalable and extensible XMPP Server.

    +

    So first of all, thanks to ejabberd and its contributers!

    +

    Enable XMPP in mailcow

    +

    To enable XMPP for a domain, you need to edit the given domain in mailcow UI:

    +

    Screen1

    +

    The chosen prefix will be used to derive your XMPP login.

    +

    A prefix xmpp_prefix for the mailbox user cowboy@develcow.de would equal to the JID cowboy@xmpp_prefix.develcow.de.

    +
    +

    Info

    +

    The login passwords for mail and XMPP are the same. XMPP users are authenticated against mailcow.

    +
    +

    Before enabling XMPP for a domain, you should create two CNAME records in DNS:

    +
    # CNAMES
    +# Name              Type       Value
    +xmpp_prefix         IN CNAME   mail.example.org. (your ${MAILCOW_HOSTNAME})
    +*.xmpp_prefix       IN CNAME   mail.example.org. (your ${MAILCOW_HOSTNAME})
    +
    +

    These two CNAMEs are essential for acquiring a certificate. Please do not add "xmpp_prefix.domain.tld" as name to ADDITIONAL_SAN.

    +

    Make sure your CNAMEs are correct. Enable XMPP for your domain now.

    +

    If you enabled XMPP first and then added your DNS records there is no need to worry. You will just need to wait for ejabberd to automatically acquire the certificates or +simply restart ejabberd-mailcow to trigger the process immediately: docker-compose restart ejabberd-mailcow.

    +

    Once ejabberd is enabled, you may want to re-run the DNS check in the mailcow UI where you will find two more SRV records:

    +

    Screen2

    +
    # SRV records
    +# Name                            Type       Value
    +_xmpp-client._tcp.xmpp_prefix     IN SRV     10 1 5222 mail.example.org. (your ${MAILCOW_HOSTNAME})
    +_xmpp-server._tcp.xmpp_prefix     IN SRV     10 1 5269 mail.example.org. (your ${MAILCOW_HOSTNAME})
    +
    +

    There is no need to restart ejabberd, add these SRV records whenever you like. These records are crucial for autoconfiguration of XMPP clients and server-to-server connections.

    +

    ACL

    +

    A domain administrator can be given the right to toggle XMPP access for domains and mailboxes, promoting users to XMPP administrators (WIP) and to change the prefix:

    +

    Screen3

    +

    Verify certificates

    +

    Once everything is setup, make sure ejabberd was able to acquire certificates:

    +

    If you see a message similar to...

    +
    ejabberd-mailcow_1   | 2021-02-13 14:40:19.507956+01:00 [error] Failed to request certificate for im.example.org, pubsub.im.example.org and 3 more hosts: Challenge failed for domain conference.im.example.org: ACME server reported: DNS problem: NXDOMAIN looking up A for conference.im.example.org - check that a DNS record exists for this domain (error type: dns)
    +
    +

    ...you may need to recheck your DNS configuration or restart ejabberd-mailcow to restart the process in case of slow DNS propagation.

    +

    Opening https://xmpp_prefix.domain.tld:5443/upload should point you to a 404 page with a valid certificate.

    +

    Why can't we use no prefix?

    +

    It does not matter which server name we point our SRV to, Jabber will always rely on the domain given in a JID. We would need to acquire a certificate for the SLD domain.tld, which hardly anyone wants to point to its mail system.

    +

    We are sorry for this circumstance. As soon as we implemented Servercows DNS API, this may be reconsidered.

    +

    My reverse proxy does not work anymore

    +

    If your reverse proxy is configured to point to a site like webmail.domain.tld which mailcow is not aware of (as in MAILCOW_HOSTNAME does not match webmail.domain.tld), you may now be redirected to the default ejabberd Nginx site.

    +

    That's because mailcow does not know it should respond to webmail.domain.tld with mailcow UI.

    +

    Method 1

    +

    A more simple approach is defining ADDITIONAL_SERVER_NAMES in mailcow.conf:

    +
    ADDITIONAL_SERVER_NAMES=webmail.domain.tld
    +
    +

    Run docker-compose up -d to apply.

    +

    Method 2

    +

    In your reverse proxy configuration, make sure you set a "Host" header that mailcow actually services, similar to this (Nginx example):

    +
    proxy_set_header Host MAILCOW_HOSTNAME;
    +# Instead of proxy_set_header Host $http_host;
    +
    +

    Now you can use whatever name you like, as long mailcow receives a known "Host" header.

    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/u_e-xmpp-faq/index.html b/u_e-xmpp-faq/index.html new file mode 100644 index 000000000..ac0941392 --- /dev/null +++ b/u_e-xmpp-faq/index.html @@ -0,0 +1,2050 @@ + + + + + + + + + + + + + + + + + + + FAQ - mailcow: dockerized documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + +

    FAQ

    + +

    FAQ

    +

    Please find the most frequently asked questions with their corresponding configuration in data/conf/ejabberd/ejabberd.yml (if any).

    +
      +
    • I do not want to run ejabberd, is there a SKIP_XMPP variable?
    • +
    +

    No, there is not. But you don't need one either.

    +

    The xmppd behaves the same way as SOGo or Solr do when disabled. A shell will be idling and ejabberd will not be started.

    +

    As soon as a domain is enabled for XMPP, the container will be restarted and ejabberd bootstrapped.

    +

    ejabberd is very light on resources, you may want to give it a try.

    +
      +
    • Are messages stored on the server?
    • +
    +

    Not by default. The default setting is to disable the message archive via mod_mam but allow users to enable the function if they want to:

    +
      mod_mam:
    +    clear_archive_on_room_destroy: true
    +    default: never
    +    compress_xml: true
    +    request_activates_archiving: true
    +
    +
      +
    • Are uploaded files stored on the server?
    • +
    +

    Yes, uploaded files are stored in the volume xmpp-uploads-vol-1.

    +

    The retention policy saves them for 30 days:

    +
      mod_http_upload_quota:
    +    max_days: 30
    +
    +
      +
    • Are messages stored when a JID is offline?
    • +
    +

    Yes, up to 1000 messages are stored for "normal" users and administrators:

    +
    shaper_rules:
    +  max_user_offline_messages:
    +    1000: admin
    +    1000: all
    +
    +
      +
    • Are messages written in group chats stored?
    • +
    +

    No, messages are not stored:

    +
      mod_muc:
    +    default_room_options:
    +      mam: false
    +
    +
      +
    • Are group chats persistent when the last participant leaves?
    • +
    +

    No, they will vanish:

    +
      mod_muc:
    +    default_room_options:
    +      persistent: false
    +
    +
      +
    • How many client sessions can be open at the same time?
    • +
    +

    10 sessions are allowed per session.

    +
    shaper_rules:
    +  max_user_sessions: 10
    +
    + + + + + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file