mirror of https://github.com/zulip/zulip.git
544 lines
17 KiB
JavaScript
544 lines
17 KiB
JavaScript
|
/*! jQuery Caret Plugin - v1.5.2 - 2014-03-25
|
||
|
* https://github.com/acdvorak/jquery.caret
|
||
|
* Copyright (c) 2012-2014 Andrew C. Dvorak; Licensed MIT */
|
||
|
(function($, undefined) {
|
||
|
|
||
|
var _input = document.createElement('input');
|
||
|
|
||
|
var _support = {
|
||
|
setSelectionRange: ('setSelectionRange' in _input) || ('selectionStart' in _input),
|
||
|
createTextRange: ('createTextRange' in _input) || ('selection' in document)
|
||
|
};
|
||
|
|
||
|
var _rNewlineIE = /\r\n/g,
|
||
|
_rCarriageReturn = /\r/g;
|
||
|
|
||
|
var _getValue = function(input) {
|
||
|
if (typeof(input.value) !== 'undefined') {
|
||
|
return input.value;
|
||
|
}
|
||
|
return $(input).text();
|
||
|
};
|
||
|
|
||
|
var _setValue = function(input, value) {
|
||
|
if (typeof(input.value) !== 'undefined') {
|
||
|
input.value = value;
|
||
|
} else {
|
||
|
$(input).text(value);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var _getIndex = function(input, pos) {
|
||
|
var norm = _getValue(input).replace(_rCarriageReturn, '');
|
||
|
var len = norm.length;
|
||
|
|
||
|
if (typeof(pos) === 'undefined') {
|
||
|
pos = len;
|
||
|
}
|
||
|
|
||
|
pos = Math.floor(pos);
|
||
|
|
||
|
// Negative index counts backward from the end of the input/textarea's value
|
||
|
if (pos < 0) {
|
||
|
pos = len + pos;
|
||
|
}
|
||
|
|
||
|
// Enforce boundaries
|
||
|
if (pos < 0) { pos = 0; }
|
||
|
if (pos > len) { pos = len; }
|
||
|
|
||
|
return pos;
|
||
|
};
|
||
|
|
||
|
var _hasAttr = function(input, attrName) {
|
||
|
return input.hasAttribute ? input.hasAttribute(attrName) : (typeof(input[attrName]) !== 'undefined');
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @class
|
||
|
* @constructor
|
||
|
*/
|
||
|
var Range = function(start, end, length, text) {
|
||
|
this.start = start || 0;
|
||
|
this.end = end || 0;
|
||
|
this.length = length || 0;
|
||
|
this.text = text || '';
|
||
|
};
|
||
|
|
||
|
Range.prototype.toString = function() {
|
||
|
return JSON.stringify(this, null, ' ');
|
||
|
};
|
||
|
|
||
|
var _getCaretW3 = function(input) {
|
||
|
return input.selectionStart;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @see http://stackoverflow.com/q/6943000/467582
|
||
|
*/
|
||
|
var _getCaretIE = function(input) {
|
||
|
var caret, range, textInputRange, rawValue, len, endRange;
|
||
|
|
||
|
// Yeah, you have to focus twice for IE 7 and 8. *cries*
|
||
|
input.focus();
|
||
|
input.focus();
|
||
|
|
||
|
range = document.selection.createRange();
|
||
|
|
||
|
if (range && range.parentElement() === input) {
|
||
|
rawValue = _getValue(input);
|
||
|
|
||
|
len = rawValue.length;
|
||
|
|
||
|
// Create a working TextRange that lives only in the input
|
||
|
textInputRange = input.createTextRange();
|
||
|
textInputRange.moveToBookmark(range.getBookmark());
|
||
|
|
||
|
// Check if the start and end of the selection are at the very end
|
||
|
// of the input, since moveStart/moveEnd doesn't return what we want
|
||
|
// in those cases
|
||
|
endRange = input.createTextRange();
|
||
|
endRange.collapse(false);
|
||
|
|
||
|
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
|
||
|
caret = rawValue.replace(_rNewlineIE, '\n').length;
|
||
|
} else {
|
||
|
caret = -textInputRange.moveStart("character", -len);
|
||
|
}
|
||
|
|
||
|
return caret;
|
||
|
}
|
||
|
|
||
|
// NOTE: This occurs when you highlight part of a textarea and then click in the middle of the highlighted portion in IE 6-10.
|
||
|
// There doesn't appear to be anything we can do about it.
|
||
|
// alert("Your browser is incredibly stupid. I don't know what else to say.");
|
||
|
// alert(range + '\n\n' + range.parentElement().tagName + '#' + range.parentElement().id);
|
||
|
|
||
|
return 0;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Gets the position of the caret in the given input.
|
||
|
* @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
|
||
|
* @returns {Number}
|
||
|
* @see http://stackoverflow.com/questions/263743/how-to-get-cursor-position-in-textarea/263796#263796
|
||
|
*/
|
||
|
var _getCaret = function(input) {
|
||
|
if (!input) {
|
||
|
return undefined;
|
||
|
}
|
||
|
|
||
|
// Mozilla, et al.
|
||
|
if (_support.setSelectionRange) {
|
||
|
return _getCaretW3(input);
|
||
|
}
|
||
|
// IE
|
||
|
else if (_support.createTextRange) {
|
||
|
return _getCaretIE(input);
|
||
|
}
|
||
|
|
||
|
return undefined;
|
||
|
};
|
||
|
|
||
|
var _setCaretW3 = function(input, pos) {
|
||
|
input.setSelectionRange(pos, pos);
|
||
|
};
|
||
|
|
||
|
var _setCaretIE = function(input, pos) {
|
||
|
var range = input.createTextRange();
|
||
|
range.move('character', pos);
|
||
|
range.select();
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Sets the position of the caret in the given input.
|
||
|
* @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
|
||
|
* @param {Number} pos
|
||
|
* @see http://parentnode.org/javascript/working-with-the-cursor-position/
|
||
|
*/
|
||
|
var _setCaret = function(input, pos) {
|
||
|
input.focus();
|
||
|
|
||
|
pos = _getIndex(input, pos);
|
||
|
|
||
|
// Mozilla, et al.
|
||
|
if (_support.setSelectionRange) {
|
||
|
_setCaretW3(input, pos);
|
||
|
}
|
||
|
// IE
|
||
|
else if (_support.createTextRange) {
|
||
|
_setCaretIE(input, pos);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Inserts the specified text at the current caret position in the given input.
|
||
|
* @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
|
||
|
* @param {String} text
|
||
|
* @see http://parentnode.org/javascript/working-with-the-cursor-position/
|
||
|
*/
|
||
|
var _insertAtCaret = function(input, text) {
|
||
|
var curPos = _getCaret(input);
|
||
|
|
||
|
var oldValueNorm = _getValue(input).replace(_rCarriageReturn, '');
|
||
|
|
||
|
var newLength = +(curPos + text.length + (oldValueNorm.length - curPos));
|
||
|
var maxLength = +input.getAttribute('maxlength');
|
||
|
|
||
|
if(_hasAttr(input, 'maxlength') && newLength > maxLength) {
|
||
|
var delta = text.length - (newLength - maxLength);
|
||
|
text = text.substr(0, delta);
|
||
|
}
|
||
|
|
||
|
_setValue(input, oldValueNorm.substr(0, curPos) + text + oldValueNorm.substr(curPos));
|
||
|
|
||
|
_setCaret(input, curPos + text.length);
|
||
|
};
|
||
|
|
||
|
var _getInputRangeW3 = function(input) {
|
||
|
var range = new Range();
|
||
|
|
||
|
range.start = input.selectionStart;
|
||
|
range.end = input.selectionEnd;
|
||
|
|
||
|
var min = Math.min(range.start, range.end);
|
||
|
var max = Math.max(range.start, range.end);
|
||
|
|
||
|
range.length = max - min;
|
||
|
range.text = _getValue(input).substring(min, max);
|
||
|
|
||
|
return range;
|
||
|
};
|
||
|
|
||
|
/** @see http://stackoverflow.com/a/3648244/467582 */
|
||
|
var _getInputRangeIE = function(input) {
|
||
|
var range = new Range();
|
||
|
|
||
|
input.focus();
|
||
|
|
||
|
var selection = document.selection.createRange();
|
||
|
|
||
|
if (selection && selection.parentElement() === input) {
|
||
|
var len, normalizedValue, textInputRange, endRange, start = 0, end = 0;
|
||
|
var rawValue = _getValue(input);
|
||
|
|
||
|
len = rawValue.length;
|
||
|
normalizedValue = rawValue.replace(/\r\n/g, "\n");
|
||
|
|
||
|
// Create a working TextRange that lives only in the input
|
||
|
textInputRange = input.createTextRange();
|
||
|
textInputRange.moveToBookmark(selection.getBookmark());
|
||
|
|
||
|
// Check if the start and end of the selection are at the very end
|
||
|
// of the input, since moveStart/moveEnd doesn't return what we want
|
||
|
// in those cases
|
||
|
endRange = input.createTextRange();
|
||
|
endRange.collapse(false);
|
||
|
|
||
|
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
|
||
|
start = end = len;
|
||
|
} else {
|
||
|
start = -textInputRange.moveStart("character", -len);
|
||
|
start += normalizedValue.slice(0, start).split("\n").length - 1;
|
||
|
|
||
|
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
|
||
|
end = len;
|
||
|
} else {
|
||
|
end = -textInputRange.moveEnd("character", -len);
|
||
|
end += normalizedValue.slice(0, end).split("\n").length - 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// normalize newlines
|
||
|
start -= (rawValue.substring(0, start).split('\r\n').length - 1);
|
||
|
end -= (rawValue.substring(0, end).split('\r\n').length - 1);
|
||
|
/// normalize newlines
|
||
|
|
||
|
range.start = start;
|
||
|
range.end = end;
|
||
|
range.length = range.end - range.start;
|
||
|
range.text = normalizedValue.substr(range.start, range.length);
|
||
|
}
|
||
|
|
||
|
return range;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Gets the selected text range of the given input.
|
||
|
* @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
|
||
|
* @returns {Range}
|
||
|
* @see http://stackoverflow.com/a/263796/467582
|
||
|
* @see http://stackoverflow.com/a/2966703/467582
|
||
|
*/
|
||
|
var _getInputRange = function(input) {
|
||
|
if (!input) {
|
||
|
return undefined;
|
||
|
}
|
||
|
|
||
|
// Mozilla, et al.
|
||
|
if (_support.setSelectionRange) {
|
||
|
return _getInputRangeW3(input);
|
||
|
}
|
||
|
// IE
|
||
|
else if (_support.createTextRange) {
|
||
|
return _getInputRangeIE(input);
|
||
|
}
|
||
|
|
||
|
return undefined;
|
||
|
};
|
||
|
|
||
|
var _setInputRangeW3 = function(input, startPos, endPos) {
|
||
|
input.setSelectionRange(startPos, endPos);
|
||
|
};
|
||
|
|
||
|
var _setInputRangeIE = function(input, startPos, endPos) {
|
||
|
var tr = input.createTextRange();
|
||
|
tr.moveEnd('textedit', -1);
|
||
|
tr.moveStart('character', startPos);
|
||
|
tr.moveEnd('character', endPos - startPos);
|
||
|
tr.select();
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Sets the selected text range of (i.e., highlights text in) the given input.
|
||
|
* @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
|
||
|
* @param {Number} startPos Zero-based index
|
||
|
* @param {Number} endPos Zero-based index
|
||
|
* @see http://parentnode.org/javascript/working-with-the-cursor-position/
|
||
|
* @see http://stackoverflow.com/a/2966703/467582
|
||
|
*/
|
||
|
var _setInputRange = function(input, startPos, endPos) {
|
||
|
startPos = _getIndex(input, startPos);
|
||
|
endPos = _getIndex(input, endPos);
|
||
|
|
||
|
// Mozilla, et al.
|
||
|
if (_support.setSelectionRange) {
|
||
|
_setInputRangeW3(input, startPos, endPos);
|
||
|
}
|
||
|
// IE
|
||
|
else if (_support.createTextRange) {
|
||
|
_setInputRangeIE(input, startPos, endPos);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Replaces the currently selected text with the given string.
|
||
|
* @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
|
||
|
* @param {String} text New text that will replace the currently selected text.
|
||
|
* @see http://parentnode.org/javascript/working-with-the-cursor-position/
|
||
|
*/
|
||
|
var _replaceInputRange = function(input, text) {
|
||
|
var $input = $(input);
|
||
|
|
||
|
var oldValue = $input.val();
|
||
|
var selection = _getInputRange(input);
|
||
|
|
||
|
var newLength = +(selection.start + text.length + (oldValue.length - selection.end));
|
||
|
var maxLength = +$input.attr('maxlength');
|
||
|
|
||
|
if($input.is('[maxlength]') && newLength > maxLength) {
|
||
|
var delta = text.length - (newLength - maxLength);
|
||
|
text = text.substr(0, delta);
|
||
|
}
|
||
|
|
||
|
// Now that we know what the user selected, we can replace it
|
||
|
var startText = oldValue.substr(0, selection.start);
|
||
|
var endText = oldValue.substr(selection.end);
|
||
|
|
||
|
$input.val(startText + text + endText);
|
||
|
|
||
|
// Reset the selection
|
||
|
var startPos = selection.start;
|
||
|
var endPos = startPos + text.length;
|
||
|
|
||
|
_setInputRange(input, selection.length ? startPos : endPos, endPos);
|
||
|
};
|
||
|
|
||
|
var _selectAllW3 = function(elem) {
|
||
|
var selection = window.getSelection();
|
||
|
var range = document.createRange();
|
||
|
range.selectNodeContents(elem);
|
||
|
selection.removeAllRanges();
|
||
|
selection.addRange(range);
|
||
|
};
|
||
|
|
||
|
var _selectAllIE = function(elem) {
|
||
|
var range = document.body.createTextRange();
|
||
|
range.moveToElementText(elem);
|
||
|
range.select();
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Select all text in the given element.
|
||
|
* @param {HTMLElement} elem Any block or inline element other than a form element.
|
||
|
*/
|
||
|
var _selectAll = function(elem) {
|
||
|
var $elem = $(elem);
|
||
|
if ($elem.is('input, textarea') || elem.select) {
|
||
|
$elem.select();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Mozilla, et al.
|
||
|
if (_support.setSelectionRange) {
|
||
|
_selectAllW3(elem);
|
||
|
}
|
||
|
// IE
|
||
|
else if (_support.createTextRange) {
|
||
|
_selectAllIE(elem);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var _deselectAll = function() {
|
||
|
if (document.selection) {
|
||
|
document.selection.empty();
|
||
|
}
|
||
|
else if (window.getSelection) {
|
||
|
window.getSelection().removeAllRanges();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
$.extend($.fn, {
|
||
|
|
||
|
/**
|
||
|
* Gets or sets the position of the caret or inserts text at the current caret position in an input or textarea element.
|
||
|
* @returns {Number|jQuery} The current caret position if invoked as a getter (with no arguments)
|
||
|
* or this jQuery object if invoked as a setter or inserter.
|
||
|
* @see http://web.archive.org/web/20080704185920/http://parentnode.org/javascript/working-with-the-cursor-position/
|
||
|
* @since 1.0.0
|
||
|
* @example
|
||
|
* <pre>
|
||
|
* // Get position
|
||
|
* var pos = $('input:first').caret();
|
||
|
* </pre>
|
||
|
* @example
|
||
|
* <pre>
|
||
|
* // Set position
|
||
|
* $('input:first').caret(15);
|
||
|
* $('input:first').caret(-3);
|
||
|
* </pre>
|
||
|
* @example
|
||
|
* <pre>
|
||
|
* // Insert text at current position
|
||
|
* $('input:first').caret('Some text');
|
||
|
* </pre>
|
||
|
*/
|
||
|
caret: function() {
|
||
|
var $inputs = this.filter('input, textarea');
|
||
|
|
||
|
// getCaret()
|
||
|
if (arguments.length === 0) {
|
||
|
var input = $inputs.get(0);
|
||
|
return _getCaret(input);
|
||
|
}
|
||
|
// setCaret(position)
|
||
|
else if (typeof arguments[0] === 'number') {
|
||
|
var pos = arguments[0];
|
||
|
$inputs.each(function(_i, input) {
|
||
|
_setCaret(input, pos);
|
||
|
});
|
||
|
}
|
||
|
// insertAtCaret(text)
|
||
|
else {
|
||
|
var text = arguments[0];
|
||
|
$inputs.each(function(_i, input) {
|
||
|
_insertAtCaret(input, text);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return this;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Gets or sets the selection range or replaces the currently selected text in an input or textarea element.
|
||
|
* @returns {Range|jQuery} The current selection range if invoked as a getter (with no arguments)
|
||
|
* or this jQuery object if invoked as a setter or replacer.
|
||
|
* @see http://stackoverflow.com/a/2966703/467582
|
||
|
* @since 1.0.0
|
||
|
* @example
|
||
|
* <pre>
|
||
|
* // Get selection range
|
||
|
* var range = $('input:first').range();
|
||
|
* </pre>
|
||
|
* @example
|
||
|
* <pre>
|
||
|
* // Set selection range
|
||
|
* $('input:first').range(15);
|
||
|
* $('input:first').range(15, 20);
|
||
|
* $('input:first').range(-3);
|
||
|
* $('input:first').range(-8, -3);
|
||
|
* </pre>
|
||
|
* @example
|
||
|
* <pre>
|
||
|
* // Replace the currently selected text
|
||
|
* $('input:first').range('Replacement text');
|
||
|
* </pre>
|
||
|
*/
|
||
|
range: function() {
|
||
|
var $inputs = this.filter('input, textarea');
|
||
|
|
||
|
// getRange() = { start: pos, end: pos }
|
||
|
if (arguments.length === 0) {
|
||
|
var input = $inputs.get(0);
|
||
|
return _getInputRange(input);
|
||
|
}
|
||
|
// setRange(startPos, endPos)
|
||
|
else if (typeof arguments[0] === 'number') {
|
||
|
var startPos = arguments[0];
|
||
|
var endPos = arguments[1];
|
||
|
$inputs.each(function(_i, input) {
|
||
|
_setInputRange(input, startPos, endPos);
|
||
|
});
|
||
|
}
|
||
|
// replaceRange(text)
|
||
|
else {
|
||
|
var text = arguments[0];
|
||
|
$inputs.each(function(_i, input) {
|
||
|
_replaceInputRange(input, text);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return this;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Selects all text in each element of this jQuery object.
|
||
|
* @returns {jQuery} This jQuery object
|
||
|
* @see http://stackoverflow.com/a/11128179/467582
|
||
|
* @since 1.5.0
|
||
|
* @example
|
||
|
* <pre>
|
||
|
* // Select the contents of span elements when clicked
|
||
|
* $('span').on('click', function() { $(this).highlight(); });
|
||
|
* </pre>
|
||
|
*/
|
||
|
selectAll: function() {
|
||
|
return this.each(function(_i, elem) {
|
||
|
_selectAll(elem);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
});
|
||
|
|
||
|
$.extend($, {
|
||
|
/**
|
||
|
* Deselects all text on the page.
|
||
|
* @returns {jQuery} The jQuery function
|
||
|
* @since 1.5.0
|
||
|
* @example
|
||
|
* <pre>
|
||
|
* // Select some text
|
||
|
* $('span').selectAll();
|
||
|
*
|
||
|
* // Deselect the text
|
||
|
* $.deselectAll();
|
||
|
* </pre>
|
||
|
*/
|
||
|
deselectAll: function() {
|
||
|
_deselectAll();
|
||
|
return this;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
}(window.jQuery || window.Zepto || window.$));
|