1789 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1789 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
if (!Array.prototype.find) {
 | 
						|
    Array.prototype.find = function(predicate) {
 | 
						|
        if (this === null) {
 | 
						|
            throw new TypeError('Array.prototype.find called on null or undefined')
 | 
						|
        }
 | 
						|
        if (typeof predicate !== 'function') {
 | 
						|
            throw new TypeError('predicate must be a function')
 | 
						|
        }
 | 
						|
        var list = Object(this);
 | 
						|
        var length = list.length >>> 0;
 | 
						|
        var thisArg = arguments[1];
 | 
						|
        var value;
 | 
						|
 | 
						|
        for (var i = 0; i < length; i++) {
 | 
						|
            value = list[i];
 | 
						|
            if (predicate.call(thisArg, value, i, list)) {
 | 
						|
                return value
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return undefined
 | 
						|
    };
 | 
						|
}
 | 
						|
 | 
						|
if (window && typeof window.CustomEvent !== "function") {
 | 
						|
  function CustomEvent$1(event, params) {
 | 
						|
    params = params || {
 | 
						|
      bubbles: false,
 | 
						|
      cancelable: false,
 | 
						|
      detail: undefined
 | 
						|
    };
 | 
						|
    var evt = document.createEvent('CustomEvent');
 | 
						|
    evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
 | 
						|
    return evt
 | 
						|
  }
 | 
						|
 | 
						|
 if (typeof window.Event !== 'undefined') {
 | 
						|
   CustomEvent$1.prototype = window.Event.prototype;
 | 
						|
 }
 | 
						|
 | 
						|
  window.CustomEvent = CustomEvent$1;
 | 
						|
}
 | 
						|
 | 
						|
class TributeEvents {
 | 
						|
  constructor(tribute) {
 | 
						|
    this.tribute = tribute;
 | 
						|
    this.tribute.events = this;
 | 
						|
  }
 | 
						|
 | 
						|
  static keys() {
 | 
						|
    return [
 | 
						|
      {
 | 
						|
        key: 9,
 | 
						|
        value: "TAB"
 | 
						|
      },
 | 
						|
      {
 | 
						|
        key: 8,
 | 
						|
        value: "DELETE"
 | 
						|
      },
 | 
						|
      {
 | 
						|
        key: 13,
 | 
						|
        value: "ENTER"
 | 
						|
      },
 | 
						|
      {
 | 
						|
        key: 27,
 | 
						|
        value: "ESCAPE"
 | 
						|
      },
 | 
						|
      {
 | 
						|
        key: 32,
 | 
						|
        value: "SPACE"
 | 
						|
      },
 | 
						|
      {
 | 
						|
        key: 38,
 | 
						|
        value: "UP"
 | 
						|
      },
 | 
						|
      {
 | 
						|
        key: 40,
 | 
						|
        value: "DOWN"
 | 
						|
      }
 | 
						|
    ];
 | 
						|
  }
 | 
						|
 | 
						|
  bind(element) {
 | 
						|
    element.boundKeydown = this.keydown.bind(element, this);
 | 
						|
    element.boundKeyup = this.keyup.bind(element, this);
 | 
						|
    element.boundInput = this.input.bind(element, this);
 | 
						|
 | 
						|
    element.addEventListener("keydown", element.boundKeydown, false);
 | 
						|
    element.addEventListener("keyup", element.boundKeyup, false);
 | 
						|
    element.addEventListener("input", element.boundInput, false);
 | 
						|
  }
 | 
						|
 | 
						|
  unbind(element) {
 | 
						|
    element.removeEventListener("keydown", element.boundKeydown, false);
 | 
						|
    element.removeEventListener("keyup", element.boundKeyup, false);
 | 
						|
    element.removeEventListener("input", element.boundInput, false);
 | 
						|
 | 
						|
    delete element.boundKeydown;
 | 
						|
    delete element.boundKeyup;
 | 
						|
    delete element.boundInput;
 | 
						|
  }
 | 
						|
 | 
						|
  keydown(instance, event) {
 | 
						|
    if (instance.shouldDeactivate(event)) {
 | 
						|
      instance.tribute.isActive = false;
 | 
						|
      instance.tribute.hideMenu();
 | 
						|
    }
 | 
						|
 | 
						|
    let element = this;
 | 
						|
    instance.commandEvent = false;
 | 
						|
 | 
						|
    TributeEvents.keys().forEach(o => {
 | 
						|
      if (o.key === event.keyCode) {
 | 
						|
        instance.commandEvent = true;
 | 
						|
        instance.callbacks()[o.value.toLowerCase()](event, element);
 | 
						|
      }
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  input(instance, event) {
 | 
						|
    instance.inputEvent = true;
 | 
						|
    instance.keyup.call(this, instance, event);
 | 
						|
  }
 | 
						|
 | 
						|
  click(instance, event) {
 | 
						|
    let tribute = instance.tribute;
 | 
						|
    if (tribute.menu && tribute.menu.contains(event.target)) {
 | 
						|
      let li = event.target;
 | 
						|
      event.preventDefault();
 | 
						|
      event.stopPropagation();
 | 
						|
      while (li.nodeName.toLowerCase() !== "li") {
 | 
						|
        li = li.parentNode;
 | 
						|
        if (!li || li === tribute.menu) {
 | 
						|
          throw new Error("cannot find the <li> container for the click");
 | 
						|
        }
 | 
						|
      }
 | 
						|
      tribute.selectItemAtIndex(li.getAttribute("data-index"), event);
 | 
						|
      tribute.hideMenu();
 | 
						|
 | 
						|
      // TODO: should fire with externalTrigger and target is outside of menu
 | 
						|
    } else if (tribute.current.element && !tribute.current.externalTrigger) {
 | 
						|
      tribute.current.externalTrigger = false;
 | 
						|
      setTimeout(() => tribute.hideMenu());
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  keyup(instance, event) {
 | 
						|
    if (instance.inputEvent) {
 | 
						|
      instance.inputEvent = false;
 | 
						|
    }
 | 
						|
    instance.updateSelection(this);
 | 
						|
 | 
						|
    if (event.keyCode === 27) return;
 | 
						|
 | 
						|
    if (!instance.tribute.allowSpaces && instance.tribute.hasTrailingSpace) {
 | 
						|
      instance.tribute.hasTrailingSpace = false;
 | 
						|
      instance.commandEvent = true;
 | 
						|
      instance.callbacks()["space"](event, this);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!instance.tribute.isActive) {
 | 
						|
      if (instance.tribute.autocompleteMode) {
 | 
						|
        instance.callbacks().triggerChar(event, this, "");
 | 
						|
      } else {
 | 
						|
        let keyCode = instance.getKeyCode(instance, this, event);
 | 
						|
 | 
						|
        if (isNaN(keyCode) || !keyCode) return;
 | 
						|
 | 
						|
        let trigger = instance.tribute.triggers().find(trigger => {
 | 
						|
          return trigger.charCodeAt(0) === keyCode;
 | 
						|
        });
 | 
						|
 | 
						|
        if (typeof trigger !== "undefined") {
 | 
						|
          instance.callbacks().triggerChar(event, this, trigger);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (
 | 
						|
      instance.tribute.current.mentionText.length <
 | 
						|
      instance.tribute.current.collection.menuShowMinLength
 | 
						|
    ) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (
 | 
						|
      ((instance.tribute.current.trigger ||
 | 
						|
        instance.tribute.autocompleteMode) &&
 | 
						|
        instance.commandEvent === false) ||
 | 
						|
      (instance.tribute.isActive && event.keyCode === 8)
 | 
						|
    ) {
 | 
						|
      instance.tribute.showMenuFor(this, true);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  shouldDeactivate(event) {
 | 
						|
    if (!this.tribute.isActive) return false;
 | 
						|
 | 
						|
    if (this.tribute.current.mentionText.length === 0) {
 | 
						|
      let eventKeyPressed = false;
 | 
						|
      TributeEvents.keys().forEach(o => {
 | 
						|
        if (event.keyCode === o.key) eventKeyPressed = true;
 | 
						|
      });
 | 
						|
 | 
						|
      return !eventKeyPressed;
 | 
						|
    }
 | 
						|
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  getKeyCode(instance, el, event) {
 | 
						|
    let tribute = instance.tribute;
 | 
						|
    let info = tribute.range.getTriggerInfo(
 | 
						|
      false,
 | 
						|
      tribute.hasTrailingSpace,
 | 
						|
      true,
 | 
						|
      tribute.allowSpaces,
 | 
						|
      tribute.autocompleteMode
 | 
						|
    );
 | 
						|
 | 
						|
    if (info) {
 | 
						|
      return info.mentionTriggerChar.charCodeAt(0);
 | 
						|
    } else {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  updateSelection(el) {
 | 
						|
    this.tribute.current.element = el;
 | 
						|
    let info = this.tribute.range.getTriggerInfo(
 | 
						|
      false,
 | 
						|
      this.tribute.hasTrailingSpace,
 | 
						|
      true,
 | 
						|
      this.tribute.allowSpaces,
 | 
						|
      this.tribute.autocompleteMode
 | 
						|
    );
 | 
						|
 | 
						|
    if (info) {
 | 
						|
      this.tribute.current.selectedPath = info.mentionSelectedPath;
 | 
						|
      this.tribute.current.mentionText = info.mentionText;
 | 
						|
      this.tribute.current.selectedOffset = info.mentionSelectedOffset;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  callbacks() {
 | 
						|
    return {
 | 
						|
      triggerChar: (e, el, trigger) => {
 | 
						|
        let tribute = this.tribute;
 | 
						|
        tribute.current.trigger = trigger;
 | 
						|
 | 
						|
        let collectionItem = tribute.collection.find(item => {
 | 
						|
          return item.trigger === trigger;
 | 
						|
        });
 | 
						|
 | 
						|
        tribute.current.collection = collectionItem;
 | 
						|
 | 
						|
        if (
 | 
						|
          tribute.current.mentionText.length >=
 | 
						|
            tribute.current.collection.menuShowMinLength &&
 | 
						|
          tribute.inputEvent
 | 
						|
        ) {
 | 
						|
          tribute.showMenuFor(el, true);
 | 
						|
        }
 | 
						|
      },
 | 
						|
      enter: (e, el) => {
 | 
						|
        // choose selection
 | 
						|
        if (this.tribute.isActive && this.tribute.current.filteredItems) {
 | 
						|
          e.preventDefault();
 | 
						|
          e.stopPropagation();
 | 
						|
          setTimeout(() => {
 | 
						|
            this.tribute.selectItemAtIndex(this.tribute.menuSelected, e);
 | 
						|
            this.tribute.hideMenu();
 | 
						|
          }, 0);
 | 
						|
        }
 | 
						|
      },
 | 
						|
      escape: (e, el) => {
 | 
						|
        if (this.tribute.isActive) {
 | 
						|
          e.preventDefault();
 | 
						|
          e.stopPropagation();
 | 
						|
          this.tribute.isActive = false;
 | 
						|
          this.tribute.hideMenu();
 | 
						|
        }
 | 
						|
      },
 | 
						|
      tab: (e, el) => {
 | 
						|
        // choose first match
 | 
						|
        this.callbacks().enter(e, el);
 | 
						|
      },
 | 
						|
      space: (e, el) => {
 | 
						|
        if (this.tribute.isActive) {
 | 
						|
          if (this.tribute.spaceSelectsMatch) {
 | 
						|
            this.callbacks().enter(e, el);
 | 
						|
          } else if (!this.tribute.allowSpaces) {
 | 
						|
            e.stopPropagation();
 | 
						|
            setTimeout(() => {
 | 
						|
              this.tribute.hideMenu();
 | 
						|
              this.tribute.isActive = false;
 | 
						|
            }, 0);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      },
 | 
						|
      up: (e, el) => {
 | 
						|
        // navigate up ul
 | 
						|
        if (this.tribute.isActive && this.tribute.current.filteredItems) {
 | 
						|
          e.preventDefault();
 | 
						|
          e.stopPropagation();
 | 
						|
          let count = this.tribute.current.filteredItems.length,
 | 
						|
            selected = this.tribute.menuSelected;
 | 
						|
 | 
						|
          if (count > selected && selected > 0) {
 | 
						|
            this.tribute.menuSelected--;
 | 
						|
            this.setActiveLi();
 | 
						|
          } else if (selected === 0) {
 | 
						|
            this.tribute.menuSelected = count - 1;
 | 
						|
            this.setActiveLi();
 | 
						|
            this.tribute.menu.scrollTop = this.tribute.menu.scrollHeight;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      },
 | 
						|
      down: (e, el) => {
 | 
						|
        // navigate down ul
 | 
						|
        if (this.tribute.isActive && this.tribute.current.filteredItems) {
 | 
						|
          e.preventDefault();
 | 
						|
          e.stopPropagation();
 | 
						|
          let count = this.tribute.current.filteredItems.length - 1,
 | 
						|
            selected = this.tribute.menuSelected;
 | 
						|
 | 
						|
          if (count > selected) {
 | 
						|
            this.tribute.menuSelected++;
 | 
						|
            this.setActiveLi();
 | 
						|
          } else if (count === selected) {
 | 
						|
            this.tribute.menuSelected = 0;
 | 
						|
            this.setActiveLi();
 | 
						|
            this.tribute.menu.scrollTop = 0;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      },
 | 
						|
      delete: (e, el) => {
 | 
						|
        if (
 | 
						|
          this.tribute.isActive &&
 | 
						|
          this.tribute.current.mentionText.length < 1
 | 
						|
        ) {
 | 
						|
          this.tribute.hideMenu();
 | 
						|
        } else if (this.tribute.isActive) {
 | 
						|
          this.tribute.showMenuFor(el);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  setActiveLi(index) {
 | 
						|
    let lis = this.tribute.menu.querySelectorAll("li"),
 | 
						|
      length = lis.length >>> 0;
 | 
						|
 | 
						|
    if (index) this.tribute.menuSelected = parseInt(index);
 | 
						|
 | 
						|
    for (let i = 0; i < length; i++) {
 | 
						|
      let li = lis[i];
 | 
						|
      if (i === this.tribute.menuSelected) {
 | 
						|
        li.classList.add(this.tribute.current.collection.selectClass);
 | 
						|
 | 
						|
        let liClientRect = li.getBoundingClientRect();
 | 
						|
        let menuClientRect = this.tribute.menu.getBoundingClientRect();
 | 
						|
 | 
						|
        if (liClientRect.bottom > menuClientRect.bottom) {
 | 
						|
          let scrollDistance = liClientRect.bottom - menuClientRect.bottom;
 | 
						|
          this.tribute.menu.scrollTop += scrollDistance;
 | 
						|
        } else if (liClientRect.top < menuClientRect.top) {
 | 
						|
          let scrollDistance = menuClientRect.top - liClientRect.top;
 | 
						|
          this.tribute.menu.scrollTop -= scrollDistance;
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        li.classList.remove(this.tribute.current.collection.selectClass);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  getFullHeight(elem, includeMargin) {
 | 
						|
    let height = elem.getBoundingClientRect().height;
 | 
						|
 | 
						|
    if (includeMargin) {
 | 
						|
      let style = elem.currentStyle || window.getComputedStyle(elem);
 | 
						|
      return (
 | 
						|
        height + parseFloat(style.marginTop) + parseFloat(style.marginBottom)
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    return height;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class TributeMenuEvents {
 | 
						|
  constructor(tribute) {
 | 
						|
    this.tribute = tribute;
 | 
						|
    this.tribute.menuEvents = this;
 | 
						|
    this.menu = this.tribute.menu;
 | 
						|
  }
 | 
						|
 | 
						|
  bind(menu) {
 | 
						|
    this.menuClickEvent = this.tribute.events.click.bind(null, this);
 | 
						|
    this.menuContainerScrollEvent = this.debounce(
 | 
						|
      () => {
 | 
						|
        if (this.tribute.isActive) {
 | 
						|
          this.tribute.hideMenu();
 | 
						|
        }
 | 
						|
      },
 | 
						|
      10,
 | 
						|
      false
 | 
						|
    );
 | 
						|
    this.windowResizeEvent = this.debounce(
 | 
						|
      () => {
 | 
						|
        if (this.tribute.isActive) {
 | 
						|
          this.tribute.hideMenu();
 | 
						|
        }
 | 
						|
      },
 | 
						|
      10,
 | 
						|
      false
 | 
						|
    );
 | 
						|
 | 
						|
    // fixes IE11 issues with mousedown
 | 
						|
    this.tribute.range
 | 
						|
      .getDocument()
 | 
						|
      .addEventListener("MSPointerDown", this.menuClickEvent, false);
 | 
						|
    this.tribute.range
 | 
						|
      .getDocument()
 | 
						|
      .addEventListener("mousedown", this.menuClickEvent, false);
 | 
						|
    window.addEventListener("resize", this.windowResizeEvent);
 | 
						|
 | 
						|
    if (this.menuContainer) {
 | 
						|
      this.menuContainer.addEventListener(
 | 
						|
        "scroll",
 | 
						|
        this.menuContainerScrollEvent,
 | 
						|
        false
 | 
						|
      );
 | 
						|
    } else {
 | 
						|
      window.addEventListener("scroll", this.menuContainerScrollEvent);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  unbind(menu) {
 | 
						|
    this.tribute.range
 | 
						|
      .getDocument()
 | 
						|
      .removeEventListener("mousedown", this.menuClickEvent, false);
 | 
						|
    this.tribute.range
 | 
						|
      .getDocument()
 | 
						|
      .removeEventListener("MSPointerDown", this.menuClickEvent, false);
 | 
						|
    window.removeEventListener("resize", this.windowResizeEvent);
 | 
						|
 | 
						|
    if (this.menuContainer) {
 | 
						|
      this.menuContainer.removeEventListener(
 | 
						|
        "scroll",
 | 
						|
        this.menuContainerScrollEvent,
 | 
						|
        false
 | 
						|
      );
 | 
						|
    } else {
 | 
						|
      window.removeEventListener("scroll", this.menuContainerScrollEvent);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  debounce(func, wait, immediate) {
 | 
						|
    var timeout;
 | 
						|
    return () => {
 | 
						|
      var context = this,
 | 
						|
        args = arguments;
 | 
						|
      var later = () => {
 | 
						|
        timeout = null;
 | 
						|
        if (!immediate) func.apply(context, args);
 | 
						|
      };
 | 
						|
      var callNow = immediate && !timeout;
 | 
						|
      clearTimeout(timeout);
 | 
						|
      timeout = setTimeout(later, wait);
 | 
						|
      if (callNow) func.apply(context, args);
 | 
						|
    };
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// Thanks to https://github.com/jeff-collins/ment.io
 | 
						|
 | 
						|
class TributeRange {
 | 
						|
    constructor(tribute) {
 | 
						|
        this.tribute = tribute;
 | 
						|
        this.tribute.range = this;
 | 
						|
    }
 | 
						|
 | 
						|
    getDocument() {
 | 
						|
        return document;
 | 
						|
    }
 | 
						|
 | 
						|
    positionMenuAtCaret(scrollTo) {
 | 
						|
        let context = this.tribute.current,
 | 
						|
            coordinates;
 | 
						|
 | 
						|
        let info = this.getTriggerInfo(false, this.tribute.hasTrailingSpace, true, this.tribute.allowSpaces, this.tribute.autocompleteMode);
 | 
						|
 | 
						|
        if (typeof info !== 'undefined') {
 | 
						|
 | 
						|
            if(!this.tribute.positionMenu){
 | 
						|
                this.tribute.menu.style.cssText = `display: block;`;
 | 
						|
                return
 | 
						|
            }
 | 
						|
 | 
						|
            if (!this.isContentEditable(context.element)) {
 | 
						|
                coordinates = this.getTextAreaOrInputUnderlinePosition(this.tribute.current.element,
 | 
						|
                    info.mentionPosition);
 | 
						|
            }
 | 
						|
            else {
 | 
						|
                coordinates = this.getContentEditableCaretPosition(info.mentionPosition);
 | 
						|
            }
 | 
						|
 | 
						|
            this.tribute.menu.style.cssText = `top: ${coordinates.top}px;
 | 
						|
                                     left: ${coordinates.left}px;
 | 
						|
                                     right: ${coordinates.right}px;
 | 
						|
                                     bottom: ${coordinates.bottom}px;
 | 
						|
                                     max-height: ${coordinates.maxHeight || 500}px;
 | 
						|
                                     max-width: ${coordinates.maxWidth || 300}px;
 | 
						|
                                     position: ${coordinates.position || 'absolute'};
 | 
						|
                                     display: block;`;
 | 
						|
 | 
						|
            if (coordinates.left === 'auto') {
 | 
						|
                this.tribute.menu.style.left = 'auto';
 | 
						|
            }
 | 
						|
 | 
						|
            if (coordinates.top === 'auto') {
 | 
						|
                this.tribute.menu.style.top = 'auto';
 | 
						|
            }
 | 
						|
 | 
						|
            if (scrollTo) this.scrollIntoView();
 | 
						|
 | 
						|
        } else {
 | 
						|
            this.tribute.menu.style.cssText = 'display: none';
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    get menuContainerIsBody() {
 | 
						|
        return this.tribute.menuContainer === document.body || !this.tribute.menuContainer;
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    selectElement(targetElement, path, offset) {
 | 
						|
        let range;
 | 
						|
        let elem = targetElement;
 | 
						|
 | 
						|
        if (path) {
 | 
						|
            for (var i = 0; i < path.length; i++) {
 | 
						|
                elem = elem.childNodes[path[i]];
 | 
						|
                if (elem === undefined) {
 | 
						|
                    return
 | 
						|
                }
 | 
						|
                while (elem.length < offset) {
 | 
						|
                    offset -= elem.length;
 | 
						|
                    elem = elem.nextSibling;
 | 
						|
                }
 | 
						|
                if (elem.childNodes.length === 0 && !elem.length) {
 | 
						|
                    elem = elem.previousSibling;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        let sel = this.getWindowSelection();
 | 
						|
 | 
						|
        range = this.getDocument().createRange();
 | 
						|
        range.setStart(elem, offset);
 | 
						|
        range.setEnd(elem, offset);
 | 
						|
        range.collapse(true);
 | 
						|
 | 
						|
        try {
 | 
						|
            sel.removeAllRanges();
 | 
						|
        } catch (error) {}
 | 
						|
 | 
						|
        sel.addRange(range);
 | 
						|
        targetElement.focus();
 | 
						|
    }
 | 
						|
 | 
						|
    replaceTriggerText(text, requireLeadingSpace, hasTrailingSpace, originalEvent, item) {
 | 
						|
        let info = this.getTriggerInfo(true, hasTrailingSpace, requireLeadingSpace, this.tribute.allowSpaces, this.tribute.autocompleteMode);
 | 
						|
 | 
						|
        if (info !== undefined) {
 | 
						|
            let context = this.tribute.current;
 | 
						|
            let replaceEvent = new CustomEvent('tribute-replaced', {
 | 
						|
                detail: {
 | 
						|
                    item: item,
 | 
						|
                    instance: context,
 | 
						|
                    context: info,
 | 
						|
                    event: originalEvent,
 | 
						|
                }
 | 
						|
            });
 | 
						|
 | 
						|
            if (!this.isContentEditable(context.element)) {
 | 
						|
                let myField = this.tribute.current.element;
 | 
						|
                let textSuffix = typeof this.tribute.replaceTextSuffix == 'string'
 | 
						|
                    ? this.tribute.replaceTextSuffix
 | 
						|
                    : ' ';
 | 
						|
                text += textSuffix;
 | 
						|
                let startPos = info.mentionPosition;
 | 
						|
                let endPos = info.mentionPosition + info.mentionText.length + textSuffix.length;
 | 
						|
                if (!this.tribute.autocompleteMode) {
 | 
						|
                    endPos += info.mentionTriggerChar.length - 1;
 | 
						|
                }
 | 
						|
                myField.value = myField.value.substring(0, startPos) + text +
 | 
						|
                    myField.value.substring(endPos, myField.value.length);
 | 
						|
                myField.selectionStart = startPos + text.length;
 | 
						|
                myField.selectionEnd = startPos + text.length;
 | 
						|
            } else {
 | 
						|
                // add a space to the end of the pasted text
 | 
						|
                let textSuffix = typeof this.tribute.replaceTextSuffix == 'string'
 | 
						|
                    ? this.tribute.replaceTextSuffix
 | 
						|
                    : '\xA0';
 | 
						|
                text += textSuffix;
 | 
						|
                let endPos = info.mentionPosition + info.mentionText.length;
 | 
						|
                if (!this.tribute.autocompleteMode) {
 | 
						|
                    endPos += info.mentionTriggerChar.length;
 | 
						|
                }
 | 
						|
                this.pasteHtml(text, info.mentionPosition, endPos);
 | 
						|
            }
 | 
						|
 | 
						|
            context.element.dispatchEvent(new CustomEvent('input', { bubbles: true }));
 | 
						|
            context.element.dispatchEvent(replaceEvent);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    pasteHtml(html, startPos, endPos) {
 | 
						|
        let range, sel;
 | 
						|
        sel = this.getWindowSelection();
 | 
						|
        range = this.getDocument().createRange();
 | 
						|
        range.setStart(sel.anchorNode, startPos);
 | 
						|
        range.setEnd(sel.anchorNode, endPos);
 | 
						|
        range.deleteContents();
 | 
						|
 | 
						|
        let el = this.getDocument().createElement('div');
 | 
						|
        el.innerHTML = html;
 | 
						|
        let frag = this.getDocument().createDocumentFragment(),
 | 
						|
            node, lastNode;
 | 
						|
        while ((node = el.firstChild)) {
 | 
						|
            lastNode = frag.appendChild(node);
 | 
						|
        }
 | 
						|
        range.insertNode(frag);
 | 
						|
 | 
						|
        // Preserve the selection
 | 
						|
        if (lastNode) {
 | 
						|
            range = range.cloneRange();
 | 
						|
            range.setStartAfter(lastNode);
 | 
						|
            range.collapse(true);
 | 
						|
            sel.removeAllRanges();
 | 
						|
            sel.addRange(range);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    getWindowSelection() {
 | 
						|
        if (this.tribute.collection[0].iframe?.getSelection) {
 | 
						|
            return this.tribute.collection[0].iframe.getSelection()
 | 
						|
        }
 | 
						|
 | 
						|
        return window.getSelection()
 | 
						|
    }
 | 
						|
 | 
						|
    getNodePositionInParent(element) {
 | 
						|
        if (element.parentNode === null) {
 | 
						|
            return 0
 | 
						|
        }
 | 
						|
 | 
						|
        for (var i = 0; i < element.parentNode.childNodes.length; i++) {
 | 
						|
            let node = element.parentNode.childNodes[i];
 | 
						|
 | 
						|
            if (node === element) {
 | 
						|
                return i
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    getContentEditableSelectedPath(ctx) {
 | 
						|
        let sel = this.getWindowSelection();
 | 
						|
        let selected = sel.anchorNode;
 | 
						|
        let path = [];
 | 
						|
        let offset;
 | 
						|
 | 
						|
        if (selected != null) {
 | 
						|
            let i;
 | 
						|
            let ce = selected.contentEditable;
 | 
						|
            while (selected !== null && ce !== 'true') {
 | 
						|
                i = this.getNodePositionInParent(selected);
 | 
						|
                path.push(i);
 | 
						|
                selected = selected.parentNode;
 | 
						|
                if (selected !== null) {
 | 
						|
                    ce = selected.contentEditable;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            path.reverse();
 | 
						|
 | 
						|
            // getRangeAt may not exist, need alternative
 | 
						|
            offset = sel.getRangeAt(0).startOffset;
 | 
						|
 | 
						|
            return {
 | 
						|
                selected: selected,
 | 
						|
                path: path,
 | 
						|
                offset: offset
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    getTextPrecedingCurrentSelection() {
 | 
						|
        let context = this.tribute.current,
 | 
						|
            text = '';
 | 
						|
 | 
						|
        if (!this.isContentEditable(context.element)) {
 | 
						|
            let textComponent = this.tribute.current.element;
 | 
						|
            if (textComponent) {
 | 
						|
                let startPos = textComponent.selectionStart;
 | 
						|
                if (textComponent.value && startPos >= 0) {
 | 
						|
                    text = textComponent.value.substring(0, startPos);
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
        } else {
 | 
						|
            let selectedElem = this.getWindowSelection().anchorNode;
 | 
						|
 | 
						|
            if (selectedElem != null) {
 | 
						|
                let workingNodeContent = selectedElem.textContent;
 | 
						|
                let selectStartOffset = this.getWindowSelection().getRangeAt(0).startOffset;
 | 
						|
 | 
						|
                if (workingNodeContent && selectStartOffset >= 0) {
 | 
						|
                    text = workingNodeContent.substring(0, selectStartOffset);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return text
 | 
						|
    }
 | 
						|
 | 
						|
    getLastWordInText(text) {
 | 
						|
        text = text.replace(/\u00A0/g, ' '); // https://stackoverflow.com/questions/29850407/how-do-i-replace-unicode-character-u00a0-with-a-space-in-javascript
 | 
						|
        var wordsArray;
 | 
						|
        if (this.tribute.autocompleteSeparator) {
 | 
						|
            wordsArray = text.split(this.tribute.autocompleteSeparator);
 | 
						|
        } else {
 | 
						|
            wordsArray = text.split(/\s+/);
 | 
						|
        }
 | 
						|
        var worldsCount = wordsArray.length - 1;
 | 
						|
        return wordsArray[worldsCount].trim();
 | 
						|
    }
 | 
						|
 | 
						|
    getTriggerInfo(menuAlreadyActive, hasTrailingSpace, requireLeadingSpace, allowSpaces, isAutocomplete) {
 | 
						|
        let ctx = this.tribute.current;
 | 
						|
        let selected, path, offset;
 | 
						|
 | 
						|
        if (!this.isContentEditable(ctx.element)) {
 | 
						|
            selected = this.tribute.current.element;
 | 
						|
        } else {
 | 
						|
            let selectionInfo = this.getContentEditableSelectedPath(ctx);
 | 
						|
 | 
						|
            if (selectionInfo) {
 | 
						|
                selected = selectionInfo.selected;
 | 
						|
                path = selectionInfo.path;
 | 
						|
                offset = selectionInfo.offset;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        let effectiveRange = this.getTextPrecedingCurrentSelection();
 | 
						|
        let lastWordOfEffectiveRange = this.getLastWordInText(effectiveRange);
 | 
						|
 | 
						|
        if (isAutocomplete) {
 | 
						|
            return {
 | 
						|
                mentionPosition: effectiveRange.length - lastWordOfEffectiveRange.length,
 | 
						|
                mentionText: lastWordOfEffectiveRange,
 | 
						|
                mentionSelectedElement: selected,
 | 
						|
                mentionSelectedPath: path,
 | 
						|
                mentionSelectedOffset: offset
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if (effectiveRange !== undefined && effectiveRange !== null) {
 | 
						|
            let mostRecentTriggerCharPos = -1;
 | 
						|
            let triggerChar;
 | 
						|
 | 
						|
            this.tribute.collection.forEach(config => {
 | 
						|
                let c = config.trigger;
 | 
						|
                let idx = config.requireLeadingSpace ?
 | 
						|
                    this.lastIndexWithLeadingSpace(effectiveRange, c) :
 | 
						|
                    effectiveRange.lastIndexOf(c);
 | 
						|
 | 
						|
                if (idx > mostRecentTriggerCharPos) {
 | 
						|
                    mostRecentTriggerCharPos = idx;
 | 
						|
                    triggerChar = c;
 | 
						|
                    requireLeadingSpace = config.requireLeadingSpace;
 | 
						|
                }
 | 
						|
            });
 | 
						|
 | 
						|
            if (mostRecentTriggerCharPos >= 0 &&
 | 
						|
                (
 | 
						|
                    mostRecentTriggerCharPos === 0 ||
 | 
						|
                    !requireLeadingSpace ||
 | 
						|
                    /[\xA0\s]/g.test(
 | 
						|
                        effectiveRange.substring(
 | 
						|
                            mostRecentTriggerCharPos - 1,
 | 
						|
                            mostRecentTriggerCharPos)
 | 
						|
                    )
 | 
						|
                )
 | 
						|
            ) {
 | 
						|
                let currentTriggerSnippet = effectiveRange.substring(mostRecentTriggerCharPos + triggerChar.length,
 | 
						|
                    effectiveRange.length);
 | 
						|
 | 
						|
                triggerChar = effectiveRange.substring(mostRecentTriggerCharPos, mostRecentTriggerCharPos + triggerChar.length);
 | 
						|
                let firstSnippetChar = currentTriggerSnippet.substring(0, 1);
 | 
						|
                let leadingSpace = currentTriggerSnippet.length > 0 &&
 | 
						|
                    (
 | 
						|
                        firstSnippetChar === ' ' ||
 | 
						|
                        firstSnippetChar === '\xA0'
 | 
						|
                    );
 | 
						|
                if (hasTrailingSpace) {
 | 
						|
                    currentTriggerSnippet = currentTriggerSnippet.trim();
 | 
						|
                }
 | 
						|
 | 
						|
                let regex = allowSpaces ? /[^\S ]/g : /[\xA0\s]/g;
 | 
						|
 | 
						|
                this.tribute.hasTrailingSpace = regex.test(currentTriggerSnippet);
 | 
						|
 | 
						|
                if (!leadingSpace && (menuAlreadyActive || !(regex.test(currentTriggerSnippet)))) {
 | 
						|
                    return {
 | 
						|
                        mentionPosition: mostRecentTriggerCharPos,
 | 
						|
                        mentionText: currentTriggerSnippet,
 | 
						|
                        mentionSelectedElement: selected,
 | 
						|
                        mentionSelectedPath: path,
 | 
						|
                        mentionSelectedOffset: offset,
 | 
						|
                        mentionTriggerChar: triggerChar
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    lastIndexWithLeadingSpace (str, trigger) {
 | 
						|
        let reversedStr = str.split('').reverse().join('');
 | 
						|
        let index = -1;
 | 
						|
 | 
						|
        for (let cidx = 0, len = str.length; cidx < len; cidx++) {
 | 
						|
            let firstChar = cidx === str.length - 1;
 | 
						|
            let leadingSpace = /\s/.test(reversedStr[cidx + 1]);
 | 
						|
 | 
						|
            let match = true;
 | 
						|
            for (let triggerIdx = trigger.length - 1; triggerIdx >= 0; triggerIdx--) {
 | 
						|
              if (trigger[triggerIdx] !== reversedStr[cidx-triggerIdx]) {
 | 
						|
                match = false;
 | 
						|
                break
 | 
						|
              }
 | 
						|
            }
 | 
						|
 | 
						|
            if (match && (firstChar || leadingSpace)) {
 | 
						|
                index = str.length - 1 - cidx;
 | 
						|
                break
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return index
 | 
						|
    }
 | 
						|
 | 
						|
    isContentEditable(element) {
 | 
						|
        return element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA'
 | 
						|
    }
 | 
						|
 | 
						|
    isMenuOffScreen(coordinates, menuDimensions) {
 | 
						|
        let windowWidth = window.innerWidth;
 | 
						|
        let windowHeight = window.innerHeight;
 | 
						|
        let doc = document.documentElement;
 | 
						|
        let windowLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
 | 
						|
        let windowTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
 | 
						|
 | 
						|
        let menuTop = typeof coordinates.top === 'number' ? coordinates.top : windowTop + windowHeight - coordinates.bottom - menuDimensions.height;
 | 
						|
        let menuRight = typeof coordinates.right === 'number' ? coordinates.right : coordinates.left + menuDimensions.width;
 | 
						|
        let menuBottom = typeof coordinates.bottom === 'number' ? coordinates.bottom : coordinates.top + menuDimensions.height;
 | 
						|
        let menuLeft = typeof coordinates.left === 'number' ? coordinates.left : windowLeft + windowWidth - coordinates.right - menuDimensions.width;
 | 
						|
 | 
						|
        return {
 | 
						|
            top: menuTop < Math.floor(windowTop),
 | 
						|
            right: menuRight > Math.ceil(windowLeft + windowWidth),
 | 
						|
            bottom: menuBottom > Math.ceil(windowTop + windowHeight),
 | 
						|
            left: menuLeft < Math.floor(windowLeft)
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    getMenuDimensions() {
 | 
						|
        // Width of the menu depends of its contents and position
 | 
						|
        // We must check what its width would be without any obstruction
 | 
						|
        // This way, we can achieve good positioning for flipping the menu
 | 
						|
        let dimensions = {
 | 
						|
            width: null,
 | 
						|
            height: null
 | 
						|
        };
 | 
						|
 | 
						|
        this.tribute.menu.style.cssText = `top: 0px;
 | 
						|
                                 left: 0px;
 | 
						|
                                 position: fixed;
 | 
						|
                                 display: block;
 | 
						|
                                 visibility; hidden;
 | 
						|
                                 max-height:500px;`;
 | 
						|
       dimensions.width = this.tribute.menu.offsetWidth;
 | 
						|
       dimensions.height = this.tribute.menu.offsetHeight;
 | 
						|
 | 
						|
       this.tribute.menu.style.cssText = `display: none;`;
 | 
						|
 | 
						|
       return dimensions
 | 
						|
    }
 | 
						|
 | 
						|
    getTextAreaOrInputUnderlinePosition(element, position, flipped) {
 | 
						|
        let properties = ['direction', 'boxSizing', 'width', 'height', 'overflowX',
 | 
						|
            'overflowY', 'borderTopWidth', 'borderRightWidth',
 | 
						|
            'borderBottomWidth', 'borderLeftWidth', 'borderStyle', 'paddingTop',
 | 
						|
            'paddingRight', 'paddingBottom', 'paddingLeft',
 | 
						|
            'fontStyle', 'fontVariant', 'fontWeight', 'fontStretch',
 | 
						|
            'fontSize', 'fontSizeAdjust', 'lineHeight', 'fontFamily',
 | 
						|
            'textAlign', 'textTransform', 'textIndent',
 | 
						|
            'textDecoration', 'letterSpacing', 'wordSpacing'
 | 
						|
        ];
 | 
						|
 | 
						|
        let div = this.getDocument().createElement('div');
 | 
						|
        div.id = 'input-textarea-caret-position-mirror-div';
 | 
						|
        this.getDocument().body.appendChild(div);
 | 
						|
 | 
						|
        let style = div.style;
 | 
						|
        let computed = window.getComputedStyle ? getComputedStyle(element) : element.currentStyle;
 | 
						|
 | 
						|
        style.whiteSpace = 'pre-wrap';
 | 
						|
        if (element.nodeName !== 'INPUT') {
 | 
						|
            style.wordWrap = 'break-word';
 | 
						|
        }
 | 
						|
 | 
						|
        style.position = 'absolute';
 | 
						|
        style.visibility = 'hidden';
 | 
						|
 | 
						|
        // transfer the element's properties to the div
 | 
						|
        properties.forEach(prop => {
 | 
						|
            style[prop] = computed[prop];
 | 
						|
        });
 | 
						|
 | 
						|
        //NOT SURE WHY THIS IS HERE AND IT DOESNT SEEM HELPFUL
 | 
						|
        // if (isFirefox) {
 | 
						|
        //     style.width = `${(parseInt(computed.width) - 2)}px`
 | 
						|
        //     if (element.scrollHeight > parseInt(computed.height))
 | 
						|
        //         style.overflowY = 'scroll'
 | 
						|
        // } else {
 | 
						|
        //     style.overflow = 'hidden'
 | 
						|
        // }
 | 
						|
 | 
						|
        let span0 = document.createElement('span');
 | 
						|
        span0.textContent =  element.value.substring(0, position);
 | 
						|
        div.appendChild(span0);
 | 
						|
 | 
						|
        if (element.nodeName === 'INPUT') {
 | 
						|
            div.textContent = div.textContent.replace(/\s/g, ' ');
 | 
						|
        }
 | 
						|
 | 
						|
        //Create a span in the div that represents where the cursor
 | 
						|
        //should be
 | 
						|
        let span = this.getDocument().createElement('span');
 | 
						|
        //we give it no content as this represents the cursor
 | 
						|
        span.textContent = '​';
 | 
						|
        div.appendChild(span);
 | 
						|
 | 
						|
        let span2 = this.getDocument().createElement('span');
 | 
						|
        span2.textContent = element.value.substring(position);
 | 
						|
        div.appendChild(span2);
 | 
						|
 | 
						|
        let rect = element.getBoundingClientRect();
 | 
						|
 | 
						|
        //position the div exactly over the element
 | 
						|
        //so we can get the bounding client rect for the span and
 | 
						|
        //it should represent exactly where the cursor is
 | 
						|
        div.style.position = 'fixed';
 | 
						|
        div.style.left = rect.left + 'px';
 | 
						|
        div.style.top = rect.top + 'px';
 | 
						|
        div.style.width = rect.width + 'px';
 | 
						|
        div.style.height = rect.height + 'px';
 | 
						|
        div.scrollTop = element.scrollTop;
 | 
						|
 | 
						|
        var spanRect = span.getBoundingClientRect();
 | 
						|
        this.getDocument().body.removeChild(div);
 | 
						|
        return this.getFixedCoordinatesRelativeToRect(spanRect);
 | 
						|
    }
 | 
						|
 | 
						|
    getContentEditableCaretPosition(selectedNodePosition) {
 | 
						|
        let range;
 | 
						|
        let sel = this.getWindowSelection();
 | 
						|
 | 
						|
        range = this.getDocument().createRange();
 | 
						|
        range.setStart(sel.anchorNode, selectedNodePosition);
 | 
						|
        range.setEnd(sel.anchorNode, selectedNodePosition);
 | 
						|
 | 
						|
        range.collapse(false);
 | 
						|
 | 
						|
        let rect = range.getBoundingClientRect();
 | 
						|
 | 
						|
        return this.getFixedCoordinatesRelativeToRect(rect);
 | 
						|
    }
 | 
						|
 | 
						|
    getFixedCoordinatesRelativeToRect(rect) {
 | 
						|
        let coordinates = {
 | 
						|
            position: 'fixed',
 | 
						|
            left: rect.left,
 | 
						|
            top: rect.top + rect.height
 | 
						|
        };
 | 
						|
 | 
						|
        let menuDimensions = this.getMenuDimensions();
 | 
						|
 | 
						|
        var availableSpaceOnTop = rect.top;
 | 
						|
        var availableSpaceOnBottom = window.innerHeight - (rect.top + rect.height);
 | 
						|
 | 
						|
        //check to see where's the right place to put the menu vertically
 | 
						|
        if (availableSpaceOnBottom < menuDimensions.height) {
 | 
						|
          if (availableSpaceOnTop >= menuDimensions.height || availableSpaceOnTop > availableSpaceOnBottom) {
 | 
						|
            coordinates.top = 'auto';
 | 
						|
            coordinates.bottom = window.innerHeight - rect.top;
 | 
						|
            if (availableSpaceOnBottom < menuDimensions.height) {
 | 
						|
              coordinates.maxHeight = availableSpaceOnTop;
 | 
						|
            }
 | 
						|
          } else {
 | 
						|
            if (availableSpaceOnTop < menuDimensions.height) {
 | 
						|
              coordinates.maxHeight = availableSpaceOnBottom;
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        var availableSpaceOnLeft = rect.left;
 | 
						|
        var availableSpaceOnRight = window.innerWidth - rect.left;
 | 
						|
 | 
						|
        //check to see where's the right place to put the menu horizontally
 | 
						|
        if (availableSpaceOnRight < menuDimensions.width) {
 | 
						|
          if (availableSpaceOnLeft >= menuDimensions.width || availableSpaceOnLeft > availableSpaceOnRight) {
 | 
						|
            coordinates.left = 'auto';
 | 
						|
            coordinates.right = window.innerWidth - rect.left;
 | 
						|
            if (availableSpaceOnRight < menuDimensions.width) {
 | 
						|
              coordinates.maxWidth = availableSpaceOnLeft;
 | 
						|
            }
 | 
						|
          } else {
 | 
						|
            if (availableSpaceOnLeft < menuDimensions.width) {
 | 
						|
              coordinates.maxWidth = availableSpaceOnRight;
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        return coordinates
 | 
						|
    }
 | 
						|
 | 
						|
    scrollIntoView(elem) {
 | 
						|
        let reasonableBuffer = 20,
 | 
						|
            clientRect;
 | 
						|
        let maxScrollDisplacement = 100;
 | 
						|
        let e = this.menu;
 | 
						|
 | 
						|
        if (typeof e === 'undefined') return;
 | 
						|
 | 
						|
        while (clientRect === undefined || clientRect.height === 0) {
 | 
						|
            clientRect = e.getBoundingClientRect();
 | 
						|
 | 
						|
            if (clientRect.height === 0) {
 | 
						|
                e = e.childNodes[0];
 | 
						|
                if (e === undefined || !e.getBoundingClientRect) {
 | 
						|
                    return
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        let elemTop = clientRect.top;
 | 
						|
        let elemBottom = elemTop + clientRect.height;
 | 
						|
 | 
						|
        if (elemTop < 0) {
 | 
						|
            window.scrollTo(0, window.pageYOffset + clientRect.top - reasonableBuffer);
 | 
						|
        } else if (elemBottom > window.innerHeight) {
 | 
						|
            let maxY = window.pageYOffset + clientRect.top - reasonableBuffer;
 | 
						|
 | 
						|
            if (maxY - window.pageYOffset > maxScrollDisplacement) {
 | 
						|
                maxY = window.pageYOffset + maxScrollDisplacement;
 | 
						|
            }
 | 
						|
 | 
						|
            let targetY = window.pageYOffset - (window.innerHeight - elemBottom);
 | 
						|
 | 
						|
            if (targetY > maxY) {
 | 
						|
                targetY = maxY;
 | 
						|
            }
 | 
						|
 | 
						|
            window.scrollTo(0, targetY);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// Thanks to https://github.com/mattyork/fuzzy
 | 
						|
class TributeSearch {
 | 
						|
    constructor(tribute) {
 | 
						|
        this.tribute = tribute;
 | 
						|
        this.tribute.search = this;
 | 
						|
    }
 | 
						|
 | 
						|
    simpleFilter(pattern, array) {
 | 
						|
        return array.filter(string => {
 | 
						|
            return this.test(pattern, string)
 | 
						|
        })
 | 
						|
    }
 | 
						|
 | 
						|
    test(pattern, string) {
 | 
						|
        return this.match(pattern, string) !== null
 | 
						|
    }
 | 
						|
 | 
						|
    match(pattern, string, opts) {
 | 
						|
        opts = opts || {};
 | 
						|
        let len = string.length,
 | 
						|
            pre = opts.pre || '',
 | 
						|
            post = opts.post || '',
 | 
						|
            compareString = opts.caseSensitive && string || string.toLowerCase();
 | 
						|
 | 
						|
        if (opts.skip) {
 | 
						|
            return {rendered: string, score: 0}
 | 
						|
        }
 | 
						|
 | 
						|
        pattern = opts.caseSensitive && pattern || pattern.toLowerCase();
 | 
						|
 | 
						|
        let patternCache = this.traverse(compareString, pattern, 0, 0, []);
 | 
						|
        if (!patternCache) {
 | 
						|
            return null
 | 
						|
        }
 | 
						|
        return {
 | 
						|
            rendered: this.render(string, patternCache.cache, pre, post),
 | 
						|
            score: patternCache.score
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    traverse(string, pattern, stringIndex, patternIndex, patternCache) {
 | 
						|
        if (this.tribute.autocompleteSeparator) {
 | 
						|
            // if the pattern search at end
 | 
						|
            pattern = pattern.split(this.tribute.autocompleteSeparator).splice(-1)[0];
 | 
						|
        }
 | 
						|
 | 
						|
        if (pattern.length === patternIndex) {
 | 
						|
 | 
						|
            // calculate score and copy the cache containing the indices where it's found
 | 
						|
            return {
 | 
						|
                score: this.calculateScore(patternCache),
 | 
						|
                cache: patternCache.slice()
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        // if string at end or remaining pattern > remaining string
 | 
						|
        if (string.length === stringIndex || pattern.length - patternIndex > string.length - stringIndex) {
 | 
						|
            return undefined
 | 
						|
        }
 | 
						|
 | 
						|
        let c = pattern[patternIndex];
 | 
						|
        let index = string.indexOf(c, stringIndex);
 | 
						|
        let best, temp;
 | 
						|
 | 
						|
        while (index > -1) {
 | 
						|
            patternCache.push(index);
 | 
						|
            temp = this.traverse(string, pattern, index + 1, patternIndex + 1, patternCache);
 | 
						|
            patternCache.pop();
 | 
						|
 | 
						|
            // if downstream traversal failed, return best answer so far
 | 
						|
            if (!temp) {
 | 
						|
                return best
 | 
						|
            }
 | 
						|
 | 
						|
            if (!best || best.score < temp.score) {
 | 
						|
                best = temp;
 | 
						|
            }
 | 
						|
 | 
						|
            index = string.indexOf(c, index + 1);
 | 
						|
        }
 | 
						|
 | 
						|
        return best
 | 
						|
    }
 | 
						|
 | 
						|
    calculateScore(patternCache) {
 | 
						|
        let score = 0;
 | 
						|
        let temp = 1;
 | 
						|
 | 
						|
        patternCache.forEach((index, i) => {
 | 
						|
            if (i > 0) {
 | 
						|
                if (patternCache[i - 1] + 1 === index) {
 | 
						|
                    temp += temp + 1;
 | 
						|
                }
 | 
						|
                else {
 | 
						|
                    temp = 1;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            score += temp;
 | 
						|
        });
 | 
						|
 | 
						|
        return score
 | 
						|
    }
 | 
						|
 | 
						|
    render(string, indices, pre, post) {
 | 
						|
        var rendered = string.substring(0, indices[0]);
 | 
						|
 | 
						|
        indices.forEach((index, i) => {
 | 
						|
            rendered += pre + string[index] + post +
 | 
						|
                string.substring(index + 1, (indices[i + 1]) ? indices[i + 1] : string.length);
 | 
						|
        });
 | 
						|
 | 
						|
        return rendered
 | 
						|
    }
 | 
						|
 | 
						|
    filter(pattern, arr, opts) {
 | 
						|
        opts = opts || {};
 | 
						|
        return arr
 | 
						|
            .reduce((prev, element, idx, arr) => {
 | 
						|
                let str = element;
 | 
						|
 | 
						|
                if (opts.extract) {
 | 
						|
                    str = opts.extract(element);
 | 
						|
 | 
						|
                    if (!str) { // take care of undefineds / nulls / etc.
 | 
						|
                        str = '';
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                let rendered = this.match(pattern, str, opts);
 | 
						|
 | 
						|
                if (rendered != null) {
 | 
						|
                    prev[prev.length] = {
 | 
						|
                        string: rendered.rendered,
 | 
						|
                        score: rendered.score,
 | 
						|
                        index: idx,
 | 
						|
                        original: element
 | 
						|
                    };
 | 
						|
                }
 | 
						|
 | 
						|
                return prev
 | 
						|
            }, [])
 | 
						|
 | 
						|
        .sort((a, b) => {
 | 
						|
            let compare = b.score - a.score;
 | 
						|
            if (compare) return compare
 | 
						|
            return a.index - b.index
 | 
						|
        })
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
class Tribute {
 | 
						|
  constructor({
 | 
						|
    values = null,
 | 
						|
    loadingItemTemplate = null,
 | 
						|
    iframe = null,
 | 
						|
    selectClass = "highlight",
 | 
						|
    containerClass = "tribute-container",
 | 
						|
    itemClass = "",
 | 
						|
    trigger = "@",
 | 
						|
    autocompleteMode = false,
 | 
						|
    autocompleteSeparator = null,
 | 
						|
    selectTemplate = null,
 | 
						|
    menuItemTemplate = null,
 | 
						|
    lookup = "key",
 | 
						|
    fillAttr = "value",
 | 
						|
    collection = null,
 | 
						|
    menuContainer = null,
 | 
						|
    noMatchTemplate = null,
 | 
						|
    requireLeadingSpace = true,
 | 
						|
    allowSpaces = false,
 | 
						|
    replaceTextSuffix = null,
 | 
						|
    positionMenu = true,
 | 
						|
    spaceSelectsMatch = false,
 | 
						|
    searchOpts = {},
 | 
						|
    menuItemLimit = null,
 | 
						|
    menuShowMinLength = 0
 | 
						|
  }) {
 | 
						|
    this.autocompleteMode = autocompleteMode;
 | 
						|
    this.autocompleteSeparator = autocompleteSeparator;
 | 
						|
    this.menuSelected = 0;
 | 
						|
    this.current = {};
 | 
						|
    this.inputEvent = false;
 | 
						|
    this.isActive = false;
 | 
						|
    this.menuContainer = menuContainer;
 | 
						|
    this.allowSpaces = allowSpaces;
 | 
						|
    this.replaceTextSuffix = replaceTextSuffix;
 | 
						|
    this.positionMenu = positionMenu;
 | 
						|
    this.hasTrailingSpace = false;
 | 
						|
    this.spaceSelectsMatch = spaceSelectsMatch;
 | 
						|
 | 
						|
    if (this.autocompleteMode) {
 | 
						|
      trigger = "";
 | 
						|
      allowSpaces = false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (values) {
 | 
						|
      this.collection = [
 | 
						|
        {
 | 
						|
          // symbol that starts the lookup
 | 
						|
          trigger: trigger,
 | 
						|
 | 
						|
          // is it wrapped in an iframe
 | 
						|
          iframe: iframe,
 | 
						|
 | 
						|
          // class applied to selected item
 | 
						|
          selectClass: selectClass,
 | 
						|
 | 
						|
          // class applied to the Container
 | 
						|
          containerClass: containerClass,
 | 
						|
 | 
						|
          // class applied to each item
 | 
						|
          itemClass: itemClass,
 | 
						|
 | 
						|
          // function called on select that retuns the content to insert
 | 
						|
          selectTemplate: (
 | 
						|
            selectTemplate || Tribute.defaultSelectTemplate
 | 
						|
          ).bind(this),
 | 
						|
 | 
						|
          // function called that returns content for an item
 | 
						|
          menuItemTemplate: (
 | 
						|
            menuItemTemplate || Tribute.defaultMenuItemTemplate
 | 
						|
          ).bind(this),
 | 
						|
 | 
						|
          // function called when menu is empty, disables hiding of menu.
 | 
						|
          noMatchTemplate: (t => {
 | 
						|
            if (typeof t === "string") {
 | 
						|
              if (t.trim() === "") return null;
 | 
						|
              return t;
 | 
						|
            }
 | 
						|
            if (typeof t === "function") {
 | 
						|
              return t.bind(this);
 | 
						|
            }
 | 
						|
 | 
						|
            return (
 | 
						|
              noMatchTemplate ||
 | 
						|
              function() {
 | 
						|
                return "<li>No Match Found!</li>";
 | 
						|
              }.bind(this)
 | 
						|
            );
 | 
						|
          })(noMatchTemplate),
 | 
						|
 | 
						|
          // column to search against in the object
 | 
						|
          lookup: lookup,
 | 
						|
 | 
						|
          // column that contains the content to insert by default
 | 
						|
          fillAttr: fillAttr,
 | 
						|
 | 
						|
          // array of objects or a function returning an array of objects
 | 
						|
          values: values,
 | 
						|
 | 
						|
          // useful for when values is an async function
 | 
						|
          loadingItemTemplate: loadingItemTemplate,
 | 
						|
 | 
						|
          requireLeadingSpace: requireLeadingSpace,
 | 
						|
 | 
						|
          searchOpts: searchOpts,
 | 
						|
 | 
						|
          menuItemLimit: menuItemLimit,
 | 
						|
 | 
						|
          menuShowMinLength: menuShowMinLength
 | 
						|
        }
 | 
						|
      ];
 | 
						|
    } else if (collection) {
 | 
						|
      if (this.autocompleteMode)
 | 
						|
        console.warn(
 | 
						|
          "Tribute in autocomplete mode does not work for collections"
 | 
						|
        );
 | 
						|
      this.collection = collection.map(item => {
 | 
						|
        return {
 | 
						|
          trigger: item.trigger || trigger,
 | 
						|
          iframe: item.iframe || iframe,
 | 
						|
          selectClass: item.selectClass || selectClass,
 | 
						|
          containerClass: item.containerClass || containerClass,
 | 
						|
          itemClass: item.itemClass || itemClass,
 | 
						|
          selectTemplate: (
 | 
						|
            item.selectTemplate || Tribute.defaultSelectTemplate
 | 
						|
          ).bind(this),
 | 
						|
          menuItemTemplate: (
 | 
						|
            item.menuItemTemplate || Tribute.defaultMenuItemTemplate
 | 
						|
          ).bind(this),
 | 
						|
          // function called when menu is empty, disables hiding of menu.
 | 
						|
          noMatchTemplate: (t => {
 | 
						|
            if (typeof t === "string") {
 | 
						|
              if (t.trim() === "") return null;
 | 
						|
              return t;
 | 
						|
            }
 | 
						|
            if (typeof t === "function") {
 | 
						|
              return t.bind(this);
 | 
						|
            }
 | 
						|
 | 
						|
            return (
 | 
						|
              noMatchTemplate ||
 | 
						|
              function() {
 | 
						|
                return "<li>No Match Found!</li>";
 | 
						|
              }.bind(this)
 | 
						|
            );
 | 
						|
          })(noMatchTemplate),
 | 
						|
          lookup: item.lookup || lookup,
 | 
						|
          fillAttr: item.fillAttr || fillAttr,
 | 
						|
          values: item.values,
 | 
						|
          loadingItemTemplate: item.loadingItemTemplate,
 | 
						|
          requireLeadingSpace: item.requireLeadingSpace,
 | 
						|
          searchOpts: item.searchOpts || searchOpts,
 | 
						|
          menuItemLimit: item.menuItemLimit || menuItemLimit,
 | 
						|
          menuShowMinLength: item.menuShowMinLength || menuShowMinLength
 | 
						|
        };
 | 
						|
      });
 | 
						|
    } else {
 | 
						|
      throw new Error("[Tribute] No collection specified.");
 | 
						|
    }
 | 
						|
 | 
						|
    new TributeRange(this);
 | 
						|
    new TributeEvents(this);
 | 
						|
    new TributeMenuEvents(this);
 | 
						|
    new TributeSearch(this);
 | 
						|
  }
 | 
						|
 | 
						|
  get isActive() {
 | 
						|
    return this._isActive;
 | 
						|
  }
 | 
						|
 | 
						|
  set isActive(val) {
 | 
						|
    if (this._isActive != val) {
 | 
						|
      this._isActive = val;
 | 
						|
      if (this.current.element) {
 | 
						|
        let noMatchEvent = new CustomEvent(`tribute-active-${val}`);
 | 
						|
        this.current.element.dispatchEvent(noMatchEvent);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  static defaultSelectTemplate(item) {
 | 
						|
    if (typeof item === "undefined")
 | 
						|
      return `${this.current.collection.trigger}${this.current.mentionText}`;
 | 
						|
    if (this.range.isContentEditable(this.current.element)) {
 | 
						|
      return (
 | 
						|
        '<span class="tribute-mention">' +
 | 
						|
        (this.current.collection.trigger +
 | 
						|
          item.original[this.current.collection.fillAttr]) +
 | 
						|
        "</span>"
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    return (
 | 
						|
      this.current.collection.trigger +
 | 
						|
      item.original[this.current.collection.fillAttr]
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  static defaultMenuItemTemplate(matchItem) {
 | 
						|
    return matchItem.string;
 | 
						|
  }
 | 
						|
 | 
						|
  static inputTypes() {
 | 
						|
    return ["TEXTAREA", "INPUT"];
 | 
						|
  }
 | 
						|
 | 
						|
  triggers() {
 | 
						|
    return this.collection.map(config => {
 | 
						|
      return config.trigger;
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  attach(el) {
 | 
						|
    if (!el) {
 | 
						|
      throw new Error("[Tribute] Must pass in a DOM node or NodeList.");
 | 
						|
    }
 | 
						|
 | 
						|
    // Check if it is a jQuery collection
 | 
						|
    if (typeof jQuery !== "undefined" && el instanceof jQuery) {
 | 
						|
      el = el.get();
 | 
						|
    }
 | 
						|
 | 
						|
    // Is el an Array/Array-like object?
 | 
						|
    if (
 | 
						|
      el.constructor === NodeList ||
 | 
						|
      el.constructor === HTMLCollection ||
 | 
						|
      el.constructor === Array
 | 
						|
    ) {
 | 
						|
      let length = el.length;
 | 
						|
      for (var i = 0; i < length; ++i) {
 | 
						|
        this._attach(el[i]);
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      this._attach(el);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  _attach(el) {
 | 
						|
    if (el.hasAttribute("data-tribute")) {
 | 
						|
      console.warn("Tribute was already bound to " + el.nodeName);
 | 
						|
    }
 | 
						|
 | 
						|
    this.ensureEditable(el);
 | 
						|
    this.events.bind(el);
 | 
						|
    el.setAttribute("data-tribute", true);
 | 
						|
  }
 | 
						|
 | 
						|
  ensureEditable(element) {
 | 
						|
    if (Tribute.inputTypes().indexOf(element.nodeName) === -1) {
 | 
						|
      if (element.contentEditable) {
 | 
						|
        element.contentEditable = true;
 | 
						|
      } else {
 | 
						|
        throw new Error("[Tribute] Cannot bind to " + element.nodeName);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  createMenu(containerClass) {
 | 
						|
    let wrapper = this.range.getDocument().createElement("div"),
 | 
						|
      ul = this.range.getDocument().createElement("ul");
 | 
						|
    wrapper.className = containerClass;
 | 
						|
    wrapper.appendChild(ul);
 | 
						|
 | 
						|
    if (this.menuContainer) {
 | 
						|
      return this.menuContainer.appendChild(wrapper);
 | 
						|
    }
 | 
						|
 | 
						|
    return this.range.getDocument().body.appendChild(wrapper);
 | 
						|
  }
 | 
						|
 | 
						|
  showMenuFor(element, scrollTo) {
 | 
						|
    // Only proceed if menu isn't already shown for the current element & mentionText
 | 
						|
    if (
 | 
						|
      this.isActive &&
 | 
						|
      this.current.element === element &&
 | 
						|
      this.current.mentionText === this.currentMentionTextSnapshot
 | 
						|
    ) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    this.currentMentionTextSnapshot = this.current.mentionText;
 | 
						|
 | 
						|
    // create the menu if it doesn't exist.
 | 
						|
    if (!this.menu) {
 | 
						|
      this.menu = this.createMenu(this.current.collection.containerClass);
 | 
						|
      element.tributeMenu = this.menu;
 | 
						|
      this.menuEvents.bind(this.menu);
 | 
						|
    }
 | 
						|
 | 
						|
    this.isActive = true;
 | 
						|
    this.menuSelected = 0;
 | 
						|
 | 
						|
    if (!this.current.mentionText) {
 | 
						|
      this.current.mentionText = "";
 | 
						|
    }
 | 
						|
 | 
						|
    const processValues = values => {
 | 
						|
      // Tribute may not be active any more by the time the value callback returns
 | 
						|
      if (!this.isActive) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      let items = this.search.filter(this.current.mentionText, values, {
 | 
						|
        pre: this.current.collection.searchOpts.pre || "<span>",
 | 
						|
        post: this.current.collection.searchOpts.post || "</span>",
 | 
						|
        skip: this.current.collection.searchOpts.skip,
 | 
						|
        extract: el => {
 | 
						|
          if (typeof this.current.collection.lookup === "string") {
 | 
						|
            return el[this.current.collection.lookup];
 | 
						|
          } else if (typeof this.current.collection.lookup === "function") {
 | 
						|
            return this.current.collection.lookup(el, this.current.mentionText);
 | 
						|
          } else {
 | 
						|
            throw new Error(
 | 
						|
              "Invalid lookup attribute, lookup must be string or function."
 | 
						|
            );
 | 
						|
          }
 | 
						|
        }
 | 
						|
      });
 | 
						|
 | 
						|
      if (this.current.collection.menuItemLimit) {
 | 
						|
        items = items.slice(0, this.current.collection.menuItemLimit);
 | 
						|
      }
 | 
						|
 | 
						|
      this.current.filteredItems = items;
 | 
						|
 | 
						|
      let ul = this.menu.querySelector("ul");
 | 
						|
 | 
						|
      if (!items.length) {
 | 
						|
        let noMatchEvent = new CustomEvent("tribute-no-match", {
 | 
						|
          detail: this.menu
 | 
						|
        });
 | 
						|
        this.current.element.dispatchEvent(noMatchEvent);
 | 
						|
        if (
 | 
						|
          (typeof this.current.collection.noMatchTemplate === "function" &&
 | 
						|
            !this.current.collection.noMatchTemplate()) ||
 | 
						|
          !this.current.collection.noMatchTemplate
 | 
						|
        ) {
 | 
						|
          this.hideMenu();
 | 
						|
        } else {
 | 
						|
          typeof this.current.collection.noMatchTemplate === "function"
 | 
						|
            ? (ul.innerHTML = this.current.collection.noMatchTemplate())
 | 
						|
            : (ul.innerHTML = this.current.collection.noMatchTemplate);
 | 
						|
            this.range.positionMenuAtCaret(scrollTo);
 | 
						|
        }
 | 
						|
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      ul.innerHTML = "";
 | 
						|
      let fragment = this.range.getDocument().createDocumentFragment();
 | 
						|
 | 
						|
      items.forEach((item, index) => {
 | 
						|
        let li = this.range.getDocument().createElement("li");
 | 
						|
        li.setAttribute("data-index", index);
 | 
						|
        li.className = this.current.collection.itemClass;
 | 
						|
        li.addEventListener("mousemove", e => {
 | 
						|
          let [li, index] = this._findLiTarget(e.target);
 | 
						|
          if (e.movementY !== 0) {
 | 
						|
            this.events.setActiveLi(index);
 | 
						|
          }
 | 
						|
        });
 | 
						|
        if (this.menuSelected === index) {
 | 
						|
          li.classList.add(this.current.collection.selectClass);
 | 
						|
        }
 | 
						|
        li.innerHTML = this.current.collection.menuItemTemplate(item);
 | 
						|
        fragment.appendChild(li);
 | 
						|
      });
 | 
						|
      ul.appendChild(fragment);
 | 
						|
 | 
						|
      this.range.positionMenuAtCaret(scrollTo);
 | 
						|
    };
 | 
						|
 | 
						|
    if (typeof this.current.collection.values === "function") {
 | 
						|
      if (this.current.collection.loadingItemTemplate) {
 | 
						|
        this.menu.querySelector("ul").innerHTML = this.current.collection.loadingItemTemplate;
 | 
						|
        this.range.positionMenuAtCaret(scrollTo);
 | 
						|
      }
 | 
						|
 | 
						|
      this.current.collection.values(this.current.mentionText, processValues);
 | 
						|
    } else {
 | 
						|
      processValues(this.current.collection.values);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  _findLiTarget(el) {
 | 
						|
    if (!el) return [];
 | 
						|
    const index = el.getAttribute("data-index");
 | 
						|
    return !index ? this._findLiTarget(el.parentNode) : [el, index];
 | 
						|
  }
 | 
						|
 | 
						|
  showMenuForCollection(element, collectionIndex) {
 | 
						|
    if (element !== document.activeElement) {
 | 
						|
      this.placeCaretAtEnd(element);
 | 
						|
    }
 | 
						|
 | 
						|
    this.current.collection = this.collection[collectionIndex || 0];
 | 
						|
    this.current.externalTrigger = true;
 | 
						|
    this.current.element = element;
 | 
						|
 | 
						|
    if (element.isContentEditable)
 | 
						|
      this.insertTextAtCursor(this.current.collection.trigger);
 | 
						|
    else this.insertAtCaret(element, this.current.collection.trigger);
 | 
						|
 | 
						|
    this.showMenuFor(element);
 | 
						|
  }
 | 
						|
 | 
						|
  // TODO: make sure this works for inputs/textareas
 | 
						|
  placeCaretAtEnd(el) {
 | 
						|
    el.focus();
 | 
						|
    if (
 | 
						|
      typeof window.getSelection != "undefined" &&
 | 
						|
      typeof document.createRange != "undefined"
 | 
						|
    ) {
 | 
						|
      var range = document.createRange();
 | 
						|
      range.selectNodeContents(el);
 | 
						|
      range.collapse(false);
 | 
						|
      var sel = window.getSelection();
 | 
						|
      sel.removeAllRanges();
 | 
						|
      sel.addRange(range);
 | 
						|
    } else if (typeof document.body.createTextRange != "undefined") {
 | 
						|
      var textRange = document.body.createTextRange();
 | 
						|
      textRange.moveToElementText(el);
 | 
						|
      textRange.collapse(false);
 | 
						|
      textRange.select();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // for contenteditable
 | 
						|
  insertTextAtCursor(text) {
 | 
						|
    var sel, range;
 | 
						|
    sel = window.getSelection();
 | 
						|
    range = sel.getRangeAt(0);
 | 
						|
    range.deleteContents();
 | 
						|
    var textNode = document.createTextNode(text);
 | 
						|
    range.insertNode(textNode);
 | 
						|
    range.selectNodeContents(textNode);
 | 
						|
    range.collapse(false);
 | 
						|
    sel.removeAllRanges();
 | 
						|
    sel.addRange(range);
 | 
						|
  }
 | 
						|
 | 
						|
  // for regular inputs
 | 
						|
  insertAtCaret(textarea, text) {
 | 
						|
    var scrollPos = textarea.scrollTop;
 | 
						|
    var caretPos = textarea.selectionStart;
 | 
						|
 | 
						|
    var front = textarea.value.substring(0, caretPos);
 | 
						|
    var back = textarea.value.substring(
 | 
						|
      textarea.selectionEnd,
 | 
						|
      textarea.value.length
 | 
						|
    );
 | 
						|
    textarea.value = front + text + back;
 | 
						|
    caretPos = caretPos + text.length;
 | 
						|
    textarea.selectionStart = caretPos;
 | 
						|
    textarea.selectionEnd = caretPos;
 | 
						|
    textarea.focus();
 | 
						|
    textarea.scrollTop = scrollPos;
 | 
						|
  }
 | 
						|
 | 
						|
  hideMenu() {
 | 
						|
    if (this.menu) {
 | 
						|
      this.menu.style.cssText = "display: none;";
 | 
						|
      this.isActive = false;
 | 
						|
      this.menuSelected = 0;
 | 
						|
      this.current = {};
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  selectItemAtIndex(index, originalEvent) {
 | 
						|
    index = parseInt(index);
 | 
						|
    if (typeof index !== "number" || isNaN(index)) return;
 | 
						|
    let item = this.current.filteredItems[index];
 | 
						|
    let content = this.current.collection.selectTemplate(item);
 | 
						|
    if (content !== null) this.replaceText(content, originalEvent, item);
 | 
						|
  }
 | 
						|
 | 
						|
  replaceText(content, originalEvent, item) {
 | 
						|
    this.range.replaceTriggerText(content, true, true, originalEvent, item);
 | 
						|
  }
 | 
						|
 | 
						|
  _append(collection, newValues, replace) {
 | 
						|
    if (typeof collection.values === "function") {
 | 
						|
      throw new Error("Unable to append to values, as it is a function.");
 | 
						|
    } else if (!replace) {
 | 
						|
      collection.values = collection.values.concat(newValues);
 | 
						|
    } else {
 | 
						|
      collection.values = newValues;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  append(collectionIndex, newValues, replace) {
 | 
						|
    let index = parseInt(collectionIndex);
 | 
						|
    if (typeof index !== "number")
 | 
						|
      throw new Error("please provide an index for the collection to update.");
 | 
						|
 | 
						|
    let collection = this.collection[index];
 | 
						|
 | 
						|
    this._append(collection, newValues, replace);
 | 
						|
  }
 | 
						|
 | 
						|
  appendCurrent(newValues, replace) {
 | 
						|
    if (this.isActive) {
 | 
						|
      this._append(this.current.collection, newValues, replace);
 | 
						|
    } else {
 | 
						|
      throw new Error(
 | 
						|
        "No active state. Please use append instead and pass an index."
 | 
						|
      );
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  detach(el) {
 | 
						|
    if (!el) {
 | 
						|
      throw new Error("[Tribute] Must pass in a DOM node or NodeList.");
 | 
						|
    }
 | 
						|
 | 
						|
    // Check if it is a jQuery collection
 | 
						|
    if (typeof jQuery !== "undefined" && el instanceof jQuery) {
 | 
						|
      el = el.get();
 | 
						|
    }
 | 
						|
 | 
						|
    // Is el an Array/Array-like object?
 | 
						|
    if (
 | 
						|
      el.constructor === NodeList ||
 | 
						|
      el.constructor === HTMLCollection ||
 | 
						|
      el.constructor === Array
 | 
						|
    ) {
 | 
						|
      let length = el.length;
 | 
						|
      for (var i = 0; i < length; ++i) {
 | 
						|
        this._detach(el[i]);
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      this._detach(el);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  _detach(el) {
 | 
						|
    this.events.unbind(el);
 | 
						|
    if (el.tributeMenu) {
 | 
						|
      this.menuEvents.unbind(el.tributeMenu);
 | 
						|
    }
 | 
						|
 | 
						|
    setTimeout(() => {
 | 
						|
      el.removeAttribute("data-tribute");
 | 
						|
      this.isActive = false;
 | 
						|
      if (el.tributeMenu) {
 | 
						|
        el.tributeMenu.remove();
 | 
						|
      }
 | 
						|
    });
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Tribute.js
 | 
						|
 * Native ES6 JavaScript @mention Plugin
 | 
						|
 **/
 | 
						|
 | 
						|
export default Tribute;
 |