third: Extract bootstrap typeahead to its own module.

Bootstrap's typeahead is the main part of the project that we've
forked, and moving it to its own module should help unlock our ability
to upgrade bootstrap itself.
This commit is contained in:
Tim Abbott 2018-12-17 09:02:30 -08:00
parent d8c454b097
commit cb9b526f0c
4 changed files with 371 additions and 364 deletions

View File

@ -96,6 +96,11 @@ Copyright: 2012 Twitter, Inc
License: Apache-2.0
Comment: The software has been modified.
Files: static/third/bootstrap-typeahead/*
Copyright: 2012 Twitter, Inc
License: Apache-2.0
Comment: Bootstrap typeahead. The software has been modified.
Files: static/third/bootstrap-notify/*
Copyright: 2013 Nijiko Yonskai
2012 Goodybag, Inc.

View File

@ -5,6 +5,7 @@ import "node_modules/jquery/dist/jquery.js";
import "node_modules/underscore/underscore.js";
import "js/blueslip.js";
import "third/bootstrap/js/bootstrap.js";
import "third/bootstrap-typeahead/typeahead.js";
import "js/common.js";
import "node_modules/moment/min/moment.min.js";
import "node_modules/moment-timezone/builds/moment-timezone-with-data.min.js";

View File

@ -0,0 +1,365 @@
/* =============================================================
* bootstrap-typeahead.js v2.1.0
* http://twitter.github.com/bootstrap/javascript.html#typeahead
* =============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================ */
!function($){
"use strict"; // jshint ;_;
/* TYPEAHEAD PUBLIC CLASS DEFINITION
* ================================= */
var Typeahead = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, $.fn.typeahead.defaults, options)
this.matcher = this.options.matcher || this.matcher
this.sorter = this.options.sorter || this.sorter
this.highlighter = this.options.highlighter || this.highlighter
this.updater = this.options.updater || this.updater
this.$menu = $(this.options.menu).appendTo('body')
this.source = this.options.source
this.shown = false
this.dropup = this.options.dropup
this.fixed = this.options.fixed || false;
if (this.fixed) {
this.$menu.css('position', 'fixed');
}
// The naturalSearch option causes arrow keys to immediately
// update the search box with the underlying values from the
// search suggestions.
this.listen()
}
Typeahead.prototype = {
constructor: Typeahead
, select: function (e) {
var val = this.$menu.find('.active').data('typeahead-value')
this.$element
.val(this.updater(val, e))
.change()
return this.hide()
}
, set_value: function () {
var val = this.$menu.find('.active').data('typeahead-value')
this.$element.val(val)
}
, updater: function (item) {
return item
}
, show: function () {
var pos;
if (this.fixed) {
// Relative to screen instead of to page
pos = this.$element[0].getBoundingClientRect();
} else {
pos = this.$element.offset();
}
pos = $.extend({}, pos, {
height: this.$element[0].offsetHeight
})
// Zulip patch: Workaround for iOS safari problems
pos.top = this.$element.offset().top;
var top_pos = pos.top + pos.height
if (this.dropup) {
top_pos = pos.top - this.$menu.outerHeight()
}
this.$menu.css({
top: top_pos
, left: pos.left
})
this.$menu.show()
this.shown = true
return this
}
, hide: function () {
this.$menu.hide()
this.shown = false
return this
}
, lookup: function (event) {
var items
this.query = this.$element.is("[contenteditable]") ? this.$element.text() : this.$element.val();
if (!this.options.helpOnEmptyStrings) {
if (!this.query || this.query.length < this.options.minLength) {
return this.shown ? this.hide() : this
}
}
items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
if (!items && this.shown) this.hide();
return items ? this.process(items) : this
}
, process: function (items) {
var that = this
items = $.grep(items, function (item) {
return that.matcher(item)
})
items = this.sorter(items)
if (!items.length) {
return this.shown ? this.hide() : this
}
return this.render(items.slice(0, this.options.items)).show()
}
, matcher: function (item) {
return ~item.toLowerCase().indexOf(this.query.toLowerCase())
}
, sorter: function (items) {
var beginswith = []
, caseSensitive = []
, caseInsensitive = []
, item
while (item = items.shift()) {
if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
else if (~item.indexOf(this.query)) caseSensitive.push(item)
else caseInsensitive.push(item)
}
return beginswith.concat(caseSensitive, caseInsensitive)
}
, highlighter: function (item) {
var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
return '<strong>' + match + '</strong>'
})
}
, render: function (items) {
var that = this
items = $(items).map(function (i, item) {
i = $(that.options.item).data('typeahead-value', item)
i.find('a').html(that.highlighter(item))
return i[0]
})
items.first().addClass('active')
this.$menu.html(items)
return this
}
, next: function (event) {
var active = this.$menu.find('.active').removeClass('active')
, next = active.next()
if (!next.length) {
next = $(this.$menu.find('li')[0])
}
next.addClass('active')
if (this.options.naturalSearch) {
this.set_value();
}
}
, prev: function (event) {
var active = this.$menu.find('.active').removeClass('active')
, prev = active.prev()
if (!prev.length) {
prev = this.$menu.find('li').last()
}
prev.addClass('active')
if (this.options.naturalSearch) {
this.set_value();
}
}
, listen: function () {
this.$element
.on('blur', $.proxy(this.blur, this))
.on('keypress', $.proxy(this.keypress, this))
.on('keyup', $.proxy(this.keyup, this))
if (this.eventSupported('keydown')) {
this.$element.on('keydown', $.proxy(this.keydown, this))
}
this.$menu
.on('click', $.proxy(this.click, this))
.on('mouseenter', 'li', $.proxy(this.mouseenter, this))
}
, eventSupported: function(eventName) {
var isSupported = eventName in this.$element
if (!isSupported) {
this.$element.setAttribute(eventName, 'return;')
isSupported = typeof this.$element[eventName] === 'function'
}
return isSupported
}
, move: function (e) {
if (!this.shown) return
switch(e.keyCode) {
case 9: // tab
case 13: // enter
case 27: // escape
e.preventDefault()
break
case 38: // up arrow
e.preventDefault()
this.prev()
break
case 40: // down arrow
e.preventDefault()
this.next()
break
}
if ((this.options.stopAdvance || (e.keyCode != 9 && e.keyCode != 13))
&& $.inArray(e.keyCode, this.options.advanceKeyCodes)) {
e.stopPropagation()
}
}
, keydown: function (e) {
this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27])
this.move(e)
}
, keypress: function (e) {
if (this.suppressKeyPressRepeat) return
this.move(e)
}
, keyup: function (e) {
switch(e.keyCode) {
case 40: // down arrow
case 38: // up arrow
break
case 9: // tab
case 13: // enter
if (!this.shown) return
this.select(e)
break
case 27: // escape
if (!this.shown) return
this.hide()
break
default:
this.lookup()
}
if ((this.options.stopAdvance || (e.keyCode != 9 && e.keyCode != 13))
&& $.inArray(e.keyCode, this.options.advanceKeyCodes)) {
e.stopPropagation()
}
e.preventDefault()
}
, blur: function (e) {
var that = this
setTimeout(function () {
if (!that.$menu.is(':hover')) {
that.hide();
}
}, 150)
}
, click: function (e) {
e.stopPropagation()
e.preventDefault()
this.select(e)
}
, mouseenter: function (e) {
this.$menu.find('.active').removeClass('active')
$(e.currentTarget).addClass('active')
}
}
/* TYPEAHEAD PLUGIN DEFINITION
* =========================== */
$.fn.typeahead = function (option) {
return this.each(function () {
var $this = $(this)
, data = $this.data('typeahead')
, options = typeof option == 'object' && option
if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.typeahead.defaults = {
source: []
, items: 8
, menu: '<ul class="typeahead dropdown-menu"></ul>'
, item: '<li><a href="#"></a></li>'
, minLength: 1
, stopAdvance: false
, dropup: false
, advanceKeyCodes: []
}
$.fn.typeahead.Constructor = Typeahead
/* TYPEAHEAD DATA-API
* ================== */
$(function () {
$('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
var $this = $(this)
if ($this.data('typeahead')) return
e.preventDefault()
$this.typeahead($this.data())
})
})
}(window.jQuery);

View File

@ -1789,370 +1789,6 @@
})
})
}(window.jQuery);/* =============================================================
* bootstrap-typeahead.js v2.1.0
* http://twitter.github.com/bootstrap/javascript.html#typeahead
* =============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================ */
!function($){
"use strict"; // jshint ;_;
/* TYPEAHEAD PUBLIC CLASS DEFINITION
* ================================= */
var Typeahead = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, $.fn.typeahead.defaults, options)
this.matcher = this.options.matcher || this.matcher
this.sorter = this.options.sorter || this.sorter
this.highlighter = this.options.highlighter || this.highlighter
this.updater = this.options.updater || this.updater
this.$menu = $(this.options.menu).appendTo('body')
this.source = this.options.source
this.shown = false
this.dropup = this.options.dropup
this.fixed = this.options.fixed || false;
if (this.fixed) {
this.$menu.css('position', 'fixed');
}
// The naturalSearch option causes arrow keys to immediately
// update the search box with the underlying values from the
// search suggestions.
this.listen()
}
Typeahead.prototype = {
constructor: Typeahead
, select: function (e) {
var val = this.$menu.find('.active').data('typeahead-value')
this.$element
.val(this.updater(val, e))
.change()
return this.hide()
}
, set_value: function () {
var val = this.$menu.find('.active').data('typeahead-value')
this.$element.val(val)
}
, updater: function (item) {
return item
}
, show: function () {
var pos;
if (this.fixed) {
// Relative to screen instead of to page
pos = this.$element[0].getBoundingClientRect();
} else {
pos = this.$element.offset();
}
pos = $.extend({}, pos, {
height: this.$element[0].offsetHeight
})
// Zulip patch: Workaround for iOS safari problems
pos.top = this.$element.offset().top;
var top_pos = pos.top + pos.height
if (this.dropup) {
top_pos = pos.top - this.$menu.outerHeight()
}
this.$menu.css({
top: top_pos
, left: pos.left
})
this.$menu.show()
this.shown = true
return this
}
, hide: function () {
this.$menu.hide()
this.shown = false
return this
}
, lookup: function (event) {
var items
this.query = this.$element.is("[contenteditable]") ? this.$element.text() : this.$element.val();
if (!this.options.helpOnEmptyStrings) {
if (!this.query || this.query.length < this.options.minLength) {
return this.shown ? this.hide() : this
}
}
items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
if (!items && this.shown) this.hide();
return items ? this.process(items) : this
}
, process: function (items) {
var that = this
items = $.grep(items, function (item) {
return that.matcher(item)
})
items = this.sorter(items)
if (!items.length) {
return this.shown ? this.hide() : this
}
return this.render(items.slice(0, this.options.items)).show()
}
, matcher: function (item) {
return ~item.toLowerCase().indexOf(this.query.toLowerCase())
}
, sorter: function (items) {
var beginswith = []
, caseSensitive = []
, caseInsensitive = []
, item
while (item = items.shift()) {
if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
else if (~item.indexOf(this.query)) caseSensitive.push(item)
else caseInsensitive.push(item)
}
return beginswith.concat(caseSensitive, caseInsensitive)
}
, highlighter: function (item) {
var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
return '<strong>' + match + '</strong>'
})
}
, render: function (items) {
var that = this
items = $(items).map(function (i, item) {
i = $(that.options.item).data('typeahead-value', item)
i.find('a').html(that.highlighter(item))
return i[0]
})
items.first().addClass('active')
this.$menu.html(items)
return this
}
, next: function (event) {
var active = this.$menu.find('.active').removeClass('active')
, next = active.next()
if (!next.length) {
next = $(this.$menu.find('li')[0])
}
next.addClass('active')
if (this.options.naturalSearch) {
this.set_value();
}
}
, prev: function (event) {
var active = this.$menu.find('.active').removeClass('active')
, prev = active.prev()
if (!prev.length) {
prev = this.$menu.find('li').last()
}
prev.addClass('active')
if (this.options.naturalSearch) {
this.set_value();
}
}
, listen: function () {
this.$element
.on('blur', $.proxy(this.blur, this))
.on('keypress', $.proxy(this.keypress, this))
.on('keyup', $.proxy(this.keyup, this))
if (this.eventSupported('keydown')) {
this.$element.on('keydown', $.proxy(this.keydown, this))
}
this.$menu
.on('click', $.proxy(this.click, this))
.on('mouseenter', 'li', $.proxy(this.mouseenter, this))
}
, eventSupported: function(eventName) {
var isSupported = eventName in this.$element
if (!isSupported) {
this.$element.setAttribute(eventName, 'return;')
isSupported = typeof this.$element[eventName] === 'function'
}
return isSupported
}
, move: function (e) {
if (!this.shown) return
switch(e.keyCode) {
case 9: // tab
case 13: // enter
case 27: // escape
e.preventDefault()
break
case 38: // up arrow
e.preventDefault()
this.prev()
break
case 40: // down arrow
e.preventDefault()
this.next()
break
}
if ((this.options.stopAdvance || (e.keyCode != 9 && e.keyCode != 13))
&& $.inArray(e.keyCode, this.options.advanceKeyCodes)) {
e.stopPropagation()
}
}
, keydown: function (e) {
this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27])
this.move(e)
}
, keypress: function (e) {
if (this.suppressKeyPressRepeat) return
this.move(e)
}
, keyup: function (e) {
switch(e.keyCode) {
case 40: // down arrow
case 38: // up arrow
break
case 9: // tab
case 13: // enter
if (!this.shown) return
this.select(e)
break
case 27: // escape
if (!this.shown) return
this.hide()
break
default:
this.lookup()
}
if ((this.options.stopAdvance || (e.keyCode != 9 && e.keyCode != 13))
&& $.inArray(e.keyCode, this.options.advanceKeyCodes)) {
e.stopPropagation()
}
e.preventDefault()
}
, blur: function (e) {
var that = this
setTimeout(function () {
if (!that.$menu.is(':hover')) {
that.hide();
}
}, 150)
}
, click: function (e) {
e.stopPropagation()
e.preventDefault()
this.select(e)
}
, mouseenter: function (e) {
this.$menu.find('.active').removeClass('active')
$(e.currentTarget).addClass('active')
}
}
/* TYPEAHEAD PLUGIN DEFINITION
* =========================== */
$.fn.typeahead = function (option) {
return this.each(function () {
var $this = $(this)
, data = $this.data('typeahead')
, options = typeof option == 'object' && option
if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.typeahead.defaults = {
source: []
, items: 8
, menu: '<ul class="typeahead dropdown-menu"></ul>'
, item: '<li><a href="#"></a></li>'
, minLength: 1
, stopAdvance: false
, dropup: false
, advanceKeyCodes: []
}
$.fn.typeahead.Constructor = Typeahead
/* TYPEAHEAD DATA-API
* ================== */
$(function () {
$('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
var $this = $(this)
if ($this.data('typeahead')) return
e.preventDefault()
$this.typeahead($this.data())
})
})
}(window.jQuery);
/* ==========================================================
* bootstrap-affix.js v2.1.0