biola_wcms_components 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/biola-wcms-components.js.coffee +2 -0
  3. data/app/assets/javascripts/components/forms/person_lookup.js.coffee +1 -1
  4. data/app/assets/javascripts/components/forms/tag_input.js.coffee +42 -0
  5. data/app/assets/stylesheets/{biola-wcms-components.scss → biola-wcms-components.css.scss} +2 -0
  6. data/app/assets/stylesheets/components/forms/_person_lookup.scss +0 -47
  7. data/app/assets/stylesheets/components/forms/_tag_input.scss +5 -0
  8. data/app/controllers/wcms_application_controller.rb +37 -0
  9. data/app/controllers/wcms_components/embedded_images_controller.rb +20 -0
  10. data/app/controllers/wcms_components/people_controller.rb +30 -0
  11. data/app/views/wcms_components/forms/_json_editor.html.slim +1 -1
  12. data/app/views/wcms_components/forms/_person_lookup.html.slim +12 -3
  13. data/app/views/wcms_components/forms/_presentation_data_editor.html.slim +3 -4
  14. data/app/views/wcms_components/forms/_related_object_tags.html.slim +12 -0
  15. data/app/views/wcms_components/forms/_tag_input.html.slim +26 -0
  16. data/app/views/wcms_components/forms/_yaml_editor.html.slim +1 -1
  17. data/app/views/wcms_components/shared/_embedded_image_uploader.html.slim +1 -1
  18. data/biola_wcms_components.gemspec +2 -0
  19. data/config/routes.rb +13 -0
  20. data/lib/biola_wcms_components.rb +3 -0
  21. data/lib/biola_wcms_components/version.rb +1 -1
  22. data/lib/components/cas_authentication.rb +87 -0
  23. data/vendor/assets/javascripts/bootstrap-tagsinput.js +580 -0
  24. data/vendor/assets/stylesheets/bootstrap-tagsinput.css +55 -0
  25. data/vendor/assets/stylesheets/typeahead.css +50 -0
  26. metadata +43 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3564d479c782c49da3714d46d516fbc535e40cfa
4
- data.tar.gz: 19904bda0bb747bfd063e87683cdb12391ec7de3
3
+ metadata.gz: f4615d1bb2bee67a6f0a7457288fc2df6796045b
4
+ data.tar.gz: 4d1f46f67c613b239f3a940d062f494c954ec969
5
5
  SHA512:
6
- metadata.gz: bea91bf31935f7321f8a5e2aadfa5e7eceef27781eb5abbc70bbb346e5dffafc03adc46a80c2d39932e93126222e495f1a70e3e54d7003e25d2068ae65c6c287
7
- data.tar.gz: 3157ae4e247df688200788bfac820cec812a1199c5fbb43217c24e82893902b24bac5e94dba651d34369331fd93b02008e7bb77778b8fb8257b8594477203f17
6
+ metadata.gz: abc4d90dfbd41be28d68801f5d7cbcd446ac7380e70960bbb3696b0c9a381a794d0c55ed23c1a7ffd1232bdda40f070315deb83fb1ed172ff73ce77c67bdd5d4
7
+ data.tar.gz: 259e675f5fdbddb58a1ba88d4c474c0d4b00e0a98ef7e2f53f466df82248843af985c0b643a882c334afe4613f64237c11ad620b024567c430ef6da5d2efad21
@@ -7,6 +7,8 @@
7
7
  #= require handlebars
8
8
  #= require typeahead
9
9
  #
10
+ #= require bootstrap-tagsinput
11
+ #
10
12
  #= require redactor
11
13
  #= require redactor_fullscreen
12
14
  #
@@ -3,7 +3,7 @@ $(document).ready ->
3
3
  people_search_url = $('.person-lookup').first().data('lookup-url')
4
4
 
