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, '&').replace(/</g, '<').replace(/>/g, '>');\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.