Merge pull request #13 from cervinko/development
Better Author and Tags editing, better multi-author support
This commit is contained in:
		
						commit
						24b2b0a1ce
					
				
							
								
								
									
										189
									
								
								cps/static/css/typeahead.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								cps/static/css/typeahead.css
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,189 @@
 | 
			
		|||
/*
 | 
			
		||||
 * typehead.js-bootstrap3.less
 | 
			
		||||
 * @version 0.2.3
 | 
			
		||||
 * https://github.com/hyspace/typeahead.js-bootstrap3.less
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the MIT license:
 | 
			
		||||
 * http://www.opensource.org/licenses/MIT
 | 
			
		||||
 */
 | 
			
		||||
.has-warning .twitter-typeahead .tt-input,
 | 
			
		||||
.has-warning .twitter-typeahead .tt-hint {
 | 
			
		||||
  border-color: #8a6d3b;
 | 
			
		||||
  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
 | 
			
		||||
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
 | 
			
		||||
}
 | 
			
		||||
.has-warning .twitter-typeahead .tt-input:focus,
 | 
			
		||||
.has-warning .twitter-typeahead .tt-hint:focus {
 | 
			
		||||
  border-color: #66512c;
 | 
			
		||||
  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
 | 
			
		||||
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
 | 
			
		||||
}
 | 
			
		||||
.has-error .twitter-typeahead .tt-input,
 | 
			
		||||
.has-error .twitter-typeahead .tt-hint {
 | 
			
		||||
  border-color: #a94442;
 | 
			
		||||
  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
 | 
			
		||||
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
 | 
			
		||||
}
 | 
			
		||||
.has-error .twitter-typeahead .tt-input:focus,
 | 
			
		||||
.has-error .twitter-typeahead .tt-hint:focus {
 | 
			
		||||
  border-color: #843534;
 | 
			
		||||
  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
 | 
			
		||||
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
 | 
			
		||||
}
 | 
			
		||||
.has-success .twitter-typeahead .tt-input,
 | 
			
		||||
.has-success .twitter-typeahead .tt-hint {
 | 
			
		||||
  border-color: #3c763d;
 | 
			
		||||
  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
 | 
			
		||||
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
 | 
			
		||||
}
 | 
			
		||||
.has-success .twitter-typeahead .tt-input:focus,
 | 
			
		||||
.has-success .twitter-typeahead .tt-hint:focus {
 | 
			
		||||
  border-color: #2b542c;
 | 
			
		||||
  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
 | 
			
		||||
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
 | 
			
		||||
}
 | 
			
		||||
.input-group .twitter-typeahead:first-child .tt-input,
 | 
			
		||||
.input-group .twitter-typeahead:first-child .tt-hint {
 | 
			
		||||
  border-bottom-left-radius: 4px;
 | 
			
		||||
  border-top-left-radius: 4px;
 | 
			
		||||
}
 | 
			
		||||
.input-group .twitter-typeahead:last-child .tt-input,
 | 
			
		||||
.input-group .twitter-typeahead:last-child .tt-hint {
 | 
			
		||||
  border-bottom-right-radius: 4px;
 | 
			
		||||
  border-top-right-radius: 4px;
 | 
			
		||||
}
 | 
			
		||||
.input-group.input-group-sm .twitter-typeahead .tt-input,
 | 
			
		||||
