diff --git a/webnotes/core/page/applications/applications.js b/webnotes/core/page/applications/applications.js index 1310f85354..947f336af8 100644 --- a/webnotes/core/page/applications/applications.js +++ b/webnotes/core/page/applications/applications.js @@ -8,27 +8,43 @@ wn.pages['applications'].onload = function(wrapper) { wn.call({ method:"webnotes.core.page.applications.applications.get_app_list", callback: function(r) { - var $main = $(wrapper).find(".layout-main") + var $main = $(wrapper).find(".layout-main"); + if(!keys(r.message).length) { $main.html('
%(app_title)s
\ +%(app_title)s
\%(app_description)s\
Publisher: %(app_publisher)s; Version: %(app_version)s
'+wn._("Checked items shown on desktop")+'
') + .appendTo(d.body); + $wrapper = $(''+m+'
').appendTo("#modules-list")); if(!wn.boot.hidden_modules || wn.boot.hidden_modules.indexOf(m)==-1) { diff --git a/webnotes/desktop.json b/webnotes/desktop.json index 8cc39c523f..b54d13b74c 100644 --- a/webnotes/desktop.json +++ b/webnotes/desktop.json @@ -6,13 +6,6 @@ "link": "Calendar/Event", "type": "view" }, - "Finder": { - "color": "#14C7DE", - "icon": "icon-folder-open", - "label": "Finder", - "link": "finder", - "type": "page" - }, "Messages": { "color": "#9b59b6", "icon": "icon-comments", diff --git a/webnotes/hooks.txt b/webnotes/hooks.txt index 377302f4fd..02c550237b 100644 --- a/webnotes/hooks.txt +++ b/webnotes/hooks.txt @@ -1,8 +1,8 @@ app_name = webnotes -app_title = Web Notes +app_title = Frappe Framework app_publisher = Web Notes Technologies app_description = Full Stack Web Application Framwork in Python -app_icon = icon-cog +app_icon = assets/webnotes/images/frappe.svg app_version = 4.0.0-wip app_color = #3498db @@ -40,4 +40,4 @@ permission_query_conditions:Event = webnotes.core.doctype.event.event.get_permis has_permission:Event = webnotes.core.doctype.event.event.has_permission permission_query_conditions:ToDo = webnotes.core.doctype.todo.todo.get_permission_query_conditions -has_permission:ToDo = webnotes.core.doctype.todo.todo.has_permission \ No newline at end of file +has_permission:ToDo = webnotes.core.doctype.todo.todo.has_permission diff --git a/webnotes/public/build.json b/webnotes/public/build.json index 68a807e494..44088cafdb 100644 --- a/webnotes/public/build.json +++ b/webnotes/public/build.json @@ -34,8 +34,12 @@ "public/css/bootstrap.css", "public/css/bootstrap-responsive.css", "public/css/font-awesome.css", - "public/css/forms.css", - "public/css/common.css", + "public/css/app.css", + "public/css/appframe.css", + "public/css/app_icon.css", + "public/css/avatar.css", + "public/css/navbar.css", + "public/css/slickgrid.css", "public/css/tree_grid.css", "public/css/nprogress.css" ], @@ -74,6 +78,7 @@ "public/js/wn/ui/field_group.js", "public/js/wn/ui/dialog.js", "public/js/wn/ui/button.js", + "public/js/wn/ui/app_icon.js", "public/js/wn/model/model.js", "public/js/wn/model/meta.js", @@ -92,7 +97,6 @@ "public/js/wn/upload.js", "public/js/wn/ui/filters.js", - "public/js/wn/ui/search.js", "public/js/wn/ui/tree.js", "public/js/wn/ui/tags.js", diff --git a/webnotes/public/css/common.css b/webnotes/public/css/app.css similarity index 58% rename from webnotes/public/css/common.css rename to webnotes/public/css/app.css index d0bc1c77d3..d5de003aeb 100644 --- a/webnotes/public/css/common.css +++ b/webnotes/public/css/app.css @@ -166,196 +166,6 @@ div#freeze { width: 0px; } -/* appframe header */ - -.appframe { - padding-top: 15px; -} - -.appframe-titlebar { - min-height: 30px; - background-color: #e7e7e7; -} - -.titlebar-item { - padding-top: 10px; - padding-bottom: 10px; -} - -.titlebar-item h4 { - width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin: 0px; -} - -.titlebar-center-item { -} - -.appframe-header { - margin-bottom: 20px; -} - -.appframe-toolbar { - margin-bottom: 0px; -} - -.appframe-header .status-bar { - text-align: right; -} - - -/* fixed navbar in appframe */ - -.appframe .navbar { - margin-left: -15px; - margin-right: -15px; - margin-bottom: 0px; - border-radius: 0px; - border-bottom: 1px solid #e7e7e7; - min-height: 51px; -} - -.appframe .navbar-form select, -.appframe .navbar-form input, -.appframe .navbar-form button, -.appframe .navbar-form label { - margin-bottom: 5px; -} - -.appframe .navbar-form { - margin-bottom: 2px; - width: 100%; -} - -.appframe-iconbar, .appframe-form { - border-bottom: 1px solid #c7c7c7; -} - -.appframe-form { - padding: 5px 0px; -} - -.appframe-form input, .appframe-form select, .appframe-form label { - font-size: 90%; - padding: 4px; - margin: 3px 0px; -} - -.appframe-form .form-group { - margin-bottom: 0px; -} - -.appframe-form .form-control { - height: 28px; -} - -.iconbar { - display: inline-block; - padding: 9px 0px; -} - -.iconbar ul { - list-style: none; - margin: 0 0 0 0; - padding: 0 0 0 0; -} - -.iconbar li { - display: inline-block; - padding-left: 4px; - padding-right: 4px; -} - -.iconbar i { - margin-top: 4px; - margin-right: 4px; - color: #888; - cursor: pointer; - font-size: 16px; -} - -.iconbar i:hover { - color: #000; -} - -.iconbar i:active { - color: #5bc0de; -} - -.iconbar .appframe-iconbar-active i { - font-weight: bold; - color: orange; -} - - -.appframe-titlebar .title-text { - font-weight: bold; -} - -.appframe-footer { - margin-top: 15px; -} - -/* home icon in main nav */ - -.navbar-icon-home { - vertical-align: middle; -} - -.navbar-icon-home:hover, -.navbar-icon-home:focus, -.navbar-icon-home:active, -.navbar-icon-home-hover{ - opacity:1; - Filter:alpha(opacity=100); /* For IE8 and earlier */ -} - -.navbar .brand { - max-height: 15px; -} - -.navbar-brand { - min-height: 20px; - height: auto; -} - -.navbar #spinner { - display: block; - float: right; - width: 20px; - margin-bottom: -5px; - margin-top: 14px; - visibility: hidden; -} - -.navbar-new-comments { - margin: -3px 0px; - padding: 2px 5px; - min-width: 20px; - text-align: center; - display: inline-block; - border-radius: 2px; - color: #999999; - background-color: #333131; -} - -.navbar-new-comments:hover, -.navbar-new-comments:active, -.navbar-new-comments:focus { - color: #fff; -} - -.navbar-new-comments-true { - color: #fff; - background-color: #e74c3c; -} - - -.btn [class^="icon-"], .nav [class^="icon-"], .btn [class*=" icon-"], .nav [class*=" icon-"] { - display: inline-block; -} .badge-important { background-color: #e74c3c; @@ -394,58 +204,6 @@ div#freeze { white-space: nowrap; } -/* avatar */ - -.avatar { - display: inline-block; - vertical-align: middle; - border-radius: 50%; - overflow: hidden; - background-color: #ddd; - border: 1px solid #eee; -} - -.avatar img { - width: 100%; - height: auto; -} - -.avatar-small { - margin-right: 5px; - width: 30px; - height: 30px; -} - -.avatar-large { - margin-right: 10px; - width: 72px; - height: 72px; -} - -/* slickgrid */ - -.slick-cell { - font-size: 12px; -} - -.slick-header-column, .slick-cell { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -.slick-headerrow-column { - background: #87ceeb; - text-overflow: clip; -} - -.slick-headerrow-column input { - margin: 0; - padding: 0; - width: 100%; - min-height: 20px; -} - .missing-image { background-color: #eee; display: table-cell; @@ -590,4 +348,11 @@ ul.linked-with-list li { padding: 2px 5px; margin-left: 15px; border-radius: 3px; - background-color: #ddd; \ No newline at end of file + background-color: #ddd; +} + +.print-preview { + padding: 50px 20px; + margin: 0px -15px; + box-shadow: 1px 1px 5px rgba(0,0,0,0.5); +} diff --git a/webnotes/public/css/app_icon.css b/webnotes/public/css/app_icon.css new file mode 100644 index 0000000000..373d931503 --- /dev/null +++ b/webnotes/public/css/app_icon.css @@ -0,0 +1,34 @@ +.app-icon { + border-radius: 5px; + padding: 20px; + display: inline-block; + margin: auto; + text-align: center; +} + + +.app-icon i { + font-size: 30px; + min-width: 30px; + color: #f8f8f8; + display: inline-block; +} + +.app-icon svg { + height: 30px; + width: 30px; +} + +.app-icon path { + fill: #f8f8f8; +} + +.app-icon-small { + padding: 12px; +} + +@media (max-width: 768px) { + .app-icon { + padding: 12px; + } +} diff --git a/webnotes/public/css/appframe.css b/webnotes/public/css/appframe.css new file mode 100644 index 0000000000..c5018dcbda --- /dev/null +++ b/webnotes/public/css/appframe.css @@ -0,0 +1,131 @@ +/* appframe header */ + +.appframe { + padding-top: 15px; +} + +.appframe-titlebar { + min-height: 30px; + background-color: #e7e7e7; +} + +.titlebar-item { + padding-top: 10px; + padding-bottom: 10px; +} + +.titlebar-item h4 { + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin: 0px; +} + +.titlebar-center-item { +} + +.appframe-header { + margin-bottom: 20px; +} + +.appframe-toolbar { + margin-bottom: 0px; +} + +.appframe-header .status-bar { + text-align: right; +} + + +/* fixed navbar in appframe */ + +.appframe .navbar { + margin-left: -15px; + margin-right: -15px; + margin-bottom: 0px; + border-radius: 0px; + border-bottom: 1px solid #e7e7e7; + min-height: 51px; +} + +.appframe .navbar-form select, +.appframe .navbar-form input, +.appframe .navbar-form button, +.appframe .navbar-form label { + margin-bottom: 5px; +} + +.appframe .navbar-form { + margin-bottom: 2px; + width: 100%; +} + +.appframe-iconbar, .appframe-form { + border-bottom: 1px solid #c7c7c7; +} + +.appframe-form { + padding: 5px 0px; +} + +.appframe-form input, .appframe-form select, .appframe-form label { + font-size: 90%; + padding: 4px; + margin: 3px 0px; +} + +.appframe-form .form-group { + margin-bottom: 0px; +} + +.appframe-form .form-control { + height: 28px; +} + +.iconbar { + display: inline-block; + padding: 9px 0px; +} + +.iconbar ul { + list-style: none; + margin: 0 0 0 0; + padding: 0 0 0 0; +} + +.iconbar li { + display: inline-block; + padding-left: 4px; + padding-right: 4px; +} + +.iconbar i { + margin-top: 4px; + margin-right: 4px; + color: #888; + cursor: pointer; + font-size: 16px; +} + +.iconbar i:hover { + color: #000; +} + +.iconbar i:active { + color: #5bc0de; +} + +.iconbar .appframe-iconbar-active i { + font-weight: bold; + color: orange; +} + + +.appframe-titlebar .title-text { + font-weight: bold; +} + +.appframe-footer { + margin-top: 15px; +} diff --git a/webnotes/public/css/avatar.css b/webnotes/public/css/avatar.css new file mode 100644 index 0000000000..a55cef4987 --- /dev/null +++ b/webnotes/public/css/avatar.css @@ -0,0 +1,25 @@ +.avatar { + display: inline-block; + vertical-align: middle; + border-radius: 50%; + overflow: hidden; + background-color: #ddd; + border: 1px solid #eee; +} + +.avatar img { + width: 100%; + height: auto; +} + +.avatar-small { + margin-right: 5px; + width: 30px; + height: 30px; +} + +.avatar-large { + margin-right: 10px; + width: 72px; + height: 72px; +} diff --git a/webnotes/public/css/forms.css b/webnotes/public/css/forms.css deleted file mode 100644 index 2980a851b9..0000000000 --- a/webnotes/public/css/forms.css +++ /dev/null @@ -1,132 +0,0 @@ -/* FORMS */ - -div.form-title { - /*background-color: #e0eeff;*/ - padding: 5px 19px 15px 19px; - border-bottom: 1px solid #eee; -} - -.appframe-titlebar .label { - vertical-align: middle; - margin-right: 7px; -} - -div.form-section-head { - border-top: 1px solid #ccc; - padding: 11px 23px 0px 23px; -} - -div.form-layout-row:first-child .form-section-head { - border-top: 0px solid #ccc !important; - margin-top: 0px; - padding-top: 0px; -} - -div.form-section-head h3 { - line-height: 20px; -} - -div.form-section-head hr { - margin: 9px 0px; -} - - -div.frm_print_wrapper { - background-color:#FFF; - border:1px solid #444; - padding: 40px; - - box-shadow:1px 1px 8px #229; - -moz-box-shadow: 1px 1px 8px #229; - -webkit-box-shadow: 1px 1px 8px #229; -} - -div.page_break { - margin: 24px 0px; - border-top: 1px dashed #888; -} - -div.dialog_frm { - position: relative; - margin: 10px; -} - -.top_cell { - height: 50px; -} - -div.attach_area { - padding: 8px; - margin: 8px; - background-color: #EEE; -} - -div.attach_area table { - width: 100%; -} - -.tablabel_normal { - margin: 0 4px 0 0; - padding: 3px 5px; - line-height: 1.3em; - display: inline; - cursor: pointer; -} - -.tablabel_selected { - margin: 0 4px 0 0; - padding: 3px 5px; - line-height: 1.3em; - font-weight: bold; - display: inline; - cursor: pointer; - color: #000; -} - -.sectionCell { - padding: 5px; - vertical-align: top; -} - -.code_area { - width: 80%; - margin: 8px; - padding: 4px; - background-color: #F8F8F8; - border: 1px solid #CCC; - overflow-x: auto; -} - -.code_text { - width: 100%; - height: 360px; - margin-top: 3px; - font-family: Courier, Fixed; - font-size: 12px; -} - -div.time_field select{ - display: inline; - margin: 2px; - width: 45px; -} - -/* sidebar */ - -div.sidebar-comment-wrapper input { - width: 70%; -} -div.sidebar-comment-message { - margin-top: 8px; - color: #777; -} - -div.sidebar-comment-text { - font-size: 12px; - font-weight: bold; - margin-top: 8px; - color: #444; -} -div.sidebar-comment-info { - color: #777; -} \ No newline at end of file diff --git a/webnotes/public/css/navbar.css b/webnotes/public/css/navbar.css new file mode 100644 index 0000000000..8fc6ad8251 --- /dev/null +++ b/webnotes/public/css/navbar.css @@ -0,0 +1,56 @@ +.navbar-icon-home { + vertical-align: middle; +} + +.navbar-icon-home:hover, +.navbar-icon-home:focus, +.navbar-icon-home:active, +.navbar-icon-home-hover{ + opacity:1; + Filter:alpha(opacity=100); /* For IE8 and earlier */ +} + +.navbar .brand { + max-height: 15px; +} + +.navbar-brand { + min-height: 20px; + height: auto; +} + +.navbar #spinner { + display: block; + float: right; + width: 20px; + margin-bottom: -5px; + margin-top: 14px; + visibility: hidden; +} + +.navbar-new-comments { + margin: -3px 0px; + padding: 2px 5px; + min-width: 20px; + text-align: center; + display: inline-block; + border-radius: 2px; + color: #999999; + background-color: #333131; +} + +.navbar-new-comments:hover, +.navbar-new-comments:active, +.navbar-new-comments:focus { + color: #fff; +} + +.navbar-new-comments-true { + color: #fff; + background-color: #e74c3c; +} + + +.btn [class^="icon-"], .nav [class^="icon-"], .btn [class*=" icon-"], .nav [class*=" icon-"] { + display: inline-block; +} diff --git a/webnotes/public/css/slickgrid.css b/webnotes/public/css/slickgrid.css new file mode 100644 index 0000000000..525b6c2f5f --- /dev/null +++ b/webnotes/public/css/slickgrid.css @@ -0,0 +1,21 @@ +.slick-cell { + font-size: 12px; +} + +.slick-header-column, .slick-cell { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.slick-headerrow-column { + background: #87ceeb; + text-overflow: clip; +} + +.slick-headerrow-column input { + margin: 0; + padding: 0; + width: 100%; + min-height: 20px; +} \ No newline at end of file diff --git a/webnotes/public/css/typeahead.css b/webnotes/public/css/typeahead.css deleted file mode 100644 index 485cd8e578..0000000000 --- a/webnotes/public/css/typeahead.css +++ /dev/null @@ -1,45 +0,0 @@ -.typeahead, -.tt-query, -.tt-hint { -} - -.tt-hint { - color: #999; -} - -.tt-dropdown-menu { - width: 100%; - margin-top: 3px; - padding: 3px 0; - background-color: #fff; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.2); - -webkit-border-radius: 8px; - -moz-border-radius: 8px; - border-radius: 8px; - -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); - -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); - box-shadow: 0 5px 10px rgba(0,0,0,.2); -} - -.tt-suggestion { - padding: 3px 20px; -} - -.tt-suggestion.tt-is-under-cursor { - color: #fff; - background-color: #0097cf; -} - -.tt-suggestion.tt-is-under-cursor .text-muted { - color: #ddd; -} - -.input-group input.tt-query { - border-top-left-radius: 4px !important; - border-bottom-left-radius: 4px !important; -} - -.tt-suggestion p { - margin: 0; -} diff --git a/webnotes/public/js/lib/typeahead.js b/webnotes/public/js/lib/typeahead.js deleted file mode 100644 index 03849c1188..0000000000 --- a/webnotes/public/js/lib/typeahead.js +++ /dev/null @@ -1,1142 +0,0 @@ -/*! - * typeahead.js 0.9.3 - * https://github.com/twitter/typeahead - * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT - */ - -(function($) { - var VERSION = "0.9.3"; - var utils = { - isMsie: function() { - var match = /(msie) ([\w.]+)/i.exec(navigator.userAgent); - return match ? parseInt(match[2], 10) : 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"; - }, - bind: $.proxy, - bindAll: function(obj) { - var val; - for (var key in obj) { - $.isFunction(val = obj[key]) && (obj[key] = $.proxy(val, obj)); - } - }, - indexOf: function(haystack, needle) { - for (var i = 0; i < haystack.length; i++) { - if (haystack[i] === needle) { - return i; - } - } - return -1; - }, - each: $.each, - 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, - getUniqueId: function() { - var counter = 0; - return function() { - return counter++; - }; - }(), - 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; - }; - }, - tokenizeQuery: function(str) { - return $.trim(str).toLowerCase().split(/[\s]+/); - }, - tokenizeText: function(str) { - return $.trim(str).toLowerCase().split(/[\s\-_]+/); - }, - getProtocol: function() { - return location.protocol; - }, - noop: function() {} - }; - var EventTarget = function() { - var eventSplitter = /\s+/; - return { - on: function(events, callback) { - var event; - if (!callback) { - return this; - } - this._callbacks = this._callbacks || {}; - events = events.split(eventSplitter); - while (event = events.shift()) { - this._callbacks[event] = this._callbacks[event] || []; - this._callbacks[event].push(callback); - } - return this; - }, - trigger: function(events, data) { - var event, callbacks; - if (!this._callbacks) { - return this; - } - events = events.split(eventSplitter); - while (event = events.shift()) { - if (callbacks = this._callbacks[event]) { - for (var i = 0; i < callbacks.length; i += 1) { - callbacks[i].call(this, { - type: event, - data: data - }); - } - } - } - return this; - } - }; - }(); - var EventBus = function() { - var namespace = "typeahead:"; - function EventBus(o) { - if (!o || !o.el) { - $.error("EventBus initialized without el"); - } - this.$el = $(o.el); - } - utils.mixin(EventBus.prototype, { - trigger: function(type) { - var args = [].slice.call(arguments, 1); - this.$el.trigger(namespace + type, args); - } - }); - return EventBus; - }(); - var PersistentStorage = function() { - var ls, methods; - try { - ls = window.localStorage; - ls.setItem("~~~", "!"); - ls.removeItem("~~~"); - } catch (err) { - ls = null; - } - function PersistentStorage(namespace) { - this.prefix = [ "__", namespace, "__" ].join(""); - this.ttlKey = "__ttl__"; - this.keyMatcher = new RegExp("^" + this.prefix); - } - 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); - } - return decode(ls.getItem(this._prefix(key))); - }, - set: function(key, val, ttl) { - if (utils.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 utils.isNumber(ttl) && now() > ttl ? true : false; - } - }; - } else { - methods = { - get: utils.noop, - set: utils.noop, - remove: utils.noop, - clear: utils.noop, - isExpired: utils.noop - }; - } - utils.mixin(PersistentStorage.prototype, methods); - return PersistentStorage; - function now() { - return new Date().getTime(); - } - function encode(val) { - return JSON.stringify(utils.isUndefined(val) ? null : val); - } - function decode(val) { - return JSON.parse(val); - } - }(); - var RequestCache = function() { - function RequestCache(o) { - utils.bindAll(this); - o = o || {}; - this.sizeLimit = o.sizeLimit || 10; - this.cache = {}; - this.cachedKeysByAge = []; - } - utils.mixin(RequestCache.prototype, { - get: function(url) { - return this.cache[url]; - }, - set: function(url, resp) { - var requestToEvict; - if (this.cachedKeysByAge.length === this.sizeLimit) { - requestToEvict = this.cachedKeysByAge.shift(); - delete this.cache[requestToEvict]; - } - this.cache[url] = resp; - this.cachedKeysByAge.push(url); - } - }); - return RequestCache; - }(); - var Transport = function() { - var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests, requestCache; - function Transport(o) { - utils.bindAll(this); - o = utils.isString(o) ? { - url: o - } : o; - requestCache = requestCache || new RequestCache(); - maxPendingRequests = utils.isNumber(o.maxParallelRequests) ? o.maxParallelRequests : maxPendingRequests || 6; - this.url = o.url; - this.wildcard = o.wildcard || "%QUERY"; - this.filter = o.filter; - this.replace = o.replace; - this.ajaxSettings = { - type: "get", - cache: o.cache, - timeout: o.timeout, - dataType: o.dataType || "json", - beforeSend: o.beforeSend - }; - this._get = (/^throttle$/i.test(o.rateLimitFn) ? utils.throttle : utils.debounce)(this._get, o.rateLimitWait || 300); - } - utils.mixin(Transport.prototype, { - _get: function(url, cb) { - var that = this; - if (belowPendingRequestsThreshold()) { - this._sendRequest(url).done(done); - } else { - this.onDeckRequestArgs = [].slice.call(arguments, 0); - } - function done(resp) { - var data = that.filter ? that.filter(resp) : resp; - cb && cb(data); - requestCache.set(url, resp); - } - }, - _sendRequest: function(url) { - var that = this, jqXhr = pendingRequests[url]; - if (!jqXhr) { - incrementPendingRequests(); - jqXhr = pendingRequests[url] = $.ajax(url, this.ajaxSettings).always(always); - } - return jqXhr; - function always() { - decrementPendingRequests(); - pendingRequests[url] = null; - if (that.onDeckRequestArgs) { - that._get.apply(that, that.onDeckRequestArgs); - that.onDeckRequestArgs = null; - } - } - }, - get: function(query, cb) { - var that = this, encodedQuery = encodeURIComponent(query || ""), url, resp; - cb = cb || utils.noop; - url = this.replace ? this.replace(this.url, encodedQuery) : this.url.replace(this.wildcard, encodedQuery); - if (resp = requestCache.get(url)) { - utils.defer(function() { - cb(that.filter ? that.filter(resp) : resp); - }); - } else { - this._get(url, cb); - } - return !!resp; - } - }); - return Transport; - function incrementPendingRequests() { - pendingRequestsCount++; - } - function decrementPendingRequests() { - pendingRequestsCount--; - } - function belowPendingRequestsThreshold() { - return pendingRequestsCount < maxPendingRequests; - } - }(); - var Dataset = function() { - var keys = { - thumbprint: "thumbprint", - protocol: "protocol", - itemHash: "itemHash", - adjacencyList: "adjacencyList" - }; - function Dataset(o) { - utils.bindAll(this); - if (utils.isString(o.template) && !o.engine) { - $.error("no template engine specified"); - } - if (!o.local && !o.prefetch && !o.remote) { - $.error("one of local, prefetch, or remote is required"); - } - this.name = o.name || utils.getUniqueId(); - this.limit = o.limit || 5; - this.minLength = o.minLength || 1; - this.header = o.header; - this.footer = o.footer; - this.valueKey = o.valueKey || "value"; - this.template = compileTemplate(o.template, o.engine, this.valueKey); - this.local = o.local; - this.prefetch = o.prefetch; - this.remote = o.remote; - this.itemHash = {}; - this.adjacencyList = {}; - this.storage = o.name ? new PersistentStorage(o.name) : null; - } - utils.mixin(Dataset.prototype, { - _processLocalData: function(data) { - this._mergeProcessedData(this._processData(data)); - }, - _loadPrefetchData: function(o) { - var that = this, thumbprint = VERSION + (o.thumbprint || ""), storedThumbprint, storedProtocol, storedItemHash, storedAdjacencyList, isExpired, deferred; - if (this.storage) { - storedThumbprint = this.storage.get(keys.thumbprint); - storedProtocol = this.storage.get(keys.protocol); - storedItemHash = this.storage.get(keys.itemHash); - storedAdjacencyList = this.storage.get(keys.adjacencyList); - } - isExpired = storedThumbprint !== thumbprint || storedProtocol !== utils.getProtocol(); - o = utils.isString(o) ? { - url: o - } : o; - o.ttl = utils.isNumber(o.ttl) ? o.ttl : 24 * 60 * 60 * 1e3; - if (storedItemHash && storedAdjacencyList && !isExpired) { - this._mergeProcessedData({ - itemHash: storedItemHash, - adjacencyList: storedAdjacencyList - }); - deferred = $.Deferred().resolve(); - } else { - deferred = $.getJSON(o.url).done(processPrefetchData); - } - return deferred; - function processPrefetchData(data) { - var filteredData = o.filter ? o.filter(data) : data, processedData = that._processData(filteredData), itemHash = processedData.itemHash, adjacencyList = processedData.adjacencyList; - if (that.storage) { - that.storage.set(keys.itemHash, itemHash, o.ttl); - that.storage.set(keys.adjacencyList, adjacencyList, o.ttl); - that.storage.set(keys.thumbprint, thumbprint, o.ttl); - that.storage.set(keys.protocol, utils.getProtocol(), o.ttl); - } - that._mergeProcessedData(processedData); - } - }, - _transformDatum: function(datum) { - var value = utils.isString(datum) ? datum : datum[this.valueKey], tokens = datum.tokens || utils.tokenizeText(value), item = { - value: value, - tokens: tokens - }; - if (utils.isString(datum)) { - item.datum = {}; - item.datum[this.valueKey] = datum; - } else { - item.datum = datum; - } - item.tokens = utils.filter(item.tokens, function(token) { - return !utils.isBlankString(token); - }); - item.tokens = utils.map(item.tokens, function(token) { - return token.toLowerCase(); - }); - return item; - }, - _processData: function(data) { - var that = this, itemHash = {}, adjacencyList = {}; - utils.each(data, function(i, datum) { - var item = that._transformDatum(datum), id = utils.getUniqueId(item.value); - itemHash[id] = item; - utils.each(item.tokens, function(i, token) { - var character = token.charAt(0), adjacency = adjacencyList[character] || (adjacencyList[character] = [ id ]); - !~utils.indexOf(adjacency, id) && adjacency.push(id); - }); - }); - return { - itemHash: itemHash, - adjacencyList: adjacencyList - }; - }, - _mergeProcessedData: function(processedData) { - var that = this; - utils.mixin(this.itemHash, processedData.itemHash); - utils.each(processedData.adjacencyList, function(character, adjacency) { - var masterAdjacency = that.adjacencyList[character]; - that.adjacencyList[character] = masterAdjacency ? masterAdjacency.concat(adjacency) : adjacency; - }); - }, - _getLocalSuggestions: function(terms) { - var that = this, firstChars = [], lists = [], shortestList, suggestions = []; - utils.each(terms, function(i, term) { - var firstChar = term.charAt(0); - !~utils.indexOf(firstChars, firstChar) && firstChars.push(firstChar); - }); - utils.each(firstChars, function(i, firstChar) { - var list = that.adjacencyList[firstChar]; - if (!list) { - return false; - } - lists.push(list); - if (!shortestList || list.length < shortestList.length) { - shortestList = list; - } - }); - if (lists.length < firstChars.length) { - return []; - } - utils.each(shortestList, function(i, id) { - var item = that.itemHash[id], isCandidate, isMatch; - isCandidate = utils.every(lists, function(list) { - return ~utils.indexOf(list, id); - }); - isMatch = isCandidate && utils.every(terms, function(term) { - return utils.some(item.tokens, function(token) { - return token.indexOf(term) === 0; - }); - }); - isMatch && suggestions.push(item); - }); - return suggestions; - }, - initialize: function() { - var deferred; - this.local && this._processLocalData(this.local); - this.transport = this.remote ? new Transport(this.remote) : null; - deferred = this.prefetch ? this._loadPrefetchData(this.prefetch) : $.Deferred().resolve(); - this.local = this.prefetch = this.remote = null; - this.initialize = function() { - return deferred; - }; - return deferred; - }, - getSuggestions: function(query, cb) { - var that = this, terms, suggestions, cacheHit = false; - if (query.length < this.minLength) { - return; - } - terms = utils.tokenizeQuery(query); - suggestions = this._getLocalSuggestions(terms).slice(0, this.limit); - if (suggestions.length < this.limit && this.transport) { - cacheHit = this.transport.get(query, processRemoteData); - } - !cacheHit && cb && cb(suggestions); - function processRemoteData(data) { - suggestions = suggestions.slice(0); - utils.each(data, function(i, datum) { - var item = that._transformDatum(datum), isDuplicate; - isDuplicate = utils.some(suggestions, function(suggestion) { - return item.value === suggestion.value; - }); - !isDuplicate && suggestions.push(item); - return suggestions.length < that.limit; - }); - cb && cb(suggestions); - } - } - }); - return Dataset; - function compileTemplate(template, engine, valueKey) { - var renderFn, compiledTemplate; - if (utils.isFunction(template)) { - renderFn = template; - } else if (utils.isString(template)) { - compiledTemplate = engine.compile(template); - renderFn = utils.bind(compiledTemplate.render, compiledTemplate); - } else { - renderFn = function(context) { - return "" + context[valueKey] + "
"; - }; - } - return renderFn; - } - }(); - var InputView = function() { - function InputView(o) { - var that = this; - utils.bindAll(this); - this.specialKeyCodeMap = { - 9: "tab", - 27: "esc", - 37: "left", - 39: "right", - 13: "enter", - 38: "up", - 40: "down" - }; - this.$hint = $(o.hint); - this.$input = $(o.input).on("blur.tt", this._handleBlur).on("focus.tt", this._handleFocus).on("keydown.tt", this._handleSpecialKeyEvent); - if (!utils.isMsie()) { - this.$input.on("input.tt", this._compareQueryToInputValue); - } else { - this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { - if (that.specialKeyCodeMap[$e.which || $e.keyCode]) { - return; - } - utils.defer(that._compareQueryToInputValue); - }); - } - this.query = this.$input.val(); - this.$overflowHelper = buildOverflowHelper(this.$input); - } - utils.mixin(InputView.prototype, EventTarget, { - _handleFocus: function() { - this.trigger("focused"); - }, - _handleBlur: function() { - this.trigger("blured"); - }, - _handleSpecialKeyEvent: function($e) { - var keyName = this.specialKeyCodeMap[$e.which || $e.keyCode]; - keyName && this.trigger(keyName + "Keyed", $e); - }, - _compareQueryToInputValue: function() { - var inputValue = this.getInputValue(), isSameQuery = compareQueries(this.query, inputValue), isSameQueryExceptWhitespace = isSameQuery ? this.query.length !== inputValue.length : false; - if (isSameQueryExceptWhitespace) { - this.trigger("whitespaceChanged", { - value: this.query - }); - } else if (!isSameQuery) { - this.trigger("queryChanged", { - value: this.query = inputValue - }); - } - }, - destroy: function() { - this.$hint.off(".tt"); - this.$input.off(".tt"); - this.$hint = this.$input = this.$overflowHelper = null; - }, - focus: function() { - this.$input.focus(); - }, - blur: function() { - this.$input.blur(); - }, - getQuery: function() { - return this.query; - }, - setQuery: function(query) { - this.query = query; - }, - getInputValue: function() { - return this.$input.val(); - }, - setInputValue: function(value, silent) { - this.$input.val(value); - !silent && this._compareQueryToInputValue(); - }, - getHintValue: function() { - return this.$hint.val(); - }, - setHintValue: function(value) { - this.$hint.val(value); - }, - getLanguageDirection: function() { - return (this.$input.css("direction") || "ltr").toLowerCase(); - }, - isOverflow: function() { - this.$overflowHelper.text(this.getInputValue()); - return this.$overflowHelper.width() > this.$input.width(); - }, - isCursorAtEnd: function() { - var valueLength = this.$input.val().length, selectionStart = this.$input[0].selectionStart, range; - if (utils.isNumber(selectionStart)) { - return selectionStart === valueLength; - } else if (document.selection) { - range = document.selection.createRange(); - range.moveStart("character", -valueLength); - return valueLength === range.text.length; - } - return true; - } - }); - return InputView; - function buildOverflowHelper($input) { - return $("").css({ - position: "absolute", - left: "-9999px", - visibility: "hidden", - whiteSpace: "nowrap", - fontFamily: $input.css("font-family"), - fontSize: $input.css("font-size"), - fontStyle: $input.css("font-style"), - fontVariant: $input.css("font-variant"), - fontWeight: $input.css("font-weight"), - wordSpacing: $input.css("word-spacing"), - letterSpacing: $input.css("letter-spacing"), - textIndent: $input.css("text-indent"), - textRendering: $input.css("text-rendering"), - textTransform: $input.css("text-transform") - }).insertAfter($input); - } - function compareQueries(a, b) { - a = (a || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - b = (b || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - return a === b; - } - }(); - var DropdownView = function() { - var html = { - suggestionsList: '' - }, css = { - suggestionsList: { - display: "block" - }, - suggestion: { - whiteSpace: "nowrap", - cursor: "pointer" - }, - suggestionChild: { - whiteSpace: "normal" - } - }; - function DropdownView(o) { - utils.bindAll(this); - this.isOpen = false; - this.isEmpty = true; - this.isMouseOverDropdown = false; - this.$menu = $(o.menu).on("mouseenter.tt", this._handleMouseenter).on("mouseleave.tt", this._handleMouseleave).on("click.tt", ".tt-suggestion", this._handleSelection).on("mouseover.tt", ".tt-suggestion", this._handleMouseover); - } - utils.mixin(DropdownView.prototype, EventTarget, { - _handleMouseenter: function() { - this.isMouseOverDropdown = true; - }, - _handleMouseleave: function() { - this.isMouseOverDropdown = false; - }, - _handleMouseover: function($e) { - var $suggestion = $($e.currentTarget); - this._getSuggestions().removeClass("tt-is-under-cursor"); - $suggestion.addClass("tt-is-under-cursor"); - }, - _handleSelection: function($e) { - var $suggestion = $($e.currentTarget); - this.trigger("suggestionSelected", extractSuggestion($suggestion)); - }, - _show: function() { - this.$menu.css("display", "block"); - }, - _hide: function() { - this.$menu.hide(); - }, - _moveCursor: function(increment) { - var $suggestions, $cur, nextIndex, $underCursor; - if (!this.isVisible()) { - return; - } - $suggestions = this._getSuggestions(); - $cur = $suggestions.filter(".tt-is-under-cursor"); - $cur.removeClass("tt-is-under-cursor"); - nextIndex = $suggestions.index($cur) + increment; - nextIndex = (nextIndex + 1) % ($suggestions.length + 1) - 1; - if (nextIndex === -1) { - this.trigger("cursorRemoved"); - return; - } else if (nextIndex < -1) { - nextIndex = $suggestions.length - 1; - } - $underCursor = $suggestions.eq(nextIndex).addClass("tt-is-under-cursor"); - this._ensureVisibility($underCursor); - this.trigger("cursorMoved", extractSuggestion($underCursor)); - }, - _getSuggestions: function() { - return this.$menu.find(".tt-suggestions > .tt-suggestion"); - }, - _ensureVisibility: function($el) { - var menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10), menuScrollTop = this.$menu.scrollTop(), elTop = $el.position().top, elBottom = elTop + $el.outerHeight(true); - if (elTop < 0) { - this.$menu.scrollTop(menuScrollTop + elTop); - } else if (menuHeight < elBottom) { - this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); - } - }, - destroy: function() { - this.$menu.off(".tt"); - this.$menu = null; - }, - isVisible: function() { - return this.isOpen && !this.isEmpty; - }, - closeUnlessMouseIsOverDropdown: function() { - if (!this.isMouseOverDropdown) { - this.close(); - } - }, - close: function() { - if (this.isOpen) { - this.isOpen = false; - this.isMouseOverDropdown = false; - this._hide(); - this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor"); - this.trigger("closed"); - } - }, - open: function() { - if (!this.isOpen) { - this.isOpen = true; - !this.isEmpty && this._show(); - this.trigger("opened"); - } - }, - setLanguageDirection: function(dir) { - var ltrCss = { - left: "0", - right: "auto" - }, rtlCss = { - left: "auto", - right: " 0" - }; - dir === "ltr" ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss); - }, - moveCursorUp: function() { - this._moveCursor(-1); - }, - moveCursorDown: function() { - this._moveCursor(+1); - }, - getSuggestionUnderCursor: function() { - var $suggestion = this._getSuggestions().filter(".tt-is-under-cursor").first(); - return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; - }, - getFirstSuggestion: function() { - var $suggestion = this._getSuggestions().first(); - return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; - }, - renderSuggestions: function(dataset, suggestions) { - var datasetClassName = "tt-dataset-" + dataset.name, - wrapper = '