diff --git a/phpBB/assets/javascript/editor.js b/phpBB/assets/javascript/editor.js
index 03f42493a2..ea06496ed2 100644
--- a/phpBB/assets/javascript/editor.js
+++ b/phpBB/assets/javascript/editor.js
@@ -383,6 +383,7 @@ function getCaretPosition(txtarea) {
return caretPos;
}
+/* import Tribute from './jquery.tribute'; */
(function($) {
'use strict';
@@ -411,6 +412,7 @@ function getCaretPosition(txtarea) {
let cachedNames = [];
let cachedAll = [];
let cachedSearchKey = 'name';
+ let tribute = null;
/**
* Get default avatar
@@ -418,7 +420,11 @@ function getCaretPosition(txtarea) {
* @returns {string} Default avatar svg code
*/
function defaultAvatar(type) {
- return (type === 'group') ? '' : '';
+ if (type === 'group') {
+ return '';
+ } else {
+ return '';
+ }
}
/**
@@ -452,15 +458,19 @@ function getCaretPosition(txtarea) {
*/
function getMatchedNames(query, items, searchKey) {
let i;
- let len;
- let _results = [];
- for (i = 0, len = items.length; i < len; i++) {
+ let itemsLength;
+ let matchedNames = [];
+ for (i = 0, itemsLength = items.length; i < itemsLength; i++) {
let item = items[i];
- if (String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase()) === 0) {
- _results.push(item);
+ if (isItemMatched(query, item, searchKey)) {
+ matchedNames.push(item);
}
}
- return _results;
+ return matchedNames;
+ }
+
+ function isItemMatched(query, item, searchKey) {
+ return String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase()) === 0;
}
/**
@@ -481,7 +491,7 @@ function getCaretPosition(txtarea) {
}
let cachedKeyword = getCachedKeyword(query),
- cachedNamesForQuery = (cachedKeyword != null) ? cachedNames[cachedKeyword] : null;
+ cachedNamesForQuery = (cachedKeyword !== null) ? cachedNames[cachedKeyword] : null;
/*
* Use cached values when we can:
@@ -514,6 +524,45 @@ function getCaretPosition(txtarea) {
};
this.handle = function(textarea) {
+ tribute = new Tribute({
+ trigger: '@',
+ allowSpaces: true,
+ containerClass: 'mention-container',
+ selectClass: 'cur',
+ itemClass: 'mention-item',
+ menuItemTemplate: function (data) {
+ const itemData = data.original;
+ let avatar = (itemData.avatar.img) ? "" + itemData.avatar.img + "" : defaultAvatar(itemData.avatar.type),
+ rank = (itemData.rank) ? "" + itemData.rank + "" : '';
+ return "" + avatar + "" + itemData.name + rank + "";
+ },
+ selectTemplate: function (item) {
+ return '[mention=' + item.original.type + ':' + item.original.id + ']' + item.original.name + '[/mention]';
+ },
+ menuItemLimit: mentionNamesLimit,
+ values: function (text, cb) {
+ remoteFilter(text, users => cb(users));
+ },
+ noMatchTemplate: function (t) {
+ console.log('No match:');
+ console.log(t);
+ },
+ lookup: function (element, mentionText) {
+ return element.hasOwnProperty('name') ? element.name : '';
+ }
+ });
+
+ tribute.attach($(textarea));
+
+ /*
+ var tribute = new Tribute({
+ values: [
+ { key: "Phil Heartman", value: "pheartman" },
+ { key: "Gordon Ramsey", value: "gramsey" }
+ ]
+ });
+ */
+/*
$(textarea).atwho({
at: "@",
acceptSpaceBar: true,
@@ -600,6 +649,7 @@ function getCaretPosition(txtarea) {
}
}
});
+ */
};
}
phpbb.mentions = new Mentions();
@@ -642,4 +692,3 @@ function getCaretPosition(txtarea) {
});
});
})(jQuery);
-
diff --git a/phpBB/assets/javascript/jquery.tribute.js b/phpBB/assets/javascript/jquery.tribute.js
new file mode 100644
index 0000000000..9c46e63577
--- /dev/null
+++ b/phpBB/assets/javascript/jquery.tribute.js
@@ -0,0 +1,1898 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global = global || self, global.Tribute = factory());
+}(this, (function () { 'use strict';
+
+ function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+ }
+
+ function _defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+ }
+
+ function _createClass(Constructor, protoProps, staticProps) {
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) _defineProperties(Constructor, staticProps);
+ return Constructor;
+ }
+
+ function _slicedToArray(arr, i) {
+ return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
+ }
+
+ function _arrayWithHoles(arr) {
+ if (Array.isArray(arr)) return arr;
+ }
+
+ function _iterableToArrayLimit(arr, i) {
+ if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
+ var _arr = [];
+ var _n = true;
+ var _d = false;
+ var _e = undefined;
+
+ try {
+ for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
+ _arr.push(_s.value);
+
+ if (i && _arr.length === i) break;
+ }
+ } catch (err) {
+ _d = true;
+ _e = err;
+ } finally {
+ try {
+ if (!_n && _i["return"] != null) _i["return"]();
+ } finally {
+ if (_d) throw _e;
+ }
+ }
+
+ return _arr;
+ }
+
+ function _unsupportedIterableToArray(o, minLen) {
+ if (!o) return;
+ if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+ var n = Object.prototype.toString.call(o).slice(8, -1);
+ if (n === "Object" && o.constructor) n = o.constructor.name;
+ if (n === "Map" || n === "Set") return Array.from(n);
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
+ }
+
+ function _arrayLikeToArray(arr, len) {
+ if (len == null || len > arr.length) len = arr.length;
+
+ for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
+
+ return arr2;
+ }
+
+ function _nonIterableRest() {
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
+ }
+
+ 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") {
+ var CustomEvent$1 = function CustomEvent(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;
+ }
+
+ var TributeEvents = /*#__PURE__*/function () {
+ function TributeEvents(tribute) {
+ _classCallCheck(this, TributeEvents);
+
+ this.tribute = tribute;
+ this.tribute.events = this;
+ }
+
+ _createClass(TributeEvents, [{
+ key: "bind",
+ value: function 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);
+ }
+ }, {
+ key: "unbind",
+ value: function 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;
+ }
+ }, {
+ key: "keydown",
+ value: function keydown(instance, event) {
+ if (instance.shouldDeactivate(event)) {
+ instance.tribute.isActive = false;
+ instance.tribute.hideMenu();
+ }
+
+ var element = this;
+ instance.commandEvent = false;
+ TributeEvents.keys().forEach(function (o) {
+ if (o.key === event.keyCode) {
+ instance.commandEvent = true;
+ instance.callbacks()[o.value.toLowerCase()](event, element);
+ }
+ });
+ }
+ }, {
+ key: "input",
+ value: function input(instance, event) {
+ instance.inputEvent = true;
+ instance.keyup.call(this, instance, event);
+ }
+ }, {
+ key: "click",
+ value: function click(instance, event) {
+ var tribute = instance.tribute;
+
+ if (tribute.menu && tribute.menu.contains(event.target)) {
+ var 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
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(function () {
+ return tribute.hideMenu();
+ });
+ }
+ }
+ }, {
+ key: "keyup",
+ value: function 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 {
+ var keyCode = instance.getKeyCode(instance, this, event);
+ if (isNaN(keyCode) || !keyCode) return;
+ var trigger = instance.tribute.triggers().find(function (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);
+ }
+ }
+ }, {
+ key: "shouldDeactivate",
+ value: function shouldDeactivate(event) {
+ if (!this.tribute.isActive) return false;
+
+ if (this.tribute.current.mentionText.length === 0) {
+ var eventKeyPressed = false;
+ TributeEvents.keys().forEach(function (o) {
+ if (event.keyCode === o.key) eventKeyPressed = true;
+ });
+ return !eventKeyPressed;
+ }
+
+ return false;
+ }
+ }, {
+ key: "getKeyCode",
+ value: function getKeyCode(instance, el, event) {
+
+ var tribute = instance.tribute;
+ var info = tribute.range.getTriggerInfo(false, tribute.hasTrailingSpace, true, tribute.allowSpaces, tribute.autocompleteMode);
+
+ if (info) {
+ return info.mentionTriggerChar.charCodeAt(0);
+ } else {
+ return false;
+ }
+ }
+ }, {
+ key: "updateSelection",
+ value: function updateSelection(el) {
+ this.tribute.current.element = el;
+ var 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;
+ }
+ }
+ }, {
+ key: "callbacks",
+ value: function callbacks() {
+ var _this = this;
+
+ return {
+ triggerChar: function triggerChar(e, el, trigger) {
+ var tribute = _this.tribute;
+ tribute.current.trigger = trigger;
+ var collectionItem = tribute.collection.find(function (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: function enter(e, el) {
+ // choose selection
+ if (_this.tribute.isActive && _this.tribute.current.filteredItems) {
+ e.preventDefault();
+ e.stopPropagation();
+ setTimeout(function () {
+ _this.tribute.selectItemAtIndex(_this.tribute.menuSelected, e);
+
+ _this.tribute.hideMenu();
+ }, 0);
+ }
+ },
+ escape: function escape(e, el) {
+ if (_this.tribute.isActive) {
+ e.preventDefault();
+ e.stopPropagation();
+ _this.tribute.isActive = false;
+
+ _this.tribute.hideMenu();
+ }
+ },
+ tab: function tab(e, el) {
+ // choose first match
+ _this.callbacks().enter(e, el);
+ },
+ space: function space(e, el) {
+ if (_this.tribute.isActive) {
+ if (_this.tribute.spaceSelectsMatch) {
+ _this.callbacks().enter(e, el);
+ } else if (!_this.tribute.allowSpaces) {
+ e.stopPropagation();
+ setTimeout(function () {
+ _this.tribute.hideMenu();
+
+ _this.tribute.isActive = false;
+ }, 0);
+ }
+ }
+ },
+ up: function up(e, el) {
+ // navigate up ul
+ if (_this.tribute.isActive && _this.tribute.current.filteredItems) {
+ e.preventDefault();
+ e.stopPropagation();
+ var 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: function down(e, el) {
+ // navigate down ul
+ if (_this.tribute.isActive && _this.tribute.current.filteredItems) {
+ e.preventDefault();
+ e.stopPropagation();
+ var 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": function _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);
+ }
+ }
+ };
+ }
+ }, {
+ key: "setActiveLi",
+ value: function setActiveLi(index) {
+ var lis = this.tribute.menu.querySelectorAll("li"),
+ length = lis.length >>> 0;
+ if (index) this.tribute.menuSelected = parseInt(index);
+
+ for (var i = 0; i < length; i++) {
+ var li = lis[i];
+
+ if (i === this.tribute.menuSelected) {
+ li.classList.add(this.tribute.current.collection.selectClass);
+ var liClientRect = li.getBoundingClientRect();
+ var menuClientRect = this.tribute.menu.getBoundingClientRect();
+
+ if (liClientRect.bottom > menuClientRect.bottom) {
+ var scrollDistance = liClientRect.bottom - menuClientRect.bottom;
+ this.tribute.menu.scrollTop += scrollDistance;
+ } else if (liClientRect.top < menuClientRect.top) {
+ var _scrollDistance = menuClientRect.top - liClientRect.top;
+
+ this.tribute.menu.scrollTop -= _scrollDistance;
+ }
+ } else {
+ li.classList.remove(this.tribute.current.collection.selectClass);
+ }
+ }
+ }
+ }, {
+ key: "getFullHeight",
+ value: function getFullHeight(elem, includeMargin) {
+ var height = elem.getBoundingClientRect().height;
+
+ if (includeMargin) {
+ var style = elem.currentStyle || window.getComputedStyle(elem);
+ return height + parseFloat(style.marginTop) + parseFloat(style.marginBottom);
+ }
+
+ return height;
+ }
+ }], [{
+ key: "keys",
+ value: function 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"
+ }];
+ }
+ }]);
+
+ return TributeEvents;
+ }();
+
+ var TributeMenuEvents = /*#__PURE__*/function () {
+ function TributeMenuEvents(tribute) {
+ _classCallCheck(this, TributeMenuEvents);
+
+ this.tribute = tribute;
+ this.tribute.menuEvents = this;
+ this.menu = this.tribute.menu;
+ }
+
+ _createClass(TributeMenuEvents, [{
+ key: "bind",
+ value: function bind(menu) {
+ var _this = this;
+
+ this.menuClickEvent = this.tribute.events.click.bind(null, this);
+ this.menuContainerScrollEvent = this.debounce(function () {
+ if (_this.tribute.isActive) {
+ _this.tribute.showMenuFor(_this.tribute.current.element, false);
+ }
+ }, 300, false);
+ this.windowResizeEvent = this.debounce(function () {
+ if (_this.tribute.isActive) {
+ _this.tribute.range.positionMenuAtCaret(true);
+ }
+ }, 300, 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);
+ }
+ }
+ }, {
+ key: "unbind",
+ value: function 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);
+ }
+ }
+ }, {
+ key: "debounce",
+ value: function debounce(func, wait, immediate) {
+ var _arguments = arguments,
+ _this2 = this;
+
+ var timeout;
+ return function () {
+ var context = _this2,
+ args = _arguments;
+
+ var later = function 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);
+ };
+ }
+ }]);
+
+ return TributeMenuEvents;
+ }();
+
+ var TributeRange = /*#__PURE__*/function () {
+ function TributeRange(tribute) {
+ _classCallCheck(this, TributeRange);
+
+ this.tribute = tribute;
+ this.tribute.range = this;
+ }
+
+ _createClass(TributeRange, [{
+ key: "getDocument",
+ value: function getDocument() {
+ var iframe;
+
+ if (this.tribute.current.collection) {
+ iframe = this.tribute.current.collection.iframe;
+ }
+
+ if (!iframe) {
+ return document;
+ }
+
+ return iframe.contentWindow.document;
+ }
+ }, {
+ key: "positionMenuAtCaret",
+ value: function positionMenuAtCaret(scrollTo) {
+ var _this = this;
+
+ var context = this.tribute.current,
+ coordinates;
+ var 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: ".concat(coordinates.top, "px;\n left: ").concat(coordinates.left, "px;\n right: ").concat(coordinates.right, "px;\n bottom: ").concat(coordinates.bottom, "px;\n position: absolute;\n 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();
+ window.setTimeout(function () {
+ var menuDimensions = {
+ width: _this.tribute.menu.offsetWidth,
+ height: _this.tribute.menu.offsetHeight
+ };
+
+ var menuIsOffScreen = _this.isMenuOffScreen(coordinates, menuDimensions);
+
+ var menuIsOffScreenHorizontally = window.innerWidth > menuDimensions.width && (menuIsOffScreen.left || menuIsOffScreen.right);
+ var menuIsOffScreenVertically = window.innerHeight > menuDimensions.height && (menuIsOffScreen.top || menuIsOffScreen.bottom);
+
+ if (menuIsOffScreenHorizontally || menuIsOffScreenVertically) {
+ _this.tribute.menu.style.cssText = 'display: none';
+
+ _this.positionMenuAtCaret(scrollTo);
+ }
+ }, 0);
+ } else {
+ this.tribute.menu.style.cssText = 'display: none';
+ }
+ }
+ }, {
+ key: "selectElement",
+ value: function selectElement(targetElement, path, offset) {
+ var range;
+ var 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;
+ }
+ }
+ }
+
+ var 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();
+ }
+ }, {
+ key: "replaceTriggerText",
+ value: function replaceTriggerText(text, requireLeadingSpace, hasTrailingSpace, originalEvent, item) {
+ var info = this.getTriggerInfo(true, hasTrailingSpace, requireLeadingSpace, this.tribute.allowSpaces, this.tribute.autocompleteMode);
+
+ if (info !== undefined) {
+ var context = this.tribute.current;
+ var replaceEvent = new CustomEvent('tribute-replaced', {
+ detail: {
+ item: item,
+ instance: context,
+ context: info,
+ event: originalEvent
+ }
+ });
+
+ if (!this.isContentEditable(context.element)) {
+ var myField = this.tribute.current.element;
+ var textSuffix = typeof this.tribute.replaceTextSuffix == 'string' ? this.tribute.replaceTextSuffix : ' ';
+ text += textSuffix;
+ var startPos = info.mentionPosition;
+ var 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
+ var _textSuffix = typeof this.tribute.replaceTextSuffix == 'string' ? this.tribute.replaceTextSuffix : '\xA0';
+
+ text += _textSuffix;
+
+ var _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);
+ }
+ }
+ }, {
+ key: "pasteHtml",
+ value: function pasteHtml(html, startPos, endPos) {
+ var range, sel;
+ sel = this.getWindowSelection();
+ range = this.getDocument().createRange();
+ range.setStart(sel.anchorNode, startPos);
+ range.setEnd(sel.anchorNode, endPos);
+ range.deleteContents();
+ var el = this.getDocument().createElement('div');
+ el.innerHTML = html;
+ var 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);
+ }
+ }
+ }, {
+ key: "getWindowSelection",
+ value: function getWindowSelection() {
+ if (this.tribute.collection.iframe) {
+ return this.tribute.collection.iframe.contentWindow.getSelection();
+ }
+
+ return window.getSelection();
+ }
+ }, {
+ key: "getNodePositionInParent",
+ value: function getNodePositionInParent(element) {
+ if (element.parentNode === null) {
+ return 0;
+ }
+
+ for (var i = 0; i < element.parentNode.childNodes.length; i++) {
+ var node = element.parentNode.childNodes[i];
+
+ if (node === element) {
+ return i;
+ }
+ }
+ }
+ }, {
+ key: "getContentEditableSelectedPath",
+ value: function getContentEditableSelectedPath(ctx) {
+ var sel = this.getWindowSelection();
+ var selected = sel.anchorNode;
+ var path = [];
+ var offset;
+
+ if (selected != null) {
+ var i;
+ var 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
+ };
+ }
+ }
+ }, {
+ key: "getTextPrecedingCurrentSelection",
+ value: function getTextPrecedingCurrentSelection() {
+ var context = this.tribute.current,
+ text = '';
+
+ if (!this.isContentEditable(context.element)) {
+ var textComponent = this.tribute.current.element;
+
+ if (textComponent) {
+ var startPos = textComponent.selectionStart;
+
+ if (textComponent.value && startPos >= 0) {
+ text = textComponent.value.substring(0, startPos);
+ }
+ }
+ } else {
+ var selectedElem = this.getWindowSelection().anchorNode;
+
+ if (selectedElem != null) {
+ var workingNodeContent = selectedElem.textContent;
+ var selectStartOffset = this.getWindowSelection().getRangeAt(0).startOffset;
+
+ if (workingNodeContent && selectStartOffset >= 0) {
+ text = workingNodeContent.substring(0, selectStartOffset);
+ }
+ }
+ }
+
+ return text;
+ }
+ }, {
+ key: "getLastWordInText",
+ value: function 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();
+ }
+ }, {
+ key: "getTriggerInfo",
+ value: function getTriggerInfo(menuAlreadyActive, hasTrailingSpace, requireLeadingSpace, allowSpaces, isAutocomplete) {
+ var _this2 = this;
+
+ var ctx = this.tribute.current;
+ var selected, path, offset;
+
+ if (!this.isContentEditable(ctx.element)) {
+ selected = this.tribute.current.element;
+ } else {
+ var selectionInfo = this.getContentEditableSelectedPath(ctx);
+
+ if (selectionInfo) {
+ selected = selectionInfo.selected;
+ path = selectionInfo.path;
+ offset = selectionInfo.offset;
+ }
+ }
+
+ var effectiveRange = this.getTextPrecedingCurrentSelection();
+ var 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) {
+ var mostRecentTriggerCharPos = -1;
+ var triggerChar;
+ this.tribute.collection.forEach(function (config) {
+ var c = config.trigger;
+ var idx = config.requireLeadingSpace ? _this2.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)))) {
+ var currentTriggerSnippet = effectiveRange.substring(mostRecentTriggerCharPos + triggerChar.length, effectiveRange.length);
+ triggerChar = effectiveRange.substring(mostRecentTriggerCharPos, mostRecentTriggerCharPos + triggerChar.length);
+ var firstSnippetChar = currentTriggerSnippet.substring(0, 1);
+ var leadingSpace = currentTriggerSnippet.length > 0 && (firstSnippetChar === ' ' || firstSnippetChar === '\xA0');
+
+ if (hasTrailingSpace) {
+ currentTriggerSnippet = currentTriggerSnippet.trim();
+ }
+
+ var 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
+ };
+ }
+ }
+ }
+ }
+ }, {
+ key: "lastIndexWithLeadingSpace",
+ value: function lastIndexWithLeadingSpace(str, trigger) {
+ var reversedStr = str.split('').reverse().join('');
+ var index = -1;
+
+ for (var cidx = 0, len = str.length; cidx < len; cidx++) {
+ var firstChar = cidx === str.length - 1;
+ var leadingSpace = /\s/.test(reversedStr[cidx + 1]);
+ var match = true;
+
+ for (var 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;
+ }
+ }, {
+ key: "isContentEditable",
+ value: function isContentEditable(element) {
+ return element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA';
+ }
+ }, {
+ key: "isMenuOffScreen",
+ value: function isMenuOffScreen(coordinates, menuDimensions) {
+ var windowWidth = window.innerWidth;
+ var windowHeight = window.innerHeight;
+ var doc = document.documentElement;
+ var windowLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
+ var windowTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
+ var menuTop = typeof coordinates.top === 'number' ? coordinates.top : windowTop + windowHeight - coordinates.bottom - menuDimensions.height;
+ var menuRight = typeof coordinates.right === 'number' ? coordinates.right : coordinates.left + menuDimensions.width;
+ var menuBottom = typeof coordinates.bottom === 'number' ? coordinates.bottom : coordinates.top + menuDimensions.height;
+ var 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)
+ };
+ }
+ }, {
+ key: "getMenuDimensions",
+ value: function 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
+ var dimensions = {
+ width: null,
+ height: null
+ };
+ this.tribute.menu.style.cssText = "top: 0px;\n left: 0px;\n position: fixed;\n display: block;\n visibility; hidden;";
+ dimensions.width = this.tribute.menu.offsetWidth;
+ dimensions.height = this.tribute.menu.offsetHeight;
+ this.tribute.menu.style.cssText = "display: none;";
+ return dimensions;
+ }
+ }, {
+ key: "getTextAreaOrInputUnderlinePosition",
+ value: function getTextAreaOrInputUnderlinePosition(element, position, flipped) {
+ var properties = ['direction', 'boxSizing', 'width', 'height', 'overflowX', 'overflowY', 'borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', 'fontStyle', 'fontVariant', 'fontWeight', 'fontStretch', 'fontSize', 'fontSizeAdjust', 'lineHeight', 'fontFamily', 'textAlign', 'textTransform', 'textIndent', 'textDecoration', 'letterSpacing', 'wordSpacing'];
+ var isFirefox = window.mozInnerScreenX !== null;
+ var div = this.getDocument().createElement('div');
+ div.id = 'input-textarea-caret-position-mirror-div';
+ this.getDocument().body.appendChild(div);
+ var style = div.style;
+ var computed = window.getComputedStyle ? getComputedStyle(element) : element.currentStyle;
+ style.whiteSpace = 'pre-wrap';
+
+ if (element.nodeName !== 'INPUT') {
+ style.wordWrap = 'break-word';
+ } // position off-screen
+
+
+ style.position = 'absolute';
+ style.visibility = 'hidden'; // transfer the element's properties to the div
+
+ properties.forEach(function (prop) {
+ style[prop] = computed[prop];
+ });
+
+ if (isFirefox) {
+ style.width = "".concat(parseInt(computed.width) - 2, "px");
+ if (element.scrollHeight > parseInt(computed.height)) style.overflowY = 'scroll';
+ } else {
+ style.overflow = 'hidden';
+ }
+
+ div.textContent = element.value.substring(0, position);
+
+ if (element.nodeName === 'INPUT') {
+ div.textContent = div.textContent.replace(/\s/g, ' ');
+ }
+
+ var span = this.getDocument().createElement('span');
+ span.textContent = element.value.substring(position) || '.';
+ div.appendChild(span);
+ var rect = element.getBoundingClientRect();
+ var doc = document.documentElement;
+ var windowLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
+ var windowTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
+ var top = 0;
+ var left = 0;
+
+ if (this.menuContainerIsBody) {
+ top = rect.top;
+ left = rect.left;
+ }
+
+ var coordinates = {
+ top: top + windowTop + span.offsetTop + parseInt(computed.borderTopWidth) + parseInt(computed.fontSize) - element.scrollTop,
+ left: left + windowLeft + span.offsetLeft + parseInt(computed.borderLeftWidth)
+ };
+ var windowWidth = window.innerWidth;
+ var windowHeight = window.innerHeight;
+ var menuDimensions = this.getMenuDimensions();
+ var menuIsOffScreen = this.isMenuOffScreen(coordinates, menuDimensions);
+
+ if (menuIsOffScreen.right) {
+ coordinates.right = windowWidth - coordinates.left;
+ coordinates.left = 'auto';
+ }
+
+ var parentHeight = this.tribute.menuContainer ? this.tribute.menuContainer.offsetHeight : this.getDocument().body.offsetHeight;
+
+ if (menuIsOffScreen.bottom) {
+ var parentRect = this.tribute.menuContainer ? this.tribute.menuContainer.getBoundingClientRect() : this.getDocument().body.getBoundingClientRect();
+ var scrollStillAvailable = parentHeight - (windowHeight - parentRect.top);
+ coordinates.bottom = scrollStillAvailable + (windowHeight - rect.top - span.offsetTop);
+ coordinates.top = 'auto';
+ }
+
+ menuIsOffScreen = this.isMenuOffScreen(coordinates, menuDimensions);
+
+ if (menuIsOffScreen.left) {
+ coordinates.left = windowWidth > menuDimensions.width ? windowLeft + windowWidth - menuDimensions.width : windowLeft;
+ delete coordinates.right;
+ }
+
+ if (menuIsOffScreen.top) {
+ coordinates.top = windowHeight > menuDimensions.height ? windowTop + windowHeight - menuDimensions.height : windowTop;
+ delete coordinates.bottom;
+ }
+
+ this.getDocument().body.removeChild(div);
+ return coordinates;
+ }
+ }, {
+ key: "getContentEditableCaretPosition",
+ value: function getContentEditableCaretPosition(selectedNodePosition) {
+ var range;
+ var sel = this.getWindowSelection();
+ range = this.getDocument().createRange();
+ range.setStart(sel.anchorNode, selectedNodePosition);
+ range.setEnd(sel.anchorNode, selectedNodePosition);
+ range.collapse(false);
+ var rect = range.getBoundingClientRect();
+ var doc = document.documentElement;
+ var windowLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
+ var windowTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
+ var left = rect.left;
+ var top = rect.top;
+ var coordinates = {
+ left: left + windowLeft,
+ top: top + rect.height + windowTop
+ };
+ var windowWidth = window.innerWidth;
+ var windowHeight = window.innerHeight;
+ var menuDimensions = this.getMenuDimensions();
+ var menuIsOffScreen = this.isMenuOffScreen(coordinates, menuDimensions);
+
+ if (menuIsOffScreen.right) {
+ coordinates.left = 'auto';
+ coordinates.right = windowWidth - rect.left - windowLeft;
+ }
+
+ var parentHeight = this.tribute.menuContainer ? this.tribute.menuContainer.offsetHeight : this.getDocument().body.offsetHeight;
+
+ if (menuIsOffScreen.bottom) {
+ var parentRect = this.tribute.menuContainer ? this.tribute.menuContainer.getBoundingClientRect() : this.getDocument().body.getBoundingClientRect();
+ var scrollStillAvailable = parentHeight - (windowHeight - parentRect.top);
+ coordinates.top = 'auto';
+ coordinates.bottom = scrollStillAvailable + (windowHeight - rect.top);
+ }
+
+ menuIsOffScreen = this.isMenuOffScreen(coordinates, menuDimensions);
+
+ if (menuIsOffScreen.left) {
+ coordinates.left = windowWidth > menuDimensions.width ? windowLeft + windowWidth - menuDimensions.width : windowLeft;
+ delete coordinates.right;
+ }
+
+ if (menuIsOffScreen.top) {
+ coordinates.top = windowHeight > menuDimensions.height ? windowTop + windowHeight - menuDimensions.height : windowTop;
+ delete coordinates.bottom;
+ }
+
+ if (!this.menuContainerIsBody) {
+ coordinates.left = coordinates.left ? coordinates.left - this.tribute.menuContainer.offsetLeft : coordinates.left;
+ coordinates.top = coordinates.top ? coordinates.top - this.tribute.menuContainer.offsetTop : coordinates.top;
+ }
+
+ return coordinates;
+ }
+ }, {
+ key: "scrollIntoView",
+ value: function scrollIntoView(elem) {
+ var reasonableBuffer = 20,
+ clientRect;
+ var maxScrollDisplacement = 100;
+ var 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;
+ }
+ }
+ }
+
+ var elemTop = clientRect.top;
+ var elemBottom = elemTop + clientRect.height;
+
+ if (elemTop < 0) {
+ window.scrollTo(0, window.pageYOffset + clientRect.top - reasonableBuffer);
+ } else if (elemBottom > window.innerHeight) {
+ var maxY = window.pageYOffset + clientRect.top - reasonableBuffer;
+
+ if (maxY - window.pageYOffset > maxScrollDisplacement) {
+ maxY = window.pageYOffset + maxScrollDisplacement;
+ }
+
+ var targetY = window.pageYOffset - (window.innerHeight - elemBottom);
+
+ if (targetY > maxY) {
+ targetY = maxY;
+ }
+
+ window.scrollTo(0, targetY);
+ }
+ }
+ }, {
+ key: "menuContainerIsBody",
+ get: function get() {
+ return this.tribute.menuContainer === document.body || !this.tribute.menuContainer;
+ }
+ }]);
+
+ return TributeRange;
+ }();
+
+ // Thanks to https://github.com/mattyork/fuzzy
+ var TributeSearch = /*#__PURE__*/function () {
+ function TributeSearch(tribute) {
+ _classCallCheck(this, TributeSearch);
+
+ this.tribute = tribute;
+ this.tribute.search = this;
+ }
+
+ _createClass(TributeSearch, [{
+ key: "simpleFilter",
+ value: function simpleFilter(pattern, array) {
+ var _this = this;
+
+ return array.filter(function (string) {
+ return _this.test(pattern, string);
+ });
+ }
+ }, {
+ key: "test",
+ value: function test(pattern, string) {
+ return this.match(pattern, string) !== null;
+ }
+ }, {
+ key: "match",
+ value: function match(pattern, string, opts) {
+ opts = opts || {};
+ var 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();
+ var patternCache = this.traverse(compareString, pattern, 0, 0, []);
+
+ if (!patternCache) {
+ return null;
+ }
+
+ return {
+ rendered: this.render(string, patternCache.cache, pre, post),
+ score: patternCache.score
+ };
+ }
+ }, {
+ key: "traverse",
+ value: function 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;
+ }
+
+ var c = pattern[patternIndex];
+ var index = string.indexOf(c, stringIndex);
+ var 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;
+ }
+ }, {
+ key: "calculateScore",
+ value: function calculateScore(patternCache) {
+ var score = 0;
+ var temp = 1;
+ patternCache.forEach(function (index, i) {
+ if (i > 0) {
+ if (patternCache[i - 1] + 1 === index) {
+ temp += temp + 1;
+ } else {
+ temp = 1;
+ }
+ }
+
+ score += temp;
+ });
+ return score;
+ }
+ }, {
+ key: "render",
+ value: function render(string, indices, pre, post) {
+ var rendered = string.substring(0, indices[0]);
+ indices.forEach(function (index, i) {
+ rendered += pre + string[index] + post + string.substring(index + 1, indices[i + 1] ? indices[i + 1] : string.length);
+ });
+ return rendered;
+ }
+ }, {
+ key: "filter",
+ value: function filter(pattern, arr, opts) {
+ var _this2 = this;
+
+ opts = opts || {};
+ return arr.reduce(function (prev, element, idx, arr) {
+ var str = element;
+
+ if (opts.extract) {
+ str = opts.extract(element);
+
+ if (!str) {
+ // take care of undefineds / nulls / etc.
+ str = '';
+ }
+ }
+
+ var rendered = _this2.match(pattern, str, opts);
+
+ if (rendered != null) {
+ prev[prev.length] = {
+ string: rendered.rendered,
+ score: rendered.score,
+ index: idx,
+ original: element
+ };
+ }
+
+ return prev;
+ }, []).sort(function (a, b) {
+ var compare = b.score - a.score;
+ if (compare) return compare;
+ return a.index - b.index;
+ });
+ }
+ }]);
+
+ return TributeSearch;
+ }();
+
+ var Tribute = /*#__PURE__*/function () {
+ function Tribute(_ref) {
+ var _this = this;
+
+ var _ref$values = _ref.values,
+ values = _ref$values === void 0 ? null : _ref$values,
+ _ref$loadingItemTempl = _ref.loadingItemTemplate,
+ loadingItemTemplate = _ref$loadingItemTempl === void 0 ? null : _ref$loadingItemTempl,
+ _ref$iframe = _ref.iframe,
+ iframe = _ref$iframe === void 0 ? null : _ref$iframe,
+ _ref$selectClass = _ref.selectClass,
+ selectClass = _ref$selectClass === void 0 ? "highlight" : _ref$selectClass,
+ _ref$containerClass = _ref.containerClass,
+ containerClass = _ref$containerClass === void 0 ? "tribute-container" : _ref$containerClass,
+ _ref$itemClass = _ref.itemClass,
+ itemClass = _ref$itemClass === void 0 ? "" : _ref$itemClass,
+ _ref$trigger = _ref.trigger,
+ trigger = _ref$trigger === void 0 ? "@" : _ref$trigger,
+ _ref$autocompleteMode = _ref.autocompleteMode,
+ autocompleteMode = _ref$autocompleteMode === void 0 ? false : _ref$autocompleteMode,
+ _ref$autocompleteSepa = _ref.autocompleteSeparator,
+ autocompleteSeparator = _ref$autocompleteSepa === void 0 ? null : _ref$autocompleteSepa,
+ _ref$selectTemplate = _ref.selectTemplate,
+ selectTemplate = _ref$selectTemplate === void 0 ? null : _ref$selectTemplate,
+ _ref$menuItemTemplate = _ref.menuItemTemplate,
+ menuItemTemplate = _ref$menuItemTemplate === void 0 ? null : _ref$menuItemTemplate,
+ _ref$lookup = _ref.lookup,
+ lookup = _ref$lookup === void 0 ? "key" : _ref$lookup,
+ _ref$fillAttr = _ref.fillAttr,
+ fillAttr = _ref$fillAttr === void 0 ? "value" : _ref$fillAttr,
+ _ref$collection = _ref.collection,
+ collection = _ref$collection === void 0 ? null : _ref$collection,
+ _ref$menuContainer = _ref.menuContainer,
+ menuContainer = _ref$menuContainer === void 0 ? null : _ref$menuContainer,
+ _ref$noMatchTemplate = _ref.noMatchTemplate,
+ noMatchTemplate = _ref$noMatchTemplate === void 0 ? null : _ref$noMatchTemplate,
+ _ref$requireLeadingSp = _ref.requireLeadingSpace,
+ requireLeadingSpace = _ref$requireLeadingSp === void 0 ? true : _ref$requireLeadingSp,
+ _ref$allowSpaces = _ref.allowSpaces,
+ allowSpaces = _ref$allowSpaces === void 0 ? false : _ref$allowSpaces,
+ _ref$replaceTextSuffi = _ref.replaceTextSuffix,
+ replaceTextSuffix = _ref$replaceTextSuffi === void 0 ? null : _ref$replaceTextSuffi,
+ _ref$positionMenu = _ref.positionMenu,
+ positionMenu = _ref$positionMenu === void 0 ? true : _ref$positionMenu,
+ _ref$spaceSelectsMatc = _ref.spaceSelectsMatch,
+ spaceSelectsMatch = _ref$spaceSelectsMatc === void 0 ? false : _ref$spaceSelectsMatc,
+ _ref$searchOpts = _ref.searchOpts,
+ searchOpts = _ref$searchOpts === void 0 ? {} : _ref$searchOpts,
+ _ref$menuItemLimit = _ref.menuItemLimit,
+ menuItemLimit = _ref$menuItemLimit === void 0 ? null : _ref$menuItemLimit,
+ _ref$menuShowMinLengt = _ref.menuShowMinLength,
+ menuShowMinLength = _ref$menuShowMinLengt === void 0 ? 0 : _ref$menuShowMinLengt;
+
+ _classCallCheck(this, Tribute);
+
+ 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: function (t) {
+ if (typeof t === "string") {
+ if (t.trim() === "") return null;
+ return t;
+ }
+
+ if (typeof t === "function") {
+ return t.bind(_this);
+ }
+
+ return noMatchTemplate || function () {
+ return "No Match Found!";
+ }.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(function (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: function (t) {
+ if (typeof t === "string") {
+ if (t.trim() === "") return null;
+ return t;
+ }
+
+ if (typeof t === "function") {
+ return t.bind(_this);
+ }
+
+ return noMatchTemplate || function () {
+ return "No Match Found!";
+ }.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);
+ }
+
+ _createClass(Tribute, [{
+ key: "triggers",
+ value: function triggers() {
+ return this.collection.map(function (config) {
+ return config.trigger;
+ });
+ }
+ }, {
+ key: "attach",
+ value: function 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) {
+ var length = el.length;
+
+ for (var i = 0; i < length; ++i) {
+ this._attach(el[i]);
+ }
+ } else {
+ this._attach(el);
+ }
+ }
+ }, {
+ key: "_attach",
+ value: function _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);
+ }
+ }, {
+ key: "ensureEditable",
+ value: function 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);
+ }
+ }
+ }
+ }, {
+ key: "createMenu",
+ value: function createMenu(containerClass) {
+ var 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);
+ }
+ }, {
+ key: "showMenuFor",
+ value: function showMenuFor(element, scrollTo) {
+ var _this2 = this;
+
+ // 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 = "";
+ }
+
+ var processValues = function processValues(values) {
+ // Tribute may not be active any more by the time the value callback returns
+ if (!_this2.isActive) {
+ return;
+ }
+
+ var items = _this2.search.filter(_this2.current.mentionText, values, {
+ pre: _this2.current.collection.searchOpts.pre || "",
+ post: _this2.current.collection.searchOpts.post || "",
+ skip: _this2.current.collection.searchOpts.skip,
+ extract: function extract(el) {
+ if (typeof _this2.current.collection.lookup === "string") {
+ return el[_this2.current.collection.lookup];
+ } else if (typeof _this2.current.collection.lookup === "function") {
+ return _this2.current.collection.lookup(el, _this2.current.mentionText);
+ } else {
+ throw new Error("Invalid lookup attribute, lookup must be string or function.");
+ }
+ }
+ });
+
+ if (_this2.current.collection.menuItemLimit) {
+ items = items.slice(0, _this2.current.collection.menuItemLimit);
+ }
+
+ _this2.current.filteredItems = items;
+
+ var ul = _this2.menu.querySelector("ul");
+
+ _this2.range.positionMenuAtCaret(scrollTo);
+
+ if (!items.length) {
+ var noMatchEvent = new CustomEvent("tribute-no-match", {
+ detail: _this2.menu
+ });
+
+ _this2.current.element.dispatchEvent(noMatchEvent);
+
+ if (typeof _this2.current.collection.noMatchTemplate === "function" && !_this2.current.collection.noMatchTemplate() || !_this2.current.collection.noMatchTemplate) {
+ _this2.hideMenu();
+ } else {
+ typeof _this2.current.collection.noMatchTemplate === "function" ? ul.innerHTML = _this2.current.collection.noMatchTemplate() : ul.innerHTML = _this2.current.collection.noMatchTemplate;
+ }
+
+ return;
+ }
+
+ ul.innerHTML = "";
+
+ var fragment = _this2.range.getDocument().createDocumentFragment();
+
+ items.forEach(function (item, index) {
+ var li = _this2.range.getDocument().createElement("li");
+
+ li.setAttribute("data-index", index);
+ li.className = _this2.current.collection.itemClass;
+ li.addEventListener("mousemove", function (e) {
+ var _this2$_findLiTarget = _this2._findLiTarget(e.target),
+ _this2$_findLiTarget2 = _slicedToArray(_this2$_findLiTarget, 2),
+ li = _this2$_findLiTarget2[0],
+ index = _this2$_findLiTarget2[1];
+
+ if (e.movementY !== 0) {
+ _this2.events.setActiveLi(index);
+ }
+ });
+
+ if (_this2.menuSelected === index) {
+ li.classList.add(_this2.current.collection.selectClass);
+ }
+
+ li.innerHTML = _this2.current.collection.menuItemTemplate(item);
+ fragment.appendChild(li);
+ });
+ ul.appendChild(fragment);
+ };
+
+ 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);
+ }
+ }
+ }, {
+ key: "_findLiTarget",
+ value: function _findLiTarget(el) {
+ if (!el) return [];
+ var index = el.getAttribute("data-index");
+ return !index ? this._findLiTarget(el.parentNode) : [el, index];
+ }
+ }, {
+ key: "showMenuForCollection",
+ value: function 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
+
+ }, {
+ key: "placeCaretAtEnd",
+ value: function 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
+
+ }, {
+ key: "insertTextAtCursor",
+ value: function 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
+
+ }, {
+ key: "insertAtCaret",
+ value: function 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;
+ }
+ }, {
+ key: "hideMenu",
+ value: function hideMenu() {
+ if (this.menu) {
+ this.menu.style.cssText = "display: none;";
+ this.isActive = false;
+ this.menuSelected = 0;
+ this.current = {};
+ }
+ }
+ }, {
+ key: "selectItemAtIndex",
+ value: function selectItemAtIndex(index, originalEvent) {
+ index = parseInt(index);
+ if (typeof index !== "number" || isNaN(index)) return;
+ var item = this.current.filteredItems[index];
+ var content = this.current.collection.selectTemplate(item);
+ if (content !== null) this.replaceText(content, originalEvent, item);
+ }
+ }, {
+ key: "replaceText",
+ value: function replaceText(content, originalEvent, item) {
+ this.range.replaceTriggerText(content, true, true, originalEvent, item);
+ }
+ }, {
+ key: "_append",
+ value: function _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;
+ }
+ }
+ }, {
+ key: "append",
+ value: function append(collectionIndex, newValues, replace) {
+ var index = parseInt(collectionIndex);
+ if (typeof index !== "number") throw new Error("please provide an index for the collection to update.");
+ var collection = this.collection[index];
+
+ this._append(collection, newValues, replace);
+ }
+ }, {
+ key: "appendCurrent",
+ value: function 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.");
+ }
+ }
+ }, {
+ key: "detach",
+ value: function 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) {
+ var length = el.length;
+
+ for (var i = 0; i < length; ++i) {
+ this._detach(el[i]);
+ }
+ } else {
+ this._detach(el);
+ }
+ }
+ }, {
+ key: "_detach",
+ value: function _detach(el) {
+ var _this3 = this;
+
+ this.events.unbind(el);
+
+ if (el.tributeMenu) {
+ this.menuEvents.unbind(el.tributeMenu);
+ }
+
+ setTimeout(function () {
+ el.removeAttribute("data-tribute");
+ _this3.isActive = false;
+
+ if (el.tributeMenu) {
+ el.tributeMenu.remove();
+ }
+ });
+ }
+ }, {
+ key: "isActive",
+ get: function get() {
+ return this._isActive;
+ },
+ set: function set(val) {
+ if (this._isActive != val) {
+ this._isActive = val;
+
+ if (this.current.element) {
+ var noMatchEvent = new CustomEvent("tribute-active-".concat(val));
+ this.current.element.dispatchEvent(noMatchEvent);
+ }
+ }
+ }
+ }], [{
+ key: "defaultSelectTemplate",
+ value: function defaultSelectTemplate(item) {
+ if (typeof item === "undefined") return "".concat(this.current.collection.trigger).concat(this.current.mentionText);
+
+ if (this.range.isContentEditable(this.current.element)) {
+ return '' + (this.current.collection.trigger + item.original[this.current.collection.fillAttr]) + "";
+ }
+
+ return this.current.collection.trigger + item.original[this.current.collection.fillAttr];
+ }
+ }, {
+ key: "defaultMenuItemTemplate",
+ value: function defaultMenuItemTemplate(matchItem) {
+ return matchItem.string;
+ }
+ }, {
+ key: "inputTypes",
+ value: function inputTypes() {
+ return ["TEXTAREA", "INPUT"];
+ }
+ }]);
+
+ return Tribute;
+ }();
+
+ /**
+ * Tribute.js
+ * Native ES6 JavaScript @mention Plugin
+ **/
+
+ return Tribute;
+
+})));
diff --git a/phpBB/styles/prosilver/template/posting_buttons.html b/phpBB/styles/prosilver/template/posting_buttons.html
index 22b9e8a7b0..944940045d 100644
--- a/phpBB/styles/prosilver/template/posting_buttons.html
+++ b/phpBB/styles/prosilver/template/posting_buttons.html
@@ -28,6 +28,7 @@
+
diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css
index 7e56e06982..1c299b73ee 100644
--- a/phpBB/styles/prosilver/theme/colours.css
+++ b/phpBB/styles/prosilver/theme/colours.css
@@ -372,7 +372,7 @@ p.post-notice {
/* colours and backgrounds for mentions.css */
/* mention dropdown */
-.atwho-view { /* mention-container */
+.mention-container { /* mention-container */
background-color: #ffffff;
box-shadow:
0 3px 1px -2px rgba(0, 0, 0, 0.2),
diff --git a/phpBB/styles/prosilver/theme/mentions.css b/phpBB/styles/prosilver/theme/mentions.css
index e35512f27a..4b1e36f800 100644
--- a/phpBB/styles/prosilver/theme/mentions.css
+++ b/phpBB/styles/prosilver/theme/mentions.css
@@ -13,7 +13,7 @@
/* Mention dropdown
---------------------------------------- */
-.atwho-view { /* mention-container */
+.mention-container { /* mention-container */
text-align: left;
border-radius: 2px;
position: absolute;
@@ -21,7 +21,7 @@
transition: all 0.2s ease;
}
-.atwho-view-ul { /* mention-list */
+.mention-container ul { /* mention-list */
overflow: auto; /* placed here for list to scroll with arrow key press */
max-height: 200px;
margin: 0;