1diff --git a/dist/converse.js b/dist/converse.js
2index ada17aa..86b1874 100644
3--- a/dist/converse.js
4+++ b/dist/converse.js
5@@ -24541,1554 +24541,15 @@ module.exports = isObjectLike;
6 				__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
7   } else { var bsn; }
8 }(this, function () {
9-
10-  /* Native Javascript for Bootstrap 4 | Internal Utility Functions
11-  ----------------------------------------------------------------*/
12-  "use strict";
13-
14-  // globals
15-  var globalObject = typeof global !== 'undefined' ? global : this||window,
16-    DOC = document, HTML = DOC.documentElement, body = 'body', // allow the library to be used in <head>
17-
18-    // Native Javascript for Bootstrap Global Object
19-    BSN = globalObject.BSN = {},
20-    supports = BSN.supports = [],
21-
22-    // function toggle attributes
23-    dataToggle    = 'data-toggle',
24-    dataDismiss   = 'data-dismiss',
25-    dataSpy       = 'data-spy',
26-    dataRide      = 'data-ride',
27-
28-    // components
29-    stringAlert     = 'Alert',
30-    stringButton    = 'Button',
31-    stringCarousel  = 'Carousel',
32-    stringCollapse  = 'Collapse',
33-    stringDropdown  = 'Dropdown',
34-    stringModal     = 'Modal',
35-    stringPopover   = 'Popover',
36-    stringScrollSpy = 'ScrollSpy',
37-    stringTab       = 'Tab',
38-    stringTooltip   = 'Tooltip',
39-    stringToast     = 'Toast',
40-
41-    // options DATA API
42-    dataAutohide      = 'data-autohide',
43-    databackdrop      = 'data-backdrop',
44-    dataKeyboard      = 'data-keyboard',
45-    dataTarget        = 'data-target',
46-    dataInterval      = 'data-interval',
47-    dataHeight        = 'data-height',
48-    dataPause         = 'data-pause',
49-    dataTitle         = 'data-title',
50-    dataOriginalTitle = 'data-original-title',
51-    dataDismissible   = 'data-dismissible',
52-    dataTrigger       = 'data-trigger',
53-    dataAnimation     = 'data-animation',
54-    dataContainer     = 'data-container',
55-    dataPlacement     = 'data-placement',
56-    dataDelay         = 'data-delay',
57-
58-    // option keys
59-    backdrop = 'backdrop', keyboard = 'keyboard', delay = 'delay',
60-    content = 'content', target = 'target', currentTarget = 'currentTarget',
61-    interval = 'interval', pause = 'pause', animation = 'animation',
62-    placement = 'placement', container = 'container',
63-
64-    // box model
65-    offsetTop    = 'offsetTop',      offsetBottom   = 'offsetBottom',
66-    offsetLeft   = 'offsetLeft',
67-    scrollTop    = 'scrollTop',      scrollLeft     = 'scrollLeft',
68-    clientWidth  = 'clientWidth',    clientHeight   = 'clientHeight',
69-    offsetWidth  = 'offsetWidth',    offsetHeight   = 'offsetHeight',
70-    innerWidth   = 'innerWidth',     innerHeight    = 'innerHeight',
71-    scrollHeight = 'scrollHeight',   height         = 'height',
72-
73-    // aria
74-    ariaExpanded = 'aria-expanded',
75-    ariaHidden   = 'aria-hidden',
76-    ariaSelected = 'aria-selected',
77-
78-    // event names
79-    clickEvent    = 'click',
80-    hoverEvent    = 'hover',
81-    keydownEvent  = 'keydown',
82-    keyupEvent    = 'keyup',
83-    resizeEvent   = 'resize',
84-    scrollEvent   = 'scroll',
85-    // originalEvents
86-    showEvent     = 'show',
87-    shownEvent    = 'shown',
88-    hideEvent     = 'hide',
89-    hiddenEvent   = 'hidden',
90-    closeEvent    = 'close',
91-    closedEvent   = 'closed',
92-    slidEvent     = 'slid',
93-    slideEvent    = 'slide',
94-    changeEvent   = 'change',
95-
96-    // other
97-    getAttribute           = 'getAttribute',
98-    setAttribute           = 'setAttribute',
99-    hasAttribute           = 'hasAttribute',
100-    createElement          = 'createElement',
101-    appendChild            = 'appendChild',
102-    innerHTML              = 'innerHTML',
103-    getElementsByTagName   = 'getElementsByTagName',
104-    preventDefault         = 'preventDefault',
105-    getBoundingClientRect  = 'getBoundingClientRect',
106-    querySelectorAll       = 'querySelectorAll',
107-    getElementsByCLASSNAME = 'getElementsByClassName',
108-    getComputedStyle       = 'getComputedStyle',
109-
110-    indexOf      = 'indexOf',
111-    parentNode   = 'parentNode',
112-    length       = 'length',
113-    toLowerCase  = 'toLowerCase',
114-    Transition   = 'Transition',
115-    Duration     = 'Duration',
116-    Webkit       = 'Webkit',
117-    style        = 'style',
118-    push         = 'push',
119-    tabindex     = 'tabindex',
120-    contains     = 'contains',
121-
122-    active     = 'active',
123-    showClass  = 'show',
124-    collapsing = 'collapsing',
125-    disabled   = 'disabled',
126-    loading    = 'loading',
127-    left       = 'left',
128-    right      = 'right',
129-    top        = 'top',
130-    bottom     = 'bottom',
131-
132-    // tooltip / popover
133-    mouseHover = ('onmouseleave' in DOC) ? [ 'mouseenter', 'mouseleave'] : [ 'mouseover', 'mouseout' ],
134-    tipPositions = /\b(top|bottom|left|right)+/,
135-
136-    // modal
137-    modalOverlay = 0,
138-    fixedTop = 'fixed-top',
139-    fixedBottom = 'fixed-bottom',
140-
141-    // transitionEnd since 2.0.4
142-    supportTransitions = Webkit+Transition in HTML[style] || Transition[toLowerCase]() in HTML[style],
143-    transitionEndEvent = Webkit+Transition in HTML[style] ? Webkit[toLowerCase]()+Transition+'End' : Transition[toLowerCase]()+'end',
144-    transitionDuration = Webkit+Duration in HTML[style] ? Webkit[toLowerCase]()+Transition+Duration : Transition[toLowerCase]()+Duration,
145-
146-    // touch since 2.0.26
147-    touchEvents = { start: 'touchstart', end: 'touchend', move:'touchmove' },
148-
149-    // set new focus element since 2.0.3
150-    setFocus = function(element){
151-      element.focus ? element.focus() : element.setActive();
152-    },
153-
154-    // class manipulation, since 2.0.0 requires polyfill.js
155-    addClass = function(element,classNAME) {
156-      element.classList.add(classNAME);
157-    },
158-    removeClass = function(element,classNAME) {
159-      element.classList.remove(classNAME);
160-    },
161-    hasClass = function(element,classNAME){ // since 2.0.0
162-      return element.classList[contains](classNAME);
163-    },
164-
165-    // selection methods
166-    getElementsByClassName = function(element,classNAME) { // returns Array
167-      return [].slice.call(element[getElementsByCLASSNAME]( classNAME ));
168-    },
169-    queryElement = function (selector, parent) {
170-      var lookUp = parent ? parent : DOC;
171-      return typeof selector === 'object' ? selector : lookUp.querySelector(selector);
172-    },
173-    getClosest = function (element, selector) { //element is the element and selector is for the closest parent element to find
174-      // source http://gomakethings.com/climbing-up-and-down-the-dom-tree-with-vanilla-javascript/
175-      var firstChar = selector.charAt(0), selectorSubstring = selector.substr(1);
176-      if ( firstChar === '.' ) {// If selector is a class
177-        for ( ; element && element !== DOC; element = element[parentNode] ) { // Get closest match
178-          if ( queryElement(selector,element[parentNode]) !== null && hasClass(element,selectorSubstring) ) { return element; }
179-        }
180-      } else if ( firstChar === '#' ) { // If selector is an ID
181-        for ( ; element && element !== DOC; element = element[parentNode] ) { // Get closest match
182-          if ( element.id === selectorSubstring ) { return element; }
183-        }
184-      }
185-      return false;
186-    },
187-
188-    // event attach jQuery style / trigger  since 1.2.0
189-    on = function (element, event, handler) {
190-      element.addEventListener(event, handler, false);
191-    },
192-    off = function(element, event, handler) {
193-      element.removeEventListener(event, handler, false);
194-    },
195-    one = function (element, event, handler) { // one since 2.0.4
196-      on(element, event, function handlerWrapper(e){
197-        handler(e);
198-        off(element, event, handlerWrapper);
199-      });
200-    },
201-    getTransitionDurationFromElement = function(element) {
202-      var duration = supportTransitions ? globalObject[getComputedStyle](element)[transitionDuration] : 0;
203-      duration = parseFloat(duration);
204-      duration = typeof duration === 'number' && !isNaN(duration) ? duration * 1000 : 0;
205-      return duration; // we take a short offset to make sure we fire on the next frame after animation
206-    },
207-    emulateTransitionEnd = function(element,handler){ // emulateTransitionEnd since 2.0.4
208-      var called = 0, duration = getTransitionDurationFromElement(element);
209-      duration ? one(element, transitionEndEvent, function(e){ !called && handler(e), called = 1; })
210-               : setTimeout(function() { !called && handler(), called = 1; }, 17);
211-    },
212-    bootstrapCustomEvent = function (eventName, componentName, related) {
213-      var OriginalCustomEvent = new CustomEvent( eventName + '.bs.' + componentName);
214-      OriginalCustomEvent.relatedTarget = related;
215-      this.dispatchEvent(OriginalCustomEvent);
216-    },
217-
218-    // tooltip / popover stuff
219-    getScroll = function() { // also Affix and ScrollSpy uses it
220-      return {
221-        y : globalObject.pageYOffset || HTML[scrollTop],
222-        x : globalObject.pageXOffset || HTML[scrollLeft]
223-      }
224-    },
225-    styleTip = function(link,element,position,parent) { // both popovers and tooltips (target,tooltip,placement,elementToAppendTo)
226-      var elementDimensions = { w : element[offsetWidth], h: element[offsetHeight] },
227-          windowWidth = (HTML[clientWidth] || DOC[body][clientWidth]),
228-          windowHeight = (HTML[clientHeight] || DOC[body][clientHeight]),
229-          rect = link[getBoundingClientRect](),
230-          scroll = parent === DOC[body] ? getScroll() : { x: parent[offsetLeft] + parent[scrollLeft], y: parent[offsetTop] + parent[scrollTop] },
231-          linkDimensions = { w: rect[right] - rect[left], h: rect[bottom] - rect[top] },
232-          isPopover = hasClass(element,'popover'),
233-          topPosition, leftPosition,
234-
235-          arrow = queryElement('.arrow',element),
236-          arrowTop, arrowLeft, arrowWidth, arrowHeight,
237-
238-          halfTopExceed = rect[top] + linkDimensions.h/2 - elementDimensions.h/2 < 0,
239-          halfLeftExceed = rect[left] + linkDimensions.w/2 - elementDimensions.w/2 < 0,
240-          halfRightExceed = rect[left] + elementDimensions.w/2 + linkDimensions.w/2 >= windowWidth,
241-          halfBottomExceed = rect[top] + elementDimensions.h/2 + linkDimensions.h/2 >= windowHeight,
242-          topExceed = rect[top] - elementDimensions.h < 0,
243-          leftExceed = rect[left] - elementDimensions.w < 0,
244-          bottomExceed = rect[top] + elementDimensions.h + linkDimensions.h >= windowHeight,
245-          rightExceed = rect[left] + elementDimensions.w + linkDimensions.w >= windowWidth;
246-
247-      // recompute position
248-      position = (position === left || position === right) && leftExceed && rightExceed ? top : position; // first, when both left and right limits are exceeded, we fall back to top|bottom
249-      position = position === top && topExceed ? bottom : position;
250-      position = position === bottom && bottomExceed ? top : position;
251-      position = position === left && leftExceed ? right : position;
252-      position = position === right && rightExceed ? left : position;
253-
254-      // update tooltip/popover class
255-      element.className[indexOf](position) === -1 && (element.className = element.className.replace(tipPositions,position));
256-
257-      // we check the computed width & height and update here
258-      arrowWidth = arrow[offsetWidth]; arrowHeight = arrow[offsetHeight];
259-
260-      // apply styling to tooltip or popover
261-      if ( position === left || position === right ) { // secondary|side positions
262-        if ( position === left ) { // LEFT
263-          leftPosition = rect[left] + scroll.x - elementDimensions.w - ( isPopover ? arrowWidth : 0 );
264-        } else { // RIGHT
265-          leftPosition = rect[left] + scroll.x + linkDimensions.w;
266-        }
267-
268-        // adjust top and arrow
269-        if (halfTopExceed) {
270-          topPosition = rect[top] + scroll.y;
271-          arrowTop = linkDimensions.h/2 - arrowWidth;
272-        } else if (halfBottomExceed) {
273-          topPosition = rect[top] + scroll.y - elementDimensions.h + linkDimensions.h;
274-          arrowTop = elementDimensions.h - linkDimensions.h/2 - arrowWidth;
275-        } else {
276-          topPosition = rect[top] + scroll.y - elementDimensions.h/2 + linkDimensions.h/2;
277-          arrowTop = elementDimensions.h/2 - (isPopover ? arrowHeight*0.9 : arrowHeight/2);
278-        }
279-      } else if ( position === top || position === bottom ) { // primary|vertical positions
280-        if ( position === top) { // TOP
281-          topPosition =  rect[top] + scroll.y - elementDimensions.h - ( isPopover ? arrowHeight : 0 );
282-        } else { // BOTTOM
283-          topPosition = rect[top] + scroll.y + linkDimensions.h;
284-        }
285-        // adjust left | right and also the arrow
286-        if (halfLeftExceed) {
287-          leftPosition = 0;
288-          arrowLeft = rect[left] + linkDimensions.w/2 - arrowWidth;
289-        } else if (halfRightExceed) {
290-          leftPosition = windowWidth - elementDimensions.w*1.01;
291-          arrowLeft = elementDimensions.w - ( windowWidth - rect[left] ) + linkDimensions.w/2 - arrowWidth/2;
292-        } else {
293-          leftPosition = rect[left] + scroll.x - elementDimensions.w/2 + linkDimensions.w/2;
294-          arrowLeft = elementDimensions.w/2 - arrowWidth/2;
295-        }
296-      }
297-
298-      // apply style to tooltip/popover and its arrow
299-      element[style][top] = topPosition + 'px';
300-      element[style][left] = leftPosition + 'px';
301-
302-      arrowTop && (arrow[style][top] = arrowTop + 'px');
303-      arrowLeft && (arrow[style][left] = arrowLeft + 'px');
304-    };
305-
306-  BSN.version = '2.0.26';
307-
308-  /* Native Javascript for Bootstrap 4 | Alert
309-  -------------------------------------------*/
310-
311-  // ALERT DEFINITION
312-  // ================
313-  var Alert = function( element ) {
314-
315-    // initialization element
316-    element = queryElement(element);
317-
318-    // bind, target alert, duration and stuff
319-    var self = this, component = 'alert',
320-      alert = getClosest(element,'.'+component),
321-      triggerHandler = function(){ hasClass(alert,'fade') ? emulateTransitionEnd(alert,transitionEndHandler) : transitionEndHandler(); },
322-      // handlers
323-      clickHandler = function(e){
324-        alert = getClosest(e[target],'.'+component);
325-        element = queryElement('['+dataDismiss+'="'+component+'"]',alert);
326-        element && alert && (element === e[target] || element[contains](e[target])) && self.close();
327-      },
328-      transitionEndHandler = function(){
329-        bootstrapCustomEvent.call(alert, closedEvent, component);
330-        off(element, clickEvent, clickHandler); // detach it's listener
331-        alert[parentNode].removeChild(alert);
332-      };
333-
334-    // public method
335-    this.close = function() {
336-      if ( alert && element && hasClass(alert,showClass) ) {
337-        bootstrapCustomEvent.call(alert, closeEvent, component);
338-        removeClass(alert,showClass);
339-        alert && triggerHandler();
340-      }
341-    };
342-
343-    // init
344-    if ( !(stringAlert in element ) ) { // prevent adding event handlers twice
345-      on(element, clickEvent, clickHandler);
346-    }
347-    element[stringAlert] = self;
348-  };
349-
350-  // ALERT DATA API
351-  // ==============
352-  supports[push]([stringAlert, Alert, '['+dataDismiss+'="alert"]']);
353-
354-
355-  /* Native Javascript for Bootstrap 4 | Button
356-  ---------------------------------------------*/
357-
358-  // BUTTON DEFINITION
359-  // ===================
360-  var Button = function( element ) {
361-
362-    // initialization element
363-    element = queryElement(element);
364-
365-    // constant
366-    var toggled = false, // toggled makes sure to prevent triggering twice the change.bs.button events
367-
368-        // strings
369-        component = 'button',
370-        checked = 'checked',
371-        reset = 'reset',
372-        LABEL = 'LABEL',
373-        INPUT = 'INPUT',
374-
375-      // private methods
376-      keyHandler = function(e){
377-        var key = e.which || e.keyCode;
378-        key === 32 && e[target] === DOC.activeElement && toggle(e);
379-      },
380-      preventScroll = function(e){
381-        var key = e.which || e.keyCode;
382-        key === 32 && e[preventDefault]();
383-      },
384-      toggle = function(e) {
385-        var label = e[target].tagName === LABEL ? e[target] : e[target][parentNode].tagName === LABEL ? e[target][parentNode] : null; // the .btn label
386-
387-        if ( !label ) return; //react if a label or its immediate child is clicked
388-
389-        var eventTarget = e[target], // the button itself, the target of the handler function
390-          labels = getElementsByClassName(eventTarget[parentNode],'btn'), // all the button group buttons
391-          input = label[getElementsByTagName](INPUT)[0];
392-
393-        if ( !input ) return; //return if no input found
394-
395-        // manage the dom manipulation
396-        if ( input.type === 'checkbox' ) { //checkboxes
397-          if ( !input[checked] ) {
398-            addClass(label,active);
399-            input[getAttribute](checked);
400-            input[setAttribute](checked,checked);
401-            input[checked] = true;
402-          } else {
403-            removeClass(label,active);
404-            input[getAttribute](checked);
405-            input.removeAttribute(checked);
406-            input[checked] = false;
407-          }
408-
409-          if (!toggled) { // prevent triggering the event twice
410-            toggled = true;
411-            bootstrapCustomEvent.call(input, changeEvent, component); //trigger the change for the input
412-            bootstrapCustomEvent.call(element, changeEvent, component); //trigger the change for the btn-group
413-          }
414-        }
415-
416-        if ( input.type === 'radio' && !toggled ) { // radio buttons
417-          if ( !input[checked] ) { // don't trigger if already active
418-            addClass(label,active);
419-            input[setAttribute](checked,checked);
420-            input[checked] = true;
421-            bootstrapCustomEvent.call(input, changeEvent, component); //trigger the change for the input
422-            bootstrapCustomEvent.call(element, changeEvent, component); //trigger the change for the btn-group
423-
424-            toggled = true;
425-            for (var i = 0, ll = labels[length]; i<ll; i++) {
426-              var otherLabel = labels[i], otherInput = otherLabel[getElementsByTagName](INPUT)[0];
427-              if ( otherLabel !== label && hasClass(otherLabel,active) )  {
428-                removeClass(otherLabel,active);
429-                otherInput.removeAttribute(checked);
430-                otherInput[checked] = false;
431-                bootstrapCustomEvent.call(otherInput, changeEvent, component); // trigger the change
432-              }
433-            }
434-          }
435-        }
436-        setTimeout( function() { toggled = false; }, 50 );
437-      };
438-
439-    // init
440-    if ( !( stringButton in element ) ) { // prevent adding event handlers twice
441-      on( element, clickEvent, toggle );
442-      queryElement('['+tabindex+']',element) && on( element, keyupEvent, keyHandler ),
443-                                                on( element, keydownEvent, preventScroll );
444-    }
445-
446-    // activate items on load
447-    var labelsToACtivate = getElementsByClassName(element, 'btn'), lbll = labelsToACtivate[length];
448-    for (var i=0; i<lbll; i++) {
449-      !hasClass(labelsToACtivate[i],active) && queryElement('input:checked',labelsToACtivate[i])
450-                                            && addClass(labelsToACtivate[i],active);
451-    }
452-    element[stringButton] = this;
453-  };
454-
455-  // BUTTON DATA API
456-  // =================
457-  supports[push]( [ stringButton, Button, '['+dataToggle+'="buttons"]' ] );
458-
459-
460-  /* Native Javascript for Bootstrap 4 | Collapse
461-  -----------------------------------------------*/
462-
463-  // COLLAPSE DEFINITION
464-  // ===================
465-  var Collapse = function( element, options ) {
466-
467-    // initialization element
468-    element = queryElement(element);
469-
470-    // set options
471-    options = options || {};
472-
473-    // event targets and constants
474-    var accordion = null, collapse = null, self = this,
475-      accordionData = element[getAttribute]('data-parent'),
476-      activeCollapse, activeElement,
477-
478-      // component strings
479-      component = 'collapse',
480-      collapsed = 'collapsed',
481-      isAnimating = 'isAnimating',
482-
483-      // private methods
484-      openAction = function(collapseElement,toggle) {
485-        bootstrapCustomEvent.call(collapseElement, showEvent, component);
486-        collapseElement[isAnimating] = true;
487-        addClass(collapseElement,collapsing);
488-        removeClass(collapseElement,component);
489-        collapseElement[style][height] = collapseElement[scrollHeight] + 'px';
490-
491-        emulateTransitionEnd(collapseElement, function() {
492-          collapseElement[isAnimating] = false;
493-          collapseElement[setAttribute](ariaExpanded,'true');
494-          toggle[setAttribute](ariaExpanded,'true');
495-          removeClass(collapseElement,collapsing);
496-          addClass(collapseElement, component);
497-          addClass(collapseElement,showClass);
498-          collapseElement[style][height] = '';
499-          bootstrapCustomEvent.call(collapseElement, shownEvent, component);
500-        });
501-      },
502-      closeAction = function(collapseElement,toggle) {
503-        bootstrapCustomEvent.call(collapseElement, hideEvent, component);
504-        collapseElement[isAnimating] = true;
505-        collapseElement[style][height] = collapseElement[scrollHeight] + 'px'; // set height first
506-        removeClass(collapseElement,component);
507-        removeClass(collapseElement,showClass);
508-        addClass(collapseElement,collapsing);
509-        collapseElement[offsetWidth]; // force reflow to enable transition
510-        collapseElement[style][height] = '0px';
511-
512-        emulateTransitionEnd(collapseElement, function() {
513-          collapseElement[isAnimating] = false;
514-          collapseElement[setAttribute](ariaExpanded,'false');
515-          toggle[setAttribute](ariaExpanded,'false');
516-          removeClass(collapseElement,collapsing);
517-          addClass(collapseElement,component);
518-          collapseElement[style][height] = '';
519-          bootstrapCustomEvent.call(collapseElement, hiddenEvent, component);
520-        });
521-      },
522-      getTarget = function() {
523-        var href = element.href && element[getAttribute]('href'),
524-          parent = element[getAttribute](dataTarget),
525-          id = href || ( parent && parent.charAt(0) === '#' ) && parent;
526-        return id && queryElement(id);
527-      };
528-
529-    // public methods
530-    this.toggle = function(e) {
531-      e[preventDefault]();
532-      if (!hasClass(collapse,showClass)) { self.show(); }
533-      else { self.hide(); }
534-    };
535-    this.hide = function() {
536-      if ( collapse[isAnimating] ) return;
537-      closeAction(collapse,element);
538-      addClass(element,collapsed);
539-    };
540-    this.show = function() {
541-      if ( accordion ) {
542-        activeCollapse = queryElement('.'+component+'.'+showClass,accordion);
543-        activeElement = activeCollapse && (queryElement('['+dataTarget+'="#'+activeCollapse.id+'"]',accordion)
544-                      || queryElement('[href="#'+activeCollapse.id+'"]',accordion) );
545-      }
546-
547-      if ( !collapse[isAnimating] || activeCollapse && !activeCollapse[isAnimating] ) {
548-        if ( activeElement && activeCollapse !== collapse ) {
549-          closeAction(activeCollapse,activeElement);
550-          addClass(activeElement,collapsed);
551-        }
552-        openAction(collapse,element);
553-        removeClass(element,collapsed);
554-      }
555-    };
556-
557-    // init
558-    if ( !(stringCollapse in element ) ) { // prevent adding event handlers twice
559-      on(element, clickEvent, self.toggle);
560-    }
561-    collapse = getTarget();
562-    collapse[isAnimating] = false;  // when true it will prevent click handlers
563-    accordion = queryElement(options.parent) || accordionData && getClosest(element, accordionData);
564-    element[stringCollapse] = self;
565-  };
566-
567-  // COLLAPSE DATA API
568-  // =================
569-  supports[push]( [ stringCollapse, Collapse, '['+dataToggle+'="collapse"]' ] );
570-
571-
572-  /* Native Javascript for Bootstrap 4 | Dropdown
573-  ----------------------------------------------*/
574-
575-  // DROPDOWN DEFINITION
576-  // ===================
577-  var Dropdown = function( element, option ) {
578-
579-    // initialization element
580-    element = queryElement(element);
581-
582-    // set option
583-    this.persist = option === true || element[getAttribute]('data-persist') === 'true' || false;
584-
585-    // constants, event targets, strings
586-    var self = this, children = 'children',
587-      parent = element[parentNode],
588-      component = 'dropdown', open = 'open',
589-      relatedTarget = null,
590-      menu = queryElement('.dropdown-menu', parent),
591-      menuItems = (function(){
592-        var set = menu[children], newSet = [];
593-        for ( var i=0; i<set[length]; i++ ){
594-          set[i][children][length] && (set[i][children][0].tagName === 'A' && newSet[push](set[i][children][0]));
595-          set[i].tagName === 'A' && newSet[push](set[i]);
596-        }
597-        return newSet;
598-      })(),
599-
600-      // preventDefault on empty anchor links
601-      preventEmptyAnchor = function(anchor){
602-        (anchor.href && anchor.href.slice(-1) === '#' || anchor[parentNode] && anchor[parentNode].href
603-          && anchor[parentNode].href.slice(-1) === '#') && this[preventDefault]();
604-      },
605-
606-      // toggle dismissible events
607-      toggleDismiss = function(){
608-        var type = element[open] ? on : off;
609-        type(DOC, clickEvent, dismissHandler);
610-        type(DOC, keydownEvent, preventScroll);
611-        type(DOC, keyupEvent, keyHandler);
612-      },
613-
614-      // handlers
615-      dismissHandler = function(e) {
616-        var eventTarget = e[target], hasData = eventTarget && (stringDropdown in eventTarget || stringDropdown in eventTarget[parentNode]);
617-        if ( (eventTarget === menu || menu[contains](eventTarget)) && (self.persist || hasData) ) { return; }
618-        else {
619-          relatedTarget = eventTarget === element || element[contains](eventTarget) ? element : null;
620-          hide();
621-        }
622-        preventEmptyAnchor.call(e,eventTarget);
623-      },
624-      clickHandler = function(e) {
625-        relatedTarget = element;
626-        show();
627-        preventEmptyAnchor.call(e,e[target]);
628-      },
629-      preventScroll = function(e){
630-        var key = e.which || e.keyCode;
631-        if( key === 38 || key === 40 ) { e[preventDefault](); }
632-      },
633-      keyHandler = function(e){
634-        var key = e.which || e.keyCode,
635-          activeItem = DOC.activeElement,
636-          idx = menuItems[indexOf](activeItem),
637-          isSameElement = activeItem === element,
638-          isInsideMenu = menu[contains](activeItem),
639-          isMenuItem = activeItem[parentNode] === menu || activeItem[parentNode][parentNode] === menu;
640-
641-        if ( isMenuItem || isSameElement ) { // navigate up | down
642-          idx = isSameElement ? 0
643-                              : key === 38 ? (idx>1?idx-1:0)
644-                              : key === 40 ? (idx<menuItems[length]-1?idx+1:idx) : idx;
645-          menuItems[idx] && setFocus(menuItems[idx]);
646-        }
647-        if ( (menuItems[length] && isMenuItem // menu has items
648-              || !menuItems[length] && (isInsideMenu || isSameElement)  // menu might be a form
649-              || !isInsideMenu ) // or the focused element is not in the menu at all
650-              && element[open] && key === 27  // menu must be open
651-        ) {
652-          self.toggle();
653-          relatedTarget = null;
654-        }
655-      },
656-
657-      // private methods
658-      show = function() {
659-        bootstrapCustomEvent.call(parent, showEvent, component, relatedTarget);
660-        addClass(menu,showClass);
661-        addClass(parent,showClass);
662-        element[setAttribute](ariaExpanded,true);
663-        bootstrapCustomEvent.call(parent, shownEvent, component, relatedTarget);
664-        element[open] = true;
665-        off(element, clickEvent, clickHandler);
666-        setTimeout(function(){
667-          setFocus( menu[getElementsByTagName]('INPUT')[0] || element ); // focus the first input item | element
668-          toggleDismiss();
669-        },1);
670-      },
671-      hide = function() {
672-        bootstrapCustomEvent.call(parent, hideEvent, component, relatedTarget);
673-        removeClass(menu,showClass);
674-        removeClass(parent,showClass);
675-        element[setAttribute](ariaExpanded,false);
676-        bootstrapCustomEvent.call(parent, hiddenEvent, component, relatedTarget);
677-        element[open] = false;
678-        toggleDismiss();
679-        setFocus(element);
680-        setTimeout(function(){ on(element, clickEvent, clickHandler); },1);
681-      };
682-
683-    // set initial state to closed
684-    element[open] = false;
685-
686-    // public methods
687-    this.toggle = function() {
688-      if (hasClass(parent,showClass) && element[open]) { hide(); }
689-      else { show(); }
690-    };
691-
692-    // init
693-    if ( !(stringDropdown in element) ) { // prevent adding event handlers twice
694-      !tabindex in menu && menu[setAttribute](tabindex, '0'); // Fix onblur on Chrome | Safari
695-      on(element, clickEvent, clickHandler);
696-    }
697-
698-    element[stringDropdown] = self;
699-  };
700-
701-  // DROPDOWN DATA API
702-  // =================
703-  supports[push]( [stringDropdown, Dropdown, '['+dataToggle+'="dropdown"]'] );
704-
705-
706-  /* Native Javascript for Bootstrap 4 | Modal
707-  -------------------------------------------*/
708-
709-  // MODAL DEFINITION
710-  // ===============
711-  var Modal = function(element, options) { // element can be the modal/triggering button
712-
713-    // the modal (both JavaScript / DATA API init) / triggering button element (DATA API)
714-    element = queryElement(element);
715-
716-      // strings
717-      var component = 'modal',
718-        staticString = 'static',
719-        modalTrigger = 'modalTrigger',
720-        paddingRight = 'paddingRight',
721-        modalBackdropString = 'modal-backdrop',
722-        // determine modal, triggering element
723-        btnCheck = element[getAttribute](dataTarget)||element[getAttribute]('href'),
724-        checkModal = queryElement( btnCheck ),
725-        modal = hasClass(element,component) ? element : checkModal;
726-
727-      if ( hasClass(element, component) ) { element = null; } // modal is now independent of it's triggering element
728-
729-    if ( !modal ) { return; } // invalidate
730-
731-    // set options
732-    options = options || {};
733-
734-    this[keyboard] = options[keyboard] === false || modal[getAttribute](dataKeyboard) === 'false' ? false : true;
735-    this[backdrop] = options[backdrop] === staticString || modal[getAttribute](databackdrop) === staticString ? staticString : true;
736-    this[backdrop] = options[backdrop] === false || modal[getAttribute](databackdrop) === 'false' ? false : this[backdrop];
737-    this[content]  = options[content]; // JavaScript only
738-
739-    // bind, constants, event targets and other vars
740-    var self = this, relatedTarget = null,
741-      bodyIsOverflowing, scrollBarWidth, overlay, overlayDelay,
742-
743-      // also find fixed-top / fixed-bottom items
744-      fixedItems = getElementsByClassName(HTML,fixedTop).concat(getElementsByClassName(HTML,fixedBottom)),
745-
746-      // private methods
747-      getWindowWidth = function() {
748-        var htmlRect = HTML[getBoundingClientRect]();
749-        return globalObject[innerWidth] || (htmlRect[right] - Math.abs(htmlRect[left]));
750-      },
751-      setScrollbar = function () {
752-        var bodyStyle = globalObject[getComputedStyle](DOC[body]),
753-            bodyPad = parseInt((bodyStyle[paddingRight]), 10), itemPad;
754-        if (bodyIsOverflowing) {
755-          DOC[body][style][paddingRight] = (bodyPad + scrollBarWidth) + 'px';
756-          modal[style][paddingRight] = scrollBarWidth+'px';
757-          if (fixedItems[length]){
758-            for (var i = 0; i < fixedItems[length]; i++) {
759-              itemPad = globalObject[getComputedStyle](fixedItems[i])[paddingRight];
760-              fixedItems[i][style][paddingRight] = ( parseInt(itemPad) + scrollBarWidth) + 'px';
761-            }
762-          }
763-        }
764-      },
765-      resetScrollbar = function () {
766-        DOC[body][style][paddingRight] = '';
767-        modal[style][paddingRight] = '';
768-        if (fixedItems[length]){
769-          for (var i = 0; i < fixedItems[length]; i++) {
770-            fixedItems[i][style][paddingRight] = '';
771-          }
772-        }
773-      },
774-      measureScrollbar = function () { // thx walsh
775-        var scrollDiv = DOC[createElement]('div'), widthValue;
776-        scrollDiv.className = component+'-scrollbar-measure'; // this is here to stay
777-        DOC[body][appendChild](scrollDiv);
778-        widthValue = scrollDiv[offsetWidth] - scrollDiv[clientWidth];
779-        DOC[body].removeChild(scrollDiv);
780-        return widthValue;
781-      },
782-      checkScrollbar = function () {
783-        bodyIsOverflowing = DOC[body][clientWidth] < getWindowWidth();
784-        scrollBarWidth = measureScrollbar();
785-      },
786-      createOverlay = function() {
787-        modalOverlay = 1;
788-
789-        var newOverlay = DOC[createElement]('div');
790-        overlay = queryElement('.'+modalBackdropString);
791-
792-        if ( overlay === null ) {
793-          newOverlay[setAttribute]('class',modalBackdropString+' fade');
794-          overlay = newOverlay;
795-          DOC[body][appendChild](overlay);
796-        }
797-      },
798-      removeOverlay = function() {
799-        overlay = queryElement('.'+modalBackdropString);
800-        if ( overlay && overlay !== null && typeof overlay === 'object' ) {
801-          modalOverlay = 0;
802-          DOC[body].removeChild(overlay); overlay = null;
803-        }
804-        bootstrapCustomEvent.call(modal, hiddenEvent, component);
805-      },
806-      keydownHandlerToggle = function() {
807-        if (hasClass(modal,showClass)) {
808-          on(DOC, keydownEvent, keyHandler);
809-        } else {
810-          off(DOC, keydownEvent, keyHandler);
811-        }
812-      },
813-      resizeHandlerToggle = function() {
814-        if (hasClass(modal,showClass)) {
815-          on(globalObject, resizeEvent, self.update);
816-        } else {
817-          off(globalObject, resizeEvent, self.update);
818-        }
819-      },
820-      dismissHandlerToggle = function() {
821-        if (hasClass(modal,showClass)) {
822-          on(modal, clickEvent, dismissHandler);
823-        } else {
824-          off(modal, clickEvent, dismissHandler);
825-        }
826-      },
827-      // triggers
828-      triggerShow = function() {
829-        resizeHandlerToggle();
830-        dismissHandlerToggle();
831-        keydownHandlerToggle();
832-        setFocus(modal);
833-        bootstrapCustomEvent.call(modal, shownEvent, component, relatedTarget);
834-      },
835-      triggerHide = function() {
836-        modal[style].display = '';
837-        element && (setFocus(element));
838-
839-        (function(){
840-          if (!getElementsByClassName(DOC,component+' '+showClass)[0]) {
841-            resetScrollbar();
842-            removeClass(DOC[body],component+'-open');
843-            overlay && hasClass(overlay,'fade') ? (removeClass(overlay,showClass), emulateTransitionEnd(overlay,removeOverlay))
844-            : removeOverlay();
845-
846-            resizeHandlerToggle();
847-            dismissHandlerToggle();
848-            keydownHandlerToggle();
849-          }
850-        }());
851-      },
852-      // handlers
853-      clickHandler = function(e) {
854-        var clickTarget = e[target];
855-        clickTarget = clickTarget[hasAttribute](dataTarget) || clickTarget[hasAttribute]('href') ? clickTarget : clickTarget[parentNode];
856-        if ( clickTarget === element && !hasClass(modal,showClass) ) {
857-          modal[modalTrigger] = element;
858-          relatedTarget = element;
859-          self.show();
860-          e[preventDefault]();
861-        }
862-      },
863-      keyHandler = function(e) {
864-        if (self[keyboard] && e.which == 27 && hasClass(modal,showClass)) {
865-          self.hide();
866-        }
867-      },
868-      dismissHandler = function(e) {
869-        var clickTarget = e[target];
870-        if ( hasClass(modal,showClass) && (clickTarget[parentNode][getAttribute](dataDismiss) === component
871-            || clickTarget[getAttribute](dataDismiss) === component
872-            || (clickTarget === modal && self[backdrop] !== staticString) ) ) {
873-          self.hide(); relatedTarget = null;
874-          e[preventDefault]();
875-        }
876-      };
877-
878-    // public methods
879-    this.toggle = function() {
880-      if ( hasClass(modal,showClass) ) {this.hide();} else {this.show();}
881-    };
882-    this.show = function() {
883-      bootstrapCustomEvent.call(modal, showEvent, component, relatedTarget);
884-
885-      // we elegantly hide any opened modal
886-      var currentOpen = getElementsByClassName(DOC,component+' '+showClass)[0];
887-      if (currentOpen && currentOpen !== modal) {
888-        modalTrigger in currentOpen && currentOpen[modalTrigger][stringModal].hide();
889-        stringModal in currentOpen && currentOpen[stringModal].hide();
890-      }
891-
892-      if ( this[backdrop] ) {
893-        !modalOverlay && createOverlay();
894-      }
895-
896-      if ( overlay && modalOverlay && !hasClass(overlay,showClass)) {
897-        overlay[offsetWidth]; // force reflow to enable trasition
898-        overlayDelay = getTransitionDurationFromElement(overlay);
899-        addClass(overlay, showClass);
900-      }
901-
902-      setTimeout( function() {
903-        modal[style].display = 'block';
904-
905-        checkScrollbar();
906-        setScrollbar();
907-
908-        addClass(DOC[body],component+'-open');
909-        addClass(modal,showClass);
910-        modal[setAttribute](ariaHidden, false);
911-
912-        hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerShow) : triggerShow();
913-      }, supportTransitions && overlay ? overlayDelay : 0);
914-    };
915-    this.hide = function() {
916-      bootstrapCustomEvent.call(modal, hideEvent, component);
917-      overlay = queryElement('.'+modalBackdropString);
918-      overlayDelay = overlay && getTransitionDurationFromElement(overlay);
919-
920-      removeClass(modal,showClass);
921-      modal[setAttribute](ariaHidden, true);
922-
923-      setTimeout(function(){
924-        hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerHide) : triggerHide();
925-      }, supportTransitions && overlay ? overlayDelay : 0);
926-    };
927-    this.setContent = function( content ) {
928-      queryElement('.'+component+'-content',modal)[innerHTML] = content;
929-    };
930-    this.update = function() {
931-      if (hasClass(modal,showClass)) {
932-        checkScrollbar();
933-        setScrollbar();
934-      }
935-    };
936-
937-    // init
938-    // prevent adding event handlers over and over
939-    // modal is independent of a triggering element
940-    if ( !!element && !(stringModal in element) ) {
941-      on(element, clickEvent, clickHandler);
942-    }
943-    if ( !!self[content] ) { self.setContent( self[content] ); }
944-    if (element) { element[stringModal] = self; modal[modalTrigger] = element; }
945-    else { modal[stringModal] = self; }
946-  };
947-
948-  // DATA API
949-  supports[push]( [ stringModal, Modal, '['+dataToggle+'="modal"]' ] );
950-
951-  /* Native Javascript for Bootstrap 4 | Popover
952-  ----------------------------------------------*/
953-
954-  // POPOVER DEFINITION
955-  // ==================
956-  var Popover = function( element, options ) {
957-
958-    // initialization element
959-    element = queryElement(element);
960-
961-    // set options
962-    options = options || {};
963-
964-    // DATA API
965-    var triggerData = element[getAttribute](dataTrigger), // click / hover / focus
966-        animationData = element[getAttribute](dataAnimation), // true / false
967-        placementData = element[getAttribute](dataPlacement),
968-        dismissibleData = element[getAttribute](dataDismissible),
969-        delayData = element[getAttribute](dataDelay),
970-        containerData = element[getAttribute](dataContainer),
971-
972-        // internal strings
973-        component = 'popover',
974-        template = 'template',
975-        trigger = 'trigger',
976-        classString = 'class',
977-        div = 'div',
978-        fade = 'fade',
979-        dataContent = 'data-content',
980-        dismissible = 'dismissible',
981-        closeBtn = '<button type="button" class="close">×</button>',
982-
983-        // check container
984-        containerElement = queryElement(options[container]),
985-        containerDataElement = queryElement(containerData),
986-
987-        // maybe the element is inside a modal
988-        modal = getClosest(element,'.modal'),
989-
990-        // maybe the element is inside a fixed navbar
991-        navbarFixedTop = getClosest(element,'.'+fixedTop),
992-        navbarFixedBottom = getClosest(element,'.'+fixedBottom);
993-
994-    // set instance options
995-    this[template] = options[template] ? options[template] : null; // JavaScript only
996-    this[trigger] = options[trigger] ? options[trigger] : triggerData || hoverEvent;
997-    this[animation] = options[animation] && options[animation] !== fade ? options[animation] : animationData || fade;
998-    this[placement] = options[placement] ? options[placement] : placementData || top;
999-    this[delay] = parseInt(options[delay] || delayData) || 200;
1000-    this[dismissible] = options[dismissible] || dismissibleData === 'true' ? true : false;
1001-    this[container] = containerElement ? containerElement
1002-                    : containerDataElement ? containerDataElement
1003-                    : navbarFixedTop ? navbarFixedTop
1004-                    : navbarFixedBottom ? navbarFixedBottom
1005-                    : modal ? modal : DOC[body];
1006-
1007-    // bind, content
1008-    var self = this,
1009-      titleString = element[getAttribute](dataTitle) || null,
1010-      contentString = element[getAttribute](dataContent) || null;
1011-
1012-    if ( !contentString && !this[template] ) return; // invalidate
1013-
1014-    // constants, vars
1015-    var popover = null, timer = 0, placementSetting = this[placement],
1016-
1017-      // handlers
1018-      dismissibleHandler = function(e) {
1019-        if (popover !== null && e[target] === queryElement('.close',popover)) {
1020-          self.hide();
1021-        }
1022-      },
1023-
1024-      // private methods
1025-      removePopover = function() {
1026-        self[container].removeChild(popover);
1027-        timer = null; popover = null;
1028-      },
1029-      createPopover = function() {
1030-        titleString = options.title || element[getAttribute](dataTitle) || null,
1031-        contentString = options.content || element[getAttribute](dataContent) || null;
1032-
1033-        popover = DOC[createElement](div);
1034-
1035-        // popover arrow
1036-        var popoverArrow = DOC[createElement](div);
1037-        popoverArrow[setAttribute](classString,'arrow');
1038-        popover[appendChild](popoverArrow);
1039-
1040-        if ( contentString !== null && self[template] === null ) { //create the popover from data attributes
1041-
1042-          popover[setAttribute]('role','tooltip');
1043-
1044-          if (titleString !== null) {
1045-            var popoverTitle = DOC[createElement]('h3');
1046-            popoverTitle[setAttribute](classString,component+'-header');
1047-
1048-            popoverTitle[innerHTML] = self[dismissible] ? titleString + closeBtn : titleString;
1049-            popover[appendChild](popoverTitle);
1050-          }
1051-
1052-          //set popover content
1053-          var popoverContent = DOC[createElement](div);
1054-          popoverContent[setAttribute](classString,component+'-body');
1055-          popoverContent[innerHTML] = self[dismissible] && titleString === null ? contentString + closeBtn : contentString;
1056-          popover[appendChild](popoverContent);
1057-
1058-        } else {  // or create the popover from template
1059-          var popoverTemplate = DOC[createElement](div);
1060-          popoverTemplate[innerHTML] = self[template];
1061-          popover[innerHTML] = popoverTemplate.firstChild[innerHTML];
1062-        }
1063-
1064-        //append to the container
1065-        self[container][appendChild](popover);
1066-        popover[style].display = 'block';
1067-        popover[setAttribute](classString, component+ ' bs-' + component+'-'+placementSetting + ' ' + self[animation]);
1068-      },
1069-      showPopover = function () {
1070-        !hasClass(popover,showClass) && ( addClass(popover,showClass) );
1071-      },
1072-      updatePopover = function() {
1073-        styleTip(element,popover,placementSetting,self[container]);
1074-      },
1075-
1076-      // event toggle
1077-      dismissHandlerToggle = function(type){
1078-        if (clickEvent == self[trigger] || 'focus' == self[trigger]) {
1079-          !self[dismissible] && type( element, 'blur', self.hide );
1080-        }
1081-        self[dismissible] && type( DOC, clickEvent, dismissibleHandler );
1082-        type( globalObject, resizeEvent, self.hide );
1083-      },
1084-
1085-      // triggers
1086-      showTrigger = function() {
1087-        dismissHandlerToggle(on);
1088-        bootstrapCustomEvent.call(element, shownEvent, component);
1089-      },
1090-      hideTrigger = function() {
1091-        dismissHandlerToggle(off);
1092-        removePopover();
1093-        bootstrapCustomEvent.call(element, hiddenEvent, component);
1094-      };
1095-
1096-    // public methods / handlers
1097-    this.toggle = function() {
1098-      if (popover === null) { self.show(); }
1099-      else { self.hide(); }
1100-    };
1101-    this.show = function() {
1102-      clearTimeout(timer);
1103-      timer = setTimeout( function() {
1104-        if (popover === null) {
1105-          placementSetting = self[placement]; // we reset placement in all cases
1106-          createPopover();
1107-          updatePopover();
1108-          showPopover();
1109-          bootstrapCustomEvent.call(element, showEvent, component);
1110-          !!self[animation] ? emulateTransitionEnd(popover, showTrigger) : showTrigger();
1111-        }
1112-      }, 20 );
1113-    };
1114-    this.hide = function() {
1115-      clearTimeout(timer);
1116-      timer = setTimeout( function() {
1117-        if (popover && popover !== null && hasClass(popover,showClass)) {
1118-          bootstrapCustomEvent.call(element, hideEvent, component);
1119-          removeClass(popover,showClass);
1120-          !!self[animation] ? emulateTransitionEnd(popover, hideTrigger) : hideTrigger();
1121-        }
1122-      }, self[delay] );
1123-    };
1124-
1125-    // init
1126-    if ( !(stringPopover in element) ) { // prevent adding event handlers twice
1127-      if (self[trigger] === hoverEvent) {
1128-        on( element, mouseHover[0], self.show );
1129-        if (!self[dismissible]) { on( element, mouseHover[1], self.hide ); }
1130-      } else if (clickEvent == self[trigger] || 'focus' == self[trigger]) {
1131-        on( element, self[trigger], self.toggle );
1132-      }
1133-    }
1134-    element[stringPopover] = self;
1135-  };
1136-
1137-  // POPOVER DATA API
1138-  // ================
1139-  supports[push]( [ stringPopover, Popover, '['+dataToggle+'="popover"]' ] );
1140-
1141-
1142-  /* Native Javascript for Bootstrap 4 | Tab
1143-  -----------------------------------------*/
1144-
1145-  // TAB DEFINITION
1146-  // ==============
1147-  var Tab = function( element, options ) {
1148-
1149-    // initialization element
1150-    element = queryElement(element);
1151-
1152-    // DATA API
1153-    var heightData = element[getAttribute](dataHeight),
1154-
1155-        // strings
1156-        component = 'tab', height = 'height', float = 'float', isAnimating = 'isAnimating';
1157-
1158-    // set options
1159-    options = options || {};
1160-    this[height] = supportTransitions ? (options[height] || heightData === 'true') : false;
1161-
1162-    // bind, event targets
1163-    var self = this, next,
1164-      tabs = getClosest(element,'.nav'),
1165-      tabsContentContainer = false,
1166-      dropdown = tabs && queryElement('.dropdown-toggle',tabs),
1167-      activeTab, activeContent, nextContent, containerHeight, equalContents, nextHeight,
1168-
1169-      // trigger
1170-      triggerEnd = function(){
1171-        tabsContentContainer[style][height] = '';
1172-        removeClass(tabsContentContainer,collapsing);
1173-        tabs[isAnimating] = false;
1174-      },
1175-      triggerShow = function() {
1176-        if (tabsContentContainer) { // height animation
1177-          if ( equalContents ) {
1178-            triggerEnd();
1179-          } else {
1180-            setTimeout(function(){ // enables height animation
1181-              tabsContentContainer[style][height] = nextHeight + 'px'; // height animation
1182-              tabsContentContainer[offsetWidth];
1183-              emulateTransitionEnd(tabsContentContainer, triggerEnd);
1184-            },50);
1185-          }
1186-        } else {
1187-          tabs[isAnimating] = false;
1188-        }
1189-        bootstrapCustomEvent.call(next, shownEvent, component, activeTab);
1190-      },
1191-      triggerHide = function() {
1192-        if (tabsContentContainer) {
1193-          activeContent[style][float] = left;
1194-          nextContent[style][float] = left;
1195-          containerHeight = activeContent[scrollHeight];
1196-        }
1197-
1198-        addClass(nextContent,active);
1199-        bootstrapCustomEvent.call(next, showEvent, component, activeTab);
1200-
1201-        removeClass(activeContent,active);
1202-        bootstrapCustomEvent.call(activeTab, hiddenEvent, component, next);
1203-
1204-        if (tabsContentContainer) {
1205-          nextHeight = nextContent[scrollHeight];
1206-          equalContents = nextHeight === containerHeight;
1207-          addClass(tabsContentContainer,collapsing);
1208-          tabsContentContainer[style][height] = containerHeight + 'px'; // height animation
1209-          tabsContentContainer[offsetHeight];
1210-          activeContent[style][float] = '';
1211-          nextContent[style][float] = '';
1212-        }
1213-
1214-        if ( hasClass(nextContent, 'fade') ) {
1215-          setTimeout(function(){
1216-            addClass(nextContent,showClass);
1217-            emulateTransitionEnd(nextContent,triggerShow);
1218-          },20);
1219-        } else { triggerShow(); }
1220-      };
1221-
1222-    if (!tabs) return; // invalidate
1223-
1224-    // set default animation state
1225-    tabs[isAnimating] = false;
1226-
1227-    // private methods
1228-    var getActiveTab = function() {
1229-        var activeTabs = getElementsByClassName(tabs,active), activeTab;
1230-        if ( activeTabs[length] === 1 && !hasClass(activeTabs[0][parentNode],'dropdown') ) {
1231-          activeTab = activeTabs[0];
1232-        } else if ( activeTabs[length] > 1 ) {
1233-          activeTab = activeTabs[activeTabs[length]-1];
1234-        }
1235-        return activeTab;
1236-      },
1237-      getActiveContent = function() {
1238-        return queryElement(getActiveTab()[getAttribute]('href'));
1239-      },
1240-      // handler
1241-      clickHandler = function(e) {
1242-        e[preventDefault]();
1243-        next = e[currentTarget];
1244-        !tabs[isAnimating] && !hasClass(next,active) && self.show();
1245-      };
1246-
1247-    // public method
1248-    this.show = function() { // the tab we clicked is now the next tab
1249-      next = next || element;
1250-      nextContent = queryElement(next[getAttribute]('href')); //this is the actual object, the next tab content to activate
1251-      activeTab = getActiveTab();
1252-      activeContent = getActiveContent();
1253-
1254-      tabs[isAnimating] = true;
1255-      removeClass(activeTab,active);
1256-      activeTab[setAttribute](ariaSelected,'false');
1257-      addClass(next,active);
1258-      next[setAttribute](ariaSelected,'true');
1259-
1260-      if ( dropdown ) {
1261-        if ( !hasClass(element[parentNode],'dropdown-menu') ) {
1262-          if (hasClass(dropdown,active)) removeClass(dropdown,active);
1263-        } else {
1264-          if (!hasClass(dropdown,active)) addClass(dropdown,active);
1265-        }
1266-      }
1267-
1268-      bootstrapCustomEvent.call(activeTab, hideEvent, component, next);
1269-
1270-      if (hasClass(activeContent, 'fade')) {
1271-        removeClass(activeContent,showClass);
1272-        emulateTransitionEnd(activeContent, triggerHide);
1273-      } else { triggerHide(); }
1274-    };
1275-
1276-    // init
1277-    if ( !(stringTab in element) ) { // prevent adding event handlers twice
1278-      on(element, clickEvent, clickHandler);
1279-    }
1280-    if (self[height]) { tabsContentContainer = getActiveContent()[parentNode]; }
1281-    element[stringTab] = self;
1282-  };
1283-
1284-  // TAB DATA API
1285-  // ============
1286-  supports[push]( [ stringTab, Tab, '['+dataToggle+'="tab"]' ] );
1287-
1288-
1289-  /* Native Javascript for Bootstrap 4 | Toast
1290-  ---------------------------------------------*/
1291-
1292-  // TOAST DEFINITION
1293-  // ==================
1294-  var Toast = function( element,options ) {
1295-
1296-    // initialization element
1297-    element = queryElement(element);
1298-
1299-    // set options
1300-    options = options || {};
1301-
1302-    // DATA API
1303-    var animationData = element[getAttribute](dataAnimation),
1304-        autohideData = element[getAttribute](dataAutohide),
1305-        delayData = element[getAttribute](dataDelay),
1306-
1307-        // strings
1308-        component = 'toast',
1309-        autohide = 'autohide',
1310-        animation = 'animation',
1311-        showing = 'showing',
1312-        hide = 'hide',
1313-        fade = 'fade';
1314-
1315-    // set instance options
1316-    this[animation] = options[animation] === false || animationData === 'false' ? 0 : 1; // true by default
1317-    this[autohide] = options[autohide] === false || autohideData === 'false' ? 0 : 1; // true by default
1318-    this[delay] = parseInt(options[delay] || delayData) || 500; // 500ms default
1319-
1320-    // bind,toast and timer
1321-    var self = this, timer = 0,
1322-        // get the toast element
1323-        toast = getClosest(element,'.toast');
1324-
1325-    // private methods
1326-    // animation complete
1327-    var showComplete = function() {
1328-        removeClass( toast, showing );
1329-        addClass( toast, showClass );
1330-        bootstrapCustomEvent.call(toast, shownEvent, component);
1331-        if (self[autohide]) { self.hide(); }
1332-      },
1333-      hideComplete = function() {
1334-        addClass( toast, hide );
1335-        bootstrapCustomEvent.call(toast, hiddenEvent, component);
1336-      },
1337-      close = function() {
1338-        removeClass( toast,showClass );
1339-        self[animation] ? emulateTransitionEnd(toast, hideComplete) : hideComplete();
1340-      },
1341-      disposeComplete = function(){
1342-        clearTimeout(timer); timer = null;
1343-        addClass( toast, hide );
1344-        off(element, clickEvent, self.hide);
1345-        element[stringToast] = null;
1346-        element = null;
1347-        toast = null;
1348-      };
1349-
1350-    // public methods
1351-    this.show = function() {
1352-      if (toast) {
1353-        bootstrapCustomEvent.call(toast, showEvent, component);
1354-        self[animation] && addClass( toast,fade );
1355-        removeClass( toast,hide );
1356-        addClass( toast,showing );
1357-
1358-        self[animation] ? emulateTransitionEnd(toast, showComplete) : showComplete();
1359-      }
1360-    };
1361-    this.hide = function(noTimer) {
1362-      if (toast && hasClass(toast,showClass)) {
1363-        bootstrapCustomEvent.call(toast, hideEvent, component);
1364-
1365-        if (noTimer) {
1366-          close();
1367-        } else {
1368-          timer = setTimeout( close, self[delay]);
1369-        }
1370-      }
1371-    };
1372-    this.dispose = function() {
1373-      if ( toast && hasClass(toast,showClass) ) {
1374-        removeClass( toast,showClass );
1375-        self[animation] ? emulateTransitionEnd(toast, disposeComplete) : disposeComplete();
1376-      }
1377-    };
1378-
1379-    // init
1380-    if ( !(stringToast in element) ) { // prevent adding event handlers twice
1381-      on(element, clickEvent, self.hide);
1382-    }
1383-    element[stringToast] = self;
1384-  };
1385-
1386-  // TOAST DATA API
1387-  // =================
1388-  supports[push]( [ stringToast, Toast, '['+dataDismiss+'="toast"]' ] );
1389-
1390-
1391-  /* Native Javascript for Bootstrap 4 | Tooltip
1392-  ---------------------------------------------*/
1393-
1394-  // TOOLTIP DEFINITION
1395-  // ==================
1396-  var Tooltip = function( element,options ) {
1397-
1398-    // initialization element
1399-    element = queryElement(element);
1400-
1401-    // set options
1402-    options = options || {};
1403-
1404-    // DATA API
1405-    var animationData = element[getAttribute](dataAnimation),
1406-        placementData = element[getAttribute](dataPlacement),
1407-        delayData = element[getAttribute](dataDelay),
1408-        containerData = element[getAttribute](dataContainer),
1409-
1410-        // strings
1411-        component = 'tooltip',
1412-        classString = 'class',
1413-        title = 'title',
1414-        fade = 'fade',
1415-        div = 'div',
1416-
1417-        // check container
1418-        containerElement = queryElement(options[container]),
1419-        containerDataElement = queryElement(containerData),
1420-
1421-        // maybe the element is inside a modal
1422-        modal = getClosest(element,'.modal'),
1423-
1424-        // maybe the element is inside a fixed navbar
1425-        navbarFixedTop = getClosest(element,'.'+fixedTop),
1426-        navbarFixedBottom = getClosest(element,'.'+fixedBottom);
1427-
1428-    // set instance options
1429-    this[animation] = options[animation] && options[animation] !== fade ? options[animation] : animationData || fade;
1430-    this[placement] = options[placement] ? options[placement] : placementData || top;
1431-    this[delay] = parseInt(options[delay] || delayData) || 200;
1432-    this[container] = containerElement ? containerElement
1433-                    : containerDataElement ? containerDataElement
1434-                    : navbarFixedTop ? navbarFixedTop
1435-                    : navbarFixedBottom ? navbarFixedBottom
1436-                    : modal ? modal : DOC[body];
1437-
1438-    // bind, event targets, title and constants
1439-    var self = this, timer = 0, placementSetting = this[placement], tooltip = null,
1440-      titleString = element[getAttribute](title) || element[getAttribute](dataTitle) || element[getAttribute](dataOriginalTitle);
1441-
1442-    if ( !titleString || titleString == "" ) return; // invalidate
1443-
1444-    // private methods
1445-    var removeToolTip = function() {
1446-        self[container].removeChild(tooltip);
1447-        tooltip = null; timer = null;
1448-      },
1449-      createToolTip = function() {
1450-        titleString = element[getAttribute](title) || element[getAttribute](dataTitle) || element[getAttribute](dataOriginalTitle); // read the title again
1451-        if ( !titleString || titleString == "" ) return false; // invalidate
1452-        tooltip = DOC[createElement](div);
1453-        tooltip[setAttribute]('role',component);
1454-
1455-        // tooltip arrow
1456-        var tooltipArrow = DOC[createElement](div);
1457-        tooltipArrow[setAttribute](classString,'arrow');
1458-        tooltip[appendChild](tooltipArrow);
1459-
1460-        var tooltipInner = DOC[createElement](div);
1461-        tooltipInner[setAttribute](classString,component+'-inner');
1462-        tooltip[appendChild](tooltipInner);
1463-        tooltipInner[innerHTML] = titleString;
1464-
1465-        self[container][appendChild](tooltip);
1466-        tooltip[setAttribute](classString, component + ' bs-' + component+'-'+placementSetting + ' ' + self[animation]);
1467-      },
1468-      updateTooltip = function () {
1469-        styleTip(element,tooltip,placementSetting,self[container]);
1470-      },
1471-      showTooltip = function () {
1472-        !hasClass(tooltip,showClass) && ( addClass(tooltip,showClass) );
1473-      },
1474-      // triggers
1475-      showTrigger = function() {
1476-        on( globalObject, resizeEvent, self.hide );
1477-        bootstrapCustomEvent.call(element, shownEvent, component);
1478-      },
1479-      hideTrigger = function() {
1480-        off( globalObject, resizeEvent, self.hide );
1481-        removeToolTip();
1482-        bootstrapCustomEvent.call(element, hiddenEvent, component);
1483-      };
1484-
1485-    // public methods
1486-    this.show = function() {
1487-      clearTimeout(timer);
1488-      timer = setTimeout( function() {
1489-        if (tooltip === null) {
1490-          placementSetting = self[placement]; // we reset placement in all cases
1491-          if(createToolTip() == false) return;
1492-          updateTooltip();
1493-          showTooltip();
1494-          bootstrapCustomEvent.call(element, showEvent, component);
1495-          !!self[animation] ? emulateTransitionEnd(tooltip, showTrigger) : showTrigger();
1496-        }
1497-      }, 20 );
1498-    };
1499-    this.hide = function() {
1500-      clearTimeout(timer);
1501-      timer = setTimeout( function() {
1502-        if (tooltip && hasClass(tooltip,showClass)) {
1503-          bootstrapCustomEvent.call(element, hideEvent, component);
1504-          removeClass(tooltip,showClass);
1505-          !!self[animation] ? emulateTransitionEnd(tooltip, hideTrigger) : hideTrigger();
1506-        }
1507-      }, self[delay]);
1508-    };
1509-    this.toggle = function() {
1510-      if (!tooltip) { self.show(); }
1511-      else { self.hide(); }
1512-    };
1513-
1514-    // init
1515-    if ( !(stringTooltip in element) ) { // prevent adding event handlers twice
1516-      element[setAttribute](dataOriginalTitle,titleString);
1517-      element.removeAttribute(title);
1518-      on(element, mouseHover[0], self.show);
1519-      on(element, mouseHover[1], self.hide);
1520-    }
1521-    element[stringTooltip] = self;
1522-  };
1523-
1524-  // TOOLTIP DATA API
1525-  // =================
1526-  supports[push]( [ stringTooltip, Tooltip, '['+dataToggle+'="tooltip"]' ] );
1527-
1528-
1529-
1530-  /* Native Javascript for Bootstrap | Initialize Data API
1531-  --------------------------------------------------------*/
1532-  var initializeDataAPI = function( constructor, collection ){
1533-      for (var i=0, l=collection[length]; i<l; i++) {
1534-        new constructor(collection[i]);
1535-      }
1536-    },
1537-    initCallback = BSN.initCallback = function(lookUp){
1538-      lookUp = lookUp || DOC;
1539-      for (var i=0, l=supports[length]; i<l; i++) {
1540-        initializeDataAPI( supports[i][1], lookUp[querySelectorAll] (supports[i][2]) );
1541-      }
1542-    };
1543-
1544-  // bulk initialize all components
1545-  DOC[body] ? initCallback() : on( DOC, 'DOMContentLoaded', function(){ initCallback(); } );
1546-
1547   return {
1548-    Alert: Alert,
1549-    Button: Button,
1550-    Collapse: Collapse,
1551-    Dropdown: Dropdown,
1552-    Modal: Modal,
1553-    Popover: Popover,
1554-    Tab: Tab,
1555-    Toast: Toast,
1556-    Tooltip: Tooltip
1557+    Alert: bootstrap.Alert,
1558+    Button: bootstrap.Button,
1559+    Collapse: bootstrap.Collapse,
1560+    Dropdown: bootstrap.Dropdown,
1561+    Modal: bootstrap.Modal,
1562+    Popover: bootstrap.Popover,
1563+    Tab: bootstrap.Tab,
1564+    Tooltip: bootstrap.Tooltip
1565   };
1566 }));
1567
1568@@ -75865,4 +74326,4 @@ converse_core.initialize = function (settings, callback) {
1569
1570 /***/ })
1571 /******/ ]);
1572-//# sourceMappingURL=converse.js.map
1573\ No newline at end of file
1574+//# sourceMappingURL=converse.js.map
1575