diff --git a/cps/db.py b/cps/db.py index ab4f5bf5..d8709335 100644 --- a/cps/db.py +++ b/cps/db.py @@ -737,6 +737,7 @@ class CalibreDB(): self.session.connection().connection.connection.create_function("lower", 1, lcase) entries = self.session.query(database).filter(tag_filter). \ filter(func.lower(database.name).ilike("%" + query + "%")).all() + # json_dumps = json.dumps([dict(name=escape(r.name.replace(*replace))) for r in entries]) json_dumps = json.dumps([dict(name=r.name.replace(*replace)) for r in entries]) return json_dumps diff --git a/cps/static/css/libs/typeahead.css b/cps/static/css/libs/typeahead.css index fde250d6..fcc17a5b 100644 --- a/cps/static/css/libs/typeahead.css +++ b/cps/static/css/libs/typeahead.css @@ -145,7 +145,7 @@ fieldset[disabled] .twitter-typeahead .tt-input { cursor: not-allowed; background-color: #eeeeee !important; } -.tt-dropdown-menu { +.tt-menu { position: absolute; top: 100%; left: 0; @@ -166,7 +166,7 @@ fieldset[disabled] .twitter-typeahead .tt-input { *border-right-width: 2px; *border-bottom-width: 2px; } -.tt-dropdown-menu .tt-suggestion { +.tt-menu .tt-suggestion { display: block; padding: 3px 20px; clear: both; @@ -175,15 +175,15 @@ fieldset[disabled] .twitter-typeahead .tt-input { color: #333333; white-space: nowrap; } -.tt-dropdown-menu .tt-suggestion.tt-cursor { +.tt-menu .tt-suggestion.tt-cursor { text-decoration: none; outline: 0; background-color: #f5f5f5; color: #262626; } -.tt-dropdown-menu .tt-suggestion.tt-cursor a { +.tt-menu .tt-suggestion.tt-cursor a { color: #262626; } -.tt-dropdown-menu .tt-suggestion p { +.tt-menu .tt-suggestion p { margin: 0; } diff --git a/cps/static/js/edit_books.js b/cps/static/js/edit_books.js index 66dd4eea..88057162 100644 --- a/cps/static/js/edit_books.js +++ b/cps/static/js/edit_books.js @@ -46,90 +46,20 @@ $(".datepicker_delete").click(function() { Takes a prefix, query typeahead callback, Bloodhound typeahead adapter and returns the completions it gets from the bloodhound engine prefixed. */ -function prefixedSource(prefix, query, cb, bhAdapter) { - bhAdapter(query, function(retArray) { +function prefixedSource(prefix, query, cb, source) { + function async(retArray) { + retArray = retArray || []; var matches = []; for (var i = 0; i < retArray.length; i++) { var obj = {name : prefix + retArray[i].name}; matches.push(obj); } cb(matches); - }); + } + source.search(query, cb, async); } -var authors = new Bloodhound({ - name: "authors", - datumTokenizer: function datumTokenizer(datum) { - return [datum.name]; - }, - queryTokenizer: Bloodhound.tokenizers.whitespace, - remote: { - url: getPath() + "/get_authors_json?q=%QUERY" - } -}); - -var series = new Bloodhound({ - name: "series", - datumTokenizer: function datumTokenizer(datum) { - return [datum.name]; - }, - queryTokenizer: function queryTokenizer(query) { - return [query]; - }, - remote: { - url: getPath() + "/get_series_json?q=", - replace: function replace(url, query) { - return url + encodeURIComponent(query); - } - } -}); - - -var tags = new Bloodhound({ - name: "tags", - datumTokenizer: function datumTokenizer(datum) { - return [datum.name]; - }, - queryTokenizer: function queryTokenizer(query) { - var tokens = query.split(","); - tokens = [tokens[tokens.length - 1].trim()]; - return tokens; - }, - remote: { - url: getPath() + "/get_tags_json?q=%QUERY" - } -}); - -var languages = new Bloodhound({ - name: "languages", - datumTokenizer: function datumTokenizer(datum) { - return [datum.name]; - }, - queryTokenizer: function queryTokenizer(query) { - return [query]; - }, - remote: { - url: getPath() + "/get_languages_json?q=", - replace: function replace(url, query) { - return url + encodeURIComponent(query); - } - } -}); - -var publishers = new Bloodhound({ - name: "publisher", - datumTokenizer: function datumTokenizer(datum) { - return [datum.name]; - }, - queryTokenizer: Bloodhound.tokenizers.whitespace, - remote: { - url: getPath() + "/get_publishers_json?q=%QUERY" - } -}); - function sourceSplit(query, cb, split, source) { - var bhAdapter = source.ttAdapter(); - var tokens = query.split(split); var currentSource = tokens[tokens.length - 1].trim(); @@ -144,84 +74,148 @@ function sourceSplit(query, cb, split, source) { for (var i = 0; i < tokens.length; i++) { prefix += tokens[i].trim() + newSplit; } - prefixedSource(prefix, currentSource, cb, bhAdapter); + prefixedSource(prefix, currentSource, cb, source); } -var promiseAuthors = authors.initialize(); -promiseAuthors.done(function() { - $("#bookAuthor").typeahead( - { - highlight: true, minLength: 1, - hint: true - }, { - name: "authors", - displayKey: "name", - source: function source(query, cb) { - return sourceSplit(query, cb, "&", authors); //sourceSplit //("&") - } - } - ); +var authors = new Bloodhound({ + name: "authors", + identify: function(obj) { return obj.name; }, + datumTokenizer: function datumTokenizer(datum) { + return [datum.name]; + }, + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: getPath() + "/get_authors_json?q=%QUERY", + wildcard: '%QUERY', + }, }); -var promiseSeries = series.initialize(); -promiseSeries.done(function() { - $("#series").typeahead( - { - highlight: true, minLength: 0, - hint: true - }, { - name: "series", - displayKey: "name", - source: series.ttAdapter() +$(".form-group #bookAuthor").typeahead( + { + highlight: true, + minLength: 1, + hint: true + }, { + name: "authors", + display: 'name', + source: function source(query, cb, asyncResults) { + return sourceSplit(query, cb, "&", authors); } - ); + } +); + + +var series = new Bloodhound({ + name: "series", + datumTokenizer: function datumTokenizer(datum) { + return [datum.name]; + }, + // queryTokenizer: Bloodhound.tokenizers.whitespace, + queryTokenizer: function queryTokenizer(query) { + return [query]; + }, + remote: { + url: getPath() + "/get_series_json?q=%QUERY", + wildcard: '%QUERY', + /*replace: function replace(url, query) { + return url + encodeURIComponent(query); + }*/ + } +}); +$(".form-group #series").typeahead( + { + highlight: true, + minLength: 0, + hint: true + }, { + name: "series", + displayKey: "name", + source: series + } +); + +var tags = new Bloodhound({ + name: "tags", + datumTokenizer: function datumTokenizer(datum) { + return [datum.name]; + }, + queryTokenizer: function queryTokenizer(query) { + var tokens = query.split(","); + tokens = [tokens[tokens.length - 1].trim()]; + return tokens; + }, + remote: { + url: getPath() + "/get_tags_json?q=%QUERY", + wildcard: '%QUERY' + } }); -var promiseTags = tags.initialize(); -promiseTags.done(function() { - $("#tags").typeahead( - { - highlight: true, minLength: 0, - hint: true - }, { - name: "tags", - displayKey: "name", - source: function source(query, cb) { - return sourceSplit(query, cb, ",", tags); - } +$(".form-group #tags").typeahead( + { + highlight: true, + minLength: 0, + hint: true + }, { + name: "tags", + display: "name", + source: function source(query, cb, asyncResults) { + return sourceSplit(query, cb, ",", tags); } - ); + } +); + +var languages = new Bloodhound({ + name: "languages", + datumTokenizer: function datumTokenizer(datum) { + return [datum.name]; + }, + queryTokenizer: function queryTokenizer(query) { + return [query]; + }, + remote: { + url: getPath() + "/get_languages_json?q=%QUERY", + wildcard: '%QUERY' + /*replace: function replace(url, query) { + return url + encodeURIComponent(query); + }*/ + } }); -var promiseLanguages = languages.initialize(); -promiseLanguages.done(function() { - $("#languages").typeahead( - { - highlight: true, minLength: 0, - hint: true - }, { - name: "languages", - displayKey: "name", - source: function source(query, cb) { - return sourceSplit(query, cb, ",", languages); //(",") - } +$(".form-group #languages").typeahead( + { + highlight: true, minLength: 0, + hint: true + }, { + name: "languages", + display: "name", + source: function source(query, cb, asyncResults) { + return sourceSplit(query, cb, ",", languages); } - ); + } +); + +var publishers = new Bloodhound({ + name: "publisher", + datumTokenizer: function datumTokenizer(datum) { + return [datum.name]; + }, + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: getPath() + "/get_publishers_json?q=%QUERY", + wildcard: '%QUERY' + } }); -var promisePublishers = publishers.initialize(); -promisePublishers.done(function() { - $("#publisher").typeahead( - { - highlight: true, minLength: 0, - hint: true - }, { - name: "publishers", - displayKey: "name", - source: publishers.ttAdapter() - } - ); -}); +$(".form-group #publisher").typeahead( + { + highlight: true, minLength: 0, + hint: true + }, { + name: "publishers", + displayKey: "name", + source: publishers + } +); $("#search").on("change input.typeahead:selected", function(event) { if (event.target.type === "search" && event.target.tagName === "INPUT") { diff --git a/cps/static/js/libs/typeahead.bundle.js b/cps/static/js/libs/typeahead.bundle.js index 8164a3c2..01a87bff 100644 --- a/cps/static/js/libs/typeahead.bundle.js +++ b/cps/static/js/libs/typeahead.bundle.js @@ -1,10 +1,21 @@ /*! - * typeahead.js 0.10.5 - * https://github.com/twitter/typeahead.js - * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT + * typeahead.js 1.3.1 + * https://github.com/corejavascript/typeahead.js + * Copyright 2013-2020 Twitter, Inc. and other contributors; Licensed MIT */ -(function($) { + +(function(root, factory) { + if (typeof define === "function" && define.amd) { + define([ "jquery" ], function(a0) { + return root["Bloodhound"] = factory(a0); + }); + } else if (typeof module === "object" && module.exports) { + module.exports = factory(require("jquery")); + } else { + root["Bloodhound"] = factory(root["jQuery"]); + } +})(this, function($) { var _ = function() { "use strict"; return { @@ -29,6 +40,12 @@ isUndefined: function(obj) { return typeof obj === "undefined"; }, + isElement: function(obj) { + return !!(obj && obj.nodeType === 1); + }, + isJQuery: function(obj) { + return obj instanceof $; + }, toStr: function toStr(s) { return _.isUndefined(s) || s === null ? "" : s + ""; }, @@ -66,12 +83,18 @@ return !!result; }, mixin: $.extend, - getUniqueId: function() { + identity: function(x) { + return x; + }, + clone: function(obj) { + return $.extend(true, {}, obj); + }, + getIdGenerator: function() { var counter = 0; return function() { return counter++; }; - }(), + }, templatify: function templatify(obj) { return $.isFunction(obj) ? obj : template; function template() { @@ -123,18 +146,30 @@ return result; }; }, + stringify: function(val) { + return _.isString(val) ? val : JSON.stringify(val); + }, + guid: function() { + function _p8(s) { + var p = (Math.random().toString(16) + "000000000").substr(2, 8); + return s ? "-" + p.substr(0, 4) + "-" + p.substr(4, 4) : p; + } + return "tt-" + _p8() + _p8(true) + _p8(true) + _p8(); + }, noop: function() {} }; }(); - var VERSION = "0.10.5"; + var VERSION = "1.3.1"; var tokenizers = function() { "use strict"; return { nonword: nonword, whitespace: whitespace, + ngram: ngram, obj: { nonword: getObjTokenizer(nonword), - whitespace: getObjTokenizer(whitespace) + whitespace: getObjTokenizer(whitespace), + ngram: getObjTokenizer(ngram) } }; function whitespace(str) { @@ -145,12 +180,25 @@ str = _.toStr(str); return str ? str.split(/\W+/) : []; } + function ngram(str) { + str = _.toStr(str); + var tokens = [], word = ""; + _.each(str.split(""), function(char) { + if (char.match(/\s+/)) { + word = ""; + } else { + tokens.push(word + char); + word += char; + } + }); + return tokens; + } function getObjTokenizer(tokenizer) { - return function setKey() { - var args = [].slice.call(arguments, 0); + return function setKey(keys) { + keys = _.isArray(keys) ? keys : [].slice.call(arguments, 0); return function tokenize(o) { var tokens = []; - _.each(args, function(k) { + _.each(keys, function(k) { tokens = tokens.concat(tokenizer(_.toStr(o[k]))); }); return tokens; @@ -173,6 +221,7 @@ if (this.size >= this.maxSize) { this.list.remove(tailItem); delete this.hash[tailItem.key]; + this.size--; } if (node = this.hash[key]) { node.val = val; @@ -227,73 +276,72 @@ }(); var PersistentStorage = function() { "use strict"; - var ls, methods; + var LOCAL_STORAGE; try { - ls = window.localStorage; - ls.setItem("~~~", "!"); - ls.removeItem("~~~"); + LOCAL_STORAGE = window.localStorage; + LOCAL_STORAGE.setItem("~~~", "!"); + LOCAL_STORAGE.removeItem("~~~"); } catch (err) { - ls = null; + LOCAL_STORAGE = null; } - function PersistentStorage(namespace) { + function PersistentStorage(namespace, override) { this.prefix = [ "__", namespace, "__" ].join(""); this.ttlKey = "__ttl__"; this.keyMatcher = new RegExp("^" + _.escapeRegExChars(this.prefix)); + this.ls = override || LOCAL_STORAGE; + !this.ls && this._noop(); } - if (ls && window.JSON) { - methods = { - _prefix: function(key) { - return this.prefix + key; - }, - _ttlKey: function(key) { - return this._prefix(key) + this.ttlKey; - }, - get: function(key) { - if (this.isExpired(key)) { - this.remove(key); + _.mixin(PersistentStorage.prototype, { + _prefix: function(key) { + return this.prefix + key; + }, + _ttlKey: function(key) { + return this._prefix(key) + this.ttlKey; + }, + _noop: function() { + this.get = this.set = this.remove = this.clear = this.isExpired = _.noop; + }, + _safeSet: function(key, val) { + try { + this.ls.setItem(key, val); + } catch (err) { + if (err.name === "QuotaExceededError") { + this.clear(); + this._noop(); } - return decode(ls.getItem(this._prefix(key))); - }, - set: function(key, val, ttl) { - if (_.isNumber(ttl)) { - ls.setItem(this._ttlKey(key), encode(now() + ttl)); - } else { - ls.removeItem(this._ttlKey(key)); - } - return ls.setItem(this._prefix(key), encode(val)); - }, - remove: function(key) { - ls.removeItem(this._ttlKey(key)); - ls.removeItem(this._prefix(key)); - return this; - }, - clear: function() { - var i, key, keys = [], len = ls.length; - for (i = 0; i < len; i++) { - if ((key = ls.key(i)).match(this.keyMatcher)) { - keys.push(key.replace(this.keyMatcher, "")); - } - } - for (i = keys.length; i--; ) { - this.remove(keys[i]); - } - return this; - }, - isExpired: function(key) { - var ttl = decode(ls.getItem(this._ttlKey(key))); - return _.isNumber(ttl) && now() > ttl ? true : false; } - }; - } else { - methods = { - get: _.noop, - set: _.noop, - remove: _.noop, - clear: _.noop, - isExpired: _.noop - }; - } - _.mixin(PersistentStorage.prototype, methods); + }, + get: function(key) { + if (this.isExpired(key)) { + this.remove(key); + } + return decode(this.ls.getItem(this._prefix(key))); + }, + set: function(key, val, ttl) { + if (_.isNumber(ttl)) { + this._safeSet(this._ttlKey(key), encode(now() + ttl)); + } else { + this.ls.removeItem(this._ttlKey(key)); + } + return this._safeSet(this._prefix(key), encode(val)); + }, + remove: function(key) { + this.ls.removeItem(this._ttlKey(key)); + this.ls.removeItem(this._prefix(key)); + return this; + }, + clear: function() { + var i, keys = gatherMatchingKeys(this.keyMatcher); + for (i = keys.length; i--; ) { + this.remove(keys[i]); + } + return this; + }, + isExpired: function(key) { + var ttl = decode(this.ls.getItem(this._ttlKey(key))); + return _.isNumber(ttl) && now() > ttl ? true : false; + } + }); return PersistentStorage; function now() { return new Date().getTime(); @@ -302,105 +350,104 @@ return JSON.stringify(_.isUndefined(val) ? null : val); } function decode(val) { - return JSON.parse(val); + return $.parseJSON(val); + } + function gatherMatchingKeys(keyMatcher) { + var i, key, keys = [], len = LOCAL_STORAGE.length; + for (i = 0; i < len; i++) { + if ((key = LOCAL_STORAGE.key(i)).match(keyMatcher)) { + keys.push(key.replace(keyMatcher, "")); + } + } + return keys; } }(); var Transport = function() { "use strict"; - var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, sharedCache = new LruCache(10); + var pendingRequestsCount = 0, pendingRequests = {}, sharedCache = new LruCache(10); function Transport(o) { o = o || {}; + this.maxPendingRequests = o.maxPendingRequests || 6; this.cancelled = false; - this.lastUrl = null; - this._send = o.transport ? callbackToDeferred(o.transport) : $.ajax; - this._get = o.rateLimiter ? o.rateLimiter(this._get) : this._get; + this.lastReq = null; + this._send = o.transport; + this._get = o.limiter ? o.limiter(this._get) : this._get; this._cache = o.cache === false ? new LruCache(0) : sharedCache; } Transport.setMaxPendingRequests = function setMaxPendingRequests(num) { - maxPendingRequests = num; + this.maxPendingRequests = num; }; Transport.resetCache = function resetCache() { sharedCache.reset(); }; _.mixin(Transport.prototype, { - _get: function(url, o, cb) { - var that = this, jqXhr; - if (this.cancelled || url !== this.lastUrl) { + _fingerprint: function fingerprint(o) { + o = o || {}; + return o.url + o.type + $.param(o.data || {}); + }, + _get: function(o, cb) { + var that = this, fingerprint, jqXhr; + fingerprint = this._fingerprint(o); + if (this.cancelled || fingerprint !== this.lastReq) { return; } - if (jqXhr = pendingRequests[url]) { + if (jqXhr = pendingRequests[fingerprint]) { jqXhr.done(done).fail(fail); - } else if (pendingRequestsCount < maxPendingRequests) { + } else if (pendingRequestsCount < this.maxPendingRequests) { pendingRequestsCount++; - pendingRequests[url] = this._send(url, o).done(done).fail(fail).always(always); + pendingRequests[fingerprint] = this._send(o).done(done).fail(fail).always(always); } else { this.onDeckRequestArgs = [].slice.call(arguments, 0); } function done(resp) { - cb && cb(null, resp); - that._cache.set(url, resp); + cb(null, resp); + that._cache.set(fingerprint, resp); } function fail() { - cb && cb(true); + cb(true); } function always() { pendingRequestsCount--; - delete pendingRequests[url]; + delete pendingRequests[fingerprint]; if (that.onDeckRequestArgs) { that._get.apply(that, that.onDeckRequestArgs); that.onDeckRequestArgs = null; } } }, - get: function(url, o, cb) { - var resp; - if (_.isFunction(o)) { - cb = o; - o = {}; - } + get: function(o, cb) { + var resp, fingerprint; + cb = cb || $.noop; + o = _.isString(o) ? { + url: o + } : o || {}; + fingerprint = this._fingerprint(o); this.cancelled = false; - this.lastUrl = url; - if (resp = this._cache.get(url)) { - _.defer(function() { - cb && cb(null, resp); - }); + this.lastReq = fingerprint; + if (resp = this._cache.get(fingerprint)) { + cb(null, resp); } else { - this._get(url, o, cb); + this._get(o, cb); } - return !!resp; }, cancel: function() { this.cancelled = true; } }); return Transport; - function callbackToDeferred(fn) { - return function customSendWrapper(url, o) { - var deferred = $.Deferred(); - fn(url, o, onSuccess, onError); - return deferred; - function onSuccess(resp) { - _.defer(function() { - deferred.resolve(resp); - }); - } - function onError(err) { - _.defer(function() { - deferred.reject(err); - }); - } - }; - } }(); - var SearchIndex = function() { + var SearchIndex = window.SearchIndex = function() { "use strict"; + var CHILDREN = "c", IDS = "i"; function SearchIndex(o) { o = o || {}; if (!o.datumTokenizer || !o.queryTokenizer) { $.error("datumTokenizer and queryTokenizer are both required"); } + this.identify = o.identify || _.stringify; this.datumTokenizer = o.datumTokenizer; this.queryTokenizer = o.queryTokenizer; + this.matchAnyQueryToken = o.matchAnyQueryToken; this.reset(); } _.mixin(SearchIndex.prototype, { @@ -413,46 +460,61 @@ data = _.isArray(data) ? data : [ data ]; _.each(data, function(datum) { var id, tokens; - id = that.datums.push(datum) - 1; + that.datums[id = that.identify(datum)] = datum; tokens = normalizeTokens(that.datumTokenizer(datum)); _.each(tokens, function(token) { var node, chars, ch; node = that.trie; chars = token.split(""); while (ch = chars.shift()) { - node = node.children[ch] || (node.children[ch] = newNode()); - node.ids.push(id); + node = node[CHILDREN][ch] || (node[CHILDREN][ch] = newNode()); + node[IDS].push(id); } }); }); }, - get: function get(query) { + get: function get(ids) { + var that = this; + return _.map(ids, function(id) { + return that.datums[id]; + }); + }, + search: function search(query) { var that = this, tokens, matches; tokens = normalizeTokens(this.queryTokenizer(query)); _.each(tokens, function(token) { var node, chars, ch, ids; - if (matches && matches.length === 0) { + if (matches && matches.length === 0 && !that.matchAnyQueryToken) { return false; } node = that.trie; chars = token.split(""); while (node && (ch = chars.shift())) { - node = node.children[ch]; + node = node[CHILDREN][ch]; } if (node && chars.length === 0) { - ids = node.ids.slice(0); + ids = node[IDS].slice(0); matches = matches ? getIntersection(matches, ids) : ids; } else { - matches = []; - return false; + if (!that.matchAnyQueryToken) { + matches = []; + return false; + } } }); return matches ? _.map(unique(matches), function(id) { return that.datums[id]; }) : []; }, + all: function all() { + var values = []; + for (var key in this.datums) { + values.push(this.datums[key]); + } + return values; + }, reset: function reset() { - this.datums = []; + this.datums = {}; this.trie = newNode(); }, serialize: function serialize() { @@ -473,10 +535,10 @@ return tokens; } function newNode() { - return { - ids: [], - children: {} - }; + var node = {}; + node[IDS] = []; + node[CHILDREN] = {}; + return node; } function unique(array) { var seen = {}, uniques = []; @@ -490,8 +552,8 @@ } function getIntersection(arrayA, arrayB) { var ai = 0, bi = 0, intersection = []; - arrayA = arrayA.sort(compare); - arrayB = arrayB.sort(compare); + arrayA = arrayA.sort(); + arrayB = arrayB.sort(); var lenArrayA = arrayA.length, lenArrayB = arrayB.length; while (ai < lenArrayA && bi < lenArrayB) { if (arrayA[ai] < arrayB[bi]) { @@ -505,169 +567,331 @@ } } return intersection; - function compare(a, b) { - return a - b; - } } }(); - var oParser = function() { + var Prefetch = function() { "use strict"; - return { - local: getLocal, - prefetch: getPrefetch, - remote: getRemote - }; - function getLocal(o) { - return o.local || null; - } - function getPrefetch(o) { - var prefetch, defaults; - defaults = { - url: null, - thumbprint: "", - ttl: 24 * 60 * 60 * 1e3, - filter: null, - ajax: {} - }; - if (prefetch = o.prefetch || null) { - prefetch = _.isString(prefetch) ? { - url: prefetch - } : prefetch; - prefetch = _.mixin(defaults, prefetch); - prefetch.thumbprint = VERSION + prefetch.thumbprint; - prefetch.ajax.type = prefetch.ajax.type || "GET"; - prefetch.ajax.dataType = prefetch.ajax.dataType || "json"; - !prefetch.url && $.error("prefetch requires url to be set"); - } - return prefetch; - } - function getRemote(o) { - var remote, defaults; - defaults = { - url: null, - cache: true, - wildcard: "%QUERY", - replace: null, - rateLimitBy: "debounce", - rateLimitWait: 300, - send: null, - filter: null, - ajax: {} - }; - if (remote = o.remote || null) { - remote = _.isString(remote) ? { - url: remote - } : remote; - remote = _.mixin(defaults, remote); - remote.rateLimiter = /^throttle$/i.test(remote.rateLimitBy) ? byThrottle(remote.rateLimitWait) : byDebounce(remote.rateLimitWait); - remote.ajax.type = remote.ajax.type || "GET"; - remote.ajax.dataType = remote.ajax.dataType || "json"; - delete remote.rateLimitBy; - delete remote.rateLimitWait; - !remote.url && $.error("remote requires url to be set"); - } - return remote; - function byDebounce(wait) { - return function(fn) { - return _.debounce(fn, wait); - }; - } - function byThrottle(wait) { - return function(fn) { - return _.throttle(fn, wait); - }; - } - } - }(); - (function(root) { - "use strict"; - var old, keys; - old = root.Bloodhound; + var keys; keys = { data: "data", protocol: "protocol", thumbprint: "thumbprint" }; - root.Bloodhound = Bloodhound; - function Bloodhound(o) { - if (!o || !o.local && !o.prefetch && !o.remote) { - $.error("one of local, prefetch, or remote is required"); + function Prefetch(o) { + this.url = o.url; + this.ttl = o.ttl; + this.cache = o.cache; + this.prepare = o.prepare; + this.transform = o.transform; + this.transport = o.transport; + this.thumbprint = o.thumbprint; + this.storage = new PersistentStorage(o.cacheKey); + } + _.mixin(Prefetch.prototype, { + _settings: function settings() { + return { + url: this.url, + type: "GET", + dataType: "json" + }; + }, + store: function store(data) { + if (!this.cache) { + return; + } + this.storage.set(keys.data, data, this.ttl); + this.storage.set(keys.protocol, location.protocol, this.ttl); + this.storage.set(keys.thumbprint, this.thumbprint, this.ttl); + }, + fromCache: function fromCache() { + var stored = {}, isExpired; + if (!this.cache) { + return null; + } + stored.data = this.storage.get(keys.data); + stored.protocol = this.storage.get(keys.protocol); + stored.thumbprint = this.storage.get(keys.thumbprint); + isExpired = stored.thumbprint !== this.thumbprint || stored.protocol !== location.protocol; + return stored.data && !isExpired ? stored.data : null; + }, + fromNetwork: function(cb) { + var that = this, settings; + if (!cb) { + return; + } + settings = this.prepare(this._settings()); + this.transport(settings).fail(onError).done(onResponse); + function onError() { + cb(true); + } + function onResponse(resp) { + cb(null, that.transform(resp)); + } + }, + clear: function clear() { + this.storage.clear(); + return this; } - this.limit = o.limit || 5; - this.sorter = getSorter(o.sorter); - this.dupDetector = o.dupDetector || ignoreDuplicates; - this.local = oParser.local(o); - this.prefetch = oParser.prefetch(o); - this.remote = oParser.remote(o); - this.cacheKey = this.prefetch ? this.prefetch.cacheKey || this.prefetch.url : null; + }); + return Prefetch; + }(); + var Remote = function() { + "use strict"; + function Remote(o) { + this.url = o.url; + this.prepare = o.prepare; + this.transform = o.transform; + this.indexResponse = o.indexResponse; + this.transport = new Transport({ + cache: o.cache, + limiter: o.limiter, + transport: o.transport, + maxPendingRequests: o.maxPendingRequests + }); + } + _.mixin(Remote.prototype, { + _settings: function settings() { + return { + url: this.url, + type: "GET", + dataType: "json" + }; + }, + get: function get(query, cb) { + var that = this, settings; + if (!cb) { + return; + } + query = query || ""; + settings = this.prepare(query, this._settings()); + return this.transport.get(settings, onResponse); + function onResponse(err, resp) { + err ? cb([]) : cb(that.transform(resp)); + } + }, + cancelLastRequest: function cancelLastRequest() { + this.transport.cancel(); + } + }); + return Remote; + }(); + var oParser = function() { + "use strict"; + return function parse(o) { + var defaults, sorter; + defaults = { + initialize: true, + identify: _.stringify, + datumTokenizer: null, + queryTokenizer: null, + matchAnyQueryToken: false, + sufficient: 5, + indexRemote: false, + sorter: null, + local: [], + prefetch: null, + remote: null + }; + o = _.mixin(defaults, o || {}); + !o.datumTokenizer && $.error("datumTokenizer is required"); + !o.queryTokenizer && $.error("queryTokenizer is required"); + sorter = o.sorter; + o.sorter = sorter ? function(x) { + return x.sort(sorter); + } : _.identity; + o.local = _.isFunction(o.local) ? o.local() : o.local; + o.prefetch = parsePrefetch(o.prefetch); + o.remote = parseRemote(o.remote); + return o; + }; + function parsePrefetch(o) { + var defaults; + if (!o) { + return null; + } + defaults = { + url: null, + ttl: 24 * 60 * 60 * 1e3, + cache: true, + cacheKey: null, + thumbprint: "", + prepare: _.identity, + transform: _.identity, + transport: null + }; + o = _.isString(o) ? { + url: o + } : o; + o = _.mixin(defaults, o); + !o.url && $.error("prefetch requires url to be set"); + o.transform = o.filter || o.transform; + o.cacheKey = o.cacheKey || o.url; + o.thumbprint = VERSION + o.thumbprint; + o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax; + return o; + } + function parseRemote(o) { + var defaults; + if (!o) { + return; + } + defaults = { + url: null, + cache: true, + prepare: null, + replace: null, + wildcard: null, + limiter: null, + rateLimitBy: "debounce", + rateLimitWait: 300, + transform: _.identity, + transport: null + }; + o = _.isString(o) ? { + url: o + } : o; + o = _.mixin(defaults, o); + !o.url && $.error("remote requires url to be set"); + o.transform = o.filter || o.transform; + o.prepare = toRemotePrepare(o); + o.limiter = toLimiter(o); + o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax; + delete o.replace; + delete o.wildcard; + delete o.rateLimitBy; + delete o.rateLimitWait; + return o; + } + function toRemotePrepare(o) { + var prepare, replace, wildcard; + prepare = o.prepare; + replace = o.replace; + wildcard = o.wildcard; + if (prepare) { + return prepare; + } + if (replace) { + prepare = prepareByReplace; + } else if (o.wildcard) { + prepare = prepareByWildcard; + } else { + prepare = identityPrepare; + } + return prepare; + function prepareByReplace(query, settings) { + settings.url = replace(settings.url, query); + return settings; + } + function prepareByWildcard(query, settings) { + settings.url = settings.url.replace(wildcard, encodeURIComponent(query)); + return settings; + } + function identityPrepare(query, settings) { + return settings; + } + } + function toLimiter(o) { + var limiter, method, wait; + limiter = o.limiter; + method = o.rateLimitBy; + wait = o.rateLimitWait; + if (!limiter) { + limiter = /^throttle$/i.test(method) ? throttle(wait) : debounce(wait); + } + return limiter; + function debounce(wait) { + return function debounce(fn) { + return _.debounce(fn, wait); + }; + } + function throttle(wait) { + return function throttle(fn) { + return _.throttle(fn, wait); + }; + } + } + function callbackToDeferred(fn) { + return function wrapper(o) { + var deferred = $.Deferred(); + fn(o, onSuccess, onError); + return deferred; + function onSuccess(resp) { + _.defer(function() { + deferred.resolve(resp); + }); + } + function onError(err) { + _.defer(function() { + deferred.reject(err); + }); + } + }; + } + }(); + var Bloodhound = function() { + "use strict"; + var old; + old = window && window.Bloodhound; + function Bloodhound(o) { + o = oParser(o); + this.sorter = o.sorter; + this.identify = o.identify; + this.sufficient = o.sufficient; + this.indexRemote = o.indexRemote; + this.local = o.local; + this.remote = o.remote ? new Remote(o.remote) : null; + this.prefetch = o.prefetch ? new Prefetch(o.prefetch) : null; this.index = new SearchIndex({ + identify: this.identify, datumTokenizer: o.datumTokenizer, queryTokenizer: o.queryTokenizer }); - this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null; + o.initialize !== false && this.initialize(); } Bloodhound.noConflict = function noConflict() { - root.Bloodhound = old; + window && (window.Bloodhound = old); return Bloodhound; }; Bloodhound.tokenizers = tokenizers; _.mixin(Bloodhound.prototype, { - _loadPrefetch: function loadPrefetch(o) { - var that = this, serialized, deferred; - if (serialized = this._readFromStorage(o.thumbprint)) { + __ttAdapter: function ttAdapter() { + var that = this; + return this.remote ? withAsync : withoutAsync; + function withAsync(query, sync, async) { + return that.search(query, sync, async); + } + function withoutAsync(query, sync) { + return that.search(query, sync); + } + }, + _loadPrefetch: function loadPrefetch() { + var that = this, deferred, serialized; + deferred = $.Deferred(); + if (!this.prefetch) { + deferred.resolve(); + } else if (serialized = this.prefetch.fromCache()) { this.index.bootstrap(serialized); - deferred = $.Deferred().resolve(); + deferred.resolve(); } else { - deferred = $.ajax(o.url, o.ajax).done(handlePrefetchResponse); + this.prefetch.fromNetwork(done); } - return deferred; - function handlePrefetchResponse(resp) { - that.clear(); - that.add(o.filter ? o.filter(resp) : resp); - that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl); + return deferred.promise(); + function done(err, data) { + if (err) { + return deferred.reject(); + } + that.add(data); + that.prefetch.store(that.index.serialize()); + deferred.resolve(); } }, - _getFromRemote: function getFromRemote(query, cb) { - var that = this, url, uriEncodedQuery; - if (!this.transport) { - return; - } - query = query || ""; - uriEncodedQuery = encodeURIComponent(query); - url = this.remote.replace ? this.remote.replace(this.remote.url, query) : this.remote.url.replace(this.remote.wildcard, uriEncodedQuery); - return this.transport.get(url, this.remote.ajax, handleRemoteResponse); - function handleRemoteResponse(err, resp) { - err ? cb([]) : cb(that.remote.filter ? that.remote.filter(resp) : resp); - } - }, - _cancelLastRemoteRequest: function cancelLastRemoteRequest() { - this.transport && this.transport.cancel(); - }, - _saveToStorage: function saveToStorage(data, thumbprint, ttl) { - if (this.storage) { - this.storage.set(keys.data, data, ttl); - this.storage.set(keys.protocol, location.protocol, ttl); - this.storage.set(keys.thumbprint, thumbprint, ttl); - } - }, - _readFromStorage: function readFromStorage(thumbprint) { - var stored = {}, isExpired; - if (this.storage) { - stored.data = this.storage.get(keys.data); - stored.protocol = this.storage.get(keys.protocol); - stored.thumbprint = this.storage.get(keys.thumbprint); - } - isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol; - return stored.data && !isExpired ? stored.data : null; - }, _initialize: function initialize() { - var that = this, local = this.local, deferred; - deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve(); - local && deferred.done(addLocalToIndex); - this.transport = this.remote ? new Transport(this.remote) : null; - return this.initPromise = deferred.promise(); + var that = this, deferred; + this.clear(); + (this.initPromise = this._loadPrefetch()).done(addLocalToIndex); + return this.initPromise; function addLocalToIndex() { - that.add(_.isFunction(local) ? local() : local); + that.add(that.local); } }, initialize: function initialize(force) { @@ -675,129 +899,318 @@ }, add: function add(data) { this.index.add(data); + return this; }, - get: function get(query, cb) { - var that = this, matches = [], cacheHit = false; - matches = this.index.get(query); - matches = this.sorter(matches).slice(0, this.limit); - matches.length < this.limit ? cacheHit = this._getFromRemote(query, returnRemoteMatches) : this._cancelLastRemoteRequest(); - if (!cacheHit) { - (matches.length > 0 || !this.transport) && cb && cb(matches); + get: function get(ids) { + ids = _.isArray(ids) ? ids : [].slice.call(arguments); + return this.index.get(ids); + }, + search: function search(query, sync, async) { + var that = this, local; + sync = sync || _.noop; + async = async || _.noop; + local = this.sorter(this.index.search(query)); + sync(this.remote ? local.slice() : local); + if (this.remote && local.length < this.sufficient) { + this.remote.get(query, processRemote); + } else if (this.remote) { + this.remote.cancelLastRequest(); } - function returnRemoteMatches(remoteMatches) { - var matchesWithBackfill = matches.slice(0); - _.each(remoteMatches, function(remoteMatch) { - var isDuplicate; - isDuplicate = _.some(matchesWithBackfill, function(match) { - return that.dupDetector(remoteMatch, match); - }); - !isDuplicate && matchesWithBackfill.push(remoteMatch); - return matchesWithBackfill.length < that.limit; + return this; + function processRemote(remote) { + var nonDuplicates = []; + _.each(remote, function(r) { + !_.some(local, function(l) { + return that.identify(r) === that.identify(l); + }) && nonDuplicates.push(r); }); - cb && cb(that.sorter(matchesWithBackfill)); + that.indexRemote && that.add(nonDuplicates); + async(nonDuplicates); } }, + all: function all() { + return this.index.all(); + }, clear: function clear() { this.index.reset(); + return this; }, clearPrefetchCache: function clearPrefetchCache() { - this.storage && this.storage.clear(); + this.prefetch && this.prefetch.clear(); + return this; }, clearRemoteCache: function clearRemoteCache() { - this.transport && Transport.resetCache(); + Transport.resetCache(); + return this; }, ttAdapter: function ttAdapter() { - return _.bind(this.get, this); + return this.__ttAdapter(); } }); return Bloodhound; - function getSorter(sortFn) { - return _.isFunction(sortFn) ? sort : noSort; - function sort(array) { - return array.sort(sortFn); - } - function noSort(array) { - return array; - } - } - function ignoreDuplicates() { - return false; - } - })(this); - var html = function() { + }(); + return Bloodhound; +}); + +(function(root, factory) { + if (typeof define === "function" && define.amd) { + define([ "jquery" ], function(a0) { + return factory(a0); + }); + } else if (typeof module === "object" && module.exports) { + module.exports = factory(require("jquery")); + } else { + factory(root["jQuery"]); + } +})(this, function($) { + var _ = function() { + "use strict"; return { - wrapper: '', - dropdown: '', - dataset: '
', - suggestions: '', - suggestion: '' + isMsie: function() { + return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; + }, + isBlankString: function(str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function(obj) { + return typeof obj === "string"; + }, + isNumber: function(obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function(obj) { + return typeof obj === "undefined"; + }, + isElement: function(obj) { + return !!(obj && obj.nodeType === 1); + }, + isJQuery: function(obj) { + return obj instanceof $; + }, + toStr: function toStr(s) { + return _.isUndefined(s) || s === null ? "" : s + ""; + }, + bind: $.proxy, + each: function(collection, cb) { + $.each(collection, reverseArgs); + function reverseArgs(index, value) { + return cb(value, index); + } + }, + map: $.map, + filter: $.grep, + every: function(obj, test) { + var result = true; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function(obj, test) { + var result = false; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + identity: function(x) { + return x; + }, + clone: function(obj) { + return $.extend(true, {}, obj); + }, + getIdGenerator: function() { + var counter = 0; + return function() { + return counter++; + }; + }, + templatify: function templatify(obj) { + return $.isFunction(obj) ? obj : template; + function template() { + return String(obj); + } + }, + defer: function(fn) { + setTimeout(fn, 0); + }, + debounce: function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments, later, callNow; + later = function() { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }, + throttle: function(func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function() { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + stringify: function(val) { + return _.isString(val) ? val : JSON.stringify(val); + }, + guid: function() { + function _p8(s) { + var p = (Math.random().toString(16) + "000000000").substr(2, 8); + return s ? "-" + p.substr(0, 4) + "-" + p.substr(4, 4) : p; + } + return "tt-" + _p8() + _p8(true) + _p8(true) + _p8(); + }, + noop: function() {} }; }(); - var css = function() { + var WWW = function() { "use strict"; - var css = { - wrapper: { - position: "relative", - display: "inline-block" - }, - hint: { - position: "absolute", - top: "0", - left: "0", - borderColor: "transparent", - boxShadow: "none", - opacity: "1" - }, - input: { - position: "relative", - verticalAlign: "top", - backgroundColor: "transparent" - }, - inputWithNoHint: { - position: "relative", - verticalAlign: "top" - }, - dropdown: { - position: "absolute", - top: "100%", - left: "0", - zIndex: "100", - display: "none" - }, - suggestions: { - display: "block" - }, - suggestion: { - whiteSpace: "nowrap", - cursor: "pointer" - }, - suggestionChild: { - whiteSpace: "normal" - }, - ltr: { - left: "0", - right: "auto" - }, - rtl: { - left: "auto", - right: " 0" - } + var defaultClassNames = { + wrapper: "twitter-typeahead", + input: "tt-input", + hint: "tt-hint", + menu: "tt-menu", + dataset: "tt-dataset", + suggestion: "tt-suggestion", + selectable: "tt-selectable", + empty: "tt-empty", + open: "tt-open", + cursor: "tt-cursor", + highlight: "tt-highlight" }; - if (_.isMsie()) { - _.mixin(css.input, { - backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" - }); + return build; + function build(o) { + var www, classes; + classes = _.mixin({}, defaultClassNames, o); + www = { + css: buildCss(), + classes: classes, + html: buildHtml(classes), + selectors: buildSelectors(classes) + }; + return { + css: www.css, + html: www.html, + classes: www.classes, + selectors: www.selectors, + mixin: function(o) { + _.mixin(o, www); + } + }; } - if (_.isMsie() && _.isMsie() <= 7) { - _.mixin(css.input, { - marginTop: "-1px" - }); + function buildHtml(c) { + return { + wrapper: '', + menu: '' + }; + } + function buildSelectors(classes) { + var selectors = {}; + _.each(classes, function(v, k) { + selectors[k] = "." + v; + }); + return selectors; + } + function buildCss() { + var css = { + wrapper: { + position: "relative", + display: "inline-block" + }, + hint: { + position: "absolute", + top: "0", + left: "0", + borderColor: "transparent", + boxShadow: "none", + opacity: "1" + }, + input: { + position: "relative", + verticalAlign: "top", + backgroundColor: "transparent" + }, + inputWithNoHint: { + position: "relative", + verticalAlign: "top" + }, + menu: { + position: "absolute", + top: "100%", + left: "0", + zIndex: "100", + display: "none" + }, + ltr: { + left: "0", + right: "auto" + }, + rtl: { + left: "auto", + right: " 0" + } + }; + if (_.isMsie()) { + _.mixin(css.input, { + backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" + }); + } + return css; } - return css; }(); var EventBus = function() { "use strict"; - var namespace = "typeahead:"; + var namespace, deprecationMap; + namespace = "typeahead:"; + deprecationMap = { + render: "rendered", + cursorchange: "cursorchanged", + select: "selected", + autocomplete: "autocompleted" + }; function EventBus(o) { if (!o || !o.el) { $.error("EventBus initialized without el"); @@ -805,9 +1218,23 @@ this.$el = $(o.el); } _.mixin(EventBus.prototype, { + _trigger: function(type, args) { + var $e = $.Event(namespace + type); + this.$el.trigger.call(this.$el, $e, args || []); + return $e; + }, + before: function(type) { + var args, $e; + args = [].slice.call(arguments, 1); + $e = this._trigger("before" + type, args); + return $e.isDefaultPrevented(); + }, trigger: function(type) { - var args = [].slice.call(arguments, 1); - this.$el.trigger(namespace + type, args); + var deprecatedType; + this._trigger(type, [].slice.call(arguments, 1)); + if (deprecatedType = deprecationMap[type]) { + this._trigger(deprecatedType, [].slice.call(arguments, 1)); + } } }); return EventBus; @@ -910,7 +1337,36 @@ tagName: "strong", className: null, wordsOnly: false, - caseSensitive: false + caseSensitive: false, + diacriticInsensitive: false + }; + var accented = { + A: "[AaªÀ-Åà-åĀ-ąǍǎȀ-ȃȦȧᴬᵃḀḁẚẠ-ảₐ℀℁℻⒜Ⓐⓐ㍱-㍴㎀-㎄㎈㎉㎩-㎯㏂㏊㏟㏿Aa]", + B: "[BbᴮᵇḂ-ḇℬ⒝Ⓑⓑ㍴㎅-㎇㏃㏈㏔㏝Bb]", + C: "[CcÇçĆ-čᶜ℀ℂ℃℅℆ℭⅭⅽ⒞Ⓒⓒ㍶㎈㎉㎝㎠㎤㏄-㏇Cc]", + D: "[DdĎďDŽ-džDZ-dzᴰᵈḊ-ḓⅅⅆⅮⅾ⒟Ⓓⓓ㋏㍲㍷-㍹㎗㎭-㎯㏅㏈Dd]", + E: "[EeÈ-Ëè-ëĒ-ěȄ-ȇȨȩᴱᵉḘ-ḛẸ-ẽₑ℡ℯℰⅇ⒠Ⓔⓔ㉐㋍㋎Ee]", + F: "[FfᶠḞḟ℉ℱ℻⒡Ⓕⓕ㎊-㎌㎙ff-fflFf]", + G: "[GgĜ-ģǦǧǴǵᴳᵍḠḡℊ⒢Ⓖⓖ㋌㋍㎇㎍-㎏㎓㎬㏆㏉㏒㏿Gg]", + H: "[HhĤĥȞȟʰᴴḢ-ḫẖℋ-ℎ⒣Ⓗⓗ㋌㍱㎐-㎔㏊㏋㏗Hh]", + I: "[IiÌ-Ïì-ïĨ-İIJijǏǐȈ-ȋᴵᵢḬḭỈ-ịⁱℐℑℹⅈⅠ-ⅣⅥ-ⅨⅪⅫⅰ-ⅳⅵ-ⅸⅺⅻ⒤Ⓘⓘ㍺㏌㏕fiffiIi]", + J: "[JjIJ-ĵLJ-njǰʲᴶⅉ⒥ⒿⓙⱼJj]", + K: "[KkĶķǨǩᴷᵏḰ-ḵK⒦Ⓚⓚ㎄㎅㎉㎏㎑㎘㎞㎢㎦㎪㎸㎾㏀㏆㏍-㏏Kk]", + L: "[LlĹ-ŀLJ-ljˡᴸḶḷḺ-ḽℒℓ℡Ⅼⅼ⒧Ⓛⓛ㋏㎈㎉㏐-㏓㏕㏖㏿flfflLl]", + M: "[MmᴹᵐḾ-ṃ℠™ℳⅯⅿ⒨Ⓜⓜ㍷-㍹㎃㎆㎎㎒㎖㎙-㎨㎫㎳㎷㎹㎽㎿㏁㏂㏎㏐㏔-㏖㏘㏙㏞㏟Mm]", + N: "[NnÑñŃ-ʼnNJ-njǸǹᴺṄ-ṋⁿℕ№⒩Ⓝⓝ㎁㎋㎚㎱㎵㎻㏌㏑Nn]", + O: "[OoºÒ-Öò-öŌ-őƠơǑǒǪǫȌ-ȏȮȯᴼᵒỌ-ỏₒ℅№ℴ⒪Ⓞⓞ㍵㏇㏒㏖Oo]", + P: "[PpᴾᵖṔ-ṗℙ⒫Ⓟⓟ㉐㍱㍶㎀㎊㎩-㎬㎰㎴㎺㏋㏗-㏚Pp]", + Q: "[Qqℚ⒬Ⓠⓠ㏃Qq]", + R: "[RrŔ-řȐ-ȓʳᴿᵣṘ-ṛṞṟ₨ℛ-ℝ⒭Ⓡⓡ㋍㍴㎭-㎯㏚㏛Rr]", + S: "[SsŚ-šſȘșˢṠ-ṣ₨℁℠⒮Ⓢⓢ㎧㎨㎮-㎳㏛㏜stSs]", + T: "[TtŢ-ťȚțᵀᵗṪ-ṱẗ℡™⒯Ⓣⓣ㉐㋏㎔㏏ſtstTt]", + U: "[UuÙ-Üù-üŨ-ųƯưǓǔȔ-ȗᵁᵘᵤṲ-ṷỤ-ủ℆⒰Ⓤⓤ㍳㍺Uu]", + V: "[VvᵛᵥṼ-ṿⅣ-Ⅷⅳ-ⅷ⒱Ⓥⓥⱽ㋎㍵㎴-㎹㏜㏞Vv]", + W: "[WwŴŵʷᵂẀ-ẉẘ⒲Ⓦⓦ㎺-㎿㏝Ww]", + X: "[XxˣẊ-ẍₓ℻Ⅸ-Ⅻⅸ-ⅻ⒳Ⓧⓧ㏓Xx]", + Y: "[YyÝýÿŶ-ŸȲȳʸẎẏẙỲ-ỹ⒴Ⓨⓨ㏉Yy]", + Z: "[ZzŹ-žDZ-dzᶻẐ-ẕℤℨ⒵Ⓩⓩ㎐-㎔Zz]" }; return function hightlight(o) { var regex; @@ -919,7 +1375,7 @@ return; } o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; - regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); + regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly, o.diacriticInsensitive); traverse(o.node, hightlightTextNode); function hightlightTextNode(textNode) { var match, patternNode, wrapperNode; @@ -945,10 +1401,17 @@ } } }; - function getRegex(patterns, caseSensitive, wordsOnly) { + function accent_replacer(chr) { + return accented[chr.toUpperCase()] || chr; + } + function getRegex(patterns, caseSensitive, wordsOnly, diacriticInsensitive) { var escapedPatterns = [], regexStr; for (var i = 0, len = patterns.length; i < len; i++) { - escapedPatterns.push(_.escapeRegExChars(patterns[i])); + var escapedWord = _.escapeRegExChars(patterns[i]); + if (diacriticInsensitive) { + escapedWord = escapedWord.replace(/\S/g, accent_replacer); + } + escapedPatterns.push(escapedWord); } regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); @@ -966,36 +1429,38 @@ 38: "up", 40: "down" }; - function Input(o) { - var that = this, onBlur, onFocus, onKeydown, onInput; + function Input(o, www) { + var id; o = o || {}; if (!o.input) { $.error("input is missing"); } - onBlur = _.bind(this._onBlur, this); - onFocus = _.bind(this._onFocus, this); - onKeydown = _.bind(this._onKeydown, this); - onInput = _.bind(this._onInput, this); + www.mixin(this); this.$hint = $(o.hint); - this.$input = $(o.input).on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); + this.$input = $(o.input); + this.$menu = $(o.menu); + id = this.$input.attr("id") || _.guid(); + this.$menu.attr("id", id + "_listbox"); + this.$hint.attr({ + "aria-hidden": true + }); + this.$input.attr({ + "aria-owns": id + "_listbox", + role: "combobox", + "aria-autocomplete": "list", + "aria-expanded": false + }); + this.query = this.$input.val(); + this.queryWhenFocused = this.hasFocus() ? this.query : null; + this.$overflowHelper = buildOverflowHelper(this.$input); + this._checkLanguageDirection(); if (this.$hint.length === 0) { this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; } - if (!_.isMsie()) { - this.$input.on("input.tt", onInput); - } else { - this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { - if (specialKeyCodeMap[$e.which || $e.keyCode]) { - return; - } - _.defer(_.bind(that._onInput, that, $e)); - }); - } - this.query = this.$input.val(); - this.$overflowHelper = buildOverflowHelper(this.$input); + this.onSync("cursorchange", this._updateDescendent); } Input.normalizeQuery = function(str) { - return (str || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " "); }; _.mixin(Input.prototype, EventEmitter, { _onBlur: function onBlur() { @@ -1003,6 +1468,7 @@ this.trigger("blurred"); }, _onFocus: function onFocus() { + this.queryWhenFocused = this.query; this.trigger("focused"); }, _onKeydown: function onKeydown($e) { @@ -1013,17 +1479,13 @@ } }, _onInput: function onInput() { - this._checkInputValue(); + this._setQuery(this.getInputValue()); + this.clearHintIfInvalid(); + this._checkLanguageDirection(); }, _managePreventDefault: function managePreventDefault(keyName, $e) { - var preventDefault, hintValue, inputValue; + var preventDefault; switch (keyName) { - case "tab": - hintValue = this.getHint(); - inputValue = this.getInputValue(); - preventDefault = hintValue && hintValue !== inputValue && !withModifier($e); - break; - case "up": case "down": preventDefault = !withModifier($e); @@ -1046,39 +1508,76 @@ } return trigger; }, - _checkInputValue: function checkInputValue() { - var inputValue, areEquivalent, hasDifferentWhitespace; - inputValue = this.getInputValue(); - areEquivalent = areQueriesEquivalent(inputValue, this.query); - hasDifferentWhitespace = areEquivalent ? this.query.length !== inputValue.length : false; - this.query = inputValue; - if (!areEquivalent) { + _checkLanguageDirection: function checkLanguageDirection() { + var dir = (this.$input.css("direction") || "ltr").toLowerCase(); + if (this.dir !== dir) { + this.dir = dir; + this.$hint.attr("dir", dir); + this.trigger("langDirChanged", dir); + } + }, + _setQuery: function setQuery(val, silent) { + var areEquivalent, hasDifferentWhitespace; + areEquivalent = areQueriesEquivalent(val, this.query); + hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false; + this.query = val; + if (!silent && !areEquivalent) { this.trigger("queryChanged", this.query); - } else if (hasDifferentWhitespace) { + } else if (!silent && hasDifferentWhitespace) { this.trigger("whitespaceChanged", this.query); } }, + _updateDescendent: function updateDescendent(event, id) { + this.$input.attr("aria-activedescendant", id); + }, + bind: function() { + var that = this, onBlur, onFocus, onKeydown, onInput; + onBlur = _.bind(this._onBlur, this); + onFocus = _.bind(this._onFocus, this); + onKeydown = _.bind(this._onKeydown, this); + onInput = _.bind(this._onInput, this); + this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); + if (!_.isMsie() || _.isMsie() > 9) { + this.$input.on("input.tt", onInput); + } else { + this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { + if (specialKeyCodeMap[$e.which || $e.keyCode]) { + return; + } + _.defer(_.bind(that._onInput, that, $e)); + }); + } + return this; + }, focus: function focus() { this.$input.focus(); }, blur: function blur() { this.$input.blur(); }, - getQuery: function getQuery() { - return this.query; + getLangDir: function getLangDir() { + return this.dir; }, - setQuery: function setQuery(query) { - this.query = query; + getQuery: function getQuery() { + return this.query || ""; + }, + setQuery: function setQuery(val, silent) { + this.setInputValue(val); + this._setQuery(val, silent); + }, + hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() { + return this.query !== this.queryWhenFocused; }, getInputValue: function getInputValue() { return this.$input.val(); }, - setInputValue: function setInputValue(value, silent) { + setInputValue: function setInputValue(value) { this.$input.val(value); - silent ? this.clearHint() : this._checkInputValue(); + this.clearHintIfInvalid(); + this._checkLanguageDirection(); }, resetInputValue: function resetInputValue() { - this.setInputValue(this.query, true); + this.setInputValue(this.query); }, getHint: function getHint() { return this.$hint.val(); @@ -1097,8 +1596,8 @@ isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); !isValid && this.clearHint(); }, - getLanguageDirection: function getLanguageDirection() { - return (this.$input.css("direction") || "ltr").toLowerCase(); + hasFocus: function hasFocus() { + return this.$input.is(":focus"); }, hasOverflow: function hasOverflow() { var constraint = this.$input.width() - 2; @@ -1121,7 +1620,11 @@ destroy: function destroy() { this.$hint.off(".tt"); this.$input.off(".tt"); - this.$hint = this.$input = this.$overflowHelper = null; + this.$overflowHelper.remove(); + this.$hint = this.$input = this.$overflowHelper = $("" + displayFn(context) + "
"; + return $('