.input-group.input-group-sm .twitter-typeahead .tt-hint {
 | 
			
		||||
  height: 30px;
 | 
			
		||||
  padding: 5px 10px;
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  line-height: 1.5;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
select.input-group.input-group-sm .twitter-typeahead .tt-input,
 | 
			
		||||
select.input-group.input-group-sm .twitter-typeahead .tt-hint {
 | 
			
		||||
  height: 30px;
 | 
			
		||||
  line-height: 30px;
 | 
			
		||||
}
 | 
			
		||||
textarea.input-group.input-group-sm .twitter-typeahead .tt-input,
 | 
			
		||||
textarea.input-group.input-group-sm .twitter-typeahead .tt-hint,
 | 
			
		||||
select[multiple].input-group.input-group-sm .twitter-typeahead .tt-input,
 | 
			
		||||
select[multiple].input-group.input-group-sm .twitter-typeahead .tt-hint {
 | 
			
		||||
  height: auto;
 | 
			
		||||
}
 | 
			
		||||
.input-group.input-group-sm .twitter-typeahead:not(:first-child):not(:last-child) .tt-input,
 | 
			
		||||
.input-group.input-group-sm .twitter-typeahead:not(:first-child):not(:last-child) .tt-hint {
 | 
			
		||||
  border-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
.input-group.input-group-sm .twitter-typeahead:first-child .tt-input,
 | 
			
		||||
.input-group.input-group-sm .twitter-typeahead:first-child .tt-hint {
 | 
			
		||||
  border-bottom-left-radius: 3px;
 | 
			
		||||
  border-top-left-radius: 3px;
 | 
			
		||||
  border-bottom-right-radius: 0;
 | 
			
		||||
  border-top-right-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
.input-group.input-group-sm .twitter-typeahead:last-child .tt-input,
 | 
			
		||||
.input-group.input-group-sm .twitter-typeahead:last-child .tt-hint {
 | 
			
		||||
  border-bottom-left-radius: 0;
 | 
			
		||||
  border-top-left-radius: 0;
 | 
			
		||||
  border-bottom-right-radius: 3px;
 | 
			
		||||
  border-top-right-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
.input-group.input-group-lg .twitter-typeahead .tt-input,
 | 
			
		||||
.input-group.input-group-lg .twitter-typeahead .tt-hint {
 | 
			
		||||
  height: 46px;
 | 
			
		||||
  padding: 10px 16px;
 | 
			
		||||
  font-size: 18px;
 | 
			
		||||
  line-height: 1.33;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
}
 | 
			
		||||
select.input-group.input-group-lg .twitter-typeahead .tt-input,
 | 
			
		||||
select.input-group.input-group-lg .twitter-typeahead .tt-hint {
 | 
			
		||||
  height: 46px;
 | 
			
		||||
  line-height: 46px;
 | 
			
		||||
}
 | 
			
		||||
textarea.input-group.input-group-lg .twitter-typeahead .tt-input,
 | 
			
		||||
textarea.input-group.input-group-lg .twitter-typeahead .tt-hint,
 | 
			
		||||
select[multiple].input-group.input-group-lg .twitter-typeahead .tt-input,
 | 
			
		||||
select[multiple].input-group.input-group-lg .twitter-typeahead .tt-hint {
 | 
			
		||||
  height: auto;
 | 
			
		||||
}
 | 
			
		||||
.input-group.input-group-lg .twitter-typeahead:not(:first-child):not(:last-child) .tt-input,
 | 
			
		||||
.input-group.input-group-lg .twitter-typeahead:not(:first-child):not(:last-child) .tt-hint {
 | 
			
		||||
  border-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
.input-group.input-group-lg .twitter-typeahead:first-child .tt-input,
 | 
			
		||||
.input-group.input-group-lg .twitter-typeahead:first-child .tt-hint {
 | 
			
		||||
  border-bottom-left-radius: 6px;
 | 
			
		||||
  border-top-left-radius: 6px;
 | 
			
		||||
  border-bottom-right-radius: 0;
 | 
			
		||||
  border-top-right-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
.input-group.input-group-lg .twitter-typeahead:last-child .tt-input,
 | 
			
		||||
.input-group.input-group-lg .twitter-typeahead:last-child .tt-hint {
 | 
			
		||||
  border-bottom-left-radius: 0;
 | 
			
		||||
  border-top-left-radius: 0;
 | 
			
		||||
  border-bottom-right-radius: 6px;
 | 
			
		||||
  border-top-right-radius: 6px;
 | 
			
		||||
}
 | 
			
		||||
.twitter-typeahead {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
.input-group .twitter-typeahead {
 | 
			
		||||
  display: table-cell !important;
 | 
			
		||||
  float: left;
 | 
			
		||||
}
 | 
			
		||||
.twitter-typeahead .tt-hint {
 | 
			
		||||
  color: #999999;
 | 
			
		||||
}
 | 
			
		||||
.twitter-typeahead .tt-input {
 | 
			
		||||
  z-index: 2;
 | 
			
		||||
}
 | 
			
		||||
.twitter-typeahead .tt-input[disabled],
 | 
			
		||||
.twitter-typeahead .tt-input[readonly],
 | 
			
		||||
fieldset[disabled] .twitter-typeahead .tt-input {
 | 
			
		||||
  cursor: not-allowed;
 | 
			
		||||
  background-color: #eeeeee !important;
 | 
			
		||||
}
 | 
			
		||||
.tt-dropdown-menu {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 100%;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  z-index: 1000;
 | 
			
		||||
  min-width: 160px;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  padding: 5px 0;
 | 
			
		||||
  margin: 2px 0 0;
 | 
			
		||||
  list-style: none;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  background-color: #ffffff;
 | 
			
		||||
  border: 1px solid #cccccc;
 | 
			
		||||
  border: 1px solid rgba(0, 0, 0, 0.15);
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
 | 
			
		||||
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
 | 
			
		||||
  background-clip: padding-box;
 | 
			
		||||
  *border-right-width: 2px;
 | 
			
		||||
  *border-bottom-width: 2px;
 | 
			
		||||
}
 | 
			
		||||
.tt-dropdown-menu .tt-suggestion {
 | 
			
		||||
  display: block;
 | 
			
		||||
  padding: 3px 20px;
 | 
			
		||||
  clear: both;
 | 
			
		||||
  font-weight: normal;
 | 
			
		||||
  line-height: 1.42857143;
 | 
			
		||||
  color: #333333;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
.tt-dropdown-menu .tt-suggestion.tt-cursor {
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
  outline: 0;
 | 
			
		||||
  background-color: #f5f5f5;
 | 
			
		||||
  color: #262626;
 | 
			
		||||
}
 | 
			
		||||
.tt-dropdown-menu .tt-suggestion.tt-cursor a {
 | 
			
		||||
  color: #262626;
 | 
			
		||||
}
 | 
			
		||||
.tt-dropdown-menu .tt-suggestion p {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,93 +0,0 @@
 | 
			
		|||
span.twitter-typeahead .tt-menu,
 | 
			
		||||
span.twitter-typeahead .tt-dropdown-menu {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 100%;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  z-index: 1000;
 | 
			
		||||
  display: none;
 | 
			
		||||
  float: left;
 | 
			
		||||
  min-width: 160px;
 | 
			
		||||
  padding: 5px 0;
 | 
			
		||||
  margin: 2px 0 0;
 | 
			
		||||
  list-style: none;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  text-align: left;
 | 
			
		||||
  background-color: #ffffff;
 | 
			
		||||
  border: 1px solid #cccccc;
 | 
			
		||||
  border: 1px solid rgba(0, 0, 0, 0.15);
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
 | 
			
		||||
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
 | 
			
		||||
  background-clip: padding-box;
 | 
			
		||||
}
 | 
			
		||||
span.twitter-typeahead .tt-suggestion {
 | 
			
		||||
  display: block;
 | 
			
		||||
  padding: 3px 20px;
 | 
			
		||||
  clear: both;
 | 
			
		||||
  font-weight: normal;
 | 
			
		||||
  line-height: 1.42857143;
 | 
			
		||||
  color: #333333;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
span.twitter-typeahead .tt-suggestion.tt-cursor,
 | 
			
		||||
span.twitter-typeahead .tt-suggestion:hover,
 | 
			
		||||
span.twitter-typeahead .tt-suggestion:focus {
 | 
			
		||||
  color: #ffffff;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
  outline: 0;
 | 
			
		||||
  background-color: #337ab7;
 | 
			
		||||
}
 | 
			
		||||
.input-group.input-group-lg span.twitter-typeahead .form-control {
 | 
			
		||||
  height: 46px;
 | 
			
		||||
  padding: 10px 16px;
 | 
			
		||||
  font-size: 18px;
 | 
			
		||||
  line-height: 1.3333333;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
}
 | 
			
		||||
.input-group.input-group-sm span.twitter-typeahead .form-control {
 | 
			
		||||
  height: 30px;
 | 
			
		||||
  padding: 5px 10px;
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  line-height: 1.5;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
span.twitter-typeahead {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
.input-group span.twitter-typeahead {
 | 
			
		||||
  display: block !important;
 | 
			
		||||
  height: 34px;
 | 
			
		||||
}
 | 
			
		||||
.input-group span.twitter-typeahead .tt-menu,
 | 
			
		||||
.input-group span.twitter-typeahead .tt-dropdown-menu {
 | 
			
		||||
  top: 32px !important;
 | 
			
		||||
}
 | 
			
		||||
.input-group span.twitter-typeahead:not(:first-child):not(:last-child) .form-control {
 | 
			
		||||
  border-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
.input-group span.twitter-typeahead:first-child .form-control {
 | 
			
		||||
  border-top-left-radius: 4px;
 | 
			
		||||
  border-bottom-left-radius: 4px;
 | 
			
		||||
  border-top-right-radius: 0;
 | 
			
		||||
  border-bottom-right-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
.input-group span.twitter-typeahead:last-child .form-control {
 | 
			
		||||
  border-top-left-radius: 0;
 | 
			
		||||
  border-bottom-left-radius: 0;
 | 
			
		||||
  border-top-right-radius: 4px;
 | 
			
		||||
  border-bottom-right-radius: 4px;
 | 
			
		||||
}
 | 
			
		||||
.input-group.input-group-sm span.twitter-typeahead {
 | 
			
		||||
  height: 30px;
 | 
			
		||||
}
 | 
			
		||||
.input-group.input-group-sm span.twitter-typeahead .tt-menu,
 | 
			
		||||
.input-group.input-group-sm span.twitter-typeahead .tt-dropdown-menu {
 | 
			
		||||
  top: 30px !important;
 | 
			
		||||
}
 | 
			
		||||
.input-group.input-group-lg span.twitter-typeahead {
 | 
			
		||||
  height: 46px;
 | 
			
		||||
}
 | 
			
		||||
.input-group.input-group-lg span.twitter-typeahead .tt-menu,
 | 
			
		||||
.input-group.input-group-lg span.twitter-typeahead .tt-dropdown-menu {
 | 
			
		||||
  top: 46px !important;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										523
									
								
								cps/static/js/bootstrap3-typeahead.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										523
									
								
								cps/static/js/bootstrap3-typeahead.js
									
									
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -1,523 +0,0 @@
 | 
			
		|||
/* =============================================================
 | 
			
		||||
 * bootstrap3-typeahead.js v3.1.0
 | 
			
		||||
 * https://github.com/bassjobsen/Bootstrap-3-Typeahead
 | 
			
		||||
 * =============================================================
 | 
			
		||||
 * Original written by @mdo and @fat
 | 
			
		||||
 * =============================================================
 | 
			
		||||
 * Copyright 2014 Bass Jobsen @bassjobsen
 | 
			
		||||
 *
 | 
			
		||||
 * 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 (root, factory) {
 | 
			
		||||
 | 
			
		||||
  'use strict';
 | 
			
		||||
 | 
			
		||||
  // CommonJS module is defined
 | 
			
		||||
  if (typeof module !== 'undefined' && module.exports) {
 | 
			
		||||
    module.exports = factory(require('jquery'));
 | 
			
		||||
  }
 | 
			
		||||
  // AMD module is defined
 | 
			
		||||
  else if (typeof define === 'function' && define.amd) {
 | 
			
		||||
    define(['jquery'], function ($) {
 | 
			
		||||
      return factory ($);
 | 
			
		||||
    });
 | 
			
		||||
  } else {
 | 
			
		||||
    factory(root.jQuery);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}(this, function ($) {
 | 
			
		||||
 | 
			
		||||
  'use strict';
 | 
			
		||||
  // jshint laxcomma: true
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 /* 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.select = this.options.select || this.select;
 | 
			
		||||
    this.autoSelect = typeof this.options.autoSelect == 'boolean' ? this.options.autoSelect : true;
 | 
			
		||||
    this.highlighter = this.options.highlighter || this.highlighter;
 | 
			
		||||
    this.render = this.options.render || this.render;
 | 
			
		||||
    this.updater = this.options.updater || this.updater;
 | 
			
		||||
    this.displayText = this.options.displayText || this.displayText;
 | 
			
		||||
    this.source = this.options.source;
 | 
			
		||||
    this.delay = this.options.delay;
 | 
			
		||||
    this.$menu = $(this.options.menu);
 | 
			
		||||
    this.$appendTo = this.options.appendTo ? $(this.options.appendTo) : null;
 | 
			
		||||
    this.shown = false;
 | 
			
		||||
    this.listen();
 | 
			
		||||
    this.showHintOnFocus = typeof this.options.showHintOnFocus == 'boolean' ? this.options.showHintOnFocus : false;
 | 
			
		||||
    this.afterSelect = this.options.afterSelect;
 | 
			
		||||
    this.addItem = false;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  Typeahead.prototype = {
 | 
			
		||||
 | 
			
		||||
    constructor: Typeahead,
 | 
			
		||||
 | 
			
		||||
    select: function () {
 | 
			
		||||
      var val = this.$menu.find('.active').data('value');
 | 
			
		||||
      this.$element.data('active', val);
 | 
			
		||||
      if(this.autoSelect || val) {
 | 
			
		||||
        var newVal = this.updater(val);
 | 
			
		||||
        // Updater can be set to any random functions via "options" parameter in constructor above.
 | 
			
		||||
        // Add null check for cases when updater returns void or undefined.
 | 
			
		||||
        if (!newVal) {
 | 
			
		||||
          newVal = "";
 | 
			
		||||
        }
 | 
			
		||||
        this.$element
 | 
			
		||||
          .val(this.displayText(newVal) || newVal)
 | 
			
		||||
          .change();
 | 
			
		||||
        this.afterSelect(newVal);
 | 
			
		||||
      }
 | 
			
		||||
      return this.hide();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    updater: function (item) {
 | 
			
		||||
      return item;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    setSource: function (source) {
 | 
			
		||||
      this.source = source;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    show: function () {
 | 
			
		||||
      var pos = $.extend({}, this.$element.position(), {
 | 
			
		||||
        height: this.$element[0].offsetHeight
 | 
			
		||||
      }), scrollHeight;
 | 
			
		||||
 | 
			
		||||
      scrollHeight = typeof this.options.scrollHeight == 'function' ?
 | 
			
		||||
          this.options.scrollHeight.call() :
 | 
			
		||||
          this.options.scrollHeight;
 | 
			
		||||
 | 
			
		||||
      var element;
 | 
			
		||||
      if (this.shown) {
 | 
			
		||||
        element = this.$menu;
 | 
			
		||||
      } else if (this.$appendTo) {
 | 
			
		||||
        element = this.$menu.appendTo(this.$appendTo);
 | 
			
		||||
      } else {
 | 
			
		||||
        element = this.$menu.insertAfter(this.$element);
 | 
			
		||||
      }
 | 
			
		||||
      element.css({
 | 
			
		||||
          top: pos.top + pos.height + scrollHeight
 | 
			
		||||
        , left: pos.left
 | 
			
		||||
        })
 | 
			
		||||
        .show();
 | 
			
		||||
 | 
			
		||||
      this.shown = true;
 | 
			
		||||
      return this;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    hide: function () {
 | 
			
		||||
      this.$menu.hide();
 | 
			
		||||
      this.shown = false;
 | 
			
		||||
      return this;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    lookup: function (query) {
 | 
			
		||||
      var items;
 | 
			
		||||
      if (typeof(query) != 'undefined' && query !== null) {
 | 
			
		||||
        this.query = query;
 | 
			
		||||
      } else {
 | 
			
		||||
        this.query = this.$element.val() ||  '';
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this.query.length < this.options.minLength && !this.options.showHintOnFocus) {
 | 
			
		||||
        return this.shown ? this.hide() : this;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      var worker = $.proxy(function() {
 | 
			
		||||
 | 
			
		||||
        if($.isFunction(this.source)) this.source(this.query, $.proxy(this.process, this));
 | 
			
		||||
        else if (this.source) {
 | 
			
		||||
          this.process(this.source);
 | 
			
		||||
        }
 | 
			
		||||
      }, this);
 | 
			
		||||
 | 
			
		||||
      clearTimeout(this.lookupWorker);
 | 
			
		||||
      this.lookupWorker = setTimeout(worker, this.delay);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    process: function (items) {
 | 
			
		||||
      var that = this;
 | 
			
		||||
 | 
			
		||||
      items = $.grep(items, function (item) {
 | 
			
		||||
        return that.matcher(item);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      items = this.sorter(items);
 | 
			
		||||
 | 
			
		||||
      if (!items.length && !this.options.addItem) {
 | 
			
		||||
        return this.shown ? this.hide() : this;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (items.length > 0) {
 | 
			
		||||
        this.$element.data('active', items[0]);
 | 
			
		||||
      } else {
 | 
			
		||||
        this.$element.data('active', null);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Add item
 | 
			
		||||
      if (this.options.addItem){
 | 
			
		||||
        items.push(this.options.addItem);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this.options.items == 'all') {
 | 
			
		||||
        return this.render(items).show();
 | 
			
		||||
      } else {
 | 
			
		||||
        return this.render(items.slice(0, this.options.items)).show();
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    matcher: function (item) {
 | 
			
		||||
    var it = this.displayText(item);
 | 
			
		||||
      return ~it.toLowerCase().indexOf(this.query.toLowerCase());
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    sorter: function (items) {
 | 
			
		||||
      var beginswith = []
 | 
			
		||||
        , caseSensitive = []
 | 
			
		||||
        , caseInsensitive = []
 | 
			
		||||
        , item;
 | 
			
		||||
 | 
			
		||||
      while ((item = items.shift())) {
 | 
			
		||||
        var it = this.displayText(item);
 | 
			
		||||
        if (!it.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item);
 | 
			
		||||
        else if (~it.indexOf(this.query)) caseSensitive.push(item);
 | 
			
		||||
        else caseInsensitive.push(item);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return beginswith.concat(caseSensitive, caseInsensitive);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    highlighter: function (item) {
 | 
			
		||||
          var html = $('<div></div>');
 | 
			
		||||
          var query = this.query;
 | 
			
		||||
          var i = item.toLowerCase().indexOf(query.toLowerCase());
 | 
			
		||||
          var len, leftPart, middlePart, rightPart, strong;
 | 
			
		||||
          len = query.length;
 | 
			
		||||
          if(len === 0){
 | 
			
		||||
              return html.text(item).html();
 | 
			
		||||
          }
 | 
			
		||||
          while (i > -1) {
 | 
			
		||||
              leftPart = item.substr(0, i);
 | 
			
		||||
              middlePart = item.substr(i, len);
 | 
			
		||||
              rightPart = item.substr(i + len);
 | 
			
		||||
              strong = $('<strong></strong>').text(middlePart);
 | 
			
		||||
              html
 | 
			
		||||
                  .append(document.createTextNode(leftPart))
 | 
			
		||||
                  .append(strong);
 | 
			
		||||
              item = rightPart;
 | 
			
		||||
              i = item.toLowerCase().indexOf(query.toLowerCase());
 | 
			
		||||
          }
 | 
			
		||||
          return html.append(document.createTextNode(item)).html();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    render: function (items) {
 | 
			
		||||
      var that = this;
 | 
			
		||||
      var self = this;
 | 
			
		||||
      var activeFound = false;
 | 
			
		||||
      var data = [];
 | 
			
		||||
      var _category = that.options.separator;
 | 
			
		||||
 | 
			
		||||
      $.each(items, function (key,value) {
 | 
			
		||||
        // inject separator
 | 
			
		||||
        if (key > 0 && value[_category] !== items[key - 1][_category]){
 | 
			
		||||
          data.push({
 | 
			
		||||
              __type: 'divider'
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // inject category header
 | 
			
		||||
        if (value[_category] && (key === 0 || value[_category] !== items[key - 1][_category])){
 | 
			
		||||
          data.push({
 | 
			
		||||
              __type: 'category',
 | 
			
		||||
              name: value[_category]
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
        data.push(value);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      items = $(data).map(function (i, item) {
 | 
			
		||||
 | 
			
		||||
        if ((item.__type || false) == 'category'){
 | 
			
		||||
            return $(that.options.headerHtml).text(item.name)[0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ((item.__type || false) == 'divider'){
 | 
			
		||||
            return $(that.options.headerDivider)[0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var text = self.displayText(item);
 | 
			
		||||
        i = $(that.options.item).data('value', item);
 | 
			
		||||
        i.find('a').html(that.highlighter(text, item));
 | 
			
		||||
        if (text == self.$element.val()) {
 | 
			
		||||
            i.addClass('active');
 | 
			
		||||
            self.$element.data('active', item);
 | 
			
		||||
            activeFound = true;
 | 
			
		||||
        }
 | 
			
		||||
        return i[0];
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (this.autoSelect && !activeFound) {
 | 
			
		||||
        items.filter(':not(.dropdown-header)').first().addClass('active');
 | 
			
		||||
        this.$element.data('active', items.first().data('value'));
 | 
			
		||||
      }
 | 
			
		||||
      this.$menu.html(items);
 | 
			
		||||
      return this;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    displayText: function(item) {
 | 
			
		||||
      return typeof item !== 'undefined' && typeof item.name != 'undefined' && item.name || item;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    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');
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    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');
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    listen: function () {
 | 
			
		||||
      this.$element
 | 
			
		||||
        .on('focus',    $.proxy(this.focus, this))
 | 
			
		||||
        .on('blur',     $.proxy(this.blur, this))
 | 
			
		||||
        .on('keypress', $.proxy(this.keypress, this))
 | 
			
		||||
        .on('input',    $.proxy(this.input, 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))
 | 
			
		||||
        .on('mouseleave', 'li', $.proxy(this.mouseleave, this));
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    destroy : function () {
 | 
			
		||||
      this.$element.data('typeahead',null);
 | 
			
		||||
      this.$element.data('active',null);
 | 
			
		||||
      this.$element
 | 
			
		||||
        .off('focus')
 | 
			
		||||
        .off('blur')
 | 
			
		||||
        .off('keypress')
 | 
			
		||||
        .off('input')
 | 
			
		||||
        .off('keyup');
 | 
			
		||||
 | 
			
		||||
      if (this.eventSupported('keydown')) {
 | 
			
		||||
        this.$element.off('keydown');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.$menu.remove();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
          // with the shiftKey (this is actually the left parenthesis)
 | 
			
		||||
          if (e.shiftKey) return;
 | 
			
		||||
          e.preventDefault();
 | 
			
		||||
          this.prev();
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        case 40: // down arrow
 | 
			
		||||
          // with the shiftKey (this is actually the right parenthesis)
 | 
			
		||||
          if (e.shiftKey) return;
 | 
			
		||||
          e.preventDefault();
 | 
			
		||||
          this.next();
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    keydown: function (e) {
 | 
			
		||||
      this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27]);
 | 
			
		||||
      if (!this.shown && e.keyCode == 40) {
 | 
			
		||||
        this.lookup();
 | 
			
		||||
      } else {
 | 
			
		||||
        this.move(e);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    keypress: function (e) {
 | 
			
		||||
      if (this.suppressKeyPressRepeat) return;
 | 
			
		||||
      this.move(e);
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    input: function (e) {
 | 
			
		||||
      this.lookup();
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    keyup: function (e) {
 | 
			
		||||
      switch(e.keyCode) {
 | 
			
		||||
        case 40: // down arrow
 | 
			
		||||
        case 38: // up arrow
 | 
			
		||||
        case 16: // shift
 | 
			
		||||
        case 17: // ctrl
 | 
			
		||||
        case 18: // alt
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        case 9: // tab
 | 
			
		||||
        case 13: // enter
 | 
			
		||||
          if (!this.shown) return;
 | 
			
		||||
          this.select();
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        case 27: // escape
 | 
			
		||||
          if (!this.shown) return;
 | 
			
		||||
          this.hide();
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
   },
 | 
			
		||||
 | 
			
		||||
   focus: function (e) {
 | 
			
		||||
      if (!this.focused) {
 | 
			
		||||
        this.focused = true;
 | 
			
		||||
        if (this.options.showHintOnFocus) {
 | 
			
		||||
          this.lookup('');
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    blur: function (e) {
 | 
			
		||||
      this.focused = false;
 | 
			
		||||
      if (!this.mousedover && this.shown) this.hide();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    click: function (e) {
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
      this.select();
 | 
			
		||||
      this.$element.focus();
 | 
			
		||||
      this.hide();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    mouseenter: function (e) {
 | 
			
		||||
      this.mousedover = true;
 | 
			
		||||
      this.$menu.find('.active').removeClass('active');
 | 
			
		||||
      $(e.currentTarget).addClass('active');
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    mouseleave: function (e) {
 | 
			
		||||
      this.mousedover = false;
 | 
			
		||||
      if (!this.focused && this.shown) this.hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /* TYPEAHEAD PLUGIN DEFINITION
 | 
			
		||||
   * =========================== */
 | 
			
		||||
 | 
			
		||||
  var old = $.fn.typeahead;
 | 
			
		||||
 | 
			
		||||
  $.fn.typeahead = function (option) {
 | 
			
		||||
    var arg = arguments;
 | 
			
		||||
     if (typeof option == 'string' && option == 'getActive') {
 | 
			
		||||
        return this.data('active');
 | 
			
		||||
     }
 | 
			
		||||
    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]) {
 | 
			
		||||
        if (arg.length > 1) {
 | 
			
		||||
          data[option].apply(data, Array.prototype.slice.call(arg ,1));
 | 
			
		||||
        } else {
 | 
			
		||||
          data[option]();
 | 
			
		||||
        }
 | 
			
		||||
      } 
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  $.fn.typeahead.defaults = {
 | 
			
		||||
        source: [],
 | 
			
		||||
        items: 8,
 | 
			
		||||
        menu: '<ul class="typeahead dropdown-menu" role="listbox"></ul>',
 | 
			
		||||
        item: '<li><a class="dropdown-item" href="#" role="option"></a></li>',
 | 
			
		||||
        minLength: 1,
 | 
			
		||||
        scrollHeight: 0,
 | 
			
		||||
        autoSelect: true,
 | 
			
		||||
        afterSelect: $.noop,
 | 
			
		||||
        addItem: false,
 | 
			
		||||
        delay: 0,
 | 
			
		||||
        separator: 'category',
 | 
			
		||||
        headerHtml: '<li class="dropdown-header"></li>',
 | 
			
		||||
        headerDivider: '<li class="divider" role="separator"></li>'
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  $.fn.typeahead.Constructor = Typeahead;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 /* TYPEAHEAD NO CONFLICT
 | 
			
		||||
  * =================== */
 | 
			
		||||
 | 
			
		||||
  $.fn.typeahead.noConflict = function () {
 | 
			
		||||
    $.fn.typeahead = old;
 | 
			
		||||
    return this;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 /* TYPEAHEAD DATA-API
 | 
			
		||||
  * ================== */
 | 
			
		||||
 | 
			
		||||
  $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
 | 
			
		||||
    var $this = $(this);
 | 
			
		||||
    if ($this.data('typeahead')) return;
 | 
			
		||||
    $this.typeahead($this.data());
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
}));
 | 
			
		||||
							
								
								
									
										133
									
								
								cps/static/js/edit_books.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								cps/static/js/edit_books.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,133 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Created by SpeedProg on 05.04.2015.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    Takes a prefix, query typeahead callback, Bloodhound typeahead adapter
 | 
			
		||||
     and returns the completions it gets from the bloodhound engine prefixed.
 | 
			
		||||
     */
 | 
			
		||||
    function prefixed_source(prefix, query, cb, bh_adapter) {
 | 
			
		||||
        bh_adapter(query, function(retArray){
 | 
			
		||||
            var matches = [];
 | 
			
		||||
            for (var i = 0; i < retArray.length; i++) {
 | 
			
		||||
                var obj = {name : prefix + retArray[i].name};
 | 
			
		||||
                matches.push(obj);
 | 
			
		||||
            }
 | 
			
		||||
            cb(matches);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
   var authors = new Bloodhound({
 | 
			
		||||
        name: 'authors',
 | 
			
		||||
        datumTokenizer: function(datum) {
 | 
			
		||||
            return [datum.name];
 | 
			
		||||
        },
 | 
			
		||||
        queryTokenizer: Bloodhound.tokenizers.whitespace,
 | 
			
		||||
        remote: {
 | 
			
		||||
            url: '/get_authors_json?q=%QUERY'
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function authors_source(query, cb) {
 | 
			
		||||
        var bh_adapter = authors.ttAdapter();
 | 
			
		||||
 | 
			
		||||
        var tokens = query.split("&");
 | 
			
		||||
        var current_author = tokens[tokens.length-1].trim();
 | 
			
		||||
 | 
			
		||||
        tokens.splice(tokens.length-1, 1); // remove last element
 | 
			
		||||
        var prefix = "";
 | 
			
		||||
        for (var i = 0; i < tokens.length; i++) {
 | 
			
		||||
            var author = tokens[i].trim();
 | 
			
		||||
            prefix += author + " & ";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        prefixed_source(prefix, current_author, cb, bh_adapter);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    var promise = authors.initialize();
 | 
			
		||||
    promise.done(function(){
 | 
			
		||||
        $("#bookAuthor").typeahead(
 | 
			
		||||
                {
 | 
			
		||||
                    highlight: true, minLength: 1,
 | 
			
		||||
                    hint: true
 | 
			
		||||
                }, {
 | 
			
		||||
                    name: 'authors', displayKey: 'name',
 | 
			
		||||
                    source: authors_source
 | 
			
		||||
                }
 | 
			
		||||
        )
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    var series = new Bloodhound({
 | 
			
		||||
        name: 'series',
 | 
			
		||||
        datumTokenizer: function(datum) {
 | 
			
		||||
            return [datum.name];
 | 
			
		||||
        },
 | 
			
		||||
        queryTokenizer: function(query) {
 | 
			
		||||
            return [query];
 | 
			
		||||
        },
 | 
			
		||||
        remote: {
 | 
			
		||||
            url: '/get_series_json?q=',
 | 
			
		||||
            replace: function(url, query) {
 | 
			
		||||
                url_query = url+encodeURIComponent(query);
 | 
			
		||||
                return url_query;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    var promise = series.initialize();
 | 
			
		||||
    promise.done(function(){
 | 
			
		||||
        $("#series").typeahead(
 | 
			
		||||
                {
 | 
			
		||||
                    highlight: true, minLength: 0,
 | 
			
		||||
                    hint: true
 | 
			
		||||
                }, {
 | 
			
		||||
                    name: 'series', displayKey: 'name',
 | 
			
		||||
                    source: series.ttAdapter()
 | 
			
		||||
                }
 | 
			
		||||
        )
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    var tags = new Bloodhound({
 | 
			
		||||
        name: 'tags',
 | 
			
		||||
        datumTokenizer: function(datum) {
 | 
			
		||||
            return [datum.name];
 | 
			
		||||
        },
 | 
			
		||||
        queryTokenizer: function(query) {
 | 
			
		||||
            tokens = query.split(",");
 | 
			
		||||
            tokens = [tokens[tokens.length-1].trim()];
 | 
			
		||||
            return tokens
 | 
			
		||||
        },
 | 
			
		||||
        remote: {
 | 
			
		||||
            url: '/get_tags_json?q=%QUERY'
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function tag_source(query, cb) {
 | 
			
		||||
        var bh_adapter = tags.ttAdapter();
 | 
			
		||||
 | 
			
		||||
        var tokens = query.split(",");
 | 
			
		||||
        var current_tag = tokens[tokens.length-1].trim();
 | 
			
		||||
 | 
			
		||||
        tokens.splice(tokens.length-1, 1); // remove last element
 | 
			
		||||
        var prefix = "";
 | 
			
		||||
        for (var i = 0; i < tokens.length; i++) {
 | 
			
		||||
            var tag = tokens[i].trim();
 | 
			
		||||
            prefix += tag + ", ";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        prefixed_source(prefix, current_tag, cb, bh_adapter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var promise = tags.initialize();
 | 
			
		||||
    promise.done(function(){
 | 
			
		||||
        $("#tags").typeahead(
 | 
			
		||||
                {
 | 
			
		||||
                    highlight: true, minLength: 0,
 | 
			
		||||
                    hint: true
 | 
			
		||||
                }, {
 | 
			
		||||
                    name: 'tags', displayKey: 'name',
 | 
			
		||||
                    source: tag_source
 | 
			
		||||
                }
 | 
			
		||||
        )
 | 
			
		||||
    });
 | 
			
		||||
							
								
								
									
										1782
									
								
								cps/static/js/typeahead.bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1782
									
								
								cps/static/js/typeahead.bundle.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -11,9 +11,14 @@
 | 
			
		|||
    </div>
 | 
			
		||||
    <div class="col-sm-9 col-lg-9 book-meta">
 | 
			
		||||
      <h2>{{entry.title}}</h2>
 | 
			
		||||
      <p class="author"><a href="{{url_for('author', name=entry.authors[0].name) }}">{{entry.authors[0].name}}</a>
 | 
			
		||||
      <p class="author">
 | 
			
		||||
          {% for author in entry.authors %}
 | 
			
		||||
            <a href="{{url_for('author', name=author.name) }}">{{author.name}}</a>
 | 
			
		||||
            {% if not loop.last %}
 | 
			
		||||
              &
 | 
			
		||||
            {% endif %}
 | 
			
		||||
          {% endfor %}
 | 
			
		||||
        </p>
 | 
			
		||||
 | 
			
		||||
      {% if entry.ratings.__len__() > 0 %}
 | 
			
		||||
        <div class="rating">
 | 
			
		||||
        <p>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,26 +1,6 @@
 | 
			
		|||
{% extends "layout.html" %}
 | 
			
		||||
{% block body %}
 | 
			
		||||
{% if book %}
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
$(document).ready(function(){
 | 
			
		||||
	$('input.typeahead').typeahead({
 | 
			
		||||
		name: 'accounts',
 | 
			
		||||
		source: function (query, process) {
 | 
			
		||||
			$.ajax({
 | 
			
		||||
				url: '{{ url_for('get_authors_json') }} ',
 | 
			
		||||
				type: 'POST',
 | 
			
		||||
				dataType: 'JSON',
 | 
			
		||||
				data: 'query=' + query,
 | 
			
		||||
				success: function(data) {
 | 
			
		||||
					console.log(data);
 | 
			
		||||
					process(data);
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
});  
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="col-sm-3 col-lg-3 col-xs-12">
 | 
			
		||||
  <div class="cover">
 | 
			
		||||
    {% if book.has_cover is defined %}
 | 
			
		||||
| 
						 | 
				
			
			@ -36,12 +16,7 @@ $(document).ready(function(){
 | 
			
		|||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
      <label for="bookAuthor">Author</label>
 | 
			
		||||
        {% if book.authors|length > 1 %}
 | 
			
		||||
          {% for author in book.authors %}
 | 
			
		||||
            <p>{{author.name.join(" & ")}}</p>
 | 
			
		||||
          {% endfor %}
 | 
			
		||||
        {% endif %}
 | 
			
		||||
      <input type="text" data-provide="typeahead" class="form-control typeahead" name="author_name" id="bookAuthor" autocomplete="off" value="{{book.authors[0].name}}">
 | 
			
		||||
      <input type="text" class="form-control typeahead" name="author_name" id="bookAuthor" value="{{' & '.join(authors)}}" autocomplete="off">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
      <label for="description">Description</label>
 | 
			
		||||
| 
						 | 
				
			
			@ -78,3 +53,11 @@ $(document).ready(function(){
 | 
			
		|||
</div>
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block js %}
 | 
			
		||||
<script src="{{ url_for('static', filename='js/typeahead.bundle.js') }}"></script>
 | 
			
		||||
<script src="{{ url_for('static', filename='js/edit_books.js') }}"></script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
{% block header %}
 | 
			
		||||
<link href="{{ url_for('static', filename='css/typeahead.css') }}" rel="stylesheet" media="screen">
 | 
			
		||||
{% endblock %}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,7 +50,14 @@
 | 
			
		|||
      </div>
 | 
			
		||||
      <div class="meta">
 | 
			
		||||
        <p class="title">{{entry.title|shortentitle}}</p>
 | 
			
		||||
        <p class="author"><a href="{{url_for('author', name=entry.authors[0].name) }}">{{entry.authors[0].name}}</a></p>
 | 
			
		||||
        <p class="author">
 | 
			
		||||
          {% for author in entry.authors %}
 | 
			
		||||
            <a href="{{url_for('author', name=author.name) }}">{{author.name}}</a>
 | 
			
		||||
            {% if not loop.last %}
 | 
			
		||||
              &
 | 
			
		||||
            {% endif %}
 | 
			
		||||
          {% endfor %}
 | 
			
		||||
        </p>
 | 
			
		||||
        {% if entry.ratings.__len__() > 0 %}
 | 
			
		||||
        <div class="rating">
 | 
			
		||||
          {% for number in range((entry.ratings[0].rating/2)|int(2)) %}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,12 +23,12 @@
 | 
			
		|||
    <script src="https://code.jquery.com/jquery.js"></script>
 | 
			
		||||
    <!-- Include all compiled plugins (below), or include individual files as needed -->
 | 
			
		||||
    <script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
 | 
			
		||||
    <script src="{{ url_for('static', filename='js/bootstrap3-typeahead.js') }}"></script>
 | 
			
		||||
    <script src="{{ url_for('static', filename='js/underscore.min.js') }}"></script>
 | 
			
		||||
    <script src="{{ url_for('static', filename='js/intention.js') }}"></script>
 | 
			
		||||
    <script src="{{ url_for('static', filename='js/context.js') }}"></script>
 | 
			
		||||
    <script src="{{ url_for('static', filename='js/plugins.js') }}"></script>
 | 
			
		||||
    <script src="{{ url_for('static', filename='js/main.js') }}"></script>
 | 
			
		||||
        {% block header %}{% endblock %}
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
    <script>
 | 
			
		||||
| 
						 | 
				
			
			@ -156,5 +156,6 @@
 | 
			
		|||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% block js %}{% endblock %}
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,14 @@
 | 
			
		|||
      </div>
 | 
			
		||||
      <div class="meta">
 | 
			
		||||
        <p class="title">{{entry.title|shortentitle}}</p>
 | 
			
		||||
        <p class="author"><a href="{{url_for('author', name=entry.authors[0].name) }}">{{entry.authors[0].name}}</a></p>
 | 
			
		||||
        <p class="author">
 | 
			
		||||
          {% for author in entry.authors %}
 | 
			
		||||
            <a href="{{url_for('author', name=author.name) }}">{{author.name}}</a>
 | 
			
		||||
            {% if not loop.last %}
 | 
			
		||||
              &
 | 
			
		||||
            {% endif %}
 | 
			
		||||
          {% endfor %}
 | 
			
		||||
        </p>
 | 
			
		||||
        {% if entry.ratings.__len__() > 0 %}
 | 
			
		||||
        <div class="rating">
 | 
			
		||||
          {% for number in range((entry.ratings[0].rating/2)|int(2)) %}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										150
									
								
								cps/web.py
									
									
									
									
									
								
							
							
						
						
									
										150
									
								
								cps/web.py
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -245,11 +245,27 @@ def get_opds_download_link(book_id, format):
 | 
			
		|||
    
 | 
			
		||||
@app.route("/get_authors_json", methods = ['GET', 'POST'])
 | 
			
		||||
def get_authors_json(): 
 | 
			
		||||
    if request.method == "POST":
 | 
			
		||||
        form = request.form.to_dict()
 | 
			
		||||
        entries = db.session.execute("select name from authors where name like '%" + form['query'] + "%'")
 | 
			
		||||
        return json.dumps([dict(r) for r in entries])
 | 
			
		||||
    if request.method == "GET":
 | 
			
		||||
        query = request.args.get('q')
 | 
			
		||||
        entries = db.session.execute("select name from authors where name like '%" + query + "%'")
 | 
			
		||||
        json_dumps = json.dumps([dict(r) for r in entries])
 | 
			
		||||
        return json_dumps
 | 
			
		||||
 | 
			
		||||
@app.route("/get_tags_json", methods = ['GET', 'POST'])
 | 
			
		||||
def get_tags_json(): 
 | 
			
		||||
    if request.method == "GET":
 | 
			
		||||
        query = request.args.get('q')
 | 
			
		||||
        entries = db.session.execute("select name from tags where name like '%" + query + "%'")
 | 
			
		||||
        json_dumps = json.dumps([dict(r) for r in entries])
 | 
			
		||||
        return json_dumps
 | 
			
		||||
        
 | 
			
		||||
@app.route("/get_series_json", methods = ['GET', 'POST'])
 | 
			
		||||
def get_series_json(): 
 | 
			
		||||
    if request.method == "GET":
 | 
			
		||||
        query = request.args.get('q')
 | 
			
		||||
        entries = db.session.execute("select name from series where name like '%" + query + "%'")
 | 
			
		||||
        json_dumps = json.dumps([dict(r) for r in entries])
 | 
			
		||||
        return json_dumps
 | 
			
		||||
 | 
			
		||||
@app.route("/", defaults={'page': 1})
 | 
			
		||||
@app.route('/page/<int:page>')
 | 
			
		||||
| 
						 | 
				
			
			@ -679,34 +695,61 @@ def edit_book(book_id):
 | 
			
		|||
    ## create the function for sorting...
 | 
			
		||||
    db.session.connection().connection.connection.create_function("title_sort",1,db.title_sort)
 | 
			
		||||
    book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
 | 
			
		||||
    author_names = []
 | 
			
		||||
    for author in book.authors:
 | 
			
		||||
        author_names.append(author.name)
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        edited_books_id = set()
 | 
			
		||||
        to_save = request.form.to_dict()
 | 
			
		||||
        if book.title != to_save["book_title"]:
 | 
			
		||||
            book.title = to_save["book_title"]
 | 
			
		||||
            edited_books_id.add(book.id)
 | 
			
		||||
 
 | 
			
		||||
        author_id = book.authors[0].id               
 | 
			
		||||
        if book.authors[0].name != to_save["author_name"].strip():
 | 
			
		||||
            is_author = db.session.query(db.Authors).filter(db.Authors.name == to_save["author_name"].strip()).first()
 | 
			
		||||
            edited_books_id.add(book.id)
 | 
			
		||||
            if book.authors[0].name not in  ("Unknown", "Unbekannt", "", " "):
 | 
			
		||||
                if is_author:
 | 
			
		||||
                    book.authors.append(is_author)
 | 
			
		||||
                    book.authors.remove(db.session.query(db.Authors).get(book.authors[0].id))
 | 
			
		||||
                    authors_books_count = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.id.is_(author_id))).count()
 | 
			
		||||
        input_authors = to_save["author_name"].split('&')
 | 
			
		||||
        input_authors = map(lambda it: it.strip(), input_authors)
 | 
			
		||||
        # we have all author names now
 | 
			
		||||
        author0_before_edit = book.authors[0].name
 | 
			
		||||
        # 1. search for authors to remove
 | 
			
		||||
        del_authors = []
 | 
			
		||||
        for c_author in book.authors:
 | 
			
		||||
            found = False
 | 
			
		||||
            for inp_author in input_authors:
 | 
			
		||||
                if inp_author == c_author.name:
 | 
			
		||||
                    found = True
 | 
			
		||||
                    break;
 | 
			
		||||
            # if the author was not found in the new list, add him to remove list
 | 
			
		||||
            if not found:
 | 
			
		||||
                del_authors.append(c_author)
 | 
			
		||||
        # 2. search for authors that need to be added
 | 
			
		||||
        add_authors = []
 | 
			
		||||
        for inp_author in input_authors:
 | 
			
		||||
            found = False
 | 
			
		||||
            for c_author in book.authors:
 | 
			
		||||
                if inp_author == c_author.name:
 | 
			
		||||
                    found = True
 | 
			
		||||
                    break;
 | 
			
		||||
            if not found:
 | 
			
		||||
                add_authors.append(inp_author)
 | 
			
		||||
        # if there are authors to remove, we remove them now
 | 
			
		||||
        if len(del_authors) > 0:
 | 
			
		||||
            for del_author in del_authors:
 | 
			
		||||
                book.authors.remove(del_author)
 | 
			
		||||
                authors_books_count = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.id.is_(del_author.id))).count()
 | 
			
		||||
                if authors_books_count == 0:
 | 
			
		||||
                        db.session.query(db.Authors).filter(db.Authors.id == author_id).delete()
 | 
			
		||||
                else:
 | 
			
		||||
                    book.authors[0].name = to_save["author_name"].strip()
 | 
			
		||||
                    for linked_book in db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.id.is_(author_id))).all():
 | 
			
		||||
                        edited_books_id.add(linked_book.id)
 | 
			
		||||
            else:
 | 
			
		||||
                if is_author:
 | 
			
		||||
                    book.authors.append(is_author)
 | 
			
		||||
                else:
 | 
			
		||||
                    book.authors.append(db.Authors(to_save["author_name"].strip(), "", ""))
 | 
			
		||||
                book.authors.remove(db.session.query(db.Authors).get(book.authors[0].id))
 | 
			
		||||
                    db.session.query(db.Authors).filter(db.Authors.id == del_author.id).delete()
 | 
			
		||||
        # if there are authors to add, we add them now!
 | 
			
		||||
        if len(add_authors) > 0:
 | 
			
		||||
            for add_author in add_authors:
 | 
			
		||||
                # check if an author with that name exists
 | 
			
		||||
                t_author = db.session.query(db.Authors).filter(db.Authors.name == add_author).first();
 | 
			
		||||
                # if no author is found add it
 | 
			
		||||
                if t_author == None:
 | 
			
		||||
                    new_author = db.Authors(add_author, add_author, "")
 | 
			
		||||
                    db.session.add(new_author)
 | 
			
		||||
                    t_author = db.session.query(db.Authors).filter(db.Authors.name == add_author).first();
 | 
			
		||||
                # add author to book
 | 
			
		||||
                book.authors.append(t_author)       
 | 
			
		||||
        if author0_before_edit != book.authors[0].name:
 | 
			
		||||
            edited_books_id.add(book.id)
 | 
			
		||||
        
 | 
			
		||||
        if to_save["cover_url"] and os.path.splitext(to_save["cover_url"])[1].lower() == ".jpg":
 | 
			
		||||
            img = requests.get(to_save["cover_url"])
 | 
			
		||||
| 
						 | 
				
			
			@ -721,14 +764,50 @@ def edit_book(book_id):
 | 
			
		|||
        else:
 | 
			
		||||
            book.comments.append(db.Comments(text=to_save["description"], book=book.id))
 | 
			
		||||
 | 
			
		||||
        for tag in to_save["tags"].split(","):
 | 
			
		||||
            if tag.strip():
 | 
			
		||||
                is_tag = db.session.query(db.Tags).filter(db.Tags.name.like('%' + tag.strip() + '%')).first()
 | 
			
		||||
                if is_tag:
 | 
			
		||||
                    book.tags.append(is_tag)
 | 
			
		||||
                else:
 | 
			
		||||
                    new_tag = db.Tags(name=tag.strip())
 | 
			
		||||
        input_tags = to_save["tags"].split(',')
 | 
			
		||||
        input_tags = map(lambda it: it.strip(), input_tags)
 | 
			
		||||
        input_tags = [x for x in input_tags if x != '']
 | 
			
		||||
        # we have all author names now
 | 
			
		||||
        # 1. search for tags to remove
 | 
			
		||||
        del_tags = []
 | 
			
		||||
        for c_tag in book.tags:
 | 
			
		||||
            found = False
 | 
			
		||||
            for inp_tag in input_tags:
 | 
			
		||||
                if inp_tag == c_tag.name:
 | 
			
		||||
                    found = True
 | 
			
		||||
                    break;
 | 
			
		||||
            # if the tag was not found in the new list, add him to remove list
 | 
			
		||||
            if not found:
 | 
			
		||||
                del_tags.append(c_tag)
 | 
			
		||||
        # 2. search for tags that need to be added
 | 
			
		||||
        add_tags = []
 | 
			
		||||
        for inp_tag in input_tags:
 | 
			
		||||
            found = False
 | 
			
		||||
            for c_tag in book.tags:
 | 
			
		||||
                if inp_tag == c_tag.name:
 | 
			
		||||
                    found = True
 | 
			
		||||
                    break;
 | 
			
		||||
            if not found:
 | 
			
		||||
                add_tags.append(inp_tag)
 | 
			
		||||
        # if there are tags to remove, we remove them now
 | 
			
		||||
        if len(del_tags) > 0:
 | 
			
		||||
            for del_tag in del_tags:
 | 
			
		||||
                book.tags.remove(del_tag)
 | 
			
		||||
                if len(del_tag.books) == 0:
 | 
			
		||||
                    db.session.delete(del_tag)
 | 
			
		||||
        # if there are tags to add, we add them now!
 | 
			
		||||
        if len(add_tags) > 0:
 | 
			
		||||
            for add_tag in add_tags:
 | 
			
		||||
                # check if a tag with that name exists
 | 
			
		||||
                new_tag = db.session.query(db.Tags).filter(db.Tags.name == add_tag).first();
 | 
			
		||||
                # if no tag is found add it
 | 
			
		||||
                if new_tag == None:
 | 
			
		||||
                    new_tag = db.Tags(add_tag)
 | 
			
		||||
                    db.session.add(new_tag)
 | 
			
		||||
                    new_tag = db.session.query(db.Tags).filter(db.Tags.name == add_tag).first();
 | 
			
		||||
                # add tag to book
 | 
			
		||||
                book.tags.append(new_tag) 
 | 
			
		||||
        
 | 
			
		||||
        if to_save["series"].strip():
 | 
			
		||||
            is_series = db.session.query(db.Series).filter(db.Series.name.like('%' + to_save["series"].strip() + '%')).first()
 | 
			
		||||
            if is_series:
 | 
			
		||||
| 
						 | 
				
			
			@ -744,14 +823,17 @@ def edit_book(book_id):
 | 
			
		|||
                new_rating = db.Ratings(rating=int(to_save["rating"].strip()))
 | 
			
		||||
                book.ratings[0] = new_rating
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
        author_names = []
 | 
			
		||||
        for author in book.authors:
 | 
			
		||||
            author_names.append(author.name)
 | 
			
		||||
        for b in edited_books_id:
 | 
			
		||||
            helper.update_dir_stucture(b)
 | 
			
		||||
        if "detail_view" in to_save:
 | 
			
		||||
            return redirect(url_for('show_book', id=book.id))
 | 
			
		||||
        else:
 | 
			
		||||
            return render_template('edit_book.html', book=book)
 | 
			
		||||
            return render_template('edit_book.html', book=book, authors=author_names)
 | 
			
		||||
    else:
 | 
			
		||||
        return render_template('edit_book.html', book=book)
 | 
			
		||||
        return render_template('edit_book.html', book=book, authors=author_names)
 | 
			
		||||
 | 
			
		||||
@app.route("/upload", methods = ["GET", "POST"])
 | 
			
		||||
@login_required
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user