Update textcomplete to 1.7.2 (#5034)

* Update textcomplete to 1.7.2

* Update to 1.7.3

* Add version 1.7.3 to file
v1.18.x
Brian Harrington 9 years ago committed by Julian Lam
parent 92f96883e5
commit 36f0637a64

@ -17,7 +17,7 @@
* Repository: https://github.com/yuku-t/jquery-textcomplete * Repository: https://github.com/yuku-t/jquery-textcomplete
* License: MIT (https://github.com/yuku-t/jquery-textcomplete/blob/master/LICENSE) * License: MIT (https://github.com/yuku-t/jquery-textcomplete/blob/master/LICENSE)
* Author: Yuku Takahashi * Author: Yuku Takahashi
* Version: 1.3.4 * Version: 1.7.3
*/ */
if (typeof jQuery === 'undefined') { if (typeof jQuery === 'undefined') {
@ -137,10 +137,6 @@ if (typeof jQuery === 'undefined') {
return Object.prototype.toString.call(obj) === '[object String]'; return Object.prototype.toString.call(obj) === '[object String]';
}; };
var isFunction = function (obj) {
return Object.prototype.toString.call(obj) === '[object Function]';
};
var uniqueId = 0; var uniqueId = 0;
function Completer(element, option) { function Completer(element, option) {
@ -148,32 +144,46 @@ if (typeof jQuery === 'undefined') {
this.id = 'textcomplete' + uniqueId++; this.id = 'textcomplete' + uniqueId++;
this.strategies = []; this.strategies = [];
this.views = []; this.views = [];
this.option = $.extend({}, Completer._getDefaults(), option); this.option = $.extend({}, Completer.defaults, option);
if (!this.$el.is('input[type=text]') && !this.$el.is('input[type=search]') && !this.$el.is('textarea') && !element.isContentEditable && element.contentEditable != 'true') { if (!this.$el.is('input[type=text]') && !this.$el.is('input[type=search]') && !this.$el.is('textarea') && !element.isContentEditable && element.contentEditable != 'true') {
throw new Error('textcomplete must be called on a Textarea or a ContentEditable.'); throw new Error('textcomplete must be called on a Textarea or a ContentEditable.');
} }
if (element === document.activeElement) { // use ownerDocument to fix iframe / IE issues
if (element === element.ownerDocument.activeElement) {
// element has already been focused. Initialize view objects immediately. // element has already been focused. Initialize view objects immediately.
this.initialize() this.initialize()
} else { } else {
// Initialize view objects lazily. // Initialize view objects lazily.
var self = this; var self = this;
this.$el.one('focus.' + this.id, function () { self.initialize(); }); this.$el.one('focus.' + this.id, function () { self.initialize(); });
// Special handling for CKEditor: lazy init on instance load
if ((!this.option.adapter || this.option.adapter == 'CKEditor') && typeof CKEDITOR != 'undefined' && (this.$el.is('textarea'))) {
CKEDITOR.on("instanceReady", function(event) {
event.editor.once("focus", function(event2) {
// replace the element with the Iframe element and flag it as CKEditor
self.$el = $(event.editor.editable().$);
if (!self.option.adapter) {
self.option.adapter = $.fn.textcomplete['CKEditor'];
self.option.ckeditor_instance = event.editor;
}
self.initialize();
});
});
}
} }
} }
Completer._getDefaults = function () { Completer.defaults = {
if (!Completer.DEFAULTS) { appendTo: 'body',
Completer.DEFAULTS = { className: '', // deprecated option
appendTo: $('body'), dropdownClassName: 'dropdown-menu textcomplete-dropdown',
zIndex: '100' maxCount: 10,
zIndex: '100',
rightEdgeOffset: 30
}; };
}
return Completer.DEFAULTS;
}
$.extend(Completer.prototype, { $.extend(Completer.prototype, {
// Public properties // Public properties
@ -185,12 +195,26 @@ if (typeof jQuery === 'undefined') {
adapter: null, adapter: null,
dropdown: null, dropdown: null,
$el: null, $el: null,
$iframe: null,
// Public methods // Public methods
// -------------- // --------------
initialize: function () { initialize: function () {
var element = this.$el.get(0); var element = this.$el.get(0);
// check if we are in an iframe
// we need to alter positioning logic if using an iframe
if (this.$el.prop('ownerDocument') !== document && window.frames.length) {
for (var iframeIndex = 0; iframeIndex < window.frames.length; iframeIndex++) {
if (this.$el.prop('ownerDocument') === window.frames[iframeIndex].document) {
this.$iframe = $(window.frames[iframeIndex].frameElement);
break;
}
}
}
// Initialize view objects. // Initialize view objects.
this.dropdown = new $.fn.textcomplete.Dropdown(element, this, this.option); this.dropdown = new $.fn.textcomplete.Dropdown(element, this, this.option);
var Adapter, viewName; var Adapter, viewName;
@ -282,7 +306,7 @@ if (typeof jQuery === 'undefined') {
var strategy = this.strategies[i]; var strategy = this.strategies[i];
var context = strategy.context(text); var context = strategy.context(text);
if (context || context === '') { if (context || context === '') {
var matchRegexp = isFunction(strategy.match) ? strategy.match(text) : strategy.match; var matchRegexp = $.isFunction(strategy.match) ? strategy.match(text) : strategy.match;
if (isString(context)) { text = context; } if (isString(context)) { text = context; }
var match = text.match(matchRegexp); var match = text.match(matchRegexp);
if (match) { return [strategy, match[strategy.index], match]; } if (match) { return [strategy, match[strategy.index], match]; }
@ -400,7 +424,7 @@ if (typeof jQuery === 'undefined') {
var $parent = option.appendTo; var $parent = option.appendTo;
if (!($parent instanceof $)) { $parent = $($parent); } if (!($parent instanceof $)) { $parent = $($parent); }
var $el = $('<ul></ul>') var $el = $('<ul></ul>')
.addClass('dropdown-menu textcomplete-dropdown') .addClass(option.dropdownClassName)
.attr('id', 'textcomplete-dropdown-' + option._oid) .attr('id', 'textcomplete-dropdown-' + option._oid)
.css({ .css({
display: 'none', display: 'none',
@ -423,7 +447,7 @@ if (typeof jQuery === 'undefined') {
footer: null, footer: null,
header: null, header: null,
id: null, id: null,
maxCount: 10, maxCount: null,
placement: '', placement: '',
shown: false, shown: false,
data: [], // Shown zipped data. data: [], // Shown zipped data.
@ -446,8 +470,8 @@ if (typeof jQuery === 'undefined') {
render: function (zippedData) { render: function (zippedData) {
var contentsHtml = this._buildContents(zippedData); var contentsHtml = this._buildContents(zippedData);
var unzippedData = $.map(this.data, function (d) { return d.value; }); var unzippedData = $.map(zippedData, function (d) { return d.value; });
if (this.data.length) { if (zippedData.length) {
var strategy = zippedData[0].strategy; var strategy = zippedData[0].strategy;
if (strategy.id) { if (strategy.id) {
this.$el.attr('data-strategy', strategy.id); this.$el.attr('data-strategy', strategy.id);
@ -786,8 +810,11 @@ if (typeof jQuery === 'undefined') {
var windowScrollBottom = $window.scrollTop() + $window.height(); var windowScrollBottom = $window.scrollTop() + $window.height();
var height = this.$el.height(); var height = this.$el.height();
if ((this.$el.position().top + height) > windowScrollBottom) { if ((this.$el.position().top + height) > windowScrollBottom) {
// only do this if we are not in an iframe
if (!this.completer.$iframe) {
this.$el.offset({top: windowScrollBottom - height}); this.$el.offset({top: windowScrollBottom - height});
} }
}
}, },
_fitToRight: function() { _fitToRight: function() {
@ -795,7 +822,7 @@ if (typeof jQuery === 'undefined') {
// to the document width so we don't know if we would have overrun it. As a heuristic to avoid that clipping // to the document width so we don't know if we would have overrun it. As a heuristic to avoid that clipping
// (which makes our elements wrap onto the next line and corrupt the next item), if we're close to the right // (which makes our elements wrap onto the next line and corrupt the next item), if we're close to the right
// edge, move left. We don't know how far to move left, so just keep nudging a bit. // edge, move left. We don't know how far to move left, so just keep nudging a bit.
var tolerance = 30; // pixels. Make wider than vertical scrollbar because we might not be able to use that space. var tolerance = this.option.rightEdgeOffset; // pixels. Make wider than vertical scrollbar because we might not be able to use that space.
var lastOffset = this.$el.offset().left, offset; var lastOffset = this.$el.offset().left, offset;
var width = this.$el.width(); var width = this.$el.width();
var maxLeft = $window.width() - tolerance; var maxLeft = $window.width() - tolerance;
@ -1008,6 +1035,7 @@ if (typeof jQuery === 'undefined') {
case 13: // ENTER case 13: // ENTER
case 40: // DOWN case 40: // DOWN
case 38: // UP case 38: // UP
case 27: // ESC
return true; return true;
} }
if (clickEvent.ctrlKey) switch (clickEvent.keyCode) { if (clickEvent.ctrlKey) switch (clickEvent.keyCode) {
@ -1041,12 +1069,14 @@ if (typeof jQuery === 'undefined') {
var pre = this.getTextFromHeadToCaret(); var pre = this.getTextFromHeadToCaret();
var post = this.el.value.substring(this.el.selectionEnd); var post = this.el.value.substring(this.el.selectionEnd);
var newSubstr = strategy.replace(value, e); var newSubstr = strategy.replace(value, e);
var regExp;
if (typeof newSubstr !== 'undefined') { if (typeof newSubstr !== 'undefined') {
if ($.isArray(newSubstr)) { if ($.isArray(newSubstr)) {
post = newSubstr[1] + post; post = newSubstr[1] + post;
newSubstr = newSubstr[0]; newSubstr = newSubstr[0];
} }
pre = pre.replace(strategy.match, newSubstr); regExp = $.isFunction(strategy.match) ? strategy.match(pre) : strategy.match;
pre = pre.replace(regExp, newSubstr);
this.$el.val(pre + post); this.$el.val(pre + post);
this.el.selectionStart = this.el.selectionEnd = pre.length; this.el.selectionStart = this.el.selectionEnd = pre.length;
} }
@ -1063,7 +1093,8 @@ if (typeof jQuery === 'undefined') {
var p = $.fn.textcomplete.getCaretCoordinates(this.el, this.el.selectionStart); var p = $.fn.textcomplete.getCaretCoordinates(this.el, this.el.selectionStart);
return { return {
top: p.top + this._calculateLineHeight() - this.$el.scrollTop(), top: p.top + this._calculateLineHeight() - this.$el.scrollTop(),
left: p.left - this.$el.scrollLeft() left: p.left - this.$el.scrollLeft(),
lineHeight: this._calculateLineHeight()
}; };
}, },
@ -1112,12 +1143,14 @@ if (typeof jQuery === 'undefined') {
var pre = this.getTextFromHeadToCaret(); var pre = this.getTextFromHeadToCaret();
var post = this.el.value.substring(pre.length); var post = this.el.value.substring(pre.length);
var newSubstr = strategy.replace(value, e); var newSubstr = strategy.replace(value, e);
var regExp;
if (typeof newSubstr !== 'undefined') { if (typeof newSubstr !== 'undefined') {
if ($.isArray(newSubstr)) { if ($.isArray(newSubstr)) {
post = newSubstr[1] + post; post = newSubstr[1] + post;
newSubstr = newSubstr[0]; newSubstr = newSubstr[0];
} }
pre = pre.replace(strategy.match, newSubstr); regExp = $.isFunction(strategy.match) ? strategy.match(pre) : strategy.match;
pre = pre.replace(regExp, newSubstr);
this.$el.val(pre + post); this.$el.val(pre + post);
this.el.focus(); this.el.focus();
var range = this.el.createTextRange(); var range = this.el.createTextRange();
@ -1163,30 +1196,35 @@ if (typeof jQuery === 'undefined') {
// When an dropdown item is selected, it is executed. // When an dropdown item is selected, it is executed.
select: function (value, strategy, e) { select: function (value, strategy, e) {
var pre = this.getTextFromHeadToCaret(); var pre = this.getTextFromHeadToCaret();
var sel = window.getSelection() // use ownerDocument instead of window to support iframes
var sel = this.el.ownerDocument.getSelection();
var range = sel.getRangeAt(0); var range = sel.getRangeAt(0);
var selection = range.cloneRange(); var selection = range.cloneRange();
selection.selectNodeContents(range.startContainer); selection.selectNodeContents(range.startContainer);
var content = selection.toString(); var content = selection.toString();
var post = content.substring(range.startOffset); var post = content.substring(range.startOffset);
var newSubstr = strategy.replace(value, e); var newSubstr = strategy.replace(value, e);
var regExp;
if (typeof newSubstr !== 'undefined') { if (typeof newSubstr !== 'undefined') {
if ($.isArray(newSubstr)) { if ($.isArray(newSubstr)) {
post = newSubstr[1] + post; post = newSubstr[1] + post;
newSubstr = newSubstr[0]; newSubstr = newSubstr[0];
} }
pre = pre.replace(strategy.match, newSubstr); regExp = $.isFunction(strategy.match) ? strategy.match(pre) : strategy.match;
pre = pre.replace(regExp, newSubstr)
.replace(/ $/, "&nbsp"); // &nbsp necessary at least for CKeditor to not eat spaces
range.selectNodeContents(range.startContainer); range.selectNodeContents(range.startContainer);
range.deleteContents(); range.deleteContents();
// create temporary elements // create temporary elements
var preWrapper = document.createElement("div"); var preWrapper = this.el.ownerDocument.createElement("div");
preWrapper.innerHTML = pre; preWrapper.innerHTML = pre;
var postWrapper = document.createElement("div"); var postWrapper = this.el.ownerDocument.createElement("div");
postWrapper.innerHTML = post; postWrapper.innerHTML = post;
// create the fragment thats inserted // create the fragment thats inserted
var fragment = document.createDocumentFragment(); var fragment = this.el.ownerDocument.createDocumentFragment();
var childNode; var childNode;
var lastOfPre; var lastOfPre;
while (childNode = preWrapper.firstChild) { while (childNode = preWrapper.firstChild) {
@ -1219,8 +1257,8 @@ if (typeof jQuery === 'undefined') {
// //
// Dropdown's position will be decided using the result. // Dropdown's position will be decided using the result.
_getCaretRelativePosition: function () { _getCaretRelativePosition: function () {
var range = window.getSelection().getRangeAt(0).cloneRange(); var range = this.el.ownerDocument.getSelection().getRangeAt(0).cloneRange();
var node = document.createElement('span'); var node = this.el.ownerDocument.createElement('span');
range.insertNode(node); range.insertNode(node);
range.selectNodeContents(node); range.selectNodeContents(node);
range.deleteContents(); range.deleteContents();
@ -1229,6 +1267,17 @@ if (typeof jQuery === 'undefined') {
position.left -= this.$el.offset().left; position.left -= this.$el.offset().left;
position.top += $node.height() - this.$el.offset().top; position.top += $node.height() - this.$el.offset().top;
position.lineHeight = $node.height(); position.lineHeight = $node.height();
// special positioning logic for iframes
// this is typically used for contenteditables such as tinymce or ckeditor
if (this.completer.$iframe) {
var iframePosition = this.completer.$iframe.offset();
position.top += iframePosition.top;
position.left += iframePosition.left;
//subtract scrollTop from element in iframe
position.top -= this.$el.scrollTop();
}
$node.remove(); $node.remove();
return position; return position;
}, },
@ -1242,7 +1291,7 @@ if (typeof jQuery === 'undefined') {
// this.getTextFromHeadToCaret() // this.getTextFromHeadToCaret()
// // => ' wor' // not '<b>hello</b> wor' // // => ' wor' // not '<b>hello</b> wor'
getTextFromHeadToCaret: function () { getTextFromHeadToCaret: function () {
var range = window.getSelection().getRangeAt(0); var range = this.el.ownerDocument.getSelection().getRangeAt(0);
var selection = range.cloneRange(); var selection = range.cloneRange();
selection.selectNodeContents(range.startContainer); selection.selectNodeContents(range.startContainer);
return selection.toString().substring(0, range.startOffset); return selection.toString().substring(0, range.startOffset);
@ -1252,6 +1301,39 @@ if (typeof jQuery === 'undefined') {
$.fn.textcomplete.ContentEditable = ContentEditable; $.fn.textcomplete.ContentEditable = ContentEditable;
}(jQuery); }(jQuery);
// NOTE: TextComplete plugin has contenteditable support but it does not work
// fine especially on old IEs.
// Any pull requests are REALLY welcome.
+function ($) {
'use strict';
// CKEditor adapter
// =======================
//
// Adapter for CKEditor, based on contenteditable elements.
function CKEditor (element, completer, option) {
this.initialize(element, completer, option);
}
$.extend(CKEditor.prototype, $.fn.textcomplete.ContentEditable.prototype, {
_bindEvents: function () {
var $this = this;
this.option.ckeditor_instance.on('key', function(event) {
var domEvent = event.data;
$this._onKeyup(domEvent);
if ($this.completer.dropdown.shown && $this._skipSearch(domEvent)) {
return false;
}
}, null, null, 1); // 1 = Priority = Important!
// we actually also need the native event, as the CKEditor one is happening to late
this.$el.on('keyup.' + this.id, $.proxy(this._onKeyup, this));
},
});
$.fn.textcomplete.CKEditor = CKEditor;
}(jQuery);
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) 2015 Jonathan Ong me@jongleberry.com // Copyright (c) 2015 Jonathan Ong me@jongleberry.com

Loading…
Cancel
Save