PageSourceSearch

https://nog.fi/dist/js/main.357543f6.bundle.js

js nog.fi collected 2026-10-05 03:40:07 UTC 425,329 bytes, 2 lines download raw bytes

1(globalThis.webpackChunk=globalThis.webpackChunk||[]).push([[8792],{74260:(n,e,t)=>{"use strict";t(53348);!function(n){const e=new WeakMap,t=new WeakMap,i=new WeakMap,a=new WeakMap,s=new WeakMap,r=new WeakMap,o=new WeakMap,l=new WeakMap,c=new WeakMap,d=new WeakMap,u=new WeakMap,h=new WeakMap,m=new WeakMap,f=new WeakMap,p=new WeakMap,v={ariaAtomic:"aria-atomic",ariaAutoComplete:"aria-autocomplete",ariaBusy:"aria-busy",ariaChecked:"aria-checked",ariaColCount:"aria-colcount",ariaColIndex:"aria-colindex",ariaColIndexText:"aria-colindextext",ariaColSpan:"aria-colspan",ariaCurrent:"aria-current",ariaDescription:"aria-description",ariaDisabled:"aria-disabled",ariaExpanded:"aria-expanded",ariaHasPopup:"aria-haspopup",ariaHidden:"aria-hidden",ariaInvalid:"aria-invalid",ariaKeyShortcuts:"aria-keyshortcuts",ariaLabel:"aria-label",ariaLevel:"aria-level",ariaLive:"aria-live",ariaModal:"aria-modal",ariaMultiLine:"aria-multiline",ariaMultiSelectable:"aria-multiselectable",ariaOrientation:"aria-orientation",ariaPlaceholder:"aria-placeholder",ariaPosInSet:"aria-posinset",ariaPressed:"aria-pressed",ariaReadOnly:"aria-readonly",ariaRelevant:"aria-relevant",ariaRequired:"aria-required",ariaRoleDescription:"aria-roledescription",ariaRowCount:"aria-rowcount",ariaRowIndex:"aria-rowindex",ariaRowIndexText:"aria-rowindextext",ariaRowSpan:"aria-rowspan",ariaSelected:"aria-selected",ariaSetSize:"aria-setsize",ariaSort:"aria-sort",ariaValueMax:"aria-valuemax",ariaValueMin:"aria-valuemin",ariaValueNow:"aria-valuenow",ariaValueText:"aria-valuetext",role:"role"};function b(n){const e=a.get(n),{form:t}=e;$(n,t,e),E(n,e.labels)}const g=(n,e=!1)=>{const t=document.createTreeWalker(n,NodeFilter.SHOW_ELEMENT,{acceptNode:n=>a.has(n)?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP});let i=t.nextNode();const s=!e||n.disabled;for(;i;)i.formDisabledCallback&&s&&D(i,n.disabled),i=t.nextNode()},y={attributes:!0,attributeFilter:["disabled","name"]},w=j()?new MutationObserver((n=>{for(const e of n){const n=e.target;if("disabled"===e.attributeName&&(n.constructor.formAssociated?D(n,n.hasAttribute("disabled")):"fieldset"===n.localName&&g(n)),"name"===e.attributeName&&n.constructor.formAssociated){const e=a.get(n),t=c.get(n);e.setFormValue(t)}}})):{};function k(n){n.forEach((n=>{const{addedNodes:e,removedNodes:t}=n,s=Array.from(e),r=Array.from(t);s.forEach((n=>{var e;if(a.has(n)&&n.constructor.formAssociated&&b(n),d.has(n)){const e=d.get(n);Object.keys(v).filter((n=>null!==e[n])).forEach((t=>{n.setAttribute(v[t],e[t])})),d.delete(n)}if(p.has(n)){const e=p.get(n);n.setAttribute("internals-valid",e.validity.valid.toString()),n.setAttribute("internals-invalid",(!e.validity.valid).toString()),n.setAttribute("aria-invalid",(!e.validity.valid).toString()),p.delete(n)}if("form"===n.localName){const e=l.get(n),t=document.createTreeWalker(n,NodeFilter.SHOW_ELEMENT,{acceptNode:n=>!a.has(n)||!n.constructor.formAssociated||e&&e.has(n)?NodeFilter.FILTER_SKIP:NodeFilter.FILTER_ACCEPT});let i=t.nextNode();for(;i;)b(i),i=t.nextNode()}"fieldset"===n.localName&&(null===(e=w.observe)||void 0===e||e.call(w,n,y),g(n,!0))})),r.forEach((n=>{const e=a.get(n);if(e&&i.get(e)&&I(e),o.has(n)){o.get(n).disconnect()}}))}))}function x(n){n.forEach((n=>{const{removedNodes:e}=n;e.forEach((e=>{const t=m.get(n.target);a.has(e)&&H(e),t.disconnect()}))}))}!j()||new MutationObserver(k);const T={childList:!0,subtree:!0},D=(n,e)=>{n.toggleAttribute("internals-disabled",e),e?n.setAttribute("aria-disabled","true"):n.removeAttribute("aria-disabled"),n.formDisabledCallback&&n.formDisabledCallback.apply(n,[e])},I=n=>{i.get(n).forEach((n=>{n.remove()})),i.set(n,[])},C=(n,e)=>{const t=document.createElement("input");return t.type="hidden",t.name=n.getAttribute("name"),n.after(t),i.get(e).push(t),t},E=(n,e)=>{if(e.length){Array.from(e).forEach((e=>e.addEventListener("click",n.click.bind(n))));let t=e[0].id;e[0].id||(t=`${e[0].htmlFor}_Label`,e[0].id=t),n.setAttribute("aria-labelledby",t)}},S=n=>{const e=Array.from(n.elements).filter((n=>!n.tagName.includes("-")&&n.validity)).map((n=>n.validity.valid)),t=l.get(n)||[],i=[...e,...Array.from(t).filter((n=>n.isConnected)).map((n=>a.get(n).validity.valid))].includes(!1);n.toggleAttribute("internals-invalid",i),n.toggleAttribute("internals-valid",!i)},_=n=>{S(B(n.target))},L=n=>{S(B(n.target))},A=n=>{const e=l.get(n.target);e&&e.size&&e.forEach((n=>{n.constructor.formAssociated&&n.formResetCallback&&n.formResetCallback.apply(n)}))},$=(n,e,t)=>{if(e){const i=l.get(e);if(i)i.add(n);else{const t=new Set;t.add(n),l.set(e,t),(n=>{const e=["button[type=submit]","input[type=submit]","button:not([type])"].map((n=>`${n}:not([disabled])`)).map((e=>`${e}:not([form])${n.id?`,${e}[form='${n.id}']`:""}`)).join(",");n.addEventListener("click",(t=>{if(t.target.closest(e)){const e=l.get(n);if(n.noValidate)return;e.size&&Array.from(e).reverse().map((n=>a.get(n).reportValidity())).includes(!1)&&t.preventDefault()}}))})(e),e.addEventListener("reset",A),e.addEventListener("input",_),e.addEventListener("change",L)}r.set(e,{ref:n,internals:t}),n.constructor.formAssociated&&n.formAssociatedCallback&&setTimeout((()=>{n.formAssociatedCallback.apply(n,[e])}),0),S(e)}},B=n=>{let e=n.parentNode;return e&&"FORM"!==e.tagName&&(e=B(e)),e},O=(n,e,t=DOMException)=>
1{if(!n.constructor.formAssociated)throw new t(e)},M=(n,e,t)=>{const i=l.get(n);return i&&i.size&&i.forEach((n=>{a.get(n)[t]()||(e=!1)})),e},H=n=>{if(n.constructor.formAssociated){const e=a.get(n),{labels:t,form:i}=e;E(n,t),$(n,i,e)}};function j(){return"undefined"!=typeof MutationObserver}class N{constructor(){this.badInput=!1,this.customError=!1,this.patternMismatch=!1,this.rangeOverflow=!1,this.rangeUnderflow=!1,this.stepMismatch=!1,this.tooLong=!1,this.tooShort=!1,this.typeMismatch=!1,this.valid=!0,this.valueMissing=!1,Object.seal(this)}}const P=n=>{let e=!0;for(let t in n)"valid"!==t&&!1!==n[t]&&(e=!1);return e},W=new WeakMap;function F(n,e){n.toggleAttribute(e,!0),n.part&&n.part.add(e)}class R extends Set{static get isPolyfilled(){return!0}constructor(n){if(super(),!n||!n.tagName||-1===n.tagName.indexOf("-"))throw new TypeError("Illegal constructor");W.set(this,n)}add(n){if(!/^--/.test(n)||"string"!=typeof n)throw new DOMException(`Failed to execute 'add' on 'CustomStateSet': The specified value ${n} must start with '--'.`);const e=super.add(n),t=W.get(this),i=`state${n}`;return t.isConnected?F(t,i):setTimeout((()=>{F(t,i)})),e}clear(){for(let[n]of this.entries())this.delete(n);super.clear()}delete(n){const e=super.delete(n),t=W.get(this);return t.isConnected?(t.toggleAttribute(`state${n}`,!1),t.part&&t.part.remove(`state${n}`)):setTimeout((()=>{t.toggleAttribute(`state${n}`,!1),t.part&&t.part.remove(`state${n}`)})),e}}function U(n,e,t,i){if("a"===t&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?n!==e||!i:!e.has(n))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===t?i:"a"===t?i.call(n):i?i.value:e.get(n)}var z;class q{constructor(n){z.set(this,void 0),function(n,e,t,i,a){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!a)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof e?n!==e||!a:!e.has(n))throw new TypeError("Cannot write private member to an object whose class did not declare it");"a"===i?a.call(n,t):a?a.value=t:e.set(n,t)}(this,z,n,"f");for(let e=0;e<n.length;e++){let t=n[e];this[e]=t,t.hasAttribute("name")&&(this[t.getAttribute("name")]=t)}Object.freeze(this)}get length(){return U(this,z,"f").length}[(z=new WeakMap,Symbol.iterator)](){return U(this,z,"f")[Symbol.iterator]()}item(n){return null==this[n]?null:this[n]}namedItem(n){return null==this[n]?null:this[n]}}class V{static get isPolyfilled(){return!0}constructor(n){if(!n||!n.tagName||-1===n.tagName.indexOf("-"))throw new TypeError("Illegal constructor");const s=n.getRootNode(),r=new N;this.states=new R(n),e.set(this,n),t.set(this,r),a.set(n,this),((n,e)=>{for(let t in v){e[t]=null;let i=null;const a=v[t];Object.defineProperty(e,t,{get:()=>i,set(t){i=t,n.isConnected?n.setAttribute(a,t):d.set(n,e)}})}})(n,this),((n,e)=>{var t;i.set(e,[]),null===(t=w.observe)||void 0===t||t.call(w,n,y)})(n,this),Object.seal(this),s instanceof DocumentFragment&&(n=>{var e,t;const i=new MutationObserver(x);(null===(e=null===window||void 0===window?void 0:window.ShadyDOM)||void 0===e?void 0:e.inUse)&&n.mode&&n.host&&(n=n.host),null===(t=i.observe)||void 0===t||t.call(i,n,{childList:!0}),m.set(n,i)})(s)}checkValidity(){const n=e.get(this);if(O(n,"Failed to execute 'checkValidity' on 'ElementInternals': The target element is not a form-associated custom element."),!this.willValidate)return!0;const i=t.get(this);if(!i.valid){const e=new Event("invalid",{bubbles:!1,cancelable:!0,composed:!1});n.dispatchEvent(e)}return i.valid}get form(){const n=e.get(this);let t;return O(n,"Failed to read the 'form' property from 'ElementInternals': The target element is not a form-associated custom element."),!0===n.constructor.formAssociated&&(t=B(n)),t}get labels(){const n=e.get(this);O(n,"Failed to read the 'labels' property from 'ElementInternals': The target element is not a form-associated custom element.");const t=n.getAttribute("id"),i=n.getRootNode();return i&&t?i.querySelectorAll(`[for="${t}"]`):[]}reportValidity(){const n=e.get(this);if(O(n,"Failed to execute 'reportValidity' on 'ElementInternals': The target element is not a form-associated custom element."),!this.willValidate)return!0;const t=this.checkValidity(),i=h.get(this);if(i&&!n.constructor.formAssociated)throw new DOMException("Failed to execute 'reportValidity' on 'ElementInternals': The target element is not a form-associated custom element.");return!t&&i&&(n.focus(),i.focus()),t}setFormValue(n){const t=e.get(this);if(O(t,"Failed to execute 'setFormValue' on 'ElementInternals': The target element is not a form-associated custom element."),I(this),null==n||n instanceof FormData)null!=n&&n instanceof FormData&&Array.from(n).reverse().forEac
1h((([n,e])=>{if("string"==typeof e){const i=C(t,this);i.name=n,i.value=e}}));else if(t.getAttribute("name")){C(t,this).value=n}c.set(t,n)}setValidity(n,i,a){const r=e.get(this);if(O(r,"Failed to execute 'setValidity' on 'ElementInternals': The target element is not a form-associated custom element."),!n)throw new TypeError("Failed to execute 'setValidity' on 'ElementInternals': 1 argument required, but only 0 present.");h.set(this,a);const o=t.get(this),l={};for(const e in n)l[e]=n[e];var c;0===Object.keys(l).length&&((c=o).badInput=!1,c.customError=!1,c.patternMismatch=!1,c.rangeOverflow=!1,c.rangeUnderflow=!1,c.stepMismatch=!1,c.tooLong=!1,c.tooShort=!1,c.typeMismatch=!1,c.valid=!0,c.valueMissing=!1);const d=Object.assign(Object.assign({},o),l);delete d.valid;const{valid:u}=((n,e,t)=>(n.valid=P(e),Object.keys(e).forEach((t=>n[t]=e[t])),t&&S(t),n))(o,d,this.form);if(!u&&!i)throw new DOMException("Failed to execute 'setValidity' on 'ElementInternals': The second argument should not be empty if one or more flags in the first argument are true.");s.set(this,u?"":i),r.isConnected?(r.toggleAttribute("internals-invalid",!u),r.toggleAttribute("internals-valid",u),r.setAttribute("aria-invalid",`${!u}`)):p.set(r,this)}get shadowRoot(){const n=e.get(this),t=u.get(n);return t||null}get validationMessage(){const n=e.get(this);return O(n,"Failed to read the 'validationMessage' property from 'ElementInternals': The target element is not a form-associated custom element."),s.get(this)}get validity(){const n=e.get(this);O(n,"Failed to read the 'validity' property from 'ElementInternals': The target element is not a form-associated custom element.");return t.get(this)}get willValidate(){const n=e.get(this);return O(n,"Failed to read the 'willValidate' property from 'ElementInternals': The target element is not a form-associated custom element."),!(n.disabled||n.hasAttribute("disabled")||n.hasAttribute("readonly"))}}let G=!1,X=!1;function Y(n){X||(X=!0,window.CustomStateSet=R,n&&(HTMLElement.prototype.attachInternals=function(...e){const t=n.call(this,e);return t.states=new R(this),t}))}function J(n=!0){if(!G){if(G=!0,"undefined"!=typeof window&&(window.ElementInternals=V),"undefined"!=typeof CustomElementRegistry){const e=CustomElementRegistry.prototype.define;CustomElementRegistry.prototype.define=function(n,t,i){if(t.formAssociated){const n=t.prototype.connectedCallback;t.prototype.connectedCallback=function(){f.has(this)||(f.set(this,!0),this.hasAttribute("disabled")&&D(this,!0)),null!=n&&n.apply(this),H(this)}}e.call(this,n,t,i)}}if("undefined"!=typeof HTMLElement&&(HTMLElement.prototype.attachInternals=function(){if(!this.tagName)return{};if(-1===this.tagName.indexOf("-"))throw new Error("Failed to execute 'attachInternals' on 'HTMLElement': Unable to attach ElementInternals to non-custom elements.");if(a.has(this))throw new DOMException("DOMException: Failed to execute 'attachInternals' on 'HTMLElement': ElementInternals for the specified element was already attached.");return new V(this)}),"undefined"!=typeof Element){function t(...n){const e=i.apply(this,n);if(u.set(this,e),j()){const n=new MutationObserver(k);window.ShadyDOM?n.observe(this,T):n.observe(e,T),o.set(this,n)}return e}const i=Element.prototype.attachShadow;Element.prototype.attachShadow=t}if(j()&&"undefined"!=typeof document){new MutationObserver(k).observe(document.documentElement,T)}"undefined"!=typeof HTMLFormElement&&function(){const n=HTMLFormElement.prototype.checkValidity;HTMLFormElement.prototype.checkValidity=function(...e){let t=n.apply(this,e);return M(this,t,"checkValidity")};const e=HTMLFormElement.prototype.reportValidity;HTMLFormElement.prototype.reportValidity=function(...n){let t=e.apply(this,n);return M(this,t,"reportValidity")};const{get:t}=Object.getOwnPropertyDescriptor(HTMLFormElement.prototype,"elements");Object.defineProperty(HTMLFormElement.prototype,"elements",{get(...n){const e=t.call(this,...n
1),i=Array.from(l.get(this)||[]);if(0===i.length)return e;const a=Array.from(e).concat(i).sort(((n,e)=>n.compareDocumentPosition?2&n.compareDocumentPosition(e)?1:-1:0));return new q(a)}})}(),(n||"undefined"!=typeof window&&!window.CustomStateSet)&&Y()}}!!customElements.polyfillWrapFlushCallback||(!function(){if("undefined"==typeof window||!window.ElementInternals||!HTMLElement.prototype.attachInternals)return!1;class n extends HTMLElement{constructor(){super(),this.internals=this.attachInternals()}}const e=`element-internals-feature-detection-${Math.random().toString(36).replace(/[^a-z]+/g,"")}`;customElements.define(e,n);const t=new n;return["shadowRoot","form","willValidate","validity","validationMessage","labels","setFormValue","setValidity","checkValidity","reportValidity"].every((n=>n in t.internals))}()?J(!1):"undefined"==typeof window||window.CustomStateSet||Y(HTMLElement.prototype.attachInternals)),n.forceCustomStateSetPolyfill=Y,n.forceElementInternalsPolyfill=J,Object.defineProperty(n,"__esModule",{value:!0})}({});t(53745),t(44789),t(56297),t(37245),t(43100),t(46484),t(62046),t(34259),t(60179),t(43569),t(27678),t(67425),t(99012),t(86591),t(29091),t(80492),t(85063),t(72702),t(66419),t(73110),t(15068),t(51492),t(95369),t(61044),t(17746),t(35614),t(69122),t(29415),t(79886),t(63962),t(36413),t(56993),t(76927),t(73656),t(18897),t(69404),t(84292),t(44402),t(16425),t(11462),t(48176),t(11701),t(33825),t(3417),t(32846),t(30853),t(89429),t(10576),t(82339),t(50628);t(73885).Q.then((()=>{document.querySelectorAll("template[data-tooltip-for]").forEach((n=>{const e=document.getElementById(n.dataset.tooltipFor);if(!e)return;const t=Object.assign(document.createElement("ind-with-tooltip"),{orientation:"horizontal",innerHTML:`<div data-tip-content>${n.innerHTML}</div>`});e.replaceWith(t),t.append(e)}))}));var i=t(89952);window.showErrorDialog=(n,e=!1)=>(0,i.A)(n,e)},50628:()=>{window.addEventListener("DOMContentLoaded",(()=>{const n=document.querySelector("#tz-selector-widget form"),e=document.getElementById("tz-modes"),t=document.getElementById("tz-mode-custom"),i=document.querySelector("#tz-custom-field select");function a(){t.checked=!0}n&&(i.querySelector("option:checked")?.scrollIntoView(),i.addEventListener("click",a),i.addEventListener("change",a),e.addEventListener("change",(n=>{"user"===n.target.value?i.querySelector(`option[value="${n.target.dataset.userTz}"]`)?.scrollIntoView():"custom"===n.target.value&&i.querySelector("option:checked")?.scrollIntoView()})),n.addEventListener("submit",(n=>{n.preventDefault()})))}))},29415:(n,e,t)=>{t(35184)(t(87837))},84292:(n,e,t)=>{t(35184)(t(31050))},69122:(n,e,t)=>{t(35184)(t(73844))},35614:(n,e,t)=>{t(35184)(t(4648))},18897:(n,e,t)=>{t(35184)(t(53291))},73656:(n,e,t)=>{t(35184)(t(15650))},69404:(n,e,t)=>{t(35184)(t(23846))},79886:(n,e,t)=>{t(35184)(t(16180))},63962:(n,e,t)=>{t(35184)(t(18420))},17746:(n,e,t)=>{t(35184)(t(16616))},44402:(n,e,t)=>{t(35184)(t(97940))},36413:(n,e,t)=>{t(35184)(t(82159))},56993:(n,e,t)=>{t(35184)(t(52251))},76927:(n,e,t)=>{t(35184)(t(9501))},11462:(n,e,t)=>{t(35184)(t(72092))},16425:(n,e,t)=>{t(35184)(t(79423))},62046:(n,e,t)=>{t(35184)(t(52664))},46484:(n,e,t)=>{t(35184)(t(226))},44789:(n,e,t)=>{t(35184)(t(40587))},34259:(n,e,t)=>{t(35184)(t(4149))},53745:(n,e,t)=>{t(35184)(t(75764))},37245:(n,e,t)=>{t(35184)(t(79287))},56297:(n,e,t)=>{t(35184)(t(2099))},43100:(n,e,t)=>{t(35184)(t(14950))},60179:(n,e,t)=>{t(35184)(t(23933))},99012:(n,e,t)=>{t(35184)(t(84390))},80492:(n,e,t)=>{t(35184)(t(24674))},29091:(n,e,t)=>{t(35184)(t(12673))},86591:(n,e,t)=>{t(35184)(t(66573))},27678:(n,e,t)=>{t(35184)(t(36436))},67425:(n,e,t)=>{t(35184)(t(63931))},43569:(n,e,t)=>{t(35184)(t(46403))},66419:(n,e,t)=>{t(35184)(t(81761))},73110:(n,e,t)=>{t(35184)(t(60732))},72702:(n,e,t)=>{t(35184)(t(61532))},15068:(n,e,t)=>{t(35184)(t(78562))},51492:(n,e,t)=>{t(35184)(t(25058))},95369:(n,e,t)=>{t(35184)(t(43831))},61044:(n,e,t)=>{t(35184)(t(72866))},85063:(n,e,t)=>{t(35184)(t(64430))},10576:(n,e,t)=>{t(35184)(t(15046))},32846:(n,e,t)=>{t(35184)(t(38544))},30853:(n,e,t)=>{t(35184)(t(46047))},89429:(n,e,t)=>{t(35184)(t(31719))},11701:(n,e,t)=>{t(35184)(t(7671))},33825:(n,e,t)=>{t(35184)(t(87303))},48176:(n,e,t)=>{t(35184)(t(17510))},3417:(n,e,t)=>{t(35184)(t(7603))},87837:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n_.mixin({\n  move: function(array, fromIndex, toIndex) {\n    array.splice(toIndex, 0, array.splice(fromIndex, 1)[0]);\n    return array;\n  },\n});\n"},31050:n=>
1{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\ntype(\n  'BrowserHistoryBroker',\n  [],\n  // handle the interaction with browser history and hashes\n  {\n    addListener: function(listener) {\n      /* add a listener that will be notified each time\n                the hash changes */\n\n      this.listeners.push(listener);\n\n      // the listener needs to know the broker,\n      // so that it can register deliberate actions\n      // (which are not back nor forward)\n      listener.registerHistoryBroker(this);\n    },\n\n    setUserAction: function(hash) {\n      /* set an action that was performed by the user:\n                that means an anchor update */\n\n      window.location.hash = hash;\n      this.currentHash = '#' + hash;\n    },\n  },\n  function() {\n    this.listeners = [];\n\n    // keep the hash that is in use\n    this.currentHash = window.location.hash;\n\n    var self = this;\n\n    // function that gets executed each time the hash changes\n    var checkHashChanged = function() {\n      if (self.currentHash != window.location.hash) {\n        each(self.listeners, function(listener) {\n          listener.notifyHistoryChange(window.location.hash);\n        });\n        self.currentHash = window.location.hash;\n      }\n    };\n\n    // onhashchange is nice (IE8, Chrome, FF 3.6, HTML5?)\n    if (document.body.onhashchange !== undefined) {\n      $E(document.body).observeEvent('hashchange', checkHashChanged);\n    } else {\n      // for older browsers, use setInterval()\n      setInterval(checkHashChanged, 500);\n    }\n\n    // btw, IE7 not supported\n  }\n);\n"},73844:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\nfunction imageFunctionGenerator(url) {\n  return function(imageId, extension) {\n    return url + '/' + imageId + '.' + (extension || 'png');\n  };\n}\n\nvar imageSrc = imageFunctionGenerator(Indico.Urls.ImagesBase);\n\nfunction pixels(val) {\n  return val + 'px';\n}\n\nfunction zeropad(number) {\n  return ('' + number).length == 1 ? '0' + number : number;\n}\n\n/**\n @namespace IndicoUI interface library\n*/\n\nvar IndicoUI = {\n  // The current used layer level\n  __globalLayerLevel: 0,\n\n  // To keep track of all used layer levels.\n  // A used level is set to true and level 0 is always used\n  __globalLayerLevels: [true],\n\n  /**\n   * Set the element's z-index to the top layer\n   */\n  assignLayerLevel: function(element) {\n    if (!exists(element)) return;\n    // Find the highest used layer\n    for (var i = this.__globalLayerLevel; i >= 0; i--) {\n      if (this.__globalLayerLevels[i]) {\n        this.__globalLayerLevel = i;\n        break;\n      }\n    }\n\n    var level = ++this.__globalLayerLevel;\n    this.__globalLayerLevels[level] = true;\n    element.setStyle('zIndex', this.__globalLayerLevel + 3000);\n  },\n  /**\n   * Marks a layer level as unused, call this function\n   * when closing an element\n   */\n  unAssignLayerLevel: function(element) {\n    if (!exists(element)) return;\n    var level = element.dom.style.zIndex;\n    if (level == '') {\n      return;\n    }\n    this.__globalLayerLevels[parseInt(level) - 3000] = false;\n  },\n};\n"},4648:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n// Place where to put page-wide Indico-related global variables\nwindow.IndicoGlobalVars = {};\n\nvar Util = {\n  parseId: function(id) {\n    /* Returns the type and the split ids for a provided composite id */\n\n    // [1234.]s0\n    var m = id.match(/^(?:([^\\.]+)\\.)?s(\\d+)$/);\n    if (m) {\n      return concat(['Session'], m.slice(1));\n    }\n\n    // [1234.]s0.0\n    m = id.match(/^(?:([^\\.]+)\\.)s(\\d+)(?:\\.|l)(\\d+)$/);\n    if (m) {\n      return concat(['SessionSlot'], m.slice(1));\n    }\n\n    // [1234.]33\n    m = id.match(/^(?:([^\\.]+)\\.)(\\d+)$/);\n    if (m) {\n      return concat(['Contribution'], m.slice(1));\n    }\n\n    // otherwise, consider it is a conference\n    return concat(['Conference'], [id]);\n  },\n\n  //truncate titles which are too long\n  truncate: function(title, length) {\n    length = length || 25;\n    if (title.length > length) {\n      return title.substring(0, length - 3) + '...';\n    } else {\n      return title;\n    }\n  },\n\n  /* formats a datetime dictionary, Date object\n       or string date (different format) as a string,\n       considering formats (sourceFormat used for strings only) */\n  formatDateTime: function(obj, format, sourceFormat) {\n    // default value\n    format = format || '%d/%m/%Y %H:%M';\n\n    var m1 = null,\n      m2 = null;\n\n    if (obj === null) {\n      return null;\n    }\n    // handle date object\n    else if (obj.constructor == Date) {\n      m1 = [null, obj.getFullYear(), obj.getMonth() + 1, obj.getDate()];\n      m2 = [null, zeropad(obj.getHours()), obj.getMinutes(), obj.getSeconds()];\n    } else if (typeof obj == 'object') {\n      // handle datetime dictionaries\n      // data comes from the server in %Y-%m-%d %H:%M:%S\n      m1 = obj.date.match(/(\\d+)[\\-\\/](\\d+)[\\-\\/](\\d+)/);\n      m2 = obj.time.match(/(\\d+):(\\d+)(?:\\:(\\d+))?/);\n    } else if (sourceFormat) {\n      // handle strings\n\n      // parse first\n      var results = Util.__parseDateTime(obj, sourceFormat);\n\n      // map the results\n      m1 = [null, results['%Y'], results['%m'], results['%d']];\n      //If we do not want to show the hour\n      m2 = [null, any(results['%H'], ''), any(results['%M'], ''), any(results['%S'], '')];\n    } else {\n      throw 'unknown source object';\n    }\n\n    if (!m1 || !m2) {\n      return null;\n    }\n\n    return format\n      .replace('%Y', zeropad(m1[1]))\n      .replace('%m', zeropad(m1[2]))\n      .replace('%d', zeropad(m1[3]))\n      .replace('%H', zeropad(m2[1]))\n      .replace('%M', zeropad(m2[2]))\n      .replace('%S', zeropad(m2[3]));\n  },\n\n  /*\n   * Internal use only!\n   * Does basic date/time parsing\n   */\n  __parseDateTime: function(string, format) {\n    // default value\n    format = format || '%Y/%m/%d %H:%M';\n\n    if (!string) {\n      return null;\n    }\n\n    var tokenOrder = [];\n\n    // build a regular expression from the format spec\n    var reFormat = format\n      .replace(/%\\w/g, function(val) {\n        // add the tokens in order to the list\n        tokenOrder.push(val);\n        return val == '%Y' ? '(\\\\d{0,4})' : '(\\\\d{0,2})';\n      })\n      .replace(' ', '\\\\s+');\n\n    // apply the regexp\n    var m = string.match('^' + reFormat + '$');\n\n    if (!m) {\n      // if there are no matches, stop\n      return null;\n    }\n\n    // set seconds at zero (sometimes they're not specified)\n    var results = {'%Y': 0, '%m': 1, '%d': 1, '%H': 0, '%M': 0, '%S': 0};\n\n    // store the results, relative to the token order\n    for (var i = 1; i < m.length; ++i) {\n      results[tokenOrder[i - 1]] = m[i];\n    }\n\n    return results;\n  },\n\n  /* parses a string into a JavaScript Date object,\n       considering formats */\n  parseJSDateTime: function(string, format) {\n    var results = Util.__parseDateTime(string, format);\n\n    if (!results) {\n      return null;\n    }\n\n    var date = new Date(results['%Y'], results['%m'] - 1, results['%d']);\n\n    if (date.getDate() != results['%d'] || date.getMonth() + 1 != results['%m']) {\n      // stuff such as 31/11\n      return null;\n    }\n\n    setTime(date, [results['%H'], results['%M'], results['%S']]);\n\n    return date;\n  },\n\n  /* parses a string into a datetime dictionary,\n       considering formats */\n  parseDateTime: function(string, format) {\n    var results = Util.__parseDateTime(string, format);\n\n    if (!results) {\n      return null;\n    }\n\n    // build the actual dates in standard format (zeropad too)\n    return {\n      date: zeropad(results['%Y']) + '/' + zeropad(results['%m']) + '/' + zeropad(results['%d']),\n      time: zero
1pad(results['%H']) + ':' + zeropad(results['%M']) + ':' + zeropad(results['%S']),\n    };\n  },\n\n  dateTimeIndicoToJS: function(obj) {\n    var m1 = obj.date.match(/(\\d+)[\\-\\/](\\d+)[\\-\\/](\\d+)/);\n    var m2 = obj.time.match(/(\\d+):(\\d+)(?::(\\d+))?/);\n\n    var date = new Date(m1[1], m1[2] - 1, m1[3] || 0);\n    setTime(date, [m2[1], m2[2], m2[3]]);\n\n    return date;\n  },\n};\n\nUtil.Validation = {\n  isEmailAddress: function(address) {\n    // Adapted RFC2822 (thanks to Jan Goyvaerts)\n    return exists(\n      address\n        .toLowerCase()\n        .match(\n          /^[a-z0-9!#$%&\\'*+\\/=?\\^_`{|}~\\-]+(?:\\.[a-z0-9!#$%&\\'*+\\/=?\\^_`{|}~\\-]+)*@(?:[a-z0-9](?:[a-z0-9\\-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9\\-]*[a-z0-9])?$/\n        )\n    );\n  },\n\n  isURL: function(address) {\n    // per RFC2396, but forcing xxx: prefix and at least some text after\n    return exists(address.match(/^(([^:\\/?#]+):)((\\/\\/)?([^\\/?#]+))([^?#]*)(\\?([^#]*))?(#(.*))?$/));\n  },\n\n  isHtml: function(text) {\n    return /<.*>[\\s\\S]*<\\/.*>|<br\\s*\\/>/.exec(text);\n  },\n};\n\nvar IndicoDateTimeFormats = {\n  International: '%d/%m/%Y %H:%M',\n  ISO8601: '%Y/%m/%d %H:%M',\n  Ordinal: '%Y%m%d',\n};\n\nIndicoDateTimeFormats.Default = IndicoDateTimeFormats.International;\nIndicoDateTimeFormats.Server = IndicoDateTimeFormats.ISO8601;\n\nIndicoDateTimeFormats.DefaultHourless = IndicoDateTimeFormats.Default.split(' ')[0];\nIndicoDateTimeFormats.ServerHourless = IndicoDateTimeFormats.Server.split(' ')[0];\n"},53291:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\ntype(\n  'PreLoadHandler',\n  [],\n  {\n    execute: function() {\n      var self = this;\n\n      if (this.counter === 0) {\n        this.process();\n      } else {\n        $L(this.actionList).each(function(preloadItem) {\n          var hook = new WatchValue();\n          hook.set(false);\n          hook.observe(function(value) {\n            if (value) {\n              bind.detach(hook);\n              self.counter--;\n\n              if (self.counter === 0) {\n                self.process();\n              }\n            }\n          });\n\n          if (preloadItem.PreLoadAction) {\n            preloadItem.run(hook);\n          } else {\n            preloadItem.call(self, hook);\n          }\n        });\n      }\n    },\n  },\n  function(list, process) {\n    this.actionList = list;\n    this.counter = list.length;\n    this.process = process;\n  }\n);\n\ntype(\n  'ProgressDialog',\n  ['ExclusivePopup'],\n  {\n    draw: function() {\n      return this.ExclusivePopup.prototype.draw.call(\n        this,\n        $('<div class=\"loadingPopup\"/>').append($('<div class=\"text\"/>').html(this.text)),\n        {background: '#424242', border: 'none', padding: '20px', overflow: 'visible'},\n        {background: '#424242', border: 'none', padding: '1px', overflow: 'auto', display: 'inline'}\n      );\n    },\n  },\n  function(text) {\n    if (text === undefined) {\n      this.text = $T('Loading...');\n    } else {\n      this.text = text;\n    }\n    this.ExclusivePopup();\n  }\n);\n\nIndicoUI.Dialogs = {};\n"},15650:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/* eslint-disable import/unambiguous */\n\ntype(\n  'ExclusivePopup',\n  ['Printable'],\n  {\n    open: function() {\n      this.draw();\n      this.canvas.dialog('open');\n    },\n\n    draw: function(content, customStyle, popupStyle) {\n      customStyle = customStyle || {};\n      if (!content) {\n        content = '';\n      } else if (content.dom) {\n        // support indico XElement objects\n        content = content.dom;\n      }\n\n      if (popupStyle === undefined) {\n        popupStyle = customStyle;\n      }
1\n      this.contentContainer = $('<div class=\"exclusivePopup\"/>').css(popupStyle).append(content);\n\n      this.showCloseButton = !!this.title;\n      this._makeCanvas();\n      this.canvas.empty().css(customStyle).append(this.contentContainer);\n      this.dialogElement.css(customStyle);\n    },\n\n    close: function() {\n      if (this.isopen) {\n        this.canvas.dialog('close');\n      }\n    },\n\n    _getDialogOptions: function() {\n      return {};\n    },\n\n    _makeCanvas: function() {\n      if (!this.canvas) {\n        var opts = $.extend(\n          true,\n          {\n            closeText: null,\n            autoOpen: false,\n            draggable: true,\n            modal: true,\n            resizable: false,\n            closeOnEscape: true,\n            title: this.title,\n            minWidth: '250px',\n            minHeight: 0,\n            open: $.proxy(this._onOpen, this),\n            close: $.proxy(this._onClose, this),\n            beforeClose: $.proxy(this._onBeforeClose, this),\n          },\n          this._getDialogOptions()\n        );\n        this.canvas = $('<div/>').dialog(opts);\n        this.canvas.attr('aria-modal', 'true');\n      }\n      if (!this.dialogElement) {\n        this.dialogElement = this.canvas.dialog('widget');\n      }\n      this.buttons = this.dialogElement.find('.ui-dialog-buttonset button');\n    },\n\n    _onBeforeClose: function(e) {\n      // Close button clicked\n      if (\n        e.originalEvent &&\n        $(e.originalEvent.currentTarget).hasClass('ui-dialog-titlebar-close')\n      ) {\n        if (isFunction(this.closeHandler) && !this.closeHandler()) {\n          return false;\n        }\n      }\n      // Escape key\n      else if (e.originalEvent && e.originalEvent.keyCode === $.ui.keyCode.ESCAPE) {\n        e.stopPropagation(); // otherwise this triggers twice for some reason\n        if (this.closeHandler === null || !this.showCloseButton) {\n          // Ignore escape if we don't have a close button\n          // But trigger data-button-back in ajaxDialog\n          this.canvas.find('[data-button-back]').trigger('click');\n          return false;\n        }\n        if (isFunction(this.closeHandler) && !this.closeHandler()) {\n          return false;\n        }\n      }\n    },\n\n    _onOpen: function(e) {\n      this.isopen = true;\n      if (this.closeHandler === null || !this.showCloseButton) {\n        this.dialogElement.find('.ui-dialog-titlebar-close').hide();\n        if (!this.title) {\n          this.dialogElement.find('.ui-dialog-titlebar').hide();\n        }\n      }\n\n      var titleId = this.dialogElement.find('.ui-dialog-title').attr('id');\n      if (titleId) {\n        this.canvas.attr('aria-labelledby', titleId);\n      }\n\n      if (this.postDraw() === true) {\n        // refresh position\n        var pos = this.canvas.dialog('option', 'position');\n        this.canvas.dialog('option', 'position', pos);\n      }\n    },\n\n    postDraw: function() {},\n\n    _onClose: function(e, ui) {\n      this.isopen = false;\n      this.canvas.dialog('destroy');\n      this.canvas.remove();\n      this.canvas = this.dialogElement = null;\n      this.buttons = [];\n    },\n  },\n  function(title, closeButtonHandler, printable, showPrintButton, noCanvas) {\n    this.title = any(title, null);\n\n    // Called when user clicks the close button, if the function\n    // returns true the dialog will be closed.\n    this.closeHandler = any(closeButtonHandler, positive);\n    // the close button will be enabled in draw() so if that method is overridden it will not be drawn\n    this.showCloseButton = false;\n\n    // The maximum allowed height, used since it doesn't look\n    // very nice it the dialog gets too big.\n    this.maxHeight = 600;\n\n    // Decides whether the popup should be printable. That is, when the user\n    // clicks print only the content of the dialog will be printed not the\n    // whole page. Should be true in general unless the dialog is containing\n    // something users normally don't want to print, i.e. the loading dialog.\n    this.printable = any(printable, true);\n\n    // Whether to show the print button or not in the title\n    // Note: the button will only be shown if the popup dialog has a title.\n    // and is printable.\n    this.showPrintButton = any(showPrintButton && title && printable, false);\n\n    this.buttons = $();\n    if (!noCanvas) {\n      this._makeCanvas();\n    }\n  }\n);\n\n/**\n * Builds an exclusive popup with a button bar\n * Constructor arguments: the same ones as ExclusivePopup\n */\ntype(\n  'ExclusivePopupWithButtons',\n  ['ExclusivePopup'],\n  {\n    _getButtons: function() {\
1n      return null;\n    },\n    _getDialogOptions: function() {\n      var self = this;\n      var buttons = this._getButtons();\n      var dlgButtons = [];\n      this.defaultButton = null;\n      if (buttons) {\n        $.each(buttons, function(i, button) {\n          dlgButtons.push({\n            text: button[0],\n            click: button[1],\n          });\n          if (button.length > 2 && button[2]) {\n            self.defaultButton = i;\n          }\n        });\n      }\n      return {\n        buttons: dlgButtons,\n      };\n    },\n    _onOpen: function(e) {\n      this.ExclusivePopup.prototype._onOpen.call(this, e);\n      if (this.defaultButton !== null) {\n        this.buttons[this.defaultButton].focus();\n      }\n\n      this.canvas.scrollblocker({\n        overflowType: 'auto',\n      });\n    },\n    _onClose: function(e) {\n      this.ExclusivePopup.prototype._onClose.call(this, e);\n      $('body').off('mousewheel wheel');\n    },\n  },\n  function(title, closeButtonHandler, printable, showPrintButton, noCanvas) {\n    this.ExclusivePopup(title, closeButtonHandler, printable, showPrintButton, noCanvas);\n  }\n);\n\n/**\n * Utility function to display a simple alert popup.\n * You can think of it as an \"alert\" replacement.\n * It will have a title, a close button, and an OK button.\n * @param {Html or String} title The title of the error popup.\n * @param {Element} content Anything you want to put inside.\n */\ntype(\n  'AlertPopup',\n  ['ExclusivePopupWithButtons'],\n  {\n    draw: function() {\n      var content = $('<div/>')\n        .css({\n          maxWidth: '400px',\n          padding: '10px',\n          textAlign: 'center',\n        })\n        .append($('<div/>').css('textAlign', 'left').html(this.content));\n      return this.ExclusivePopupWithButtons.prototype.draw.call(this, content);\n    },\n\n    _getButtons: function() {\n      var self = this;\n      return [\n        [\n          $T('OK'),\n          function() {\n            self.close();\n            self.callback();\n          },\n        ],\n      ];\n    },\n  },\n\n  function(title, content, callback) {\n    if (content.dom) {\n      // Indico XElement object\n      content = content.dom;\n    }\n    this.content = content;\n    this.callback = callback || positive;\n    this.ExclusivePopupWithButtons(title, this.callback);\n  }\n);\n\n/**\n * Utility function to display a simple alert popup.\n * You can think of it as an \"confirm\" replacement.\n * It will have a title, a close button, an OK button and a Cancel button.\n * @param {Html or String} title The title of the error popup.\n * @param {Element} content Anything you want to put inside.\n * @param {function} handler A function that will be called with a boolean as argument:\n *                   true if the user pressers \"ok\", or false if the user presses \"cancel\"\n */\ntype(\n  'ConfirmPopup',\n  ['ExclusivePopupWithButtons'],\n  {\n    draw: function() {\n      return this.ExclusivePopup.prototype.draw.call(this, this.content, this.style);\n    },\n    _getButtons: function() {\n      var self = this;\n      return [\n        [\n          $T(this.buttonTitle),\n          function() {\n            self.close();\n            self.handler(true);\n          },\n        ],\n        [\n          $T(this.cancelButtonTitle),\n          function() {\n            self.close();\n            self.handler(false);\n          },\n        ],\n      ];\n    },\n  },\n\n  function(title, content, handler, buttonTitle, cancelButtonTitle, style) {\n    var self = this;\n\n    this.buttonTitle = buttonTitle || 'OK';\n    this.cancelButtonTitle = cancelButtonTitle || 'Cancel';\n    this.style = style || {};\n    this.content = content;\n    this.handler = handler;\n    this.ExclusivePopupWithButtons(title, function() {\n      self.handler(false);\n      return true;\n    });\n  }\n);\n\n/**\n * Utility function to display a three buttons popup.\n * The difference with ConfirmButton is the existence of a third button.\n * Apart from the title and close button, the three buttons display, two of them configurables and Cancel\n * @param {Html or String} title The title of the error popup.\n * @param {Element} content Anything you want to put inside.\n * @param {function} handler A function that will be called with an Integer as argument:\n *                   1 if the user press button1, 2 for button2, 0 for \"Cancel\"\n */\ntype(\n  'SpecialRemovePopup',\n  ['ExclusivePopupWithButtons'],\n  {\n    draw: function() {\
1n      return this.ExclusivePopupWithButtons.prototype.draw.call(this, this.content);\n    },\n\n    _getButtons: function() {\n      var self = this;\n      return [\n        [\n          $T(this.buttonTitle1),\n          function() {\n            self.close();\n            self.handler(1);\n          },\n        ],\n        [\n          $T(this.buttonTitle2),\n          function() {\n            self.close();\n            self.handler(2);\n          },\n        ],\n        [\n          $T('Cancel'),\n          function() {\n            self.close();\n            self.handler(0);\n          },\n        ],\n      ];\n    },\n  },\n\n  function(title, content, handler, buttonTitle1, buttonTitle2) {\n    var self = this;\n\n    this.buttonTitle1 = buttonTitle1;\n    this.buttonTitle2 = buttonTitle2;\n    this.content = content;\n    this.handler = handler;\n    this.ExclusivePopupWithButtons(title, function() {\n      self.handler(0);\n      return true;\n    });\n  }\n);\n\n/**\n * Utility function to display a popup with errors.\n * Useful for notifying the user of input mistakes or other errors.\n * @param {Html or String} title The title of the error popup.\n * @param {Array of String} errors An Array of strings with the errors to display.\n * @param {Html or String} afterMessage A message to display after the list of errors.\n */\ntype(\n  'ErrorPopup',\n  ['ExclusivePopup'],\n  {\n    draw: function() {\n      var errorList = null;\n      if (this.errors.length == 1) {\n        errorList = Html.div({className: 'errorList'}, this.errors[0]);\n      } else {\n        errorList = Html.ul('errorList');\n        each(this.errors, function(e) {\n          errorList.append(Html.li('', e));\n        });\n      }\n\n      return this.ExclusivePopup.prototype.draw.call(\n        this,\n        Widget.block([errorList, this.afterMessage]).dom\n      );\n    },\n  },\n\n  function(title, errors, afterMessage) {\n    this.afterMessage = afterMessage;\n    this.errors = errors;\n    this.ExclusivePopup(title, positive);\n  }\n);\n"},23846:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n(function() {\n  'use strict';\n\n  IndicoUI.Dialogs.Util = {\n    progress: function(text) {\n      var dialog = new ProgressDialog(text);\n      dialog.open();\n\n      return function() {\n        dialog.close();\n      };\n    },\n  };\n})();\n"},16180:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\nIndicoUI.Effect = {\n  followScroll: function() {\n    $.each($('.follow-scroll'), function() {\n      if (!$(this).data('original-offset')) {\n        $(this).data('original-offset', $(this).offset());\n      }\n\n      var eloffset = $(this).data('original-offset');\n      var windowpos = $(window).scrollTop();\n      if (windowpos > eloffset.top) {\n        if (!$(this).hasClass('sticky-scrolling')) {\n          $(this).data({\n            'original-left': $(this).css('left'),\n            'original-width': $(this).css('width'),\n          });\n          $(this).css('width', $(this).width());\n          $(this).css('left', eloffset.left);\n          $(this).addClass('sticky-scrolling');\n        }\n      } else {\n        if ($(this).hasClass('sticky-scrolling')) {\n          $(this).css('left', $(this).data('original-left'));\n          $(this).css('width', $(this).data('original-width'));\n          $(this).removeClass('sticky-scrolling');\n        }\n      }\n    });\n  },\n};\n"},18420:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\nfunction mouseOverSwitch(observable, overAction, outAction) {\n  observable.observeEvent('mouseover', function(event) {\n    overAction(event);\n  });\n\n  observable.observeEvent('mouseout', function(event) {\n    outAction(event);\n  });\n}\n\nfunction highlightWithMouse(observable, target) {\n  /*\n   * observable - trigger element (mouse over, out)\n   * target - target element (will look brighter)\n   */\n\n  if (target.__highlightSet) {\n    return observable;\n  }
1\n\n  mouseOverSwitch(\n    observable,\n    function() {\n      target.__tmpColor = target.dom.style.backgroundColor;\n      if (target.__tmpColor === '') {\n        return;\n      }\n\n      var bgColor = target.dom.style.backgroundColor;\n      var s = bgColor.match(/rgb\\((\\d+), (\\d+), (\\d+)\\)/);\n      if (!s) {\n        s = bgColor.match(/#(..)(..)(..)/);\n        if (!s) {\n          return;\n        }\n        s[1] = parseInt('0x' + s[1], 16);\n        s[2] = parseInt('0x' + s[2], 16);\n        s[3] = parseInt('0x' + s[3], 16);\n      }\n\n      s[1] = Math.floor(s[1] * 1.05);\n      s[2] = Math.floor(s[2] * 1.05);\n      s[3] = Math.floor(s[3] * 1.05);\n      target.setStyle('backgroundColor', 'rgb(' + s[1] + ',' + s[2] + ',' + s[3] + ')');\n    },\n    function() {\n      target.setStyle('backgroundColor', target.__tmpColor);\n    }\n  );\n\n  target.__highlightSet = true;\n\n  return observable;\n}\n\ntype('Printable', [], {\n  print: function(element, title) {\n    var body = $E(document.body);\n    var bodyCSSClasses = body.dom.className;\n    var elementCSSClasses = element.dom.className;\n\n    // Make sure element is visible when printing\n    element.dom.className = elementCSSClasses + ' print';\n\n    // Make sure body is invisible\n    body.dom.className = bodyCSSClasses += ' noprint';\n\n    window.print();\n\n    // Restore\n    //body.dom.className = bodyCSSClasses;\n    //element.dom.className = elementCSSClasses\n  },\n});\n"},16616:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n// Extensions to Presentation\n// Candidates for integration?\n\nextend(Html.prototype, {\n  ancestorOf: function(child) {\n    if (!child) {\n      return false;\n    } else if (this.dom == child.dom) {\n      return true;\n    } else if (child.getParent() === null) {\n      return false;\n    } else if (child.getParent() == this) {\n      return true;\n    } else if (child.getParent().dom == document.body) {\n      return false;\n    } else {\n      return this.ancestorOf(child.getParent());\n    }\n  },\n  replaceWith: function(element) {\n    this.getParent().dom.replaceChild(element.dom, this.dom);\n  },\n});\n\nextend(WatchObject.prototype, {\n  clone: function() {\n    return $O(this.getAll());\n  },\n});\n\nfunction eventTarget(event) {\n  return any(event.srcElement, event.target);\n}\n\nfunction $N(name) {\n  return translate(document.getElementsByName(name), $E);\n}\n\n// Function that always returns true\nfunction positive() {\n  return true;\n}\n\nfunction createObject(clazz, args) {\n  function Dummy() {}\n\n  Dummy.prototype = clazz.prototype;\n  var x = new Dummy();\n  x.constructor = clazz;\n  clazz.apply(x, args);\n\n  return x;\n}\n\nfunction escapeHTML(html) {\n  // from Prototype\n  return html.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\nvar SortCriteria = {\n  Default: function(e1, e2) {\n    return e1 == e2 ? 0 : e1 < e2 ? -1 : 1;\n  },\n  Integer: function(e1, e2) {\n    if (isNaN(parseInt(e1, 10)) || isNaN(parseInt(e2, 10))) {\n      return SortCriteria.Default(e1, e2);\n    } else {\n      return parseInt(e1, 10) == parseInt(e2, 10)\n        ? 0\n        : parseInt(e1, 10) < parseInt(e2, 10)\n          ? -1\n          : 1;\n    }\n  },\n};\n\nfunction partition(list, start, end, index, cmp) {\n  var pval = list.item(index);\n  list.swap(index, end);\n  var store = start;\n\n  for (var i = start; i < end; ++i) {\n    if (cmp(list.item(i), pval) < 0) {\n      list.swap(i, store);\n      store++;\n    }\n  }\n\n  list.swap(store, end);\n  return store;\n}\n\nfunction quicksort(list, start, end, cmp) {\n  if (end > start) {\n    var pnew = partition(list, start, end, start, cmp);\n    quicksort(list, start, pnew - 1, cmp);\n    quicksort(list, pnew + 1, end, cmp);\n  }\n}\n\nextend(WatchList.prototype, {\n  swap: function(source, destination) {\n    var itemS = this.item(source);\n    var itemD = this.item(destination);\n    this.replaceAt(source, itemD);\n    this.replaceAt(destination, itemS);\n  },\n\n  sort: function(compare) {\n    compare = compare || SortCriteria.Default;\n    quicksort(this, 0, this.length.get() - 1, compare);\n  },\n});\n\nHtml.unescaped = map({div: null, span: null}
1, function(value, elemType) {\n  return function() {\n    var res = Html[elemType].apply(this, arguments);\n    res.dom.innerHTML = res.get();\n    return res;\n  };\n});\n\ndeclareMixin = function(targetName, sourceName, mixins) {\n  type(targetName, [sourceName].concat(mixins), {}, function() {\n    this[sourceName].apply(this, arguments);\n  });\n};\n"},97940:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/**\n * Truncates a category path if it is too long.\n */\nUtil.truncateCategPath = function(list) {\n  var first = list.slice(0, 1);\n  var last = list.length > 1 ? list.slice(-1) : [];\n  list = list.slice(1, -1);\n\n  var truncated = false;\n\n  var chars = list.join('');\n  while (chars.length > 10) {\n    truncated = true;\n    list = list.slice(1);\n    chars = list.join('');\n  }\n\n  if (truncated) {\n    list = concat(['...'], list);\n  }\n\n  return translate(concat(first, list, last), function(val) {\n    return escapeHTML(val);\n  });\n};\n"},82159:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\nvar addedWebkitJqueryFixDiv = false;\n\ntype('IWidget', [], {\n  draw: function(content) {\n    return content;\n  },\n\n  postDraw: function() {},\n\n  _drawHeader: function() {\n    return '';\n  },\n\n  disable: function() {},\n\n  enable: function() {},\n\n  show: function() {},\n\n  hide: function() {},\n});\n\n/**\n * Base class for widgets that store internally a list and want to represent each item with a template;\n * the list will be a <ul> and the items a <li> each, bound to the item data.\n *\n * The <ul> will have a random DOM id and each <li> will also have a DOM id: the <ul>'s id + _ + item_key.\n *\n * Inherits from WatchObject: each item will have a key and a value (the item data).\n * When adding a new item, it will always appear last in the DOM.\n * To add / remove items use the corresponding WatchObject methods: \"set(key, item)\" to add, or \"set(key, null)\" to remove.\n *\n * This class also has a \"setMessage\" method, which will delete all the items in the <ul> and display the message inside.\n *\n * Inheriting classes will have to implement the _drawItem method.\n *\n * @param {String} listCssClass CSS class for the <ul> element.\n * @param {function} mouseoverObserver A function that will be called when the user hovers the mouse over one of the <li>.\n *                                     It will be called with 4 arguments:\n *                                     1. true / false. true for mouseover, false for mouseout.\n *                                     2. a \"pair\" object representing the item corresponding to the <li>.\n *                                     pair.key will be the item's key and pair.get() the item's data.\n *                                     3. the <li> XElement that received the event\n *                                     4. the event object\n *\n */\ntype(\n  'EnumWidget',\n  ['WatchObject', 'IWidget'],\n  {\n    /**\n     * The DOM id of the <ul> element, chosen randomly at construction.\n     */\n    getId: function() {\n      return this.id;\n    },\n\n    /**\n     * We cannot call this function \"clear\" because\n     * WatchObject's \"clear\" is defined in the WatchObject constructor\n     * and would squash our function.\n     */\n    clearList: function() {\n      var self = this;\n      self.message = null;\n      self.clear(); // this gets rid of the <li> representing (key,values)\n      self.domList.clear(); //this gets rid of eventual messages\n    },\n\n    /*\n     * Overloaded\n     */\n    _getHeader: function() {\n      return null;\n    },\n\n    /**\n     * Sets a message inside the list.\n     * This will delete all the internal data.\n     */\n    setMessage: function(message) {\n      this.clearList();\n      this.message = message;\n      this.domList.append(Html.li('listMessage', message));\n    }
1,\n\n    draw: function() {\n      var self = this;\n      var returnedDom = $B(self.domList, self, function(pair) {\n        var listItem = self._iteratingElement(\n          {id: self.id + '_' + pair.key, pair: pair},\n          self._drawItem(pair)\n        );\n        if (exists(self.mouseoverObserver)) {\n          listItem.observeEvent('mouseover', function(event) {\n            self.mouseoverObserver(true, pair, listItem, event);\n          });\n          listItem.observeEvent('mouseout', function(event) {\n            self.mouseoverObserver(false, pair, listItem, event);\n          });\n        }\n\n        self._postDraw(pair);\n        return listItem;\n      });\n\n      // Optional header\n      var header = self._getHeader();\n      if (header) {\n        self.domList.insert(header);\n      }\n\n      if (exists(this.message)) {\n        this.domList.append(self._iteratingElement('listMessage', this.message));\n      }\n      return this.IWidget.prototype.draw.call(this, returnedDom);\n    },\n\n    _postDraw: function(pair) {\n      return '';\n    },\n\n    _drawItem: function(pair) {\n      // Function to be overloaded by inheriting classes\n      // pair is a key, value pair that can be retrieved with pair.key, pair.get()\n      return '';\n    },\n  },\n\n  /**\n   * Constructor\n   */\n  function(listCssClass, mouseoverObserver) {\n    this.WatchObject();\n    this.id = Html.generateId();\n    this.domList = this._containerElement({id: this.id, className: listCssClass});\n    this.listCssClass = listCssClass;\n    this.mouseoverObserver = mouseoverObserver;\n  }\n);\n\ntype('ListWidgetMixin', [], {\n  _iteratingElement: function() {\n    return Html.li.apply(this, arguments);\n  },\n\n  _containerElement: function() {\n    return Html.ul.apply(this, arguments);\n  },\n});\n\ndeclareMixin('ListWidget', 'EnumWidget', ['ListWidgetMixin']);\n\n/**\n * Base class which adds selection capability to ListWidget.\n * See first the documentation of ListWidget.\n *\n * @param {function} selectedObserver Function that will be called when an item is selected / unselected.\n *                                    It will be passed the list of currently selected items, as a WatchObject\n *                                    similar to the internal WatchObject that holds all the items.\n *\n * @param {Boolean} onlyOne false by default. If true, selecting an item will deselect a previously selected item.\n *\n * @param {String} listCssClass see ListWidget doc.\n * @param {String} selectedCssClass CSS class that will be added to the <li> if they are selected.\n * @param {String} unselectedCssClass CSS class that will be added to the <li> if they are unselected.\n *\n * @param {function} mouseoverObserver see ListWidget doc.\n */\ntype(\n  'SelectableListWidget',\n  ['ListWidget'],\n  {\n    getSelectedList: function() {\n      return this.selectedList;\n    },\n\n    clearSelection: function() {\n      var self = this;\n      each(self.domList, function(listItem) {\n        if (listItem.dom.className.search(self.selectedCssClass) >= 0) {\n          listItem.dom.className = listItem.dom.className.replace(\n            self.selectedCssClass,\n            self.unselectedCssClass\n          );\n        }\n      });\n      this.selectedList.clear();\n    },\n\n    selectAll: function() {\n      var self = this;\n      each(self.domList, function(listItem) {\n        listItem.dom.className = self.selectedCssClass;\n      });\n      each(self, function(value, key) {\n        self.selectedList.set(key, value);\n      });\n      if (exists(self.selectedObserver)) {\n        self.selectedObserver(self.selectedList);\n      }\n    },\n\n    /**\n     * We cannot call this function \"clear\" because\n     * WatchObject's \"clear\" is defined in the WatchObject constructor\n     * and would squash our function.\n     */\n    clearList: function() {\n      this.clearSelection();\n      this.ListWidget.prototype.clearList.call(this);\n    },\n\n    /**\n     * Sets a message inside the list.\n     * This will delete all the internal data.\n     */\n    setMessage: function(message) {\n      this.clearSelection();\n      this.ListWidget.prototype.setMessage.call(this, message);\n    },\n\n    selectItem: function(listItem, pair) {\n      var self = this;\n      if (exists(self.selectedList.get(pair.key))) {\n        self.selectedList.set(pair.key, null);\n        listItem.dom.className = self.unselectedCssClass;\n      } else {\n        if (self.onlyOne) {\n          self.clearSelection();\n        }\n        self.selectedList.set(pair.key, pair.get());\n        listItem.dom.className = self.selectedCssClass;\n      }\n\n      if (exists(self.selectedObserver)) {\n        self.selectedObserver(self.selectedList);\n      }\n    },\n\n    getReturnedDOM: function(self) {\n      var dom = $B(\n        self.domList,\n        self,\n\n        function(pair) {\n          var listItem = Html.li({id: self.id + '_' + pair.key}, self._drawItem(pair));\n          if (pair.get().get('unselectable') === true) {\n            listItem.dom.className += ' unselectable';\n          } else {\n            listItem.observeClick(function(event) {\n              self.selectItem(listItem, pair);\n            });\n          }\n          if (exists(self.mouseoverObserver)) {\n            listItem.observeEvent('mouseover', function(event) {\n              self.mouseoverObserver(true, pair, listItem, event);\n            });\n            listItem.observeEvent('mouseout', function(event) {\n              self.mouseoverObserver(false, pair, listItem, event);\n            });\n          }\n          return listItem;\n        }\n      );\n\n      if (exists(this.message)) {\n        this.domList.append(Html.li('listMessage', this.message));\n      }\n\n      return dom;\n    }
1,\n\n    draw: function() {\n      var self = this;\n      var returnedDom = self.getReturnedDOM(self);\n\n      return this.IWidget.prototype.draw.call(this, returnedDom);\n    },\n    _drawItem: function(pair) {\n      // Function to be overloaded by inheriting classes\n      // pair is a key, value pair that can be retrieved with pair.key, pair.get()\n      return '';\n    },\n  },\n  function(\n    selectedObserver,\n    onlyOne,\n    listCssClass,\n    selectedCssClass,\n    unselectedCssClass,\n    mouseoverObserver\n  ) {\n    this.selectedList = $O();\n    this.selectedObserver = selectedObserver;\n    this.onlyOne = any(onlyOne, false);\n    if (exists(selectedCssClass)) {\n      this.selectedCssClass = selectedCssClass;\n    } else {\n      this.selectedCssClass = 'selectedListItem';\n    }\n    if (exists(unselectedCssClass)) {\n      this.unselectedCssClass = unselectedCssClass;\n    } else {\n      this.unselectedCssClass = 'unselectedListItem';\n    }\n    this.ListWidget(listCssClass, mouseoverObserver);\n  }\n);\n\ntype(\n  'JTabWidget',\n  ['IWidget'],\n  {\n    _addTab: function(label, content, data) {\n      var id = _.uniqueId('x-tab-');\n      $(content).css('display', '').find('script').remove();
1\n      var container = $('<div/>', {id: id})\n        .data(data || {})\n        .html(content);\n      var navLink = $('<a>', {\n        href: '#' + id,\n        text: this._titleTemplate(label),\n      });\n      this.widget.append(container);\n      this.widget.find('.ui-tabs-nav').append($('<li>').append(navLink));\n      this.widget.tabs('refresh');\n      if (this.widget.tabs('option', 'active') === false) {\n        // focus first tab\n        this.widget.tabs('option', 'active', 0);\n      }\n      this._fixTabAria();\n    },\n    _fixTabAria: function(activeIndex) {\n      if (activeIndex === undefined) {\n        activeIndex = this.widget.tabs('option', 'active');\n      }\n      this.widget.find('.ui-tabs-nav > li').each(function(i) {\n        var li = $(this);\n        var a = li.find('> a');\n        var isActive = i === activeIndex;\n        li.attr('role', 'presentation')\n          .removeAttr('tabindex')\n          .removeAttr('aria-selected')\n          .removeAttr('aria-expanded');\n        a.attr('role', 'tab')\n          .attr('tabindex', isActive ? '0' : '-1')\n          .attr('aria-selected', String(isActive))\n          .attr('aria-expanded', String(isActive));\n        var controls = li.attr('aria-controls');\n        if (controls) {\n          a.attr('aria-controls', controls);\n        }\n      });\n    },\n    _titleTemplate: function(text) {\n      return text;\n    },\n    draw: function() {\n      var self = this;\n      // We are going to be visible right after this function, so let's update the scroll buttons when size information is available\n      _.defer(function() {\n        self._updateScrollButtons();\n        // Also call the on draw functions here\n        $.each(self._onDraw, function(i, func) {\n          func();\n        });\n        self._onDraw = [];\n      });\n      return self.widget[0];\n    },\n    enable: function() {\n      this.widget.tabs('enable');\n      for (var i = 0, num = this.widget.data('ui-tabs').anchors.length; i < num; i++) {\n        this.enableTab(i);\n      }\n      if (this.scrollable) {\n        this._updateScrollButtons();\n      }\n    },\n    disable: function() {\n      this.widget.tabs('disable');\n      for (var i = 0, num = this.widget.data('ui-tabs').anchors.length; i < num; i++) {\n        this.disableTab(i);\n      }\n      if (this.scrollable) {\n        this.scrollButtons.children().prop('disabled', true);\n      }\n    },\n    enableTab: function(index) {\n      this.widget.tabs('enable', index);\n    },\n    disableTab: function(index) {\n      this.widget.tabs('disable', index);\n    },\n    getLabel: function(index) {\n      return $('.ui-tabs-nav > li', this.widget).eq(index);\n    },\n    getTabIndex: function(label) {\n      var self = this;\n      return $('.ui-tabs-nav > li', self.widget)\n        .filter(function() {\n          return $(this).text() == self._titleTemplate(label);\n        })\n        .eq(0)\n        .index();\n    },\n    getSelectedIndex: function() {\n      return this.widget.tabs('option', 'active');\n    },\n    getSelectedTab: function() {\n      return this.getLabel(this.getSelectedIndex());\n    },\n    setSelectedTab: function(labelOrIndex) {\n      var idx = _.isNumber(labelOrIndex) ? labelOrIndex : this.getTabIndex(labelOrIndex);\n      this.widget.tabs('option', 'active', idx);\n      this.scrollToTab(idx, true);\n    },\n    getSelectedPanel: function() {\n      var index = this.widget.tabs('option', 'active');\n      return $('> div.ui-tabs-panel', this.widget).eq(index);\n    },\n    heightToTallestTab: function() {\n      var maxHeight = 0;\n      var panel = $('> div.ui-tabs-panel', this.widget);\n      panel.each(function() {\n        maxHeight = Math.max(maxHeight, $(this).height());\n      });\n      // Chrome sometimes shows scrollbars until a tab change if we don't wait a moment with applying the height\n      _.defer(function() {\n        panel.height(maxHeight);\n      });\n    },\n    makeScrollable: function() {\n      var self = this;\n      if (self.scrollable) {\n        return;\n      }\n      self.scrollable = true;\n      var nav = $('> .ui-tabs-nav', self.widget); // the ul containing the tabs\n      nav.css('padding', 0);\n      // by wrapping the div and disabling floating for tabs we ensure tabs do not wrap into another line\n      nav.wrap(\n        $('<div/>').css({\n          whiteSpace: 'nowrap',\n          overflow: 'hidden',\n        })\n      );\n      nav.find('> li').css({\n        display: 'block',\n        float: 'left',\n      });\n      // save some space for the scroll buttons\n      nav.css({\n        marginLeft: '17px',\n        marginRight: '15px',\n      });\n\n      var arrowsTopMargin = '4px';\n      var arrowsCommonCss = {\n        cursor: 'pointer',\n        zIndex: 99,\n        position: 'absolute',\n        top: '1px',\n        height: '26px',\n      };\n\n      self.scrollOffset = 0; // how many tabs did the user scroll to the right\n      // Create buttons to scroll left/right\n      self.scrollButtons = $('<div/>').disableSelection().css({\n        position: 'relative',\n        zIndex: 100,\n      });\n      $('<button type=\"button\"/>')\n        .disableSelection()\n        .css(arrowsCommonCss)\n        .css('left', 0)\n        .addClass('ui-state-active ui-corner-tl ui-corner-bl tab-scroll-button')\n        .append(\n          $('<span/>')\n            .disableSelection()\n            .addClass('ui-icon ui-icon-caret-1-w')\n            .html($T('Previous tab'))\n            .css('marginTop', arrowsTopMargin)\n        )\n        .click(function() {\
1n          self.scrollToTab(Math.max(0, self.scrollOffset - 1));\n        })\n        .appendTo(self.scrollButtons);\n      $('<button type=\"button\"/>')\n        .disableSelection()\n        .css(arrowsCommonCss)\n        .css('right', 0)\n        .addClass('ui-state-active ui-corner-tr ui-corner-br tab-scroll-button')\n        .append(\n          $('<span/>')\n            .addClass('ui-icon ui-icon-caret-1-e')\n            .html($T('Next tab'))\n            .css('marginTop', arrowsTopMargin)\n        )\n        .click(function() {\n          self.scrollToTab(Math.min(nav.find('> li').length - 1, self.scrollOffset + 1));\n        })\n        .appendTo(self.scrollButtons);\n      self.scrollButtons.prependTo(self.widget);\n      // Scroll to the currently selected tab\n      self.scrollToTab(self.getSelectedIndex(), true);\n    },\n    _updateScrollButtons: function() {\n      if (!this.scrollable) {\n        return;\n      }\n      var nav = $('.ui-tabs-nav:first', this.widget);\n      var visibleTabs = 0;\n      var width = 0;\n      var navWidth = nav.width();\n      nav.find('> li:visible').each(function() {\n        width += $(this).outerWidth(true);\n        if (width >= navWidth) {\n          return false;\n        }\n        visibleTabs++;\n      });\n      var lastElementShown = false;\n      if (this.scrollOffset + visibleTabs == nav.find('> li').length) {\n        lastElementShown = true;\n      }\n      // no prev allowed if scrolled to the far left\n      this.scrollButtons\n        .children()\n        .eq(0)\n        .prop('disabled', this.scrollOffset == 0);\n      // no next allowed if last element is visible\n      this.scrollButtons.children().eq(1).prop('disabled', lastElementShown);\n    },\n    scrollToTab: function(idx, fuzzy) {\n      var self = this;\n      if (!self.scrollable) {\n        return;\n      }\n      if (fuzzy && !$('.ui-tabs-nav:first', self.widget).width()) {\n        // If we want a fuzzy selection (show as many tabs as possible) and we do not have size information yet, delay everything\n        self._onDraw.push(function() {\n          self.scrollToTab(idx, fuzzy);\n        });\n        return;\n      } else if (fuzzy && idx > 0) {\n        // Yuck! Since tabs have different sizes (due to content and also selected/unselected) we need to test how far we need to scroll.\n        // Would be much nicer if we could calculate it but the width is not available at all for hidden elements.\n        var nav = $('.ui-tabs-nav:first', self.widget);\n\n        var origIdx = idx;\n        while (idx >= 0) {\n          self.scrollToTab(idx - 1); // try scrolling left 1 tab\n          if (nav.find(' > li').eq(origIdx).is(':hidden')) {\n            // if our tab is now hidden, scroll one tab forward again and stop\n            self.scrollToTab(idx);\n            return;\n          }\n          idx--;\n        }\n        // if we did not break before, something probably went wrong - let's scroll to the tab directly\n        self.scrollToTab(origIdx);\n      }\n\n      // Here we perform the actual \"scrolling\" (which is actually just hiding the \"scrolled out\" tabs)\n      self.scrollOffset = idx;\n      // show all tabs and then hide those before the visible ones\n      $('.ui-tabs-nav:first > li', self.widget).show().slice(0, self.scrollOffset).hide();\n      // hide the tabs after the visible ones (to ensure we don't get a \"half\" tab)\n      var nav = $('.ui-tabs-nav:first', self.widget);\n      var visibleTabs = 0;\n      var width = 0;\n      var updateTabsAfter = function() {\n        var navWidth = nav.width();\n        nav.find('> li:visible').each(function() {\n          width += $(this).outerWidth(true);\n          if (width >= navWidth) {\n            return false;\n          }\n          visibleTabs++;\n        });\n        $('.ui-tabs-nav:first > li', self.widget)\n          .slice(self.scrollOffset + visibleTabs)\n          .hide();\n      };\n      // We only have a width if the tab widget is already visible - otherwise don't do anything\n      if (nav.width()) {\n        updateTabsAfter();\n      } else {\n        self._onDraw.push(updateTabsAfter);\n      }\n      self._updateScrollButtons();\n    }
1,\n    _drawExtraButtons: function() {\n      // Add any extra buttons displayed under the tabs\n      var container = $('<div class=\"tabExtraButtons\"/>');\n      $.each(this.extraButtons, function(i, btnData) {\n        var btn = $(btnData.btn.dom || btnData.btn);\n        btn.click(function() {\n          btnData.onclick(btn);\n        });\n        container.append(btn);\n      });\n      this.widget.append(container);\n    },\n    _notifyTabChange: function() {},\n  },\n  function(tabs, width, height, initialSelection, extraButtons, canvas) {\n    var self = this;\n    self.scrollable = false;\n    self._onDraw = [];\n    self.widget = $('<div><ul/></div>');\n    self.extraButtons = extraButtons || [];\n    // create canvas element\n    if (canvas) {\n      self.canvas = $(canvas.dom || canvas);\n    } else {\n      self.canvas = $('<div/>');\n    }\n    self.width = exists(width)\n      ? typeof width == 'string' && width.indexOf('%') >= 0\n        ? width\n        : pixels(width)\n      : width;\n    if (self.width) {\n      self.widget.width(self.width);\n    }\n    if (height) {\n      self.widget.css('minHeight', height);\n    }\n    if (self.extraButtons) {\n      self._drawExtraButtons();\n    }\n    self.widget.tabs({\n      beforeActivate: function(e, ui) {\n        self._notifyTabChange();\n      },\n    });\n    self.widget.on('tabsactivate', function(e, ui) {\n      var idx = ui.newTab.index();\n      _.defer(function() {\n        self._fixTabAria(idx);\n      });\n    });\n    self.widget.find('.ui-tabs-nav')[0].addEventListener(\n      'keydown',\n      function(e) {\n        if (!e.target.matches('a')) {\n          return;\n        }\n        var anchors = Array.from(self.widget.find('.ui-tabs-nav li a'));\n        var idx = anchors.indexOf(e.target);\n        if (idx === -1) {\n          return;\n        }\n        var next;\n        if (e.key === 'ArrowRight') {\n          next = Math.min(idx + 1, anchors.length - 1);\n        } else if (e.key === 'ArrowLeft') {\n          next = Math.max(idx - 1, 0);\n        } else if (e.key === 'Home') {\n          next = 0;\n        } else if (e.key === 'End') {\n          next = anchors.length - 1;\n        } else {\n          return;\n        }\n        e.preventDefault();\n        e.stopImmediatePropagation();\n        if (next !== idx) {\n          self.setSelectedTab(next);\n        }\n        anchors[next].focus();\n      },\n      true\n    );\n    // add initial tabs\n    $.each(tabs, function(i, tab) {\n      var content = tab[1].dom ? tab[1].dom : tab[1];\n      self._addTab(tab[0], content);\n    });\n    if (initialSelection) {\n      self.setSelectedTab(initialSelection);\n    }\n  }\n);\n\ntype(\n  'JLookupTabWidget',\n  ['JTabWidget'],\n  {\n    _addTab: function(label, generator) {\n      var content = '<div/>';\n      this.JTabWidget.prototype._addTab.call(this, label, content, {generator: generator});\n    },\n    _generateContent: function(panel) {\n      var container = $('> div', panel);\n      var generator = $(panel).data('generator');\n      try {\n        var content = generator();\n      } catch (e) {\n        if (e == 'stopDrawing') {\n          return;\n        }\n        throw e;\n      }\n      if (content.dom) {\n        content = content.dom;\n      }\n      this.canvas.empty().append(content);\n      container.empty().append(this.canvas);\n    },\n  },\n  function(tabs, width, height, initialSelection, extraButtons, canvas) {\n    var self = this;\n    self.JTabWidget(tabs, width, height, initialSelection, extraButtons, canvas);\n    self.widget.bind('tabsactivate', function(e, ui) {\n      self._generateContent(ui.newPanel);\n    });\n    self._generateContent(self.getSelectedPanel());\n  }\n);\n\ntype(\n  'TabWidget',\n  ['IWidget'],\n  {\n    _titleTemplate: function(text) {\n      return text;\n    },\n\n    enableTab: function(index) {\n      this.tabs[index].dom.style.display = 'inline';\n    },\n\n    disableTab: function(index) {\n      this.tabs[index].dom.style.display = 'none';\n    },\n\n    enable: function() {\n      this.disableOverlay.dom.style.display = 'none';\n    }
1,\n\n    disable: function() {\n      this.disableOverlay.dom.style.display = 'block';\n    },\n\n    _notifyTabChange: function(from, to) {\n      // to be overloaded\n    },\n\n    _drawContent: function() {\n      var self = this;\n\n      try {\n        each(this.optionDict, function(value, key) {\n          if (key == self.selected.get()) {\n            value.dom.style.display = 'block';\n          } else {\n            value.dom.style.display = 'none';\n          }\n        });\n      } catch (e) {\n        if (e == 'stopDrawing') {\n          // Otherwise stop drawing\n        }\n      }\n    },\n\n    draw: function(dataRetrievalFunc) {\n      var self = this;\n\n      this.tabList = $B(Html.ul('tabList'), this.options, function(value) {\n        var liItem = Html.li(\n          value == self.selected.get() ? 'tabSelected' : 'tabUnselected',\n          Html.span({}, self._titleTemplate(value))\n        );\n        liItem.observeClick(function() {\n          self._notifyTabChange(self.selected.get(), value);\n          self.selected.set(value);\n        });\n        liItem.observeEvent('mouseover', function() {\n          liItem.setStyle('backgroundPosition', '0 0px');\n        });\n        liItem.observeEvent('mouseout', function() {\n          liItem.setStyle('backgroundPosition', '0 -30px');\n        });\n\n        self.selected.observe(function(selValue) {\n          liItem.dom.className = value == selValue ? 'tabSelected' : 'tabUnselected';\n        });\n\n        self.tabs.push(liItem);\n\n        return liItem;\n      });\n      var arrow, bg;\n\n      arrow = Html.div({className: 'tabScrollArrow', style: {backgroundPosition: '0 -30px'}});\n      bg = Html.div({className: 'tabScrollArrowBg'}, arrow);\n      this.scrollArrows.right = [bg, arrow];\n\n      arrow = Html.div({className: 'tabScrollArrow', style: {backgroundPosition: '0 -15px'}});\n      bg = Html.div({className: 'tabScrollArrowBg'}, arrow);\n      this.scrollArrows.left = [bg, arrow];\n\n      this.scrollArrows.left[0].observeClick(function(event) {\n        self.scrollLeft();\n      });\n      this.scrollArrows.right[0].observeClick(function(event) {\n        self.scrollRight();\n      });\n\n      // Mouseover / mouseout event for changing background\n      this.scrollArrows.left[0].observeEvent('mouseover', function() {\n        if (!self.scrollArrowStates.left) {\n          return;\n        }\n        self.scrollArrows.left[0].dom.style.backgroundPosition = '0 -75px';\n      });\n      this.scrollArrows.left[0].observeEvent('mouseout', function() {\n        if (!self.scrollArrowStates.left) {\n          return;\n        }\n        self.scrollArrows.left[0].dom.style.backgroundPosition = '0 -60px';\n      });\n      this.scrollArrows.right[0].observeEvent('mouseover', function() {\n        if (!self.scrollArrowStates.right) {\n          return;\n        }\n        self.scrollArrows.right[0].dom.style.backgroundPosition = '0 -75px';\n      });\n      this.scrollArrows.right[0].observeEvent('mouseout', function() {\n        if (!self.scrollArrowStates.right) {\n          return;\n        }\n        self.scrollArrows.right[0].dom.style.backgroundPosition = '0 -60px';\n      });\n\n      var extraButtons = '';\n      // Add any extra buttons displayed under the tabs\n      if (this.extraButtons.length) {\n        extraButtons = Html.div('tabExtraButtons');\n      }\n      each(this.extraButtons, function(btn, index) {\n        extraButtons.append(btn.btn);\n        btn.btn.observeClick(function() {\n          btn.onclick(btn.btn);\n        });\n      });\n\n      // this piece of code is sensitive to exceptions\n      // coming from the drawing functions (for LookupTabWidget)\n      if (dataRetrievalFunc) {\n        dataRetrievalFunc.call(this);\n      } else {\n        each(this.optionDict, function(value, key) {\n          self.canvas.append(value);\n        });\n      }\n\n      this._drawContent();\n\n      var wrapperStyle = this.width ? {width: this.width} : {};\n\n      this.container = this.IWidget.prototype.draw.call(\n        this,\n        Html.div(\n          {style: wrapperStyle},\n          Html.div(\n            {className: 'tabListContainer', style: {position: 'relative'}},\n            this.scrollArrows.left[0],\n            this.scrollArrows.right[0],\n            this.tabList,\n            Html.div(\n              'tabGradient',\n              extraButtons /*,\n                                       Html.div({className: 'tabBorderGradient', style: {cssFloat: 'left'}}),\n                                       Html.div({className: 'tabBorderGradient', style: {cssFloat: 'right'}})*/\n            )\n          ),\n          Html.div(\n            {\n              style: {\
1n                marginTop: pixels(10),\n                width: self.width ? self.width : 'auto',\n                minHeight: self.height ? pixels(self.height) : 'auto',\n              },\n            },\n            this.canvas\n          ),\n          this.disableOverlay\n        )\n      );\n\n      return this.container;\n    },\n\n    postDraw: function() {\n      var self = this;\n\n      // If no tabs exist just return\n      if (this.tabs.length === 0 || !this.checkTabOverflow()) {\n        return;\n      }\n\n      this.tabList.dom.style.paddingLeft = '35px';\n      this.tabList.dom.style.paddingRight = '35px';\n\n      // Iterate and hide all tabs that don't fit\n      for (var i = this.tabs.length - 1; i > 0; i--) {\n        if (self.checkTabOverflow()) {\n          this.tabs[i].dom.style.display = 'none';\n        } else {\n          self.rightTabIndex = i;\n          break;\n        }\n      }\n\n      this.scrollArrows.right[0].dom.style.left = pixels(this.container.dom.clientWidth - 17);\n\n      // Initially allow scroll right but not left\n      this.setScrollArrowState('right', true);\n      this.setScrollArrowState('left', false);\n    },\n\n    heightToTallestTab: function() {\n      var currentSelectedTab = this.getSelectedTab();\n      var maxHeight = 0;\n      for (var i = this.tabs.length - 1; i >= 0; i--) {\n        this.setSelectedTab(this.options.item(i));\n        maxHeight = Math.max(maxHeight, this.container.dom.offsetHeight);\n      }\n      this.container.setStyle('height', maxHeight);\n      this.setSelectedTab(currentSelectedTab);\n    },\n\n    checkTabOverflow: function(tab) {\n      // some magic numbers used... it would be great if a better solution\n      // was possible\n      return this.tabList.dom.offsetHeight > 30;\n    },\n\n    scrollRight: function() {\n      if (!this.scrollArrowStates.right) {\n        return;\n      }\n\n      var rightTab = this.tabs[this.rightTabIndex];\n      var nextRightTab = this.tabs[this.rightTabIndex + 1];\n\n      this.rightTabIndex++;\n\n      rightTab.dom.style.marginRight = '';\n      nextRightTab.dom.style.display = 'inline';\n\n      // Hide as many tab as needed to the left to make the new tab to the right fit\n      for (var i = this.leftTabIndex; i < this.tabs.length - 1 && this.checkTabOverflow(); i++) {\n        this.tabs[i].dom.style.display = 'none';\n        this.tabs[i].dom.style.marginLeft = '';\n        this.leftTabIndex++;\n      }\n\n      // Add as many tabs as possible to the right\n      for (i = this.rightTabIndex + 1; i < this.tabs.length; i++) {\n        this.tabs[i].dom.style.display = 'inline';\n        this.tabs[i - 1].dom.style.marginRight = '';\n        // If overflow, revert the changes\n        if (this.checkTabOverflow()) {\n          this.tabs[i].dom.style.display = 'none';\n          this.tabs[i].dom.style.marginRight = '';\n          break;\n        } else {\n          this.rightTabIndex++;\n        }\n      }\n\n      // Activate/deactivate the arrows accordingly\n      this.setScrollArrowState('left', true);\n      if (this.rightTabIndex == this.tabs.length - 1) {\n        this.setScrollArrowState('right', false);\n      }\n    },\n\n    scrollLeft: function() {\n      if (!this.scrollArrowStates.left) {\n        return;\n      }\n\n      var leftTab = this.tabs[this.leftTabIndex];\n      var prevLeftTab = this.tabs[this.leftTabIndex - 1];\n\n      this.leftTabIndex--;\n\n      leftTab.dom.style.marginLeft = '';\n      prevLeftTab.dom.style.display = 'inline';\n\n      // Hide as many tab as needed to the right to make the new tab to the left fit\n      for (var i = this.rightTabIndex; i >= 0 && this.checkTabOverflow(); i--) {\n        this.tabs[i].dom.style.display = 'none';\n        this.tabs[i].dom.style.marginRight = '';\n        this.rightTabIndex--;\n      }\n\n      // Add as many tabs as possible to the left\n      for (i = this.leftTabIndex - 1; i >= 0; i--) {\n        this.tabs[i].dom.style.display = 'inline';\n        this.tabs[i + 1].dom.style.marginLeft = '';\n        // If overflow, revert the changes\n        if (this.checkTabOverflow()) {\n          this.tabs[i].dom.style.display = 'none';\n          this.tabs[i].dom.style.marginLeft = '';\n          break;\n        } else {\n          this.leftTabIndex--;\n        }\n      }\n\n      // Acvtivate/deactivate the arrows accordingly\n      this.setScrollArrowState('right', true);\n      if (this.leftTabIndex < 1) {\n        this.setScrollArrowState('left', false);\n      }\n    },\n\n    setScrollArrowState: function(direction, active) {\n      var bg = this.scrollArrows[direction][0];\n      var arrow = this.scrollArrows[direction][1];\n      this.scrollArrowStates[direction] = active;\n\n      if (active) {\n        arrow.dom.style.backgroundPosition = '0 -' + pixels(direction == 'right' ? 0 : 15);\n        arrow.dom.style.borderColor = '#999999';\n        bg.dom.style.backgroundPosition = '0 -60px';\n        bg.dom.style.cursor = 'pointer';\n      } else {\n        arrow.dom.style.backgroundPosition = '0 -' + pixels(direction == 'right' ? 30 : 45);\n        arrow.dom.style.borderColor = '#D5D5D5';\n        bg.dom.style.backgroundPosition = '0 -90px';\n        bg.dom.style.cursor = '';\n      }\n      // Make sure arrow is visible\n      bg.dom.style.display = 'block';\n    },\n\n    /**\n     * Returns the string with the title of the currently selected tab\n     */\n    getSelectedTab: function() {\
1n      return this.selected.get();\n    },\n\n    /**\n     * Changed the currently selected tab\n     * @param {string} newSelectedTab The name of the new tab to be selected\n     */\n    setSelectedTab: function(newSelectedTab) {\n      this.selected.set(newSelectedTab);\n    },\n\n    initializeDisableOverlay: function() {\n      this.disableOverlay = Html.div({\n        style: {\n          display: 'none',\n          background: 'white',\n          opacity: '0.7',\n          height: '50px',\n          width: '100%',\n          position: 'absolute',\n          top: '0',\n          left: '0',\n          filter: 'alpha(opacity=70)',\n        },\n      });\n    },\n  },\n\n  function(options, width, height, initialSelection, extraButtons, canvas) {\n    var self = this;\n\n    this.width = exists(width)\n      ? typeof width == 'string' && width.indexOf('%') >= 0\n        ? width\n        : pixels(width)\n      : width;\n\n    this.height = height;\n    this.tabs = [];\n    this.leftTabIndex = 0;\n    this.rightTabIndex = 0;\n    this.scrollArrows = {};\n    this.scrollArrowStates = {};\n    this.extraButtons = any(extraButtons, []);\n    this.canvas = canvas || Html.div('canvas');\n\n    if (!exists(initialSelection)) {\n      initialSelection = options[0][0];\n    }\n\n    this.options = new WatchList();\n\n    //replace with appropriate method, when ready\n\n    $L(options).each(function(pair) {\n      var value = pair[1];\n      var key = pair[0];\n\n      self.options.append(key);\n    });\n\n    this.optionDict = {};\n    each(options, function(item) {\n      self.optionDict[item[0]] = item[1];\n    });\n\n    this.selected = new WatchValue();\n\n    this.selected.set(initialSelection);\n\n    this.selected.observe(function(value) {\n      self._drawContent();\n    });\n\n    this.initializeDisableOverlay();\n  }\n);\n\ntype(\n  'LookupTabWidget',\n  ['TabWidget'],\n  {\n    _drawContent: function() {\n      var self = this;\n\n      try {\n        this.canvas.set(this.optionDict[self.selected.get()]());\n      } catch (e) {\n        if (e == 'stopDrawing') {\n          // Otherwise stop drawing\n        }\n      }\n    },\n\n    draw: function() {\n      return this.TabWidget.prototype.draw.call(this, function() {\n        this.canvas.set(Html.div({}));\n      });\n    },\n  },\n\n  function(options, width, height, initialSelection, extraButtons, canvas) {\n    this.TabWidget(options, width, height, initialSelection, extraButtons, canvas);\n  }\n);\n\nvar progressIndicator = function(small, center) {\n  var htmlTag = small ? Html.span : Html.div;\n  return htmlTag(\n    center ? {style: {textAlign: 'center'}} : {},\n    Html.img({src: imageSrc(small ? 'loading' : 'load_big', 'gif'), alt: 'Loading...'})\n  );\n};\n\ntype(\n  'PopupWidget',\n  [],\n  {\n    draw: function(content, x, y, styles, setWidth) {\n      var self = this;\n\n      this.x = x;\n      this.y = y;\n\n      /*\n       * Create canvas if not already created.\n       * Used to avoid errors for classes that don't\n       * call the constructor.\n       */\n      if (!exists(this.canvas)) {\n        this.canvas = Html.div({});\n      }\n\n      styles = any(styles, {\n        // If the canvas has been set to fixed position don't change it\n        position: 'absolute',\n        left: pixels(x),\n        top: pixels(y),\n      });\n\n      if (setWidth) {\n        var width = $('body').width() - x;\n        styles.width = pixels(width);\n      }\n\n      $(this.canvas.dom).css(styles).html(content.dom);\n\n      IndicoUI.assignLayerLevel(this.canvas);\n\n      /* This fixes an issue caused by chrome/jquery (embedded e.g. by Adblock) which\n       * prevents dialogs from showing. Inserting an empty div fixes it (probably whatever\n       * incorrect was still active now affects that diff instead of the dialog)\n       * Related bug report: http://code.google.com/p/chromium/issues/detail?id=72568\n       */\n      if (!addedWebkitJqueryFixDiv && navigator.userAgent.indexOf('WebKit') != -1) {\n        $('body').append('<div/>');\n      }\n      addedWebkitJqueryFixDiv = true; // also set to true if we do not have a webkit browser so we don't check again\n\n      return this.canvas;\n    },\n\n    /**\n     * Opens the PopupWidget\n     * @param {Integer} x The horizontal position of the top left corner.\n     * @param {Integer} y The vertical position of the top left corner.\n     */\n    open: function(x, y) {\n      var stuff = this.draw(x, y).dom;\n      $('body').append(stuff);\n      this.isopen = true;\n      this.postDraw();\n    }
1,\n\n    isOpen: function() {\n      return this.isopen;\n    },\n\n    postDraw: function() {},\n\n    close: function(e) {\n      var self = this;\n\n      IndicoUI.unAssignLayerLevel(this.canvas);\n      $E(document.body).remove(self.canvas);\n\n      this.isopen = false;\n    },\n  },\n  function(styleData) {\n    this.canvas = Html.div({style: styleData} || {});\n  }\n);\n\ntype(\n  'HistoryListener',\n  [],\n  {\n    _addToHistory: function(hash) {\n      // Add hash to history\n      if (this.historyBroker) {\n        this.historyBroker.setUserAction(hash);\n      }\n    },\n\n    registerHistoryBroker: function(broker) {\n      this.historyBroker = broker;\n    },\n\n    notifyHistoryChange: function(hash) {\n      this._retrieveHistoryState(hash);\n    },\n  },\n  function() {}\n);\n\ntype(\n  'ErrorAware',\n  [],\n  {\n    _setElementErrorState: function(element, text) {\n      return IndicoUtil.markInvalidField(element, text);\n    },\n\n    setError: function(text) {\n      if (!text) {\n        // everything back to normal\n        if (this._stopErrorList) {\n          each(this._stopErrorList, function(elem) {\n            elem();\n          });\n\n          this._stopErrorList = [];\n        }\n      } else {\n        this._setErrorState(text);\n      }\n      return this._stopErrorList;\n    },\n  },\n  function() {}\n);\n"},52251:n=>
1{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\ntype(\n  'RealtimeTextBox',\n  ['IWidget', 'WatchAccessor', 'ErrorAware'],\n  {\n    _setErrorState: function(text) {\n      this._stopErrorList = this._setElementErrorState(this.input, text);\n    },\n\n    draw: function() {\n      this.enableEvent();\n      return this.IWidget.prototype.draw.call(this, this.input);\n    },\n    get: function() {\n      if (this.input.dom.disabled) {\n        return null;\n      } else {\n        return this.input.get();\n      }\n    },\n    set: function(value) {\n      return this.input.set(value);\n    },\n    observe: function(observer) {\n      this.observers.push(observer);\n    },\n    observeEvent: function(event, observer) {\n      return this.input.observeEvent(event, observer);\n    },\n    observeOtherKeys: function(observer) {\n      this.otherKeyObservers.push(observer);\n    },\n    unbind: function() {\n      bind.detach(this.input);\n    },\n    disable: function() {\n      this.input.dom.disabled = true;\n    },\n    enable: function() {\n      this.input.dom.disabled = false;\n      this.enableEvent();\n    },\n    setStyle: function(prop, value) {\n      this.input.setStyle(prop, value);\n    },\n    notifyChange: function(keyCode, event) {\n      var value = true;\n      var self = this;\n\n      each(this.observers, function(func) {\n        value = value && func(self.get(), keyCode, event);\n      });\n      return value;\n    },\n    enableEvent: function() {\n      var self = this;\n\n      this.input.observeEvent('keydown', function(event) {\n        var keyCode = event.keyCode;\n        var value = true;\n\n        if (\n          (keyCode < 32 && keyCode != 8) ||\n          (keyCode >= 33 && keyCode < 46) ||\n          (keyCode >= 112 && keyCode <= 123)\n        ) {\n          each(self.otherKeyObservers, function(func) {\n            value = value && func(self.get(), keyCode, event);\n          });\n          return value;\n        }\n        return true;\n      });\n\n      // fire onChange event each time there's a new char\n      this.input.observeEvent('keyup', function(event) {\n        var keyCode = event.keyCode;\n\n        if (\n          !(\n            (keyCode < 32 && keyCode != 8) ||\n            (keyCode >= 33 && keyCode < 46) ||\n            (keyCode >= 112 && keyCode <= 123)\n          )\n        ) {\n          var value = self.notifyChange(keyCode, event);\n          Dom.Event.dispatch(self.input.dom, 'change');\n          return value;\n        }\n        return true;\n      });\n    },\n  },\n  function(args) {\n    this.observers = [];\n    this.otherKeyObservers = [];\n    this.input = Html.input('text', args);\n  }\n);\n\ntype(\n  'RealtimeTextArea',\n  ['RealtimeTextBox'],\n  {\n    onChange: function(callback) {\n      this.input.observeEvent('change', callback);\n    },\n  },\n  function(args) {\n    this.RealtimeTextBox(clone(args));\n    this.input = Html.textarea(args);\n  }\n);\n\n/**\n * Normal text field which is triggering an action when the user actions the ENTER key.\n */\ntype(\n  'EnterObserverTextBox',\n  ['IWidget', 'WatchAccessor'],\n  {\n    draw: function() {\n      return this.IWidget.prototype.draw.call(this, this.input);\n    },\n    get: function() {\n      return this.input.get();\n    },\n    set: function(value) {\n      return this.input.set(value);\n    },\n    observe: function(observer) {\n      this.input.observe(observer);\n    },\n  },\n  function(id, args, keyPressAction) {\n    var self = this;\n    this.input = Html.input(id, args);\n    // fire event when ENTER key is pressed\n    this.input.observeEvent('keypress', function(event) {\n      if (event.keyCode == 13) {\n        Dom.Event.dispatch(self.input.dom, 'change');\n        return keyPressAction();\n      }\n      Dom.Event.dispatch(self.input.dom, 'change');\n    });\n  }\n);\n"},9501:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\ntype(\n  'ChainedPopupWidget',\n  ['PopupWidget'],\n  {\n    clickTriggersClose: function(target) {\n      var result = true;\n\n      // check chain\n      each(this.chainElements, function(element) {\n        // since usually the first element of the chain is an Html.a...\n        element = element.ChainedPopupWidget ? element.canvas : element;\n        result = result && !element.ancestorOf(target);\n      });\n\n      return result && !this.canvas.ancestorOf(target);\n    },\n\n    open: function(x, y) {\n      var self = this;\n      if (this.active) {\n        // don't let the popup open twice [** Update: It's not working ! **]\n        return;\n      } else {\n        this.active = true;\n      }\n\n      this.PopupWidget.prototype.open.call(this, x, y);\n\n      var handler = function(event) {\n        if (self.clickTriggersClose($E(eventTarget(event)))) {\n          // if the click should be followed by a\n          // closing action (out of the chain)\n          self.close();\n          // call close() over the whole chain\n          each(self.chainElements, function(element) {\n            if (element.ChainedPopupWidget) {\n              element.close();\n            }\n          });\n        }\n      };\n\n      // define a handler for onclick events\n      $('html').on('click.chained_menu', handler);\n      this.handler = handler;\n    }
1,\n\n    postDraw: function() {\n      this.PopupWidget.prototype.postDraw.call(this);\n\n      if (this.alignRight) {\n        // Hide it to avoid flickering\n        this.canvas.dom.style.visibility = 'hidden';\n\n        // Place it to the left in order to measure the width\n        // if placed to the right it might be to wide for the page\n        this.canvas.dom.style.left = 0;\n\n        // Align right side of the canvas to the x position given\n        this.canvas.dom.style.left = pixels(this.x - this.canvas.dom.offsetWidth);\n\n        this.canvas.dom.style.visibility = 'visible';\n      }\n    },\n\n    close: function() {\n      // close() cleans up the onclick handler too\n      this.active = false;\n      $('html').off('click.chained_menu', this.handler);\n      this.PopupWidget.prototype.close.call(this);\n    },\n  },\n  function(chainElements, alignRight) {\n    this.PopupWidget();\n    this.chainElements = chainElements;\n    this.active = false;\n    this.alignRight = any(alignRight, false);\n  }\n);\n\ntype(\n  'PopupMenu',\n  ['ChainedPopupWidget'],\n  {\n    _processItem: function(pair) {\n      var self = this;\n      var value = pair.get();\n      var link = Html.a('fake-link', value.display);\n      link.setAttribute('id', pair.key);\n      link.setAttribute('title', value.description);\n\n      if (typeof value.action == 'string') {\n        link.setAttribute('href', value.action);\n        if (self.linkToExternalWindow) {\n          link.setAttribute('target', '_blank');\n        }\n        if (value.ajaxDialog) {\n          $(link.dom).ajaxDialog({\n            title: value.display,\n          });\n        }\n        if (self.closeOnClick || value.closeOnClick) {\n          link.observeClick(function() {\n            self.close();\n          });\n        }\n      } else {\n        link.observeClick(\n          value.action.PopupWidget || value.action.ExclusivePopup\n            ? function(e) {\n                if (self.selected) {\n                  self.selected.dom.className = null;\n                  self.selected = null;\n                }\n\n                link.dom.className = 'selected';\n                self.selected = link;\n                var pos = listItem.getAbsolutePosition();\n\n                each(self.items, function(item, key) {\n                  if (item.PopupWidget && item.isOpen()) {\n                    item.close();\n                  }\n                });\n\n                var target = pair.get().action;\n                target.open(pos.x + (target.alignRight ? 0 : link.dom.offsetWidth), pos.y - 1);\n\n                return false;\n              }\n            : function() {\n                // assume it's a callback function\n                var ret = pair.get().action(self);\n                if ((self.closeOnClick && ret !== false) || ret === true) {\n                  self.close();\n                }\n              }\n        );\n      }\n\n      var listItem = null;\n      if (pair.key === this.currentItem) {\n        listItem = Html.li('current', link);\n      } else {\n        listItem = Html.li({}, link);\n      }\n\n      return listItem;\n    },\n    close: function() {\n      if (this.closeHandler()) {\n        this.ChainedPopupWidget.prototype.close.call(this);\n      }\n    },\n    draw: function(x, y) {\n      var self = this;\n\n      // create an Html.ul from the items that\n      // were provided through the constructor\n      var content = $B(Html.ul(self.cssClass), this.items, function(pair) {\n        return self._processItem(pair);\n      });\n\n      return this.PopupWidget.prototype.draw.call(this, content, x, y);\n    },\n  },\n  function(\n    items,\n    chainElements,\n    cssClass,\n    closeOnClick,\n    alignRight,\n    closeHandler,\n    currentItem,\n    linkToExternalWindow\n  ) {\n    this.ChainedPopupWidget(chainElements, alignRight);\n    this.items = items;\n    this.currentItem = any(currentItem, []);\n    this.selected = null;\n    this.cssClass = 'popupList ' + any(cssClass, '');\n    this.closeOnClick = any(closeOnClick, false);
1\n    this.closeHandler = any(closeHandler, function() {\n      return true;\n    });\n    this.linkToExternalWindow = any(linkToExternalWindow, false);\n  }\n);\n\ntype(\n  'SectionPopupMenu',\n  ['PopupMenu'],\n  {\n    draw: function(x, y) {\n      var self = this;\n\n      var sectionContent = Html.ul(self.cssClass);\n\n      each(this.items, function(item, key) {\n        var section = null;\n        if (key !== '') {\n          section = Html.li('section', Html.div('line', Html.div('name', key)));\n          section.setAttribute('title', item['description']);\n        }\n\n        // add the menu items\n        var tmp = $B(Html.ul('subPopupList'), item['content'], _.bind(self._processItem, self));\n        sectionContent.append(Html.li({}, section, tmp));\n      });\n\n      return this.PopupWidget.prototype.draw.call(this, sectionContent, x, y);\n    },\n  },\n  function(items, chainElements, cssClass, closeOnClick, alignRight, closeHandler) {\n    this.ChainedPopupWidget(chainElements, alignRight);\n    this.items = items;\n    this.selected = null;\n    this.cssClass = 'popupList sectionPopupList ' + any(cssClass, '');\n    this.closeOnClick = any(closeOnClick, false);\n    this.closeHandler = any(closeHandler, function() {\n      return true;\n    });\n  }\n);\n\ntype(\n  'CheckPopupWidget',\n  ['ChainedPopupWidget'],\n  {\n    draw: function(x, y, maxHeight, styles) {\n      var self = this;\n      var optionsId = Html.generateId();\n\n      // need to store checkboxes, for IE compatibility\n      // purposes\n      this.checkboxes = {};\n\n      var content = null;\n\n      if (this.options.isEmpty() && this.noOptionsMessage) {\n        content = Html.ul(\n          {\n            className: 'popupList',\n            style: {\n              maxHeight: pixels(maxHeight),\n              fontStyle: 'italic',\n              color: '#444444',\n              padding: pixels(5),\n            },\n          },\n          this.noOptionsMessage\n        );\n        return this.PopupWidget.prototype.draw.call(this, content, x, y, styles);\n      }\n      content = $B(\n        Html.ul({\n          className: 'popupList popupListCheckboxes',\n          style: {\n            maxHeight: pixels(maxHeight),\n            overflowY: 'auto',\n            overflowX: 'hidden',\n            padding: pixels(2),\n          },\n        }),\n        this.options,\n        function(pair) {\n          var optionCheck = Html.checkbox({});\n          optionCheck.dom.name = optionsId;\n\n          optionCheck.observeClick(function(e) {\n            // Make sure the onclick event is not captured by\n            // parent elements as this would undo the click on the\n            // checkbox.\n            if (!e) {\n              e = window.event;\n            }\n            e.cancelBubble = true;\n            if (e.stopPropagation) {\n              e.stopPropagation();\n            }\n          });\n\n          self.checkboxes[pair.key] = optionCheck;\n          $B(optionCheck, self.object.accessor(pair.key));\n\n          var color = self.colors.get(pair.key);\n          if (!color) {\n            color = 'transparent';\n          }\n          var textColor = self.textColors.get(pair.key);\n          if (!textColor) {\n            textColor = 'black';\n          }\n\n          //var span = Html.div({className: 'item', style: {cursor: 'pointer'}}, )\n          var span = Html.span('wrapper', pair.get());\n\n          var li = Html.li(\n            {style: {backgroundColor: color, color: textColor, marginBottom: '2px'}},\n            optionCheck,\n            span\n          );\n\n          li.observeClick(function() {\n            self.object.set(pair.key, !self.object.get(pair.key));\n          });\n          return li;\n        }\n      );\n\n      return this.PopupWidget.prototype.draw.call(this, content, x, y, styles);\n    },\n\n    postDraw: function() {\n      // called after all the rendering is done\n      var self = this;\n\n      each(this.checkboxes, function(check, key) {\n        if (self.object.get(key)) {\n          check.set(true);\n        }\n      });\n    },\n  },\n\n  function(options, object, colors, textColors, chainElements, noOptionsMessage) {\n    this.ChainedPopupWidget(chainElements);\n    this.options = options;\n    this.object = object;\n    this.colors = colors;\n    this.textColors = textColors;\n\n    // A message to be shown if the the dict of options is empty\n    this.noOptionsMessage = any(noOptionsMessage, null);\n  }\n);\n"},72092:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/* eslint-disable import/unambiguou
1s */\n\n/**\n @namespace Utility functions for IndicoUI\n*/\n\nvar IndicoUtil = {\n  /**\n   * Formats a JS Date, returning a string (DD/MM/YY HH:MM)\n   * @param {Date} date A JS Date\n   * @param {Integer} weekDayOption If different from 0, will add the weekday in different ways.\n   * @return A string representation of the Date object\n   */\n  formatDateTime: function(date, weekDayOption) {\n    if (!exists(weekDayOption)) {\n      weekDayOption = 0;\n    }\n\n    var dateText =\n      zeropad(date.getDate()) +\n      '/' +\n      zeropad(date.getMonth() + 1) +\n      '/' +\n      date.getFullYear() +\n      ' ' +\n      zeropad(date.getHours()) +\n      ':' +\n      zeropad(date.getMinutes());\n\n    switch (weekDayOption) {\n      case 1:\n        return Indico.Data.WeekDays[date.getDay()] + ' ' + dateText;\n      case 2:\n        return Indico.Data.WeekDays[date.getDay()].substring(0, 3) + ' ' + dateText;\n      case 3:\n        return dateText + ' (' + Indico.Data.WeekDays[date.getDay()] + ')';\n      case 4:\n        return dateText + ' (' + Indico.Data.WeekDays[date.getDay()].substring(0, 3) + ')';\n      default:\n        return dateText;\n    }\n  },\n\n  /**\n   * Formats a JS Date, returning a string (YYYYMMDD)\n   * @param {Date} date A JS Date\n   * @return A string representation of the Date object\n   */\n  formatDate2: function(date) {\n    return '' + date.getFullYear() + zeropad(date.getMonth() + 1) + zeropad(date.getDate());\n  },\n\n  /**\n   * Parses a string (DD/MM/YY HH:MM) into a JS Date, returning a string\n   * @param {String} strDateTime A string representation of the date/time\n   * @return A JS Date with the parsed date/time\n   */\n  parseDateTime: function(strDateTime) {\n    strDateTime = strDateTime.split(' ');\n    if (strDateTime.length != 2) {\n      return null;\n    }\n\n    var sdate = parseDate(strDateTime[0]);\n    var stime = parseTime(strDateTime[1]);\n\n    if (!sdate || !stime) {\n      return null;\n    }\n\n    var sdatetime = new Date(sdate[2], sdate[1] - 1, sdate[0]);\n    setTime(sdatetime, stime);\n\n    return sdatetime;\n  },\n\n  parseDate: function(strDate) {\n    var sdate = parseDate(strDate);\n\n    if (!sdate) {\n      return null;\n    }\n\n    var sdatetime = new Date(sdate[2], sdate[1] - 1, sdate[0]);\n    setTime(sdatetime, [0, 0, 0, 0]);\n\n    return sdatetime;\n  },\n\n  /**\n   * Parses a json date object into a JS Date\n   * @param {Dictionary} A dictionary with date and time keys set\n   * @return A JS Date with the parsed date/time\n   */\n  parseJsonDate: function(date) {\n    var year = parseInt(date.date.substr(0, 4), 10);\n    var month = parseInt(date.date.substr(5, 2), 10);\n    var day = parseInt(date.date.substr(8, 2), 10);\n\n    var hour = parseInt(date.time.substr(0, 2), 10);\n    var min = parseInt(date.time.substr(3, 2), 10);\n\n    return new Date(year, month - 1, day, hour, min);\n  },\n\n  /**\n   *\n   */\n  isInteger: function(s) {\n    var isEmpty = function(s) {\n      return !exists(s) || s.length === 0;\n    };\n\n    var isDigit = function(c) {\n      return c >= '0' && c <= '9';\n    };\n\n    var i;\n\n    if (isEmpty(s)) {\n      return false;\n    }\n\n    for (i = 0; i < s.length; i++) {\n      var c = s.charAt(i);\n      if (!isDigit(c)) {\n        return false;\n      }\n    }\n    return true;\n  },\n\n  /**\n   * Determines if a string is in a valid time format (hh:mm)\n   * @param {String} s The input string\n   * @return {Booleab} true if the string is a valid time string, false otherwise\n   */\n  isTime: function(s) {\n    var regExp = new RegExp('(^([0-9]|[0-1][0-9]|[2][0-3]):([0-5][0-9])$)');\n    return regExp.test(s);\n  },\n\n  /**\n   * Marks an input field (text, checkbox or select) as invalid.\n   * It will change its CSS class so that it appears red, and place an error tooltip over it on mouseover.\n   * If the value of the field changes / the user types in the field, the red color and the tooltip will disappear\n   * @param {XElement} component: the field to mark as invalid\n   * @param {XElement} error: the string or Html object that will be displayed in the error popup.\n   * @return {[XElement, Array of functions]} returns an array with 2 elements:\n   *                                          -An Html.div() XElement with the tooltip that was created.\n   *                                          -An array of functions that should be called to cancel all the event observations.\n   */\n  markInvalidField: function(component, error, passive) {\n    // passive - don't check actively for changes / keypresses\n    if (component.dom.type) {\n      if (startsWith(component.dom.type, 'select')) {\n        if (component.dom.className.slice(-13) != 'hasError') {\n          component.dom.className += ' hasError';\n        }\n      } else {\n        if (component.dom.className.slice(-7) != 'hasError') {\n          component.dom.className += ' hasError';\n        }\n      }\n    } else {\n      if (component.dom.className.slice(-7) != 'hasError') {\n        component.dom.className += ' hasError';\n      }\n    }\n\n    var oList = []; //list of functions that we will call to stop observing events\n\n    var stopObserving = function() {\
1n      each(oList, function(value) {\n        value();\n      });\n    };\n\n    // we'll add a function that removes the coloring first of all\n    oList.push(function() {\n      $(component.dom).removeClass('hasError').qtip('destroy');\n    });\n\n    $(component.dom).qtip({\n      content: {\n        text: error.dom ? $(error.dom) : error,\n      },\n    });\n\n    if (!passive) {\n      $(component.dom).on('keypress change select click', stopObserving);\n    }\n\n    return oList;\n  },\n\n  /**\n   * Parameter manager.\n   * Checks if all the parameters in a form are correct.\n   * If not, it sets the input background-color to red and adds a tooltip explaining the error.\n   * How to use:\n   * 1. make one: pm = parameterManager()\n   * 2. add stuff to it: pm.add(Html.edit({style: {width: '300px'}}), 'text', false)\n   * 3. make checks: pm.check()\n   */\n  parameterManager: function() {\n    var entryList = new WatchList(); //elements to check\n    var classList = {}; //original class (style) value for each element to check\n    var eventList = {}; //all the 'error' events for each element to check\n\n    //var radioButtonChecks = {}; //\n    var radioButtonLabelStopObserving = {};\n\n    this.clear = function() {\n      entryList = new WatchList();\n      classList = {};\n      eventList = {};\n    };\n\n    /**\n     * Check entries for errors\n     */\n    this.check = function() {\n      var self = this;\n      var hasErrors = false;\n\n      entryList.each(function(value) {\n        var component = value[0];\n        var dataType = value[1];\n        var allowEmpty = value[2];\n        var extraCheckFunction = value[3];\n        var error = null;\n\n        // ErrorAware classes don't want to do this\n        if (component.ErrorAware) {\n          component.setError(false);\n        } else {\n          //--- Restore original values (if it is the second time, there must\n          //    be components with error styles)  ---\n\n          component.dom.className = classList[component.dom.id];\n\n          if (exists(eventList[component.dom.id])) {\n            // --- Remove all the ERROR observers\n            $L(eventList[component.dom.id]).each(function(value) {\n              value();\n            });\n\n            delete eventList[component.dom.id];\n          }\n          //---------------------------------\n        }\n\n        //--- Check if there are errors ---\n        if (dataType == 'checkBoxList' && !exists(extraCheckFunction)) {\n          if (!allowEmpty && !self.checkCheckBoxes(component)) {\n            // component must be the parent element of all the checkboxes\n            error = Html.span({}, $T('At least one must be selected'));\n          }\n        } else if (dataType == 'checkBox' && !exists(extraCheckFunction)) {\n          if (!allowEmpty && !component.dom.checked) {\n            error = Html.span({}, $T('Please enable the checkbox'));\n          }\n        } else if (dataType == 'radio' && !exists(extraCheckFunction)) {\n          if (!allowEmpty && !self.checkRadioButton(component)) {\n            error = Html.span({}, $T('Please choose an option'));\n          }\n        } else if (dataType == 'select' && !exists(extraCheckFunction)) {\n          if (!allowEmpty && !self.checkSelect(component)) {\n            error = Html.span({}, $T('Please choose an option'));\n          }\n        } else if (\n          dataType == 'int' &&\n          !(allowEmpty && trim(component.get()) === '') &&\n          !IndicoUtil.isInteger(component.get())\n        ) {\n          error = Html.span({}, $T('Field must be a number'));\n        } else if (\n          dataType == 'unsigned_int' &&\n          !(allowEmpty && trim(component.get()) === '') &&\n          (!IndicoUtil.isInteger(component.get()) || component.get() <= 0)\n        ) {\n          error = Html.span({}, 'Field must be a positive number');\n        } else if (\n          dataType == 'non_negative_int' &&\n          !(allowEmpty && trim(component.get()) === '') &&\n          (!IndicoUtil.isInteger(component.get()) || component.get() < 0)\n        ) {\n          error = Html.span({}, $T('Field must be a positive number'));\n        } else if (\n          dataType == 'datetime' &&\n          !(allowEmpty && trim(component.get()) === '') &&\n          !IndicoUtil.parseDateTime(component.get())\n        ) {\n          error = Html.span({}
1, $T('Date format is not valid. It should be dd/mm/yyyy hh:mm'));\n        } else if (\n          dataType == 'email' &&\n          !(allowEmpty && trim(component.get()) === '') &&\n          !Util.Validation.isEmailAddress(component.get())\n        ) {\n          error = Html.span({}, $T('Invalid e-mail address'));\n        } else if (\n          dataType == 'url' &&\n          !(allowEmpty && trim(component.get()) === '') &&\n          !Util.Validation.isURL(component.get())\n        ) {\n          error = Html.span({}, $T('Invalid URL'));\n        } else if (dataType == 'time' && !IndicoUtil.isTime(trim(component.get()))) {\n          error = Html.span({}, $T('Time format is not valid. It should be hh:mm'));\n        } else if (\n          !allowEmpty &&\n          component.get() != null &&\n          (!isString(component.get()) || trim(component.get()) === '')\n        ) {\n          error = Html.span({}, $T('Field is mandatory'));\n        }\n        if (exists(extraCheckFunction)) {\n          error = error || extraCheckFunction(component.get());\n        }\n        //--------------------------------\n\n        //---- Display error -------\n        if (exists(error)) {\n          hasErrors = true;\n\n          var oList;\n\n          if (component.ErrorAware) {\n            oList = component.setError(error);\n          } else if (dataType == 'checkBoxList' || dataType == 'radio' || dataType == 'select') {\n            oList = IndicoUtil.markInvalidField(component, error);\n          } else if (component.dom.type != 'radio') {\n            oList = IndicoUtil.markInvalidField(component, error);\n          } else {\n            // XXX: Is this code still used anywhere?!\n            // If yes, please add a comment where, if not let's remove it!\n            // The jQuery/qtip things here are untested btw.. if you find out where this is used, please test it. :)\n            var $label = $('#' + component.dom.id + 'Label');\n            var $component = $(component.dom);\n            $label.addClass('invalidLabel');\n            $component.addClass('invalid');\n            $component.add($label).qtip({\n              content: {\n                text: error.dom ? $(error.dom) : error,\n              },\n            });\n\n            var stopObserving = function() {\n              $component.qtip('destroy');\n              $label.qtip('destroy');\n            };\n            oList = [stopObserving]; //list of functions that we will call to stop observing events\n\n            if (!exists(radioButtonLabelStopObserving[component.dom.name])) {\n              radioButtonLabelStopObserving[component.dom.name] = [];\n            }\n            radioButtonLabelStopObserving[component.dom.name].push(stopObserving);\n\n            each($N(component.dom.name), function(component) {\n              component.observeEvent('click', function(event) {\n                each($N(component.dom.name), function(subcomponent) {\n                  $(subcomponent.dom).removeClass('invalid');\n                  $('#' + subcomponent.dom.id + 'Label').removeClass('invalidLabel');\n                });\n                if (exists(radioButtonLabelStopObserving[component.dom.name])) {\n                  each(radioButtonLabelStopObserving[component.dom.name], function(stopObserver) {\n                    stopObserver();\n                  });\n                }\n              });\n            });\n          }\n\n          if (!component.ErrorAware) {\n            eventList[component.dom.id] = oList;\n          }\n        }\n        //----------------------------\n      });\n      return !hasErrors;\n    };\n\n    this.checkRadioButton = function(component) {\n      var name = component.dom.name;\n      /*if (!exists(radioButtonChecks[name])) {\n                radioButtonChecks[name] = false;\n                var radioButtons = $N(name);\n                each (radioButtons, function(rb) {\n                    if (rb.dom.checked) {\n                        radioButtonChecks[name] = true;\n                    }\n                });\n            }\n            return radioButtonChecks[name];*/\n      var radioButtonChecks = false;\n      var radioButtons = $N(name);\n      each(radioButtons, function(rb) {\n        if (rb.dom.checked) {\n          radioButtonChecks = true;\n        }\n      });\n      return radioButtonChecks;\n    };\n\n    this.checkSelect = function(component) {\n      return component.dom.value != '';\n    };\n\n    // component must be the parent element of all the checkboxes\n    this.checkCheckBoxes = function(component) {\n      for (var i = 0; i < component.dom.childNodes.length;
1 i++) {\n        var node = component.dom.childNodes[i];\n        if (node.type == 'checkbox' && node.checked) {\n          return true;\n        }\n      }\n      return false;\n    };\n\n    /**\n     * Adds a form input to the list of parameters to check.\n     *  @param {Html component} component The input component\n     *  @param {String} dataType Type of the data: text, int, date, etc\n     *  @param {Boolean} allowEmpty True if the component is mandatory\n     *\n     */\n    this.add = function(component, dataType, allowEmpty, extraCheckFunction) {\n      if (component instanceof jQuery) {\n        component = $E(component[0]);\n      }\n\n      // Add new entry\n      entryList.append([component, dataType, allowEmpty, extraCheckFunction]);\n\n      // only for DOM elements\n      if (!component.ErrorAware) {\n        // Assign an auto ID, if the object has no ID (if it's a DOM element)\n        if (!component.dom.id) {\n          component.dom.id = Html.generateId();\n        }\n\n        classList[component.dom.id] = component.dom.className;\n      }\n      return component;\n    };\n\n    this.remove = function(component) {\n      if (component instanceof jQuery) {\n        component = $E(component[0]);\n      }\n\n      var removeEntry = null;\n      each(entryList, function(entry) {\n        if (entry[0] == component) {\n          removeEntry = entry;\n        }\n      });\n\n      if (removeEntry) {\n        entryList.remove(removeEntry);\n      }\n    };\n  },\n\n  /**\n   * Simple function used in sorting\n   */\n  compare: function(elem1, elem2) {\n    if (elem1 > elem2) return 1;\n    if (elem1 < elem2) return -1;\n    if (elem1 == elem2) return 0;\n  },\n};\n"},79423:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\nIndicoUI.Widgets = {\n  /**\n        @namespace Generic, reusable, components\n        */\n  Generic: {\n    /**\n     * (DEPRECATED)\n     * Creates a tooltip above the given element.\n     * Example of usage:\n     *\n     * var homeButtonPopup = function(event) {\n     *     IndicoUI.Widgets.Generic.tooltip(this, event, \"<span style='padding:3px'>Go to Indico Home Page</span>\");\n     * }\n     * $E('homeButton').dom.onmouseover = homeButtonPopup;\n     *\n     * @param {Object} in_this (view example)\n     * @param {Object} event (view example)\n     * @param {String} content Whatever content is desired.\n     */\n    tooltip: function(in_this, event, content) {\n      var $this = $(in_this);\n      if ($this.data('hasTooltip')) {\n        return;\n      }\n      $this.data('hasTooltip', true).qtip({\n        content: {\n          text: content,\n        },\n        show: {\n          ready: true,\n        },\n      });\n\n      // Return the onmouseout handler in case\n      // it needs to be called from outside\n      return function() {\n        $this.qtip('hide');\n      };\n    },\n  },\n};\n"},52664:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/**\n * Curries the method with the args.\n * @param {Function} method\n * @param {Object} ... args\n * @return {Function}\n */\nfunction curry(method) {\n  var args = $A(arguments, 1);\n  return function() {\n    return method.apply(this, concat(args, arguments));\n  };\n}\n\n/**\n * Returns a template that invokes an input value on the object with the arguments.\n * @param {Object} self\n * @param {Array} [args]\n * @return {Function} template\n */\nfunction invoker(object, args) {\n  args = any(args, []);\n  return function Invoke(func) {\n    return (arguments.callee.result = func.apply(object, args));\n  };\n}\n\n/**\n * Creates a sequential invoker from the functions.\n * @param {Function} ... functions\n * @return {Function} sequence\n */\nfunction sequence() {\n  var functions = compact(arguments);\n  return function Sequence() {\n    iterate(functions, invoker(this, $A(arguments)));\n  };\n}\n\n/**\n * Returns a multi-function with attachable methods.\n * @return {Function} commands\n */\nfunction commands() {\n  var methods = new Bag();
1\n  return mixinInstance(\n    function() {\n      return methods.each(invoker(this, $A(arguments)));\n    },\n    methods,\n    Attachable\n  );\n}\n\n/**\n * Creates a command from the method and the caption.\n * @param {Function} method\n * @param {String} caption\n * @return {Function} command\n */\nfunction command(method, caption) {\n  function Command() {\n    return method.apply(this, $A(arguments));\n  }\n  Command.caption = caption;\n  return Command;\n}\n\n/**\n * Invokes the method with the arguments if it exists.\n * @param {Function} command\n */\nfunction invoke(method) {\n  if (exists(method)) {\n    return method.apply(this, $A(arguments, 1));\n  }\n}\n\n/**\n * Delays execution of the method for the timeout and returns a function to cancel the delayed execution.\n * @param {Function} method\n * @param {Number} timeout\n * @return {Function} cancel\n */\nfunction delay(method, timeout) {\n  var id = setTimeout(method, timeout);\n  return function() {\n    clearTimeout(id);\n  };\n}\n\n/**\n * Defers execution of the method and returns a function to cancel the deferred execution.\n * @param {Function} method\n * @return {Function} cancel\n */\nfunction defer(method) {\n  return delay(method, 1);\n}\n\n/**\n * Schedule execution of the method and returns a function to cancel the scheduled execution.\n * @param {Function} method\n * @return {Function} cancel\n */\nfunction schedule(method) {\n  if (exists(method.scheduled)) {\n    return method.scheduled;\n  } else {\n    method.scheduled = defer(function() {\n      delete method.scheduled;\n      method();\n    });\n    return method.scheduled;\n  }\n}\n\nfunction delayedBind(target, key, builder) {\n  target[key] = function() {\n    var args = $A(arguments);\n    var method = builder.apply(this, args);\n    target[key] = method;\n    return method.apply(this, args);\n  };\n}\n"},226:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/**\n * Read-only source.\n */\ntype(\n  'Getter',\n  [],\n  {\n    /**\n     * Returns value from the source.\n     * @return {Object} value\n     */\n    get: function() {},\n  },\n  /**\n   *\n   * @param {Function} get\n   * @constructor\n   */\n  function(get) {\n    this.get = get;\n  }\n);\n\n/**\n * Read-write source.\n */\ntype(\n  'Accessor',\n  ['Getter'],\n  {\n    /**\n     * Sets the new value and returns the previous value.\n     * @param {Object} value\n     * @return {Object} old value\n     */\n    set: function(value) {},\n  },\n  /**\n   *\n   * @param {Function} get\n   * @param {Function} set\n   * @constructor\n   */\n  function(get, set) {\n    this.get = get;\n    this.set = set;\n  }\n);\n\n/**\n * Object with change notifications.\n */\ntype(\n  'Observable',\n  [],\n  {\n    /**\n     * Attaches the new observer and returns function that detaches the observer.\n     * @param {Function} observer\n     * @return {Function} function to detach observer\n     */\n    observe: function(observer) {},\n  },\n  /**\n   *\n   * @param {Function} observer\n   * @constructor\n   */\n  function(observer) {\n    this.observe = observer;\n  }\n);\n\n/**\n * Observable that can simulate a change.\n */\ntype(\n  'InvokableObservable',\n  ['Observable'],\n  {\n    /**\n     * Simulates a change invoking the given observer.\n     * @param {Function} observer\n     */\n    invokeObserver: function(observer) {},\n  },\n  /**\n   *\n   * @param {Function} observer\n   * @param {Function} invokeObserver\n   * @constructor\n   */\n  function(observer, invokeObserver) {\n    this.observe = observer;\n    this.invokeObserver = invokeObserver;\n  }\n);\n\n/**\n * Observable read-only source.\n */\ntype(\n  'WatchGetter',\n  ['Getter', 'InvokableObservable'],\n  {},\n  /**\n   *\n   * @param {Function} get\n   * @param {Function} observe\n   * @param {Function} invokeObserver\n   * @constructor\n   */\n  function(get, observe, invokeObserver) {\n    this.get = get;\n    this.observe = observe;\n    this.invokeObserver = invokeObserver;\n  }\n);\n\n/**\n * Observable read-write source.\n */\ntype(\n  'WatchAccessor',\n  ['WatchGetter', 'Accessor'],\n  {},\n  /**\n   *\n   * @param {Function} get\n   * @param {Function} set\n   * @param {Function} observe\n   * @param {Function} invokeObserver\n   * @constructor\n   */\n  function(get, set, observe, invokeObserver) {\n    this.get = get;\n    this.set = set;\n    this.observe = observe;\n    this.invokeObserver = invokeObserver;\n  }\n);\n\n/**\n * Sometimes unreadable source.\n */\ntype('CanGet', [], {\n  /**\n   * Returns true if the source can be read.\n   * @return {Boolean}
1 result\n   */\n  canGet: function() {\n    throw new Error('Not implemented');\n  },\n});\n\n/**\n * Object with manual refreshing.\n */\ntype(\n  'Refreshable',\n  [],\n  {\n    /**\n     * Triggers refresh.\n     */\n    refresh: function() {},\n  },\n  /**\n   *\n   * @param {Function} refresh\n   * @constructor\n   */\n  function(refresh) {\n    this.refresh = refresh;\n  }\n);\n\n/**\n * Source state enumeration.\n */\nvar SourceState = new Enum('None', 'Loading', 'Loaded', 'Committing', 'Error');\n\n/**\n * Abstract remote source.\n */\ntype('Source', ['Refreshable'], {\n  /**\n   * State of the source.\n   * @type {WatchGetter}\n   */\n  state: null,\n\n  /**\n   * Error of the source.\n   * @type {WatchGetter}\n   */\n  error: null,\n});\n\n/**\n * Source with enumerable items.\n */\ntype(\n  'Enumerable',\n  [],\n  {\n    /**\n     * Enumerates all items.\n     * @param {Function} iterator\n     * @return {Object} result of iterator\n     */\n    each: function(iterator) {},\n\n    isEmpty: function() {},\n  },\n  /**\n   *\n   * @param {Object} each\n   * @constructor\n   */\n  function(each, isEmpty) {\n    this.each = each;\n    this.isEmpty = isEmpty;\n  }\n);\n\ntype('WatchListable', ['Enumerable'], {\n  /**\n   * Attaches the list observer and returns a function to detach the observer.\n   * @param {Object} listObserver\n   * @return {Function}\n   */\n  observe: function(listObserver) {},\n});\n\n/**\n * Collection with sequential access.\n */\ntype(\n  'List',\n  ['Enumerable'],\n  {\n    /**\n     * Number of items.\n     * @type {Getter}\n     */\n    length: null,\n\n    /**\n     * Returns item at index\n     * @param {Number} index\n     * @return {Object} item\n     */\n    item: function(index) {},\n\n    /**\n     * Returns accessor for item\n     * @param {Number} index\n     * @return {Accessor}\n     */\n    accessor: function(index) {\n      var self = this;\n      return new Accessor(\n        function() {\n          return self.item(index);\n        },\n        function(value) {\n          return self.replaceAt(index, value);\n        }\n      );\n    },\n\n    /**\n     * Returns all items in an array\n     * @return {Array} items\n     */\n    allItems: function() {},\n\n    /**\n     * Appends the new item and returns an index of the item.\n     * @param {Object} item\n     * @return {Number} index\n     */\n    append: function(item) {},\n\n    /**\n     * Inserts the new item at the index if available,\n     * otherwise inserts the item at the beginning.\n     * Returns the index of the item.\n     * @param {Object} item\n     * @param {Number} [index]\n     * @return {Number} index\n     */\n    insert: function(item, index) {},\n\n    /**\n     * Removes the item and returns an index of the item.\n     * @param {Object} item\n     * @return {Number} index\n     */\n    remove: function(item) {},\n\n    /**\n     * Removes an item at the given index and returns the item.\n     * @param {Object} index\n     * @return {Object} item\n     */\n    removeAt: function(index) {},\n\n    /**\n     * Replaces item at index and returns previous item\n     * @param {Number} index\n     * @param {Object} item\n     * @return {Object} previous item\n     */\n    replaceAt: function(index, item) {\n      var old = this.removeAt(index);\n      this.insert(item, index);\n      return old;\n    },\n\n    /**\n     * Moves an item from the source to the destination index\n     * and returns the item.\n     * @param {Number} source\n     * @param {Number} destination\n     * @return {Object} item\n     */\n    move: function(source, destination) {\n      var item = this.removeAt(source);\n      this.insert(item, destination);\n      return item;\n    },\n\n    /**\n     * Removes all items and return these items.\n     * @returns {Array} cleared elements\n     */\n    clear: function() {},\n\n    /**\n     * Returns index of item or null if not found\n     * @param {Object} item\n     * @return {Number} index\n     */\n    indexOf: function(item) {},\n  },\n  /**\n   * Builds a new list on top of the array.\n   * @param {Array} array\n   * @constructor\n   */\n  function(array) {\n    array = getArray(array);\n    var self = this;
1\n    this.length = new Getter(function() {\n      return array.length;\n    });\n    this.item = function(index) {\n      return array[index];\n    };\n    this.allItems = function() {\n      return array.slice(0);\n    };\n    this.append = function(item) {\n      array.push(item);\n      return array.length - 1;\n    };\n    this.insert = function(item, index) {\n      if (!exists(index)) {\n        index = 0;\n      }\n      array.splice(index, 0, item);\n      return index;\n    };\n    this.remove = function(item) {\n      var index = this.indexOf(item);\n      if (exists(index)) {\n        array.splice(index, 1);\n      }\n      return index;\n    };\n    this.removeAt = function(index) {\n      return array.splice(index, 1)[0];\n    };\n    this.clear = function() {\n      var old = array;\n      array = [];\n      return old;\n    };\n    if (isFunction(array.indexOf)) {\n      this.indexOf = function(item) {\n        var index = array.indexOf(item);\n        return index === -1 ? null : index;\n      };\n    } else {\n      this.indexOf = function(item) {\n        for (var i in array) {\n          if (array[i] === item) {\n            return i;\n          }\n        }\n        return null;\n      };\n    }\n    this.each = function(iterator) {\n      return iterate(array, iterator);\n    };\n    this.isEmpty = function() {\n      return array.length === 0;\n    };\n  }\n);\n\n/**\n * Lookup\n */\ntype(\n  'Lookup',\n  [],\n  {\n    /**\n     * Gets a value using the key.\n     * @param {String} key\n     * @return {Object} value\n     */\n    get: function(key) {\n      throw new Error('Not implemented');\n    },\n  },\n  function(values) {\n    this.get = function(key) {\n      var value = values[key];\n      if (exists(value)) {\n        if (value.Getter) {\n          return value.get();\n        } else if (isFunction(value)) {\n          return value();\n        }\n      }\n      return value;\n    };\n  }\n);\n\n/**\n * Map-like collection.\n */\ntype('Dictionary', ['Lookup', 'Enumerable'], {\n  /**\n   * Sets the value with the key and returns a previous value.\n   * @param {String} key\n   * @param {String} value\n   * @return {Object} old\n   */\n  set: function(key, value) {\n    throw new Error('Not implemented');\n  },\n\n  /**\n   * Get values of the all properties.\n   * @return {Object} values\n   */\n  getAll: function() {\n    throw new Error('Not implemented');\n  },\n\n  /**\n   * Updates multiple properties.\n   * @param {Object} values\n   * @return {Object} changes\n   */\n  update: function(values) {\n    throw new Error('Not implemented');\n  },\n\n  /**\n   * Replaces multiple properties.\n   * @param {Object} values\n   * @return {Object} changes\n   */\n  replace: function(values) {\n    throw new Error('Not implemented');\n  },\n\n  /**\n   * Deletes all properties and returns their values.\
1n   * @method clear\n   * @return {Object} changes\n   */\n  clear: function() {\n    throw new Error('Not implemented');\n  },\n});\n\n/**\n * Attachable source.\n */\ntype('Attachable', [], {\n  /**\n   * Attaches the new item and returns a function to detach this item.\n   * @param {Object} item\n   * @param {Function} item remover\n   */\n  attach: function(item) {\n    throw new Error('Not implemented');\n  },\n});\n\n/**\n * Attachable source that can enumerate its items.\n */\ntype('EnumerableAttachable', ['Attachable', 'Enumerable']);\n\ntype('Equatable', [], {\n  /**\n   *\n   */\n  equals: function(item) {\n    return this == item;\n  },\n});\n"},40587:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/**\n * Invokes the iterator for the number times and returns a result of the iterator.\n * The iterator receives one argument, the number of the iteration.\n * @param {Number} number\n * @param {Object} iterator\n * @return {Object}\n */\nfunction times(number, iterator) {\n  for (var i = 0; i < number; i++) {\n    iterator(i);\n  }\n  return iterator.result;\n}\n\n/**\n * For each item of the list invokes the iterator with two arguments:\n * the item of the list and index of the item.\n * The list is an array like object: an array,\n * or an object with numeric property length (e.g. arguments).\n * The offset is an optional argument that specifies the offset where the iteration starts.\n * Returns a result from the iterator.\n * @param {Array, Object} list\n * @param {Function} iterator\n * @param {Number} [offset]\n * @return {Object}\n */\nfunction iterate(list, iterator, offset) {\n  for (var i = any(offset, 0), length = list.length; i < length; i++) {\n    iterator(list[i], i);\n  }\n  return iterator.result;\n}\n\n/**\n * For each property of the object invokes the iterator with two arguments:\n * a value of the property and a key of the property.\n * Returns a result from the iterator.\n * @param {Object} object\n * @param {Function} iterator\n * @return {Object}\n */\nfunction enumerate(object, iterator) {\n  for (var key in object) {\n    iterator(object[key], key);\n  }\n  return iterator.result;\n}\n\n/**\n * According the type of the source invokes specific iteration routine:\n * for Enumerables calls its method each(),\n * for array like objects invokes iterate(),\n * for numbers times(), and for other objects enumerate().\n * Returns a result of the used function.\n * @param {Enumerable, Array, Number, Object} source\n * @param {Function} iterator\n * @return {Object}\n */\nfunction each(source, iterator) {\n  if (!exists(source)) {\n    return iterator.result;\n  }\n  if (source.Enumerable) {\n    return source.each(iterator);\n  }\n  if (isArray(source) || isNumber(source.length)) {\n    return iterate(source, iterator);\n  }\n  if (isNumber(source)) {\n    return times(source, iterator);\n  }\n  return enumerate(source, iterator);\n}\n\nfunction builder(construct, build) {\n  var result = construct();\n  var iterator = function(value, key) {\n    build(result, key, value);\n  };\n  iterator.result = result;\n  return iterator;\n}\n\n/**\n * Returns an iterator that stacks items to an array\n * and sets the array as the result of the iterator.\n * If a template is provided,\n * it first applies the template to these items.\n * @param {Function} [template]\n * @return {Function}\n */\nfunction stacker(template) {\n  var result = [];\n  var iterator;\n  if (exists(template)) {\n    iterator = function(value, key) {\n      result.push(template(value, key));\n    };\n  } else {\n    iterator = function(value) {\n      result.push(value);\n    };\n  }\n  iterator.result = result;\n  return iterator;\n}\n\n/**\n * Returns an iterator that adds items to an object\n * and sets the object as the result of the iterator.\n * If a template is provided,\n * it first applies the template to these items.\n * @param {Function} [template]\n * @return {Function}\n */\nfunction mapper(template) {\n  var result = {};\n  var iterator;\n  if (exists(template)) {\n    iterator = function(value, key) {\n      result[key] = template(value, key);\n    };\n  } else {\n    iterator = function(value, key) {\n      result[key] = value;\n    };\n  }\n  iterator.result = result;\n  return iterator;\n}\n\nfunction where(match, action) {\n  function iterator(item, key) {\n    if (match(item, key)) {\n      action(item);\n    }\n  }\n  iterator.result = action.result;\n  return iterator;\n}\n\n/**\n * Returns an iterator\n * that invokes the action only for existing items\n * and sets a result from the action as the result of the iterator\n * @param {Function} action\n * @return {Function}\n */\nfunction existing(action) {\n  return where(exists, action);\n}\n\nfunction keyGetter(value, key) {\n  return key;\n}\n\n/**\n * Specifies that item was found.\n */\nvar $found = {type: 'Found'};\n\n/**\n * Returns an index of the first match. If no item matches returns null.\n * @param {Object} items\n * @param {Function} match\n * @return {Number}\n */\nfunction indexOf(items, match) {\n  var index = 0;\n  try {\n    each(items, function(value, key) {\n      if (match(value, key)) {\n        throw $found;\n      }\n      index++;\n    });\n    return null;\n  } catch (e) {\n    if (e === $found) {\n      return index;\n    }\n    throw e;\n  }\n}\n\n/**\n * Returns the value and the key of the first matched item.\n * @param {Object} items\n * @param {Object} match\n * @return {Array}\n */\nfunction search(items, match) {\n  var result;\n  try {\n    each(items, function(value, key) {\n      if (match(value, key)) {\n        result = [value, key];\n        throw $found;\n      }\n    });\n    return null;\n  } catch (e) {\n    if (e === $found) {\n      return result;\n    }\n    throw e;\n  }\n}\n\n/**\n * Returns template that return true if an input object (or its property with the given key) is equal to the value.\n * @param {Object}
1 value\n * @param {String} [key]\n * @param {Function}\n */\nfunction match(value, key) {\n  if (exists(key)) {\n    return function(object) {\n      return object[key] === value;\n    };\n  } else {\n    return function(object) {\n      return object === value;\n    };\n  }\n}\n"},4149:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\nfunction Point(x, y) {\n  if (!exists(x)) {\n    this.x = 0;\n    this.y = 0;\n  } else if (isObject(x)) {\n    this.x += x.x;\n    this.y += x.y;\n  } else if (!exists(y)) {\n    this.x = x;\n    this.y = x;\n  } else {\n    this.x = x;\n    this.y = y;\n  }\n}\n\nPoint.prototype = {\n  move: function(x, y) {\n    if (isObject(x)) {\n      this.x += x.x;\n      this.y += x.y;\n    } else {\n      this.x += x;\n      this.y += y;\n    }\n    return this;\n  },\n  scale: function(x, y) {\n    if (isObject(x)) {\n      this.x *= x.x;\n      this.y *= x.y;\n    } else if (!exists(y)) {\n      this.x *= x;\n      this.y *= x;\n    } else {\n      this.x *= x;\n      this.y *= y;\n    }\n    return this;\n  },\n};\n"},75764:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\nthis.global = this;\n\nif (typeof global.include !== 'function') {\n  var include = function(script) {\n    document.write('
1<script type=\"text/javascript\" src=\"' + script + '\"><\/script>');\n  };\n}\n\nfunction extract(text, start, stop) {\n  var startIndex;\n  if (!empty(start)) {\n    startIndex = text.indexOf(start);\n    if (startIndex < 0) {\n      return null;\n    }\n    startIndex += start.length;\n  }\n  var stopIndex = text.indexOf(stop, startIndex);\n  if (stopIndex < startIndex) {\n    return null;\n  }\n  return text.substring(startIndex, stopIndex);\n}\n\nfunction equals(a, b) {\n  if (!exists(a)) return !exists(b);\n  if (!exists(b)) return !exists(a);\n  if (a.Equatable) return a.equals(b);\n  if (b.Equatable) return b.equals(a);\n  return a == b;\n}\n\n/**\n * Two-way template that returns the input value.\n * @param {Object} value\n * @return {Object}\n */\nfunction pass(value) {\n  return value;\n}\npass.toTarget = pass;\npass.toSource = pass;\n\n/**\n * Two-way template that returns inverted value.\n * @param {Object} value\n * @return {Boolean}\n */\nfunction invert(value) {\n  return !value;\n}\ninvert.toTarget = invert;\ninvert.toSource = invert;\n\n/**\n * Returns true if the value is not undefined and not null.\n * @param {Object} value\n * @return {Boolean}\n */\nfunction exists(value) {\n  return value !== undefined && value !== null;\n}\n\n/**\n * Returns true if the value is an empty array or object with no properties.\n * @param {Object} value\n * @return {Boolean}\n */\nfunction empty(value) {\n  return (\n    !exists(value) ||\n    value === '' ||\n    (isArray(value) && value.length === 0) ||\n    (isObject(value) && (value.Enumerable ? value.isEmpty() : !hasProperties(value)))\n  );\n}\n\n/**\n * Returns true if the object has at least one property.\n * @param {Object} object\n * @return {Boolean}\n */\nfunction hasProperties(object) {\n  for (var key in object) {\n    return true;\n  }\n  return false;\n}\n\n/**\n * Returns the first existing value.\n * @param {Object} ... values\n * @return {Object}\n */\nfunction any() {\n  for (var i = 0, length = arguments.length; i < length; i++) {\n    var arg = arguments[i];\n    if (exists(arg)) {\n      return arg;\n    }\n  }\n}\n\n/**\n * Returns a return value of the first function that does not throw an exception.\n * @param {Function} ... functions\n * @return {Object}\n */\nfunction tryAny() {\n  for (var i = 0; i < arguments.length; i++) {\n    var method = arguments[i];\n    try {\n      return method();\n    } catch (e) {}\n  }\n}\n\nfunction get(value, builder) {\n  return exists(value) ? value : builder();\n}\n\n/**\n * Returns true if the value is an object.\n * @param {Object} value\n * @return {Boolean}\n */\nfunction isObject(value) {\n  // We do not use _.isObject here since it has another idea of what\n  // an \"object\" is.\n  return typeof value == 'object' && value !== null;\n}\n\n/**\n * Returns true if the value is a number.\n * @param {Object} value\n * @return {Boolean}\n */\nvar isNumber = _.isNumber;\n\n/**\n * Returns true if the value is a string.\n * @param {Object} value\n * @return {Boolean}\n */\nvar isString = _.isString;\n\n/**\n * Returns true if the value is a function.\n * @param {Object} value\n * @return {Boolean}\n */\nvar isFunction = _.isFunction;\n\n/**\n * Returns true if the value is an array.\n * @param {Object} value\n * @return {Boolean}\n */\nvar isArray = _.isArray;\n\n/**\n * Returns true if the value is a DOM node.\n * @param {Object} value\n * @return {Boolean}\n */\nfunction isDom(value) {\n  return exists(value) && isNumber(value.nodeType);\n}\n\n/**\n * Sets the value to the property of the object\n * if the property does not exist or contains a not existing value\n * and returns that new value.\n * Otherwise returns the current value of the property.\n * @param {Object} object\n * @param {String} property\n * @param {Object} value\n * @return {Object} result\n */\nfunction init(object, property, value) {\n  var result = object[property];\n  return exists(result) ? result : (object[property] = value);\n}\n\n/**\n * Initializes the property of the object using the initializer\n * if the property does not exist or contains a not existing value\n * and returns a value returned from the initializer.\n * Otherwise returns the current value of the property.\n * @param {Object} object\n * @param {String} property\n * @param {Function} initializer\n * @return {Object} result\n */\nfunction obtain(object, property, initializer) {\n  var result = object[property];\n  return exists(result) ? result : (object[property] = initializer());\n}\n\n/**\n * Copies properties\n * @param {Object} properties\n * @return {Object} result\n */\nfunction clone(object) {\n  var result = new object.constructor();\n  for (var key in object) {\n    result[key] = object[key];\n  }\n  return result;\n}\n\n/**\n * Copies properties of the source to the target and returns the target.\n * @param {Object} target\n * @param {Object} source\n * @return {Object} target\n */\nfunction extend(target, source) {\n  for (var key in source) {\n    target[key] = source[key];\n  }\n  return target;\n}\n\n/**\n * Function that does nothing.\n */\nfunction nothing() {}\n\n/**\n * Converts value to string. If the value is a function or does not exists it returns empty string.\n * @param {Object}
1 value\n * @return {String}\n */\nfunction str(value) {\n  switch (typeof value) {\n    case 'boolean':\n      return value ? 'true' : 'false';\n    case 'number':\n    case 'object':\n      if (value === null) {\n        return '';\n      }\n    case 'string':\n      return String(value);\n    default:\n      return '';\n  }\n}\n\n/**\n * Returns a function that creates an instance from given type.\n * @param {Function} type\n * @return {Function}\n */\nfunction construct(type) {\n  return function() {\n    return new type();\n  };\n}\n\nfunction provide(type) {\n  return function(value) {\n    return exists(value) ? value : new type();\n  };\n}\n\nvar newObject = construct(Object);\nvar newArray = construct(Array);\nvar getObject = provide(newObject);\nvar getArray = provide(newArray);\n\nfunction objectize(key, value) {\n  var obj = {};\n  obj[key] = value;\n  return obj;\n}\n\n/**\n * Browser detection from Prototype JavaScript Framework.\n */\nvar Browser = {\n  WebKit:\n    navigator.appVersion.indexOf('AppleWebKit/') > -1\n      ? extract(navigator.appVersion, 'AppleWebKit/', ' ')\n      : false,\n};\n"},79287:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/**\n * Returns true if the text starts with the string.\n * @param {String} text\n * @param {String} string\n * @return {Boolean} result\n */\nfunction startsWith(text, string) {\n  return string == text.slice(0, string.length);\n}\n\nvar specialCharMap = {\n  '\\b': '\\\\b',\n  '\\t': '\\\\t',\n  '\\n': '\\\\n',\n  '\\f': '\\\\f',\n  '\\r': '\\\\r',\n  '\\\\': '\\\\\\\\',\n  '\"': '\\\\\"',\n};\n\n/**\n * Returns escaped string text.\n * @param {String} text\n * @return {String}\n */\nfunction escapeString(text) {\n  return (\n    '\"' +\n    text.replace(/[\\x00-\\x1f\\\\\\\"]/g, function(value) {\n      if (value in specialCharMap) {\n        return specialCharMap[value];\n      } else {\n        return '\\\\u00' + zeroPad(value.charCodeAt(0).toString(16), 2);\n      }\n    }) +\n    '\"'\n  );\n}\n\n/**\n * Formats the text using the args.\n * @param {String} text\n * @param {Array, Object} args\n * @return {String}\n */\nfunction format(text, args) {\n  return text.replace(/(\\{\\{)|(\\}\\})|(\\{[^\\}]*\\})/g, function(string) {\n    switch (string) {\n      case '{{':\n        return '{';\n      case '}}':\n        return '}';\n    }\n    if (!exists(args)) {\n      return '';\n    }\n    return str(args[string.slice(1, -1)]);\n  });\n}\n\nfunction trim(text) {\n  return trimEnd(trimStart(text));\n}\n\nfunction trimStart(text) {\n  return text.replace(/^\\s+/, '');\n}\n\nfunction trimEnd(text) {\n  return text.replace(/\\s+$/, '');\n}\n\nfunction padLeft(text, size, character) {\n  while (text.length < size) {\n    text = character + text;\n  }\n  return text;\n}\n\nfunction zeroPad(text, size) {\n  return padLeft(text, size, '0');\n}\n"},2099:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/**\n * Returns true if the object is an array or is listable.\n * @param {Object} object\n * @return {Boolean}\n */\nfunction isArrayOrListable(object) {\n  return isArray(object) || (object.Enumerable && !object.Dictionary);\n}\n\n/**\n * Creates a new array from the list starting at given offset and applying the template to each item.\n * @param {Array, Object} items\n * @param {Number} [offset]\n * @param {Function} [template]\n * @return {Array}\n */\nfunction $A(list, offset, template) {\n  return iterate(list, stacker(template), offset);\n}\n\nfunction translate(source, template) {\n  return each(source, stacker(template));\n}\n\nfunction map(source, template) {\n  return each(source, mapper(template));\n}\n\nfunction dict(source, template) {\n  return each(\n    source,\n    builder(newObject, function(target, key, value) {\n      var pair = template(value, key);\n      target[pair[0]] = pair[1];\n    })\n  );\n}\n\n/**\n * Creates a new array that contains items from the list but not existing ones.\n * @param {Array, Object} items\n * @param {Number} [offset]\n * @param {Function} [template]\n * @return {Array}\n */\nfunction compact(list, offset, template) {\n  return iterate(list, existing(stacker(template)), offset);\n}\n\n/**\n * Returns true if the collection contains an item that matches.\n * @param {Object} collection\n * @param {Function} match\n * @return {Boolean} result\n */\nfunction includes(collection, match) {\n  return exists(indexOf(collection, match));\n}\n\n/**\n * Creates a new array that contains items from all the lists.\n * @param {Array, Object} ... lists\n * @return {Array}\n */\nfunction concat() {\n  var result = [];\n  iterate(arguments, function(arg) {\n    if (exists(arg)) {\n      iterate(arg, function(item) {\n        result.push(item);\n      });\n    }\n  });\n  return result;\n}\n\n/**\n * Creates a new object that contains merged properties of the input objects.\n * @params {Object} ... objects\n * @return {Object}\n */\nfunction merge() {\n  var result = {};\n  iterate(arguments, function(arg) {\n    if (exists(arg)) {\n      extend(result, arg);\n    }\n  });\n  return result;\n}\n\nfunction keys(source) {\n  return translate(source, keyGetter);\n}\n\n/**\n * Adds the value as a new property to the object,\n * notifies the observer, and returns a new key.\n * @param {Object} object\n * @param {Object}
1 value\n * @param {Function} observer\n * @return {String} key\n */\nfunction addProperty(object, value, observer) {\n  var key = 1;\n  while (key in object) {\n    key++;\n  }\n  object[key] = value;\n  observer(value, key, null);\n  return key;\n}\n\n/**\n * Changes a property with the given key of the object,\n * notifies the observer if the previous value was different,\n * and the returns previous value.\n * @param {Object} object\n * @param {Object} key\n * @param {Object} value\n * @param {Function} observer\n * @return {Object} old\n */\nfunction changeProperty(object, key, value, observer) {\n  var old = object[key];\n  if (old !== value) {\n    if (exists(value)) {\n      object[key] = value;\n    } else {\n      delete object[key];\n    }\n    observer(value, key, old);\n  }\n  return old;\n}\n\n/**\n * Changes properties of the object,\n * notifies the observer with changes,\n * and returns the changes.\n * @param {Object} object\n * @param {Object} values\n * @param {Function} observer\n * @return {Object} changes\n */\nfunction changeProperties(object, values, observer) {\n  var changes = {};\n  enumerate(values, function(value, key) {\n    var old = object[key];\n    if (!equals(old, value)) {\n      changes[key] = old;\n      object[key] = value;\n    }\n  });\n  enumerate(changes, function(value, key) {\n    observer(object[key], key, value);\n  });\n  return changes;\n}\n\n/**\n * Replaces properties of the object with new values,\n * notifies the observer with changes,\n * and returns the changes.\n * @param {Object} object\n * @param {Object} values\n * @param {Function} observer\n * @return {Object} changes\n */\nfunction replaceProperties(object, values, observer) {\n  var changes = clone(values);\n  enumerate(object, function(value, key) {\n    if (!(key in changes)) {\n      changes[key] = null;\n    }\n  });\n  return changeProperties(object, changes, observer);\n}\n\n/**\n * Creates watch getter from the watch accessor.\n * @param {WatchAccessor} accessor\n * @return {WatchGetter} getter\n */\nfunction getWatchGetter(accessor) {\n  return new WatchGetter(accessor.get, accessor.observe, accessor.invokeObserver);\n}\n\n/**\n * Returns true if the source implements CanGet interface and canGet() returns false.\n * @param {Object} source\n * @return {Boolean} result\n */\nfunction cannotGet(source) {\n  return source.CanGet ? !source.canGet() : false;\n}\n\n/**\n * Returns function that sets the value to the accessor.\n * @param {Accessor} accessor\n * @param {Object} value\n * @return {Function} setter\n */\nfunction setter(accessor, value) {\n  return function() {\n    accessor.set(value);\n  };\n}\n"},14950:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/**\n * Creates a named type from the mixins, the code, and with the constructor.\n * @param {String} name\n * @param {Array} mixins\n * @param {Object} [code]\n * @param {Function} [constructor]\n * @return {Function} constructor\n */\nfunction type(name, mixins, members, constructor) {\n  constructor = any(constructor, function() {});\n  members = getObject(members);\n  members[name] = constructor;\n  members.constructor = constructor;\n  constructor.prototype = members;\n  constructor.mixins = [name];\n  mixinType(constructor, mixins);\n  this[name] = constructor;\n  return constructor;\n}\n\n/**\n * Mixes mixins into the target type.\n * @param {Function} target\n * @param {Array} mixins\n * @return {Function} target\n */\nfunction mixinType(target, mixins) {\n  var code = target.prototype;\n  var mixs = target.mixins;\n  iterate(mixins, function(mixinName) {\n    const mixinCode = window[mixinName];\n    enumerate(mixinCode.prototype, function(value, key) {\n      if (!exists(code[key]) || code[key] === Object.prototype[key]) {\n        code[key] = value;\n      }\n    });\n    if (exists(mixinCode.mixins)) {\n      iterate(mixinCode.mixins, function(item) {\n        if (!code[item]) {\n          mixs.push(item);\n        }\n      });\n    } else {\n      // simple type\n      if (!code[mixinName]) {\n        mixs.push(mixinName);\n      }\n      init(code, mixinName, mixinCode);\n    }\n  });\n  return target;\n}\n\n/**\n * Mixes the mixin of the source into the target instance.\n * @param {Object} target\n * @param {Object} source\n * @param {Object} mixin\n * @return {Object} target;\n */\nfunction mixinInstance(target, source, mixin) {\n  enumerate(mixin.prototype, function(value, key) {\n    target[key] = source[key];\n  });\n  return target;\n}\n\n/**\n * Creates an enumeration of the given names.\n * @param {String} ... names\n * @constructor\n */\nfunction Enum() {\n  var self = this;\n  iterate(arguments, function(item, index) {\n    self[item] = index;\n  });\n}\n"},23933:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/**\n * Unordered attachable collection.\n */\ntype('Bag', ['EnumerableAttachable'], {}
1, function() {\n  var counter = 0;\n  var items = {};\n  // IE enumeration safe\n  var pendingItems = null;\n\n  function getDetach(id) {\n    return function detach() {\n      if (id in items) {\n        delete items[id];\n        return true;\n      }\n      return false;\n    };\n  }\n  this.each = function(iterator) {\n    pendingItems = {};\n    var result = enumerate(items, iterator);\n    extend(items, pendingItems);\n    pendingItems = null;\n    return result;\n  };\n\n  // defers set if enumerating (for IE)\n  this.attach = function(item) {\n    var id = counter++;\n    if (exists(pendingItems)) {\n      pendingItems[id] = item;\n    } else {\n      items[id] = item;\n    }\n    return getDetach(id);\n  };\n});\n"},84390:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n// SUPER LEVEL\n\n/**\n * Binds the target to the source using the template. If the target is a write-only elements, it uses bind.element(), otherwise it uses bind().\n * @param {Object} target\n * @param {Object} source\n * @param {Object} template\n */\nfunction $B(target, source, template) {\n  if (exists(target) && target.XElement && cannotGet(target)) {\n    return bind.element(target, source, template);\n  } else {\n    return bind(target, source, template);\n  }\n}\n\n// HIGH LEVEL\n\n/**\n * Direct binding.\n * @param {Accessor, List, Dictionary} target\n * @param {Object} source\n * @param {Function, String} [template]\n * @return {Accessor, List, Dictionary} target\n */\nvar bind = function(target, source, template) {\n  if (!exists(source)) {\n    return bind.detach(target);\n  }\n  if (!exists(target)) {\n    return obtainTemplate(template)(source);\n  }\n  if (target.Accessor) {\n    return bind.accessor(target, source, template);\n  }\n  if (target.List) {\n    return bind.toList(target, source, template);\n  }\n  if (target.Dictionary) {\n    return bind.toDictionary(target, source, template);\n  }\n  bind.detach(target);\n  throw new Error('Not implemented.');\n};\n\n/**\n * Place source to the target element.\n * @param {XElement} target\n * @param {XElement, Object} source\n * @param {Function} [template]\n */\nbind.element = function(target, source, template) {\n  return bind.attach(target, bind.internal.element(target, source, template));\n};\n\n// MEDIUM LEVEL\n\n/**\n * Two-way binding of accessors.\n * @param {Accessor} target\n * @param {Accessor, Object} source\n * @param {Object|String} [template]\n * @return {Accessor} target\n */\nbind.accessor = function(target, source, template, listenOnly) {\n  if (cannotGet(target)) {\n    if (cannotGet(source)) {\n      return bind.detach(target);\n    } else {\n      return bind.toAccessor(target, source, template);\n    }\n  } else if (source.Accessor) {\n    if (cannotGet(source)) {\n      return bind.toAccessor(source, target, template);\n    } else {\n      var toTarget, toSource;\n      if (!exists(template)) {\n        toTarget = null;\n        toSource = null;\n      } else if (isString(template)) {\n        toTarget = template;\n        toSource = function(value) {\n          source[template] = value;\n        };\n      } else {\n        toTarget = template.toTarget;\n        toSource = template.toSource;\n      }\n      var lock = {};\n      bind.toAccessor(target, source, toTarget, listenOnly, lock);\n      bind.toAccessor(source, target, toSource, true, lock);\n      return target;\n    }\n  } else {\n    return bind.toAccessor(target, source, template);\n  }\n};\n\n/**\n * Binds the target to the source accessor.\n * @param {Accessor} target\n * @param {Object} [source]\n * @param {Function, String} [template]\n * @return {Accessor} target\n */\nbind.toAccessor = function(target, source, template, listenOnly, lock) {\n  return bind.setter(\n    target,\n    source,\n    templatedSetter(template, function(value) {\n      if (lock) {\n        if (!lock.active) {\n          lock.active = true;\n          target.set(value);\n          lock.active = null;\n        }\n      } else {\n        target.set(value);\n      }\n    }),\n    listenOnly\n  );\n};\n\n/**\n * Binds the target list to the source.\n * @param {List} target\n * @param {Object} [source]\n * @param {Function, String} [template]\n * @return {List} target\n */\nbind.toList = function(target, source, template) {\n  return bind.setter(target, source, function(value) {\n    if (!exists(value)) {\n      target.clear();\n      return null;\n    }\n    if (isArrayOrListable(value)) {\n      return bind.internal.list(target, value, template);\n    }\n    return bind.internal.objectToList(target, value, template);\n  });\n};\n\n/**\n * Binds the target dictionary to the source.\n * @param {Dictionary} target\n * @param {Object} [source]\n * @param {Function, String} [template]\n * @return {Dictionary} target\n */\nbind.toDictionary = function(target, source, template) {\n  return bind.setter(target, source, function(value) {\n    if (!exists(value)) {\n      target.clear();\n      return null;\n    }\n    if (isArrayOrListable(value)) {\n      return bind.internal.listToDictionary(target, value, template);\n    }\n    return bind.internal.objectToDictionary(target, value, template);\n  });\n};\n\n// LOW LEVEL\n\n/**\n * Binds two lists.\n * @param {List} target\n * @param {WatchList, Enumerable, Array} source\n * @param {Function, String} [template]\n * @return {List} target\n */\nbind.list = function(target, source, template) {\n  return bind.attach(target, bind.internal.list(target, source, template));\n};\n\n/**\n * Binds the target list tp the source object.\n * @param {List} target\n * @param {WatchObject, Object} source\n * @param {Function, String} [template]\n * @return {List} target\n */\nbind.objectToList = function(target, source, template) {\n  return bind.attach(target, bind.internal.objectToList(target, source, template));\n};\n\n/**\n * Binds the target dictionary to the source list.\n * @param {Dictionary} target\n * @param {WatchList, Enumerable, Array} source\n * @param {Function, String} [template]\n * @return {Dictionary} target\n */\nbind.listToDictionary = function(target, source, template) {\n  return bind.attach(target, bind.internal.listToDictionary(target, source, template));\n};\n\n/**\n * Binds the target dictionary to the source object.\n * @param {Dictionary} target\n * @param {WatchObject, Object} source\n * @param {Function, String} [template]\n * @return {Dictionary} target\n */\nbind.objectToDictionary = function(target, source, template) {\n  return bind.attach(target, bind.internal.objectToDictionary(target, source, template));\n};\n\n/**\n * Binds the list sequentially\n * @param {List} target\n * @param {WatchList, Enumerable, Array} source\n * @param {Function} template\n */\nbind.sequence = function(target, source, template) {\n  return bind.attach(target, bind.internal.sequence(target, source, template));\n};\n\n/**\n * Binds the target using the setter to the source getter.\n * @param {Object} target\n * @param {Getter, Object} source\n * @param {Function} setter\n * @return {Object} target\n */\nbind.setter = function(target, source, setter, listenOnly) {\n  function gain(value, listenOnly) {\n    if (exists(value) && value.Getter) {\n      return bind.internal.getter(gain, value, listenOnly);\n    }\n    if (!listenOnly) {\n      setter(value);\n    }\n  }\n  return bind.attach(target, gain(source, listenOnly));\n};\n\n/**\n * Attaches the binding stop function to the target.\n * @param {Object} target\n * @param {Function} stop\n * @return {Object}\n */\nbind.attach = function(target, stop) {\n  if (target.unbind) {\n    target.unbind();\n    if (stop) {\n      target.unbind = stop;\n    } else {\n      delete target.unbind;\n    }\n  } else if (stop) {\n    target.unbind = stop;\n  }\n  return target;\n};\n\n/**\n * Unbinds the target.\n * @param {Object} target\n * @return {Object} target\n */\nbind.detach = function(target) {\n  if (target.unbind) {\n    target.unbind();\n    delete target['unbind'];\n  }\n  return target;\n};\n\n// INTERNAL LEVEL\n\nbind.internal = {};\n\nbind.internal.element = function(target, source, template) {\n  if (!exists(target)) {\n    return obtainTemplate(template)(source);\n  }\n\n  function gain(value) {\n    if (!exists(value) || value.XElement || !isObject(value)) {\n      target.clear();\n      target.append(obtainTemplate(template)(value, target));\n      return null;\n    }\n    if (value.Getter) {\n      return bind.internal.getter(gain, value);\n    }\n    if (isArrayOrListable(value)) {\n      return bind.internal.list(target, value, template);\n    }\n    return bind.internal.list(target, $L(value), template);\n  }\n\n  return gain(source);\n};\n\nbind.internal.getter = function(gain, value, listenOnly) {\n  var stop = gain(value.get(), listenOnly);\n  if (value.WatchGetter) {\n    return sequence(\n      stop,\n      value.observe(\n        sequence(stop, function(value) {\n          gain(value);\n        })\n      )\n    );\n  }\n  return stop;\n};\n\nbind.internal.list = function(target, list, template) {\n  template = obtainTemplate(template);\n  function inserter(item, index) {\n    target.insert(template(item, target), index);\n  }\n  target.clear();\n  return processListable(list, {\n    itemAdded: inserter,\n    itemRemoved: function(item, index) {\n      target.removeAt(index);\n    },\n    itemMoved: function(item, source, destination) {\n      target.move(source, destination);\n    },\n  });\n};\n\nbind.internal.objectToList = function(target, object, template) {\n  template = obtainTemplate(template);\n  if (object.WatchObject) {\n    var list = new WatchList();\n    each(object, function(value, key) {\n      list.append(new WatchPair(key, value));\n    });\n    return sequence(\n      bind.internal.list(target, list, template),\n      object.observe(function(value, key, obj, old) {\n        if (exists(value)) {\n          var result = search(list, match(key, 'key'));\n          if (exists(result)) {\n            result[0].set(value);\n          } else {\n            list.append(new WatchPair(key, value));\n          }\n        } else {\n          var index = indexOf(list, match(key, 'key'));\n          if (exists(index)) {\n            list.removeAt(index);\n          }\n        }\n      })\n    );\n  } else {\n    target.clear();\n    each(object, function(value, key) {\n      target.append(template(new WatchPair(key, value), target));\n    });\n  }\n  return null;\n};\n\nbind.internal.listToDictionary = function(target, source, template) {\n  throw new Error('Not implemented.');\n};\n\nbind.internal.objectToDictionary = function(target, source, template) {\n  if (source.WatchObject) {\n    throw new Error('Not implemented.');\n  }\n  if (exists(template)) {\n    source = map(source, template);\n  }\n  target.update(source);\n  return null;\n};\n\nbind.internal.sequence = function(target, list, template) {\n  template = obtainTemplate(template);
1\n  function update() {\n    target.clear();\n    each(list, function(item, index) {\n      target.insert(template(item, index, target), index);\n    });\n  }\n  update();\n  if (list.WatchList) {\n    return list.observe(update);\n  }\n  return null;\n};\n"},24674:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n// acceptable formats:\n//  'h', 'hh', 'hmm', 'hhmm',\n//  'h+', 'hh+', 'h+m', 'h+mm', 'hh+m', 'hh+mm'\n// returns [h, m]\n\n/**\n * Parses time from the text.\n * @param {String} text\n * @return {Array}\n */\nfunction parseTime(text) {\n  text = trim(text);\n  if (text == '') {\n    return null;\n  }\n  var hour = 0;\n  var min = 0;\n  if (/^\\d{1,4}$/.test(text)) {\n    // 'h', 'hh', 'hmm', 'hhmm'\n    switch (text.length) {\n      case 1:\n        hour = parseInt(text, 10);\n        break;\n      case 2:\n        hour = parseInt(text.substr(0, 2), 10);\n        break;\n      case 3:\n        hour = parseInt(text.substr(0, 1), 10);\n        min = parseInt(text.substr(1, 2), 10);\n        break;\n      case 4:\n        hour = parseInt(text.substr(0, 2), 10);\n        min = parseInt(text.substr(2, 2), 10);\n        break;\n    }\n  } else {\n    var match = /^(\\d{1,2})[\\W]+(\\d{1,2})?$/.exec(text);\n    if (!match) {\n      return null;\n    }\n    // 'h+', 'hh+', 'h+M', 'h+mm', 'hh+M', 'hh+mm'\n    hour = parseInt(match[1], 10);\n    var minText = any(match[2], '');\n    if (minText != '') {\n      min = parseInt(minText, 10);\n    }\n  }\n  return [hour, min];\n}\n\n// hh:mm\n/**\n * Formats time.\n * @param {Date} time\n * @return {String}\n */\nfunction formatTime(time) {\n  return time.getHours().toString() + ':' + time.getMinutes().toPaddedString(2);\n}\n\n// tuple is [hour, min, sec, msec]\n/**\n * Sets the time from the tuple.\n * @param {Date} time\n * @param {Array} tuple\n */\nfunction setTime(time, tuple) {\n  time.setHours(any(tuple[0], 0), any(tuple[1], 0), any(tuple[2], 0), any(tuple[3], 0));\n}\n\n// returns [hour, min, sec, msec]\n/**\n * Returns a tuple from the time.\n * @param {Date} time\n * @return {Array}\n */\nfunction getTime(time) {\n  return [time.getHours(), time.getMinutes(), time.getSeconds(), time.getMilliseconds()];\n}\n\n// acceptable formats:\n//  'd', 'dm', 'ddm', 'ddmm',\n//      'ddmmy', 'ddmmYY', 'ddmmyyy', 'ddmmyyyy',\n//      'd+', d+m', 'd+m+', 'd+m+YY', 'd+m+y'\n//  YY - current century\n// returns [d, m, y]\n\n/**\n * Parses date from the text.\n * @param {String} text\n * @return {Array}\n */\nfunction parseDate(text) {\n  text = trim(text);\n  if (text == '') {\n    return null;\n  }\n  var day = null;\n  var month = null;\n  var year = null;\n  if (/^\\d{1,8}$/.test(text)) {\n    // 'd', 'dm', 'ddm', 'ddmm', 'ddmmY', 'ddmmYY', 'ddmmyyy', 'ddmmyyyy'\n    switch (text.length) {\n      case 1:\n        day = parseInt(text.substr(0, 1), 10);\n        break;\n      case 2:\n        day = parseInt(text.substr(0, 1), 10);\n        month = parseInt(text.substr(1, 1), 10);\n        break;\n      case 3:\n        day = parseInt(text.substr(0, 2), 10);\n        month = parseInt(text.substr(2, 1), 10);\n        break;\n      case 4:\n        day = parseInt(text.substr(0, 2), 10);\n        month = parseInt(text.substr(2, 2), 10);\n        break;\n      case 5:\n        day = parseInt(text.substr(0, 2), 10);\n        month = parseInt(text.substr(2, 2), 10);\n        year = [parseInt(text.substr(4, 1), 10)];\n        break;\n      case 6:\n        day = parseInt(text.substr(0, 2), 10);\n        month = parseInt(text.substr(2, 2), 10);\n        year = [parseInt(text.substr(4, 2), 10)];\n        break;\n      case 7:\n        day = parseInt(text.substr(0, 2), 10);\n        month = parseInt(text.substr(2, 2), 10);\n        year = parseInt(text.substr(4, 3), 10);\n        break;\n      case 8:\n        day = parseInt(text.substr(0, 2), 10);\n        month = parseInt(text.substr(2, 2), 10);\n        year = parseInt(text.substr(4, 4), 10);\n        break;\n    }\n  } else {\n    var match = /^(\\d{1,2})[\\W]+((\\d{1,2})([\\W]+(\\d{1,4})?)?)?$/.exec(text);\n    if (!match) {\n      return null;\n    }\n    // 'd+', d+m', 'd+m+', 'd+m+YY', 'd+m+y'\n    day = parseInt(match[1], 10);\n    var monthText = any(match[3], '');
1\n    if (monthText != '') {\n      month = parseInt(monthText, 10);\n    }\n    var yearText = any(match[5], '');\n    if (exists(yearText)) {\n      year = parseInt(yearText, 10);\n      if (yearText.length < 3) {\n        year = [year];\n      }\n    }\n  }\n  return [day, month, year];\n}\n\n// tuple is [day, month, year]\n/**\n * Sets the date from the tuple.\n * @param {Date} date\n * @param {Array} tuple\n */\nfunction setDate(date, tuple) {\n  var day = tuple[0];\n  var month = tuple[1];\n  var year = tuple[2];\n  if (exists(year)) {\n    if (isArray(year)) {\n      year = Math.floor(date.getFullYear() / 100) * 100 + year[0];\n    }\n    date.setFullYear(year);\n  }\n  if (exists(month)) {\n    date.setMonth(month - 1);\n  }\n  if (exists(day)) {\n    date.setDate(day);\n  }\n}\n\n// returns [day, month, year]\n/**\n * Returns a tuple from the date.\n * @param {Date} date\n * @return {Array}\n */\nfunction getDate(date) {\n  return [date.getDate(), date.getMonth() + 1, date.getFullYear()];\n}\n"},12673:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\nvar Json = {\n  /**\n   * Encodes the object into a json.\n   * @param {Object} object\n   * @return {String} json\n   */\n  write: function(object) {\n    if (!exists(object)) {\n      return 'null';\n    }\n    switch (typeof object) {\n      case 'boolean':\n      case 'number':\n        return str(object);\n      case 'string':\n        return escapeString(object);\n      case 'object':\n        if (object.Getter) {\n          return Json.write(object.get());\n        }\n        if (object.Dictionary) {\n          object = object.getAll();\n        }\n        if (object.Enumerable) {\n          return '[' + object.each(stacker(Json.write)).join(',') + ']';\n        }\n        if (isArray(object)) {\n          return '[' + iterate(object, stacker(Json.write)).join(',') + ']';\n        }\n        var properties = [];\n        enumerate(object, function(value, key) {\n          if (!isFunction(value)) {\n            properties.push(escapeString(key) + ':' + Json.write(value));\n          }\n        });\n        return '{' + properties.join(',') + '}';\n      default:\n        throw 'Invalid object: ' + str(object);\n    }\n  },\n};\n"},66573:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/**\n * If the template does not exist returns pass,\n * if the template is a function returns the template,\n * otherwise returns a function that\n * returns a property of an input value determined by the template.\n * @param {Function, String} [template]\n * @return {Function}\n */\nfunction obtainTemplate(template) {\n  if (!exists(template)) {\n    return pass;\n  }\n  if (isFunction(template)) {\n    return template;\n  }\n  return function(value) {\n    return exists(value) ? value[template] : null;\n  };\n}\n\n/**\n * If the template does not exist returns the setter,\n * if the template is a function returns a function\n * that applies the template on an input value and invokes the setter,\n * otherwise returns a function that invokes the setter\n * with a propertyof an input value determined by the template.\n * @param {Function, String} template\n * @param {Function} setter\n * @return {Function}\n */\nfunction templatedSetter(template, setter) {\n  if (!exists(template)) {\n    return setter;\n  }\n  if (isFunction(template)) {\n    return function(value) {\n      setter(template(value));\n    };\n  }\n  return function(value) {\n    setter(exists(value) ? value[template] : null);\n  };\n}\n\n/**\n * Returns a template that returns a case according an input value.\n * @param {Dictionary|Object} values\n * @return {Function}\n */\nfunction splitter(values) {\n  if (values.Lookup) {\n    return function(key) {\n      return values.get(key);\n    };\n  } else {\n    return function(key) {\n      return values[key];\n    };\n  }\n}\n\n/**\n * Selects a value from the options.\n */\ntype(\n  'Chooser',\n  ['WatchAccessor'],\n  {\n    /**\n     * Returns a setter that selects a value from the options according the key.\n     * @param {String} key\n     * @return {Function} setter\n     */\n    option: function(key) {\n      var self = this;
1\n      return function() {\n        self.set(key);\n      };\n    },\n  },\n  /**\n   * Initializes chooser with the options.\n   * @param {Object} options\n   */\n  function(options) {\n    var value = $V();\n    mixinInstance(this, value, WatchGetter);\n    this.set = templatedSetter(splitter(options), value.set);\n  }\n);\n"},36436:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/**\n * Creates a watch list and binds it to the source.\n * @param {Object} source\n * @param {Function} template\n * @return {WatchCollection}\n */\nfunction $L(source, template) {\n  return bind.toList(new WatchList(), source, template);\n}\n\n/**\n * Observable list.\n */\ntype(\n  'WatchList',\n  ['WatchListable', 'List'],\n  {\n    /**\n     * Creates a new watch accessor for the key.\n     * @param {String} key\n     * @return {WatchAccessor}\n     */\n    accessor: function(index) {\n      var self = this;\n      return new WatchAccessor(\n        function() {\n          return self.item(index);\n        },\n        function(value) {\n          return self.replaceAt(index, value);\n        },\n        function(observer) {\n          return self.observe({\n            itemAdded: function(it, idx) {\n              if (idx == index) {\n                observer(it, self.item(index + 1));\n              } else if (idx < index) {\n                observer(self.item(index), self.item(index + 1));\n              }\n            },\n            itemRemoved: function(it, idx) {\n              if (idx == index) {\n                observer(self.item(index), it);\n              } else if (idx < index) {\n                observer(self.item(index), self.item(index - 1));\n              }\n            },\n            itemMoved: function(it, idx) {\n              var src = idx[0];\n              var dst = idx[1];\n              if (src == index) {\n                if (dst != index) {\n                  observer(self.item(index), it);\n                }\n              } else if (src < index) {\n                if (dst >= index) {\n                  observer(self.item(index), self.item(index - 1));\n                }\n              } else {\n                if (dst <= index) {\n                  observer(self.item(index), self.item(index + 1));\n                }\n              }\n            },\n          });\n        },\n        function(observer) {\n          var value = self.item(index);\n          return observer(value, value);\n        }\n      );\n    },\n  },\n  /**\n   * Initializes a new watch list with the items.\n   * @param {Object} ... items\n   */\n  function() {\n    var self = this;\n    var items = new List($A(arguments));\n    var length = $V(items.length);\n    var listObservers = commands();\n\n    // Enumerable\n    this.each = items.each;\n    this.isEmpty = items.isEmpty;\n\n    // List\n    this.length = getWatchGetter(length);\n    this.item = items.item;\n    this.allItems = items.allItems;\n    this.append = function(item) {\n      var index = items.append(item);\n      listObservers('itemAdded', item, index, self);\n      length.set(items.length.get());\n      return index;\n    };\n    this.insert = function(item, index) {\n      if (!exists(index) || index < items.length.get()) {\n        index = items.insert(item, index);\n        listObservers('itemAdded', item, index, self);\n        length.set(items.length.get());\n        return index;\n      }\n      while (index-- > items.length) {\n        self.append(null);\n      }\n      return self.append(item);\n    };\n    this.remove = function(item) {\n      var index = items.remove(item);\n      if (index >= 0) {\n        listObservers('itemRemoved', item, index, self);\n        length.set(items.length.get());\n      }\n      return index;\n    };\n    this.removeAt = function(index) {\n      var item = items.removeAt(index);\n      if (exists(item)) {\n        listObservers('itemRemoved', item, index, self);\n      }\n      length.set(items.length.get());\n      return item;\n    };\n    this.move = function(source, destination) {\n      var item = items.move(source, destination);\n      listObservers('itemMoved', item, [source, destination], self);\n      length.set(items.length.get());\n      return item;\n    };
1\n    this.clear = function() {\n      // FIXME?\n      var old = items.clear();\n      iterate(old, function(item) {\n        listObservers('itemRemoved', item, 0, self);\n      });\n      length.set(items.length.get());\n      return old;\n    };\n    this.indexOf = items.indexOf;\n\n    // WatchListable\n    this.observe = function(listObserver) {\n      var observer = isFunction(listObserver)\n        ? listObserver\n        : function(evt) {\n            var eventObserver = listObserver[evt];\n            if (exists(eventObserver)) {\n              eventObserver.apply(listObserver, $A(arguments, 1));\n            }\n          };\n      return listObservers.attach(observer);\n    };\n  }\n);\n\nfunction processListable(source, processor) {\n  each(source, processor.itemAdded);\n  if (source.WatchListable) {\n    return source.observe(processor);\n  }\n}\n"},63931:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/**\n * Creates a watch object and binds it to the source.\n * @param {Object} source\n * @param {Function} template\n * @return {WatchObject}\n */\nfunction $O(source, template) {\n  return bind.toDictionary(new WatchObject(), source, template);\n}\n\n/**\n * Observable object.\n */\ntype(\n  'WatchObject',\n  ['Dictionary', 'Observable'],\n  {\n    /**\n     * Adds a new property, initializes it with the value, returns a key of the new property.\n     * @param {Object} value\n     * @return {String} key\n     */\n    add: function(value) {},\n\n    /**\n     * Creates a new watch getter for the key.\n     * @param {String} key\n     * @return {WatchGetter}\n     */\n    getter: function(key) {\n      var self = this;\n      return new WatchGetter(\n        function() {\n          return self.get(key);\n        },\n        function(observer) {\n          return self.observe(function(value, key, self, old) {\n            return observer(value, old);\n          }, key);\n        },\n        function(observer) {\n          var value = self.get(key);\n          return observer(value, value);\n        }\n      );\n    },\n\n    /**\n     * Creates a new watch accessor for the key.\n     * @param {String} key\n     * @return {WatchAccessor}\n     */\n    accessor: function(key) {\n      var self = this;\n      return new WatchAccessor(\n        function() {\n          return self.get(key);\n        },\n        function(value) {\n          return self.set(key, value);\n        },\n        function(observer) {\n          return self.observe(function(value, key, self, old) {\n            return observer(value, old);\n          }, key);\n        },\n        function(observer) {\n          var value = self.get(key);\n          return observer(value, value);\n        }\n      );\n    },\n  },\n  /**\n   * Initializes a new watch object and attaches watch accessors for the keys.\n   * @param {String} ... keys\n   */\n  function() {\n    var self = this;\n    var properties = {};\n    var propertyObservers = {};\n    var objectObservers = commands();\n    var notify = function(value, key, old) {\n      var propertyObserver = propertyObservers[key];\n      if (exists(propertyObserver)) {\n        propertyObserver(value, key, self, old);\n      }\n      objectObservers(value, key, self, old);\n    };\n\n    // Enumerable\n    this.each = function(iterator) {\n      return enumerate(properties, iterator);\n    };\n    this.isEmpty = function() {\n      return !hasProperties(properties);\n    };\n\n    // Dictionary\n    this.get = function(key) {\n      return properties[key];\n    };\n    this.set = function(key, value) {\n      return changeProperty(properties, key, value, notify);\n    };\n    this.getAll = function() {\n      return clone(properties);\n    };\n    this.update = function(values) {\n      return changeProperties(properties, values, notify);\n    };\n    this.replace = function(values) {\n      return replaceProperties(properties, values, notify);\n    };
1\n    this.clear = function() {\n      var old = properties;\n      properties = {};\n      enumerate(old, function(value, key) {\n        notify(null, key, value);\n      });\n      return old;\n    };\n    // Observable\n    this.observe = function(observer, key) {\n      if (exists(key)) {\n        var propertyObserver = propertyObservers[key];\n        if (!exists(propertyObserver)) {\n          propertyObserver = commands();\n          propertyObservers[key] = propertyObserver;\n        }\n        return propertyObserver.attach(observer);\n      } else {\n        return objectObservers.attach(observer);\n      }\n    };\n    // WatchObject\n    this.add = function(value) {\n      return addProperty(properties, value, notify);\n    };\n    return mixWatchAccessors(this, arguments, this);\n  }\n);\n\n/**\n * Defines a named watch type with the properties based on the mixins.\n * @param {Object} name\n * @param {Array} properties\n * @param {Array} [mixins]\n * @return {Function} constructor({Object} ... values)\n */\nfunction watchType(name, properties, mixins) {\n  var create = type(name, any(mixins, []), {}, function(source) {\n    if (this instanceof arguments.callee) {\n      var object = new WatchObject();\n      var self = this;\n      iterate(create.mixins, function(mixin) {\n        mixWatchAccessors(self, self[mixin].watchProperties, object);\n      });\n      watchType.init(this, arguments);\n    } else {\n      return watchType.load(new arguments.callee(), source);\n    }\n  });\n  create.watchProperties = properties;\n  return create;\n}\n\n/**\n *\n * @param {WatchType} target\n * @param {Array} values\n */\nwatchType.init = function(target, values) {\n  var counter = 0;\n  iterate(target.constructor.mixins, function(mixin) {\n    iterate(target[mixin].watchProperties, function(property) {\n      target[property].set(values[counter++]);\n    });\n  });\n  return target;\n};\n\n/**\n *\n * @param {WatchType} target\n * @param {Object} object\n */\nwatchType.load = function(target, object) {\n  if (object instanceof target.constructor) {\n    iterate(target.constructor.mixins, function(mixin) {\n      iterate(target[mixin].watchProperties, function(property) {\n        var value = object[property];\n        if (exists(value) && value.Getter) {\n          target[property].set(value.get());\n        }\n      });\n    });\n  } else {\n    iterate(target.constructor.mixins, function(mixin) {\n      iterate(target[mixin].watchProperties, function(property) {\n        target[property].set(object[property]);\n      });\n    });\n  }\n  return target;\n};\n\n/**\n * Attaches watch accessors created from the properties from the object to the target and returns the object.\n * @param {Object} target\n * @param {Array} properties\n * @param {WatchObject} [object]\n * @return {WatchObject}\n */\nfunction mixWatchAccessors(target, properties, object) {\n  if (!exists(object)) {\n    object = new WatchObject();\n  }\n  iterate(properties, function(property) {\n    target[property] = object.accessor(property);\n  });\n  return object;\n}\n"},46403:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/**\n * Creates a watch value and binds it to the source.\n * @param {Object} source\n * @param {Function} template\n * @return {WatchValue}\n */\nfunction $V(source, template) {\n  return bind.toAccessor(new WatchValue(), source, template);\n}\n\n/**\n * Observable value.\n */\ntype(\n  'WatchValue',\n  ['WatchAccessor'],\n  {},\n  /**\n   * Initializes a new watch value with the given value.\n   * @param {Object} value\n   */\n  function(value) {\n    var valueObservers = commands();\n    this.WatchAccessor(\n      function() {\n        return value;\n      },\n      function(newValue) {\n        if (!equals(value, newValue)) {\n          var oldValue = value;\n          value = newValue;\n          valueObservers(newValue, oldValue);\n        }\n      },\n      valueObservers.attach,\n      function(observer) {\n        return observer(value, value);\n      }\n    );\n  }\n);\n\ntype(\n  'WatchPair',\n  ['WatchValue'],\n  {\n    key: null,\n  },\n  function(key, value) {\n    this.key = key;\n    this.WatchValue(value);\n  }\n);\n"},81761:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/**\n * Returns dom node\n * @param {Object} object\n * @return {Dom} dom\n */\nfunction getDom(object) {\n  if (!exists(object)) {\n    return Html.hidden().dom;\n  }\n  if (isDom(object)) {\n    return object;\n  }\n  if (object.XElement) {\n    return object.dom;\n  }\n  return document.createTextNode(str(object));\n}\n\nvar Dom = {\n  Event: {\n    /**\n     * Fires event\n     * @param {Dom} dom\n     * @param {String} eventName\n     * @return {Event} event\n     */\n    dispatch: function(dom, eventName) {\n      var event;\n      if (document.createEvent) {\n        event = document.createEvent('HTMLEvents');\n        event.initEvent(eventName, true, true);\n        dom.dispatchEvent(event);\n      } else {\n        event = document.createEventObject();\n        dom.fireEvent('on' + eventName, event);\n      }\n      return event;\n    },\n  },\n\n  List: {\n    // Enumerable\n    each: function(dom, iterator) {\n      return iterate(dom.childNodes, iterator);\n    },\n\n    // List\n    length: function(dom) {\n      return dom.childNodes.length;\n    },\n    item: function(dom, index) {\n      return dom.childNodes[index];\n    },\n    append: function(dom, item) {\n      dom.appendChild(getDom(item));\n      return dom.childNodes.length - 1;\n    },\n    insert: function(dom, item, index) {\n      if (!exists(index)) {\n        index = 0;\n      }\n      var items = dom.childNodes;\n      if (index < items.length) {\n        dom.insertBefore(getDom(item), items[index]);\n        return index;\n      }\n      while (index-- > items.length) {\n        Dom.List.append(dom, null);\n      }\n      return Dom.List.append(dom, item);\n    }
1,\n    remove: function(dom, item) {\n      try {\n        dom.removeChild(getDom(item));\n        return Number.MAX_VALUE; // FIXME should return index\n      } catch (e) {\n        return -1;\n      }\n    },\n    removeAt: function(dom, index) {\n      var items = dom.childNodes;\n      if (index >= items.length) {\n        return null;\n      }\n      return dom.removeChild(items[index]);\n    },\n    clear: function(dom) {\n      var items = [];\n      var item;\n      while ((item = dom.firstChild)) {\n        items.push(dom.removeChild(item));\n      }\n      return items;\n    },\n  },\n\n  Content: {\n    get: function(dom) {\n      return any(dom.textContent, dom.innerText);\n    },\n    add: function(dom, value) {\n      if (!exists(value)) {\n        return 0;\n      }\n      if (!isDom(value)) {\n        if (value.XElement) {\n          value = value.dom;\n        } else {\n          if (isArray(value) || value.Enumerable) {\n            var number = 0;\n            each(value, function(item) {\n              number += Dom.Content.add(dom, item);\n            });\n            return number;\n          } else {\n            value = document.createTextNode(str(value));\n          }\n        }\n      }\n      dom.appendChild(value);\n      return 1;\n    },\n    /**\n     * Sets content\n     * @param {Dom} dom\n     * @param {Object} ... content\n     */\n    set: function(dom) {\n      var old = Dom.List.clear(dom);\n      iterate(\n        arguments,\n        function(value) {\n          if (exists(value)) {\n            Dom.Content.add(dom, value);\n          }\n        },\n        1\n      );\n      return old;\n    },\n  },\n\n  Style: {},\n};\n\ndelayedBind(Dom, 'createElementNS', function() {\n  return document.createElementNS\n    ? function(document, uri, name) {\n        return document.createElementNS(uri, name);\n      }\n    : function(document, uri, name) {\n        var namespace = name.split(':', 2)[0];\n        if (\n          !includes(document.namespaces, function(item) {\n            return item.name == namespace && item.urn == uri;\n          })\n        ) {\n          document.namespaces.add(namespace, uri);\n        }\n        return document.createElement(name);\n      };\n});\n\nDom.get = function(dom, name) {\n  return dom[name];\n};\ndelayedBind(Dom, 'set', function(dom) {\n  return dom.setAttributeNS\n    ? function(dom, name, value) {\n        if (exists(value)) {\n          dom.setAttributeNS(null, name, value);\n        } else {\n          dom.removeAttributeNS(null, name);\n        }\n        return dom;\n      }\n    : function(dom, name, value) {\n        dom[name] = value;\n        return dom;\n      };\n});\n\ndelayedBind(Dom.Style, 'get', function(document) {\n  return document.defaultView\n    ? function(dom, key) {\n        var value = dom.style[key];\n        if (!exists(value)) {\n          var css = document.defaultView.getComputedStyle(dom, null);\n          if (exists(css)) {\n            value = css[key];\n          }\n        }\n        return value;\n      }\n    : function(dom, key) {\n        return dom.style[key];\n      };\n});\nDom.Style.set = function(dom, key, value) {\n  if (exists(value)) {\n    dom.style[key] = value;\n  } else {\n    // CHECK THAT\n    dom.style[key] = '';\n  }\n  return dom;\n};\n"},60732:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\nextend(Html.prototype, {\n  isBody: function() {\n    return document.body === this.dom;\n  },\n\n  getOffsetParent: function() {\n    return $E(this.getAttribute('offsetParent'));\n  },\n});\n\n// based on mootools\ndelayedBind(Html.prototype, 'getAbsolutePosition', function(dom) {\n  return this.dom.getBoundingClientRect && !Browser.WebKit\n    ? function() {\n        var bound = this.dom.getBoundingClientRect();\n        var html = document.documentElement;\n        return new Point(\n          Math.round(bound.left + html.scrollLeft - html.clientLeft),\n          Math.round(bound.top + html.scrollTop - html.clientTop)\n        );\n      }
1\n    : function() {\n        var position = new Point();\n        var element = this;\n        while (exists(element) && !element.isBody()) {\n          position.move(\n            new Point(element.getAttribute('offsetLeft'), element.getAttribute('offsetTop'))\n          );\n          element = element.getOffsetParent();\n        }\n        return position;\n      };\n});\n"},61532:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\ntype(\n  'Html',\n  ['XElement', 'WatchAccessor', 'CanGet'],\n  {\n    isField: function() {\n      return exists(Html.isCheckField(this.dom));\n    },\n\n    isCheckField: function() {\n      return Html.isCheckField(this.dom);\n    },\n\n    // XElement\n    getters: merge(XElement.prototype.getters),\n    setters: merge(XElement.prototype.setters),\n\n    styleGetters: merge(XElement.prototype.styleGetters),\n    styleSetters: merge(XElement.prototype.styleSetters),\n\n    /**\n     * @see XElement.getStyle\n     */\n    getStyleBase: XElement.prototype.getStyle,\n    getStyle: function(key) {\n      var value = this.getStyleBase(key);\n      return value == 'auto' ? null : value;\n    },\n\n    // WatchAccessor\n    get: function() {\n      var checkField = Html.isCheckField(this.dom);\n      if (!exists(checkField)) {\n        return Dom.Content.get(this.dom);\n      } else if (checkField) {\n        return this.dom.checked;\n      } else {\n        return this.dom.value;\n      }\n    },\n    /**\n     * Sets value\n     * @param {Object} value\n     * @param {Object} ... content\n     */\n    set: function(value) {\n      var checkField = Html.isCheckField(this.dom);\n      if (!exists(checkField)) {\n        return Dom.Content.set(this.dom, $A(arguments));\n      } else if (checkField) {\n        var checked = this.dom.checked;\n        if (checked == !value) {\n          this.dom.checked = !checked;\n          if (exists(this.observers)) {\n            this.observers(!checked);\n          }\n        }\n      } else {\n        this.dom.value = str(value);\n      }\n    },\n    observe: function(observer) {\n      var checkField = Html.isCheckField(this.dom);\n      if (!exists(checkField)) {\n        return;\n      } else {\n        var self = this;\n        var notify = function(evt) {\n          observer(self.get());\n        };\n        if (!exists(this.observers)) {\n          this.observers = commands();\n          var notify = function(evt) {\n            self.observers(self.get());\n          };\n          if (checkField) {\n            this.observeClick(notify);\n          } else {\n            this.observeChange(notify);\n          }\n        }\n        return this.observers.attach(observer);\n      }\n    },\n    invokeObserver: function(observer) {\n      return observer(this.get());\n    },\n\n    // CanGet\n    canGet: function() {\n      return this.isField();\n    },\n  },\n  function(source, attributes) {\n    this.XElement(source, attributes, $A(arguments, 2));\n  }\n);\n\nextend(Html, {\n  toDimensions: function(value) {\n    return isNumber(value) ? Math.round(value) + 'px' : value;\n  },\n\n  isCheckField: function(dom) {\n    switch (dom.tagName.toLowerCase()) {\n      case 'input':\n        switch (dom.type.toLowerCase()) {\n          case 'checkbox':\n          case 'radio':\n            return true;\n          default:\n            return false;\n        }\n      case 'select':\n      case 'textarea':\n        return false;\n      default:\n        return null;\n    }\n  },\n\n  /**\n   * Makes attributes from class name or object\n   * @param {String, Object} source\n   * @param {Object} [other]\n   * @return {Object} attributes\n   */\n  makeAttributes: function(source, other) {\n    var attribs;\n    if (exists(source)) {\n      attribs = isString(source)\n        ? {\n            className: source,\n          }\n        : source;\n      if (exists(other)) {\n        extend(attribs, other);\n      }\n    } else {\n      attribs = exists(other) ? other : {};\n    }\n    for (var key in attribs) {\n      if (attribs[key] === undefined) {\n        delete attribs[key];\n      }\n    }\n    return attribs;\n  },\n\n  /**\n   * Builds an H element with the level, the content, and the attributes.\n   * @param {Number} level\n   * @param {Object} content\n   * @param {String, Object} [attributes]\n   * @return {XElement} element\n   */\n  h: function(level, content, attributes) {\n    return new Html('h' + level, Html.makeAttributes(attributes), content);\n  },\n\n  /**\n   * Builds an INPUT element with the type, the attributes, and the value.\n   * @param {String} type\n   * @param {Object} [attributes]\n   * @param {String} [value]\n   * @return {XElement} element\n   */\n  input: function(type, attributes, value) {\n    return new Html(\n      'input',\n      Html.makeAttributes(attributes, {\n        type: type,\n        value: value,\n      })\n    );\n  },\n\n  /**\n   * Builds an INPUT type=text element with the attributes and the value.\n   * @param {Object} [attributes]\n   * @param {Object} [value]\n   * @return {XElement} element\n   */\n  edit: function(attributes, value) {\n    return new Html(\n      'input',\n      Html.makeAttributes(attributes, {\n        type: 'text',\n        value: value,\n      })\n    );\n  },\n\n  /**\n   * Builds an INPUT type=checkbox element with the attributes and the value.\n   * @param {Object} [attributes]\n   * @param {Boolean} [checked]\n   * @return {XElement} element\n   */\n  checkbox: function(attributes, checked) {\n    var elem = new Html(\n      'input',\n      Html.makeAttributes(attributes, {\n        type: 'checkbox',\n      })\n    );\n\n    elem.set(checked);\n    return elem;\n  },\n\n  /**\n   * Builds an INPUT type=radio element with the attributes and the value.\n   * @param {Object} [attributes]\n   * @param {Boolean} [checked]\n   * @return {XElement} element\n   */\n  radio: function(attributes, checked) {\n    return new Html(\n      'input',\n      Html.makeAttributes(attributes, {\n        type: 'radio',\n        checked: checked,\n      })\n    );\n  },\n\n  email: function(attributes) {\n    return new Html(\n      'input',\n      Html.makeAttributes(attributes, {\n        type: 'text',\n        vcard_name: 'vCard.Email',\n      })\n    );\n  },\n\n  password: function(attributes) {\n    return new Html(\n      'input',\n      Html.makeAttributes(attributes, {\n        type: 'password',\n        autocomplete: 'on',\n      })\n    );\n  },\n\n  hidden: function(attributes) {\n    return new Html('div', Html.makeAttributes(attributes)).style({\n      display: 'none',\n    });\n  },\n});\n\niterate(\n  [\n    /* links */ 'a',\n    /* boxes */ 'div',\n    'span',\n    'p',\n    /* lists */ 'ul',\n    'ol',\n    'li',\n    /* tables */ 'table',\n    'caption',\n    'thead',\n    'tfoot',\n    'tbody',\n    'tr',\n    'th',\n    'td',\n    /* styles */ 'strong',\n    'em',\n    'emp',\n    'pre',\n    'small',\n    /* headers */ 'h1',\n    'h2',\n    'h3',\n    'h4',\n    'h5',\n    'h6',\n    /* controls */ 'form',\n    'button',\n    'label',\n    'textarea',\n    'img',\n    'select',\n    'option',\n    'optgroup',\n    /* specials */ 'object',\n    'param',\n    'embed',\n    'iframe',\n    'br',\n  ],\n  function(tag) {\n    Html[tag] = function(attributes) {\n      return new Html(tag, Html.makeAttributes(attributes), $A(arguments, 1));\n    };\n  }\n);
1\n\nHtml.prototype.getters.enctype = function() {\n  return this.dom.enctype;\n};\nHtml.prototype.setters.enctype = function(value) {\n  this.dom.enctype = value;\n  if ('encoding' in this.dom) {\n    // FIX IE\n    this.dom.encoding = value;\n  }\n  return this.dom;\n};\n\n// due to setAttributeNS for Dom.set()\niterate(['className', 'htmlFor'], function(name) {\n  Html.prototype.getters[name] = function() {\n    return this.dom[name];\n  };\n  Html.prototype.setters[name] = function(value) {\n    this.dom[name] = value;\n  };\n});\n\ndelayedBind(Html.prototype.styleGetters, 'opacity', function(dom) {\n  return Dom.Style.get(dom, 'opacity') === undefined\n    ? function(dom) {\n        var filter = dom.filters['opacity'];\n        return exists(filter) ? filter.opacity / 100 : 1;\n      }\n    : function(dom) {\n        var value = Dom.Style.get(dom, 'opacity');\n        return exists(value) ? parseFloat(value) : 1;\n      };\n});\ndelayedBind(Html.prototype.styleSetters, 'opacity', function() {\n  return function(value) {\n    return objectize('opacity', value == 1 || value === '' ? '' : value < 0.00001 ? 0 : value);\n  };\n});\n\ndelayedBind(Html.prototype.styleGetters, 'cssFloat', function(dom) {\n  return Dom.Style.get(dom, 'cssFloat') === undefined\n    ? function(dom) {\n        return Dom.Style.get(dom, 'styleFloat');\n      }\n    : function(dom) {\n        return Dom.Style.get(dom, 'cssFloat');\n      };\n});\ndelayedBind(Html.prototype.styleSetters, 'cssFloat', function() {\n  return function(value) {\n    return objectize('cssFloat', Html.toDimensions(value));\n  };\n});\n\niterate(\n  [\n    'left',\n    'right',\n    'top',\n    'bottom',\n    'width',\n    'height',\n    'borderWidth',\n    'padding',\n    'paddingTop',\n    'paddingRight',\n    'paddingBottom',\n    'paddingLeft',\n    'margin',\n    'marginTop',\n    'marginRight',\n    'marginBottom',\n    'marginLeft',\n    'border',\n    'borderTop',\n    'borderRight',\n    'borderBottom',\n    'borderLeft',\n    'lineHeight',\n  ],\n  function(key) {\n    Html.prototype.styleSetters[key] = function(value) {\n      return objectize(key, Html.toDimensions(value));\n    };\n  }\n);\n\niterate(['margin', 'padding', 'border'], function(name) {\n  iterate(['Top', 'Right', 'Bottom', 'Left'], function(dir) {\n    var key = name + dir;\n    Html.prototype.styleSetters[key] = function(value) {\n      return objectize(key, Html.toDimensions(value));\n    };\n  });\n\n  Html.prototype.styleSetters[name] = function(value) {\n    if (isObject(value)) {\n      var obj = {};\n      iterate(['Top', 'Right', 'Bottom', 'Left'], function(dir) {\n        var key = dir.toLowerCase();\n        if (key in value) {\n          obj[name + dir] = value[key];\n        }\n      });\n      return obj;\n    } else {\n      return objectize(name, Html.toDimensions(value));\n    }\n  };\n});\n\niterate(['selected', 'disabled'], function(name) {\n  Html.prototype.getters[name] = function() {\n    return exists(Dom.get(this.dom, name));\n  };\n  Html.prototype.setters[name] = function(value) {\n    return Dom.set(this.dom, name, value ? name : null);\n  };\n});\n\n/**\n * Generates a new id for HTML element.\n * @return {String}\n */\nHtml.generateId = function() {\n  return '_GID' + Html.generateId.current++;\n};\nHtml.generateId.current = 1;\n\nHtml.addScript = function(path) {\n  var script = document.createElement('script');\n  script.src = path;\n  script.type = 'text/javascript';\n  document.getElementsByTagName('head')[0].appendChild(script);\n};\n\nHtml.$ = function() {\n  return new XElement($.apply(this, arguments).get(0));\n};\n"},78562:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\nString.prototype.format = function() {\n  var args = arguments;\n  return this.replace(/{(\\d+)}/g, function(match, number) {\n    return typeof args[number] != 'undefined' ? args[number] : '';\n  });\n};\n"},25058:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/**\n * Namespace for widgets;\n */\nvar Widget = {};\n\nfunction WidgetContext(value) {\n  return value === window ? new WidgetContext() : value;\n}\n\nWidgetContext.prototype = Widget;\n\nfunction WidgetBuilder(method, attributes) {\n  var builder = function(source, attribs) {\n    var args = $A(arguments);\n    if (args.length >= 2) {\n      args[1] = merge(args[1], attributes);\n    }\n    return method.apply(this, args);\n  };\n  builder.customize = function(attribs) {\n    return WidgetBuilder(method, merge(attributes, attribs));\n  };\n  extend(builder, WidgetBuilder);\n  return builder;\n}\n"},43831:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\nWidget.t = function(id) {\n  return id;\n};\n\n/**\n * Option template\n * @param {WatchPair, Object} item\n * @return {XElement} option\n */\nWidget.option = function(item) {\n  var option = Html.option();\n  if (item.WatchPair) {\n    $B(option, item);\n    $B(option.accessor('value'), item.key);\n    $B(option.accessor('selected'), item.selected);\n    $B(option.accessor('disabled'), item.disabled);\n  } else {\n    option.setAttribute('value', item);\n    option.set(item);\n  }\n  return option;\n};\n\n/**\n * Command template\n * @param {Function} command\n * @param {XElement} target\n * @return {String} caption\n */\nWidget.clickable = function(command, target) {\n  invoke(target.stopClickable);\n  target.stopClickable = target.observeClick(function(event) {\n    command(event);\n    return false;\n  });\n  if (target.getTag() == 'a') {\n    target.setAttribute('href', '#');\n  }\n  return command.caption;\n};\n"},72866:n=>
1{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/**\n * Widget control\n * @param {Function} panelBuilder\n * @param {Function} [itemTemplate]\n * @param {Object} [attributes]\n * @return {Function} control builder\n */\nfunction WidgetControl(panelBuilder, itemTemplate, attributes) {\n  /**\n   * Builds a control\n   * @param {Object} source\n   * @return {Element}\n   */\n  var controlBuilder = function(source) {\n    // bind.element used because it should also bind the items to select field etc.\n    return bind.element(panelBuilder(attributes), source, itemTemplate);\n  };\n\n  /**\n   * @param {Object} attribs\n   * @return {Function} control builder\n   */\n  controlBuilder.customize = function(attribs) {\n    return WidgetControl(panelBuilder, itemTemplate, merge(attributes, attribs));\n  };\n\n  extend(controlBuilder, WidgetBuilder);\n\n  return controlBuilder;\n}\n\nWidget.link = WidgetControl(Html.a, Widget.clickable);\nWidget.text = WidgetControl(Html.span);\nWidget.inline = WidgetControl(Html.span, Widget.text);\nWidget.line = WidgetControl(Html.p);\nWidget.block = WidgetControl(Html.div);\nWidget.lines = WidgetControl(Html.div, Widget.line);\n"},64430:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n// \"Element\" is used by Firefox for native DOM elements!\n\n/**\n * Wrapper for a native DOM element. It handles cross-browser compatibility.\n */\ntype(\n  'XElement',\n  ['List'],\n  {\n    /**\n     * Attaches the observer to a click event. Returns a function to detach the observer.\n     * @param {Function} observer\n     * @return {Function} function to remove observer\n     */\n    observeClick: function(observer) {\n      return this.observeEvent('click', observer);\n    },\n\n    /**\n     * Attaches the observer to a change event. Returns a function to detach the observer.\n     * @param {Function} observer\n     * @return {Function} function to remove observer\n     */\n    observeChange: function(observer) {\n      return this.observeEvent('change', observer);\n    },\n\n    observeKeyPress: function(observer) {\n      return this.observeEvent('keypress', function(e) {\n        var key = e.keyCode || e.which;\n        return observer(key);\n      });\n    },\n\n    /**\n     * Attaches the observer to an event with the given name. Returns a function to detach the observer.\n     * @param {String} eventName\n     * @param {Function} observer\n     * @return {Function} function to remove observer\n     */\n    observeEvent: function(eventName, observer) {\n      var self = this;\n      return obtain(obtain(this, 'eventObservers', construct(Object)), eventName, function() {\n        var observers = commands();\n        self.dom['on' + eventName] = function(e) {\n          if (window.event) {\n            e = window.event;\n          }\n          return observers(e);\n        };\n        return observers;\n      }).attach(observer);\n    },\n\n    /**\n     * Triggers an event with the given name. Returns an event object of the event.\n     * @param {String} eventName\n     * @return {Event} event\n     */\n    dispatchEvent: function(eventName) {\n      return Dom.Event.dispatch(this.dom, eventName);\n    },\n\n    getters: {},\n    setters: {},\n    /**\n     * Returns a value of an attribute with the given name.\n     * @param {String} name\n     * @return {Object} value\n     */\n    getAttribute: function(name) {\n      var getter = this.getters[name];\n      return exists(getter) ? getter.call(this) : Dom.get(this.dom, name);\n    },\n\n    /**\n     * Sets the value to an attribute with the given name. Returns the element.\n     * @param {String} name\n     * @param {Object} value\n     * @return {XElement}\n     */\n    setAttribute: function(name, value) {\n      var setter = this.setters[name];\n      if (exists(setter)) {\n        return setter.call(this, value);\n      }\n      Dom.set(this.dom, name, value);\n      return this;\n    },\n\n    /**\n     * Set multiple attributes\n     * @param {Object} values\n     */\n    attribute: function(values) {\n      var self = this;\n      enumerate(values, function(value, name) {\n        if (name.charAt(0) == '$') {\n          var key = name.substr(1);\n          if (isFunction(self[key])) {\n            self[key](value);\n          } else {\n            self[key] = value;\n          }\n        } else {\n          self.setAttribute(name, value);\n        }\n      });\n      return this;\n    },\n\n    styleGetters: {},\n    styleSetters: {},\n    /**\n     * Gets style for the key.\n     * @param {String} key\n     * @return {Object}
1 value\n     */\n    getStyle: function(key) {\n      var getter = this.styleGetters[key];\n      return exists(getter) ? getter(this.dom) : Dom.Style.get(this.dom, name);\n    },\n\n    /**\n     * Sets a style with the key and the value or styles with the values. Returns the element.\n     * @param {String} key\n     * @param {Object} value\n     */\n    setStyle: function(key, value) {\n      var setter = this.styleSetters[key];\n      if (exists(setter)) {\n        var self = this;\n        enumerate(setter(value), function(v, k) {\n          Dom.Style.set(self.dom, k, v);\n        });\n      } else {\n        Dom.Style.set(this.dom, key, value);\n      }\n      return this;\n    },\n\n    /**\n     * Sets style values\n     * @param {Dom} dom\n     * @param {Object} values\n     */\n    style: function(values) {\n      var self = this;\n      enumerate(values, function(value, key) {\n        self.setStyle(key, value);\n      });\n      return this;\n    },\n\n    /**\n     * Returns an accessor for a attribute with the name.\n     * @param {String} name\n     * @return {Accessor} accessor\n     */\n    accessor: function(name) {\n      var self = this;\n      return new Accessor(\n        function() {\n          return self.getAttribute(name);\n        },\n        function(value) {\n          return self.setAttribute(name, value);\n        }\n      );\n    },\n\n    /**\n     * Returns content.\n     * @return {Array}\n     */\n    getContent: function() {\n      return Dom.Content.get(this.dom);\n    },\n\n    /**\n     * Adds the value to content. Returns a number of added nodes.\n     * @param {Object} value\n     * @return {Number} number of nodes\n     */\n    addContent: function(value) {\n      schedule(this.itemsUpdated);\n      return Dom.Content.add(this.dom, value);\n    },\n\n    /**\n     * Returns a name of a tag of the element.\n     * @return {String} tag\n     */\n    getTag: function() {\n      return this.dom.tagName.toLowerCase();\n    },\n\n    getParent: function() {\n      return $E(this.dom.parentNode);\n    },\n\n    detach: function() {\n      var parent = this.getParent();\n      if (exists(parent)) {\n        parent.remove(this);\n      }\n    },\n\n    destroy: function() {\n      delete this.dom.$element;\n      delete this.dom;\n    },\n\n    // Enumerable\n    each: function(iterator) {\n      return Dom.List.each(this.dom, function(item, index) {\n        return iterator($E(item), index);\n      });\n    },\n\n    // List\n    length: function() {\n      return Dom.List.length(this.dom);\n    },\n    item: function(index) {\n      return $E(Dom.List.item(this.dom, index));\n    },\n    append: function(item) {\n      schedule(this.itemsUpdated);\n      return Dom.List.append(this.dom, item);\n    },\n    insert: function(item, index) {\n      schedule(this.itemsUpdated);\n      return Dom.List.insert(this.dom, item, index);\n    },\n    remove: function(item) {\n      schedule(this.itemsUpdated);\n      return Dom.List.remove(this.dom, item);\n    },\n    removeAt: function(index) {\n      schedule(this.itemsUpdated);\n      return Dom.List.removeAt(this.dom, index);\n    },\n    move: function(source, destination) {\n      schedule(this.itemsUpdated);\n      return Dom.List.move(this.dom, source, destination);\n    },\n    clear: function() {\n      schedule(this.itemsUpdated);\n      return Dom.List.clear(this.dom);\n    },\n  },\n  /**\n   * Creates a new element from the tag name or a native DOM element, sets the attributes, and adds the content.\n   * @param {String, Dom} tag name or source\n   * @param {Object} [attributes]\n   * @param {Object} ... content\n   */\n  function(source, attributes) {\n    var self = this;\n\n    self.itemsUpdated = commands();\n    if (isString(source)) {\n      source = document.createElement(source);\n    } else if (isArray(source)) {\n      source = Dom.createElementNS(document, source[0], source[1]);\n    }\n    // <LEAK>\n    self.dom = source;\n    try {\n      source.$element = self;\n    } catch (e) {\n      // ignore (IE expando bug, e.g. DispHTMLDOMTextNode)\n    }\n    XElement.elements.push(self);\n    // </LEAK>\n    self.length = new Getter(curry(XElement.getLength, self)); // closure leak avoidance\n    if (exists(attributes)) {\n      self.attribute(attributes);\n    }\n    iterate(\n      arguments,\n      function(item) {\n        self.addContent(item);\n      },\n      2\n    );\n  }\n);\n\nXElement.prototype.setters.style = XElement.prototype.style;\n\nXElement.getLength = function(element) {\n  return Dom.List.length(element.dom);\n};\n\nXElement.elements = [];
1\nXElement.cleanNow = function() {\n  var elements = [];\n  var body = document.body;\n  var html = body.parentNode;\n  iterate(XElement.elements, function(item) {\n    if ((item.dom && item.dom.offsetParent) || item === body || item === html) {\n      elements.push(item);\n    } else {\n      item.destroy();\n    }\n  });\n  XElement.elements = elements;\n};\n\nXElement.clean = function() {\n  schedule(XElement.cleanNow);\n};\n\n/**\n * Returns an element by the id or from the native DOM element.\n * @param {String, Dom} source\n * @return {XElement} element\n */\nfunction $E(source) {\n  if (isString(source)) {\n    source = document.getElementById(source);\n  }\n  if (!exists(source)) {\n    return source;\n  }\n  var element = source.$element;\n  if (exists(element)) {\n    return element;\n  }\n  return new Html(source);\n}\n"},15046:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\ntype(\n  'TimetableManagementActions',\n  [],\n  {\n    deleteEntry: function(eventData) {\n      var self = this;\n      var info = new WatchObject();\n\n      info.set('scheduleEntry', eventData.scheduleEntryId);\n      info.set('conference', eventData.conferenceId);\n      info.set('sessionTimetable', self.isSessionTimetable);\n\n      var urlArgs = {\n        event_id: eventData.conferenceId,\n        entry_id: eventData.scheduleEntryId,\n      };\n      if (self.timetable.isSessionTimetable) {\n        urlArgs.session_id =\n          self.timetable.contextInfo.sessionId || self.timetable.contextInfo.timetableSession.id;\n      }\n      $.ajax({\n        url: build_url(Indico.Urls.Timetable.entries.delete, urlArgs),\n        method: 'POST',\n        complete: IndicoUI.Dialogs.Util.progress(),\n        error: function(xhr) {\n          if (xhr.status == 404) {\n            handleErrorResponse(xhr);\n          } else {\n            handleAjaxError(xhr);\n          }\n        },\n        success: function(data) {\n          if (data) {\n            self.timetable._updateDay(data.update);\n            handleFlashes(data);\n          }\n        },\n      });\n    },\n\n    /*\n     * Edit start and end date. date format has to be dd/mm/yy mm:hh\n     */\n    editEntryStartEndDate: function(startDate, endDate, eventData, shift, undo) {\n      var self = this;\n      var urlArgs = {\n        event_id: eventData.conferenceId,\n        entry_id: eventData.scheduleEntryId,\n      };\n\n      if (self.isSessionTimetable) {\n        urlArgs.session_id =\n          self.timetable.contextInfo.sessionId || self.timetable.contextInfo.timetableSession.id;\n      }\n\n      return $.ajax({\n        url: build_url(Indico.Urls.Timetable.entries[shift ? 'shift' : 'editDatetime'], urlArgs),\n        method: 'POST',\n        data: {startDate: startDate, endDate: endDate},\n        error: function(xhr) {\n          handleAjaxError(xhr);\n          self.timetable.timetableDrawer.redraw();\n        },\n        complete: IndicoUI.Dialogs.Util.progress(),\n        success: function(data) {\n          if (undo) {\n            self.timetable.enableUndo(undo, {eventData: eventData, shifted: shift});\n          }\n          if (data) {\n            handleNotifications(data);\n            self.timetable._updateDay(data.update);\n          }\n        },\n      });\n    },\n\n    /* Takes care of moving a contribution/timeblock to another session/timetable.\n     * This goes for both \"drag and drop\" as well as the regular \"MoveEntry Dialog clicking\"*/\n    moveToSession: function(eventData, data, undo, newTime) {\n      var self = this;\n\n      // if nothing has been selected yet\n      if (!data) {\n        return false;\n      }\n\n      var urlArgs = {\n        event_id: eventData.conferenceId,\n        entry_id: eventData.scheduleEntryId,\n      };\n      if (self.isSessionTimetable) {\n        urlArgs.session_id = self.timetable.contextInfo.sessionId;\n      }\n\n      return $.ajax({\n        url: build_url(Indico.Urls.Timetable.entries.move, urlArgs),\n        method: 'POST',\n        data: JSON.stringify(data),\n        dataType: 'json',\n        contentType: 'application/json',\n        error: function(xhr) {\n          handleAjaxError(xhr);\n          self.timetable.timetableDrawer.redraw();\n        },\n        complete: IndicoUI.Dialogs.Util.progress(),\n        success: function(data) {\
1n          if (undo) {\n            self.timetable.enableUndo(undo, {eventData: eventData, entry: data.entry}, null);\n          }\n\n          self.timetable._updateMovedEntry(data.entry, data.entry.old.id);\n          handleNotifications(data);\n        },\n      });\n    },\n\n    switchToIntervalTimetable: function(intervalId) {\n      this.timetable.switchToInterval(intervalId);\n    },\n\n    /*\n     * Translate the date string with format yyyymmdd into a javascript\n     * Date object.\n     */\n    _dateStr2Date: function(dateStr) {\n      var year = parseInt(dateStr.substr(0, 4), 10);\n      var month = parseInt(dateStr.substr(4, 2), 10);\n      var day = parseInt(dateStr.substr(6, 2), 10);\n\n      return new Date(year, month - 1, day);\n    },\n\n    _addParams: function(type) {\n      return {\n        startDate: Util.formatDateTime(this.eventInfo.startDate, IndicoDateTimeFormats.Server),\n        selectedDay: Util.formatDateTime(\n          this._dateStr2Date(this.timetable.currentDay),\n          IndicoDateTimeFormats.Server.slice(0, 8)\n        ),\n        roomInfo: {\n          location: this.eventInfo.location,\n          room: this.eventInfo.room,\n          address: this.eventInfo.address,\n        },\n        conference: this.eventInfo.id,\n        sessionTimetable: any(this.isSessionTimetable, false),\n        type: type,\n        parentType: 'Event',\n      };\n    },\n    _addToSessionParams: function(session, type) {\n      var params = this._addParams(type);\n\n      if (type != 'SessionSlot') {\n        // If it's not for a session slot, we take the location from the session\n        params.roomInfo = {\n          location: session.location,\n          room: session.room,\n          address: session.address,\n        };\n      }\n\n      params.sessionConveners = session.sessionConveners;\n\n      // If sessionId exists then use that value, otherwise just use the id\n      // This is needed since the session can either be an entry in the timetable\n      // or an entry in this.eventInfo.session.\n      params.session = exists(session.sessionId) ? session.sessionId : session.id;\n      if (type != 'SessionSlot') {\n        params.slot = session.sessionSlotId;\n        params.type = 'Session' + params.type;\n      }\n\n      params.parentType = type == 'SessionSlot' ? 'Session' : 'SessionSlot';\n\n      return params;\n    },\n\n    addContribution: function() {\n      var self = this;\n      var params;\n\n      if (this.session !== null) {\n        params = this._addToSessionParams(this.session, 'Contribution');\n      } else {\n        params = this._addParams('Contribution');\n      }\n\n      var canCreateNew = !self.isSessionTimetable || self.timetable.canManageSession;\n      var dialog = new AddContributionDialog(\n        $O(params),\n        this.timetable,\n        function(result) {\n          handleNotifications(result);\n          self._addEntries(result.entries);\n        },\n        canCreateNew\n      );\n\n      dialog.execute();\n    },\n\n    addBreak: function() {\n      var self = this;\n      var params = self._addParams();\n      var urlArgs = {\n        event_id: self.eventInfo.id,\n        day: params.selectedDay,\n      };\n      if (self.session !== null) {\n        urlArgs.session_block_id = self.session.sessionSlotId;\n      }\n      if (self.isSessionTimetable) {\n        urlArgs.session_id = self.timetable.contextInfo.sessionId;\n      }\n      ajaxDialog({\n        trigger: self,\n        url: build_url(Indico.Urls.Timetable.breaks.add, urlArgs),\n        title: $T.gettext('Add break'),\n        onClose: function(data) {\n          if (data) {\n            handleNotifications(data);\n            self.timetable._updateDay(data.update);\n          }\n        },\n      });\n    },\n\n    addSession: function() {\n      var self = this;\n      var params = this._addParams('Session');\n      ajaxDialog({\n        trigger: this,\n        url: build_url(Indico.Urls.Timetable.sessions.add, {event_id: params.conference}),\n        title: $T.gettext('Add session'),\n        onClose: function(data) {\
1n          if (data) {\n            handleNotifications(data);\n            self.timetable.eventInfo.sessions[data.session.id] = data.session;\n\n            if (data.success) {\n              self.addSessionSlot(data.session);\n            }\n          }\n        },\n      });\n    },\n\n    addSessionSlot: function(session) {\n      var self = this;\n      var params = this._addToSessionParams(session, 'SessionSlot');\n      var urlArgs = {\n        event_id: params.conference,\n        day: params.selectedDay,\n        parent_session_id: params.session,\n      };\n      if (self.isSessionTimetable) {\n        urlArgs.session_id = self.timetable.contextInfo.timetableSession.id;\n      }\n      ajaxDialog({\n        trigger: this,\n        url: build_url(Indico.Urls.Timetable.sessionBlocks.add, urlArgs),\n        title: $T.gettext('Add session block'),\n        onLoadError: function(xhr) {\n          if (xhr.status == 404) {\n            handleErrorResponse(xhr);\n          } else {\n            handleAjaxError(xhr);\n          }\n\n          return false;\n        },\n        onError: function(xhr) {\n          if (xhr.status == 404) {\n            handleErrorResponse(xhr);\n            $(this).trigger('ajaxDialog:close');\n          } else {\n            handleAjaxError(xhr);\n          }\n        },\n        onClose: function(data) {\n          if (data) {\n            handleNotifications(data);\n            self.timetable._updateDay(data.update);\n          }\n        },\n      });\n    },\n\n    swapEntry: function(eventData, direction) {\n      var self = this;\n      var info = this._getLocatorParams(eventData);\n\n      info.set('direction', direction);\n\n      if (exists(eventData.sessionId)) {\n        info.set('session', eventData.sessionId);\n        info.set('slot', eventData.sessionSlotId);\n      }\n\n      var urlArgs = {\n        event_id: eventData.conferenceId,\n        entry_id: eventData.scheduleEntryId,\n      };\n\n      if (self.timetable.isSessionTimetable) {\n        urlArgs.session_id =\n          self.timetable.contextInfo.sessionId || self.timetable.contextInfo.timetableSession.id;\n      }\n\n      $.ajax({\n        url: build_url(Indico.Urls.Timetable.entries.swap, urlArgs),\n        method: 'POST',\n        data: info.getAll(),\n        error: handleAjaxError,\n        complete: IndicoUI.Dialogs.Util.progress(),\n        success: function(data) {\n          if (data) {\n            self.timetable._updateDay(data.update);\n          }\n        },\n      });\n    },\n\n    /*\n     * Iterates through entries and adds all of them\n     */\n    _addEntries: function(entries) {\n      var self = this;\n      each(entries, function(update) {\n        self.timetable._updateDay(update);\n      });\n    },\n  },\n  function(timetable, eventInfo, isSessionTimetable) {\n    this.timetable = timetable;\n    this.eventInfo = eventInfo;\n    this.isSessionTimetable = isSessionTimetable;\n  }\n);\n\ntype(\n  'TopLevelTimeTableManagementActions',\n  ['TimetableManagementActions'],\n  {\n    _getLocatorParams: function(eventData) {\n      var info = new WatchObject();\n      info.set('scheduleEntryId', eventData.scheduleEntryId);\n      info.set('conference', eventData.conferenceId);\n\n      if (this.isSessionTimetable) {\n        info.set('sessionTimetable', this.isSessionTimetable);\n        info.set('sessionId', eventData.sessionId);\n      }\n\n      return info;\n    },\n  },\n  function(timetable, eventInfo, contextInfo, isSessionTimetable) {\n    this.TimetableManagementActions(timetable, eventInfo, isSessionTimetable);\n    this.session = null;\n  }\n);\n\ntype(\n  'IntervalTimeTableManagementActions',\n  ['TimetableManagementActions'],\n  {\n    _getLocatorParams: function(eventData) {\n      var info = new WatchObject();\n      info.set('scheduleEntryId', eventData.scheduleEntryId);\n      info.set('conference', eventData.conferenceId);\n      info.set('sessionId', eventData.sessionId);\n      info.set('sessionSlotId', eventData.sessionSlotId);\n\n      return info;\n    },\n  },\n  function(timetable, eventInfo, intervalInfo, isSessionTimetable) {\n    this.TimetableManagementActions(timetable, eventInfo, isSessionTimetable);\n    this.session = intervalInfo;\n  }\n);\n"},38544:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/* eslint-disable import/unambiguou
1s, object-shorthand */\n\nvar TimetableDefaults = {\n  topMargin: 30,\n  bottomMargin: 40,\n  leftMargin: 55,\n  rightMargin: 5,\n  resolution: 1,\n  menuWidth: 150,\n  blockMargin: 4, // Margin used inside timetable blocks\n  wholeday: 7, // # of hours of duration for a timetable event to be seen as be during the whole day.\n  minContribHeight: 20, // Minimum height for a contrib displayed inside a session TODO: remove?\n  layouts: {\n    compact: {\n      name: 'Compact',\n      values: {\n        pxPerHour: 150,\n        pxPerSpace: 2,\n        minPxPerBlock: 50,\n      },\n      manager: new CompactLayoutManager(),\n    },\n\n    proportional: {\n      name: 'Proportional',\n      values: {\n        pxPerHour: 120, //50 when not proportional\n        minPxPerBlock: 25,\n      },\n      manager: new ProportionalLayoutManager(),\n    },\n\n    poster: {name: 'Poster', manager: new PosterLayoutManager()},\n\n    room: {name: 'Room', manager: new RoomLayoutManager()},\n  },\n  filters: {\n    session: {name: $T('Session'), filter: new SessionFilter()},\n    room: {name: $T('Room'), filter: new RoomFilter()},\n  },\n};\n\ntype(\n  'TimeTable',\n  ['HistoryListener'],\n  {\n    /*\n     * Translates the keys used in the data dictionary into titles\n     * displayed in the tab control\n     */\n\n    _draw: function(timetableDiv) {\n      return $('<div/>')\n        .css({width: this.width})\n        .append(\n          $('<div/>').css('display', 'block'),\n          this.legend,\n          this.header,\n          timetableDiv.dom,\n          this.loadingIndicator.dom\n        )\n        .get();\n    },\n\n    _getMenu: function() {\n      return Html.div();\n    },\n\n    postDraw: function() {\n      this.timetableDrawer.postDraw();\n      //this.LookupTabWidget.prototype.postDraw.call(this);\n    },\n\n    getData: function() {\n      return this.data;\n    },\n\n    get_elem: function(blkId) {\n      return $(this.getTimetableDrawer()._blockMap[blkId]);\n    },\n\n    getById: function(id) {\n      var info = Util.parseId(id);\n      var type = info[0];\n      var compositeId = '';\n      info = info.slice(2);\n\n      if (type == 'Conference') {\n        return this.eventInfo;\n      } else if (type == 'Contribution') {\n        throw 'not implemented!';\n      } else if (type == 'Session') {\n        return this.eventInfo.sessions[info[0]];\n      } else if (type == 'SessionSlot') {\n        compositeId = 's' + info[0] + 'l' + info[1];\n      } else {\n        throw 'unrecognized id!';\n      }\n\n      for (var day in this.data) {\n        if (this.data[day][compositeId]) {\n          return this.data[day][compositeId];\n        }\n      }\n    },\n\n    setSelectedTab: function(val) {\n      // same as inherited, but return deferred\n      var dfr = $.Deferred();\n      $('body').one('timetable_ready', function() {\n        dfr.resolve();\n      });\n      this.JLookupTabWidget.prototype.setSelectedTab.call(this, val);\n      return dfr.promise();\n    },\n\n    getTimetableDrawer: function() {\n      return this.timetableDrawer;\n    },\n\n    setData: function(data, intervalData, startTime, endTime) {\n      this.timetableDrawer.setData(data, startTime, endTime);\n    },\n\n    _createLoadingIndicator: function() {\n      return Html.div('timetableLoading', $T('Updating the timetable...'));\n    },\n    /*\n     * To be overloaded. Header content displayed above the timetable.\n     */\n    _getHeader: function() {\n      return Html.div({});\n    },\n\n    /*\n     * To be overloaded. Returns the small \"Session legend\" displayed in\n     * detailed mode (only).\n     */\n    _getLegend: function() {\n      return Html.div({});\n    },\n    /*\n     * To be overloaded. Returns buttons to be displayed below the tabs in\n     * the tab widget.\n     */\n    _functionButtons: function() {\n      return [];\n    },\n\n    //To be overloaded\n    redrawLegend: function() {\n      return;\n    },\n  },\n  function(data, width, wrappingElement, detailLevel, managementMode) {\n    var self = this;\n    this.data = data;\n\n    this.enabled = true;\n\n    this.processedWarnings = new WatchList();\n\n    this.width = width;\n    this.loadingIndicator = this._createLoadingIndicator();\n    this.header = this._getHeader();\n    this.legend = $('<div/>');\n  }\n);\n\ntype(\n  'DisplayTimeTable',\n  ['TimeTable'],\n  {\n    filterMenu: function() {\
1n      var self = this;\n\n      var filterLink = Html.a({href: '#'}, $T('Filter'));\n      this.filterMenu = new TimetableFilterMenu(filterLink, self.timetableDrawer);\n\n      filterLink.observeClick(function(e) {\n        var pos = filterLink.getAbsolutePosition();\n        self.filterMenu.open(pos.x + filterLink.dom.offsetWidth, pos.y);\n        return false;\n      });\n\n      return Html.ul(\n        {className: 'inner', style: {display: 'none'}},\n        Html.li('menuConfMiddleCell', filterLink)\n      );\n    },\n\n    printMenu: function() {\n      var self = this;\n\n      var printLink = Html.a({href: '#'}, $T('Printable version'));\n      printLink.observeClick(function(e) {\n        self.print();\n      });\n      return Html.ul(\n        {className: 'inner', style: {display: 'none'}},\n        Html.li('menuConfMiddleCell', printLink)\n      );\n    },\n    print: function() {\n      var self = this;\n\n      self.timetableDrawer.setPrintableVersion(true);\n      var timetableElements = translate(\n        self.timetableDrawer.canvas.dom.childNodes,\n        function(value) {\n          return value;\n        }\n      );\n\n      var goBackLink = $('<a/>')\n        .prop('href', window.location.hash)\n        .html($T('Go back'))\n        .css('font-size', '17px');\n      var separator = $('<a/>').html(' | ').css('fontSize', '17px');\n      var printLink = $('<a/>')\n        .prop('href', window.location.hash)\n        .html($T('Print'))\n        .css('font-size', '17px');\n\n      var links = $('<span/>').append(goBackLink, separator, printLink).css('float', 'right');\n\n      var header = $('<div/>').addClass('timetableHeader clearfix').append(links).css({\n        padding: '0px 5px 5px 5px',\n        'border-bottom': '1px solid black',\n        'text-align': 'center',\n        width: self.timetableDrawer.width,\n      });\n      header.append(\n        $('<span/>').append(self._titleTemplate(self.timetableDrawer.day)).css('float', 'left')\n      );\n\n      goBackLink.click(function() {\n        location.reload();\n      });\n\n      printLink.click(function() {\n        window.print();\n      });\n\n      var timetableDiv = $('<div/>')\n        .append(timetableElements)\n        .css({'padding-top': '20px', position: 'relative'});\n\n      $('body').html(header.add(timetableDiv));\n      $('body').css('padding', '30px');\n    },\n\n    pdf: function() {\n      if ($('html').data('static-site')) {\n        window.location = build_url(Indico.Urls.Timetable.default_pdf, {\n          event_id: this.eventInfo.id,\n        });\n      } else {\n        ajaxDialog({\n          url: build_url(Indico.Urls.Timetable.pdf, {event_id: this.eventInfo.id}),\n          title: $T.gettext('Export to PDF'),\n        });\n      }\n    },\n\n    fullScreen: function() {\n      var self = this;\n\n      IndicoUI.Dialogs.Util.progress($T('Switching to full screen mode...'));\n      // This timeout is needed in order to give time to the progress indicator to be rendered\n      setTimeout(function() {\n        self.timetableDrawer.width = $(window).width() - 50; // 50 is a width offset.\n\n        var header = $('<div/>')\n          .addClass('timetableFullScreenHeader clearfix')\n          .css('width', self.timetableDrawer.width);\n        header.append(\n          $('<span/>').append(self._titleTemplate(self.timetableDrawer.day)).css('float', 'left')\n        );\n\n        var timetableCanvas = $('#timetable_canvas');\n        $('#timetable_canvas').width('width', self.timetableDrawer.width);\n        $('body').html(header);\n        $('body').css('padding', '30px');\n        $('.timetableFullScreenHeader').before(self._getExtraButtons());\n        $('.timetableFullScreenHeader').before(self.legend);\n        $('.timetableFullScreenHeader').after(timetableCanvas);\n        self.timetableDrawer.redraw(self.currentDay);\n\n        self._filterSetup();\n        if (self.timetableDrawer.detail.get() == 'contribution') {\n          var newDetailLevel =\n            self.timetableDrawer.detail.get() == 'contribution' ? 'session' : 'contribution';\n          self.timetableDrawer.detail.set(newDetailLevel);\n          self.toggleDetailedView();\n        }\n      }, 50);\n    }
1,\n\n    _getExtraButtons: function() {\n      var self = this;\n      var container = $('<div class=\"tabExtraButtons\"/>');\n      var goBackButton = {\n        btn: Html.button('buttonWhite', $T('Exit Full Screen')),\n        onclick: function(btnContainer) {\n          location.reload();\n        },\n      };\n      var buttons = self._functionButtons();\n      buttons[2] = goBackButton;\n      $.each(buttons, function(i, btnData) {\n        var btn = $(btnData.btn.dom || btnData.btn);\n        btn.click(function() {\n          btnData.onclick(btn);\n        });\n        container.append(btn);\n      });\n      goBackButton.btn.dom.style.background = '#9F883B';\n      return container;\n    },\n\n    _filterSetup: function() {\n      var self = this;\n      this.filter = new TimeTableFilter(this.timetableDrawer, function() {\n        // When closed restore the filter button color\n        self.filterButtonContainer.css('background', '');\n        return true;\n      });\n      this.filter.draw();\n    },\n\n    toggleDetailedView: function() {\n      var self = this;\n      var detailLevel = this.timetableDrawer.detail.get();\n      var newDetailLevel = detailLevel == 'contribution' ? 'session' : 'contribution';\n      this.timetableDrawer.detail.set(newDetailLevel);\n      var state = newDetailLevel == 'contribution';\n      this.inDetailedMode = state;\n      //detailsButton.btn.set(state ? \"Hide details\" : \"Show details\");\n      this.detailsButton.btn.getParent().dom.style.background = state ? '#9F883B' : '';\n      this._addToHistory(this.currentDay + (state ? '.detailed' : ''));\n\n      var legend;\n\n      //If the \"Detailed view\" button is clicked and \"activated\".\n      if (this.inDetailedMode) {\n        /* Draw legend or \"undraw\" legend (getLegend() returns an empty div)\n               when toggling for detailed view. */\n        legend = this._getLegend();\n        this.legend.replaceWith(legend);\n\n        if (this._legendActive) {\n          self._toggleLegend(true);\n        } else {\n          self._legendPostDraw();\n        }\n      } else {\n        if (this._legendActive) {\n          self._toggleLegend(false);\n        }\n        legend = $('<div/>');\n        this.legend.replaceWith(legend);\n      }\n      this.legend = legend;\n    },\n\n    _functionButtons: function() {\n      var self = this;\n\n      this.printButton = {\n        btn: Html.button('printButtonWhite', $T('Print')),\n        onclick: function(btnContainer) {\n          self.print();\n        },\n      };\n\n      this.pdfButton = {\n        btn: Html.button('buttonWhite', $T('PDF')),\n        onclick: function(btnContainer) {\n          self.pdf();\n        },\n      };\n\n      this.fullScreenButton = {\n        btn: Html.button('buttonWhite', $T('Full screen')),\n        onclick: function(btnContainer) {\n          self.fullScreen();\n        },\n      };\n\n      // TODO: Needs to be implemented\n      this.linkButton = Html.div('linkButtonWhite', $T('Link'));\n\n      this.detailsButton = {\n        btn: Html.button(\n          {className: 'buttonWhite', id: 'detailsButton'},\n          Html.span({}, $T('Detailed view'))\n        ),\n        onclick: function() {\n          self.toggleDetailedView();\n        },\n      };\n\n      this.filterButton = {\n        btn: Html.button('buttonWhite', $T('Filter')),\n        onclick: function(btnContainer) {\n          // Save the container so that the filter button background\n          // color can be restored when filter is closed\n          self.filterButtonContainer = btnContainer;\n          self.filter.toggle();\n          var state = self.filter.state.get();\n          self._filterActive = state;\n          btnContainer.css('background', state ? '#9F883B' : '');\n        },\n      };\n\n      return [\n        this.printButton,\n        this.pdfButton,\n        this.fullScreenButton,\n        this.detailsButton,\n        this.filterButton,\n      ];\n    },\n  },\n  function(data, width, wrappingElement, detailLevel) {\n    this.TimeTable(data, width, wrappingElement, detailLevel, false);\n\n    // Set data[all] so that the All days tab is created\n    if (keys(data).length > 1) {\n      this.data.all = data;\n    }\n  }\n);\n\ntype(\n  'TopLevelTimeTableMixin',\n  ['JLookupTabWidget'],\n  {\n    draw: function() {\
1n      return this.JLookupTabWidget.prototype.draw.call(this);\n    },\n\n    getDays: function() {\n      return this.sortedKeys;\n    },\n\n    disable: function() {\n      this.enabled = false;\n      this.JLookupTabWidget.prototype.disable.call(this);\n    },\n\n    enable: function() {\n      this.enabled = true;\n      this.JLookupTabWidget.prototype.enable.call(this);\n    },\n\n    _titleTemplate: function(text) {\n      if (text == 'all') {\n        return $T('All days');\n      }\n\n      return moment(text).format(\n        $T.pgettext('momentjs date format for timetable tab headers', 'ddd DD/MM')\n      );\n    },\n\n    _parseDayInterval: function(hash) {\n      var m = hash.match(/#(\\d{8}|all)(?:\\.((?:s\\d+)|detailed))?/);\n\n      if (m) {\n        return [m[1], m[2]];\n      } else {\n        return [null, null];\n      }\n    },\n\n    switchToInterval: function(intervalId) {\n      var dfr = $.Deferred();\n      this.disable();\n\n      var intervalInfo = this.data[this.currentDay][intervalId];\n      var data = intervalInfo.entries;\n\n      this.intervalTimeTable = new IntervalManagementTimeTable(\n        this,\n        data,\n        intervalInfo,\n        this.eventInfo,\n        this.width.slice(0, -2),\n        this.canvas[0],\n        'contribution',\n        this.isSessionTimetable,\n        this.customLinks,\n        this.canManageSession,\n        this.canManageBlocks,\n        this.canManageContribs\n      );\n\n      this.intervalTimeTable.setData(intervalInfo);\n      var content = this.intervalTimeTable.draw();\n      this.canvas.html(content[0]);\n      this.intervalTimeTable.postDraw();\n\n      $('body').trigger('timetable_switch_interval', this.intervalTimeTable);\n\n      dfr.resolve();\n      return dfr.promise();\n    },\n\n    postDraw: function() {\n      this.TimeTable.prototype.postDraw.call(this);\n    },\n\n    switchToTopLevel: function(day) {\n      day = day || this.currentDay;\n      var dfr = $.Deferred();\n      this.enable();\n      this.setSelectedTab(day || this.currentDay);\n      this._generateContent(this.getSelectedPanel());\n      this.timetableDrawer.redraw();\n\n      // Refresh header (menu, etc...)\n      var header = this._getHeader();\n      this.header.replaceWith(header);\n      this.header = header;\n\n      $('body').trigger('timetable_switch_toplevel', this);\n      window.location = '#' + day;\n\n      dfr.resolve();\n      return dfr.promise();\n    },\n  },\n  function(\n    data,\n    width,\n    wrappingElement,\n    detailLevel,\n    managementActions,\n    historyBroker,\n    timetableLayoutId\n  ) {\n    var self = this;\n\n    this.managementActions = managementActions;\n\n    this.canvas = Html.div({});\n\n    historyBroker.addListener(this);\n\n    this.timetableDrawer = new TimetableDrawer(\n      this,\n      width,\n      wrappingElement,\n      detailLevel,\n      this._functionButtons(),\n      this.loadingIndicator,\n      !!managementActions,\n      managementActions,\n      timetableLayoutId\n    );\n\n    this.sortedKeys = keys(this.data);\n    this.sortedKeys.sort();\n\n    var today = new Date();\n    var todayStr = IndicoUtil.formatDate2(today);\n\n    var originalHash = window.location.hash;\n\n    var dayAndInterval = this._parseDayInterval(originalHash);\n    var initialTab = null;\n\n    if (dayAndInterval[0]) {\n      initialTab = dayAndInterval[0];\n    }\n\n    // if nothing is found\n    if (initialTab === null) {\n      // look for today\n      if (exists(data[todayStr])) {\n        initialTab = todayStr;\n      } else {\n        // look for the first non-empty day\n        for (var day in this.data) {\n          if (Object.getOwnPropertyNames(this.data[day]).length > 0) {\n            initialTab = day;\n            break;\n          }\n        }\n        if (initialTab === null) {\n          // otherwise show the first day\n          initialTab = this.sortedKeys[0];\n        }\n      }\n    }\n\n    this.currentDay = initialTab;\n\n    this.JLookupTabWidget(\n      translate(this.sortedKeys, function(key) {\n        return [\n          key,\n          function() {\
1n            var detailed = self.inDetailedMode ? '.detailed' : '';\n\n            self.currentDay = key;\n            // each time one tab is clicked,\n            // drawDay is called over a different day\n            if (key == 'all') {\n              self._addToHistory('all' + detailed);\n              return self._draw(self.timetableDrawer.drawAllDays());\n            } else {\n              // Are we switching to interval mode\n              if (!self.enabled) {\n                // stop everything, we don'w want the LookupTabWidget\n                // to replace the contents\n                throw 'stopDrawing';\n              }\n\n              self._addToHistory(key + detailed);\n              return self._draw(self.timetableDrawer.drawDay(key));\n            }\n          },\n        ];\n      }),\n      this.width,\n      100,\n      initialTab,\n      this._functionButtons(),\n      this.canvas\n    );\n    this.makeScrollable();\n\n    if (dayAndInterval[1]) {\n      var subref = dayAndInterval[1];\n      // TODO: replace with appropriate notification system\n      setTimeout(function() {\n        if (subref == 'detailed') {\n          self.toggleDetailedView();\n        } else {\n          self.switchToInterval(subref);\n        }\n      }, 500);\n    }\n  }\n);\n\ntype(\n  'IntervalTimeTableMixin',\n  [],\n  {\n    draw: function() {\n      // Add hash to history\n      this.parentTimetable._addToHistory(this.currentDay + '.' + this.contextInfo.id);\n\n      return this._draw(this.timetableDrawer.drawDay(this.currentDay));\n    },\n\n    _getInfoBoxContent: function() {\n      return Html.div(\n        {},\n        Html.span(\n          {style: {fontStyle: 'italic', fontSize: '0.9em'}},\n          $T('You are viewing the contents of the session block:')\n        ),\n        Html.div(\n          {style: {fontWeight: 'bold', marginTop: '5px', fontSize: '1.3em'}},\n          this._generateSlotTitle(this.contextInfo),\n          Html.span(\n            {style: {fontWeight: 'normal'}},\n            ' (',\n            $B(Html.span({}), this.slotStartTime),\n            ' - ',\n            $B(Html.span({}), this.slotEndTime),\n            ')'\n          )\n        )\n      );\n    },\n\n    postDraw: function() {\n      this.timetableDrawer.postDraw();\n    },\n\n    setData: function(data) {\n      var day = IndicoUtil.formatDate2(IndicoUtil.parseJsonDate(data.startDate));\n      var ttData = {};\n\n      this.data = data.entries;\n\n      // WatchValues, so that interval changes can be handled\n      this.slotStartTime = new WatchValue(data.startDate.time.substring(0, 5));\n      this.slotEndTime = new WatchValue(data.endDate.time.substring(0, 5));\n\n      ttData[day] = data.entries;\n\n      this.currentDay = day;\n      this.timetableDrawer.setData(ttData, day, data.isPoster);\n\n      // The time must be update each time new data is set\n      this.infoBox.set(this._getInfoBoxContent());\n    },\n\n    getDays: function() {\n      return this.parentTimetable.getDays();\n    },\n\n    getById: function(id) {\n      return this.parentTimetable.getById(id);\n    },\n  },\n  function(parent, width, wrappingElement, managementActions, layout) {\n    this.managementActions = managementActions;\n    this.parentTimetable = parent;\n\n    this.timetableDrawer = new IntervalTimetableDrawer(\n      this,\n      width,\n      wrappingElement,\n      this._functionButtons(),\n      this.loadingIndicator,\n      !!managementActions,\n      managementActions,\n      layout || 'compact'\n    );\n  }\n);\n\ntype(\n  'ManagementTimeTable',\n  ['TimeTable', 'UndoMixin'],\n  {\n    _generateSlotTitle: function(slotData) {\n      return slotData.title + (slotData.slotTitle ? ': ' + slotData.slotTitle : '');\n    },\n\n    _createInfoArea: function() {\n      // this is a client-side hack that compensates some algorithm weaknesses\n\n      var closeButton = Html.div({\n        className: 'balloonPopupCloseButton',\n        style: {position: 'absolute', top: '10px', right: '10px', padding: '0px'},\n      });\n\n      var self = this;
1\n\n      closeButton.observeClick(function() {\n        self._hideWarnings();\n      });\n\n      return Html.div(\n        'timetableManagementInfoArea',\n        Html.div({}, $T('Your changes triggered the automatic modification of some settings:')),\n        $B(Html.ul({}), this.processedWarnings, function(item) {\n          var title = item[4];\n          var atoms = Util.parseId(item[1]);\n\n          var message = {\n            OWNER_START_DATE_EXTENDED: {\n              SessionSlot:\n                $T('The <strong>starting time</strong> of the session interval <strong>') +\n                title +\n                $T('</strong> was moved from '),\n              Session:\n                $T('The <strong>starting time</strong> of the session interval <strong>') +\n                title +\n                $T('</strong> was moved from '),\n              Conference: $T(\n                'The <strong>starting time</strong> of the <strong>Event</strong> was moved from '\n              ),\n            },\n            OWNER_END_DATE_EXTENDED: {\n              SessionSlot:\n                $T('The <strong>ending time</strong> of the session interval <strong>') +\n                title +\n                $T('</strong> was moved from '),\n              Session:\n                $T('The <strong>ending time</strong> of the session interval <strong>') +\n                title +\n                $T('</strong> was moved from '),\n              Conference: $T(\n                'The <strong>ending time</strong> of the <strong>Event</strong> was moved from '\n              ),\n            },\n            ENTRIES_MOVED: {\n              SessionSlot:\n                $T('The contents of the interval <strong>') +\n                title +\n                $T('</strong> were moved from '),\n            },\n          }[item[0]][atoms[0]];\n\n          var span = Html.span({style: {verticalAlign: 'middle', marginLeft: '5px'}});\n          span.dom.innerHTML =\n            message +\n            ' <strong>' +\n            item[3] +\n            '</strong>' +\n            $T(' to ') +\n            '<strong>' +\n            item[2] +\n            '</strong>';\n          return Html.li({}, span);\n        }),\n        closeButton\n      );\n    },\n\n    _updateMovedEntry: function(result, oldEntryId) {\n      return this._updateEntry(result, oldEntryId, function(data) {\n        var oldDate = IndicoUtil.formatDateTime(\n          IndicoUtil.parseJsonDate(result.old.startDate),\n          'd/MM/YYYY hh:mm'\n        );\n        var oldDay = Util.formatDateTime(\n          Util.parseDateTime(oldDate, IndicoDateTimeFormats.Default),\n          IndicoDateTimeFormats.Ordinal\n        );\n        if (result.old.sessionId) {\n          // block was inside session slot\n          delete data[oldDay]['s' + result.old.sessionSlotEntryId].entries[result.old.id];\n        } else {\n          // block was in top level\n          delete data[oldDay][result.old.id];\n        }\n\n        if (result.slotEntry) {\n          // block moves inside session slot\n          data[result.day][result.slotEntry.id].entries[result.id] = result.entry;\n          // updates the time of the session if it has to be extended\n          data[result.day][result.slotEntry.id].startDate.time = result.slotEntry.startDate.time;\n          data[result.day][result.slotEntry.id].endDate.time = result.slotEntry.endDate.time;\n          data[result.day][result.slotEntry.id].duration = result.slotEntry.duration;\n        } else {\n          // block moves to top level\n          data[result.day][result.id] = result.entry;\n        }\n      });\n    },\n\n    /*\n     *\n     * Is called every time a timetable entry has been successfully\n     * added or updated. Updates and redraws the timetable.\n     * @param originalArgs this are the original args. If they are passed, we can remove the entry\n     * from the index before adding it again (just in case the date has changed).\n     *\n     */\n\n    _processAutoOps: function(result) {\n      this._hideWarnings();\n\n      var self = this;\n\n      if (result.autoOps && result.autoOps.length > 0) {\n        each(result.autoOps, function(op) {\n          var warning = self._processWarning(op);\n          if (warning && self.processedWarnings.indexOf(warning) === null) {\n            self.warningArea.dom.style.display = 'block';\n            self.processedWarnings.append(warning);\n          }\n        });\n      }\n    }
1,\n\n    _hideWarnings: function() {\n      this.warningArea.dom.style.display = 'none';\n      this.warnings.clear();\n      this.processedWarnings.clear();\n    },\n\n    _processWarning: function(entry) {\n      /*\n       * entry - the warning 'entry', a list [src, msg, target, newValue]\n       * startTime - the original starting time for the timeblock\n       * endTime - the original ending time for the timeblock\n       * [slotTitle] - title, if the entry is a slot\n       */\n\n      var msg = entry[1];\n      var finalTime = entry[3];\n\n      var type = Util.parseId(entry[2])[0];\n\n      var conference = null;\n      var slot = null;\n      var title = '';\n\n      var startTime = '';\n      var endTime = '';\n\n      if (type == 'Session') {\n        return null;\n      } else if (type == 'Conference') {\n        conference = this.getById(entry[2]);\n        title = conference.title;\n        startTime = conference.startDate.time.slice(0, 5);\n        endTime = conference.endDate.time.slice(0, 5);\n      } else if (type == 'SessionSlot') {\n        slot = this.getById(entry[2]);\n        startTime = slot.startDate.time.slice(0, 5);\n        endTime = slot.endDate.time.slice(0, 5);\n        title = this._generateSlotTitle(slot);\n      }\n\n      if (msg == 'OWNER_END_DATE_EXTENDED') {\n        // Make sure that something changed, otherwise the\n        // warning will be suppressed\n        if (endTime != finalTime) {\n          // slice(1) to ignore first value\n          return concat(entry.slice(1), [endTime, title]);\n        }\n      } else if (msg == 'OWNER_START_DATE_EXTENDED') {\n        // Again, make sure that something changed\n\n        if (startTime != finalTime) {\n          // slice(1) to ignore first value\n          return concat(entry.slice(1), [startTime, title]);\n        }\n      } else {\n        return concat(entry.slice(1), [startTime, title]);\n      }\n\n      return null;\n    },\n\n    _allowCreateHere: function(elementType) {\n      switch (elementType) {\n        case 'Session':\n          return this.contextInfo._type == 'Conference';\n        case 'Break':\n          return this.contextInfo._type == 'Conference'\n            ? true\n            : this.contextInfo.isPoster === false;\n        case 'Contribution':\n          return true;\n      }\n    },\n\n    _retrieveSessionColor: function(session) {\n      return this.getById('s' + session.id).color;\n    },\n\n    _openSessionMenu: function(triggerElement, parent) {\n      if (exists(this.addMenu) && this.addMenu.isOpen()) {\n        return;\n      }\n\n      var self = this;\n      var sessions = {};\n      each(this.eventInfo.sessions, function(session, key) {\n        sessions[session.id] = {};\n        sessions[session.id].func = function() {\n          self.managementActions.addSessionSlot(session);\n        };\n        sessions[session.id].color = self._retrieveSessionColor(session);\n        sessions[session.id].title = session.title;\n      });\n\n      var menu = {\n        '': {\n          content: {\n            'Create a new session': function() {\n              self.managementActions.addSession();\n              $('.button-menu').dropdown('close');\n            },\n          },\n          description: '',\n        },\n        'Add another block to:': {content: sessions, description: ''},\n      };\n\n      var te = new Html(triggerElement.find('a').get(0));\n      var sessMenu = new SessionSectionPopupMenu(\n        menu,\n        [te],\n        'timetableSectionPopupList popupListChained',\n        true,\n        true\n      );\n\n      var pos = triggerElement.offset();\n      sessMenu.open(pos.left, pos.top - 1);\n    },\n\n    _createAddMenu: function(elem) {\n      var self = this;\n      var ul = $('<ul class=\"i-dropdown\"/>');\n\n      if (this._allowCreateHere('Session')) {\n        var sessionAdd = $('<a href=\"#\"/>').text($T('Session block')).appendTo(ul).wrap('<li/>');\n        sessionAdd.bind('menu_select', function() {\n          if (keys(self.eventInfo.sessions).length === 0) {\n            $(this).closest('.group').dropdown('close');\n            self.managementActions.addSession();\n          } else {\n            self._openSessionMenu($(this).parent(), ul);\n          }\n          return true;\n        });\n      }\n\n      if (this._allowCreateHere('Contribution')) {\n        $('<a href=\"#\"/>')\n          .text($T('Contribution'))\n          .bind('menu_select', function() {\
1n            self.managementActions.addContribution();\n            // Dirty hack to avoid Chrome glitch\n            $(this).closest('.group').dropdown('close');\n            return false;\n          })\n          .appendTo(ul)\n          .wrap('<li/>');\n      }\n      if (this._allowCreateHere('Break')) {\n        $('<a href=\"#\"/>')\n          .text($T('Break'))\n          .bind('menu_select', function() {\n            self.managementActions.addBreak();\n            $(this).closest('.group').dropdown('close');\n            return false;\n          })\n          .appendTo(ul)\n          .wrap('<li/>');\n      }\n\n      return ul;\n    },\n\n    _getHeader: function() {\n      var self = this;\n\n      this.infoBox = Html.div({className: 'toolbar f-j-end', style: {margin: '5px'}});\n\n      this.addMenuLink = this.contextInfo.isPoster\n        ? $('<a href=\"#\" data-toggle=\"menu_select\"/>')\n            .text($T('Add poster'))\n            .bind('menu_select', function() {\n              self.managementActions.addContribution();\n            })\n        : $('<a href=\"#\" id=\"add_new\" class=\"arrow hide-if-locked\" data-toggle=\"dropdown\">').text(\n            $T.pgettext('Timetable entry', 'Add new')\n          );\n\n      this.rescheduleLink = $('<a href=\"#\" class=\"hide-if-locked\" data-toggle=\"menu_select\">').text(\n        $T('Reschedule')\n      );\n      this.rescheduleLink.bind('menu_select', function() {\n        var popup = new RescheduleDialog(self);\n        popup.open();\n        return false;\n      });\n\n      this.fitInnerTimetableLink = $(\n        '<a href=\"#\" class=\"hide-if-locked\" data-toggle=\"menu_select\">'\n      ).text($T('Fit to content'));\n      this.fitInnerTimetableLink.bind('menu_select', function() {\n        var popup = new FitInnerTimetableDialog(self);\n        popup.open();\n        return false;\n      });\n\n      this.addIntervalLink = $(\n        '<a href=\"#\" class=\"hide-if-locked\" data-toggle=\"menu_select\"/>'\n      ).text($T('Add new block'));\n      this.addIntervalLink.bind('menu_select', function() {\n        self.managementActions.addSessionSlot(self.eventInfo.timetableSession);\n        return false;\n      });\n\n      var customLinks = $();\n      for (var linkName in this.customLinks) {\n        var link = $('<a href=\"#\" class=\"hide-if-locked\">')\n          .text(linkName)\n          .addClass('js-{0}'.format(this.customLinks[linkName]))\n          .data('timetable', self);\n        customLinks = customLinks.add(link);\n      }\n\n      this.warningArea = this._createInfoArea();\n      this.warningArea.dom.style.display = 'none';\n\n      this.menu = $('<div class=\"group\"/>');\n\n      if (this.isSessionTimetable && this.isTopLevel) {\n        if (this.canManageBlocks) {\n          this.menu.append(this.addIntervalLink);\n        }\n      } else {\n        this.menu.append(this.addMenuLink);\n      }\n\n      if (!this.contextInfo.isPoster) {\n        if (this.canManageBlocks) {\n          if (this.contextInfo.entryType == 'Session') {\n            this.fitInnerTimetableLink.appendTo(this.menu);\n          }\n          this.rescheduleLink.appendTo(this.menu);\n        }\n      }\n\n      customLinks.appendTo(this.menu);\n\n      var tt_hour_tip = $('<div id=\"tt_hour_tip\"/>')\n        .hide()\n        .append($('<img/>').prop('src', imageSrc('tt_time')).prop('title', $T('Add one hour')));\n      var tt_status_info = $('<div id=\"tt_status_info\" />');\n\n      this.menu.children('a').addClass('i-button');\n\n      if (!this.contextInfo.isPoster && (!this.isTopLevel || !this.sessionTimetable)) {\n        this.menu.find('#add_new').after(this._createAddMenu(this.addMenuLink.parent()));\n      }\n\n      var ret = $('<div/>').append(\n        this.warningArea.dom,\n        $('<div id=\"headPanel\" class=\"ui-follow-scroll\"></div>').append(\n          $('<div class=\"toolbar f-j-end\" id=\"tt_menu\"/>').append(\n            this.menu.dropdown({effect_on: 'slideDown'}),\n            tt_status_info\n          )\n        ),\n        this.infoBox.dom,\n        tt_hour_tip\n      );\n\n      var extra = this.getTTMenu();\n      if (extra) {\n        ret.find('#tt_menu .group').after(extra);\n      }\n\n      return ret;\n    },\n  },\n  function(\n    data,\n    contextInfo,\n    eventInfo,\n    width,\n    wrappingElement,\n    detailLevel,\n    customLinks,\n    canManageSession,\n    canManageBlocks,\n    canManageContribs,\n    canManageEvent\n  ) {\n    this.customLinks = customLinks;\n    this.eventInfo = eventInfo;\n    this.contextInfo = contextInfo;\n    this.canManageSession = canManageSession || false;\n    this.canManageBlocks = canManageBlocks || false;\n    this.canManageContribs = canManageContribs || false;\n    this.canManageEvent = canManageEvent || false;\n    this.warnings = new WatchList();\n    this.TimeTable(data, width, wrappingElement, detailLevel, true);\n  }\n);\n\ntype(\n  'TopLevelDisplayTimeTable',\n  ['DisplayTimeTable', 'TopLevelTimeTableMixin'],\n  {\n    _retrieveHistoryState: function(hash) {\n      var currentDay = this._parseDayInterval(hash
1)[0];\n      this.setSelectedTab(currentDay);\n    },\n\n    _getLegend: function() {\n      var self = this;\n      //Initially show N and have the rest hidden (buried under \"...more\")\n      self._maxLegendItemsShownInitially = 4;\n\n      var toggleLegendButton = $('<div id=\"legendMainToggle\">' + $T('Session legend') + '</div>');\n\n      if (this._legendActive) {\n        toggleLegendButton.addClass('active');\n      }\n\n      toggleLegendButton.click(function() {\n        self._legendActive = !self._legendActive;\n        self._toggleLegend(self._legendActive);\n      });\n\n      self._toggleLegendButton = toggleLegendButton;\n\n      this.legendItems = self._legendItemsContainer();\n      // hide it so that we can execute the nice scroll down animation\n      this.legendItems.hide();\n\n      // create 'update' event that will be called when the day changes (redraw)\n      this.legendItems.bind('update', function() {\n        $(this).html(self._legendItemsContainer().children());\n        if (self._legendActive && self.legendSessionInfo[self.currentDay].length) {\n          $(this).slideDown();\n        } else {\n          $(this).slideUp();\n        }\n        self._legendPostDraw();\n      });\n\n      return $('<div id=\"wholeLegend\"/>').append(toggleLegendButton).append(this.legendItems);\n    },\n\n    _legendItemsContainer: function() {\n      var self = this;\n      var state = false;\n      var moreText = $T('see more...');\n      var showMoreLink = $('<a id=\"showMoreLink\" class=\"showMoreLink\">' + moreText + '</a>').click(\n        function() {\n          if (!state) {\n            self._fadeShowAllLegendItems();\n            showMoreLink.text($T('less...'));\n          } else {\n            self._fadeHideLegendItems();\n            showMoreLink.text(moreText);\n          }\n          self._legendPostDraw();\n          state = !state;\n        }\n      );\n\n      var closeButton = $('<div class=\"legendCloseButton\"/>').click(function() {\n        self._toggleLegend(false);\n        self._legendActive = false;\n      });\n\n      var sessions = self.legendSessionInfo[this.currentDay];\n      var container = $(\n        '<div id=\"timeTableLegend\" class=\"timeTableLegend ui-follow-scroll\">'\n      ).append(closeButton);\n\n      if (sessions.length) {\n        // Returns a div with each color + session name element\n        var legendElements = self._generateLegendDivItems(sessions);\n        container.append($('<div id=\"legendItemContainer\"/>').append(legendElements));\n        if (sessions.length > self._maxLegendItemsShownInitially) {\n          container.append(showMoreLink);\n        }\n      }\n      return container;\n    },\n\n    // Generates the \"legend items\"\n    // (a small colored rounded square with the sessions title on its right hand side)\n    // Returns a Div with those legend element items.\n    _generateLegendDivItems: function(sessions) {\n      var self = this;\n      var showNumSessionsCounter = 0;
1\n      var container = $('<div>');\n\n      $.each(sessions, function(idx, l) {\n        var div = $('<div class=\"legendItem\" />').append(\n          $('<div class=\"timeTableItemColour\" />').css('background', l[2]),\n          $('<span/>').text(l[1])\n        );\n\n        container.append(div);\n\n        if (idx >= self._maxLegendItemsShownInitially) {\n          div.hide();\n        }\n      });\n      return container;\n    },\n\n    redrawLegend: function() {\n      if (this.legendItems) {\n        this.legendItems.trigger('update');\n      }\n    },\n\n    // Used by the \"...more\"-button when you want to show more elements\n    _fadeShowAllLegendItems: function() {\n      $('.legendItem').fadeIn();\n    },\n\n    // Used by the \"...less\"-button when you want to hide more elementsmaxLegendItemsShownInitially\n    // Note, it does NOT HIDE ALL: depending on \"maxLegendItemsShownInitially\".\n    _fadeHideLegendItems: function() {\n      $('.legendItem').slice(this._maxLegendItemsShownInitially).fadeOut();\n    },\n\n    /* This function is also called in \"this.filterButton\" in order\n       to hide it (the Session Legend) when the Filter is brought up */\n    _toggleLegend: function(state) {\n      var self = this;\n\n      if (state) {\n        $('#legendMainToggle').addClass('active');\n        if (this.legendSessionInfo[this.currentDay].length) {\n          $('#timeTableLegend').slideDown();\n        }\n      } else {\n        $('#timeTableLegend').slideUp();\n        $('#legendMainToggle').removeClass('active');\n      }\n      this._legendPostDraw();\n    },\n\n    _legendPostDraw: function() {\n      if ($('#detailsButton').length) {\n        $('#legendMainToggle').position({\n          my: 'left top',\n          at: 'left bottom',\n          of: $('#detailsButton'),\n        });\n        $('#timeTableLegend:visible').width($('#timeTableLegend').get(0).clientWidth - 10);\n      }\n    },\n\n    _extractSessionInfo: function(data) {\n      // get an a dictionary where the keys are days and the values are lists\n      // of [id, title, color] tuples (sessions only)\n      var days = {};\n\n      _(data).each(function(entries, day) {\n        days[day] = _(entries)\n          .chain()\n          .select(function(e) {\n            return e.entryType == 'Session';\n          })\n          .groupBy(function(e) {\n            return e.sessionId;\n          })\n          .reduce(function(l, s) {\n            return l.concat(s[0]);\n          }, [])\n          .map(function(e) {\n            return [e.id, e.title, e.color, e.sessionId];\n          })\n          .sortBy(function(e) {\n            return e[1];\n          })\n          .value();\n      });\n\n      // for \"all days\", put it all together\n      days['all'] = _(Object.values(days))\n        .chain()\n        .flatten(true)\n        .groupBy(function(e) {\n          return e[3];\n        })\n        .reduce(function(l, s) {\n          return l.concat([s[0]]);\n        }, [])\n        .sortBy(function(e) {\n          return e[1];\n        })\n        .value();\n\n      return days;\n    },\n  },\n  function(\n    data,\n    contextInfo,\n    width,\n    wrappingElement,\n    detailLevel,\n    historyBroker,\n    timetableLayoutId\n  ) {\n    this.postDraw = TopLevelTimeTableMixin.prototype.postDraw;\n    this.legendSessionInfo = this._extractSessionInfo(data);\n    this._legendActive = true;\n\n    this.DisplayTimeTable(data, width, wrappingElement, detailLevel);\n    this.TopLevelTimeTableMixin(\n      data,\n      width,\n      wrappingElement,\n      detailLevel,\n      null,\n      historyBroker,\n      timetableLayoutId\n    );\n\n    this.eventInfo = contextInfo;\n    this._filterSetup();\n  }\n);\n\ntype(\n  'TopLevelManagementTimeTable',\n  ['ManagementTimeTable', 'TopLevelTimeTableMixin'],\n  {\n    _deleteOldEntry: function(data, result, oldEntryId) {\n      /*\n       * Deletes the old version of the entry\n       */\n\n      var oldStartDate;\n      // Check whether we're operating *over* an existing slot or not\n      // it is a slot && slot exists in timetable\n      if (result.entry.entryType == 'Session' && data[this.currentDay][result.id]) {\n        var slot = data[this.currentDay][result.id];\n        oldStartDate = slot.startDate.date.replace(/-/g, '');\n      } else {\n        // Contribution or break - event timetable limits\n        oldStartDate = this.currentDay;\n      }\n\n      delete data[oldStartDate][oldEntryId];\n      return data[oldStartDate][oldEntryId] ? data[oldStartDate][oldEntryId].entries : null;\n    },\n\n    _updateEntry: function(result, oldEntryId, updateCycle) {\n      var self = this;\n      var data = this.getData();\n\n      // AutoOp Warnings (before updates are done)\n      this._processAutoOps(result);\n\n      // Deletes the old version of the entry and temporarily\n      // stores the entry contents to be used later on this function\n      var oldContent = this._deleteOldEntry(data, result, oldEntryId);\n\n      // Here's the update cycle\n      if (updateCycle) {\n        updateCycle(data);\n      } else {\n        // If none is defined in the function args,\n        // execute the default action\n        if (exists(result.slotEntry)) {\n          data[result.day][result.slotEntry.id].entries[result.entry.id] = result.entry;\n        } else {\n          data[result.day][result.id] = result.entry;\n        }\n\n        // A session interval may contain entries, that\n        // should be preserved (e.g. content (contribs, breaks) of\n        // the slot).\n        if (oldContent) {\n          data[result.day][result.id].entries =\n            result.entry.entryType == 'Session' ? result.entry.entries : oldContent;\n        }\n\n        // If a session slot is added, let's update the list of sessions\n        if (result.session) {\n          this.eventInfo.sessions[result.session.id] = result.session;\n        }\n      }\n\n      var dfr = $.Deferred();
1\n      $('body').one('timetable_redraw', function() {\n        $('body').trigger('timetable_update', self);\n        dfr.resolve();\n      });\n      this.timetableDrawer.redraw();\n      return dfr.promise();\n    },\n\n    /**\n     * updateDay should be used when all the entries for a given day will be changed by the\n     * info received in 'update'.\n     *\n     * update must contain:\n     *     update.day: string with a given day, e.g. \"20100828\"\n     *     update.entries: all entries for a given day\n     *     update.session: info about the session\n     */\n    _updateDay: function(update) {\n      this._processAutoOps(update);\n\n      var data = this.getData();\n      var entries = {};\n\n      entries[update.day] = update.entries;\n      extend(data, entries);\n      this._removeMissingEntries(update.entries, update.day);\n\n      if (exists(update.session)) {\n        this.eventInfo.sessions[update.session.id] = update.session;\n      }\n\n      var self = this;\n      var dfr = $.Deferred();\n      $('body').bind('timetable_redraw', function() {\n        dfr.resolve();\n        $('body').trigger('timetable_update', self);\n      });\n      this.timetableDrawer.redraw();\n      return dfr.promise();\n    },\n\n    _removeMissingEntries: function(entries, day) {\n      var self = this;\n      _.each(self.data[day], function(value, key) {\n        if (!entries[key]) {\n          delete self.data[key];\n        }\n      });\n    },\n\n    _updateSessionData: function(sessionId, fields, newValues) {\n      var data = this.getData();\n\n      for (var day in data) {\n        for (var entry in data[day]) {\n          if (\n            data[day][entry]['entryType'] == 'Session' &&\
1n            data[day][entry]['sessionId'] == sessionId\n          ) {\n            for (var i = 0; i < fields.length; ++i) {\n              data[day][entry][fields[i]] = newValues[i];\n            }\n          }\n        }\n      }\n\n      this.timetableDrawer.redraw();\n    },\n\n    _getInfoBoxContent: function() {\n      return '';\n    },\n\n    getTTMenu: function() {\n      if (this.isSessionTimetable && this.canManageEvent) {\n        var goBackLink = $('<a>', {\n          class: 'icon-arrow-up i-button',\n          href: build_url(Indico.Urls.Timetable.management, {event_id: this.eventInfo.id}),\n        }).text($T.gettext('Go to event timetable'));\n        return $('<div>', {class: 'group right'}).append(goBackLink);\n      } else {\n        return null;\n      }\n    },\n\n    _retrieveHistoryState: function(hash) {\n      var dayInterval = this._parseDayInterval(hash);\n\n      var currentDay = dayInterval[0] || keys(this.data)[0];\n      if (dayInterval[1]) {\n        this.setSelectedTab(dayInterval[0]);\n        this.switchToInterval(dayInterval[1]);\n      } else {\n        this.switchToTopLevel();\n        this.setSelectedTab(dayInterval[0]);\n      }\n    },\n  },\n  function(\n    data,\n    eventInfo,\n    width,\n    wrappingElement,\n    detailLevel,\n    historyBroker,\n    isSessionTimetable,\n    customLinks,\n    canManageSession,\n    canManageBlocks,\n    canManageContribs,\n    canManageEvent\n  ) {\n    this.isSessionTimetable = isSessionTimetable;\n    this.isTopLevel = true;\n\n    this.ManagementTimeTable(\n      data,\n      eventInfo,\n      eventInfo,\n      width,\n      wrappingElement,\n      detailLevel,\n      customLinks,\n      canManageSession,\n      canManageBlocks,\n      canManageContribs,\n      canManageEvent\n    );\n    var managementActions = new TopLevelTimeTableManagementActions(\n      this,\n      eventInfo,\n      eventInfo,\n      isSessionTimetable\n    );\n    this.TopLevelTimeTableMixin(\n      data,\n      width,\n      wrappingElement,\n      detailLevel,\n      managementActions,\n      historyBroker,\n      'proportional'\n    );\n\n    this.postDraw = TopLevelTimeTableMixin.prototype.postDraw;\n  }\n);\n\ntype(\n  'IntervalManagementTimeTable',\n  ['ManagementTimeTable', 'IntervalTimeTableMixin'],\n  {\n    _updateTimes: function(newStartTime, newEndTime) {\n      this.slotStartTime.set(newStartTime.slice(0, 5));\n      this.slotEndTime.set(newEndTime.slice(0, 5));\n    },\n\n    _updateEntry: function(result, oldEntryId, updateCycle) {\n      var self = this;\n      var slot = this.contextInfo;\n\n      // Delete the old entry\n      delete this.parentTimetable.data[this.currentDay][slot.id].entries[oldEntryId];\n\n      this._processAutoOps(result);\n\n      // Here's the update cycle\n      if (updateCycle) {\n        updateCycle(this.parentTimetable.getData());\n      } else {\n        if (result.session) {\n          // Account for \"collateral damage\" on sessions\n          this.parentTimetable.eventInfo.sessions[result.session.id] = result.session;\n        }\n\n        if (exists(result.slotEntry)) {\n          // Save the entries, otherwise they are lost\n          result.slotEntry.entries = slot.entries;\n          this.parentTimetable.data[result.day][result.slotEntry.id] = result.slotEntry;\n          this.contextInfo = result.slotEntry;\n        }\n        this.parentTimetable.data[result.day][slot.id].entries[result.entry.id] = result.entry;\n\n        // Update the times for the slot\n        this._updateTimes(result.slotEntry.startDate.time, result.slotEntry.endDate.time);\n      }\n\n      var dfr = $.Deferred();\n      $('body').one('timetable_redraw', function() {\n        $('body').trigger('timetable_update', self);\n        dfr.resolve();\n      });\n      this.timetableDrawer.redraw();\n      return dfr.promise();\n    },\n\n    /**\n     * updateDay should be used when all the entries for a given slot will be changed by the\n     * info received in 'update'.\n     *\n     * update must contain:\n     *     update.day: string with a given day, e.g. \"20100828\"\n     *     update.entries: all entries for a given slot\
1n     *     update.slotEntry: info about the slot\n     *     update.session: info about the session\n     */\n    _updateDay: function(update) {\n      this._processAutoOps(update);\n\n      var slot = this.contextInfo;\n      extend(this.data, update.entries);\n      this._removeMissingEntries(update.entries);\n\n      if (exists(update.session)) {\n        this.parentTimetable.eventInfo.sessions[update.session.sessionId] = update.session;\n      }\n\n      if (exists(update.slotEntry)) {\n        // Save the entries, otherwise they are lost\n        update.slotEntry.entries = slot.entries;\n        this.parentTimetable.data[update.day][update.slotEntry.id] = update.slotEntry;\n        this.contextInfo = update.slotEntry;\n\n        // Update the times for the slot\n        this._updateTimes(update.slotEntry.startDate.time, update.slotEntry.endDate.time);\n      }\n\n      var dfr = $.Deferred();\n      $('body').bind('timetable_redraw', function() {\n        dfr.resolve();\n      });\n\n      this.timetableDrawer.redraw();\n      return dfr.promise();\n    },\n\n    _removeMissingEntries: function(entries) {\n      var self = this;\n      _.each(self.data, function(value, key) {\n        if (!entries[key]) {\n          delete self.data[key];\n        }\n      });\n    },\n\n    getTTMenu: function() {\n      var self = this;\n      var goBackLink = $('<a class=\"icon-arrow-up i-button go_back\" href=\"#\"/>')\n        .text($T('Up to timetable'))\n        .click(function() {\n          self.parentTimetable.switchToTopLevel();\n          self._hideWarnings();\n          self.session = null;\n          return false;\n        });\n\n      return $('<div class=\"group right\"/>').append(goBackLink);\n    },\n  },\n  function(\n    parent,\n    data,\n    contextInfo,\n    eventInfo,\n    width,\n    wrappingElement,\n    detailLevel,\n    isSessionTimetable,\n    customLinks,\n    canManageSession,\n    canManageBlocks,\n    canManageContribs\n  ) {\n    this.isSessionTimetable = isSessionTimetable;\n    this.isTopLevel = false;\n    this.ManagementTimeTable(\n      data,\n      contextInfo,\n      eventInfo,\n      width,\n      wrappingElement,\n      detailLevel,\n      customLinks,\n      canManageSession,\n      canManageBlocks,\n      canManageContribs\n    );\n    var managementActions = new IntervalTimeTableManagementActions(\n      this,\n      eventInfo,\n      contextInfo,\n      isSessionTimetable\n    );\n    this.IntervalTimeTableMixin(parent, width, wrappingElement, managementActions, 'proportional');\n\n    this.canvas = Html.div({});\n    this.isPoster = contextInfo.isPoster;\n\n    this.setData = IntervalTimeTableMixin.prototype.setData;\n    this.getById = IntervalTimeTableMixin.prototype.getById;\n    this.postDraw = IntervalTimeTableMixin.prototype.postDraw;\n  }\n);\n\ntype(\n  'SessionDisplayTimeTable',\n  ['TopLevelDisplayTimeTable'],\n  {\n    _functionButtons: function() {\n      var self = this;\n\n      this.TopLevelDisplayTimeTable.prototype._functionButtons.call(this);\n\n      return [this.printButton, this.fullScreenButton, this.filterButton];\n    },\n  },\n  function(data, eventInfo, width, wrappingElement, historyBroker) {\n    this.TopLevelDisplayTimeTable(\n      data,\n      eventInfo,\n      width,\n      wrappingElement,\n      'contribution',\n      historyBroker,\n      null\n    );\n  }\n);\n"},46047:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\nfunction _revert_element(ui) {\n  // make element assume previous size\n  if (ui.originalSize) {\n    ui.helper.height(ui.originalSize.height);\n  }\n}\n\ntype(\n  'TimeDisplacementManager',\n  [],\n  {\n    // some caching, in order to speed up computation\n    _cached_attrs: {\n      gridTop: function() {\n        return $('#timetable_grid').offset().top;\n      },\n      heightLimits: function() {\n        return [$('.hourLine:first').offset().top, $('.hourLine:last').offset().top + 1];\n      },\n    },\n\n    _cache_get: function(param) {\n      if (this._cache[param] === undefined) {\n        this._cache[param] = this._cached_attrs[param]();\n      }\n      return this._cache[param];\n    },\n\n    _cache_set: function(param, val) {\n      this._cache[param] = val;\n    },\n\n    // tooltip (shows time while dragging)\n    _initializeTooltip: function() {\
1n      var tip = $('<div id=\"dragTip\"><span>' + $T('move to resize') + '</span></div>');\n      tip.append($('<div class=\"pointer\"/>'));\n      $('body').append(tip.hide().fadeIn(300));\n    },\n\n    _updateTipPos: function(gridTime, block, type) {\n      var txt = gridTime[0] + ':' + gridTime[1];\n      if (txt != this.txt) {\n        $('#dragTip span').text(txt);\n        this.txt = txt;\n      }\n      $('#dragTip').position({\n        my: 'right center',\n        at: type == 'resize' ? 'left bottom' : 'left top',\n        of: block,\n        offset: '-5 0',\n      });\n    },\n\n    _killTip: function() {\n      $('#dragTip').fadeOut(300, function() {\n        $(this).remove();\n      });\n    },\n\n    _snap: function(block, time) {\n      // snapping blocks to tooltip \"grid\"\n\n      var hourline = $('#hourLine_' + parseFloat(time[0]));\n      var hourHeight = hourline.height();\n\n      var target = Math.floor(hourline.position().top + (time[1] / 60) * hourHeight);\n      // smoothly animate block size\n      block.clearQueue();\n      block.animate(\n        {height: target - block.position().top},\n        {\n          queue: true,\n        }\n      );\n\n      var tip = $('#dragTip');\n      tip.css({top: this._cache_get('gridTop') + target - tip.outerHeight() / 2});\n    },\n\n    release: function(startHour, startMinute, endHour, endMinute, eventData, ui, undo_caption) {\n      var self = this;\n      var startDT = !startHour\n        ? eventData.startDate\n        : {date: eventData.startDate.date, time: startHour + ':' + startMinute};\n      var endDT;\n\n      if (!endHour) {\n        endDT = new Date(Util.dateTimeIndicoToJS(startDT).getTime() + eventData.duration * 60000);\n      } else {\n        endDT = {date: eventData.endDate.date, time: endHour + ':' + endMinute};\n      }\n\n      self.managementActions\n        .editEntryStartEndDate(\n          Util.formatDateTime(startDT, IndicoDateTimeFormats.Server),\n          Util.formatDateTime(endDT, IndicoDateTimeFormats.Server),\n          eventData,\n          $(window).data('shiftIsPressed'),\n          undo_caption\n        )\n        .fail(function() {\n          // if something goes wrong, reset the element\n          _revert_element(ui);\n        });\n\n      self._cache = {};\n    },\n\n    requestNewHourLine: function() {\n      var first = this._grid.first()[0];\n      if (first > 0) {\n        this._addHourLine('up', first - 1);\n        // if we've just added the midnight line, hide the button\n        return first > 1;\n      }\n    },\n\n    _addHourLine: function(direction, hour) {\n      var last = this._grid.last();\n      var first = this._grid.first();\n      var last_hourline = $('#hourLine_' + last[0]);\n      var first_hourline = $('#hourLine_' + first[0]);\n\n      if (direction == 'down') {\n        var height = $('#hourLine_' + ((last[0] == 0 ? 24 : last[0]) - 1)).height();\n        var top = last[1] + height;\n        this._grid.push([hour, top]);\n        var width = last_hourline.width();\n      } else {\n        var top = first[0];\n        var height = $('#hourLine_' + first[0]).height();\n\n        this._grid.each(function(tuple) {\n          tuple[1] += height;\n        });\n        $('.hourLine, .timetableBlock').css('top', function(i, val) {\n          return parseFloat(val) + height;\n        });\n\n        this._grid.unshift([hour, 0]);\n        var width = first_hourline.width();\n      }\n\n      var hourline = $('<div class=\"hourLine\"/>')\n        .attr('id', 'hourLine_' + hour)\n        .css({top: top + 'px', height: height, width: width})\n        .text(zeropad(hour) + ':00');\n\n      if (direction == 'down') {\n        $('#timetable_grid').append(hourline);\n        last_hourline.height(height);\n      } else {\n        $('#timetable_grid').prepend(hourline);\n      }\n\n      $('#timetable_canvas').height($('#timetable_canvas').height() + height);\n      $('#timetable').height($('#timetable_canvas').height() + height);\n\n      var ttdrawer = this._timetable.getTimetableDrawer();\n      ttdrawer.make_droppable(hourline, hour);\n\n      return height;\n    },\n\n    _hourLineNow: function(elem, type) {\n      type = type || 'resize';\n\n      var height = elem.height();\n      var newPos = elem.offset().top + (type == 'drag' ? 0 : height);\n      var otherTip = newPos + (type == 'drag' ? height : height);
1\n\n      var curBestDiff = Number.MAX_VALUE;\n      var curHour = null;\n      var parentPos = this._cache_get('gridTop');\n      var hl = this._cache_get('heightLimits'); //[minHeight, maxHeight]\n      var last = this._grid.last();\n      var first = this._grid.first();\n\n      if (otherTip > hl[1]) {\n        if (last[0] != 0) {\n          var height = this._addHourLine('down', (last[0] + 1) % 24);\n          // recalculate [minHeight, maxHeight] next time\n          this._cache_set('heightLimits'); // set as dirty\n\n          if (type == 'drag') {\n            elem.data('ui-draggable')._setContainment();\n          } else {\n            elem.data('ui-resizable').resetContainment();\n          }\n        }\n      } else if (newPos > hl[1]) {\n        return last;\n      } else if (newPos <= hl[0]) {\n        return first;\n      }\n\n      // find the hour line that we are currently on\n      // (the one that is before the pointer and closest to it)\n      this._grid.each(function(tuple) {\n        // tuple = [hour, px]\n        // block abs pos - grid abs offs - line rel offs\n        var diff = newPos - parentPos - tuple[1];\n\n        if (diff > 0 && diff < curBestDiff) {\n          curBestDiff = diff;\n          curHour = tuple;\n        }\n      });\n      return curHour;\n    },\n\n    round5: function(hour, minute) {\n      // closest minute\n      var closest = Math.floor((minute + 2.5) / 5) * 5;\n      if (closest > 59) {\n        return [zeropad((hour + 1) % 24), zeropad(0)];\n      } else {\n        return [zeropad(hour), zeropad(closest)];\n      }\n    },\n\n    /* This function determines what hour + minute the time block\n     * should start at for the current dragging pixel position in the timetable.\n     */\n    _updateTime: function(elem, type) {\n      // type = 'resize' | 'drag'\n\n      var hour_line = this._hourLineNow(elem, type);\n\n      if (hour_line == this._grid.last()) {\n        // if we are past the last hour line, just return it\n        var gridTime = [zeropad(this._grid.last()[0]), '00'];\n      } else {\n        var blockPosTop = elem.position().top;\n        var reference = type == 'drag' ? blockPosTop : blockPosTop + elem.height();\n\n        var gridTime = this.round5(\n          hour_line[0],\n          this._calculateRelativeMinuteOffset(reference, hour_line)\n        );\n      }\n\n      this._updateTipPos(gridTime, elem, type);\n      if (type == 'resize') {\n        this._snap(elem, gridTime, type);\n      }\n\n      return gridTime;\n    },\n\n    _calculateRelativeMinuteOffset: function(position, hour_line) {\n      var hour = hour_line[0];\n      var hourTop = hour_line[1];\n      var hourHeight = $('#hourLine_' + hour).height();\n      var offsetHeight = position - hourTop;\n      var minute = Math.floor((offsetHeight / hourHeight) * 59);\n\n      return minute > 60 || minute < 0 ? null : minute;\n    },\n\n    _withNoEvents: function(func) {\n      var ttdrawer = this._timetable.getTimetableDrawer();\n\n      ttdrawer.toggleEvents(false);\n      func();\n\n      _.defer(function() {\n        ttdrawer.toggleEvents(true);\n      });\n    },\n  },\n  function(timetable) {\n    this._timetable = timetable;\n    this._cache = {};\n  }\n);\n\ntype('DraggableBlockMixin', [], {\n  _makeDraggable: function() {\n    var self = this;\n    var originalWidth = this.element.width();\n    var originalHeight = this.element.height();\n\n    /* Resize timeblock if nedeed */\n    var maxCol = self.timetable.getTimetableDrawer().maxCol;\n    var newWidth = Math.round($('#timetable').width() / maxCol);\n\n    var heightThreshold = 450;\n    var heightChange = originalHeight > heightThreshold;\n    var newHeight = heightChange ? heightThreshold : originalHeight;\n\n    var widthChange = !(newWidth == originalWidth);\n    var hourLine_mode = false;\n\n    var draggable = this.element.super_draggable({\n      //Center the mouse in the middle of the block while dragging\n      containment: $('#timetable'),\n      revert: 'invalid',\n      refreshPositions: true,\n      start: function(event, ui) {\n        /* Close and destroy balloon so it will be updated. For an unknown reason, if the\n         * qTip is not disabled first it is re-opened when the drag action ends. */\n        var $qbubble = self.element.find('[data-hasqtip]');\n        if ($qbubble.length) {\n          $qbubble.qbubble('destroy');\n          $qbubble.qbubble('api').disable();\n        }\n\n        /* Resize timeblock if nedeed */\n        maxCol = self.timetable.getTimetableDrawer().maxCol;\n        newWidth = maxCol > 1 ? Math.round($('#timetable').width() / maxCol) : originalWidth;\n\n        ui.helper.animate({width: newWidth});\n        ui.helper.height(newHeight);\n\n        $(this).data('ui-draggable')._setContainment(newWidth, newHeight);\n\n        self._initializeTooltip();\n\n        var pos = ui.helper.position();\n        draggable.data('initialPosition', pos);\n      },\n      drag: function(event, ui) {\n        if (hourLine_mode && $(this).position().left > 0) {\n          hourLine_mode = false;\n          $('.ui-droppable').super_droppable('enable');\n        }\n        if ($(this).position().left == 0) {\n          $('.hourLine.ui-droppable').super_droppable('enable');\n          $('.timetableBlock.ui-droppable').super_droppable('disable');\n          hourLine_mode = true;\n        }\n      },\n      stop: function(event, ui) {\n        //reset original width\n        if (widthChange || heightChange) {\n          ui.helper.animate({\n            width: originalWidth,\n            height: originalHeight,\n          });\n        }\n        self._killTip();\n      },\n    });\n\n    draggable.data('eventData', this.eventData);\n  },\n});\n\ntype('ResizableBlockMixin', [], {\n  _makeResizable: function() {\
1n    var self = this;\n\n    this.resizable = this.element.reset_resizable({\n      // properties\n      ignoreShift: true,\n      containment: $('#timetable_canvas'),\n      maxWidth: this.element.width(),\n      minWidth: this.element.width(),\n\n      // events\n      start: function(event, ui) {\n        // when resizing starts, initialize the tooltip if it still\n        // does not exist\n        if (!$('#dragTip').length) {\n          self._initializeTooltip(ui.helper, function() {\n            self._updateTime(ui.helper, 'resize');\n          });\n        } else {\n          $('#dragTip').fadeIn(300);\n        }\n      },\n      resize: function(event, ui) {\n        // as the cursor is being dragged, update the time in the\n        // tooltip. also store it for later\n\n        var hours = self._updateTime(ui.helper, 'resize');\n        if (hours) {\n          $(ui.helper).data('endHour', hours[0]);\n          $(ui.helper).data('endMinute', hours[1]);\n        }\n      },\n      stop: function(event, ui) {\n        // the mouse was released, ask for the resize to be performed\n        // and kill the tooltip\n        ui.helper.stop(true);\n        var data = $(ui.helper).data();\n\n        self._withNoEvents(function() {\n          self.release(null, null, data.endHour, data.endMinute, self.eventData, ui, 'resize');\n        });\n\n        self._killTip();\n      },\n    });\n  },\n});\n\ntype(\n  'DroppableTimetableMixin',\n  ['TimeDisplacementManager'],\n  {\n    _shiftKeyListener: function() {\n      var indicatorDiv = $('.bottomTip');\n      var self = this;\n\n      //If its not already drawn/appended\n      if (!(indicatorDiv.length > 0)) {\n        indicatorDiv = $('<div class=\"bottomTip\"/>')\n          .html(\n            $T(\n              '<kbd class=\"tt-key\">Shift</kbd> is currently pressed. Changes will be applied to blocks after.'\n            )\n          )\n          .appendTo('body');\n      }\n\n      // Keyboard Key \"Shift\" pressed > listener for shifting while dragging blocks\n      $(document)\n        .keydown(function(e) {\n          // if Shift is currently pressed\n          if (\n            self._timetable.isTopLevel &&\n            self._timetable.isSessionTimetable &&\n            !self._timetable.canManageBlocks\n          ) {\n            return;\n          }\n\n          if (e.keyCode == '16') {\n            $(window).data('shiftIsPressed', true);\n            indicatorDiv.fadeIn('fast');\n          }\n        })\n        .keyup(function(e) {\n          if (e.keyCode == '16') {\n            $(window).data('shiftIsPressed', false);\n            indicatorDiv.fadeOut('fast');\n          }\n        })\n        .blur(function() {\n          $(window).data('shiftIsPressed', false);\n          indicatorDiv.fadeOut('fast');\n        });\n\n      $(window).data('shiftIsPressed', false); //default value is false\n    },\n\n    make_droppable: function(element, hour) {\n      //\"unique\" name for each drag-handler per hourLine\n      var thisDragSpaceName = 'drag.' + hour;\n      var minute = null;\n      var self = this;\n\n      element.droppable({\n        drop: function(event, ui) {\n          // execute making sure click events on blocks are disabled\n          self._withNoEvents(function() {\n            // Prevent firing the same request many times\n            // It may happen for huge blocks that span multiple hourLines\n            if (event.target.id === 'hourLine_' + hour.toString().replace(/^0/, '')) {\n              self.release(\n                hour,\n                minute,\n                null,\n                null,\n                $(ui.draggable).data('eventData'),\n                ui.helper,\n                'placementChange'\n              );\n            }\n          });\n        },\n        tolerance: 'touch',\n        accept: '.ui-draggable.timetableBlock',\n        over: function(event, ui) {\n          $('#timetable').on(\n            thisDragSpaceName,\n            '.ui-draggable.timetableBlock',\n            function(event, ui) {\n              if (element.droppable('option', 'disabled')) {\n                return;\n              }\n              var gridTime = self._updateTime(ui.helper, 'drag');\n              //null means that the drag is not allowed\n              if (gridTime == null) {\n                return;\n              }\n              hour = gridTime[0];\n              minute = gridTime[1];\n            }\n          );\n        }
1,\n        out: function(event, ui) {\n          $('#timetable').off(thisDragSpaceName, '.ui-draggable.timetableBlock');\n        },\n      });\n    },\n  },\n  function(timetable) {\n    this.TimeDisplacementManager(this.timetable);\n    if (this.managementMode) {\n      this._shiftKeyListener.call(this);\n    }\n    this._grid = _(this.grid);\n  }\n);\n\ntype('DroppableBlockMixin', [], {\n  _makeDroppable: function() {\n    if (this.eventData.entryType != 'Session') {\n      return;\n    }\n\n    var self = this;\n\n    function isSession(ui) {\n      return $(ui.draggable).hasClass('timetableSession');\n    }\n\n    function isTouchingWall(ui) {\n      return $(ui.draggable).position().left == 0;\n    }\n\n    function removeBottomMove(ui) {\n      $('#tt_bottom_move').fadeOut(500, function() {\n        $(this).remove();\n      });\n    }\n\n    var inside = false;\n\n    this.element.super_droppable({\n      drop: function(event, ui) {\n        $('#dragTip').remove();\n\n        //If a session is dropped on top do nothing.\n        if (isSession(ui) || isTouchingWall(ui)) {\n          return;\n        }\n\n        var blockEventData = $(ui.draggable).data('eventData');\n        var chosenValue = {parent_id: self.eventData.scheduleEntryId};\n        var initialPosition = $(ui.draggable).data('initialPosition');\n\n        removeBottomMove();\n\n        self._withNoEvents(function() {\n          self.managementActions.moveToSession(blockEventData, chosenValue, 'drop');\n        });\n        return;\n      },\n      greedy: true,\n      tolerance: 'pointer',\n      activeClass: 'ui-droppable-active',\n      over: function(event, ui) {\n        /* If you touch the wall while also\n         * over a session - show that session with the style of\n         * \"not current drop target\"\n         */\n\n        if (isSession(ui) || isTouchingWall(ui)) {\n          return;\n        }\n\n        if (!inside) {\n          var newWidth = $(this).width() * 0.75,\n            newHeight = $(this).height() * 0.75;\n          ui.draggable.data('draggingWidth', ui.draggable.width());\n          ui.draggable.data('draggingHeight', ui.draggable.height());\n\n          ui.draggable.animate({width: newWidth});\n          ui.draggable.height({height: newHeight});\n\n          ui.draggable.data('ui-draggable')._setContainment(newWidth, newHeight);\n\n          $('.timetableBlock.ui-droppable').not(this).super_droppable('disable');\n          $('.hourLine.ui-droppable').droppable('disable');\n          $('#dragTip').hide();\n          inside = true;\n\n          if (!$('#tt_bottom_move').length) {\n            $('<div class=\"bottomTip\" id=\"tt_bottom_move\"/>')\n              .html('<div class=\"circle\"></div>' + $T('Drop to move block inside session'))\n              .appendTo('body')\n              .fadeIn();\n          } else {\n            $('#tt_bottom_move').stop(true).fadeTo(500, 1);\n          }\n        }\n      },\n      out: function(event, ui) {\n        inside = false;\n        if (isSession(ui)) {\n          return;\n        }\n\n        ui.draggable.width(ui.draggable.data('draggingWidth'));\n        ui.draggable.height(ui.draggable.data('draggingHeight'));\n\n        $('#dragTip').show();\n        $('.timetableBlock.ui-droppable').not(this).super_droppable('enable');\n        $('.hourLine.ui-droppable').droppable('enable');\n\n        removeBottomMove();\n      },\n    });\n  },\n});\n\ntype(\n  'DragAndDropBlockMixin',\n  ['DroppableBlockMixin', 'ResizableBlockMixin', 'DraggableBlockMixin', 'TimeDisplacementManager'],\n  {\n    _postDraw: function() {\n      var entryType = this.eventData.entryType;\n      if (\n        !this.timetable.isSessionTimetable ||\n        entryType != 'Session' ||\n        this.timetable.canManageBlocks\n      ) {\n        this._makeResizable();\n        this._makeDraggable();\n        this._makeDroppable();\n      }\n    },\n  },\n\n  function() {\n    this.TimeDisplacementManager(this.timetable);\n    this.element = $(this.block.dom);\n    this._grid = _(this.timetable.getTimetableDrawer().grid);\n  }\n);\n\nvar activeTT;\n\nfunction switchTT(event, tt) {\n  // set the current timetable as active, so that the button only acts on it\n  activeTT = tt;\n  var tdrawer = tt.getTimetableDrawer();\n  if (!tdrawer.isPoster) {\n    if (tdrawer._grid.first()[0] != 0) {\n      // we start at some other hour before midnight, show the button\n      $('#tt_hour_tip').fadeIn();\n    } else {\n      $('#tt_hour_tip').fadeOut();\n    }\n  }\n}
1\n\n$(function() {\n  // bind several timetable events to the switchTT function (timetable transitions)\n  $('body').bind('timetable_ready', function(event, tt) {\n    $('#tt_menu').css('width', tt.width);\n\n    switchTT(event, tt);\n    $('#tt_hour_tip').unbind();\n    $('#tt_hour_tip').click(function(evt) {\n      if (!activeTT.getTimetableDrawer().requestNewHourLine()) {\n        $(this).fadeOut();\n      }\n    });\n  }); // postDraw\n\n  $('body').one('timetable_ready', function() {\n    var closeMenu = function() {\n      var dropdown = $('#tt_menu .group');\n      if (dropdown) {\n        try {\n          dropdown.dropdown('close');\n        } catch (e) {}\n      }\n    };\n\n    // initialize sticky headers\n    $.ui.sticky({\n      sticky: closeMenu,\n      normal: closeMenu,\n    });\n  });\n\n  $('body').bind('timetable_switch_toplevel', switchTT); // switch to top level\n  $('body').bind('timetable_switch_interval', switchTT); // switch to interval\n  $('body').bind('timetable_update', switchTT); // changes in local timetable\n});\n"},31719:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n/* eslint-disable import/unambiguous, object-shorthand */\n\ntype(\n  'TimetableBlockBase',\n  [],\n  {\n    openPopup: function(event) {\n      var self = this;\n      self.popupActive = true;\n      self._drawPopup(event);\n    },\n\n    createMaterialMenu: function(attachments, triggerElement, closeHandler) {\n      var menuItems = {},\n        sections = {'': {content: menuItems, description: ''}};\n\n      $.each(attachments.files, function(i, attachment) {\n        menuItems['material' + attachment.download_url] = {\n          action: attachment.download_url,\n          display: attachment.title,\n          description: attachment.description,\n        };\n      });\n\n      $.each(attachments.folders, function(i, folder) {\n        var sectionItems = {},\n          section = folder.title;\n\n        $.each(folder.attachments, function(i, attachment) {\n          sectionItems['material' + attachment.download_url] = {\n            action: attachment.download_url,\n            display: attachment.title,\n            description: attachment.description,\n          };\n        });\n        sections[section] = {content: sectionItems, description: folder.description};\n      });\n      return new SectionPopupMenu(sections, [triggerElement], null, null, true, closeHandler);\n    },\n\n    getMaterialMenu: function(attachments) {\n      var root = $('<ul class=\"material_list\"/>');\n\n      each(attachments.files, function(attachment) {\n        var resource_html = $('<li/>').append(\n          $('<a/>').attr('href', attachment.download_url).text(attachment.title)\n        );\n        root.append(resource_html);\n      });\n\n      each(attachments.folders, function(folder) {\n        var resources = $('<ul class=\"resource_list\"/>');\n        each(folder.attachments, function(attachment) {\n          var resource_html = $('<li/>').append(\n            $('<a/>').attr('href', attachment.download_url).text(attachment.title)\n          );\n          resources.append(resource_html);\n        });\n        var material_html = $('<li/>').append($('<h3/>').append(folder.title), resources);\n        root.append(material_html);\n      });\n      return root;\n    },\n\n    createMaterialButton: function(attachments) {\n      var self = this;\n\n      var button = Html.a('i-link entry-attachments');\n      var $button = $(button.dom);\n      $button\n        .qtip({\n          content: {\n            text: self.getMaterialMenu(attachments),\n          },\n          show: {\n            event: false,\n          },\n          hide: {\n            event: 'unfocus',\n          },\n          events: {\n            show: function() {\n              $button.addClass('open').trigger('indico:closeAutoTooltip');\n            }
1,\n            hide: function() {\n              $button.removeClass('open');\n            },\n          },\n          position: {\n            my: 'top right',\n            at: 'bottom left',\n          },\n          style: {\n            classes: 'material_tip',\n          },\n          suppress: false,\n        })\n        .on('click', function(evt) {\n          evt.stopPropagation();\n          $(this).qtip('show');\n        })\n        .attr('title', $T.gettext('Show materials'))\n        .data('qtip-position', 'right');\n\n      return button;\n    },\n\n    _getRightSideDecorators: function() {\n      return Html.span({});\n    },\n\n    _formatConveners: function(conveners) {\n      if (conveners) {\n        return _.map(_.sortBy(conveners, _.property('displayOrderKey')), _.property('name')).join(\n          ', '\n        );\n      } else {\n        return '';\n      }\n    },\n  },\n  function(timetable) {\n    this.timetable = timetable;\n    this.popupActive = false;\n    this.popupAllowClose = true;\n  }\n);\n\ntype(\n  'TimetableBlockNormal',\n  ['TimetableBlockBase'],\n  {\n    _getTitle: function() {\n      var title = this.eventData.title;\n\n      if (this.eventData.slotTitle && this.eventData.slotTitle !== '') {\n        title += ': ' + this.eventData.slotTitle;\n      }\n      if (this.eventData.code) {\n        title += ` (${this.eventData.code})`;\n      }\n      return title;\n    },\n\n    _blockDescription: function(block, event) {\n      var self = this;\n\n      this.titleDiv = Html.div(\n        {className: 'timetableBlockTitle', style: {fontWeight: this.eventData.fontWeight}},\n        this._getTitle()\n      );\n\n      this.headerBlock = Html.div({className: 'timetableBlockWrapper'}, this.titleDiv);\n      this.footerBlock = Html.div({className: 'timetableBlockWrapper', style: {marginTop: 'auto'}});\n\n      this.div = Html.div(\n        {className: 'entry-content', style: {width: '100%', height: '100%'}},\n        this.headerBlock\n      );\n\n      if (this.compactMode) {\n        this.timeDiv = Html.div(\n          'timetableBlockTimeDiscreet',\n          this.eventData.startDate.time.substring(0, 5) +\n            ' - ' +\n            this.eventData.endDate.time.substring(0, 5)\n        );\n        this.div.insert(this.timeDiv);\n      } else {\n        if (this.eventData.startDate.time == this.eventData.endDate.time) {\n          this.timeDiv = Html.div(\n            {className: 'timetableBlockTime'},\n            this.eventData.startDate.time.substring(0, 5)\n          );\n        } else {\n          this.timeDiv = Html.div(\n            {className: 'timetableBlockTime'},\n            this.eventData.startDate.time.substring(0, 5) +\n              ' - ' +\n              this.eventData.endDate.time.substring(0, 5)\n          );\n        }\n\n        this.locationDiv = Html.div('timetableBlockLocation');\n        var addComma = false;\n        if (\n          this.eventData.room &&\n          this.timetable.getTimetableDrawer().layoutChooser.get().shouldShowRoom()\n        ) {\n          this.locationDiv.append(this.eventData.room);\n          addComma = true;\n        }\n        if (this.eventData.location) {\n          this.locationDiv.append(addComma ? ', ' : '');\n          this.locationDiv.append(this.eventData.location);\n        }\n\n        // If it's a contribution add the speakers information\n        if (self.eventData.presenters && self.eventData.presenters.length > 0) {\n          var firstPresenter = self.eventData.presenters[0];\n          this.presentersDiv = Html.div({className: 'timetableBlockPresenters'});\n          this.presentersDiv.append(firstPresenter.name);\n          if (self.eventData.presenters.length > 1) {\n            this.presentersDiv.append(' ' + $T('et al.'));\n          }\n          this.headerBlock.insert(this.presentersDiv);\n        }\n\n        // Add material menu\n        if (\n          !self.managementActions &&\n          self.eventData.attachments &&\n          self.eventData.attachments.files\n        ) {\n          this.headerBlock.insert(this.createMaterialButton(this.eventData.attachments));\n        }\n\n        //this.titleWrapper.insert(this.createManageButton());\n\n        //Add converner's info\n        this.convenerDiv = Html.div(\n          'timetableBlockConvener',\n          self._formatConveners(this.eventData.conveners)\n        );\n\n        if (this.eventData.conveners) {\n          this.headerBlock.append(this.convenerDiv);\n        }\n        this.footerBlock.append(this.locationDiv);\n        this.footerBlock.append(this.timeDiv);\n        this.div.append(this.footerBlock);\n      }\n\n      return this.div;\n    }
1,\n\n    redraw: function() {\n      this.block.clear();\n      this.block.set(this._blockDescription());\n    },\n\n    draw: function(leftPos, width) {\n      var self = this;\n\n      this.leftPos = leftPos;\n      this.width = width;\n      // the block height minus an extra padding to prevent nodes from being too close\n      this.height = this.blockData.end - this.blockData.start - 2;\n      this.topPos = this.blockData.start;\n\n      //To disable dragging while outside management mode\n      var blockIsDraggable = this.detailLevel != 'session' && this.detailLevel != 'contribution';\n\n      var additionalClasses = 'ui-draggable ui-resizable';\n\n      var contributionClass = blockIsDraggable\n        ? 'timetableContribution ' + additionalClasses\n        : 'timetableContribution';\n      var breakClass = blockIsDraggable ? 'timetableBreak ' + additionalClasses : 'timetableBreak';\n      var sessionClass = blockIsDraggable\n        ? 'timetableSession ' + additionalClasses\n        : 'timetableSession';\n      var classTable = {\n        Session: sessionClass,\n        Contribution: contributionClass,\n        Break: breakClass,\n      };\n\n      $(this.block.dom).css({\n        position: 'absolute',\n        top: pixels(this.topPos),\n        height: pixels(this.height),\n        'background-color': this.eventData.color,\n        color: this.eventData.textColor,\n        left: pixels(this.leftPos),\n        width: pixels(this.width - 3),\n        'border-bottom-style': this.blockData.unfinished ? 'dashed' : '',\n      });\n\n      $(this.block.dom).addClass('timetableBlock ' + classTable[this.eventData.entryType]);\n\n      this.block.set(this._getRightSideDecorators(), this._blockDescription());\n\n      // This is a special case, when users shows contribution details it doesn't\n      // apply to poster sessions. Instead add some graphical elements to indicate\n      // that this sessions contains several contributions.\n      if (this.detailLevel == 'contribution' && this.eventData.isPoster && this.height > 30) {\n        var pileDiv = this.createPileEffect();\n        pileDiv.dom.onmouseover = function() {};\n        var numContribs = 0;\n        each(this.eventData.entries, function(value, key) {\n          if (value.entryType == 'Contribution') {\n            numContribs++;\n          }\n        });\n        pileDiv.dom.onmouseover = function(event) {\n          if (self.popupActive) {\n            return;\n          }\n          IndicoUI.Widgets.Generic.tooltip(\n            pileDiv.dom,\n            event,\n            \"<div style='padding:3px'>\" +\n              $T('This poster session has ') +\n              numContribs +\n              $T(' contribution(s).') +\n              '<br / >' +\n              $T('Please click for more information.') +\n              '</div>'\n          );\n        };\n      }\n\n      if (!self.printableVersion) {\n        $(this.block.dom).on('click', '.entry-content', function(e) {\n          if (!self.timetable.getTimetableDrawer().eventsDisabled) {\n            $(this).trigger('tt_block.balloon', e);\n          }\n        });\n        highlightWithMouse(this.div, this.block);\n      }\n      return this.block;\n    },\n\n    _postDraw: function() {},\n\n    postDraw: function(hook) {\n      const parentDivHeight = this.div.dom.parentNode.offsetHeight;\n      const contentHeight = () =>\n        Array.from(this.div.dom.childNodes).reduce((acc, x) => acc + x.offsetHeight, 0);\n\n      // If nothing has been drawn do nothing\n      if (!parentDivHeight) {\n        return;\n      }\n\n      // If the content (header + footer) is bigger than the block, we'll start by reducing the footer\n      if (contentHeight() > parentDivHeight - 8) {\n        this.footerBlock.dom.style.whiteSpace = 'nowrap';\n      }\n\n      // If the content is bigger than the block, we'll remove line wrappings first\n      // 8 is equivalent to 2x the block padding\n      if (this.headerBlock.dom.offsetHeight > parentDivHeight - 8) {\n        this.headerBlock.dom.style.whiteSpace = 'nowrap';\n      }\n\n      // If the content still doesn't fit, we'll fit everything in one line\n      if (this.headerBlock.dom.offsetHeight > parentDivHeight - 8) {\n        this.headerBlock.dom.style.flexWrap = 'nowrap';\n      }\n\n      this._postDraw();\n      return null;\n    }
1,\n    createPileEffect: function() {\n      var self = this;\n      var pileEffect = Html.div(\n        {style: {position: 'absolute', top: '3px', left: '0', width: pixels(this.width - 3)}},\n        Html.div(\n          {},\n          Html.div('timetableBlock timetableBlockPileEffect'),\n          Html.div('timetableBlock timetableBlockPileEffect'),\n          Html.div('timetableBlock timetableBlockPileEffect')\n        )\n      );\n\n      this.div.insert(pileEffect);\n      this.headerBlock.dom.style.paddingTop = '15px';\n      if (this.compactMode) {\n        this.timeDiv.dom.style.paddingTop = '15px';\n      }\n\n      return pileEffect;\n    },\n    setColors: function(textColor, bgColor) {\n      this.block.dom.style.backgroundColor = bgColor;\n      this.block.dom.style.color = textColor;\n    },\n  },\n  function(timetable, eventData, blockData, compactMode, printableVersion, detailLevel) {\n    this.TimetableBlockBase(timetable);\n    this.compactMode = compactMode;\n    this.eventData = eventData;\n    this.blockData = blockData;\n    this.margin = TimetableDefaults.blockMargin;\n    this.printableVersion = printableVersion;\n    this.detailLevel = detailLevel;\n    this.arrows = Html.span({});\n    this.block = Html.div({});\n\n    var self = this;\n    $(this.block.dom).bind('tt_block.balloon', function(event, originalEvent) {\n      if (!self.popupActive) {\n        self.openPopup(originalEvent);\n      }\n    });\n  }\n);\n\ntype(\n  'TimetableBlockWholeDayBase',\n  ['TimetableBlockBase'],\n  {\n    _blockDescription: function(block, event) {\n      var self = this;\n\n      this.titleDiv = Html.div(\n        {className: 'timetableBlockTitle', style: {fontWeight: this.eventData.fontWeight}},\n        this.eventData.title\n      );\n      this.headerBlock = Html.div({}, this._getRightSideDecorators(), this.titleDiv);\n\n      this.div = Html.div({style: {width: '100%', height: '100%'}}, this.headerBlock);\n\n      this.timeDiv = Html.div(\n        'timetableBlockTimeDiscreet',\n        this.eventData.startDate.time.substring(0, 5) +\n          ' - ' +\n          this.eventData.endDate.time.substring(0, 5)\n      );\n      this.div.insert(this.timeDiv);\n\n      // Add material menu\n      if (self.eventData.attachments && self.eventData.attachments.files) {\n        this.headerBlock.insert(this.createMaterialButton(this.eventData.attachments));\n      }\n\n      return this.div;\n    },\n\n    draw: function() {\n      var self = this;\n\n      var classTable = {\n        Session: 'timetableSession',\n        Contribution: 'timetableContribution ',\n        Break: 'timetableBreak ',\n      };\n\n      var block = Html.div(\n        {\n          style: {\n            backgroundColor: this.eventData.color,\n            color: this.eventData.textColor,\n            maxHeight: '30px',\n            margin: '2px 0',\n            overflow: 'hidden',\n          },\n          className: 'timetableBlock ' + classTable[this.eventData.entryType],\n        },\n        this._blockDescription()\n      );\n\n      if (!self.printableVersion) {\n        block.dom.style.cursor = 'pointer';\n        block.observeClick(function(e) {\n          self.openPopup(e);\n        });\n        highlightWithMouse(this.div, block);\n      }\n\n      return block;\n    },\n    postDraw: function() {},\n  },\n  function(timetable, eventData, blockData) {\n    this.TimetableBlockBase(timetable);\n\n    this.eventData = eventData;\n    this.blockData = blockData;\n    this.margin = TimetableDefaults.blockMargin;\n    this.arrows = Html.span({});\n  }\n);\n\nfunction loadBalloonContent(self, api, editable) {\n  var entryId = self.eventData.scheduleEntryId\n    ? self.eventData.scheduleEntryId\n    : self.eventData.id.substring(1);\n  var url = editable\n    ? Indico.Urls.Timetable.entries.info.manage\n    : Indico.Urls.Timetable.entries.info.display;\n  var urlParams = {\n    event_id: self.eventData.conferenceId,\n    entry_id: entryId,\n  };\n  if (self.timetable.isSessionTimetable) {\n    urlParams.session_id = self.eventData.sessionId;
1\n    urlParams.is_session_timetable = 1;\n  }\n\n  $.ajax({\n    url: build_url(url, urlParams),\n  }).then(\n    function(content) {\n      // Set the tooltip content upon successful retrieval\n      api.set('content.text', content.html);\n      var $content = api.elements.content;\n\n      if (editable) {\n        var qtipId = $content.closest('.qtip').data('qtip-id');\n        var $balloonQtip = $('[data-hasqtip=' + qtipId + ']');\n        var closeBalloon = false;\n        $content.find('.js-edit-time').ajaxqbubble({\n          url: build_url(Indico.Urls.Timetable.entries.editTime, urlParams),\n          qBubbleOptions: {\n            style: {\n              classes: 'balloon-time-qtip',\n            },\n            position: {\n              at: 'top center',\n              my: 'bottom center',\n            },\n            events: {\n              hidden: function() {\n                if (closeBalloon) {\n                  $balloonQtip.qbubble('hide');\n                }\n              },\n            },\
1n          },\n          qtipConstructor: function(element, qtipOptions) {\n            $balloonQtip.qbubble('createNested', element, qtipOptions);\n          },\n          onClose: function(data) {\n            handleNotifications(data);\n            if (data) {\n              self.timetable._updateDay(data.update);\n              /* Since the entry update creates a new div we can't\n               * easily reposition the balloon on the timetable entry. */\n              closeBalloon = true;\n            }\n          },\n        });\n\n        $content\n          .on('indico:confirmed', '.js-delete', function(evt) {\n            evt.preventDefault();\n            self.managementActions.deleteEntry(self.eventData);\n          })\n          .on('click', '.js-clone', function(evt) {\n            evt.preventDefault();\n            hideBalloon();\n          })\n          .on('indico:confirmed', '.js-clone', function(evt) {\n            evt.preventDefault();\n\n            var cloneUrlParams = $.extend({}, urlParams, {\n              contrib_id: self.eventData.contributionId,\n            });\n\n            $.ajax({\n              url: build_url(Indico.Urls.Timetable.contributions.clone, cloneUrlParams),\n              method: 'POST',\n              dataType: 'json',\n              contentType: 'application/json',\n              complete: IndicoUI.Dialogs.Util.progress(),\n              error: handleAjaxError,\n              success: function(data) {\n                if (data) {\n                  self.timetable._updateDay(data.update);\n                }\n              },\n            });\n          })\n          .on('ajaxDialog:closed', '.js-manage-attachments, .js-manage-subcontribs', function(e) {\n            api.set('content.text', function(evt, api) {\n              loadBalloonContent(self, api, editable);\n            });\n          })\n          .on('ajaxDialog:loadError', '.js-move, .js-edit', function(evt, xhr) {\n            if (xhr.status == 404) {\n              evt.preventDefault();\n              handleErrorResponse(xhr);\n            }\n          });\n\n        $content.find('.js-switch-to-interval').on('click', function() {\n          self.managementActions.switchToIntervalTimetable(self.eventData.id);\n        });\n\n        $content.find('.js-edit').on('click', function() {\n          var extraParams = $(this).data('extra-params');\n          ajaxDialog({\n            trigger: this,\n            url: build_url(\n              Indico.Urls.Timetable.entries.edit,\n              $.extend({}, urlParams, extraParams)\n            ),\n            title: $(this).data('title'),\n            onClose: function(data) {\n              if (data) {\n                handleNotifications(data);\n                self.timetable._updateDay(data.update);\n              }\n            },\n          });\n        });\n\n        $content.find('.js-move').on('click', function() {\n          var urlArgs = {\n            event_id: self.eventData.conferenceId,\n            entry_id: self.eventData.scheduleEntryId,\n          };\n          if (self.timetable.isSessionTimetable) {\n            urlArgs.session_id = self.timetable.contextInfo.sessionId;\n          }\n          ajaxDialog({\n            url: build_url(Indico.Urls.Timetable.entries.move, urlArgs),\n            data: {day: self.timetable.currentDay},\n            title: $T.gettext('Move entry'),\n            subtitle: $(this).data('subtitle'),\n            trigger: this,\n            onClose: function(data) {\n              if (data) {\n                self.managementActions.moveToSession(self.eventData, data, true, null);\n              }\n            },\n          });\n        });\n\n        var $picker = $content.find('.palette-picker');\n        $picker.palettepicker({\n          availableColors: $picker.data('palette'),\n          selectedColor: $picker.data('initial-color'),\n          onSelect: function(background, text) {\n            $.ajax({\n              url: $picker.data('href'),\n              method: $picker.data('method'),\n              data: JSON.stringify({colors: {text: text, background: background}}),\n              dataType: 'json',\n              contentType: 'application/json',\n              complete: IndicoUI.Dialogs.Util.progress(),\n              error: handleAjaxError,\n              success: function(data) {\
1n                self.managementActions._addEntries(data.entries);\n              },\n            });\n          },\n          qtipConstructor: function(element, qtipOptions) {\n            var qtipId = $picker.closest('.qtip').data('qtip-id');\n            $('[data-hasqtip=' + qtipId + ']').qbubble('createNested', element, qtipOptions);\n          },\n        });\n      }\n      $content.find('.js-hide-balloon').on('click', api.hide.bind(api));\n      // Change the target of the qTip position in order to open it at the mouse position\n      $content.closest('.qtip').trigger('qbubble:ajaxload');\n    },\n    function(xhr, status, error) {\n      if (xhr.status === 404 || xhr.status === 403) {\n        handleErrorResponse(xhr);\n        api.set(\n          'content.text',\n          $T.gettext('This timetable entry does not exist anymore. Please refresh the page.')\n        );\n      }\n    }\n  );\n\n  return $T.gettext('Loading...');\n}\n\nfunction handleErrorResponse(xhr) {\n  cornerMessage({\n    message: $T.gettext('This timetable entry does not exist anymore. Please refresh the page.'),\n    actionLabel: $T.gettext('Refresh'),\n    actionCallback: function() {\n      location.reload();\n    },\n    duration: 10000,\n    class: 'error',\n  });\n}\n\nfunction drawBalloon(self, evt, editable) {\n  var timetableBlock = $(self.div.dom);\n  var entryId = self.eventData.scheduleEntryId\n    ? self.eventData.scheduleEntryId\n    : self.eventData.id.substring(1);\n\n  if ($('html').data('static-site')) {\n    var url;\n    if (self.eventData.entryType == 'Session') {\n      url = build_url(Indico.Urls.Sessions.display_session, {\n        event_id: self.eventData.conferenceId,\n        session_id: self.eventData.sessionId,\n      });\n    } else if (self.eventData.entryType == 'Contribution') {\n      url = build_url(Indico.Urls.Contributions.display_contribution, {\n        event_id: self.eventData.conferenceId,\n        contrib_id: self.eventData.contributionId,\n      });\n    } else {\n      return;\n    }\n    location.href = url;\n  } else {\n    var isBreak = self.eventData.entryType == 'Break';\n\n    timetableBlock.qbubble({\n      id: entryId.toString(),\n      content: {\n        text: function(evt, api) {\n          return loadBalloonContent(self, api, editable);\n        },\n      },\n      show: {\n        ready: true,\n      },\n      hide: {\n        event: 'unfocus',\n        fixed: true,\n      },\n      events: {\n        hide: function(evt, api) {\n          if (evt.originalEvent && evt.originalEvent.type === 'mouseleave') {\n            evt.preventDefault();\n          }\n        },\n        render: function(e, api) {\n          api.elements.target.on('click', function() {\n            api.hide();\n          });\n        },\n      },\n      position: {\n        at: 'top center',\n        my: 'bottom center',\n        target: [evt.pageX, evt.pageY],\n        adjust: {\n          mouse: false,\n        },\n        effect: false,\n      },\n      style: {\n        classes:\n          'balloon-qtip ' +\n          (editable ? 'edit-mode' : 'display-mode') +\n          (isBreak ? ' no-details' : ''),\n      },\n    });\n  }\n}\n\ntype('TimetableBlockDisplayMixin', [], {\n  _drawPopup: function(evt) {\n    drawBalloon(this, evt, false);\n  },\n});\n\ntype(\n  'TimetableBlockManagementMixin',\n  ['DragAndDropBlockMixin'],\n  {\n    _drawPopup: function(evt) {\n      drawBalloon(this, evt, true);\n    },\n\n    _getRightSideDecorators: function() {\n      return this.arrows;\n    },\n\n    _drawArrows: function() {\n      var self = this;\n      var arrowUp = $('<a>', {class: 'i-link icon-collapse'});\n      var arrowDown = $('<a>', {class: 'i-link icon-expand'});\n\n      if (self.eventData.canSwapUp) {\n        arrowUp.attr('title', $T.gettext('Move up'));\n        arrowUp.on('click', function() {\n          self.managementActions.swapEntry(self.eventData, 'up');\n        });\n      } else {\n        arrowUp.addClass('disabled');\n      }\n\n      if (self.eventData.canSwapDown) {\n        arrowDown.attr('title', $T.gettext('Move down'));
1\n        arrowDown.on('click', function() {\n          self.managementActions.swapEntry(self.eventData, 'down');\n        });\n      } else {\n        arrowDown.addClass('disabled');\n      }\n\n      if (self.eventData.canSwapDown || self.eventData.canSwapUp) {\n        this.arrows = Html.div({className: 'entry-arrows'}, arrowUp.get(0), arrowDown.get(0));\n      }\n    },\n  },\n  function() {\n    var isParallel = this.timetable.isSessionTimetable\n      ? this.eventData.isParallelInSession\n      : this.eventData.isParallel;\n\n    if (!isParallel && (!this.timetable.isTopLevel || this.timetable.canManageBlocks)) {\n      this._drawArrows();\n    }\n    this.DragAndDropBlockMixin();\n  }\n);\n\ntype(\n  'TimetableBlockWholeDayDisplay',\n  ['TimetableBlockWholeDayBase', 'TimetableBlockDisplayMixin'],\n  {},\n  function(timetable, eventData, blockData) {\n    this.TimetableBlockWholeDayBase(timetable, eventData, blockData);\n  }\n);\n\ntype(\n  'TimetableBlockWholeDayManagement',\n  ['TimetableBlockWholeDayBase', 'TimetableBlockManagementMixin'],\n  {},\n  function(timetable, eventData, blockData, managementActions) {\n    this.TimetableBlockWholeDayBase(timetable, eventData, blockData);\n    this.managementActions = managementActions;\n    this.TimetableBlockManagementMixin();\n\n    this._getRightSideDecorators = TimetableBlockManagementMixin.prototype._getRightSideDecorators;\n    this._postDraw = TimetableBlockManagementMixin.prototype._postDraw;\n  }\n);\n\ntype(\n  'TimetableBlockNormalDisplay',\n  ['TimetableBlockNormal', 'TimetableBlockDisplayMixin'],\n  {},\n  function(timetable, eventData, blockData, compactMode, printableVersion, detailLevel) {\n    this.TimetableBlockNormal(\n      timetable,\n      eventData,\n      blockData,\n      compactMode,\n      printableVersion,\n      detailLevel\n    );\n  }\n);\n\ntype(\n  'TimetableBlockNormalManagement',\n  ['TimetableBlockNormal', 'TimetableBlockManagementMixin'],\n  {\n    _getTitle: function() {\n      var title = this.TimetableBlockNormal.prototype._getTitle.call(this);\n      return this.eventData.friendlyId >= 0 ? this.eventData.friendlyId + ' - ' + title : title;\n    },\n  },\n  function(\n    timetable,\n    eventData,\n    blockData,\n    compactMode,\n    printableVersion,\n    detailLevel,\n    managementActions\n  ) {\n    this.TimetableBlockNormal(\n      timetable,\n      eventData,\n      blockData,\n      compactMode,\n      printableVersion,\n      detailLevel\n    );\n    this.managementActions = managementActions;\n    this.TimetableBlockManagementMixin();\n\n    this._getRightSideDecorators = TimetableBlockManagementMixin.prototype._getRightSideDecorators;\n    this._postDraw = TimetableBlockManagementMixin.prototype._postDraw;\n  }\n);\n\ntype(\n  'TimetableDrawer',\n  ['IWidget', 'DroppableTimetableMixin'],\n  {\n    _minuteDifference: function(time1, time2) {\n      var t1 = parseInt(time1.substring(0, 2), 10) * 60 + parseInt(time1.substring(3, 6), 10);\n      var t2 = parseInt(time2.substring(0, 2), 10) * 60 + parseInt(time2.substring(3, 6), 10);\n\n      return t2 - t1;\n    },\n\n    _drawGrid: function(scale) {\n      var scaleDiv = Html.div({\n        id: 'timetable_grid',\n        style: {\n          position: 'relative',\n          top: pixels(TimetableDefaults.topMargin),\n        },\n      });\n\n      var last = scale[scale.length - 1][0];\n\n      for (var n = 0; n < scale.length; ++n) {\n        var hour = scale[n][0];\n        var px = scale[n][1];\n\n        if (scale[n].length > 2) {\n          scaleDiv.append(\n            Html.div({\n              style: {\n                position: 'absolute',\n                top: pixels(px),\n                width: pixels(this.width),\n                height: hour == last ? '0px' : scale[n + 1][1] - px,\n                borderTop: '1px dotted red',\n                fontSize: '11px',\n              },\n            })\n          );\n          continue;\n        }\n\n        var hourLineDiv = Html.div(\n          {\n            id: 'hourLine_' + parseInt(hour),\n            className: 'hourLine',\n            style: {\
1n              top: pixels(px),\n              width: pixels(this.width),\n              height: hour == last ? '20px' : scale[n + 1][1] - px,\n            },\n          },\n          zeropad(hour) + ':00'\n        );\n\n        this.make_droppable($(hourLineDiv.dom), parseInt(hour));\n\n        scaleDiv.append(hourLineDiv);\n      }\n      return Html.div({}, this.layoutChooser.get().getHeader(this.width), scaleDiv);\n    },\n\n    _drawWholeDayBlocks: function(data, blocks) {\n      var self = this;\n\n      var wholeDayBlockDiv = Html.div({\n        style: {\n          position: 'relative',\n          marginTop: pixels(TimetableDefaults.topMargin),\n          top: pixels(10),\n          marginLeft: pixels(TimetableDefaults.leftMargin),\n          marginRight: pixels(TimetableDefaults.rightMargin),\n        },\n      });\n\n      self.wholeDayBlocks = [];\n\n      var blockAdded = false;\n      each(blocks, function(blockData) {\n        var eventData = data[blockData.id];\n\n        var block;\n        if (self.managementMode) {\n          block = new TimetableBlockWholeDayManagement(\n            self.timetable,\n            eventData,\n            blockData,\n            self.managementActions\n          );\n        } else {\n          block = new TimetableBlockWholeDayDisplay(self.timetable, eventData, blockData);\n        }\n        wholeDayBlockDiv.append(block.draw(0, 100));\n        blockAdded = true;\n      });\n\n      return blockAdded ? wholeDayBlockDiv : Html.div({});\n    },\n\n    _drawBlocks: function(data, blocks, groups) {\n      var self = this;\n      var colN = 0;\n\n      var blockDiv = Html.div({\n        style: {\n          position: 'relative',\n          top: pixels(TimetableDefaults.topMargin),\n        },\n      });\n      this.blocks = [];\n      this._blockMap = {};\n\n      each(blocks, function(blockData) {\n        var nCol = self.layoutChooser.get().getNumColumnsForGroup(groups[blockData.group]);\n        self.maxCol = self.maxCol == null || self.maxCol < nCol ? nCol : self.maxCol;\n\n        var colWidth = Math.floor((self.width - TimetableDefaults.leftMargin) / nCol);\n\n        var leftPos = TimetableDefaults.leftMargin + colWidth * blockData.assigned;\n\n        var width;\n        if (parseInt(blockData.assigned, 10) == nCol - 1) {\n          // If last colument make sure the blocks spans all the way to the end\n          // of the timetable. Makes sure that the right margin is always aligned.\n          width = self.width - leftPos - TimetableDefaults.rightMargin;\n        } else {\n          width = colWidth;\n        }\n\n        var eventData = data[blockData.id];\n        var block;\n        var compactMode = false;\n        // For now don't use the compact mode. Activating it makes short entries displaying less\n        // information in the block (only time and title).\n        //if (blockData.collapsed || (blockData.end - blockData.start < TimetableDefaults.layouts.proportional.values.pxPerHour))\n        //    compactMode = true;\n\n        if (self.managementMode) {\n          block = new TimetableBlockNormalManagement(\n            self.timetable,\n            eventData,\n            blockData,\n            compactMode,\n            self.printableVersion,\n            self.detail.get(),\n            self.managementActions\n          );\n        } else {\n          block = new TimetableBlockNormalDisplay(\n            self.timetable,\n            eventData,\n            blockData,\n            compactMode,\n            self.printableVersion,\n            self.detail.get()\n          );\n        }\n        blockDiv.append(block.draw(leftPos, width));\n        self._blockMap[blockData.id] = block.block.dom;\n        self.blocks.push(block);\n      });\n\n      return blockDiv;\n    },\n\n    setLayout: function(layout) {\n      this.layout.set(layout);\n      this.layoutChooser.set(layout);\n    },\n\n    redraw: function(day) {\n      if (this.preventRedraw) {\n        return;\n      }\n\n      this.timetable.redrawLegend();\n\n      day = any(day, this.day);\n      if (day == 'all') {\n        this.redrawAllDays();\n        return;\n      }\n\n      var dayFiltered = this.applyFilters(this.data[day]);\n\n      if (this.detail.get() == 'contribution') {\n        dayFiltered = this.flatten(dayFiltered);\n      }\n\n      var dayData = this.layoutChooser\n        .get()\n        .drawDay(dayFiltered, 'session', this.startTime, this.endTime, this.managementMode);\n      var height = dayData[0] + TimetableDefaults.topMargin + TimetableDefaults.bottomMargin;\n      this.wrappingElement.setStyle('height', pixels(height + (this.printableVersion ? 0 : 100))); // +100 to have margin for the tabs\n\n      this.grid.length = 0;\n      $.merge(this.grid, dayData[1]);
1\n\n      var gridElems = this._drawGrid(this.grid);\n      var blocks = this._drawBlocks(dayFiltered, dayData[2], dayData[3]);\n      var wholeDayBlocks = this._drawWholeDayBlocks(dayFiltered, dayData[4]);\n\n      // Only do if not all days are drawn\n      this.canvas.set([\n        wholeDayBlocks,\n        Html.div({style: {position: 'relative'}}, gridElems, blocks),\n      ]);\n      var totalHeight = height + wholeDayBlocks.dom.offsetHeight;\n\n      this.canvas.dom.style.height = pixels(totalHeight);\n\n      this.postDraw();\n\n      $('body').trigger('timetable_redraw', this);\n\n      return totalHeight;\n    },\n\n    setPrintableVersion: function(printableVersion) {\n      this.printableVersion = printableVersion;\n      this.redraw();\n    },\n\n    postDraw: function() {\n      each(this.blocks, function(block) {\n        if (exists(block.postDraw)) {\n          block.postDraw();\n        }\n      });\n    },\n\n    flatten: function(data) {\n      var result = {};\n      each(data, function(entry, key) {\n        // sessions that are not poster sessions will be\n        // 'converted' to contributions\n        if (entry.entryType == 'Session' && !entry.isPoster && keys(entry.entries).length > 0) {\n          each(entry.entries, function(subentry, subkey) {\n            result[subkey] = clone(subentry);\n            result[subkey].color = subentry.entryType == 'Break' ? subentry.color : entry.color;\n            result[subkey].textColor =\n              subentry.entryType == 'Break' ? subentry.textColor : entry.textColor;\n          });\n        } else {\n          result[key] = entry;\n        }\n      });\n\n      return result;\n    },\n\n    drawDay: function(day) {\n      this.day = day;\n\n      this.preventRedraw = true;\n      this.updateFilters(day);\n      this.preventRedraw = false;\n\n      this.setLoading(true, this.redraw, day);\n\n      return this.canvas;\n    },\n\n    drawAllDays: function() {\n      this.day = 'all';\n\n      this.preventRedraw = true;\n      this.updateFilters('all');\n      this.preventRedraw = false;\n\n      this.setLoading(true, this.redrawAllDays);\n\n      return this.canvas;\n    },\n\n    redrawAllDays: function() {\n      var self = this;\n\n      var days = [];\n      // Add the elements to an array and sort it based on date\n      each(this.data.all, function(value, key) {\n        if (key != 'all') {\n          value.date = key;\n          days.push(value);\n        }\n      });\n      days.sort(function(a, b) {\n        if (a.date > b.date) {\n          return 1;\n        }\n        return -1;\n      });\n\n      var div = Html.div({});\n      var height = 0;\n      var headerHeight = 0;\n\n      // Retrieve the header height by adding it to the dom tree and then\n      // removing it... is there a better way?\n      var header = Html.div({className: 'timetableHeader', style: {visibility: 'hidden'}}, 'Test');\n      self.wrappingElement.append(header);\n      headerHeight = header.dom.offsetHeight + 20; // 20 is the top margin\n      self.wrappingElement.remove(header);\n\n      var firstDay = true;\n      each(days, function(value) {\n        var day = value.date;\n        delete value.date;\n        var h = self.redraw(day);\n\n        // Copy all the elements in canvas\n        var elements = translate(self.canvas.dom.childNodes, function(value) {\n          return $E(value);\n        });\n        var dayCanvas = Html.div({style: {position: 'relative', height: pixels(h)}}, elements);\n\n        // Add a div containing the date\n        var headerStyle = {width: pixels(self.width - 10)}; // subtract the padding\n        if (self.printableVersion) {\n          headerStyle.textAlign = 'center';\n          headerStyle.borderBottom = '0';\n        } else if (firstDay) {\n          headerStyle.marginTop = '0';\n        }\n\n        const dayStr = moment(day).format(\n          $T.pgettext('momentjs date format for timetable tab headers', 'ddd DD/MM')\n        );\n\n        header = Html.div({className: 'timetableHeader', style: headerStyle}, dayStr);\n        div.append(header);\n        // Add the timetable canvas\n        div.append(dayCanvas);\n\n        // Increase total height with this day's height + height of div containing the date\n        height += h + headerHeight;\n\n        firstDay = false;\n      });\n      // The extra margin added to the height, if not printable version\n      // then the height of the tabs needs to be added\n      var margin = 30 + (self.printableVersion ? 0 : 100);\n      self.wrappingElement.setStyle('height', pixels(height + margin));\n\n      this.canvas.set(div);\n\n      return this.canvas;\n    },\n\n    setWidth: function(width) {\n      this.width = width;\n    }
1,\n\n    applyFilters: function(data) {\n      var result = {};\n      var self = this;\n\n      each(data, function(event, key) {\n        var show = true;\n        self.filterState.each(function(value, filterName) {\n          if (value) {\n            var filter = TimetableDefaults.filters[filterName].filter;\n            if (!filter.apply(event)) {\n              show = false;\n            }\n          }\n        });\n        if (show) {\n          result[key] = event;\n        }\n      });\n\n      return result;\n    },\n    updateFilters: function() {\n      var self = this;\n\n      self.filterState.each(function(value, filterName) {\n        if (value) {\n          TimetableDefaults.filters[filterName].filter.setData(self.data, self.day);\n        }\n      });\n    },\n    /*\n     * Since the loading indicator needs to be shown before the rest of the code\n     * is executed a small timeout is added before calling the funcToCall.\n     */\n    setLoading: function(loading, funcToCall, arg) {\n      var self = this;\n      funcToCall = any(funcToCall, function() {});\n      this.loading += loading ? 1 : -1;\n\n      if (this.loading > 0) {\n        this.loadingIndicator.dom.style.visibility = 'visible';\n        setTimeout(function() {\n          // call redraw function\n          funcToCall.call(self, arg);\n          $('body').trigger('timetable_ready', self.timetable);\n          self.setLoading(false);\n        }, 100);\n      } else {\n        this.loadingIndicator.dom.style.visibility = 'hidden';\n        this.loading = 0;\n      }\n    },\n    setData: function(data, startTime, endTime) {\n      this.startTime = startTime;\n      this.endTime = endTime;\n      this.data = data;\n      this.redraw();\n    },\n    toggleEvents: function(value) {\n      if (value === undefined) {\n        this.eventsDisabled = !this.eventsDisabled;\n      } else {\n        this.eventsDisabled = !value;\n      }\n    },\n  },\n  function(\n    timetable,\n    width,\n    wrappingElement,\n    detailLevel,\n    extraButtons,\n    loadingIndicator,\n    managementMode,\n    managementActions,\n    defaultLayout\n  ) {\n    var self = this;\n\n    this.grid = [];\n    this.wrappingElement = wrappingElement;\n    this.canvas = Html.div({id: 'timetable_canvas'});\n    this.filterList = new WatchList();\n    this.data = timetable.data;\n    this.timetable = timetable;\n    this.blocks = [];\n    this.width = width;\n    // Prevents redraw when set to true\n    this.preventRedraw = false;\n    // Used to switch between normal and printable version\n    this.printableVersion = false;\n\n    // layoutKey -> layoutManager map\n    this.layoutChooser = new Chooser(\n      map(TimetableDefaults.layouts, function(value, key) {\n        return value.manager;\n      })\n    );\n\n    this.detail = new WatchValue();\n    this.layout = new WatchValue();\n    // if greater > 0 the timetable is loading\n    this.loading = 0;\n    this.loadingIndicator = loadingIndicator;\n\n    this.managementMode = managementMode;\n    this.managementActions = managementActions;\n\n    // default layout is 'compact'\n    this.layout.set(any(defaultLayout, 'compact'));\n    this.layoutChooser.set(any(defaultLayout, 'compact'));\n    // default detail level is 'session'\n    this.detail.set(any(detailLevel, 'session'));\n    this.eventsDisabled = false;\n\n    var filterState = map(TimetableDefaults.filters, function(value, key) {\n      // simultaneously, set the data for the filters\n      //value.filter.setData(data);\n      return true;\n    });\n\n    // create a filterName -> filterObject list\n    this.filterState = $O(filterState);\n\n    this.detail.observe(function(value) {\n      if (!self.preventRedraw) {\n        self.setLoading(true, self.redraw);\n      }\n    });\n\n    this.layout.observe(function(value) {\n      self.layoutChooser.set(value);\n      if (!self.preventRedraw) {\n        self.setLoading(true, self.redraw);\n      }\n    });\n\n    this.DroppableTimetableMixin();\n  }\n);\n\ntype(\n  'IntervalTimetableDrawer',\n  ['TimetableDrawer'],\n  {\n    posterRedraw: function() {\
1n      // TO USE: in a future interval timetable for display mode.\n      //var dayFiltered = this.applyFilters(this.data[this.day]);\n      //var dayData = this.layoutChooser.get().drawDay(dayFiltered);\n      var dayData = this.layoutChooser.get().drawDay(this.data[this.day]);\n      var blocks = this._posterBlocks(dayData);\n      this.canvas.set(Html.div({style: {position: 'relative'}}, blocks));\n\n      var height =\n        this.canvas.dom.clientHeight +\n        50 +\n        TimetableDefaults.topMargin +\n        TimetableDefaults.bottomMargin;\n\n      this.wrappingElement.setStyle('height', pixels(height + (this.printableVersion ? 0 : 100))); // +100 to have margin for the tabs\n\n      this.postDraw();\n\n      return height;\n    },\n\n    redraw: function() {\n      if (this.isPoster) {\n        return this.posterRedraw();\n      } else {\n        return this.TimetableDrawer.prototype.redraw.call(this);\n      }\n    },\n\n    _posterBlocks: function(data) {\n      data = _.sortBy(data, function(d) {\n        return [d.startDate.date, d.startDate.time, d.title];\n      });\n      var self = this;\n\n      var blockDiv = Html.div({\n        style: {\n          position: 'relative',\n          top: pixels(TimetableDefaults.topMargin),\n        },\n      });\n\n      self.blocks = [];\n      var topPx = 0;\n      each(data, function(blockData, id) {\n        function createEditLink(blockData) {\n          var link = $('<a>', {\n            class: 'i-link icon-edit',\n            title: $T.gettext('Edit'),\n            data: {\n              title: $T.gettext('Edit poster'),\n              eventId: blockData.conferenceId,\n              timetableEntryId: blockData.scheduleEntryId,\n            },\n          }).on('click', function(evt, params) {\n            var $this = $(this);\n            var urlArgs = {\n              event_id: $this.data('eventId'),\n              entry_id: $this.data('timetableEntryId'),\n            };\n            if (self.timetable.isSessionTimetable) {\n              urlArgs.session_id = self.timetable.contextInfo.sessionId;\n            }\n            ajaxDialog({\n              trigger: this,\n              url: build_url(Indico.Urls.Timetable.entries.edit, urlArgs),\n              title: $this.data('title'),\n              onClose: function(data) {\n                if (data) {\n                  handleNotifications(data);\n                  self.timetable._updateDay(data.update);\n                }\n              },\n            });\n          });\n\n          return Html.$(link);\n        }\n\n        function createProtectionLink(blockData) {\n          var link = $('<a>', {\n            class: 'i-link icon-shield',\n            title: $T.gettext('Manage protection'),\n            data: {\n              title: $T.gettext('Manage poster protection'),\n              eventId: blockData.conferenceId,\n              contribId: blockData.contributionId,\n            },\n          }).on('click', function(evt) {\n            var $this = $(this);\n            var urlArgs = {\n              event_id: $this.data('eventId'),\n              contrib_id: $this.data('contribId'),\n            };\n            ajaxDialog({\n              trigger: this,\n              url: build_url(Indico.Urls.Timetable.contributions.protection, urlArgs),\n              title: $this.data('title'),\n              onClose: function(data) {\n                if (data && data.entries) {\n                  self.managementActions._addEntries(data.entries);\n                }\n              },\n            });\n          });\n\n          return Html.$(link);\n        }\n\n        function createDeleteLink(blockData) {\n          var tooltipText, dialogTitle, dialogText;\n          if (self.timetable.eventInfo.isConference) {\n            tooltipText = $T.gettext('Unschedule');\n            dialogTitle = $T.gettext('Unschedule poster');\n            dialogText = $T.gettext('Are you sure you want to unschedule the poster?');\n          } else {\n            tooltipText = $T.gettext('Delete');\n            dialogTitle = $T.gettext('Delete poster');\n            dialogText = $T.gettext('Are you sure you want to delete the poster?');\n          }\n          var link = $('<a>', {\n            class: 'i-link icon-remove',\n            title: tooltipText,\n            data: {\n              title: dialogTitle,\n              confirm: dialogText,\n            },\n          }).on('click', function(evt) {\n            confirmPrompt($(this).data('confirm'), $(this).data('title')).then(function() {\
1n              self.managementActions.deleteEntry(blockData);\n            });\n          });\n\n          return Html.$(link);\n        }\n\n        var entryTools = Html.div({className: 'group right'});\n        if (!self.timetable.isSessionTimetable || self.timetable.canManageContribs) {\n          entryTools.append(createEditLink(blockData));\n          entryTools.append(createProtectionLink(blockData));\n        }\n        if (self.timetable.eventInfo.isConference || self.timetable.canManageContribs) {\n          entryTools.append(createDeleteLink(blockData));\n        }\n        var entryInfo = Html.div({}, blockData.friendlyId + ' - ' + blockData.title);\n        var block = Html.div({className: 'posterEntry'}, entryTools, entryInfo, Html.div({}));\n        blockDiv.append(block);\n        self.blocks.push(block);\n      });\n\n      return blockDiv;\n    },\n\n    setData: function(data, day, isPoster) {\n      this.isPoster = isPoster;\n      this.day = day;\n      if (this.isPoster) {\n        this.setLayout('poster');\n      } else {\n        this.setLayout(this.layout.get());\n      }\n      this.TimetableDrawer.prototype.setData.call(this, data);\n    },\n  },\n  function(\n    data,\n    width,\n    wrappingElement,\n    extraButtons,\n    loadingIndicator,\n    managementMode,\n    managementActions,\n    layout\n  ) {\n    this.TimetableDrawer(\n      data,\n      width,\n      wrappingElement,\n      'session',\n      extraButtons,\n      loadingIndicator,\n      managementMode,\n      managementActions,\n      data.isPoster ? 'poster' : 'proportional'\n    );\n    this.wrappingElement = data.parentTimetable.timetableDrawer.wrappingElement;\n  }\n);\n\n(function() {\n  'use strict';\n\n  $(document).on('qbubble:ajaxload', '.balloon-qtip.display-mode:not(.no-details)', function() {\n    var $description = $(this).find('.description');\n    $description.on('click', function(e) {\n      var newTab = e.which == 2; // Middle click\n      var url = $(this).data('displayHref');\n      if (newTab) {\n        window.open(url, '_blank');\n      } else {\n        window.location.href = url;\n      }\n    });\n  });\n})();\n"},7671:n=>
1{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\n// Filters - User Interface\n\ntype(\n  'TimetableFilterList',\n  ['CheckPopupWidget'],\n  {\n    draw: function(x, y) {\n      return this.CheckPopupWidget.prototype.draw.call(this, x, y, 300, {\n        position: 'fixed',\n        left: pixels(x),\n        bottom: pixels(y),\n      });\n    },\n  },\n  function(filterName, timetableDrawer, chainElements, noOptionsMessage) {\n    var filterObject = TimetableDefaults.filters[filterName].filter;\n    var optionStates = filterObject.getOptionStates();\n    var optionNames = filterObject.getOptionNames();\n    var optionColors = filterObject.getOptionColors();\n    var optionTextColors = filterObject.getOptionTextColors();\n\n    optionStates.observe(function() {\n      if (!timetableDrawer.preventRedraw) timetableDrawer.setLoading(true, timetableDrawer.redraw);\n    });\n\n    this.CheckPopupWidget(\n      optionNames,\n      optionStates,\n      optionColors,\n      optionTextColors,\n      chainElements,\n      noOptionsMessage\n    );\n  }\n);\n\ntype(\n  'TimetableFilterMenu',\n  ['PopupMenu'],\n  {},\n\n  function(triggerElement, timetableDrawer) {\n    var menu = {};\n    var self = this;\n\n    // build a dictionary that represents the menu\n    each(TimetableDefaults.filters, function(value, key) {\n      // each entry will present a list of filters\n      menu[value.name] = new TimetableFilterList(\n        key,\n        timetableDrawer,\n        [triggerElement, self],\n        'No ' + key + 's found'\n      );\n    });\n\n    this.PopupMenu(menu, [triggerElement]);\n    this.timetableDrawer = timetableDrawer;\n  }\n);\n\n// Filter definitions - Core\n\ntype(\n  'Filter',\n  [],\n  {\n    getOptionNames: function() {\n      return this.options;\n    },\n\n    getOptionStates: function() {\n      return this.optionStates;\n    },\n\n    getOptionColors: function() {\n      return this.optionColors;\n    },\n\n    getOptionTextColors: function() {\n      return this.optionTextColors;\n    },\n\n    setData: function(data, day) {\n      this.data = data;\n      this.day = day;\n      this.computeOptions();\n    },\n\n    computeOptions: function() {\n      var self = this;\n\n      this.options.clear();\n      this.optionStates.clear();\n\n      var dayData = this.data[this.day];\n      if (this.day == 'all') {\n        each(this.data, function(dayData, day) {\n          each(dayData, function(entryData, entry) {\n            self.processEvent(entryData, entry);\n          });\n        });\n      } else {\n        each(dayData, function(entryData, entry) {\n          self.processEvent(entryData, entry);\n        });\n      }\n    },\n    reset: function() {\n      var self = this;\n      each(this.options, function(value, id) {\n        self.resetEvent(id);\n      });\n    },\n  },\n  function() {\n    this.options = new WatchObject();\n    this.optionStates = new WatchObject();\n    this.optionColors = new WatchObject();\n    this.optionTextColors = new WatchObject();\n  }\n);\n\ntype(\n  'SessionFilter',\n  ['Filter'],\n  {\n    apply: function(event) {\n      var show = true;\n      this.optionStates.each(function(value, key) {\n        if (event.entryType == 'Session' && key == event.sessionId) {\n          if (!value) {\n            show = false;\n          }\n        }\n      });\n      return show;\n    },\n\n    processEvent: function(entryData, entry) {\n      var self = this;\n      if (entryData.entryType == 'Session') {\n        self.options.set(entryData.sessionId, entryData.title);\n        self.optionStates.set(entryData.sessionId, true);\n        self.optionColors.set(entryData.sessionId, entryData.color);\n        self.optionTextColors.set(entryData.sessionId, entryData.textColor);\n      }\n    },\n\n    resetEvent: function(entry) {\n      this.optionStates.set(entry, true);\n    },\n  },\n  function() {\n    this.Filter();\n  }\n);\n\ntype(\n  'RoomFilter',\n  ['Filter'],\n  {\n    apply: function(event) {\n      var show = true;\n      this.optionStates.each(function(value, key) {\n        if (event.room == key) {\n          if (!value) {\n            show = false;\n          }\n        }\n      });\n      return show;\n    },\n\n    processEvent: function(entryData, entry) {\n      var self = this;\n      if (exists(entryData.room) && trim(entryData.room) !== '') {\n        self.options.set(entryData.room, entryData.room);\n        self.optionStates.set(entryData.room, true);\n      }\n    },\n\n    resetEvent: function(entry) {\n      this.optionStates.set(entry, true);\n    },\n  }
1,\n  function() {\n    this.Filter();\n  }\n);\n\ntype(\n  'TimeTableFilter',\n  ['IWidget'],\n  {\n    draw: function() {\n      var self = this;\n\n      var content = Html.div('content clearfix');\n\n      var closeButton = Html.div('closeButton');\n      closeButton.observeClick(function(e) {\n        if (self.closeHandler()) self.show(false);\n      });\n      content.append(closeButton);\n\n      this.tableRow = Html.tr();\n      content.append(\n        Html.table({cellPadding: 0, cellSpacing: 1, border: 0}, Html.tbody({}, this.tableRow))\n      );\n\n      var sessionsLink = Html.span('i-button borderless dropup-menu', $T('Sessions'));\n      var sessionsMenu = new TimetableFilterList(\n        'session',\n        self.timetableDrawer,\n        [sessionsLink],\n        $T('No sessions in the timetable')\n      );\n      this.setUpFilterMenu(sessionsLink, sessionsMenu);\n\n      var roomsLink = Html.span('i-button borderless dropup-menu', $T('Rooms'));\n      var roomsMenu = new TimetableFilterList(\n        'room',\n        self.timetableDrawer,\n        [roomsLink],\n        $T('No rooms found in the timetable')\n      );\n      this.setUpFilterMenu(roomsLink, roomsMenu);\n\n      this.tableRow.insert(\n        Html.td({style: {width: '90px', color: '#777', fontWeight: 'bold'}}, $T('Filter options'))\n      );\n\n      var resetButton = Html.input(\n        'button',\n        {style: {cssFloat: 'right', marginRight: '50px'}},\n        $T('Reset filter')\n      );\n      resetButton.observeClick(function() {\n        each(TimetableDefaults.filters, function(TTfilter) {\n          TTfilter.filter.reset();\n        });\n      });\n\n      this.tableRow.append(Html.td({style: {width: 'auto'}}, resetButton));\n\n      this.div.append(content);\n\n      this.height = $(self.div.dom).height();\n\n      // Hide it by default\n      this.div.dom.style.display = 'none';\n    },\n    setUpFilterMenu: function(link, menu) {\n      var self = this;\n\n      this.tableRow.append(Html.td({}, link));\n\n      link.observeClick(function() {\n        if (menu.active) {\n          menu.close();\n          return;\n        }\n        var pos = link.getAbsolutePosition();\n        menu.open(pos.x - 30, self.height);\n      });\n    },\n    show: function(show) {\n      this.state.set(!this.state.get());\n      if (show) $(this.div.dom).slideDown(200);\n      else $(this.div.dom).slideUp(200);\n    },\n    toggle: function() {\n      this.show(!this.state.get());\n    },\n  },\n  function(timetableDrawer, closeHandler) {\n    var self = this;\n\n    this.timetableDrawer = timetableDrawer;\n    this.closeHandler = closeHandler;\n\n    this.state = new WatchValue(false);\n\n    this.div = Html.div({className: 'timetableFilter', style: {display: 'none'}});\n    $E(document.body).append(this.div);\n  }\n);\n"},87303:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\ntype('TimetableLayoutManager', [], {\n  _buildCheckpointTable: function(data) {\n    /* Checkpoints are time points where events either start or end */\n    var checkpoints = {};\n    var addCheckpoint = function(key, time, type, sessionId, sessionSlotId) {\n      if (!checkpoints[time]) {\n        checkpoints[time] = [];\n      }\n      checkpoints[time].push([key, type, sessionId, sessionSlotId]);\n    };\n\n    // Enforce key ordering\n    // In case we are dealing with structures that are inside a session,\n    // and have a non-null sessionCode, use it for ordering\n    var orderedKeys = keys(data);\n\n    orderedKeys.sort(function(e1, e2) {\n      // if there's a session code\n      if (exists(data[e1].sessionCode) && exists(data[e2].sessionCode)) {\n        var byCode = SortCriteria.Integer(data[e1].sessionCode, data[e2].sessionCode);\n        if (byCode != 0) {\n          return byCode;\n        }\n      }\n\n      // default behavior\n      return SortCriteria.Integer(e1, e2);\n    });\n\n    each(orderedKeys, function(key) {\n      var value = data[key];\n      var sTime = value.startDate.time.replace(/:/g, '');\n      var eTime = value.endDate.time.replace(/:/g, '');\n\n      // If a poster session with a duration of > 7h then don't place\n      // it in the grid but rather on the top as a whole day event\n      if (value.isPoster && value.duration > TimetableDefaults.wholeDay * 60) {\n        addCheckpoint(key, sTime, 'wholeday', value.sessionId, value.sessionSlotId);\n      } else {\n        addCheckpoint(key, sTime, 'start', value.sessionId, value.sessionSlotId);\n\n        if (eTime >= sTime) {\n          addCheckpoint(key, eTime, 'end');\n        } else if (eTime == '000000') {\n          addCheckpoint(key, '240000', 'end');\n        } else {\n          addCheckpoint(key, 'nextday', 'end');\n        }\n      }\n    });\n    this.checkpoints = checkpoints;\n    return checkpoints;\n  },\n\n  pointsBetween: function(hStart, hEnd) {\n    var result = [];\n\n    each(this.checkpoints, function(points, time) {\n      if (\n        (hStart == 'nextday' && time == 'nextday') ||\n        (hStart != 'nextday' && time > hStart && time < hEnd)\n      ) {\n        result = concat(result, points);\n      }\n    });\n\n    return result;\n  },\n\n  assign: function(assigned, block) {\n    var ks = keys(assigned);\n    ks.sort();\n\n    for (var key in ks) {\n      if (!assigned[key]) {\n        block.assigned = parseInt(key, 10);\n        assigned[key] = block;\n        return;\n      }\n    }\n\n    // nothing assigned in cycle\n    // add a new key\n    var newElem = ks.length;\n    block.assigned = parseInt(newElem, 10);\n    assigned[newElem] = block;\n  },\n\n  reorderAssigned: function(assigned, lastAssigned, currentGroup, currentPos) {\n    var getLastAssignedId = function(block) {\n      return '{0}l{1}'.format(block.sessionId, block.sessionSlotId);\n    };\n\n    var correctlyAssigned = function(block) {\n      return (\n        lastAssigned[getLastAssignedId(block)] &&\n        lastAssigned[getLastAssignedId(block)].col === block.assigned\n      );\n    };\n\n    // Returns number of previously processed session slots\n    var numAssignedBlocks = function(sessionId) {\n      return keys(lastAssigned[sessionId].blocks).length;\n    };\n\n    // Adds/updates a block in the lastAssigned dictionary\n    var lastAssign = function(block, col) {\n      if (!lastAssigned[getLastAssignedId(block)]) {\n        lastAssigned[getLastAssignedId(block)] = {blocks: {}};\n      }\n      var lastAssignedCol = lastAssigned[getLastAssignedId(block)].col || -1;\n      if (!isNaN(parseInt(col, 10)) && col > lastAssignedCol) {\n        lastAssigned[getLastAssignedId(block)].col = col;\n      }\n      lastAssigned[getLastAssignedId(block)].blocks[block.id] = true;\n    };\n\n    // Changes the column of a block\n    var reassign = function(block, col) {\n      assigned[block.assigned] = null;\n      block.assigned = parseInt(col, 10);\n      assigned[parseInt(col, 10)] = block;\n      lastAssign(block, parseInt(col, 10));\n    };\n\n    var swap_columns = function(block1, block2) {\n      var block1_old_col = block1.assigned;\n      reassign(block1, block2.assigned);\n      reassign(block2, block1_old_col);\n      assigned[block1.assigned] = block1;\n    };\n\n    for (var key in currentGroup) {\n      var block = currentGroup[key];\n\n      // If this is not a session slot (that is a block\n      // that has sessionId set) then we don't care about in\n      // which column it is placed\n      if (!exists(block.sessionId)) {\n        continue;\n      }\n\n      if (exists(lastAssigned[getLastAssignedId(block)])) {\n        lastAssign(block);\n      } else {\n        // This block has never been assigned before. Just update the lastAssigned.\n        lastAssign(block, block.assigned);\n        continue;\n      }\n\n      if (correctlyAssigned(block)) {\n        // The block has already got its preferred position\n        continue;\n      }\n\n      var preferredCol = lastAssigned[getLastAssignedId(block)].col;\n      var existingBlock = assigned[preferredCol];\n\n      // If there's no block on the preferred column\n      if (!existingBlock) {\n        // if the block starts at the current position, it is safe to move it to a free place\n        // otherwise we can overlap an existing one\n        if (block.start == currentPos && preferredCol < _(assigned).size()) {\n          reassign(block, preferredCol);\n        }\n      } else if (\n        !exists(existingBlock.sessionId) ||\n        !exists(lastAssigned[existingBlock.sessionId]) ||\n        numAssignedBlocks(block.sessionId) > numAssignedBlocks(existingBlock.sessionId)\n      ) {\n        // The block currently placed in the preferred column has either no preferred column\n        // or has a preferred column but has fewer previous placed session slots (this\n        // gives lower priority).\n\n        // Only do the swap if the existing block starts at the same time\n        // otherwise there might be overlapping blocks. Is there a better way\n        // to handle this so that this check is not needed?\n        if (existingBlock.start == block.start) {\n          swap_columns(existingBlock, block);\n        }\n      }\n    }\n  },\n\n  getBlock: function(blocks, key) {\n    var block;\n    if (blocks[key]) {\n      block = blocks[key];\n    }
1 else {\n      block = blocks[key] = {id: key, collapsed: false};\n    }\n\n    return block;\n  },\n\n  addWholeDayBlock: function(blocks, key) {\n    blocks[key] = {id: key};\n  },\n\n  getNumColumnsForGroup: function(group) {\n    return group[1];\n  },\n\n  getHeader: function() {\n    return null;\n  },\n\n  shouldShowRoom: function() {\n    return true;\n  },\n  reorderColumns: function(group) {},\n});\n\ntype('IncrementalLayoutManager', ['TimetableLayoutManager'], {\n  name: 'incremental',\n  drawDay: function(data, detailLevel, startTime, endTime, managementMode) {\n    var self = this;\n\n    this.eventData = data;\n\n    this.detailLevel = any(detailLevel, 'session');\n\n    var checkpoints = this._buildCheckpointTable(data);\n\n    var ks = keys(checkpoints);\n    ks.sort();\n    managementMode = any(managementMode, true);\n    var startingHour, endingHour;\n    if (ks.length > 1) {\n      startingHour = parseInt(ks[0].substring(0, 2), 10);\n\n      var last = ks.length - 1;\n\n      // account for 'nextday' entries\n      while (!endingHour && last >= 0) {\n        endingHour = parseInt(ks[last].substring(0, 2), 10);\n        last--;\n      }\n    } else {\n      startingHour = parseInt(any(startTime, '8:00').split(':')[0], 10);\n      endingHour = parseInt(any(endTime, '17:00').split(':')[0], 10);\n    }\n\n    var endMin;\n\n    var algData = {\n      grid: [], // Positions of all the time lines\n      assigned: {}, // columns bound to blocks\n      blocks: {}, // Dict of blocks\n      active: 0, // number of of active time blocks\n      currentGroup: [], // current processed group\n      topPx: 0, // counter when iterating from top to bottom of timetable\n      groups: [], // Isolated group of timetable blocks (all blocks in parallel)\n      extraPx: {}, // Used for increasing the pixels for time blocks. Used when a time\n      // time table block needs extra space.\n      lastAssigned: {}, // Remembers to what column a session has been assigned before\n      // makes it possible align sessions under each other\n      wholeDayBlocks: {}, // All the block that should be shown as spanning the whole day,\n      // i.e. a poster session.\n    };\n\n    var hEnd;\n\n    if (managementMode) {\n      //add hour before start if we are in management mode\n      if (startingHour > 0) {\n        for (var min = 0; min < 60; min += TimetableDefaults.resolution) {\n          self.processTimeBlock(\n            startingHour - 1,\n            startingHour,\n            (startingHour - 1) * 60,\n            min,\n            algData\n          );\n        }\n      }\n    }\n\n    for (\n      var minutes = 0;\n      minutes < (endingHour + 1 - startingHour) * 60;\n      minutes += TimetableDefaults.resolution\n    ) {\n      // current block is [minutes, minutes + 5]\n      var startMin = startingHour * 60 + minutes;\n      endMin = startingHour * 60 + minutes + TimetableDefaults.resolution;\n      var hStart = zeropad(parseInt(startMin / 60, 10)) + '' + zeropad(startMin % 60);\n      hEnd = zeropad(parseInt(endMin / 60, 10)) + '' + zeropad(endMin % 60);\n\n      self.processTimeBlock(hStart, hEnd, startMin, minutes, algData);\n    }\n\n    if ($L(ks).indexOf('nextday') !== null) {\n      self.processTimeBlock('nextday', 'nextday', startingHour * 60 + minutes, minutes, algData);\n    } else if (endMin / 60 < 25 && managementMode) {\n      // add last hour + 1 to the grid\n      // (only if the next hour is not after midnight)\n      algData.grid.push([(endMin / 60) % 24, algData.topPx]);\n    }\n\n    var counter = 0;\n    each(algData.groups, function(group) {\n      self.reorderColumns(group[0]);\n      each(group[0], function(block) {\n        block.group = counter;\n      });\n      counter++;\n    });\n\n    return [algData.topPx, algData.grid, algData.blocks, algData.groups, algData.wholeDayBlocks];\n  },\n});\n\ntype('CompactLayoutManager', ['IncrementalLayoutManager'], {\n  name: 'compact',\n  processTimeBlock: function(hStart, hEnd, startMin, minutes, algData) {\n    var self = this;\n\n    // get all the checkpoints in [hStart, hEnd]\n    var points = self.pointsBetween(hStart, hEnd);\n    var incrementPx = 0;\n\n    var block;\n    var smallBlockList = [];\n\n    var pxStep = Math.floor(\n      (TimetableDefaults.layouts.compact.values.pxPerHour * TimetableDefaults.resolution) / 60\n    );\n\n    var endPoints = [];\n\n    each(points, function(point) {\n      if (point[1] == 'end') {\n        block = self.getBlock(algData.blocks, point[0]);\n\n        block.end = algData.topPx;\n        if (algData.assigned[block.assigned]) {\n          algData.active--;\n          // this means it has been started in a previous timeslot\n          algData.assigned[block.assigned] = null;\n\n          // diff: how much does it take for the block to reach\n          // the minimum size?\n          var diff =\n            TimetableDefaults.layouts.compact.values.minPxPerBlock - (block.end - block.start);\n\n          if (diff > 0) {\n            // increase it by diff\n            block.end += diff;\n            incrementPx = diff > incrementPx ? diff : incrementPx;\n            algData.topPx += incrementPx;\n          }\n\n          // check if block goes beyond the timetable limits\n          // (ends after midnight)\n          if (hStart == 'nextday') {\n            // mark it as 'unfinished' and add an extra space\n            block.end += hStart == 'nextday' ? 20 : 0;\n            block.unfinished = true;\n          }\n\n          // save reference for all blocks that end here, will use it below\n          endPoints.push(block);\n        } else {\n          // otherwise, it is ending just before it starts:\n          // this means that the duration is less than our \"resolution\"\n          // so, let's add it to smallBlockList\n          smallBlockList.push(block);\n        }\n      }\n    });\n\n    if (endPoints.length) {\n      // now every block that ends at this point must have the same 'end\"\n      // since some of them may have been expanded ('diff' above), we need to set them all\n      // to the max value\n      var maxPx = _(endPoints).max(function(block) {\n        return block.end;\n      }).end;\n      _(endPoints).each(function(block) {\n        block.end = maxPx;\n      });\n    }\n\n    if (minutes % 60 === 0) {\n      algData.grid.push([(startMin / 60) % 24, algData.topPx]);\n    }\n\n    if (!algData.active) {\n      if (algData.currentGroup.length > 0) {\n        algData.groups.push([algData.currentGroup, keys(algData.assigned).length]);\n        algData.currentGroup = [];\n        algData.assigned = {};\n      }\n    }\n\n    var blockAdded = false;\n\n    each(points, function(point) {\n      if (point[1] == 'start') {\n        blockAdded = true;\n\n        block = self.getBlock(algData.blocks, point[0]);\n        block.sessionId = point[2];\n        block.sessionSlotId = point[3];\n        block.start = algData.topPx;\n\n        algData.active++;\n        self.assign(algData.assigned, block);\n        algData.currentGroup.push(block);\n      } else if (point[1] == 'wholeday') {\n        self.addWholeDayBlock(algData.wholeDayBlocks, point[0]);\n      }\n    });\n\n    // Try to reaorder the assigned blocks based on their session siblings' position\n    if (blockAdded) {\n      self.reorderAssigned(\n        algData.assigned,\n        algData.lastAssigned,\n        algData.currentGroup,\n        algData.topPx\n      );\n    }\n\n    if (algData.active > 0) {\n      var extraPx = 0;\n      each(algData.extraPx, function(value, key) {\n        if (value > extraPx) {\n          extraPx = value;\n        }\n      });\n      algData.topPx += pxStep + extraPx;\n    } else {\n      algData.topPx += TimetableDefaults.layouts.compact.values.pxPerSpace;\n    }\n\n    each(smallBlockList, function(block) {\n      block.end = block.start + TimetableDefaults.layouts.compact.values.minPxPerBlock;\n      algData.topPx += TimetableDefaults.layouts.compact.values.minPxPerBlock;\n      algData.assigned[block.assigned] = null;\n      algData.active--;\n    });\n  },\n});\n\ntype('ProportionalLayoutManager', ['IncrementalLayoutManager'], {\n  name: 'proportional',\n  processTimeBlock: function(hStart, hEnd, startMin, minutes, algData) {\n    var self = this;\n\n    // get all the checkpoints in [hStart, hEnd]\n    var points = self.pointsBetween(hStart, hEnd);\n    var incrementPx = 0;\n\n    var block;\n\n    var pxStep = Math.floor(\n      (TimetableDefaults.layouts.proportional.values.pxPerHour * TimetableDefaults.resolution) / 60\n    );\n    var smallBlocks = [];\n\n    each(points, function(point) {\n      if (point[1] == 'end') {\n        block = self.getBlock(algData.blocks, point[0]);\n\n        block.end = algData.topPx;\n\n        if (algData.assigned[block.assigned]) {\n          algData.active--;\n          algData.assigned[block.assigned] = null;\n\n          var diff =\n            TimetableDefaults.layouts.proportional.values.minPxPerBlock - (block.end - block.start);\n          if (diff > 0) {\n            block.end += diff;\n            block.collapsed = true;\n            algData.topPx += diff;\n          }\n\n          // check if block goes beyond the timetable limits\n          // (ends after midnight)\n          if (hStart == 'nextday') {\n            // mark it as 'unfinished' and add an extra space\n            block.end += hStart == 'nextday' ? 20 : 0;\n            block.unfinished = true;\n          }\n        } else {\n          smallBlocks.push(block);\n        }\n      }\n    });\n\n    var hour = startMin / 60;\n    if (minutes % 60 === 0 && hour <= 24) {\n      algData.grid.push([hour % 24, algData.topPx]);\n    }\n\n    if (!algData.active) {\n      if (algData.currentGroup.length > 0) {\n        algData.groups.push([algData.currentGroup, keys(algData.assigned).length]);\n        algData.currentGroup = [];\n        algData.assigned = {};\n      }\n    }\n\n    each(points, function(point) {\n      if (point[1] == 'start') {\n        block = self.getBlock(algData.blocks, point[0]);\n        block.start = algData.topPx;\n        algData.active++;\n        self.assign(algData.assigned, block);\n        algData.currentGroup.push(block);\n      }\n    });\n\n    each(smallBlocks, function(block) {\n      algData.active--;\n      algData.assigned[block.assigned] = null;\n      block.collapsed = true;\n      block.end = algData.topPx + TimetableDefaults.layouts.proportional.values.minPxPerBlock;\n    });\n\n    if (smallBlocks.length > 0) {\n      algData.topPx += TimetableDefaults.layouts.proportional.values.minPxPerBlock;\n    }\n\n    algData.topPx += pxStep;\n  },\n});\n\ntype(\n  'RoomLayoutManager',\n  ['CompactLayoutManager'],\n  {\n    drawDay: function(data, detailLevel, startTime, endTime) {\n      this.roomsCols = {};\n      return this.CompactLayoutManager.prototype.drawDay.call(\n        this,\n        data,\n        detailLevel,\n        startTime,\n        endTime\n      );\n    },\n\n    assign: function(assigned, block) {\n      var roomName = this.eventData[block.id].room;\n      var col = 0;\n      if (!exists(this.roomsCols[roomName])) {\n        // If there is no room name, the block will be in the column 0 (and take all the available width)\n        if (trim(roomName) !== '') {\n          col = this.roomsCols[roomName] = keys(this.roomsCols).length;\n        }\n      } else {\n        col = this.roomsCols[roomName];\n      }\n\n      block.assigned = parseInt(col, 10);\n      assigned[col] = block;\n    },\n\n    reorderColumns: function(currentGroup) {\n      var self = this;\n      var roomNames = keys(this.roomsCols);\n      roomNames.sort();\n\n      this.roomsCols = {};\n      var counter = 0;\n      each(roomNames, function(name) {\n        self.roomsCols[name] = counter;\n        counter++;\n      });\n\n      for (var key in currentGroup) {\n        var block = currentGroup[key];\n        var roomName = this.eventData[block.id].room;\n        // If there is no room name, the block will be in the column 0 (and take all the available width)\n        var col = 0;\n        if (trim(roomName) !== '') {\n          col = this.roomsCols[roomName];\n        }\n        block.assigned = col;\n      }\n    }
1,\n\n    getNumColumnsForGroup: function(group) {\n      if (group[0].length == 1 && this.eventData[group[0][0].id].room === '') {\n        return 1;\n      } else {\n        return keys(this.roomsCols).length;\n      }\n    },\n\n    getHeader: function(width) {\n      var roomNames = keys(this.roomsCols);\n      var cols = roomNames.length;\n      var borderPixels = 1; // this is because of the separators between the room names\n      return Html.div(\n        {\n          style: {\n            marginLeft: pixels(TimetableDefaults.leftMargin),\n            paddingBottom: pixels(10),\n            paddingTop: pixels(20),\n          },\n        },\n        translate(roomNames, function(key) {\n          return Html.div(\n            {\n              className: 'headerRoomLayoutTimeTable',\n              style: {\n                width: pixels(\n                  Math.floor((width - TimetableDefaults.leftMargin) / cols) - borderPixels\n                ),\n              },\n            },\n            key\n          );\n        })\n      );\n    },\n\n    shouldShowRoom: function() {\n      return false;\n    },\n  },\n\n  function() {\n    this.roomsCols = {};\n  }\n);\n\ntype('PosterLayoutManager', ['TimetableLayoutManager'], {\n  drawDay: function(data, detailLevel) {\n    return data;\n  },\n});\n"},17510:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\ntype(\n  'UnscheduledContributionList',\n  ['SelectableListWidget'],\n  {\n    draw: function() {\n      var self = this;\n      var lastSort = 'friendly_id';\n      var selectAll = Html.span('fake-link', $T.pgettext('Selection', 'All'));\n      var selectNone = Html.span('fake-link', $T.pgettext('Selection', 'None'));\n      var sortById = Html.span(\n        {className: 'fake-link', id: 'sortById', style: {fontWeight: 'bold'}},\n        $T('ID')\n      );\n      var sortByTitle = Html.span({className: 'fake-link', id: 'sortByTitle'}, $T('Title'));\n      var toolbar = Html.div(\n        {className: 'toolbar', style: {margin: pixels(3)}},\n        $T('Sort by: '),\n        sortById,\n        ', ',\n        sortByTitle,\n        ' ',\n        $T('Select: '),\n        selectAll,\n        ', ',\n        selectNone\n      );\n\n      sortById.observeClick(function() {\n        self._sortList('friendly_id', lastSort == 'friendly_id');\n        lastSort = 'friendly_id';\n      });\n\n      sortByTitle.observeClick(function() {\n        self._sortList('title', lastSort == 'title');\n        lastSort = 'title';\n      });\n\n      selectAll.observeClick(function() {\n        self.selectAll();\n      });\n\n      selectNone.observeClick(function() {\n        self._clearSelection();\n      });\n\n      return [toolbar, this.SelectableListWidget.prototype.draw.call(this)];\n    },\n\n    _drawItem: function(pair) {\n      var self = this;\n      var elem = pair.get(); // elem is a WatchObject\n\n      var speakers = translate(elem.get('speakerList'), function(speaker) {\n        return speaker.familyName;\n      }).join(', ');\n      var selected = false;\n\n      var id = Html.em(\n        {'data-id': elem.get('friendly_id'), style: {paddingLeft: '5px', fontSize: '0.9em'}},\n        elem.get('friendly_id')\n      );\n      return Html.div({}, id, ' - ', elem.get('title') + (speakers ? ' (' + speakers + ')' : ''));\n    },\n\n    _sortList: function(type, second_click) {\n      const self = this;\n      const selected = Object.values(this.getSelectedList().getAll()).map(item => item.get('id'));\n      const initial = Object.values(self.getAll());\n\n      $('#sortById').css('font-weight', type == 'friendly_id' ? 'bold' : '');\n      $('#sortByTitle').css('font-weight', type == 'title' ? 'bold' : '');\n\n      // XXX if someone ever needs to debug something here: it's basically random whether `_` in\n      // here is underscore or lodash...\n      const sorted = _.sortBy(\n        initial.map((value, key) => ({key, value: value.get(type)})),\n        'value'\n      );\n\n      // reverse order only if second click, else reset order\n      this.reverseState = second_click ? !this.reverseState : false;\n\n      if (this.reverseState) {\n        sorted.reverse();\n      }\n\n      // Clear selection and elements\n      self._clearSelection();\n      self.clear();\n\n      // Add sorted items\n      sorted.forEach((item, i) => {\n        self.set(String(i), initial[item.key]);\n      });\n\n      // Reselect items\n      each(self.domList, function(listItem) {\n        if (selected.indexOf($(listItem.dom).find('em').data('id') + '') > -1) {\n          listItem.eventObservers.click();\n        }\n      });\n    }
1,\n\n    getList: function() {\n      return this.getSelectedList();\n    },\n\n    _clearSelection: function() {\n      var self = this;\n      self.clearSelection();\n      if (exists(self.selectedObserver)) {\n        self.selectedObserver(self.selectedList);\n      }\n    },\n  },\n  function(existing, observer) {\n    var self = this;\n\n    this.selected = new WatchList();\n    this.reverseState = false;\n    this.SelectableListWidget(observer, false, 'UnscheduledContribList');\n\n    // Sort by title and add to the list\n    each(existing, function(item, index) {\n      self.set(index, $O(item));\n    });\n    this._sortList('friendly_id');\n  }\n);\n\ntype(\n  'AddContributionDialog',\n  ['ExclusivePopupWithButtons', 'PreLoadHandler'],\n  {\n    _preload: [\n      function(hook) {\n        var self = this;\n        var args = {event_id: self.args.get('conference')};\n        if (self.timetable.contextInfo.sessionSlotId) {\n          args.session_block_id = self.timetable.contextInfo.sessionSlotId;\n        }\n        if (self.timetable.isSessionTimetable) {\n          args.session_id = self.timetable.contextInfo.sessionId;\n        }\n        $.ajax({\n          url: build_url(Indico.Urls.Timetable.contributions.notScheduled, args),\n          cache: false,\n          complete: IndicoUI.Dialogs.Util.progress(),\n          error: handleAjaxError,\n          success: function(data) {\n            self.existing = $L(data.contributions);\n            self._processDialogState();\n            hook.set(true);\n          },\n        });\n      },\n    ],\n\n    _processDialogState: function() {\n      var self = this;\n      if (this.existing.length.get() === 0 && self.canCreateNew) {\n        var dialog = createObject(AddNewContributionDialog, self.newArgs);\n        dialog.draw();\n        this.open = function() {};\n      } else {\n        this.ExclusivePopupWithButtons($T('Add Contribution'), function() {\n          self.close();\n        });\n      }\n    },\n\n    addExisting: function(contributionIds, date) {\n      var self = this;\n      var urlArgs = {event_id: self.args.get('conference')};\n      if (self.args.get('slot')) {\n        urlArgs.block_id = self.args.get('slot');\n      }\n      if (self.timetable.isSessionTimetable) {\n        urlArgs.session_id = self.args.get('session');\n      }\n      $.ajax({\n        url: build_url(Indico.Urls.Timetable.contributions.schedule, urlArgs),\n        data: JSON.stringify({\n          contribution_ids: contributionIds,\n          day: date,\n        }),\n        method: 'POST',\n        contentType: 'application/json',\n        complete: IndicoUI.Dialogs.Util.progress(),\n        error: handleAjaxError,\n        success: function(data) {\n          self.close();\n          if (data) {\n            handleNotifications(data);\n            self.timetable._updateDay(data.update);\n          }\n        },\n      });\n    },\n\n    existingSelectionObserver: function(selectedList) {\n      if (typeof this.saveButton == 'undefined') {\n        return;\n      }\n      if (selectedList.isEmpty()) {\n        this.saveButton.disabledButtonWithTooltip('disable');\n      } else {\n        this.saveButton.disabledButtonWithTooltip('enable');\n      }\n    },\n\n    _getButtons: function() {\n      var self = this;\n      return [\n        [\n          $T('Add selected'),\n          function() {\n            var ids = translate(self.unscheduledList.getList(), function(contrib) {\n              return contrib.get('id');\n            });\n            self.addExisting(ids, self.selectedDay);\n          },\n        ],\n        [\n          $T('Close'),\n          function() {\n            self.close();\n          },\n        ],\n      ];\n    },\n\n    draw: function() {\n      var self = this;\n\n      self.unscheduledList = new UnscheduledContributionList(self.existing, function(selectedList) {\n        self.existingSelectionObserver(selectedList);\n      });\n\n      var createNewLink;\n      if (self.canCreateNew) {\n        createNewLink = Html.li(\n          {\n            style: {marginBottom: '10px'},\n          }
1,\n          Widget.link(\n            command(function() {\n              var dialog = createObject(AddNewContributionDialog, self.newArgs);\n              self.close();\n              dialog.draw();\n            }, $T('Create a new one'))\n          )\n        );\n      }\n\n      var content = Html.div(\n        {},\n        $T('You may choose to:'),\n        Html.ul(\n          {},\n          createNewLink,\n          Html.li(\n            {},\n            $T('Choose one (or more) unscheduled'),\n            Html.div('UnscheduledContribListDiv', self.unscheduledList.draw())\n          )\n        )\n      );\n\n      this.saveButton = this.buttons.eq(0);\n      this.saveButton.disabledButtonWithTooltip({\n        tooltip: $T('To add an unscheduled contribution, please select at least one'),\n        disabled: true,\n      });\n\n      return this.ExclusivePopupWithButtons.prototype.draw.call(this, content);\n    },\n  },\n  function(args, timetable, callback, canCreateNew) {\n    var self = this;\n    this.newArgs = Array.prototype.slice.call(arguments, 0);\n    this.args = args;\n    this.selectedDay = args.get('selectedDay');\n    this.callback = callback;\n    this.timetable = timetable;\n    this.canCreateNew = canCreateNew || false;\n\n    this.PreLoadHandler(self._preload, function() {\n      self.open();\n    });\n  }\n);\n\ntype(\n  'AddNewContributionDialog',\n  [],\n  {\n    draw: function() {\n      var self = this;\n      var urlArgs = {\n        event_id: self.args.get('conference'),\n        day: self.args.get('selectedDay'),\n      };\n      if (self.args.get('slot')) {\n        urlArgs.session_block_id = self.args.get('slot');\n      }\n      if (self.timetable.isSessionTimetable) {\n        urlArgs.session_id = self.args.get('session');\n      }\n      ajaxDialog({\n        url: build_url(Indico.Urls.Timetable.contributions.add, urlArgs),\n        title: $T.gettext('Add contribution'),\n        onClose: function(data) {\n          if (data) {\n            self.callback(data);\n          }\n        },\n      });\n    },\n  },\n\n  function(args, timetable, callback) {\n    var self = this;\n    this.args = clone(args);\n    this.timetable = timetable;\n    this.callback = callback;\n  }\n);\n\n/**\n * Dialog to reschedule a timetable day or an interval\n * @param {Timetable} parentTimetable The timetable object from which this dialog is launched.\n */\ntype(\n  'RescheduleDialog',\n  ['ExclusivePopupWithButtons'],\n  {\n    /**\n     * For top level timetable, returns the current day formatted as Fri 26/07\n     */\n    __getCurrentDayText: function() {\n      return this.tt._titleTemplate(this.tt.currentDay);\n    },\n\n    /**\n     * For an interval timetable, returns the title of the interval\n     */\n    __getIntervalTitle: function() {\n      if (this.tt.contextInfo.slotTitle) {\n        return '\"{0}: {1}\"'.format(this.tt.contextInfo.title, this.tt.contextInfo.slotTitle);\n      } else {\n        return '\"{0}\"'.format(this.tt.contextInfo.title);\n      }\n    },\n\n    /**\n     * Draws the step 1: choose \"starting time\" or \"duration\" as action\n     */\n    __drawChooseAction: function() {\n      var self = this;\n\n      // Step 1: choose action\n      var actionChooseTitle = Html.div(\n        'rescheduleTitle',\n        $T('Step 1: Choose type of rescheduling')\n      );\n\n      var startTimeRescheduleRB = Html.radio({\n        name: 'rescheduleAction',\n        id: 'startTimeRescheduleRB',\n        style: {verticalAlign: 'middle'},\n      });\n      var startTimeRescheduleExample = Html.a(\n        {href: Indico.Urls.ImagesBase + '/resched_ex_1.png', title: 'Starting Time Example'},\n        $T('See an example')\n      );\n      $(startTimeRescheduleExample.dom).colorbox();\n      var startTimeRescheduleLabel = Html.label(\n        {style: {fontWeight: 'normal'}},\n        Html.div('rescheduleLabelTitle', $T('Adjust starting time of all entries')),\n        Html.div(\n          'rescheduleLabelDetails',\n          this.isTopLevelTimetable\n            ? $T('Move the entries of ') + this.__getCurrentDayText() + $T(' by changing their')\n            : $T('Move the entries of the interval ') +\n                this.__getIntervalTitle() +\n                $T(' by changing their'),\n          Html.strong({}, $T(' starting times. ')),\n          this.isTopLevelTimetable\n            ? $T('The first entry will start when the event starts (') +\n                this.tt.eventInfo.startDate.time.slice(0, 5) +\n                '), '\n            : $T('The first entry will start when the interval starts (') +\n                this.tt.contextInfo.startDate.time.slice(0, 5) +\n                '), ',\n          $T(\n            'and the other entries will follow consecutively after it. The durations of the entries will not change. '\n          ),\n          startTimeRescheduleExample\n        )\n      );\n\n      startTimeRescheduleLabel.dom.htmlFor = 'startTimeRescheduleRB';\n\n      var durationRescheduleRB = Html.radio({\n        name: 'rescheduleAction',\n        id: 'durationRescheduleRB',\n        style: {verticalAlign: 'middle'},\n      });\n      var durationRescheduleExample = Html.a(\n        {href: Indico.Urls.ImagesBase + '/resched_ex_2.png', title: 'Duration Example'},\n        $T('See an example')\n      );\n      $(durationRescheduleExample.dom).colorbox();\n      var durationRescheduleLabel = Html.label(\n        {style: {fontWeight: 'normal'}},\n        Html.div('rescheduleLabelTitle', $T('Adjust duration of all entries')),\n        Html.div(\n          'rescheduleLabelDetails',\n          $T('Adjust the '),\n          Html.strong({}, $T(' duration ')),\n          $T('of the entries of '),\n          this.isTopLevelTimetable\n            ? this.__getCurrentDayText() + ','\n            : $T('the interval ') + this.__getIntervalTitle(),\n          $T(\n            \" to fill the gaps between them, so that their starting time don't change. \" +\n              'If a time gap is specified, the duration will be extended up to the value of ' +\n              'this time gap before the starting time of the next entry. '\n          ),\n          durationRescheduleExample\n        )\n      );\n\n      durationRescheduleLabel.dom.htmlFor = 'durationRescheduleRB';\n\n      var actionChoose = Html.table({\n        cellpadding: 0,\n        cellPadding: 0,\n        cellspacing: 0,\n        cellSpacing: 0,\n      });\n      var actionChooseTbody = Html.tbody();\n\n      var startTimeRescheduleTr = Html.tr();\n      startTimeRescheduleTr.append(Html.td('rescheduleAction', startTimeRescheduleRB));\n      startTimeRescheduleTr.append(\n        Html.td(\n          {className: 'rescheduleAction', style: {paddingRight: pixels(5)}},\n          startTimeRescheduleLabel\n        )\n      );\n      actionChooseTbody.append(startTimeRescheduleTr);\n\n      var durationRescheduleTr = Html.tr();\n      durationRescheduleTr.append(Html.td('rescheduleAction', durationRescheduleRB));\n      durationRescheduleTr.append(\n        Html.td(\n          {className: 'rescheduleAction', style: {paddingRight: pixels(5)}},\n          durationRescheduleLabel\n        )\n      );\n      actionChooseTbody.append(durationRescheduleTr);
1\n\n      actionChoose.append(actionChooseTbody);\n\n      startTimeRescheduleRB.observeClick(function() {\n        if (self.rescheduleAction == 'time') {\n          if (self.fitInnerAction == 'noFit') {\n            self.rescheduleButton.disabledButtonWithTooltip('disable');\n          }\n          self.rescheduleAction = 'none';\n          startTimeRescheduleTr.dom.className = '';\n          startTimeRescheduleRB.dom.checked = false;\n        } else {\n          self.rescheduleButton.disabledButtonWithTooltip('enable');\n          self.rescheduleAction = 'time';\n          startTimeRescheduleTr.dom.className = 'selectedAction';\n          durationRescheduleTr.dom.className = '';\n        }\n      });\n      durationRescheduleRB.observeClick(function() {\n        if (self.rescheduleAction == 'duration') {\n          if (self.fitInnerAction == 'noFit') {\n            self.rescheduleButton.disabledButtonWithTooltip('disable');\n          }\n          self.rescheduleAction = 'none';\n          durationRescheduleTr.dom.className = '';\n          durationRescheduleRB.dom.checked = false;\n        } else {\n          self.rescheduleButton.disabledButtonWithTooltip('enable');\n          self.rescheduleAction = 'duration';\n          durationRescheduleTr.dom.className = 'selectedAction';\n          startTimeRescheduleTr.dom.className = '';\n        }\n      });\n\n      return Html.div('rescheduleSection', actionChooseTitle, actionChoose);\n    },\n\n    /**\n     * Draws the step 2: choose gap between entries\n     */\n    __drawChooseInterval: function() {\n      var self = this;\n      // Step 2: choose interval between entries\n      var intervalTitle = Html.div(\n        'rescheduleTitle',\n        $T('Step 2: Choose time gap between entries')\n      );\n\n      this.minuteInput = Html.input(\n        'text',\n        {style: {width: '3em', textAlign: 'right', marginTop: pixels(5), marginBottom: pixels(5)}},\n        '0'\n      );\n      var timeInputLabel = Html.span({style: {marginLeft: pixels(5)}}, '(minutes)');\n      var intervalInputDiv = Html.div(\n        {style: {textAlign: 'center'}},\n        this.minuteInput,\n        timeInputLabel\n      );\n\n      this.intervalExplanationDiv = Html.div();\n\n      this.minuteInput.observeEvent('change', function(event) {\n        self.__intervalObserver();\n      });\n\n      return Html.div(\n        'rescheduleSection',\n        intervalTitle,\n        intervalInputDiv,\n        this.intervalExplanationDiv\n      );\n    },\n\n    /**\n     * Draws the step 3: choose whether to fit the inner entries or not\n     */\n    __drawFitInner: function() {\n      var self = this;\n      // Step 3: choose to fit or not the inner entries\n      var fitInnerTitle = Html.div(\n        'rescheduleTitle',\n        $T('Step 3: Choose to fit sessions to their content')\n      );\n\n      this.fitInnerCheckBox = Html.checkbox({}, false);\n      this.fitInnerCheckBox.dom.name = 'fitInnerCheckBox';\n\n      var fitInnerLabel = Html.label(\n        {htmlFor: 'fitInnerCheckBox', className: 'rescheduleLabelTitle'},\n        'Fit all the sessions contained on ' + this.__getCurrentDayText() + ' to their content.'\n      );\n      var fitInnerDiv = Html.div(\n        {style: {textAlign: 'center'}},\n        this.fitInnerCheckBox,\n        fitInnerLabel\n      );\n\n      this.fitInnerExplanationDiv = Html.div({\n        className: 'rescheduleLabelDetails',\n        style: {paddingLeft: pixels(30), paddingTop: pixels(8)},\n      });\n      this.fitInnerExplanationDiv.set(\n        'This changes the start and end times of the session blocks occurring on ' +\n          this.__getCurrentDayText() +\n          ' in order to fit their respective content ',\n        Html.strong({}, 'before'),\n        ' performing the rescheduling.'\n      );\n\n      this.fitInnerCheckBox.observeEvent('change', function(event) {\n        self.__fitInnerObserver();\n      });\n\n      return Html.div('fitInnerSection', fitInnerTitle, fitInnerDiv, this.fitInnerExplanationDiv);\n    },\n\n    /**\n     * Function that will be called when the gap between entries changes\n     */\n    __intervalObserver: function() {\
1n      var minutes = this.minuteInput.get();\n\n      var errors = false;\n      if (!IndicoUtil.isInteger(minutes) || minutes < 0) {\n        return;\n      }\n\n      minutes = parseInt(minutes, 10);\n\n      if (minutes === 0) {\n        this.intervalExplanationDiv.set($T('There will be no gaps between consecutive entries.'));\n      } else {\n        var h = Math.floor(minutes / 60);\n        var m = minutes % 60;\n\n        var intervalExplanationText = $T('Entries will be separated by gaps of ');\n        if (h === 1) {\n          intervalExplanationText += $T('1 hour ');\n        } else if (h > 0) {\n          intervalExplanationText += h + $T(' hours ');\n        }\n\n        if (h !== 0 && m !== 0) {\n          intervalExplanationText += $T('and ');\n        }\n\n        if (m === 1) {\n          intervalExplanationText += $T('1 minute.');\n        } else if (m > 0) {\n          intervalExplanationText += m + $T(' minutes.');\n        }\n\n        this.intervalExplanationDiv.set(intervalExplanationText);\n      }\n    },\n\n    /**\n     * Function that will be called when the 'Fit checkbox' gets checked/unchecked\n     */\n    __fitInnerObserver: function() {\n      var checked = this.fitInnerCheckBox.get();\n\n      if (checked) {\n        this.rescheduleButton.disabledButtonWithTooltip('enable');\n        this.fitInnerAction = 'doFit';\n      } else {\n        if (this.rescheduleAction == 'none') {\n          this.rescheduleButton.disabledButtonWithTooltip('disable');\n        }\n        this.fitInnerAction = 'noFit';\n      }\n    },\n\n    _getButtons: function() {\n      var self = this;\n      return [\n        [\n          $T('Reschedule'),\n          function() {\n            self.__reschedule();\n          },\n        ],\n        [\n          $T('Cancel'),\n          function() {\n            self.close();\n          },\n        ],\n      ];\n    },\n\n    /**\n     * Builds the parameter manager that checks validity of fields\n     */\n    __buildParameterManager: function() {\n      this.parameterManager = new IndicoUtil.parameterManager();\n      this.parameterManager.add(this.minuteInput, 'non_negative_int', false);\n    },\n\n    /**\n     * Function called when the user presses the reschedule button\n     */\n    __reschedule: function() {\n      var self = this;\n\n      if (this.parameterManager.check()) {\n        var confirmHandler = function(confirm) {\n          if (!confirm) {\n            return;\n          }\n          var urlArgs = {event_id: self.tt.eventInfo.id};\n          var data = {\n            mode: self.rescheduleAction,\n            gap: +self.minuteInput.get(),\n            day: self.tt.currentDay,\n            fit_blocks: self.fitInnerAction == 'doFit',\n          };\n          if (self.isIntervalTimetable) {\n            data.session_block_id = self.tt.contextInfo.sessionSlotId;\n          } else if (self.isTopLevelTimetable && exists(self.tt.contextInfo.timetableSession)) {\n            data.session_id = self.tt.contextInfo.timetableSession.id;\n          }\n          if (self.tt.isSessionTimetable) {\n            urlArgs.session_id = self.tt.contextInfo.sessionId.toString();\n          }\n          $.ajax({\n            url: build_url(Indico.Urls.Timetable.reschedule, urlArgs),\n            method: 'POST',\n            data: JSON.stringify(data),\n            contentType: 'application/json',\n            complete: IndicoUI.Dialogs.Util.progress($T.gettext('Rescheduling...')),\n            error: handleAjaxError,\n            success: function() {\n              location.reload();\n            },\n          });\n        };\n\n        var confirmText = Html.div(\n          {},\n          Html.div(\n            {},\n            $T(\n              'Are you sure you want to reschedule entries ' +\n                (this.isTopLevelTimetable\n                  ? 'on ' + this.__getCurrentDayText()\n                  : 'of the interval ' + this.__getIntervalTitle()) +\n                '?'\n            )\n          ),\n          Html.div(\n            {},\n            this.fitInnerAction === 'doFit'\n              ? $T('The entries that are part of a session will')\n              : '',\n            this.fitInnerAction === 'doFit'\n              ? this.rescheduleAction === 'none'\n                ? ''\n                : $T(' first')\n              : '',\n            this.fitInnerAction === 'doFit' ? $T(' be fitted to their content.') : ''\n          ),\n          this.rescheduleAction === 'none'\n            ? Html.div({}, '')\n            : Html.div(\n                {},\n                this.fitInnerAction === 'doFit' ? $T('Then, all entries ') : $T('All entr
1ies '),\n                $T(' will have their '),\n                this.rescheduleAction === 'time' ? $T('starting times') : $T('duration'),\n                $T(' changed.')\n              ),\n          Html.br(),\n          Html.div('rescheduleWarning', 'This change cannot be undone.')\n        );\n\n        var confirmPopup = new ConfirmPopup(\n          $T('Please review your choice'),\n          confirmText,\n          confirmHandler\n        );\n        confirmPopup.open();\n      }\n    },\n\n    /**\n     * Draw the dialog\n     */\n    draw: function() {\n      var self = this;\n\n      this.rescheduleButton = this.buttons.eq(0);\n      this.rescheduleButton.disabledButtonWithTooltip({\n        tooltip: $T('Please select the rescheduling type'),\n        disabled: true,\n      });\n      var actionChooseDiv = this.__drawChooseAction();\n      var intervalDiv = this.__drawChooseInterval();\n      var actionFitDiv = '';\n      if (this.isTopLevelTimetable) {\n        actionFitDiv = this.__drawFitInner();\n      }\n\n      this.mainContent = Html.div(\n        {style: {width: pixels(450)}},\n        actionChooseDiv,\n        intervalDiv,\n        actionFitDiv\n      );\n\n      this.__intervalObserver();\n      if (this.isTopLevelTimetable) {\n        this.__fitInnerObserver();\n      }\n      this.__buildParameterManager();\n\n      return this.ExclusivePopupWithButtons.prototype.draw.call(this, this.mainContent);\n    },\n  },\n  /**\n   * Constructor\n   */\n  function(parentTimetable) {\n    this.ExclusivePopupWithButtons($T('Reschedule Entries'));\n    this.tt = parentTimetable;\n\n    this.isTopLevelTimetable = exists(this.tt.TopLevelManagementTimeTable);\n    this.isIntervalTimetable = exists(this.tt.IntervalManagementTimeTable);\n\n    this.rescheduleAction = 'none';\n    this.timeInput = null;\n    this.fitInnerAction = 'noFit';\n    this.rescheduleButton = null;\n  }\n);\n\n/**\n * Dialog to fit a session\n * @param {Timetable} parentTimetable The timetable object from which this dialog is launched.\n */\ntype(\n  'FitInnerTimetableDialog',\n  ['ConfirmPopup'],\n  {\n    /**\n     * Returns the title of the session block\n     */\n    __generateSessionBlockTitle: function() {\n      if (this.tt.contextInfo.slotTitle) {\n        return '\"{0}: {1}\"'.format(this.tt.contextInfo.title, this.tt.contextInfo.slotTitle);\n      } else {\n        return '\"{0}\"'.format(this.tt.contextInfo.title);\n      }\n    },\n\n    /**\n     * Builds the content for the ConfirmPopup\n     */\n    __getContent: function() {\n      var msg = $T.gettext(\n        'This will change the starting and ending times of the session block {0} so that it encompasses all entries defined in its timetable.'\n      );\n      return Html.div(\n        'fitInnerTimetableDialog',\n        msg.format(this.__generateSessionBlockTitle()),\n        Html.br(),\n        $T.gettext('Are you sure you want to proceed?')\n      );\n    },\n\n    /**\n     * Handler when the user closes or presses OK / Cancel\n     */\n    __handler: function(confirm) {\n      var self = this;\n      if (!confirm || !this.tt.IntervalManagementTimeTable) {\n        return;\n      }\n      var urlArgs = {\n        event_id: self.tt.contextInfo.conferenceId,\n        block_id: self.tt.contextInfo.sessionSlotId,\n      };\n      if (self.tt.isSessionTimetable) {\n        urlArgs.session_id = self.tt.contextInfo.sessionId;\n      }\n      $.ajax({\n        url: build_url(Indico.Urls.Timetable.sessionBlocks.fit, urlArgs),\n        method: 'POST',\n        complete: IndicoUI.Dialogs.Util.progress($T.gettext('Fitting...')),\n        error: handleAjaxError,\n        success: function() {\n          location.reload();\n        },\n      });\n    },\n  },\n\n  /**\n   * Constructor\n   */\n  function(parentTimetable) {\n    this.tt = parentTimetable;\n    this.ConfirmPopup($T('Fit timetable to content'), this.__getContent(), this.__handler);\n  }\n);\n\ntype(\n  'SessionSectionPopupMenu',\n  ['SectionPopupMenu'],\n  {\n    _processItem: function(pair) {\n      var self = this;\n      var value = pair.get();\n      var color = null;\n      var title = null;\n\n      if (exists(value.title)) {\n        title = value.title;\n      } else {\n        title = pair.key;\n      }\n\n      if (exists(value.color)) {\n        color = value.color;\n        value = value.func;\n      }\n\n      var colorSquare = null;\n      if (color !== null) {\n        colorSquare = Html.div({\n          style: {\
1n            backgroundColor: color,\n            color: color,\n            cssFloat: 'right',\n            width: '15px',\n            height: '15px',\n          },\n        });\n      }\n\n      var link = Html.a(\n        {\n          className: 'fake-link',\n          style: {display: 'inline', padding: '0 4px 0 0'},\n        },\n        title\n      );\n      var divInput = Html.div({}, colorSquare, link);\n\n      if (typeof value == 'string') {\n        link.setAttribute('href', value);\n        if (self.closeOnClick) {\n          link.observeClick(function() {\n            self.close();\n          });\n        }\n      } else {\n        link.observeClick(\n          value.PopupWidget\n            ? function(e) {\n                if (self.selected) {\n                  self.selected.dom.className = null;\n                  self.selected = null;\n                }\n\n                link.dom.className = 'selected';\n                self.selected = link;\n\n                var pos = listItem.getAbsolutePosition();\n\n                each(self.items, function(item, key) {\n                  if (item.PopupWidget && item.isOpen()) {\n                    item.close();\n                  }\n                });\n\n                value.open(pos.x + (value.alignRight ? 0 : link.dom.offsetWidth), pos.y - 1);\n\n                return false;\n              }\n            : function() {\n                // assume it's a callback function\n                value(self);\n                if (self.closeOnClick) {\n                  self.close();\n                }\n              }\n        );\n      }\n\n      var listItem = Html.li({style: {marginBottom: '2px'}}, divInput);\n      return listItem;\n    },\n  },\n\n  function(items, chainElements, cssClass, closeOnClick, alignRight, closeHandler) {\n    this.SectionPopupMenu(items, chainElements, cssClass, closeOnClick, alignRight, closeHandler);\n  }\n);\n"},7603:n=>{n.exports="// This file is part of Indico.\n// Copyright (C) 2002 - 2026 CERN\n//\n// Indico is free software; you can redistribute it and/or\n// modify it under the terms of the MIT License; see the\n// LICENSE file for more details.\n\ntype('UndoMixin', [], {\n  updateUndoDiv: function(tt_status_info) {\n    /* A \"button\" that appears after an action is performed */\n    // TODO: remove the class 'hidden' once Undo is working fine again\n    var undo_contents = $('<a href=\"#\" class=\"i-button icon-undo warning hidden\"/>')\n      .text($T('Undo last operation'))\n      .click(function() {\n        // \"Undo\" should be executed over the _current_ timetable, not the original source of\n        // the event. Being so, we call a global event that will then invoke the correct TT\n        // management actions object\n\n        $('body').trigger('timetable_undo');\n        return false;\n      });\n    var elem = tt_status_info || $('#tt_status_info');\n    if ($(window).data('undo')) {\n      elem.show().html($('<div class=\"group right\"/>').append(undo_contents));\n    } else {\n      elem.hide();\n    }\n  },\n\n  enableUndo: function(undoLabel, savedData) {\n    $(window).data('undo', {\n      // identifier for the operation\n      label: undoLabel,\n      // store current event data\n      data: $.extend(true, {}, savedData),\n    });\n    this.updateUndoDiv();\n  },\n});\n\nfunction highlight_undo(elemId) {\n  var elem = activeTT.get_elem(elemId);\n\n  if (\n    elem.offset().top > $(window).scrollTop() + $(window).height() ||\n    elem.offset().top + elem.height() < $(window).scrollTop()\n  ) {\n    $('html, body').animate(\n      {\n        scrollTop: elem.offset().top - 150,\n      },\n      500\n    );\n  }\n  elem.effect('pulsate', {times: 3}, 500);\n}\n\nfunction undo_action() {\n  var undo_info = $(window).data('undo');\n  var data = undo_info.data;\n  var management = activeTT.managementActions;\n  var ordinalStartDate = Util.formatDateTime(\n    data.eventData.startDate,\n    IndicoDateTimeFormats.Ordinal\n  );\n  var dfr;\n\n  if (undo_info.label == 'placementChange' || undo_info.label == 'resize') {\n    dfr = management.editEntryStartEndDate(\n      Util.formatDateTime(data.eventData.startDate, IndicoDateTimeFormats.Server),\n      Util.formatDateTime(data.eventData.endDate, IndicoDateTimeFormats.Server),\n      data.eventData,\n      data.shifted,\n      null\n    );\n  } else if (undo_info.label == 'drop') {\n    // block (session/day) where entry used to be located\n    var oldBlock = data.eventData.sessionId\n      ? data.eventData.sessionId + ':' + data.eventData.sessionSlotId\n      : 'conf:' + ordinalStartDate;\n    var oldStartDate = Util.formatDateTime(data.eventData.startDate, IndicoDateTimeFormats.Server);\n    dfr = management.moveToSession(data.entry, oldBlock, null, oldStartDate);\n  }
1\n  return dfr.done(function() {\n    highlight_undo(data.eventData.id);\n  });\n}\n\nfunction goto_slot(slotId) {\n  return activeTT.switchToInterval(slotId).then(function() {\n    return undo_action();\n  });\n}\n\nfunction goto_origin(data) {\n  var ordinalStartDate = Util.formatDateTime(\n    data.eventData.startDate,\n    IndicoDateTimeFormats.Ordinal\n  );\n  var parentTT = activeTT.parentTimetable ? activeTT.parentTimetable : activeTT;\n  var inSlot = !!activeTT.parentTimetable;\n  var toSlot = !!data.eventData.sessionId && data.eventData.entryType == 'Contribution';\n\n  var goto_day = function(day) {\n    var next_step = function() {\n      if (toSlot) {\n        var slotId = 's' + data.eventData.sessionId + 'l' + data.eventData.sessionSlotId;\n        return goto_slot(slotId);\n      } else {\n        return undo_action();\n      }\n    };\n\n    if (parentTT.currentDay != day) {\n      // wrong day? change it.\n      return parentTT.setSelectedTab(day).then(next_step);\n    } else {\n      return next_step();\n    }\n  };\n\n  if (inSlot) {\n    if (\n      toSlot &&\n      activeTT.contextInfo.id == 's' + data.eventData.sessionId + 'l' + data.eventData.sessionSlotId\n    ) {\n      // if we are in a slot which happens to be the one we want to go to\n      return undo_action();\n    } else {\n      // otherwise, we are still in a slot but we want to go somewhere else\n      // first step, go up to top level\n      return parentTT.switchToTopLevel().then(function() {\n        // then go to desired day\n        return goto_day(ordinalStartDate);\n      });\n    }\n  } else if (!inSlot) {\n    // ok, we are not in a slot\n    // we have to go to the correct day first (regardless of where we want to go)\n    return goto_day(ordinalStartDate);\n  }\n}\n\n$(function() {\n  if ($('#timetableDiv').data('mode') != 'management') {\n    return;\n  }\n  $('body')\n    .bind('timetable_undo', function() {\n      // global event handler, executes undo on whatever timetable is active at the time\n\n      var undo_info = $(window).data('undo');\n      var data = undo_info.data;\n      var ordinalStartDate = Util.formatDateTime(\n        data.eventData.startDate,\n        IndicoDateTimeFormats.Ordinal\n      );\n\n      goto_origin(data).done(function() {\n        // at the end, just remove the undo info\n        $(window).removeData('undo');\n      });\n    })\n    .bind('timetable_ready', function(event, tt) {\n      // each time the timetable reloads, update the undo\n      tt.updateUndoDiv();\n    });\n});\n"},35184:n=>{n.exports=function(n){function e(n){"undefined"!=typeof console&&(console.error||console.log)("[Script Loader]",n)}try{"undefined"!=typeof execScript&&"undefined"!=typeof attachEvent&&"undefined"==typeof addEventListener?execScript(n):"undefined"!=typeof eval?eval.call(null,n):e("EvalError: No eval function available")}catch(n){e(n)}}}},n=>{n.O(0,[4644,4394,2076],(()=>{return e=74260,n(n.s=e);var e}));n.O()}]);
2//# sourceMappingURL=main.357543f6.bundle.js.map

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.