diff --git a/app/controllers/public/profile_controller.rb b/app/controllers/public/profile_controller.rb
index fca23a7..0e5c83d 100644
--- a/app/controllers/public/profile_controller.rb
+++ b/app/controllers/public/profile_controller.rb
@@ -19,6 +19,11 @@ class ProfileController < PublicController
@network_activities = @profile.tracked_notifications.visible.paginate(:per_page => 15, :page => params[:page]) if @network_activities.empty?
@activities = @profile.activities.paginate(:per_page => 15, :page => params[:page])
end
+
+ # TODO Find a way to filter these through sql
+ @network_activities = filter_private_scraps(@network_activities)
+ @activities = filter_private_scraps(@activities)
+
@tags = profile.article_tags
allow_access_to_page
end
@@ -231,6 +236,7 @@ class ProfileController < PublicController
@scrap = Scrap.new(params[:scrap])
@scrap.sender= sender
@scrap.receiver= receiver
+ @scrap.marked_people = treat_followed_entries(params[:filter_followed])
@tab_action = params[:tab_action]
@message = @scrap.save ? _("Message successfully sent.") : _("You can't leave an empty message.")
activities = @profile.activities.paginate(:per_page => 15, :page => params[:page]) if params[:not_load_scraps].nil?
@@ -253,6 +259,14 @@ class ProfileController < PublicController
end
end
+ def search_followed
+ result = []
+ circles = find_by_contents(:circles, user, user.circles.where(:profile_type => 'Person'), params[:q])[:results]
+ followed = find_by_contents(:followed, user, Profile.followed_by(user), params[:q])[:results]
+ result = circles + followed
+ render :text => prepare_to_token_input_by_class(result).to_json
+ end
+
def view_more_activities
@activities = @profile.activities.paginate(:per_page => 10, :page => params[:page])
render :partial => 'profile_activities_list', :locals => {:activities => @activities}
@@ -434,7 +448,6 @@ class ProfileController < PublicController
end
end
-
protected
def check_access_to_profile
@@ -480,4 +493,41 @@ class ProfileController < PublicController
render_not_found unless profile.allow_followers?
end
+ def treat_followed_entries(entries)
+ return [] if entries.blank? || profile != user
+
+ followed = []
+ entries.split(',').map do |entry|
+ klass, identifier = entry.split('_')
+ case klass
+ when 'Person'
+ followed << Person.find(identifier)
+ when 'Circle'
+ circle = Circle.find(identifier)
+ followed += Profile.in_circle(circle)
+ end
+ end
+ followed.uniq
+ end
+
+ def filter_private_scraps(activities)
+ activities = Array(activities)
+ activities.delete_if do |item|
+ if item.kind_of?(ProfileActivity)
+ target = item.activity
+ owner = profile
+ else
+ target = item.target
+ owner = item.user
+ end
+ !environment.admins.include?(user) &&
+ #TODO Consider this if allowing to mark people on organization's wall
+ #!profile.admins.include?(user) &&
+ owner != user &&
+ target.is_a?(Scrap) &&
+ target.marked_people.present? &&
+ !target.marked_people.include?(user)
+ end
+ activities
+ end
end
diff --git a/app/helpers/article_helper.rb b/app/helpers/article_helper.rb
index f15158a..ff82b2a 100644
--- a/app/helpers/article_helper.rb
+++ b/app/helpers/article_helper.rb
@@ -160,6 +160,10 @@ module ArticleHelper
array.map { |object| {:label => object.name, :value => object.name} }
end
+ def prepare_to_token_input_by_class(array)
+ array.map { |object| {:id => "#{object.class.name}_#{object.id || object.name}", :name => "#{object.name} (#{_(object.class.name)})", :class => object.class.name}}
+ end
+
def cms_label_for_new_children
_('New article')
end
diff --git a/app/helpers/token_helper.rb b/app/helpers/token_helper.rb
index 43540b0..008da6b 100644
--- a/app/helpers/token_helper.rb
+++ b/app/helpers/token_helper.rb
@@ -5,10 +5,11 @@ module TokenHelper
end
def token_input_field_tag(name, element_id, search_action, options = {}, text_field_options = {}, html_options = {})
- options[:min_chars] ||= 3
+ options[:min_chars] ||= 2
options[:hint_text] ||= _("Type in a search term")
options[:no_results_text] ||= _("No results")
options[:searching_text] ||= _("Searching...")
+ options[:placeholder] ||= 'null'
options[:search_delay] ||= 1000
options[:prevent_duplicates] ||= true
options[:backspace_delete_item] ||= false
@@ -20,6 +21,9 @@ module TokenHelper
options[:on_delete] ||= 'null'
options[:on_ready] ||= 'null'
options[:query_param] ||= 'q'
+ options[:theme] ||= 'null'
+ options[:results_formatter] ||= 'null'
+ options[:token_formatter] ||= 'null'
result = text_field_tag(name, nil, text_field_options.merge(html_options.merge({:id => element_id})))
result += javascript_tag("jQuery('##{element_id}')
@@ -29,6 +33,7 @@ module TokenHelper
hintText: #{options[:hint_text].to_json},
noResultsText: #{options[:no_results_text].to_json},
searchingText: #{options[:searching_text].to_json},
+ placeholder: #{options[:placeholder].to_json},
searchDelay: #{options[:search_delay].to_json},
preventDuplicates: #{options[:prevent_duplicates].to_json},
backspaceDeleteItem: #{options[:backspace_delete_item].to_json},
@@ -39,6 +44,9 @@ module TokenHelper
onAdd: #{options[:on_add]},
onDelete: #{options[:on_delete]},
onReady: #{options[:on_ready]},
+ theme: #{options[:theme] == 'null' ? options[:theme] : options[:theme].to_json},
+ resultsFormater: #{options[:results_formatter]},
+ tokenFormater: #{options[:token_formatter]},
});
")
result += javascript_tag("jQuery('##{element_id}').focus();") if options[:focus]
diff --git a/app/jobs/notify_activity_to_profiles_job.rb b/app/jobs/notify_activity_to_profiles_job.rb
index 42d6f88..98a94ee 100644
--- a/app/jobs/notify_activity_to_profiles_job.rb
+++ b/app/jobs/notify_activity_to_profiles_job.rb
@@ -19,8 +19,13 @@ class NotifyActivityToProfilesJob < Struct.new(:tracked_action_id)
# Notify the user
ActionTrackerNotification.create(:profile_id => tracked_action.user.id, :action_tracker_id => tracked_action.id)
- # Notify all followers
- ActionTrackerNotification.connection.execute("INSERT INTO action_tracker_notifications(profile_id, action_tracker_id) SELECT DISTINCT c.person_id, #{tracked_action.id} FROM profiles_circles AS p JOIN circles as c ON c.id = p.circle_id WHERE p.profile_id = #{tracked_action.user.id} AND (c.person_id NOT IN (SELECT atn.profile_id FROM action_tracker_notifications AS atn WHERE atn.action_tracker_id = #{tracked_action.id}))")
+ if target.is_a?(Scrap) && target.marked_people.present?
+ # Notify only marked people
+ ActionTrackerNotification.connection.execute("INSERT INTO action_tracker_notifications(profile_id, action_tracker_id) SELECT DISTINCT profiles.id, #{tracked_action.id} FROM profiles WHERE profiles.id IN (#{target.marked_people.map(&:id).join(',')})")
+ else
+ # Notify all followers
+ ActionTrackerNotification.connection.execute("INSERT INTO action_tracker_notifications(profile_id, action_tracker_id) SELECT DISTINCT c.person_id, #{tracked_action.id} FROM profiles_circles AS p JOIN circles as c ON c.id = p.circle_id WHERE p.profile_id = #{tracked_action.user.id} AND (c.person_id NOT IN (SELECT atn.profile_id FROM action_tracker_notifications AS atn WHERE atn.action_tracker_id = #{tracked_action.id}))")
+ end
if tracked_action.user.is_a? Organization
ActionTrackerNotification.connection.execute "insert into action_tracker_notifications(profile_id, action_tracker_id) " +
diff --git a/app/models/circle.rb b/app/models/circle.rb
index d5e5478..ee4c897 100644
--- a/app/models/circle.rb
+++ b/app/models/circle.rb
@@ -1,4 +1,10 @@
class Circle < ApplicationRecord
+ SEARCHABLE_FIELDS = {
+ :name => {:label => _('Name'), :weight => 1}
+ }
+
+ _('Circle')
+
has_many :profile_followers
belongs_to :person
diff --git a/app/models/person.rb b/app/models/person.rb
index 828b536..a68fa4e 100644
--- a/app/models/person.rb
+++ b/app/models/person.rb
@@ -121,6 +121,8 @@ class Person < Profile
where 'profile_suggestions.suggestion_type = ? AND profile_suggestions.enabled = ?', 'Community', true
}, through: :suggested_profiles, source: :suggestion
+ has_and_belongs_to_many :marked_scraps, :join_table => :private_scraps, :class_name => 'Scrap'
+
scope :more_popular, -> { order 'friends_count DESC' }
scope :abusers, -> {
diff --git a/app/models/scrap.rb b/app/models/scrap.rb
index 3cc869a..a5a4a62 100644
--- a/app/models/scrap.rb
+++ b/app/models/scrap.rb
@@ -2,7 +2,7 @@ class Scrap < ApplicationRecord
include SanitizeHelper
- attr_accessible :content, :sender_id, :receiver_id, :scrap_id
+ attr_accessible :content, :sender_id, :receiver_id, :scrap_id, :marked_people
SEARCHABLE_FIELDS = {
:content => {:label => _('Content'), :weight => 1},
@@ -19,6 +19,8 @@ class Scrap < ApplicationRecord
where profile_activities: {activity_type: 'Scrap'}
}, foreign_key: :activity_id, dependent: :destroy
+ has_and_belongs_to_many :marked_people, :join_table => :private_scraps, :class_name => 'Person'
+
after_create :create_activity
after_update :update_activity
diff --git a/app/views/profile/_profile_wall.html.erb b/app/views/profile/_profile_wall.html.erb
index 831347a..a5c9501 100644
--- a/app/views/profile/_profile_wall.html.erb
+++ b/app/views/profile/_profile_wall.html.erb
@@ -1,8 +1,11 @@
<%= _("%s's wall") % @profile.name %>
<%= flash[:error] %>
- <%= form_remote_tag :url => {:controller => 'profile', :action => 'leave_scrap', :tab_action => 'wall' }, :update => 'profile_activities', :success => "jQuery('#leave_scrap_content').val('')", :complete => "jQuery('#leave_scrap_form').removeClass('loading').find('*').attr('disabled', false)", :loading => "jQuery('#leave_scrap_form').addClass('loading').find('*').attr('disabled', true)", :html => {:id => 'leave_scrap_form' } do %>
- <%= limited_text_area :scrap, :content, 420, 'leave_scrap_content', :cols => 50, :rows => 2, :class => 'autogrow' %>
+ <%= form_remote_tag :url => {:controller => 'profile', :action => 'leave_scrap', :tab_action => 'wall' }, :update => 'profile_activities', :success => "jQuery('#leave_scrap_content').val(''); jQuery('#filter-followed').tokenInput('clear')", :complete => "jQuery('#leave_scrap_form').removeClass('loading').find('*').attr('disabled', false)", :loading => "jQuery('#leave_scrap_form').addClass('loading').find('*').attr('disabled', true)", :html => {:id => 'leave_scrap_form' } do %>
+ <%= limited_text_area :scrap, :content, 420, 'leave_scrap_content', :rows => 2, :class => 'autogrow' %>
+ <% if profile == user %>
+ <%= token_input_field_tag(:filter_followed, 'filter-followed', {:action => 'search_followed'}, {:theme => 'facebook', :placeholder => _('Filter followed, friends or group of friends to send them a private scrap...')}) %>
+ <% end %>
<%= submit_button :new, _('Share') %>
<% end %>
diff --git a/db/migrate/20160705162914_create_private_scraps.rb b/db/migrate/20160705162914_create_private_scraps.rb
new file mode 100644
index 0000000..cfa2154
--- /dev/null
+++ b/db/migrate/20160705162914_create_private_scraps.rb
@@ -0,0 +1,8 @@
+class CreatePrivateScraps < ActiveRecord::Migration
+ def change
+ create_table :private_scraps do |t|
+ t.references :person
+ t.references :scrap
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5019b82..a97d4ac 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160608123748) do
+ActiveRecord::Schema.define(version: 20160705162914) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -524,6 +524,11 @@ ActiveRecord::Schema.define(version: 20160608123748) do
t.datetime "updated_at"
end
+ create_table "private_scraps", force: :cascade do |t|
+ t.integer "person_id"
+ t.integer "scrap_id"
+ end
+
create_table "product_qualifiers", force: :cascade do |t|
t.integer "product_id"
t.integer "qualifier_id"
diff --git a/public/javascripts/jquery.tokeninput.js b/public/javascripts/jquery.tokeninput.js
index 548ec70..4b69d82 100644
--- a/public/javascripts/jquery.tokeninput.js
+++ b/public/javascripts/jquery.tokeninput.js
@@ -1,208 +1,293 @@
/*
* jQuery Plugin: Tokenizing Autocomplete Text Entry
- * Version 1.5.0
- * Requires jQuery 1.6+
+ * Version 1.6.2
*
* Copyright (c) 2009 James Smith (http://loopj.com)
* Licensed jointly under the GPL and MIT licenses,
* choose which one suits your project best!
*
*/
-
-(function ($) {
-// Default settings
-var DEFAULT_SETTINGS = {
- hintText: "Type in a search term",
- noResultsText: "No results",
- searchingText: "Searching...",
- deleteText: "×",
+;(function ($) {
+ var DEFAULT_SETTINGS = {
+ // Search settings
+ method: "GET",
+ queryParam: "q",
searchDelay: 300,
minChars: 1,
- permanentDropdown: false,
- showAllResults: false,
- tokenLimit: null,
+ propertyToSearch: "name",
jsonContainer: null,
- method: "GET",
contentType: "json",
- queryParam: "q",
- tokenDelimiter: ",",
- preventDuplicates: false,
+ excludeCurrent: false,
+ excludeCurrentParameter: "x",
+
+ // Prepopulation settings
prePopulate: null,
processPrePopulate: false,
+
+ // Display settings
+ hintText: "Type in a search term",
+ noResultsText: "No results",
+ searchingText: "Searching...",
+ deleteText: "×",
animateDropdown: true,
- dontAdd: false,
+ placeholder: null,
+ theme: null,
+ zindex: 999,
+ resultsLimit: null,
+
+ enableHTML: false,
+
+ resultsFormatter: function(item) {
+ var string = item[this.propertyToSearch];
+ return "" + (this.enableHTML ? string : _escapeHTML(string)) + "";
+ },
+
+ tokenFormatter: function(item) {
+ var string = item[this.propertyToSearch];
+ return "" + (this.enableHTML ? string : _escapeHTML(string)) + "
";
+ },
+
+ // Tokenization settings
+ tokenLimit: null,
+ tokenDelimiter: ",",
+ preventDuplicates: false,
+ tokenValue: "id",
+
+ // Behavioral settings
+ allowFreeTagging: false,
+ allowTabOut: false,
+ autoSelectFirstResult: false,
+
+ // Callbacks
onResult: null,
+ onCachedResult: null,
onAdd: null,
+ onFreeTaggingAdd: null,
onDelete: null,
+ onReady: null,
+
+ // Other settings
idPrefix: "token-input-",
- zindex: 999,
- backspaceDeleteItem: true
-};
-
-// Default classes to use when theming
-var DEFAULT_CLASSES = {
- tokenList: "token-input-list",
- token: "token-input-token",
- tokenDelete: "token-input-delete-token",
- selectedToken: "token-input-selected-token",
- highlightedToken: "token-input-highlighted-token",
- dropdown: "token-input-dropdown",
- dropdownItem: "token-input-dropdown-item",
- dropdownItem2: "token-input-dropdown-item2",
- selectedDropdownItem: "token-input-selected-dropdown-item",
- inputToken: "token-input-input-token",
- blurText: "token-input-blur-text",
-};
-
-// Input box position "enum"
-var POSITION = {
- BEFORE: 0,
- AFTER: 1,
- END: 2
-};
-
-// Keys "enum"
-var KEY = {
- BACKSPACE: 8,
- DELETE: 46,
- TAB: 9,
- ENTER: 13,
- ESCAPE: 27,
- SPACE: 32,
- PAGE_UP: 33,
- PAGE_DOWN: 34,
- END: 35,
- HOME: 36,
- LEFT: 37,
- UP: 38,
- RIGHT: 39,
- DOWN: 40,
- NUMPAD_ENTER: 108,
- COMMA: 188
-};
-
-// Additional public (exposed) methods
-var methods = {
- init: function(url_or_data_or_function, options) {
- return this.each(function () {
- $(this).data("tokenInputObject", new $.TokenList(this, url_or_data_or_function, options));
- });
- },
- clear: function() {
- this.data("tokenInputObject").clear();
- return this;
- },
- add: function(item) {
- this.data("tokenInputObject").add(item);
- return this;
- },
- remove: function(item) {
- this.data("tokenInputObject").remove(item);
- return this;
- }
-}
-
-// Expose the .tokenInput function to jQuery as a plugin
-$.fn.tokenInput = function (method) {
- // Method calling and initialization logic
- if(methods[method]) {
- return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
- } else {
- return methods.init.apply(this, arguments);
- }
-};
-
-// TokenList class for each input
-$.TokenList = function (input, url_or_data, options) {
- //
- // Initialization
- //
- var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
-
- // Configure the data source
- if(typeof(url_or_data) === "string") {
- // Set the url to query against
- settings.url = url_or_data;
-
- // Make a smart guess about cross-domain if it wasn't explicitly specified
- if(settings.crossDomain === undefined) {
- if(settings.url.indexOf("://") === -1) {
- settings.crossDomain = false;
- } else {
- settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.url.split(/\/+/g)[1]);
- }
+
+ // Keep track if the input is currently in disabled mode
+ disabled: false
+ };
+
+ // Default classes to use when theming
+ var DEFAULT_CLASSES = {
+ tokenList : "token-input-list",
+ token : "token-input-token",
+ tokenReadOnly : "token-input-token-readonly",
+ tokenDelete : "token-input-delete-token",
+ selectedToken : "token-input-selected-token",
+ highlightedToken : "token-input-highlighted-token",
+ dropdown : "token-input-dropdown",
+ dropdownItem : "token-input-dropdown-item",
+ dropdownItem2 : "token-input-dropdown-item2",
+ selectedDropdownItem : "token-input-selected-dropdown-item",
+ inputToken : "token-input-input-token",
+ focused : "token-input-focused",
+ disabled : "token-input-disabled"
+ };
+
+ // Input box position "enum"
+ var POSITION = {
+ BEFORE : 0,
+ AFTER : 1,
+ END : 2
+ };
+
+ // Keys "enum"
+ var KEY = {
+ BACKSPACE : 8,
+ TAB : 9,
+ ENTER : 13,
+ ESCAPE : 27,
+ SPACE : 32,
+ PAGE_UP : 33,
+ PAGE_DOWN : 34,
+ END : 35,
+ HOME : 36,
+ LEFT : 37,
+ UP : 38,
+ RIGHT : 39,
+ DOWN : 40,
+ NUMPAD_ENTER : 108,
+ COMMA : 188
+ };
+
+ var HTML_ESCAPES = {
+ '&' : '&',
+ '<' : '<',
+ '>' : '>',
+ '"' : '"',
+ "'" : ''',
+ '/' : '/'
+ };
+
+ var HTML_ESCAPE_CHARS = /[&<>"'\/]/g;
+
+ function coerceToString(val) {
+ return String((val === null || val === undefined) ? '' : val);
+ }
+
+ function _escapeHTML(text) {
+ return coerceToString(text).replace(HTML_ESCAPE_CHARS, function(match) {
+ return HTML_ESCAPES[match];
+ });
+ }
+
+ // Additional public (exposed) methods
+ var methods = {
+ init: function(url_or_data_or_function, options) {
+ var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
+
+ return this.each(function () {
+ $(this).data("settings", settings);
+ $(this).data("tokenInputObject", new $.TokenList(this, url_or_data_or_function, settings));
+ });
+ },
+ clear: function() {
+ this.data("tokenInputObject").clear();
+ return this;
+ },
+ add: function(item) {
+ this.data("tokenInputObject").add(item);
+ return this;
+ },
+ remove: function(item) {
+ this.data("tokenInputObject").remove(item);
+ return this;
+ },
+ get: function() {
+ return this.data("tokenInputObject").getTokens();
+ },
+ toggleDisabled: function(disable) {
+ this.data("tokenInputObject").toggleDisabled(disable);
+ return this;
+ },
+ setOptions: function(options){
+ $(this).data("settings", $.extend({}, $(this).data("settings"), options || {}));
+ return this;
+ },
+ destroy: function () {
+ if (this.data("tokenInputObject")) {
+ this.data("tokenInputObject").clear();
+ var tmpInput = this;
+ var closest = this.parent();
+ closest.empty();
+ tmpInput.show();
+ closest.append(tmpInput);
+ return tmpInput;
}
- } else if(typeof(url_or_data) === "object") {
- // Set the local data to search through
- settings.local_data = url_or_data;
- }
-
- // Build class names
- if(settings.classes) {
- // Use custom class names
- settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes);
- } else if(settings.theme) {
- // Use theme-suffixed default class names
- settings.classes = {};
- $.each(DEFAULT_CLASSES, function(key, value) {
- settings.classes[key] = value + "-" + settings.theme;
- });
- } else {
- settings.classes = DEFAULT_CLASSES;
- }
-
-
- // Save the tokens
- var saved_tokens = [];
-
- // Keep track of the number of tokens in the list
- var token_count = 0;
-
- // Basic cache to save on db hits
- var cache = new $.TokenList.Cache();
-
- // Keep track of the timeout, old vals
- var timeout;
- var input_val = '';
-
- // Create a new text input an attach keyup events
- var input_box = $("")
- .css({
- outline: "none"
- })
- .attr("id", settings.idPrefix + input.id)
- .focus(function () {
- if (settings.tokenLimit === null || settings.tokenLimit !== token_count) {
- if(settings.permanentDropdown || settings.showAllResults) {
- hide_dropdown_hint();
+ }
+ };
+
+ // Expose the .tokenInput function to jQuery as a plugin
+ $.fn.tokenInput = function (method) {
+ // Method calling and initialization logic
+ if (methods[method]) {
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+ } else {
+ return methods.init.apply(this, arguments);
+ }
+ };
+
+ // TokenList class for each input
+ $.TokenList = function (input, url_or_data, settings) {
+ //
+ // Initialization
+ //
+
+ // Configure the data source
+ if (typeof(url_or_data) === "string" || typeof(url_or_data) === "function") {
+ // Set the url to query against
+ $(input).data("settings").url = url_or_data;
+
+ // If the URL is a function, evaluate it here to do our initalization work
+ var url = computeURL();
+
+ // Make a smart guess about cross-domain if it wasn't explicitly specified
+ if ($(input).data("settings").crossDomain === undefined && typeof url === "string") {
+ if(url.indexOf("://") === -1) {
+ $(input).data("settings").crossDomain = false;
+ } else {
+ $(input).data("settings").crossDomain = (location.href.split(/\/+/g)[1] !== url.split(/\/+/g)[1]);
+ }
+ }
+ } else if (typeof(url_or_data) === "object") {
+ // Set the local data to search through
+ $(input).data("settings").local_data = url_or_data;
+ }
+
+ // Build class names
+ if($(input).data("settings").classes) {
+ // Use custom class names
+ $(input).data("settings").classes = $.extend({}, DEFAULT_CLASSES, $(input).data("settings").classes);
+ } else if($(input).data("settings").theme) {
+ // Use theme-suffixed default class names
+ $(input).data("settings").classes = {};
+ $.each(DEFAULT_CLASSES, function(key, value) {
+ $(input).data("settings").classes[key] = value + "-" + $(input).data("settings").theme;
+ });
+ } else {
+ $(input).data("settings").classes = DEFAULT_CLASSES;
+ }
+
+ // Save the tokens
+ var saved_tokens = [];
+
+ // Keep track of the number of tokens in the list
+ var token_count = 0;
+
+ // Basic cache to save on db hits
+ var cache = new $.TokenList.Cache();
+
+ // Keep track of the timeout, old vals
+ var timeout;
+ var input_val;
+
+ // Create a new text input an attach keyup events
+ var input_box = $("")
+ .css({
+ outline: "none"
+ })
+ .attr("id", $(input).data("settings").idPrefix + input.id)
+ .focus(function () {
+ if ($(input).data("settings").disabled) {
+ return false;
} else
- show_dropdown_hint();
- if (settings.showAllResults)
- do_search();
- }
- })
- .blur(function () {
- if(settings.permanentDropdown)
- show_dropdown_hint();
- else {
+ if ($(input).data("settings").tokenLimit === null || $(input).data("settings").tokenLimit !== token_count) {
+ show_dropdown_hint();
+ }
+ token_list.addClass($(input).data("settings").classes.focused);
+ })
+ .blur(function () {
hide_dropdown();
- }
- })
- .bind("keyup keydown blur update", resize_input)
- .keydown(function (event) {
- var previous_token;
- var next_token;
-
- switch(event.keyCode) {
- case KEY.LEFT:
- case KEY.RIGHT:
- case KEY.UP:
- case KEY.DOWN:
- if(!$(this).val()) {
+
+ if ($(input).data("settings").allowFreeTagging) {
+ add_freetagging_tokens();
+ }
+
+ $(this).val("");
+ token_list.removeClass($(input).data("settings").classes.focused);
+ })
+ .bind("keyup keydown blur update", resize_input)
+ .keydown(function (event) {
+ var previous_token;
+ var next_token;
+
+ switch(event.keyCode) {
+ case KEY.LEFT:
+ case KEY.RIGHT:
+ case KEY.UP:
+ case KEY.DOWN:
+ if(this.value.length === 0) {
previous_token = input_token.prev();
next_token = input_token.next();
- if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {
+ if((previous_token.length && previous_token.get(0) === selected_token) ||
+ (next_token.length && next_token.get(0) === selected_token)) {
// Check if there is a previous/next token and it is selected
if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
deselect_token($(selected_token), POSITION.BEFORE);
@@ -217,650 +302,805 @@ $.TokenList = function (input, url_or_data, options) {
select_token($(next_token.get(0)));
}
} else {
- var dropdown_item = null;
-
- if (event.keyCode == KEY.LEFT && (this.selectionStart > 0 || this.selectionStart != this.selectionEnd))
- return true;
- else if (event.keyCode == KEY.RIGHT && (this.selectionEnd < $(this).val().length || this.selectionStart != this.selectionEnd))
- return true;
- else if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
- dropdown_item = $(selected_dropdown_item).next();
- } else {
- dropdown_item = $(selected_dropdown_item).prev();
+ var dropdown_item = null;
+
+ if (event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
+ dropdown_item = $(dropdown).find('li').first();
+
+ if (selected_dropdown_item) {
+ dropdown_item = $(selected_dropdown_item).next();
}
+ } else {
+ dropdown_item = $(dropdown).find('li').last();
- if(dropdown_item.length) {
- select_dropdown_item(dropdown_item);
+ if (selected_dropdown_item) {
+ dropdown_item = $(selected_dropdown_item).prev();
}
- return false;
+ }
+
+ select_dropdown_item(dropdown_item);
}
+
break;
- case KEY.BACKSPACE:
- case KEY.DELETE:
- previous_token = input_token.prev();
- next_token = input_token.next();
+ case KEY.BACKSPACE:
+ previous_token = input_token.prev();
- if(!$(this).val().length && settings.backspaceDeleteItem) {
- if(selected_token) {
- delete_token($(selected_token));
- input_box.focus();
- } else if(KEY.DELETE && next_token.length) {
- select_token($(next_token.get(0)));
- } else if(KEY.BACKSPACE && previous_token.length) {
- select_token($(previous_token.get(0)));
+ if (this.value.length === 0) {
+ if (selected_token) {
+ delete_token($(selected_token));
+ hiddenInput.change();
+ } else if(previous_token.length) {
+ select_token($(previous_token.get(0)));
}
return false;
- } else if(!settings.permanentDropdown && $(this).val().length === 1) {
- hide_dropdown();
+ } else if($(this).val().length === 1) {
+ hide_dropdown();
+ } else {
+ // set a timeout just long enough to let this function finish.
+ setTimeout(function(){ do_search(); }, 5);
+ }
+ break;
+
+ case KEY.TAB:
+ case KEY.ENTER:
+ case KEY.NUMPAD_ENTER:
+ case KEY.COMMA:
+ if(selected_dropdown_item) {
+ add_token($(selected_dropdown_item).data("tokeninput"));
+ hiddenInput.change();
} else {
- // set a timeout just long enough to let this function finish.
- setTimeout(function(){do_search();}, 5);
+ if ($(input).data("settings").allowFreeTagging) {
+ if($(input).data("settings").allowTabOut && $(this).val() === "") {
+ return true;
+ } else {
+ add_freetagging_tokens();
+ }
+ } else {
+ $(this).val("");
+ if($(input).data("settings").allowTabOut) {
+ return true;
+ }
+ }
+ event.stopPropagation();
+ event.preventDefault();
}
- break;
-
- case KEY.TAB:
- case KEY.ENTER:
- case KEY.NUMPAD_ENTER:
- case KEY.COMMA:
- if(selected_dropdown_item) {
- add_token($(selected_dropdown_item).data("tokeninput"));
- input_box.focus();
return false;
- }
- break;
- case KEY.ESCAPE:
- hide_dropdown();
- return true;
+ case KEY.ESCAPE:
+ hide_dropdown();
+ return true;
- default:
- if(String.fromCharCode(event.which)) {
- // set a timeout just long enough to let this function finish.
- setTimeout(function(){do_search();}, 5);
+ default:
+ if (String.fromCharCode(event.which)) {
+ // set a timeout just long enough to let this function finish.
+ setTimeout(function(){ do_search(); }, 5);
}
break;
- }
- });
-
- // Keep a reference to the original input box
- var hidden_input = $(input)
- .hide()
- .val("")
- .focus(function () {
- input_box.focus();
- })
- .blur(function () {
- input_box.blur();
- });
-
- // Keep a reference to the selected token and dropdown item
- var selected_token = null;
- var selected_token_index = 0;
- var selected_dropdown_item = null;
-
- // The list to store the token items in
- var token_list = $("")
- .addClass(settings.classes.tokenList)
- .click(function (event) {
- var li = $(event.target).closest("li");
- if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
- toggle_select_token(li);
- } else {
- // Deselect selected token
- if(selected_token) {
- deselect_token($(selected_token), POSITION.END);
- }
-
- // Transfer focus
- if (!input_box.is(':focus'))
- input_box.focus();
- }
+ }
+ });
+
+ // Keep reference for placeholder
+ if (settings.placeholder) {
+ input_box.attr("placeholder", settings.placeholder);
+ }
+
+ // Keep a reference to the original input box
+ var hiddenInput = $(input)
+ .hide()
+ .val("")
+ .focus(function () {
+ focusWithTimeout(input_box);
})
- .mouseover(function (event) {
- var li = $(event.target).closest("li");
- if(li && selected_token !== this) {
- li.addClass(settings.classes.highlightedToken);
- }
+ .blur(function () {
+ input_box.blur();
+
+ //return the object to this can be referenced in the callback functions.
+ return hiddenInput;
})
- .mouseout(function (event) {
- var li = $(event.target).closest("li");
- if(li && selected_token !== this) {
- li.removeClass(settings.classes.highlightedToken);
+ ;
+
+ // Keep a reference to the selected token and dropdown item
+ var selected_token = null;
+ var selected_token_index = 0;
+ var selected_dropdown_item = null;
+
+ // The list to store the token items in
+ var token_list = $("")
+ .addClass($(input).data("settings").classes.tokenList)
+ .click(function (event) {
+ var li = $(event.target).closest("li");
+ if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
+ toggle_select_token(li);
+ } else {
+ // Deselect selected token
+ if(selected_token) {
+ deselect_token($(selected_token), POSITION.END);
+ }
+
+ // Focus input box
+ focusWithTimeout(input_box);
+ }
+ })
+ .mouseover(function (event) {
+ var li = $(event.target).closest("li");
+ if(li && selected_token !== this) {
+ li.addClass($(input).data("settings").classes.highlightedToken);
+ }
+ })
+ .mouseout(function (event) {
+ var li = $(event.target).closest("li");
+ if(li && selected_token !== this) {
+ li.removeClass($(input).data("settings").classes.highlightedToken);
+ }
+ })
+ .insertBefore(hiddenInput);
+
+ // The token holding the input box
+ var input_token = $("")
+ .addClass($(input).data("settings").classes.inputToken)
+ .appendTo(token_list)
+ .append(input_box);
+
+ // The list to store the dropdown items in
+ var dropdown = $("")
+ .addClass($(input).data("settings").classes.dropdown)
+ .appendTo("body")
+ .hide();
+
+ // Magic element to help us resize the text input
+ var input_resizer = $("")
+ .insertAfter(input_box)
+ .css({
+ position: "absolute",
+ top: -9999,
+ left: -9999,
+ width: "auto",
+ fontSize: input_box.css("fontSize"),
+ fontFamily: input_box.css("fontFamily"),
+ fontWeight: input_box.css("fontWeight"),
+ letterSpacing: input_box.css("letterSpacing"),
+ whiteSpace: "nowrap"
+ });
+
+ // Pre-populate list if items exist
+ hiddenInput.val("");
+ var li_data = $(input).data("settings").prePopulate || hiddenInput.data("pre");
+
+ if ($(input).data("settings").processPrePopulate && $.isFunction($(input).data("settings").onResult)) {
+ li_data = $(input).data("settings").onResult.call(hiddenInput, li_data);
+ }
+
+ if (li_data && li_data.length) {
+ $.each(li_data, function (index, value) {
+ insert_token(value);
+ checkTokenLimit();
+ input_box.attr("placeholder", null)
+ });
+ }
+
+ // Check if widget should initialize as disabled
+ if ($(input).data("settings").disabled) {
+ toggleDisabled(true);
+ }
+
+ // Initialization is done
+ if (typeof($(input).data("settings").onReady) === "function") {
+ $(input).data("settings").onReady.call();
+ }
+
+ //
+ // Public functions
+ //
+
+ this.clear = function() {
+ token_list.children("li").each(function() {
+ if ($(this).children("input").length === 0) {
+ delete_token($(this));
+ }
+ });
+ };
+
+ this.add = function(item) {
+ add_token(item);
+ };
+
+ this.remove = function(item) {
+ token_list.children("li").each(function() {
+ if ($(this).children("input").length === 0) {
+ var currToken = $(this).data("tokeninput");
+ var match = true;
+ for (var prop in item) {
+ if (item[prop] !== currToken[prop]) {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ delete_token($(this));
+ }
+ }
+ });
+ };
+
+ this.getTokens = function() {
+ return saved_tokens;
+ };
+
+ this.toggleDisabled = function(disable) {
+ toggleDisabled(disable);
+ };
+
+ // Resize input to maximum width so the placeholder can be seen
+ resize_input();
+
+ //
+ // Private functions
+ //
+
+ function escapeHTML(text) {
+ return $(input).data("settings").enableHTML ? text : _escapeHTML(text);
+ }
+
+ // Toggles the widget between enabled and disabled state, or according
+ // to the [disable] parameter.
+ function toggleDisabled(disable) {
+ if (typeof disable === 'boolean') {
+ $(input).data("settings").disabled = disable
+ } else {
+ $(input).data("settings").disabled = !$(input).data("settings").disabled;
+ }
+ input_box.attr('disabled', $(input).data("settings").disabled);
+ token_list.toggleClass($(input).data("settings").classes.disabled, $(input).data("settings").disabled);
+ // if there is any token selected we deselect it
+ if(selected_token) {
+ deselect_token($(selected_token), POSITION.END);
+ }
+ hiddenInput.attr('disabled', $(input).data("settings").disabled);
+ }
+
+ function checkTokenLimit() {
+ if($(input).data("settings").tokenLimit !== null && token_count >= $(input).data("settings").tokenLimit) {
+ input_box.hide();
+ hide_dropdown();
+ return;
+ }
+ }
+
+ function resize_input() {
+ if(input_val === (input_val = input_box.val())) {return;}
+
+ // Get width left on the current line
+ var width_left = token_list.width() - input_box.offset().left - token_list.offset().left;
+ // Enter new content into resizer and resize input accordingly
+ input_resizer.html(_escapeHTML(input_val) || _escapeHTML(settings.placeholder));
+ // Get maximum width, minimum the size of input and maximum the widget's width
+ input_box.width(Math.min(token_list.width(),
+ Math.max(width_left, input_resizer.width() + 30)));
+ }
+
+ function add_freetagging_tokens() {
+ var value = $.trim(input_box.val());
+ var tokens = value.split($(input).data("settings").tokenDelimiter);
+ $.each(tokens, function(i, token) {
+ if (!token) {
+ return;
}
- })
- .insertBefore(hidden_input);
-
- // The token holding the input box
- var input_token = $("")
- .addClass(settings.classes.inputToken)
- .appendTo(token_list)
- .append(input_box);
-
- // The list to store the dropdown items in
- var dropdown = $("")
- .addClass(settings.classes.dropdown)
- .hide();
- dropdown.appendTo("body");
- if (!settings.permanentDropdown)
- dropdown.appendTo("body");
- else
- $(input).after(dropdown.show());
-
- if (settings.permanentDropdown || settings.showAllResults) {
- do_search();
- if (!settings.permanentDropdown && settings.showAllResults)
- hide_dropdown();
- }
-
- // Hint for permanentDropdown
- if (settings.permanentDropdown || settings.showAllResults)
- show_dropdown_hint();
-
- // Magic element to help us resize the text input
- var input_resizer = $("
")
- .insertAfter(input_box)
- .css({
- position: "absolute",
- top: -9999,
- left: -9999,
- width: "auto",
- fontSize: input_box.css("fontSize"),
- fontFamily: input_box.css("fontFamily"),
- fontWeight: input_box.css("fontWeight"),
- letterSpacing: input_box.css("letterSpacing"),
- whiteSpace: "nowrap"
- });
-
- // Pre-populate list if items exist
- hidden_input.val("");
- var li_data = settings.prePopulate || hidden_input.data("pre");
- if(settings.processPrePopulate && $.isFunction(settings.onResult)) {
- li_data = settings.onResult.call(hidden_input, li_data);
- }
- if(li_data && li_data.length) {
- $.each(li_data, function (index, value) {
- insert_token(value);
- checkTokenLimit({init: true});
- });
- }
-
-
- //
- // Public functions
- //
-
- this.clear = function() {
- token_list.children("li").each(function() {
- if ($(this).children("input").length === 0) {
- delete_token($(this));
+
+ if ($.isFunction($(input).data("settings").onFreeTaggingAdd)) {
+ token = $(input).data("settings").onFreeTaggingAdd.call(hiddenInput, token);
}
- });
- }
-
- this.add = function(item) {
- add_token(item);
- }
-
- this.remove = function(item) {
- token_list.children("li").each(function() {
- if ($(this).children("input").length === 0) {
- var currToken = $(this).data("tokeninput");
- var match = true;
- for (var prop in item) {
- if (item[prop] !== currToken[prop]) {
- match = false;
- break;
+ var object = {};
+ object[$(input).data("settings").tokenValue] = object[$(input).data("settings").propertyToSearch] = token;
+ add_token(object);
+ });
+ }
+
+ // Inner function to a token to the list
+ function insert_token(item) {
+ var $this_token = $($(input).data("settings").tokenFormatter(item));
+ var readonly = item.readonly === true;
+
+ if(readonly) $this_token.addClass($(input).data("settings").classes.tokenReadOnly);
+
+ $this_token.addClass($(input).data("settings").classes.token).insertBefore(input_token);
+
+ // The 'delete token' button
+ if(!readonly) {
+ $("
" + $(input).data("settings").deleteText + "")
+ .addClass($(input).data("settings").classes.tokenDelete)
+ .appendTo($this_token)
+ .click(function () {
+ if (!$(input).data("settings").disabled) {
+ delete_token($(this).parent());
+ hiddenInput.change();
+ return false;
}
- }
- if (match) {
- delete_token($(this));
- }
- }
- });
- }
-
- //
- // Private functions
- //
-
- function checkTokenLimit(options) {
- if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
- input_box.hide();
- hide_dropdown();
- return;
- } else if (options && !options.init) {
- input_box.focus();
- }
- }
-
- function resize_input() {
- if(input_val === (input_val = input_box.val())) {return;}
-
- // Enter new content into resizer and resize input accordingly
- var escaped = input_val.replace(/&/g, '&').replace(/\s/g,' ').replace(//g, '>');
- input_resizer.html(escaped);
- input_box.width(input_resizer.width() + 30);
-
- if((settings.permanentDropdown || settings.showAllResults) && input_box.hasClass(settings.classes.blurText))
- input_val = '';
- }
-
- function is_printable_character(keycode) {
- return ((keycode >= 48 && keycode <= 90) || // 0-1a-z
- (keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * .
- (keycode >= 186 && keycode <= 192) || // ; = , - . / ^
- (keycode >= 219 && keycode <= 222)); // ( \ ) '
- }
-
- // Inner function to a token to the list
- function insert_token(item) {
- var this_token = $("
"+ item.name +"
")
- .addClass(settings.classes.token)
- .insertBefore(input_token);
-
- // The 'delete token' button
- $("
" + settings.deleteText + "")
- .addClass(settings.classes.tokenDelete)
- .appendTo(this_token)
- .click(function () {
- delete_token($(this).parent());
- return false;
- });
-
- // Store data on the token
- var token_data = {"id": item.id, "name": item.name};
- $.data(this_token.get(0), "tokeninput", item);
-
- // Save this token for duplicate checking
- saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index));
- selected_token_index++;
-
- // Update the hidden input
- var token_ids = $.map(saved_tokens, function (el) {
- return el.id;
- });
- hidden_input.val(token_ids.join(settings.tokenDelimiter));
-
- token_count += 1;
-
- return this_token;
- }
-
- // Add a token to the token list based on user input
- function add_token (item) {
- if (settings.dontAdd)
- return;
-
- var callback = settings.onAdd;
-
- // See if the token already exists and select it if we don't want duplicates
- if(token_count > 0 && settings.preventDuplicates) {
- var found_existing_token = null;
- token_list.children().each(function () {
- var existing_token = $(this);
- var existing_data = $.data(existing_token.get(0), "tokeninput");
- if(existing_data && existing_data.id === item.id) {
- found_existing_token = existing_token;
- return false;
- }
- });
+ });
+ }
- if(found_existing_token) {
- select_token(found_existing_token);
- input_token.insertAfter(found_existing_token);
- return;
- }
- }
+ // Store data on the token
+ var token_data = item;
+ $.data($this_token.get(0), "tokeninput", item);
- // Insert the new tokens
- insert_token(item);
- checkTokenLimit();
+ // Save this token for duplicate checking
+ saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index));
+ selected_token_index++;
- // Clear input box
- input_box.val("");
+ // Update the hidden input
+ update_hiddenInput(saved_tokens, hiddenInput);
- // Don't show the help dropdown, they've got the idea
- hide_dropdown();
+ token_count += 1;
- // Execute the onAdd callback if defined
- if($.isFunction(callback)) {
- callback.call(hidden_input,item);
- }
- }
-
- // Select a token in the token list
- function select_token (token) {
- token.addClass(settings.classes.selectedToken);
- selected_token = token.get(0);
-
- // Hide input box
- input_box.val("");
-
- // Hide dropdown if it is visible (eg if we clicked to select token)
- hide_dropdown();
- }
-
- // Deselect a token in the token list
- function deselect_token (token, position) {
- token.removeClass(settings.classes.selectedToken);
- selected_token = null;
-
- if(position === POSITION.BEFORE) {
- input_token.insertBefore(token);
- selected_token_index--;
- } else if(position === POSITION.AFTER) {
- input_token.insertAfter(token);
- selected_token_index++;
- } else {
- input_token.appendTo(token_list);
- selected_token_index = token_count;
- }
+ // Check the token limit
+ if($(input).data("settings").tokenLimit !== null && token_count >= $(input).data("settings").tokenLimit) {
+ input_box.hide();
+ hide_dropdown();
+ }
- // Show the input box and give it focus again
- input_box.focus();
- }
+ return $this_token;
+ }
- // Toggle selection of a token in the token list
- function toggle_select_token(token) {
- var previous_selected_token = selected_token;
+ // Add a token to the token list based on user input
+ function add_token (item) {
+ var callback = $(input).data("settings").onAdd;
+
+ // See if the token already exists and select it if we don't want duplicates
+ if(token_count > 0 && $(input).data("settings").preventDuplicates) {
+ var found_existing_token = null;
+ token_list.children().each(function () {
+ var existing_token = $(this);
+ var existing_data = $.data(existing_token.get(0), "tokeninput");
+ if(existing_data && existing_data[settings.tokenValue] === item[settings.tokenValue]) {
+ found_existing_token = existing_token;
+ return false;
+ }
+ });
+
+ if(found_existing_token) {
+ select_token(found_existing_token);
+ input_token.insertAfter(found_existing_token);
+ focusWithTimeout(input_box);
+ return;
+ }
+ }
- if(selected_token) {
- deselect_token($(selected_token), POSITION.END);
- }
+ // Squeeze input_box so we force no unnecessary line break
+ input_box.width(1);
- if(previous_selected_token === token.get(0)) {
- deselect_token(token, POSITION.END);
- } else {
- select_token(token);
- }
- }
+ // Insert the new tokens
+ if($(input).data("settings").tokenLimit == null || token_count < $(input).data("settings").tokenLimit) {
+ insert_token(item);
+ // Remove the placeholder so it's not seen after you've added a token
+ input_box.attr("placeholder", null);
+ checkTokenLimit();
+ }
+
+ // Clear input box
+ input_box.val("");
- // Delete a token from the token list
- function delete_token (token) {
- // Remove the id from the saved list
- var token_data = $.data(token.get(0), "tokeninput");
- var callback = settings.onDelete;
+ // Don't show the help dropdown, they've got the idea
+ hide_dropdown();
- var index = token.prevAll().length;
- if(index > selected_token_index) index--;
+ // Execute the onAdd callback if defined
+ if($.isFunction(callback)) {
+ callback.call(hiddenInput,item);
+ }
+ }
- // Delete the token
- token.remove();
- selected_token = null;
+ // Select a token in the token list
+ function select_token (token) {
+ if (!$(input).data("settings").disabled) {
+ token.addClass($(input).data("settings").classes.selectedToken);
+ selected_token = token.get(0);
- // Remove this token from the saved list
- saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1));
- if(index < selected_token_index) selected_token_index--;
+ // Hide input box
+ input_box.val("");
- // Update the hidden input
- var token_ids = $.map(saved_tokens, function (el) {
- return el.id;
- });
- hidden_input.val(token_ids.join(settings.tokenDelimiter));
+ // Hide dropdown if it is visible (eg if we clicked to select token)
+ hide_dropdown();
+ }
+ }
- token_count -= 1;
+ // Deselect a token in the token list
+ function deselect_token (token, position) {
+ token.removeClass($(input).data("settings").classes.selectedToken);
+ selected_token = null;
+
+ if(position === POSITION.BEFORE) {
+ input_token.insertBefore(token);
+ selected_token_index--;
+ } else if(position === POSITION.AFTER) {
+ input_token.insertAfter(token);
+ selected_token_index++;
+ } else {
+ input_token.appendTo(token_list);
+ selected_token_index = token_count;
+ }
- if(settings.tokenLimit !== null)
- input_box.show().val("");
+ // Show the input box and give it focus again
+ focusWithTimeout(input_box);
+ }
- // Execute the onDelete callback if defined
- if($.isFunction(callback)) {
- callback.call(hidden_input,token_data);
- }
- }
+ // Toggle selection of a token in the token list
+ function toggle_select_token(token) {
+ var previous_selected_token = selected_token;
- // Hide and clear the results dropdown
- function hide_dropdown () {
- if (!settings.permanentDropdown) {
- dropdown.hide();
- if (!settings.showAllResults)
- dropdown.empty();
- selected_dropdown_item = null;
- }
- if (settings.showAllResults)
- show_dropdown_hint();
- }
-
- function show_dropdown() {
- if (!settings.permanentDropdown)
- dropdown.css({
- position: "absolute",
- top: $(token_list).offset().top + $(token_list).outerHeight(),
- left: $(token_list).offset().left,
- 'z-index': settings.zindex
- }).show();
- else
- dropdown.css({
- position: "relative",
- }).show();
- }
-
- function show_dropdown_searching () {
- if(settings.searchingText) {
- dropdown.html("
"+settings.searchingText+"
");
- show_dropdown();
- }
- }
-
- function show_dropdown_hint () {
- if(settings.hintText) {
- if(settings.permanentDropdown || settings.showAllResults) {
- if (input_val == '') {
- input_box.val(settings.hintText);
- input_box.addClass(settings.classes.blurText);
- }
+ if(selected_token) {
+ deselect_token($(selected_token), POSITION.END);
+ }
+
+ if(previous_selected_token === token.get(0)) {
+ deselect_token(token, POSITION.END);
} else {
- dropdown.html("
"+settings.hintText+"
");
- show_dropdown();
+ select_token(token);
}
- }
- }
-
- function hide_dropdown_hint () {
- if (input_box.hasClass(settings.classes.blurText)) {
- input_box.val('');
- input_box.removeClass(settings.classes.blurText);
- }
- }
-
- // Highlight the query part of the search term
- function highlight_term(value, term) {
- return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "
$1");
- }
-
- // Populate the results dropdown with some results
- function populate_dropdown (query, results) {
- if(results && results.length) {
- dropdown.empty();
- var dropdown_ul = $("
")
- .appendTo(dropdown)
- .mouseover(function (event) {
- select_dropdown_item($(event.target).closest("li"));
- })
- .mousedown(function (event) {
- add_token($(event.target).closest("li").data("tokeninput"));
- input_box.blur();
- return false;
- })
- .hide();
+ }
- $.each(results, function(index, value) {
- var this_li = $("- " + highlight_term(value.name, query) + "
")
- .appendTo(dropdown_ul);
+ // Delete a token from the token list
+ function delete_token (token) {
+ // Remove the id from the saved list
+ var token_data = $.data(token.get(0), "tokeninput");
+ var callback = $(input).data("settings").onDelete;
- if(index % 2) {
- this_li.addClass(settings.classes.dropdownItem);
- } else {
- this_li.addClass(settings.classes.dropdownItem2);
- }
+ var index = token.prevAll().length;
+ if(index > selected_token_index) index--;
- if(index === 0) {
- select_dropdown_item(this_li);
- }
+ // Delete the token
+ token.remove();
+ selected_token = null;
- $.data(this_li.get(0), "tokeninput", value);
- });
+ // Show the input box and give it focus again
+ focusWithTimeout(input_box);
- show_dropdown();
+ // Remove this token from the saved list
+ saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1));
+ if (saved_tokens.length == 0) {
+ input_box.attr("placeholder", settings.placeholder)
+ }
+ if(index < selected_token_index) selected_token_index--;
- if(settings.animateDropdown) {
- dropdown_ul.slideDown("fast");
- } else {
- dropdown_ul.show();
- }
- } else {
- if(settings.noResultsText) {
- dropdown.html(""+settings.noResultsText+"
");
- show_dropdown();
- }
- }
- }
+ // Update the hidden input
+ update_hiddenInput(saved_tokens, hiddenInput);
- // Highlight an item in the results dropdown
- function select_dropdown_item (item) {
- if(item) {
- if(selected_dropdown_item) {
- deselect_dropdown_item($(selected_dropdown_item));
- }
+ token_count -= 1;
- item.addClass(settings.classes.selectedDropdownItem);
- selected_dropdown_item = item.get(0);
-
- isBefore = item[0].offsetTop <= (dropdown[0].scrollTop + dropdown[0].scrollWidth);
- isAfter = item[0].offsetTop >= dropdown[0].scrollTop;
- visible = isBefore && isAfter;
- if (!visible) {
- if (isBefore)
- dropdown[0].scrollTop = item[0].offsetTop;
- else //isAfter
- dropdown[0].scrollTop = item[0].offsetTop - dropdown[0].scrollWidth;
- }
- }
- }
-
- // Remove highlighting from an item in the results dropdown
- function deselect_dropdown_item (item) {
- item.removeClass(settings.classes.selectedDropdownItem);
- selected_dropdown_item = null;
- }
-
- // Do a search and show the "searching" dropdown if the input is longer
- // than settings.minChars
- function do_search() {
- var query = input_box.val().toLowerCase();
-
- if(query && query.length) {
- if(selected_token) {
- deselect_token($(selected_token), POSITION.AFTER);
- }
+ if($(input).data("settings").tokenLimit !== null) {
+ input_box
+ .show()
+ .val("");
+ focusWithTimeout(input_box);
+ }
+
+ // Execute the onDelete callback if defined
+ if($.isFunction(callback)) {
+ callback.call(hiddenInput,token_data);
+ }
+ }
- if(query.length >= settings.minChars) {
- show_dropdown_searching();
- clearTimeout(timeout);
+ // Update the hidden input box value
+ function update_hiddenInput(saved_tokens, hiddenInput) {
+ var token_values = $.map(saved_tokens, function (el) {
+ if(typeof $(input).data("settings").tokenValue == 'function')
+ return $(input).data("settings").tokenValue.call(this, el);
+
+ return el[$(input).data("settings").tokenValue];
+ });
+ hiddenInput.val(token_values.join($(input).data("settings").tokenDelimiter));
+
+ }
+
+ // Hide and clear the results dropdown
+ function hide_dropdown () {
+ dropdown.hide().empty();
+ selected_dropdown_item = null;
+ }
+
+ function show_dropdown() {
+ dropdown
+ .css({
+ position: "absolute",
+ top: token_list.offset().top + token_list.outerHeight(true),
+ left: token_list.offset().left,
+ width: token_list.width(),
+ 'z-index': $(input).data("settings").zindex
+ })
+ .show();
+ }
+
+ function show_dropdown_searching () {
+ if($(input).data("settings").searchingText) {
+ dropdown.html("" + escapeHTML($(input).data("settings").searchingText) + "
");
+ show_dropdown();
+ }
+ }
+
+ function show_dropdown_hint () {
+ if($(input).data("settings").hintText) {
+ dropdown.html("" + escapeHTML($(input).data("settings").hintText) + "
");
+ show_dropdown();
+ }
+ }
+
+ var regexp_special_chars = new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g');
+ function regexp_escape(term) {
+ return term.replace(regexp_special_chars, '\\$&');
+ }
- timeout = setTimeout(function(){
- run_search(query);
- }, settings.searchDelay);
- } else {
- hide_dropdown();
+ // Highlight the query part of the search term
+ function highlight_term(value, term) {
+ return value.replace(
+ new RegExp(
+ "(?![^&;]+;)(?!<[^<>]*)(" + regexp_escape(term) + ")(?![^<>]*>)(?![^&;]+;)",
+ "gi"
+ ), function(match, p1) {
+ return "" + escapeHTML(p1) + "";
}
- } else if (settings.permanentDropdown || settings.showAllResults)
- run_search('');
- }
-
- // Do the actual search
- function run_search(query) {
- var cached_results = cache.get(query);
- if(cached_results) {
- populate_dropdown(query, cached_results);
- } else {
- // Are we doing an ajax search or local data search?
- if(settings.url) {
- // Extract exisiting get params
- var ajax_params = {};
- ajax_params.data = {};
- if(settings.url.indexOf("?") > -1) {
- var parts = settings.url.split("?");
- ajax_params.url = parts[0];
-
- var param_array = parts[1].split("&");
- $.each(param_array, function (index, value) {
- var kv = value.split("=");
- ajax_params.data[kv[0]] = kv[1];
- });
- } else {
- ajax_params.url = settings.url;
- }
-
- // Prepare the request
- ajax_params.data[settings.queryParam] = query;
- ajax_params.type = settings.method;
- ajax_params.dataType = settings.contentType;
- if(settings.crossDomain) {
- ajax_params.dataType = "jsonp";
- }
-
- // Attach the success callback
- ajax_params.success = function(results) {
- if($.isFunction(settings.onResult)) {
- results = settings.onResult.call(hidden_input, results);
+ );
+ }
+
+ function find_value_and_highlight_term(template, value, term) {
+ return template.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + regexp_escape(value) + ")(?![^<>]*>)(?![^&;]+;)", "g"), highlight_term(value, term));
+ }
+
+ // exclude existing tokens from dropdown, so the list is clearer
+ function excludeCurrent(results) {
+ if ($(input).data("settings").excludeCurrent) {
+ var currentTokens = $(input).data("tokenInputObject").getTokens(),
+ trimmedList = [];
+ if (currentTokens.length) {
+ $.each(results, function(index, value) {
+ var notFound = true;
+ $.each(currentTokens, function(cIndex, cValue) {
+ if (value[$(input).data("settings").propertyToSearch] == cValue[$(input).data("settings").propertyToSearch]) {
+ notFound = false;
+ return false;
+ }
+ });
+
+ if (notFound) {
+ trimmedList.push(value);
+ }
+ });
+ results = trimmedList;
+ }
+ }
+
+ return results;
+ }
+
+ // Populate the results dropdown with some results
+ function populateDropdown (query, results) {
+ // exclude current tokens if configured
+ results = excludeCurrent(results);
+
+ if(results && results.length) {
+ dropdown.empty();
+ var dropdown_ul = $("")
+ .appendTo(dropdown)
+ .mouseover(function (event) {
+ select_dropdown_item($(event.target).closest("li"));
+ })
+ .mousedown(function (event) {
+ add_token($(event.target).closest("li").data("tokeninput"));
+ hiddenInput.change();
+ return false;
+ })
+ .hide();
+
+ if ($(input).data("settings").resultsLimit && results.length > $(input).data("settings").resultsLimit) {
+ results = results.slice(0, $(input).data("settings").resultsLimit);
+ }
+
+ $.each(results, function(index, value) {
+ var this_li = $(input).data("settings").resultsFormatter(value);
+
+ this_li = find_value_and_highlight_term(this_li ,value[$(input).data("settings").propertyToSearch], query);
+ this_li = $(this_li).appendTo(dropdown_ul);
+
+ if(index % 2) {
+ this_li.addClass($(input).data("settings").classes.dropdownItem);
+ } else {
+ this_li.addClass($(input).data("settings").classes.dropdownItem2);
}
- cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
- // only populate the dropdown if the results are associated with the active search query
- if(input_box.val().toLowerCase() === query) {
- populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
+ if(index === 0 && $(input).data("settings").autoSelectFirstResult) {
+ select_dropdown_item(this_li);
}
- };
-
- // Make the request
- $.ajax(ajax_params);
- } else if(settings.local_data) {
- // Do the search through local data
- var results = $.grep(settings.local_data, function (row) {
- return row.name.toLowerCase().indexOf(query.toLowerCase()) > -1;
- });
- if($.isFunction(settings.onResult)) {
- results = settings.onResult.call(hidden_input, results);
- }
- cache.add(query, results);
- populate_dropdown(query, results);
- }
- }
- }
-};
+ $.data(this_li.get(0), "tokeninput", value);
+ });
+
+ show_dropdown();
+
+ if($(input).data("settings").animateDropdown) {
+ dropdown_ul.slideDown("fast");
+ } else {
+ dropdown_ul.show();
+ }
+ } else {
+ if($(input).data("settings").noResultsText) {
+ dropdown.html("" + escapeHTML($(input).data("settings").noResultsText) + "
");
+ show_dropdown();
+ }
+ }
+ }
+
+ // Highlight an item in the results dropdown
+ function select_dropdown_item (item) {
+ if(item) {
+ if(selected_dropdown_item) {
+ deselect_dropdown_item($(selected_dropdown_item));
+ }
-// Really basic cache for the results
-$.TokenList.Cache = function (options) {
- var settings = $.extend({
- max_size: 500
- }, options);
+ item.addClass($(input).data("settings").classes.selectedDropdownItem);
+ selected_dropdown_item = item.get(0);
+ }
+ }
+
+ // Remove highlighting from an item in the results dropdown
+ function deselect_dropdown_item (item) {
+ item.removeClass($(input).data("settings").classes.selectedDropdownItem);
+ selected_dropdown_item = null;
+ }
- var data = {};
- var size = 0;
+ // Do a search and show the "searching" dropdown if the input is longer
+ // than $(input).data("settings").minChars
+ function do_search() {
+ var query = input_box.val();
- var flush = function () {
- data = {};
- size = 0;
+ if(query && query.length) {
+ if(selected_token) {
+ deselect_token($(selected_token), POSITION.AFTER);
+ }
+
+ if(query.length >= $(input).data("settings").minChars) {
+ show_dropdown_searching();
+ clearTimeout(timeout);
+
+ timeout = setTimeout(function(){
+ run_search(query);
+ }, $(input).data("settings").searchDelay);
+ } else {
+ hide_dropdown();
+ }
+ }
+ }
+
+ // Do the actual search
+ function run_search(query) {
+ var cache_key = query + computeURL();
+ var cached_results = cache.get(cache_key);
+ if (cached_results) {
+ if ($.isFunction($(input).data("settings").onCachedResult)) {
+ cached_results = $(input).data("settings").onCachedResult.call(hiddenInput, cached_results);
+ }
+ populateDropdown(query, cached_results);
+ } else {
+ // Are we doing an ajax search or local data search?
+ if($(input).data("settings").url) {
+ var url = computeURL();
+ // Extract existing get params
+ var ajax_params = {};
+ ajax_params.data = {};
+ if(url.indexOf("?") > -1) {
+ var parts = url.split("?");
+ ajax_params.url = parts[0];
+
+ var param_array = parts[1].split("&");
+ $.each(param_array, function (index, value) {
+ var kv = value.split("=");
+ ajax_params.data[kv[0]] = kv[1];
+ });
+ } else {
+ ajax_params.url = url;
+ }
+
+ // Prepare the request
+ ajax_params.data[$(input).data("settings").queryParam] = query;
+ ajax_params.type = $(input).data("settings").method;
+ ajax_params.dataType = $(input).data("settings").contentType;
+ if ($(input).data("settings").crossDomain) {
+ ajax_params.dataType = "jsonp";
+ }
+
+ // exclude current tokens?
+ // send exclude list to the server, so it can also exclude existing tokens
+ if ($(input).data("settings").excludeCurrent) {
+ var currentTokens = $(input).data("tokenInputObject").getTokens();
+ var tokenList = $.map(currentTokens, function (el) {
+ if(typeof $(input).data("settings").tokenValue == 'function')
+ return $(input).data("settings").tokenValue.call(this, el);
+
+ return el[$(input).data("settings").tokenValue];
+ });
+
+ ajax_params.data[$(input).data("settings").excludeCurrentParameter] = tokenList.join($(input).data("settings").tokenDelimiter);
+ }
+
+ // Attach the success callback
+ ajax_params.success = function(results) {
+ cache.add(cache_key, $(input).data("settings").jsonContainer ? results[$(input).data("settings").jsonContainer] : results);
+ if($.isFunction($(input).data("settings").onResult)) {
+ results = $(input).data("settings").onResult.call(hiddenInput, results);
+ }
+
+ // only populate the dropdown if the results are associated with the active search query
+ if(input_box.val() === query) {
+ populateDropdown(query, $(input).data("settings").jsonContainer ? results[$(input).data("settings").jsonContainer] : results);
+ }
+ };
+
+ // Provide a beforeSend callback
+ if (settings.onSend) {
+ settings.onSend(ajax_params);
+ }
+
+ // Make the request
+ $.ajax(ajax_params);
+ } else if($(input).data("settings").local_data) {
+ // Do the search through local data
+ var results = $.grep($(input).data("settings").local_data, function (row) {
+ return row[$(input).data("settings").propertyToSearch].toLowerCase().indexOf(query.toLowerCase()) > -1;
+ });
+
+ cache.add(cache_key, results);
+ if($.isFunction($(input).data("settings").onResult)) {
+ results = $(input).data("settings").onResult.call(hiddenInput, results);
+ }
+ populateDropdown(query, results);
+ }
+ }
+ }
+
+ // compute the dynamic URL
+ function computeURL() {
+ var settings = $(input).data("settings");
+ return typeof settings.url == 'function' ? settings.url.call(settings) : settings.url;
+ }
+
+ // Bring browser focus to the specified object.
+ // Use of setTimeout is to get around an IE bug.
+ // (See, e.g., http://stackoverflow.com/questions/2600186/focus-doesnt-work-in-ie)
+ //
+ // obj: a jQuery object to focus()
+ function focusWithTimeout(object) {
+ setTimeout(
+ function() {
+ object.focus();
+ },
+ 50
+ );
+ }
+ };
+
+ // Really basic cache for the results
+ $.TokenList.Cache = function (options) {
+ var settings, data = {}, size = 0, flush;
+
+ settings = $.extend({ max_size: 500 }, options);
+
+ flush = function () {
+ data = {};
+ size = 0;
};
this.add = function (query, results) {
- if(size > settings.max_size) {
- flush();
- }
+ if (size > settings.max_size) {
+ flush();
+ }
- if(!data[query]) {
- size += 1;
- }
+ if (!data[query]) {
+ size += 1;
+ }
- data[query] = results;
+ data[query] = results;
};
this.get = function (query) {
- return data[query];
+ return data[query];
};
-};
+ };
+
}(jQuery));
diff --git a/public/stylesheets/profile-activity.scss b/public/stylesheets/profile-activity.scss
index 6163fe1..82e6690 100644
--- a/public/stylesheets/profile-activity.scss
+++ b/public/stylesheets/profile-activity.scss
@@ -3,7 +3,8 @@
padding-left: 0;
clear: both;
}
-#profile-activity li, #profile-network li, #profile-wall li {
+
+.profile-activities li {
display: block;
padding: 3px 0px;
margin-bottom: 3px;
@@ -367,6 +368,9 @@ li.profile-activity-item.upload_image .activity-gallery-images-count-1 img {
.profile-wall-message {
margin: 0;
}
+.limited-text-area {
+ margin-bottom: 15px;
+}
.limited-text-area p {
margin: 0;
font-size: 11px;
@@ -378,7 +382,8 @@ li.profile-activity-item.upload_image .activity-gallery-images-count-1 img {
margin-bottom: 10px;
}
#leave_scrap_content_limit, #leave_scrap_content_left {
- float: left;
+ float: right;
+ margin-right: 2px;
}
#leave_scrap {
float: left;
@@ -392,6 +397,9 @@ li.profile-activity-item.upload_image .activity-gallery-images-count-1 img {
#leave_scrap .loading textarea {
background: url('/images/loading-small.gif') 50% 50% no-repeat;
}
+#leave_scrap .submit {
+ margin-top: 5px;
+}
.profile-send-reply {
color: #aaa;
}
@@ -598,7 +606,6 @@ li.profile-activity-item.upload_image .activity-gallery-images-count-1 img {
}
#profile-wall #leave_scrap textarea {
- width: 442px;
height: 100px
}
.profile-wall-scrap-replies {
diff --git a/public/stylesheets/vendor/token-input-facebook.css b/public/stylesheets/vendor/token-input-facebook.css
index 8edd461..5ddde0c 100644
--- a/public/stylesheets/vendor/token-input-facebook.css
+++ b/public/stylesheets/vendor/token-input-facebook.css
@@ -7,7 +7,7 @@ ul.token-input-list-facebook {
border: 1px solid #8496ba;
cursor: text;
font-size: 12px;
- font-family: Verdana;
+ font-family: Verdana, sans-serif;
min-height: 1px;
z-index: 999;
margin: 0;
@@ -80,7 +80,7 @@ div.token-input-dropdown-facebook {
border-bottom: 1px solid #ccc;
cursor: default;
font-size: 11px;
- font-family: Verdana;
+ font-family: Verdana, sans-serif;
z-index: 1;
}
@@ -119,8 +119,4 @@ div.token-input-dropdown-facebook ul li em {
div.token-input-dropdown-facebook ul li.token-input-selected-dropdown-item-facebook {
background-color: #3b5998;
color: #fff;
-}
-.token-input-blur-text-facebook {
- font-style: italic;
- color: #AAA;
-}
+}
\ No newline at end of file
diff --git a/test/functional/profile_controller_test.rb b/test/functional/profile_controller_test.rb
index 8f3e389..754c2ef 100644
--- a/test/functional/profile_controller_test.rb
+++ b/test/functional/profile_controller_test.rb
@@ -7,6 +7,7 @@ class ProfileControllerTest < ActionController::TestCase
self.default_params = {profile: 'testuser'}
def setup
+ @controller = ProfileController.new
@profile = create_user('testuser').person
end
attr_reader :profile
@@ -759,7 +760,7 @@ class ProfileControllerTest < ActionController::TestCase
login_as(profile.identifier)
get :index, :profile => p1.identifier
- assert_nil assigns(:activities)
+ assert assigns(:activities).blank?
end
should 'see the activities_items paginated' do
@@ -954,14 +955,14 @@ class ProfileControllerTest < ActionController::TestCase
should 'not have activities defined if not logged in' do
p1= fast_create(Person)
get :index, :profile => p1.identifier
- assert_nil assigns(:actvities)
+ assert assigns(:actvities).blank?
end
should 'not have activities defined if logged in but is not following profile' do
login_as(profile.identifier)
p1= fast_create(Person)
get :index, :profile => p1.identifier
- assert_nil assigns(:activities)
+ assert assigns(:activities).blank?
end
should 'have activities defined if logged in and is following profile' do
@@ -2045,4 +2046,216 @@ class ProfileControllerTest < ActionController::TestCase
assert_redirected_to "/some/url"
end
+ should "search followed people or circles" do
+ login_as(@profile.identifier)
+ c1 = Circle.create!(:name => 'Family', :person => @profile, :profile_type => Person)
+ c2 = Circle.create!(:name => 'Work', :person => @profile, :profile_type => Person)
+ p1 = create_user('emily').person
+ p2 = create_user('wollie').person
+ p3 = create_user('mary').person
+ ProfileFollower.create!(:profile => p1, :circle => c2)
+ ProfileFollower.create!(:profile => p2, :circle => c1)
+ ProfileFollower.create!(:profile => p3, :circle => c1)
+
+ get :search_followed, :q => 'mily'
+ assert_equal 'Family (Circle)', json_response[0]['name']
+ assert_equal 'Circle', json_response[0]['class']
+ assert_equal "Circle_#{c1.id}", json_response[0]['id']
+ assert_equal 'emily (Person)', json_response[1]['name']
+ assert_equal 'Person', json_response[1]['class']
+ assert_equal "Person_#{p1.id}", json_response[1]['id']
+
+ get :search_followed, :q => 'wo'
+ assert_equal 'Work (Circle)', json_response[0]['name']
+ assert_equal 'Circle', json_response[0]['class']
+ assert_equal "Circle_#{c2.id}", json_response[0]['id']
+ assert_equal 'wollie (Person)', json_response[1]['name']
+ assert_equal 'Person', json_response[1]['class']
+ assert_equal "Person_#{p2.id}", json_response[1]['id']
+
+ get :search_followed, :q => 'mar'
+ assert_equal 'mary (Person)', json_response[0]['name']
+ assert_equal 'Person', json_response[0]['class']
+ assert_equal "Person_#{p3.id}", json_response[0]['id']
+ end
+
+ should 'treat followed entries' do
+ login_as(@profile.identifier)
+ c1 = Circle.create!(:name => 'Family', :person => @profile, :profile_type => Person)
+ p1 = create_user('emily').person
+ p2 = create_user('wollie').person
+ p3 = create_user('mary').person
+ ProfileFollower.create!(:profile => p1, :circle => c1)
+ ProfileFollower.create!(:profile => p3, :circle => c1)
+
+ entries = "Circle_#{c1.id},Person_#{p1.id},Person_#{p2.id}"
+ marked_people = @controller.send(:treat_followed_entries, entries)
+
+ assert_equivalent [p1,p2,p3], marked_people
+ end
+
+ should 'return empty followed entries if the user is not on his wall' do
+ login_as(@profile.identifier)
+ c1 = Circle.create!(:name => 'Family', :person => @profile, :profile_type => Person)
+ p1 = create_user('emily').person
+ p2 = create_user('wollie').person
+ p3 = create_user('mary').person
+ ProfileFollower.create!(:profile => p1, :circle => c1)
+ ProfileFollower.create!(:profile => p3, :circle => c1)
+
+ entries = "Circle_#{c1.id},Person_#{p1.id},Person_#{p2.id}"
+ @controller.stubs(:profile).returns(@profile)
+ @controller.stubs(:user).returns(p1)
+ marked_people = @controller.send(:treat_followed_entries, entries)
+
+ assert_empty marked_people
+ end
+
+ should 'leave private scrap' do
+ login_as(@profile.identifier)
+ c1 = Circle.create!(:name => 'Family', :person => @profile, :profile_type => Person)
+ p1 = create_user('emily').person
+ p2 = create_user('wollie').person
+ ProfileFollower.create!(:profile => p1, :circle => c1)
+ ProfileFollower.create!(:profile => p2, :circle => c1)
+
+ content = 'Remember my birthday!'
+
+ post :leave_scrap, :profile => @profile.identifier, :scrap => {:content => content}, :filter_followed => "Person_#{p1.id},Person_#{p2.id}"
+
+ scrap = Scrap.last
+ assert_equal content, scrap.content
+ assert_equivalent [p1,p2], scrap.marked_people
+ end
+
+ should 'list private scraps on wall for marked people' do
+ c1 = Circle.create!(:name => 'Family', :person => @profile, :profile_type => Person)
+ p1 = create_user('emily').person
+ ProfileFollower.create!(:profile => p1, :circle => c1)
+ p1.add_friend(@profile)
+ scrap = Scrap.create!(:content => 'Secret message.', :sender_id => @profile.id, :receiver_id => @profile.id, :marked_people => [p1])
+ scrap_activity = ProfileActivity.where(:activity => scrap).first
+ login_as(p1.identifier)
+
+ get :index, :profile => @profile.identifier
+
+ assert assigns(:activities).include?(scrap_activity)
+ end
+
+ should 'not list private scraps on wall for not marked people' do
+ c1 = Circle.create!(:name => 'Family', :person => @profile, :profile_type => Person)
+ p1 = create_user('emily').person
+ p2 = create_user('wollie').person
+ not_marked = create_user('jack').person
+ not_marked.add_friend(@profile)
+ ProfileFollower.create!(:profile => p1, :circle => c1)
+ ProfileFollower.create!(:profile => p2, :circle => c1)
+ ProfileFollower.create!(:profile => not_marked, :circle => c1)
+ scrap = Scrap.create!(:content => 'Secret message.', :sender_id => @profile.id, :receiver_id => @profile.id, :marked_people => [p1,p2])
+ scrap_activity = ProfileActivity.where(:activity => scrap).first
+ login_as(not_marked.identifier)
+
+ get :index, :profile => @profile.identifier
+
+ assert !assigns(:activities).include?(scrap_activity)
+ end
+
+ should 'list private scraps on wall for creator' do
+ c1 = Circle.create!(:name => 'Family', :person => @profile, :profile_type => Person)
+ p1 = create_user('emily').person
+ ProfileFollower.create!(:profile => p1, :circle => c1)
+ scrap = Scrap.create!(:content => 'Secret message.', :sender_id => @profile.id, :receiver_id => @profile.id, :marked_people => [p1])
+ scrap_activity = ProfileActivity.where(:activity => scrap).first
+ login_as(@profile.identifier)
+
+ get :index, :profile => @profile.identifier
+
+ assert assigns(:activities).include?(scrap_activity)
+ end
+
+ should 'list private scraps on wall for environment administrator' do
+ c1 = Circle.create!(:name => 'Family', :person => @profile, :profile_type => Person)
+ p1 = create_user('emily').person
+ admin = create_user('env-admin').person
+ env = @profile.environment
+ env.add_admin(admin)
+ admin.add_friend(@profile)
+ ProfileFollower.create!(:profile => p1, :circle => c1)
+ scrap = Scrap.create!(:content => 'Secret message.', :sender_id => @profile.id, :receiver_id => @profile.id, :marked_people => [p1])
+ scrap_activity = ProfileActivity.where(:activity => scrap).first
+ login_as(admin.identifier)
+
+ get :index, :profile => @profile.identifier
+
+ assert assigns(:activities).include?(scrap_activity)
+ end
+
+ should 'list private scraps on network for marked people' do
+ c1 = Circle.create!(:name => 'Family', :person => @profile, :profile_type => Person)
+ p1 = create_user('emily').person
+ p2 = create_user('wollie').person
+ p2.add_friend(p1)
+ p1.add_friend(p2)
+ ProfileFollower.create!(:profile => p1, :circle => c1)
+ ProfileFollower.create!(:profile => p2, :circle => c1)
+ scrap = Scrap.create!(:content => 'Secret message.', :sender_id => @profile.id, :receiver_id => @profile.id, :marked_people => [p1,p2])
+ process_delayed_job_queue
+ scrap_activity = p1.tracked_notifications.where(:target => scrap).first
+ login_as(p2.identifier)
+
+ get :index, :profile => p1.identifier
+
+ assert assigns(:network_activities).include?(scrap_activity)
+ end
+
+ should 'not list private scraps on network for not marked people' do
+ c1 = Circle.create!(:name => 'Family', :person => @profile, :profile_type => Person)
+ p1 = create_user('emily').person
+ not_marked = create_user('jack').person
+ not_marked.add_friend(p1)
+ ProfileFollower.create!(:profile => p1, :circle => c1)
+ ProfileFollower.create!(:profile => not_marked, :circle => c1)
+ scrap = Scrap.create!(:content => 'Secret message.', :sender_id => @profile.id, :receiver_id => @profile.id, :marked_people => [p1])
+ process_delayed_job_queue
+ scrap_activity = p1.tracked_notifications.where(:target => scrap).first
+ login_as(not_marked.identifier)
+
+ get :index, :profile => p1.identifier
+
+ assert !assigns(:network_activities).include?(scrap_activity)
+ end
+
+ should 'list private scraps on network for creator' do
+ c1 = Circle.create!(:name => 'Family', :person => @profile, :profile_type => Person)
+ p1 = create_user('emily').person
+ p1.add_friend(@profile)
+ ProfileFollower.create!(:profile => p1, :circle => c1)
+ scrap = Scrap.create!(:content => 'Secret message.', :sender_id => @profile.id, :receiver_id => @profile.id, :marked_people => [p1])
+ process_delayed_job_queue
+ scrap_activity = p1.tracked_notifications.where(:target => scrap).first
+ login_as(@profile.identifier)
+
+ get :index, :profile => p1.identifier
+
+ assert assigns(:network_activities).include?(scrap_activity)
+ end
+
+ should 'list private scraps on network for environment admin' do
+ c1 = Circle.create!(:name => 'Family', :person => @profile, :profile_type => Person)
+ p1 = create_user('emily').person
+ admin = create_user('env-admin').person
+ env = @profile.environment
+ env.add_admin(admin)
+ admin.add_friend(p1)
+ ProfileFollower.create!(:profile => p1, :circle => c1)
+ scrap = Scrap.create!(:content => 'Secret message.', :sender_id => @profile.id, :receiver_id => @profile.id, :marked_people => [p1])
+ process_delayed_job_queue
+ scrap_activity = p1.tracked_notifications.where(:target => scrap).first
+ login_as(admin.identifier)
+
+ get :index, :profile => p1.identifier
+
+ assert assigns(:network_activities).include?(scrap_activity)
+ end
+
end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 52e91e6..cfea236 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -199,5 +199,9 @@ class ActiveSupport::TestCase
ret
end
+ def json_response
+ ActiveSupport::JSON.decode(@response.body)
+ end
+
end
diff --git a/test/unit/notify_activity_to_profiles_job_test.rb b/test/unit/notify_activity_to_profiles_job_test.rb
index c0e6ed5..3d7920f 100644
--- a/test/unit/notify_activity_to_profiles_job_test.rb
+++ b/test/unit/notify_activity_to_profiles_job_test.rb
@@ -52,6 +52,27 @@ class NotifyActivityToProfilesJobTest < ActiveSupport::TestCase
end
end
+ should 'notify only marked people on marked scraps' do
+ profile = create_user('scrap-creator').person
+ c1 = Circle.create!(:name => 'Family', :person => profile, :profile_type => Person)
+ p1 = create_user('emily').person
+ p2 = create_user('wollie').person
+ not_marked = create_user('jack').person
+ not_marked.add_friend(p1)
+ not_marked.add_friend(p2)
+ not_marked.add_friend(profile)
+ ProfileFollower.create!(:profile => p1, :circle => c1)
+ ProfileFollower.create!(:profile => p2, :circle => c1)
+ ProfileFollower.create!(:profile => not_marked, :circle => c1)
+
+ scrap = Scrap.create!(:content => 'Secret message.', :sender_id => profile.id, :receiver_id => profile.id, :marked_people => [p1,p2])
+ process_delayed_job_queue
+
+ assert p1.tracked_notifications.where(:target => scrap).present?
+ assert p2.tracked_notifications.where(:target => scrap).present?
+ assert not_marked.tracked_notifications.where(:target => scrap).blank?
+ end
+
should 'not notify the communities members' do
person = fast_create(Person)
community = fast_create(Community)
--
libgit2 0.21.2