5
5
  peopleSearch = new Bloodhound(
6
- datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value')
6
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('id')
7
7
  queryTokenizer: Bloodhound.tokenizers.whitespace
8
8
  # The prefetch url returns a list of all the faculty
9
9
  prefetch: people_search_url
@@ -0,0 +1,42 @@
1
+ initializeTaginput = (selector) ->
2
+ $(selector).each ->
3
+ tagsinput = $(this)
4
+ tagsinputOptions = {}
5
+
6
+
7
+ # If typeahead key is set, initialize typeahead. Otherwise just initialize a normal tagsinput
8
+ # available parameters:
9
+ # :url => url to the remote json data
10
+ # :key => the key to use for each element in the json array
11
+ # :query_param => The parameter key we should use to append the search to the url
12
+ # Defaults to 'q'
13
+ #
14
+ if options = tagsinput.data('typeahead')
15
+ options.query_param ||= 'q'
16
+
17
+ typeaheadEngine = new Bloodhound
18
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace(options.key)
19
+ queryTokenizer: Bloodhound.tokenizers.whitespace
20
+ remote: "#{options.url}?#{options.query_param}=%QUERY"
21
+ typeaheadEngine.initialize()
22
+
23
+ tagsinputOptions.typeaheadjs =
24
+ displayKey: options.key
25
+ valueKey: options.key
26
+ highlight: true
27
+ source: typeaheadEngine.ttAdapter()
28
+
29
+ # Initialize tagsinput with options
30
+ tagsinput.tagsinput tagsinputOptions
31
+
32
+
33
+
34
+ $(document).ready ->
35
+
36
+ # Initialize tagsinput on page load
37
+ initializeTaginput("input[data-role=tagsinput]")
38
+
39
+ $(".modal").on "shown.bs.modal", (e) ->
40
+ # Initialize tagsinputs underneath modal.
41
+ initializeTaginput("input[data-role=tagsinput]")
42
+
@@ -1,5 +1,7 @@
1
1
  // External files
2
2
  @import "../../../vendor/assets/stylesheets/redactor";
3
+ @import "../../../vendor/assets/stylesheets/bootstrap-tagsinput";
4
+ @import "../../../vendor/assets/stylesheets/typeahead";
3
5
 
4
6
  // Mixins and settings
5
7
  @import "./settings";
@@ -1,48 +1,5 @@
1
1
  .person-lookup {
2
- .typeahead, .tt-query, .tt-hint {
3
- width: 396px;
4
- height: 34px;
5
- padding: 8px 12px;
6
- font-size: 15px;
7
- line-height: 15px;
8
- border: 1px solid #ccc;
9
- -webkit-border-radius: 4px;
10
- -moz-border-radius: 4px;
11
- border-radius: 4px;
12
- outline: none;
13
- }
14
-
15
- .tt-query {
16
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
17
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
18
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
19
- }
20
-
21
- .tt-hint {
22
- color: #999
23
- }
24
-
25
- .tt-dropdown-menu {
26
- text-align: left;
27
- width: 422px;
28
- margin-top: 12px;
29
- padding: 8px 0;
30
- background-color: #fff;
31
- border: 1px solid #ccc;
32
- border: 1px solid rgba(0, 0, 0, 0.2);
33
- -webkit-border-radius: 4px;
34
- -moz-border-radius: 4px;
35
- border-radius: 4px;
36
- -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
37
- -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
38
- box-shadow: 0 5px 10px rgba(0,0,0,.2);
39
- }
40
-
41
2
  .tt-suggestion, .tt-empty-message {
42
- padding: 3px 20px;
43
- font-size: 16px;
44
- line-height: 24px;
45
-
46
3
  .affiliations {
47
4
  color: #999;
48
5
  font-size: 12px;
@@ -61,10 +18,6 @@
61
18
  }
62
19
 
63
20
  .tt-suggestion.tt-cursor {
64
- cursor: pointer;
65
- color: #fff;
66
- background-color: #0097cf;
67
-
68
21
  .affiliations {
69
22
  color: #fff;
70
23
  }
@@ -0,0 +1,5 @@
1
+ .bootstrap-tagsinput {
2
+
3
+ // Hide the hint with tagsinput, it doesn't show up correctly.
4
+ .tt-hint { display: none; }
5
+ }
@@ -0,0 +1,37 @@
1
+ # ApplicationController should inherit from this controller.
2
+ class WcmsApplicationController < ActionController::Base
3
+ include Pundit
4
+
5
+ protect_from_forgery with: :exception
6
+
7
+ before_action :authenticate!
8
+ after_action :verify_authorized
9
+ after_action :verify_policy_scoped, only: :index
10
+ rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
11
+
12
+ layout -> { (@layout || :application).to_s }
13
+
14
+ helper_method :current_user
15
+ def current_user
16
+ authentication.user
17
+ end
18
+
19
+ protected
20
+
21
+ def authenticate!
22
+ authentication.perform or render_error_page(401)
23
+ end
24
+
25
+ def authentication
26
+ @authentication ||= CasAuthentication.new(session)
27
+ end
28
+
29
+ def render_error_page(status)
30
+ render file: "#{Rails.root}/public/#{status}", formats: [:html], status: status, layout: false
31
+ end
32
+
33
+ def user_not_authorized
34
+ render_error_page(403)
35
+ end
36
+
37
+ end
@@ -0,0 +1,20 @@
1
+ class WcmsComponents::EmbeddedImagesController < ApplicationController
2
+
3
+ skip_after_action :verify_authorized
4
+ skip_after_action :verify_policy_scoped
5
+
6
+ def create
7
+ # Anyone who is logged in should be able to access this.
8
+ @embedded_image = EmbeddedImage.new
9
+
10
+ file = params[:file]
11
+ @embedded_image.upload = file
12
+
13
+ if @embedded_image.save
14
+ render json: { filelink: @embedded_image.upload.url }
15
+ else
16
+ render json: { error: true, messages: @embedded_image.errors.full_messages}
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,30 @@
1
+ class WcmsComponents::PeopleController < ApplicationController
2
+
3
+ skip_after_action :verify_authorized
4
+ skip_after_action :verify_policy_scoped
5
+
6
+ def index
7
+ # For security reasons, this should only be available to employees.
8
+ if current_user.admin? || current_user.has_role?(:employee)
9
+ if params[:q].present?
10
+ @people = permitted_people.custom_search(params[:q]).asc(:first_name, :last_name).limit(20)
11
+ else
12
+ # If no query string is present, return all faculty for pre-cached data.
13
+ @people = permitted_people.where(affiliations: 'faculty').custom_search(params[:q]).asc(:first_name, :last_name)
14
+ end
15
+
16
+ render json: @people.map{|p| {id: p.id.to_s, name: p.name, email: p.biola_email, affiliations: p.affiliations.to_a.join(', '), image: p.profile_photo_url} }.to_json
17
+ else
18
+ user_not_authorized
19
+ end
20
+ end
21
+
22
+
23
+ private
24
+
25
+ def permitted_people
26
+ # Return all people who are either employees or not private.
27
+ Person.where({'$or' => [{affiliations: ['employee'] }, {privacy: { '$ne' => true }}] })
28
+ end
29
+
30
+ end
@@ -3,7 +3,7 @@ ruby:
3
3
  attribute ||= nil
4
4
  html_class ||= 'form-control json_editor'
5
5
  value ||= nil
6
- embedded_image_url ||= nil # should be the post url for creating new embedded images
6
+ embedded_image_url ||= wcms_components_embedded_images_url
7
7
 
8
8
 
9
9
  = wcms_component("shared/embedded_image_uploader", embedded_image_url: embedded_image_url)
@@ -1,10 +1,19 @@
1
1
  ruby:
2
- lookup_url ||= nil
2
+ form ||= nil
3
+ person_id_key ||= :person_id
4
+ lookup_url ||= wcms_components_people_url
5
+ placeholder ||= 'First or last name'
6
+ required ||= false
7
+
8
+ html_options = {placeholder: placeholder, required: required, class: 'form-control typeahead'}
3
9
 
4
10
  - if lookup_url
5
11
  .person-lookup data-lookup-url=lookup_url
6
- = hidden_field_tag :person_id, '', class: 'hidden-person-id'
7
- = text_field_tag :person_name, '', placeholder: 'First or last name', required: true, class: 'form-control typeahead'
12
+ - if form
13
+ = form.hidden_field person_id_key, class: 'hidden-person-id'
14
+ - else
15
+ = hidden_field_tag person_id_key, '', class: 'hidden-person-id'
16
+ = text_field_tag :person_name, '', html_options
8
17
 
9
18
  - else
10
19
  p
@@ -1,16 +1,15 @@
1
1
  / Example:
2
2
  / = wcms_component "forms/presentation_data_editor",
3
- / schema: @generic_object.presentation_data_template.schema,
4
- / data: @generic_object.presentation_data,
5
3
  / form: f,
6
- / embedded_image_url: create_embedded_images_url
4
+ / schema: @generic_object.presentation_data_template.schema,
5
+ / presentation_data: @generic_object.presentation_data
7
6
  /
8
7
 
9
8
  ruby:
10
9
  schema ||= []
11
10
  presentation_data ||= {}
12
11
  form ||= nil
13
- embedded_image_url ||= nil # should be the post url for creating new embedded images
12
+ embedded_image_url ||= wcms_components_embedded_images_url
14
13
 
15
14
  = wcms_component "shared/embedded_image_uploader",
16
15
  embedded_image_url: embedded_image_url
@@ -0,0 +1,12 @@
1
+ ruby:
2
+ form ||= nil
3
+ attribute ||= nil
4
+ value ||= value
5
+ html_class ||= 'form-control'
6
+
7
+
8
+ = wcms_component "forms/tag_input",
9
+ form: form,
10
+ attribute: :related_object_tags_string,
11
+ value: value,
12
+ html_class: html_class
@@ -0,0 +1,26 @@
1
+ / Example:
2
+ / = wcms_component "forms/tag_input",
3
+ / form: f,
4
+ / attribute: :courses_string,
5
+ / typeahead: { url: courses_path(format: :json), key: 'course_key'}
6
+ /
7
+
8
+
9
+ ruby:
10
+ form ||= nil
11
+ attribute ||= nil
12
+ value ||= value
13
+ html_class ||= 'form-control'
14
+ typeahead ||= nil # for available options see tag_input.js.coffee
15
+ options ||= {}
16
+ html_options = options.merge({
17
+ class: html_class,
18
+ data: {role: 'tagsinput', typeahead: typeahead}
19
+ })
20
+ html_options[:value] = value unless value.nil?
21
+
22
+
23
+ - if form
24
+ = form.text_field attribute, html_options
25
+ - else
26
+ = text_field_tag attribute, value, html_options
@@ -3,7 +3,7 @@ ruby:
3
3
  attribute ||= nil
4
4
  html_class ||= 'form-control'
5
5
  value ||= nil
6
- embedded_image_url ||= nil # should be the post url for creating new embedded images
6
+ embedded_image_url ||= wcms_components_embedded_images_url
7
7
 
8
8
  # Set default value
9
9
  # We have to do it this way in case value is an empty string (as opposed to nil).
@@ -1,5 +1,5 @@
1
1
  ruby:
2
- embedded_image_url ||= nil # should be the post url for creating new embedded images
2
+ embedded_image_url ||= wcms_components_embedded_images_url
3
3
 
4
4
  - if embedded_image_url
5
5
  javascript:
@@ -19,7 +19,9 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency "ace-rails-ap", "~> 3.0"
22
+ spec.add_dependency "buweb_content_models", ">= 0.82"
22
23
  spec.add_dependency "coffee-rails", ">= 4.0"
24
+ spec.add_dependency "pundit", "~> 0.3"
23
25
  spec.add_dependency "sass-rails", ">= 4.0"
24
26
  spec.add_dependency "slim", ">= 2.0"
25
27
  spec.add_development_dependency "bundler", "~> 1.3"
data/config/routes.rb ADDED
@@ -0,0 +1,13 @@
1
+ Rails.application.routes.draw do
2
+
3
+ namespace :wcms_components do
4
+ resources :embedded_images, only: [:create], defaults: { format: 'json' }
5
+
6
+ # "people#index" is used for search purposes
7
+ resources :people, only: [:index], defaults: { format: 'json' }
8
+ end
9
+
10
+ # this is just a convenience to create a named route to rack-cas' logout
11
+ get '/logout' => -> env { [200, { 'Content-Type' => 'text/html' }, ['Rack::CAS should have caught this']] }, as: :logout
12
+
13
+ end
@@ -1,6 +1,8 @@
1
1
  require "biola_wcms_components/version"
2
2
  require "biola_wcms_components/engine" if defined?(::Rails)
3
3
  require "ace-rails-ap"
4
+ require "buweb_content_models"
5
+ require "pundit"
4
6
  require "coffee-rails"
5
7
  require "sass-rails"
6
8
  require "slim"
@@ -18,5 +20,6 @@ module BiolaWcmsComponents
18
20
  end
19
21
 
20
22
 
23
+ autoload :CasAuthentication, 'components/cas_authentication'
21
24
  autoload :MenuBlock, 'components/menu_block'
22
25
  autoload :PresentationDataEditor, 'components/presentation_data_editor'
@@ -1,3 +1,3 @@
1
1
  module BiolaWcmsComponents
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,87 @@
1
+ class CasAuthentication
2
+ def initialize(session)
3
+ @session = session
4
+ end
5
+
6
+ def user
7
+ if username.present?
8
+ @user ||= User.find_or_initialize_by(username: username)
9
+ end
10
+ end
11
+
12
+ def perform
13
+ if authenticated?
14
+ true
15
+ elsif present?
16
+ if new_user?
17
+ if create_user!
18
+ authenticate!
19
+ end
20
+ elsif unauthenticated?
21
+ authenticate!
22
+ update_extra_attributes!
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :session
30
+
31
+ def present?
32
+ session['cas'].present?
33
+ end
34
+
35
+ def new_user?
36
+ !!user.try(:new_record?)
37
+ end
38
+
39
+ def authenticated?
40
+ session[:username].present?
41
+ end
42
+
43
+ def unauthenticated?
44
+ !authenticated?
45
+ end
46
+
47
+ def authenticate!
48
+ session[:username] = username
49
+ end
50
+
51
+ def update_extra_attributes!
52
+ user.biola_id = extra_attr(:employeeId) if extra_attr_has_key?(:employeeId)
53
+ user.first_name = extra_attr(:eduPersonNickname) if extra_attr_has_key?(:eduPersonNickname)
54
+ user.last_name = extra_attr(:sn) if extra_attr_has_key?(:sn)
55
+ user.email = extra_attr(:mail) if extra_attr_has_key?(:mail)
56
+ user.photo_url = extra_attr(:url).gsub('.jpg', '_large.jpg') if extra_attr_has_key?(:url)
57
+ user.entitlements = extra_attrs(:eduPersonEntitlement) if extra_attr_has_key?(:eduPersonEntitlement)
58
+ user.affiliations = extra_attrs(:eduPersonAffiliation) if extra_attr_has_key?(:eduPersonAffiliation)
59
+ user.save
60
+ end
61
+ alias :create_user! :update_extra_attributes!
62
+
63
+ def username
64
+ session[:username] || attrs['user']
65
+ end
66
+
67
+ def attrs
68
+ @attrs ||= (session['cas'] || {}).with_indifferent_access
69
+ end
70
+
71
+ def extra_attributes
72
+ @extra_attributes ||= (attrs['extra_attributes'] || {}).with_indifferent_access
73
+ end
74
+
75
+ def extra_attr_has_key?(key)
76
+ extra_attributes.has_key? key
77
+ end
78
+
79
+ def extra_attr(key)
80
+ # Many values come back as arrays but don't really need to be
81
+ extra_attrs(key).first
82
+ end
83
+
84
+ def extra_attrs(key)
85
+ Array(extra_attributes[key]).map(&:to_s)
86
+ end
87
+ end
@@ -0,0 +1,580 @@
1
+ // NOTE: There are a few places I made changes to,
2
+ // so consider them when updating. Search this document for the key "CHANGED"
3
+ //
4
+ // https://github.com/timschlechter/bootstrap-tagsinput
5
+ // Vesion: 0.4.2
6
+
7
+ (function ($) {
8
+ "use strict";
9
+
10
+ var defaultOptions = {
11
+ tagClass: function(item) {
12
+ return 'label label-info';
13
+ },
14
+ itemValue: function(item) {
15
+ return item ? item.toString() : item;
16
+ },
17
+ itemText: function(item) {
18
+ return this.itemValue(item);
19
+ },
20
+ freeInput: true,
21
+ addOnBlur: true,
22
+ maxTags: undefined,
23
+ maxChars: undefined,
24
+ confirmKeys: [13, 44],
25
+ onTagExists: function(item, $tag) {
26
+ $tag.hide().fadeIn();
27
+ },
28
+ trimValue: false,
29
+ allowDuplicates: false
30
+ };
31
+
32
+ /**
33
+ * Constructor function
34
+ */
35
+ function TagsInput(element, options) {
36
+ this.itemsArray = [];
37
+
38
+ this.$element = $(element);
39
+ this.$element.hide();
40
+
41
+ this.isSelect = (element.tagName === 'SELECT');
42
+ this.multiple = (this.isSelect && element.hasAttribute('multiple'));
43
+ this.objectItems = options && options.itemValue;
44
+ this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : '';
45
+ this.inputSize = Math.max(1, this.placeholderText.length);
46
+
47
+ this.$container = $('<div class="bootstrap-tagsinput"></div>');
48
+ this.$input = $('<input type="text" placeholder="' + this.placeholderText + '"/>').appendTo(this.$container);
49
+
50
+ this.$element.after(this.$container);
51
+
52
+ // CHANGED: (RH) This is keeping the input from resizing automatically.
53
+ // var inputWidth = (this.inputSize < 3 ? 3 : this.inputSize) + "em";
54
+ // this.$input.get(0).style.cssText = "min-width: " + inputWidth + "";
55
+ this.build(options);
56
+ }
57
+
58
+ TagsInput.prototype = {
59
+ constructor: TagsInput,
60
+
61
+ /**
62
+ * Adds the given item as a new tag. Pass true to dontPushVal to prevent
63
+ * updating the elements val()
64
+ */
65
+ add: function(item, dontPushVal, options) {
66
+ var self = this;
67
+
68
+ if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags)
69
+ return;
70
+
71
+ // Ignore falsey values, except false
72
+ if (item !== false && !item)
73
+ return;
74
+
75
+ // Trim value
76
+ if (typeof item === "string" && self.options.trimValue) {
77
+ item = $.trim(item);
78
+ }
79
+
80
+ // Throw an error when trying to add an object while the itemValue option was not set
81
+ if (typeof item === "object" && !self.objectItems)
82
+ throw("Can't add objects when itemValue option is not set");
83
+
84
+ // Ignore strings only containg whitespace
85
+ if (item.toString().match(/^\s*$/))
86
+ return;
87
+
88
+ // If SELECT but not multiple, remove current tag
89
+ if (self.isSelect && !self.multiple && self.itemsArray.length > 0)
90
+ self.remove(self.itemsArray[0]);
91
+
92
+ if (typeof item === "string" && this.$element[0].tagName === 'INPUT') {
93
+ var items = item.split(',');
94
+ if (items.length > 1) {
95
+ for (var i = 0; i < items.length; i++) {
96
+ this.add(items[i], true);
97
+ }
98
+
99
+ if (!dontPushVal)
100
+ self.pushVal();
101
+ return;
102
+ }
103
+ }
104
+
105
+ var itemValue = self.options.itemValue(item),
106
+ itemText = self.options.itemText(item),
107
+ tagClass = self.options.tagClass(item);
108
+
109
+ // Ignore items allready added
110
+ var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0];
111
+ if (existing && !self.options.allowDuplicates) {
112
+ // Invoke onTagExists
113
+ if (self.options.onTagExists) {
114
+ var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; });
115
+ self.options.onTagExists(item, $existingTag);
116
+ }
117
+ return;
118
+ }
119
+
120
+ // if length greater than limit
121
+ if (self.items().toString().length + item.length + 1 > self.options.maxInputLength)
122
+ return;
123
+
124
+ // raise beforeItemAdd arg
125
+ var beforeItemAddEvent = $.Event('beforeItemAdd', { item: item, cancel: false, options: options});
126
+ self.$element.trigger(beforeItemAddEvent);
127
+ if (beforeItemAddEvent.cancel)
128
+ return;
129
+
130
+ // register item in internal array and map
131
+ self.itemsArray.push(item);
132
+
133
+ // add a tag element
134
+ var $tag = $('<span class="tag ' + htmlEncode(tagClass) + '">' + htmlEncode(itemText) + '<span data-role="remove"></span></span>');
135
+ $tag.data('item', item);
136
+ self.findInputWrapper().before($tag);
137
+ $tag.after(' ');
138
+
139
+ // add <option /> if item represents a value not present in one of the <select />'s options
140
+ if (self.isSelect && !$('option[value="' + encodeURIComponent(itemValue) + '"]',self.$element)[0]) {
141
+ var $option = $('<option selected>' + htmlEncode(itemText) + '</option>');
142
+ $option.data('item', item);
143
+ $option.attr('value', itemValue);
144
+ self.$element.append($option);
145
+ }
146
+
147
+ if (!dontPushVal)
148
+ self.pushVal();
149
+
150
+ // Add class when reached maxTags
151
+ if (self.options.maxTags === self.itemsArray.length || self.items().toString().length === self.options.maxInputLength)
152
+ self.$container.addClass('bootstrap-tagsinput-max');
153
+
154
+ self.$element.trigger($.Event('itemAdded', { item: item, options: options }));
155
+ },
156
+
157
+ /**
158
+ * Removes the given item. Pass true to dontPushVal to prevent updating the
159
+ * elements val()
160
+ */
161
+ remove: function(item, dontPushVal, options) {
162
+ var self = this;
163
+
164
+ if (self.objectItems) {
165
+ if (typeof item === "object")
166
+ item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == self.options.itemValue(item); } );
167
+ else
168
+ item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == item; } );
169
+
170
+ item = item[item.length-1];
171
+ }
172
+
173
+ if (item) {
174
+ var beforeItemRemoveEvent = $.Event('beforeItemRemove', { item: item, cancel: false, options: options });
175
+ self.$element.trigger(beforeItemRemoveEvent);
176
+ if (beforeItemRemoveEvent.cancel)
177
+ return;
178
+
179
+ $('.tag', self.$container).filter(function() { return $(this).data('item') === item; }).remove();
180
+ $('option', self.$element).filter(function() { return $(this).data('item') === item; }).remove();
181
+ if($.inArray(item, self.itemsArray) !== -1)
182
+ self.itemsArray.splice($.inArray(item, self.itemsArray), 1);
183
+ }
184
+
185
+ if (!dontPushVal)
186
+ self.pushVal();
187
+
188
+ // Remove class when reached maxTags
189
+ if (self.options.maxTags > self.itemsArray.length)
190
+ self.$container.removeClass('bootstrap-tagsinput-max');
191
+
192
+ self.$element.trigger($.Event('itemRemoved', { item: item, options: options }));
193
+ },
194
+
195
+ /**
196
+ * Removes all items
197
+ */
198
+ removeAll: function() {
199
+ var self = this;
200
+
201
+ $('.tag', self.$container).remove();
202
+ $('option', self.$element).remove();
203
+
204
+ while(self.itemsArray.length > 0)
205
+ self.itemsArray.pop();
206
+
207
+ self.pushVal();
208
+ },
209
+
210
+ /**
211
+ * Refreshes the tags so they match the text/value of their corresponding
212
+ * item.
213
+ */
214
+ refresh: function() {
215
+ var self = this;
216
+ $('.tag', self.$container).each(function() {
217
+ var $tag = $(this),
218
+ item = $tag.data('item'),
219
+ itemValue = self.options.itemValue(item),
220
+ itemText = self.options.itemText(item),
221
+ tagClass = self.options.tagClass(item);
222
+
223
+ // Update tag's class and inner text
224
+ $tag.attr('class', null);
225
+ $tag.addClass('tag ' + htmlEncode(tagClass));
226
+ $tag.contents().filter(function() {
227
+ return this.nodeType == 3;
228
+ })[0].nodeValue = htmlEncode(itemText);
229
+
230
+ if (self.isSelect) {
231
+ var option = $('option', self.$element).filter(function() { return $(this).data('item') === item; });
232
+ option.attr('value', itemValue);
233
+ }
234
+ });
235
+ },
236
+
237
+ /**
238
+ * Returns the items added as tags
239
+ */
240
+ items: function() {
241
+ return this.itemsArray;
242
+ },
243
+
244
+ /**
245
+ * Assembly value by retrieving the value of each item, and set it on the
246
+ * element.
247
+ */
248
+ pushVal: function() {
249
+ var self = this,
250
+ val = $.map(self.items(), function(item) {
251
+ return self.options.itemValue(item).toString();
252
+ });
253
+
254
+ self.$element.val(val, true).trigger('change');
255
+ },
256
+
257
+ /**
258
+ * Initializes the tags input behaviour on the element
259
+ */
260
+ build: function(options) {
261
+ var self = this;
262
+
263
+ self.options = $.extend({}, defaultOptions, options);
264
+ // When itemValue is set, freeInput should always be false
265
+ if (self.objectItems)
266
+ self.options.freeInput = false;
267
+
268
+ makeOptionItemFunction(self.options, 'itemValue');
269
+ makeOptionItemFunction(self.options, 'itemText');
270
+ makeOptionFunction(self.options, 'tagClass');
271
+
272
+ // CHANGED: (RH) Removed support for Typeahead Bootstrap version 2.3.2
273
+
274
+ // typeahead.js
275
+ if (self.options.typeaheadjs) {
276
+ var typeaheadjs = self.options.typeaheadjs || {};
277
+
278
+ self.$input.typeahead(null, typeaheadjs).on('typeahead:selected', $.proxy(function (obj, datum) {
279
+ if (typeaheadjs.valueKey)
280
+ self.add(datum[typeaheadjs.valueKey]);
281
+ else
282
+ self.add(datum);
283
+ self.$input.typeahead('val', '');
284
+ }, self));
285
+ }
286
+
287
+ self.$container.on('click', $.proxy(function(event) {
288
+ if (! self.$element.attr('disabled')) {
289
+ self.$input.removeAttr('disabled');
290
+ }
291
+ self.$input.focus();
292
+ }, self));
293
+
294
+ if (self.options.addOnBlur && self.options.freeInput) {
295
+ self.$input.on('focusout', $.proxy(function(event) {
296
+ // HACK: only process on focusout when no typeahead opened, to
297
+ // avoid adding the typeahead text as tag
298
+ if ($('.typeahead, .twitter-typeahead', self.$container).length === 0) {
299
+ self.add(self.$input.val());
300
+ self.$input.val('');
301
+ }
302
+ }, self));
303
+ }
304
+
305
+
306
+ self.$container.on('keydown', 'input', $.proxy(function(event) {
307
+ var $input = $(event.target),
308
+ $inputWrapper = self.findInputWrapper();
309
+
310
+ if (self.$element.attr('disabled')) {
311
+ self.$input.attr('disabled', 'disabled');
312
+ return;
313
+ }
314
+
315
+ switch (event.which) {
316
+ // BACKSPACE
317
+ case 8:
318
+ if (doGetCaretPosition($input[0]) === 0) {
319
+ var prev = $inputWrapper.prev();
320
+ if (prev) {
321
+ self.remove(prev.data('item'));
322
+ }
323
+ }
324
+ break;
325
+
326
+ // DELETE
327
+ case 46:
328
+ if (doGetCaretPosition($input[0]) === 0) {
329
+ var next = $inputWrapper.next();
330
+ if (next) {
331
+ self.remove(next.data('item'));
332
+ }
333
+ }
334
+ break;
335
+
336
+ // LEFT ARROW
337
+ case 37:
338
+ // Try to move the input before the previous tag
339
+ var $prevTag = $inputWrapper.prev();
340
+ if ($input.val().length === 0 && $prevTag[0]) {
341
+ $prevTag.before($inputWrapper);
342
+ $input.focus();
343
+ }
344
+ break;
345
+ // RIGHT ARROW
346
+ case 39:
347
+ // Try to move the input after the next tag
348
+ var $nextTag = $inputWrapper.next();
349
+ if ($input.val().length === 0 && $nextTag[0]) {
350
+ $nextTag.after($inputWrapper);
351
+ $input.focus();
352
+ }
353
+ break;
354
+ default:
355
+ // ignore
356
+ }
357
+
358
+ // Reset internal input's size
359
+ var textLength = $input.val().length,
360
+ wordSpace = Math.ceil(textLength / 5),
361
+ size = textLength + wordSpace + 1;
362
+ $input.attr('size', Math.max(this.inputSize, $input.val().length));
363
+ }, self));
364
+
365
+ self.$container.on('keypress', 'input', $.proxy(function(event) {
366
+ var $input = $(event.target);
367
+
368
+ if (self.$element.attr('disabled')) {
369
+ self.$input.attr('disabled', 'disabled');
370
+ return;
371
+ }
372
+
373
+ var text = $input.val(),
374
+ maxLengthReached = self.options.maxChars && text.length >= self.options.maxChars;
375
+ if (self.options.freeInput && (keyCombinationInList(event, self.options.confirmKeys) || maxLengthReached)) {
376
+ self.add(maxLengthReached ? text.substr(0, self.options.maxChars) : text);
377
+ $input.val('');
378
+ event.preventDefault();
379
+ }
380
+
381
+ // Reset internal input's size
382
+ var textLength = $input.val().length,
383
+ wordSpace = Math.ceil(textLength / 5),
384
+ size = textLength + wordSpace + 1;
385
+ $input.attr('size', Math.max(this.inputSize, $input.val().length));
386
+ }, self));
387
+
388
+ // Remove icon clicked
389
+ self.$container.on('click', '[data-role=remove]', $.proxy(function(event) {
390
+ if (self.$element.attr('disabled')) {
391
+ return;
392
+ }
393
+ self.remove($(event.target).closest('.tag').data('item'));
394
+ }, self));
395
+
396
+ // Only add existing value as tags when using strings as tags
397
+ if (self.options.itemValue === defaultOptions.itemValue) {
398
+ if (self.$element[0].tagName === 'INPUT') {
399
+ self.add(self.$element.val());
400
+ } else {
401
+ $('option', self.$element).each(function() {
402
+ self.add($(this).attr('value'), true);
403
+ });
404
+ }
405
+ }
406
+ },
407
+
408
+ /**
409
+ * Removes all tagsinput behaviour and unregsiter all event handlers
410
+ */
411
+ destroy: function() {
412
+ var self = this;
413
+
414
+ // Unbind events
415
+ self.$container.off('keypress', 'input');
416
+ self.$container.off('click', '[role=remove]');
417
+
418
+ self.$container.remove();
419
+ self.$element.removeData('tagsinput');
420
+ self.$element.show();
421
+ },
422
+
423
+ /**
424
+ * Sets focus on the tagsinput
425
+ */
426
+ focus: function() {
427
+ this.$input.focus();
428
+ },
429
+
430
+ /**
431
+ * Returns the internal input element
432
+ */
433
+ input: function() {
434
+ return this.$input;
435
+ },
436
+
437
+ /**
438
+ * Returns the element which is wrapped around the internal input. This
439
+ * is normally the $container, but typeahead.js moves the $input element.
440
+ */
441
+ findInputWrapper: function() {
442
+ var elt = this.$input[0],
443
+ container = this.$container[0];
444
+ while(elt && elt.parentNode !== container)
445
+ elt = elt.parentNode;
446
+
447
+ return $(elt);
448
+ }
449
+ };
450
+
451
+ /**
452
+ * Register JQuery plugin
453
+ */
454
+ $.fn.tagsinput = function(arg1, arg2, arg3) {
455
+ var results = [];
456
+
457
+ this.each(function() {
458
+ var tagsinput = $(this).data('tagsinput');
459
+ // Initialize a new tags input
460
+ if (!tagsinput) {
461
+ tagsinput = new TagsInput(this, arg1);
462
+ $(this).data('tagsinput', tagsinput);
463
+ results.push(tagsinput);
464
+
465
+ if (this.tagName === 'SELECT') {
466
+ $('option', $(this)).attr('selected', 'selected');
467
+ }
468
+
469
+ // Init tags from $(this).val()
470
+ $(this).val($(this).val());
471
+ } else if (!arg1 && !arg2) {
472
+ // tagsinput already exists
473
+ // no function, trying to init
474
+ results.push(tagsinput);
475
+ } else if(tagsinput[arg1] !== undefined) {
476
+ // Invoke function on existing tags input
477
+ if(tagsinput[arg1].length === 3 && arg3 !== undefined){
478
+ var retVal = tagsinput[arg1](arg2, null, arg3);
479
+ }else{
480
+ var retVal = tagsinput[arg1](arg2);
481
+ }
482
+ if (retVal !== undefined)
483
+ results.push(retVal);
484
+ }
485
+ });
486
+
487
+ if ( typeof arg1 == 'string') {
488
+ // Return the results from the invoked function calls
489
+ return results.length > 1 ? results : results[0];
490
+ } else {
491
+ return results;
492
+ }
493
+ };
494
+
495
+ $.fn.tagsinput.Constructor = TagsInput;
496
+
497
+ /**
498
+ * Most options support both a string or number as well as a function as
499
+ * option value. This function makes sure that the option with the given
500
+ * key in the given options is wrapped in a function
501
+ */
502
+ function makeOptionItemFunction(options, key) {
503
+ if (typeof options[key] !== 'function') {
504
+ var propertyName = options[key];
505
+ options[key] = function(item) { return item[propertyName]; };
506
+ }
507
+ }
508
+ function makeOptionFunction(options, key) {
509
+ if (typeof options[key] !== 'function') {
510
+ var value = options[key];
511
+ options[key] = function() { return value; };
512
+ }
513
+ }
514
+ /**
515
+ * HtmlEncodes the given value
516
+ */
517
+ var htmlEncodeContainer = $('<div />');
518
+ function htmlEncode(value) {
519
+ if (value) {
520
+ return htmlEncodeContainer.text(value).html();
521
+ } else {
522
+ return '';
523
+ }
524
+ }
525
+
526
+ /**
527
+ * Returns the position of the caret in the given input field
528
+ * http://flightschool.acylt.com/devnotes/caret-position-woes/
529
+ */
530
+ function doGetCaretPosition(oField) {
531
+ var iCaretPos = 0;
532
+ if (document.selection) {
533
+ oField.focus ();
534
+ var oSel = document.selection.createRange();
535
+ oSel.moveStart ('character', -oField.value.length);
536
+ iCaretPos = oSel.text.length;
537
+ } else if (oField.selectionStart || oField.selectionStart == '0') {
538
+ iCaretPos = oField.selectionStart;
539
+ }
540
+ return (iCaretPos);
541
+ }
542
+
543
+ /**
544
+ * Returns boolean indicates whether user has pressed an expected key combination.
545
+ * @param object keyPressEvent: JavaScript event object, refer
546
+ * http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
547
+ * @param object lookupList: expected key combinations, as in:
548
+ * [13, {which: 188, shiftKey: true}]
549
+ */
550
+ function keyCombinationInList(keyPressEvent, lookupList) {
551
+ var found = false;
552
+ $.each(lookupList, function (index, keyCombination) {
553
+ if (typeof (keyCombination) === 'number' && keyPressEvent.which === keyCombination) {
554
+ found = true;
555
+ return false;
556
+ }
557
+
558
+ if (keyPressEvent.which === keyCombination.which) {
559
+ var alt = !keyCombination.hasOwnProperty('altKey') || keyPressEvent.altKey === keyCombination.altKey,
560
+ shift = !keyCombination.hasOwnProperty('shiftKey') || keyPressEvent.shiftKey === keyCombination.shiftKey,
561
+ ctrl = !keyCombination.hasOwnProperty('ctrlKey') || keyPressEvent.ctrlKey === keyCombination.ctrlKey;
562
+ if (alt && shift && ctrl) {
563
+ found = true;
564
+ return false;
565
+ }
566
+ }
567
+ });
568
+
569
+ return found;
570
+ }
571
+
572
+ /**
573
+ * Initialize tagsinput behaviour on inputs and selects which have
574
+ * data-role=tagsinput
575
+ */
576
+ // CHANGED: (RH) I do this myself... see tag_input.js.cofee
577
+ // $(function() {
578
+ // $("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput();
579
+ // });
580
+ })(window.jQuery);
@@ -0,0 +1,55 @@
1
+ .bootstrap-tagsinput {
2
+ background-color: #fff;
3
+ border: 1px solid #ccc;
4
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
5
+ display: block;
6
+ padding: 4px 6px;
7
+ color: #555;
8
+ vertical-align: middle;
9
+ border-radius: 4px;
10
+ max-width: 100%;
11
+ line-height: 22px;
12
+ cursor: text;
13
+ }
14
+ .bootstrap-tagsinput input {
15
+ border: none;
16
+ box-shadow: none;
17
+ outline: none;
18
+ background-color: transparent;
19
+ padding: 0 6px;
20
+ margin: 0;
21
+ width: auto !important;
22
+ max-width: inherit;
23
+ }
24
+ .bootstrap-tagsinput.form-control input::-moz-placeholder {
25
+ color: #777;
26
+ opacity: 1;
27
+ }
28
+ .bootstrap-tagsinput.form-control input:-ms-input-placeholder {
29
+ color: #777;
30
+ }
31
+ .bootstrap-tagsinput.form-control input::-webkit-input-placeholder {
32
+ color: #777;
33
+ }
34
+ .bootstrap-tagsinput input:focus {
35
+ border: none;
36
+ box-shadow: none;
37
+ }
38
+ .bootstrap-tagsinput .tag {
39
+ margin-right: 2px;
40
+ color: white;
41
+ }
42
+ .bootstrap-tagsinput .tag [data-role="remove"] {
43
+ margin-left: 8px;
44
+ cursor: pointer;
45
+ }
46
+ .bootstrap-tagsinput .tag [data-role="remove"]:after {
47
+ content: "x";
48
+ padding: 0px 2px;
49
+ }
50
+ .bootstrap-tagsinput .tag [data-role="remove"]:hover {
51
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
52
+ }
53
+ .bootstrap-tagsinput .tag [data-role="remove"]:hover:active {
54
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
55
+ }
@@ -0,0 +1,50 @@
1
+ .typeahead, .tt-query, .tt-hint {
2
+ width: 396px;
3
+ height: 34px;
4
+ padding: 8px 12px;
5
+ font-size: 15px;
6
+ line-height: 15px;
7
+ border: 1px solid #ccc;
8
+ -webkit-border-radius: 4px;
9
+ -moz-border-radius: 4px;
10
+ border-radius: 4px;
11
+ outline: none;
12
+ }
13
+
14
+ .tt-query {
15
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
16
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
17
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
18
+ }
19
+
20
+ .tt-hint {
21
+ color: #999
22
+ }
23
+
24
+ .tt-dropdown-menu {
25
+ text-align: left;
26
+ width: 422px;
27
+ margin-top: 12px;
28
+ padding: 8px 0;
29
+ background-color: #fff;
30
+ border: 1px solid #ccc;
31
+ border: 1px solid rgba(0, 0, 0, 0.2);
32
+ -webkit-border-radius: 4px;
33
+ -moz-border-radius: 4px;
34
+ border-radius: 4px;
35
+ -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
36
+ -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
37
+ box-shadow: 0 5px 10px rgba(0,0,0,.2);
38
+ }
39
+
40
+ .tt-suggestion, .tt-empty-message {
41
+ padding: 3px 20px;
42
+ font-size: 16px;
43
+ line-height: 24px;
44
+ }
45
+
46
+ .tt-suggestion.tt-cursor {
47
+ cursor: pointer;
48
+ color: #fff;
49
+ background-color: #0097cf;
50
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: biola_wcms_components
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Hall
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-25 00:00:00.000000000 Z
11
+ date: 2015-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ace-rails-ap
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: buweb_content_models
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0.82'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0.82'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: coffee-rails
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +52,20 @@ dependencies:
38
52
  - - ">="
39
53
  - !ruby/object:Gem::Version
40
54
  version: '4.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pundit
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.3'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.3'
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: sass-rails
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -112,16 +140,21 @@ files:
112
140
  - app/assets/javascripts/components/forms/json_editor.js.coffee
113
141
  - app/assets/javascripts/components/forms/person_lookup.js.coffee
114
142
  - app/assets/javascripts/components/forms/presentation_data_editor.js.coffee
143
+ - app/assets/javascripts/components/forms/tag_input.js.coffee
115
144
  - app/assets/javascripts/components/forms/yaml_editor.js.coffee
116
145
  - app/assets/javascripts/configuration/file_uploader.js.coffee
117
146
  - app/assets/javascripts/configuration/setup_redactor.js.coffee
118
147
  - app/assets/stylesheets/_mixins.scss
119
148
  - app/assets/stylesheets/_settings.scss
120
- - app/assets/stylesheets/biola-wcms-components.scss
149
+ - app/assets/stylesheets/biola-wcms-components.css.scss
121
150
  - app/assets/stylesheets/components/alerts/_message_list.scss
122
151
  - app/assets/stylesheets/components/forms/_person_lookup.scss
123
152
  - app/assets/stylesheets/components/forms/_presentation_data_editor.scss
153
+ - app/assets/stylesheets/components/forms/_tag_input.scss
124
154
  - app/assets/stylesheets/components/navigation/_site_nav.scss
155
+ - app/controllers/wcms_application_controller.rb
156
+ - app/controllers/wcms_components/embedded_images_controller.rb
157
+ - app/controllers/wcms_components/people_controller.rb
125
158
  - app/helpers/wcms_components/alerts_helper.rb
126
159
  - app/helpers/wcms_components/component_helper.rb
127
160
  - app/helpers/wcms_components/navigation_helper.rb
@@ -130,6 +163,8 @@ files:
130
163
  - app/views/wcms_components/forms/_person_lookup.html.slim
131
164
  - app/views/wcms_components/forms/_presentation_data_editor.html.slim
132
165
  - app/views/wcms_components/forms/_redactor_editor.html.slim
166
+ - app/views/wcms_components/forms/_related_object_tags.html.slim
167
+ - app/views/wcms_components/forms/_tag_input.html.slim
133
168
  - app/views/wcms_components/forms/_text_area.html.slim
134
169
  - app/views/wcms_components/forms/_yaml_editor.html.slim
135
170
  - app/views/wcms_components/navigation/_page_nav.html.slim
@@ -138,17 +173,22 @@ files:
138
173
  - app/views/wcms_components/shared/_embedded_image_uploader.html.slim
139
174
  - biola_wcms_components.gemspec
140
175
  - config/locales/en.yml
176
+ - config/routes.rb
141
177
  - lib/biola_wcms_components.rb
142
178
  - lib/biola_wcms_components/configuration.rb
143
179
  - lib/biola_wcms_components/engine.rb
144
180
  - lib/biola_wcms_components/version.rb
181
+ - lib/components/cas_authentication.rb
145
182
  - lib/components/menu_block.rb
146
183
  - lib/components/presentation_data_editor.rb
184
+ - vendor/assets/javascripts/bootstrap-tagsinput.js
147
185
  - vendor/assets/javascripts/handlebars.js
148
186
  - vendor/assets/javascripts/redactor.js
149
187
  - vendor/assets/javascripts/redactor_fullscreen.js
150
188
  - vendor/assets/javascripts/typeahead.js
189
+ - vendor/assets/stylesheets/bootstrap-tagsinput.css
151
190
  - vendor/assets/stylesheets/redactor.css
191
+ - vendor/assets/stylesheets/typeahead.css
152
192
  homepage: https://github.com/biola/biola-wcms-components
153
193
  licenses:
154
194
  - MIT