bremen_short_url/BackEnd/assets/vendor/tributejs/TributeRange.js

596 lines
25 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = 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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
// Thanks to https://github.com/jeff-collins/ment.io
var TributeRange = function () {
function TributeRange(tribute) {
_classCallCheck(this, TributeRange);
this.tribute = tribute;
this.tribute.range = this;
}
_createClass(TributeRange, [{
key: 'getDocument',
value: function getDocument() {
var iframe = void 0;
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 = void 0;
var info = this.getTriggerInfo(false, false, true, this.tribute.allowSpaces);
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.getDocument().activeElement, info.mentionPosition);
} else {
coordinates = this.getContentEditableCaretPosition(info.mentionPosition);
}
this.tribute.menu.style.cssText = 'top: ' + coordinates.top + 'px;\n left: ' + coordinates.left + 'px;\n right: ' + coordinates.right + 'px;\n bottom: ' + coordinates.bottom + 'px;\n position: absolute;\n zIndex: 10000;\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);
if (menuIsOffScreen.horizontally || menuIsOffScreen.vertically) {
_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 = void 0;
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();
}
// TODO: this may not be necessary anymore as we are using mouseup instead of click
}, {
key: 'resetSelection',
value: function resetSelection(targetElement, path, offset) {
if (!this.isContentEditable(targetElement)) {
if (targetElement !== this.getDocument().activeElement) {
targetElement.focus();
}
} else {
this.selectElement(targetElement, path, offset);
}
}
}, {
key: 'replaceTriggerText',
value: function replaceTriggerText(text, requireLeadingSpace, hasTrailingSpace, originalEvent, item) {
var context = this.tribute.current;
// TODO: this may not be necessary anymore as we are using mouseup instead of click
// this.resetSelection(context.element, context.selectedPath, context.selectedOffset)
var info = this.getTriggerInfo(true, hasTrailingSpace, requireLeadingSpace, this.tribute.allowSpaces);
// Create the event
var replaceEvent = new CustomEvent('tribute-replaced', {
detail: {
item: item,
event: originalEvent
}
});
if (info !== undefined) {
if (!this.isContentEditable(context.element)) {
var myField = this.getDocument().activeElement;
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;
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;
this.pasteHtml(text, info.mentionPosition, info.mentionPosition + info.mentionText.length + 1);
}
context.element.dispatchEvent(replaceEvent);
}
}
}, {
key: 'pasteHtml',
value: function pasteHtml(html, startPos, endPos) {
var range = void 0,
sel = void 0;
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 = void 0,
lastNode = void 0;
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 = void 0;
if (selected != null) {
var i = void 0;
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: 'getTriggerInfo',
value: function getTriggerInfo(menuAlreadyActive, hasTrailingSpace, requireLeadingSpace, allowSpaces) {
var _this2 = this;
var ctx = this.tribute.current;
var selected = void 0,
path = void 0,
offset = void 0;
if (!this.isContentEditable(ctx.element)) {
selected = this.getDocument().activeElement;
} else {
var selectionInfo = this.getContentEditableSelectedPath(ctx);
if (selectionInfo) {
selected = selectionInfo.selected;
path = selectionInfo.path;
offset = selectionInfo.offset;
}
}
var effectiveRange = this.getTextPrecedingCurrentSelection();
if (effectiveRange !== undefined && effectiveRange !== null) {
var mostRecentTriggerCharPos = -1;
var triggerChar = void 0;
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 + 1, effectiveRange.length);
triggerChar = effectiveRange.substring(mostRecentTriggerCharPos, mostRecentTriggerCharPos + 1);
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;
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, char) {
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 = char === reversedStr[cidx];
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 contentWidth = menuDimensions.width + coordinates.left;
var contentHeight = menuDimensions.height + coordinates.top;
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);
return {
horizontally: Math.ceil(contentWidth - windowLeft) >= windowWidth,
vertically: Math.ceil(contentHeight - windowTop) >= windowHeight
};
}
}, {
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 zIndex: 10000;\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 = 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 coordinates = {
top: rect.top + windowTop + span.offsetTop + parseInt(computed.borderTopWidth) + parseInt(computed.fontSize) - element.scrollTop,
left: rect.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.horizontally) {
coordinates.right = windowWidth - coordinates.left;
coordinates.left = 'auto';
}
var parentHeight = this.tribute.menuContainer ? this.tribute.menuContainer.offsetHeight : this.getDocument().body.offsetHeight;
if (menuIsOffScreen.vertically) {
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';
}
this.getDocument().body.removeChild(div);
return coordinates;
}
}, {
key: 'getContentEditableCaretPosition',
value: function getContentEditableCaretPosition(selectedNodePosition) {
var markerTextChar = '';
var markerEl = void 0,
markerId = 'sel_' + new Date().getTime() + '_' + Math.random().toString().substr(2);
var range = void 0;
var sel = this.getWindowSelection();
var prevRange = sel.getRangeAt(0);
range = this.getDocument().createRange();
range.setStart(sel.anchorNode, selectedNodePosition);
range.setEnd(sel.anchorNode, selectedNodePosition);
range.collapse(false);
// Create the marker element containing a single invisible character using DOM methods and insert it
markerEl = this.getDocument().createElement('span');
markerEl.id = markerId;
markerEl.appendChild(this.getDocument().createTextNode(markerTextChar));
range.insertNode(markerEl);
sel.removeAllRanges();
sel.addRange(prevRange);
var rect = markerEl.getBoundingClientRect();
var doc = document.documentElement;
var windowLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
var windowTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
var coordinates = {
left: rect.left + windowLeft,
top: rect.top + markerEl.offsetHeight + windowTop
};
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
var menuDimensions = this.getMenuDimensions();
var menuIsOffScreen = this.isMenuOffScreen(coordinates, menuDimensions);
if (menuIsOffScreen.horizontally) {
coordinates.left = 'auto';
coordinates.right = windowWidth - rect.left - windowLeft;
}
var parentHeight = this.tribute.menuContainer ? this.tribute.menuContainer.offsetHeight : this.getDocument().body.offsetHeight;
if (menuIsOffScreen.vertically) {
var parentRect = this.tribute.menuContainer ? this.tribute.menuContainer.getBoundingClientRect() : this.getDocument().body.getBoundingClientRect();
var scrollStillAvailable = parentHeight - (windowHeight - parentRect.top);
windowLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
windowTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
coordinates.top = 'auto';
coordinates.bottom = scrollStillAvailable + (windowHeight - rect.top);
}
markerEl.parentNode.removeChild(markerEl);
return coordinates;
}
}, {
key: 'scrollIntoView',
value: function scrollIntoView(elem) {
var reasonableBuffer = 20,
clientRect = void 0;
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);
}
}
}]);
return TributeRange;
}();
exports.default = TributeRange;
module.exports = exports['default'];