fat_free_crm 0.11.1 → 0.11.2

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.

Potentially problematic release.


This version of fat_free_crm might be problematic. Click here for more details.

Files changed (179) hide show
  1. data/Gemfile +30 -12
  2. data/Gemfile.lock +131 -119
  3. data/Procfile +1 -1
  4. data/README.md +1 -1
  5. data/app/assets/images/notifications.png +0 -0
  6. data/app/assets/javascripts/application.js.erb +3 -0
  7. data/app/assets/javascripts/crm_textarea_autocomplete.js +44 -0
  8. data/app/assets/stylesheets/application.css.erb +2 -0
  9. data/app/assets/stylesheets/common.scss +7 -11
  10. data/app/assets/stylesheets/textarea_autocomplete.scss +42 -0
  11. data/app/controllers/admin/application_controller.rb +5 -5
  12. data/app/controllers/admin/field_groups_controller.rb +11 -51
  13. data/app/controllers/admin/fields_controller.rb +13 -59
  14. data/app/controllers/admin/plugins_controller.rb +1 -4
  15. data/app/controllers/admin/settings_controller.rb +0 -4
  16. data/app/controllers/admin/tags_controller.rb +11 -66
  17. data/app/controllers/admin/users_controller.rb +20 -83
  18. data/app/controllers/application_controller.rb +83 -69
  19. data/app/controllers/comments_controller.rb +12 -29
  20. data/app/controllers/emails_controller.rb +1 -5
  21. data/app/controllers/entities/accounts_controller.rb +13 -32
  22. data/app/controllers/entities/campaigns_controller.rb +17 -32
  23. data/app/controllers/entities/contacts_controller.rb +20 -38
  24. data/app/controllers/entities/leads_controller.rb +33 -55
  25. data/app/controllers/entities/opportunities_controller.rb +26 -42
  26. data/app/controllers/entities_controller.rb +92 -83
  27. data/app/controllers/home_controller.rb +1 -10
  28. data/app/controllers/lists_controller.rb +1 -4
  29. data/app/controllers/{entities/tasks_controller.rb → tasks_controller.rb} +21 -32
  30. data/app/controllers/users_controller.rb +6 -5
  31. data/app/helpers/accounts_helper.rb +32 -9
  32. data/app/helpers/application_helper.rb +15 -1
  33. data/app/helpers/campaigns_helper.rb +1 -1
  34. data/app/helpers/comments_helper.rb +11 -1
  35. data/app/helpers/leads_helper.rb +1 -1
  36. data/app/helpers/opportunities_helper.rb +1 -1
  37. data/app/{models/mailers/notifier.rb → mailers/dropbox_mailer.rb} +5 -16
  38. data/app/mailers/subscription_mailer.rb +37 -0
  39. data/{lib/tasks/dropbox.rake → app/mailers/user_mailer.rb} +11 -13
  40. data/app/models/entities/account.rb +3 -1
  41. data/app/models/entities/campaign.rb +3 -1
  42. data/app/models/entities/contact.rb +3 -1
  43. data/app/models/entities/lead.rb +6 -5
  44. data/app/models/entities/opportunity.rb +3 -1
  45. data/app/models/fields/field.rb +1 -1
  46. data/app/models/polymorphic/comment.rb +34 -0
  47. data/app/models/{entities → polymorphic}/task.rb +16 -3
  48. data/app/models/setting.rb +15 -15
  49. data/app/models/users/ability.rb +12 -5
  50. data/app/models/users/user.rb +7 -2
  51. data/app/views/accounts/index.html.haml +1 -1
  52. data/app/views/accounts/index.js.rjs +1 -1
  53. data/app/views/admin/plugins/index.html.haml +1 -7
  54. data/app/views/{shared/auto_complete.html.haml → application/_auto_complete.html.haml} +0 -0
  55. data/app/views/{shared → application}/index.atom.builder +1 -1
  56. data/app/views/{shared → application}/index.rss.builder +1 -1
  57. data/app/views/campaigns/index.html.haml +1 -1
  58. data/app/views/campaigns/index.js.rjs +1 -1
  59. data/app/views/comments/_new.html.haml +6 -0
  60. data/app/views/comments/_subscription_links.html.haml +13 -0
  61. data/app/views/comments/new.js.rjs +2 -0
  62. data/app/views/contacts/_top_section.html.haml +3 -13
  63. data/app/views/contacts/index.html.haml +1 -1
  64. data/app/views/contacts/index.js.rjs +1 -1
  65. data/app/views/{notifier/dropbox_ack_notification.html.haml → dropbox_mailer/dropbox_notification.html.haml} +2 -2
  66. data/app/views/{shared → entities}/attach.js.rjs +1 -1
  67. data/app/views/entities/contacts.js.rjs +1 -1
  68. data/app/views/{shared/discard.rjs → entities/discard.js.rjs} +0 -0
  69. data/app/views/entities/leads.js.rjs +1 -1
  70. data/app/views/entities/opportunities.js.rjs +1 -1
  71. data/app/views/entities/subscription_update.js.rjs +4 -0
  72. data/app/views/entities/versions.js.rjs +1 -1
  73. data/app/views/layouts/_footer.html.haml +1 -1
  74. data/app/views/layouts/application.html.haml +3 -0
  75. data/app/views/leads/_contact.html.haml +1 -0
  76. data/app/views/leads/index.html.haml +1 -1
  77. data/app/views/leads/index.js.rjs +1 -1
  78. data/app/views/opportunities/_top_section.html.haml +4 -14
  79. data/app/views/opportunities/index.html.haml +1 -1
  80. data/app/views/opportunities/index.js.rjs +1 -1
  81. data/app/views/subscription_mailer/comment_notification.text.erb +7 -0
  82. data/app/views/{notifier → user_mailer}/password_reset_instructions.html.haml +0 -0
  83. data/config/application.rb +3 -1
  84. data/config/environments/development.rb +1 -1
  85. data/config/environments/test.rb +3 -0
  86. data/config/initializers/action_mailer.rb +8 -5
  87. data/config/initializers/cancan.rb +151 -0
  88. data/config/initializers/constants.rb +1 -0
  89. data/config/initializers/locale.rb +20 -0
  90. data/config/initializers/paper_trail.rb +4 -5
  91. data/config/initializers/relative_url_root.rb +0 -1
  92. data/config/initializers/squeel.rb +5 -0
  93. data/config/locales/cz_fat_free_crm.yml +3 -3
  94. data/config/locales/de.yml +2 -2
  95. data/config/locales/de_fat_free_crm.yml +651 -596
  96. data/config/locales/en-GB_fat_free_crm.yml +3 -3
  97. data/config/locales/en-US_fat_free_crm.yml +13 -3
  98. data/config/locales/es_fat_free_crm.yml +3 -3
  99. data/config/locales/fr-CA_fat_free_crm.yml +3 -3
  100. data/config/locales/fr_fat_free_crm.yml +3 -3
  101. data/config/locales/it_fat_free_crm.yml +3 -3
  102. data/config/locales/pl_fat_free_crm.yml +3 -3
  103. data/config/locales/pt-BR_fat_free_crm.yml +3 -3
  104. data/config/locales/ru_fat_free_crm.yml +3 -3
  105. data/config/locales/sv-SE_fat_free_crm.yml +3 -3
  106. data/config/locales/th_fat_free_crm.yml +3 -3
  107. data/config/routes.rb +10 -0
  108. data/config/settings.default.yml +29 -10
  109. data/config/unicorn.rb +4 -0
  110. data/db/migrate/20111201030535_add_field_groups_klass_name.rb +3 -1
  111. data/db/migrate/20120314080441_add_subscribed_users_to_entities.rb +23 -0
  112. data/db/migrate/20120405080727_change_subscribed_users_to_set.rb +24 -0
  113. data/db/migrate/20120405080742_change_further_subscribed_users_to_set.rb +27 -0
  114. data/db/migrate/20120413034923_add_index_on_versions_item_type.rb +5 -0
  115. data/db/schema.rb +109 -126
  116. data/fat_free_crm.gemspec +12 -18
  117. data/lib/fat_free_crm.rb +0 -1
  118. data/lib/fat_free_crm/core_ext/array.rb +1 -0
  119. data/lib/fat_free_crm/gem_dependencies.rb +1 -0
  120. data/lib/fat_free_crm/mail_processor/base.rb +226 -0
  121. data/lib/fat_free_crm/mail_processor/comment_replies.rb +86 -0
  122. data/lib/fat_free_crm/mail_processor/dropbox.rb +288 -0
  123. data/lib/fat_free_crm/permissions.rb +6 -19
  124. data/lib/fat_free_crm/renderers.rb +0 -8
  125. data/lib/fat_free_crm/tabs.rb +1 -1
  126. data/lib/fat_free_crm/version.rb +1 -1
  127. data/lib/plugins/country_select/lib/country_select.rb +2 -2
  128. data/lib/tasks/mail_processing.rake +60 -0
  129. data/spec/controllers/admin/users_controller_spec.rb +0 -2
  130. data/spec/controllers/{accounts_controller_spec.rb → entities/accounts_controller_spec.rb} +7 -9
  131. data/spec/controllers/{campaigns_controller_spec.rb → entities/campaigns_controller_spec.rb} +7 -7
  132. data/spec/controllers/{contacts_controller_spec.rb → entities/contacts_controller_spec.rb} +5 -9
  133. data/spec/controllers/{leads_controller_spec.rb → entities/leads_controller_spec.rb} +7 -9
  134. data/spec/controllers/{opportunities_controller_spec.rb → entities/opportunities_controller_spec.rb} +8 -15
  135. data/spec/controllers/tasks_controller_spec.rb +1 -5
  136. data/spec/controllers/users_controller_spec.rb +5 -9
  137. data/spec/factories/subscription_factories.rb +6 -0
  138. data/spec/lib/mail_processor/base_spec.rb +164 -0
  139. data/spec/lib/mail_processor/comment_replies_spec.rb +63 -0
  140. data/spec/lib/{dropbox_spec.rb → mail_processor/dropbox_spec.rb} +73 -181
  141. data/spec/lib/mail_processor/sample_emails/dropbox.rb +167 -0
  142. data/spec/mailers/subscription_mailer_spec.rb +17 -0
  143. data/spec/models/{base → entities}/account_contact_spec.rb +0 -0
  144. data/spec/models/{base → entities}/account_opportunity_spec.rb +0 -0
  145. data/spec/models/{base → entities}/account_spec.rb +4 -0
  146. data/spec/models/{base → entities}/campaign_spec.rb +4 -0
  147. data/spec/models/{base → entities}/contact_opportunity_spec.rb +0 -0
  148. data/spec/models/{base → entities}/contact_spec.rb +4 -0
  149. data/spec/models/{base → entities}/lead_spec.rb +4 -0
  150. data/spec/models/{base → entities}/opportunity_spec.rb +4 -0
  151. data/spec/models/polymorphic/comment_spec.rb +15 -0
  152. data/spec/models/{base → polymorphic}/task_spec.rb +124 -30
  153. data/spec/models/polymorphic/version_spec.rb +1 -1
  154. data/spec/shared/controllers.rb +5 -7
  155. data/spec/shared/models.rb +46 -0
  156. data/spec/spec_helper.rb +3 -4
  157. data/spec/support/mail_processor_mocks.rb +30 -0
  158. data/spec/support/uploaded_file.rb +3 -0
  159. data/spec/views/{common → application}/auto_complete.haml_spec.rb +1 -1
  160. data/vendor/assets/images/jquery-ui/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  161. data/vendor/assets/images/jquery-ui/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  162. data/vendor/assets/images/jquery-ui/ui-bg_flat_10_000000_40x100.png +0 -0
  163. data/vendor/assets/images/jquery-ui/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  164. data/vendor/assets/images/jquery-ui/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  165. data/vendor/assets/images/jquery-ui/ui-bg_glass_65_ffffff_1x400.png +0 -0
  166. data/vendor/assets/images/jquery-ui/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  167. data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  168. data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  169. data/vendor/assets/images/jquery-ui/ui-icons_222222_256x240.png +0 -0
  170. data/vendor/assets/images/jquery-ui/ui-icons_228ef1_256x240.png +0 -0
  171. data/vendor/assets/images/jquery-ui/ui-icons_ef8c08_256x240.png +0 -0
  172. data/vendor/assets/images/jquery-ui/ui-icons_ffd27a_256x240.png +0 -0
  173. data/vendor/assets/images/jquery-ui/ui-icons_ffffff_256x240.png +0 -0
  174. data/vendor/assets/javascripts/textarea_autocomplete.js +605 -0
  175. data/vendor/assets/stylesheets/jquery-ui.custom.css.erb +565 -0
  176. metadata +234 -154
  177. data/config/locales/simple_form.en.yml +0 -24
  178. data/lib/fat_free_crm/dropbox.rb +0 -439
  179. data/spec/lib/dropbox/email_samples.rb +0 -77
@@ -38,7 +38,7 @@ describe Version do
38
38
 
39
39
  it "should select all versions except one" do
40
40
  @versions = Version.for(@current_user).exclude_events(:view)
41
- @versions.map(&:event).should == %w(create destroy update)
41
+ @versions.map(&:event).sort.should == %w(create destroy update)
42
42
  end
43
43
 
44
44
  it "should select all versions except many" do
@@ -21,9 +21,9 @@ module SharedControllerSpecs
21
21
  end
22
22
  end
23
23
 
24
- it "should render shared/auto_complete template" do
24
+ it "should render application/_auto_complete template" do
25
25
  post :auto_complete, :auto_complete_query => @query
26
- response.should render_template("shared/auto_complete")
26
+ response.should render_template("application/_auto_complete")
27
27
  end
28
28
  end
29
29
 
@@ -39,7 +39,7 @@ module SharedControllerSpecs
39
39
  if @model.is_a?(Account) && @attachment.respond_to?(:account) # Skip Tasks...
40
40
  assigns[:account].should == @attachment.reload.account
41
41
  end
42
- response.should render_template("shared/attach")
42
+ response.should render_template("entities/attach")
43
43
  end
44
44
 
45
45
  it "should not attach the asset that is already attached" do
@@ -51,7 +51,7 @@ module SharedControllerSpecs
51
51
 
52
52
  xhr :put, :attach, :id => @model.id, :assets => @attachment.class.name.tableize, :asset_id => @attachment.id
53
53
  assigns[:attached].should == nil
54
- response.should render_template("shared/attach")
54
+ response.should render_template("entities/attach")
55
55
  end
56
56
 
57
57
  it "should display flash warning when the model is no longer available" do
@@ -78,7 +78,7 @@ module SharedControllerSpecs
78
78
  assigns[:account].should == @model if @model.is_a?(Account)
79
79
  assigns[:campaign].should == @model if @model.is_a?(Campaign)
80
80
 
81
- response.should render_template("shared/discard")
81
+ response.should render_template("entities/discard")
82
82
  end
83
83
 
84
84
  it "should display flash warning when the model is no longer available" do
@@ -97,6 +97,4 @@ module SharedControllerSpecs
97
97
  response.body.should == "window.location.reload();"
98
98
  end
99
99
  end
100
-
101
100
  end
102
-
@@ -34,4 +34,50 @@ module SharedModelSpecs
34
34
  end
35
35
  end
36
36
  end
37
+
38
+ require "cancan/matchers"
39
+
40
+ shared_examples_for Ability do |klass|
41
+
42
+ subject { ability }
43
+ let(:ability){ Ability.new(user) }
44
+ let(:user){ FactoryGirl.create(:user) }
45
+ let(:factory){ klass.model_name.underscore }
46
+
47
+ context "create" do
48
+ it{ should be_able_to(:create, klass) }
49
+ end
50
+
51
+ context "when public access" do
52
+ let(:asset){ FactoryGirl.create(factory, :access => 'Public') }
53
+
54
+ it{ should be_able_to(:manage, asset) }
55
+ end
56
+
57
+ context "when private access owner" do
58
+ let(:asset){ FactoryGirl.create(factory, :access => 'Private', :user_id => user.id) }
59
+
60
+ it{ should be_able_to(:manage, asset) }
61
+ end
62
+
63
+ context "when private access not owner" do
64
+ let(:asset){ FactoryGirl.create(factory, :access => 'Private') }
65
+
66
+ it{ should_not be_able_to(:manage, asset) }
67
+ end
68
+
69
+ context "when shared access with permission" do
70
+ let(:asset){ FactoryGirl.create(factory, :access => 'Shared', :permissions => [permission]) }
71
+ let(:permission){ Permission.new(:user => user) }
72
+
73
+ it{ should be_able_to(:manage, asset) }
74
+ end
75
+
76
+ context "when shared access with no permission" do
77
+ let(:asset){ FactoryGirl.create(factory, :access => 'Shared', :permissions => [permission]) }
78
+ let(:permission){ Permission.new(:user => FactoryGirl.create(:user)) }
79
+
80
+ it{ should_not be_able_to(:manage, asset) }
81
+ end
82
+ end
37
83
  end
data/spec/spec_helper.rb CHANGED
@@ -22,8 +22,6 @@ Spork.prefork do
22
22
  require 'factory_girl'
23
23
  require 'ffaker'
24
24
 
25
- Dir[Rails.root.join("spec/factories/**/*.rb")].each{ |f| require File.expand_path(f) }
26
-
27
25
  # Requires supporting ruby files with custom matchers and macros, etc,
28
26
  # in spec/support/ and its subdirectories.
29
27
  Dir[Rails.root.join("spec/support/**/*.rb")].each{ |f| require File.expand_path(f) }
@@ -56,10 +54,11 @@ Spork.prefork do
56
54
 
57
55
  config.before(:each) do
58
56
  PaperTrail.enabled = false
59
- end
60
57
 
61
- config.before(:each, :type => :view) do
58
+ # Overwrite locale settings within "config/settings.yml" if necessary.
59
+ # In order to ensure that test still pass if "Setting.locale" is not set to "en-US".
62
60
  I18n.locale = 'en-US'
61
+ Setting.locale = 'en-US' unless Setting.locale == 'en-US'
63
62
  end
64
63
 
65
64
  config.after(:each, :type => :view) do
@@ -0,0 +1,30 @@
1
+ module MockIMAP
2
+ def mock_imap
3
+ @imap = mock
4
+ @settings = @crawler.instance_variable_get("@settings")
5
+ @settings[:address] = @mock_address
6
+ Net::IMAP.stub!(:new).with(@settings[:server], @settings[:port], @settings[:ssl]).and_return(@imap)
7
+ end
8
+
9
+ def mock_connect
10
+ mock_imap
11
+ @imap.stub!(:login).and_return(true)
12
+ @imap.stub!(:select).and_return(true)
13
+ end
14
+
15
+ def mock_disconnect
16
+ @imap.stub!(:disconnected?).and_return(false)
17
+ @imap.stub!(:logout).and_return(true)
18
+ @imap.stub!(:disconnect).and_return(true)
19
+ end
20
+
21
+ def mock_message(body)
22
+ @fetch_data = mock
23
+ @fetch_data.stub!(:attr).and_return("RFC822" => body)
24
+ @imap.stub!(:uid_search).and_return([ :uid ])
25
+ @imap.stub!(:uid_fetch).and_return([ @fetch_data ])
26
+ @imap.stub!(:uid_copy).and_return(true)
27
+ @imap.stub!(:uid_store).and_return(true)
28
+ body
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ Rack::Test::UploadedFile.class_eval do
2
+ attr_accessor :tempfile
3
+ end
@@ -1,6 +1,6 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
- describe "/shared/auto_complete" do
3
+ describe "/application/_auto_complete" do
4
4
  include AccountsHelper
5
5
 
6
6
  before do
@@ -0,0 +1,605 @@
1
+ /**
2
+ * Plugin Name: areacomplete
3
+ * Author: Amir Harel, Sean Templeton, Nathan Broadbent
4
+ * Copyright: amir harel (harel.amir1@gmail.com)
5
+ * Twitter: @amir_harel
6
+ * Version 1.4.1
7
+ * Published at : http://www.amirharel.com/2011/03/07/implementing-autocomplete-jquery-plugin-for-textarea/
8
+ */
9
+
10
+ (function($){
11
+ /**
12
+ * @param obj
13
+ * @attr wordCount {Number} the number of words the user want to for matching it with the dictionary
14
+ * @attr mode {String} set "outter" for using an autocomplete that is being displayed in the outter layout of the textarea, as opposed to inner display
15
+ * @attr on {Object} containing the followings:
16
+ * @attr query {Function} will be called to query if there is any match for the user input
17
+ */
18
+ $.fn.areacomplete = function(obj){
19
+ if( typeof $.browser.msie != 'undefined' ) obj.mode = 'outter';
20
+ this.each(function(index,element){
21
+ if( element.nodeName == 'TEXTAREA' ){
22
+ makeAutoComplete(element,obj);
23
+ }
24
+ });
25
+ }
26
+
27
+ var browser = {isChrome: $.browser.webkit };
28
+
29
+ function getTextAreaSelectionEnd(ta) {
30
+ var textArea = ta;//document.getElementById('textarea1');
31
+ if (document.selection) { //IE
32
+ var bm = document.selection.createRange().getBookmark();
33
+ var sel = textArea.createTextRange();
34
+ sel.moveToBookmark(bm);
35
+ var sleft = textArea.createTextRange();
36
+ sleft.collapse(true);
37
+ sleft.setEndPoint("EndToStart", sel);
38
+ return sleft.text.length + sel.text.length;
39
+ }
40
+ return textArea.selectionEnd; //ff & chrome
41
+ }
42
+
43
+ function getDefaultCharArray(){
44
+ return {
45
+ '`':0,
46
+ '~':0,
47
+ '1':0,
48
+ '!':0,
49
+ '2':0,
50
+ '@':0,
51
+ '3':0,
52
+ '#':0,
53
+ '4':0,
54
+ '$':0,
55
+ '5':0,
56
+ '%':0,
57
+ '6':0,
58
+ '^':0,
59
+ '7':0,
60
+ '&':0,
61
+ '8':0,
62
+ '*':0,
63
+ '9':0,
64
+ '(':0,
65
+ '0':0,
66
+ ')':0,
67
+ '-':0,
68
+ '_':0,
69
+ '=':0,
70
+ '+':0,
71
+ 'q':0,
72
+ 'Q':0,
73
+ 'w':0,
74
+ 'W':0,
75
+ 'e':0,
76
+ 'E':0,
77
+ 'r':0,
78
+ 'R':0,
79
+ 't':0,
80
+ 'T':0,
81
+ 'y':0,
82
+ 'Y':0,
83
+ 'u':0,
84
+ 'U':0,
85
+ 'i':0,
86
+ 'I':0,
87
+ 'o':0,
88
+ 'O':0,
89
+ 'p':0,
90
+ 'P':0,
91
+ '[':0,
92
+ '{':0,
93
+ ']':0,
94
+ '}':0,
95
+ 'a':0,
96
+ 'A':0,
97
+ 's':0,
98
+ 'S':0,
99
+ 'd':0,
100
+ 'D':0,
101
+ 'f':0,
102
+ 'F':0,
103
+ 'g':0,
104
+ 'G':0,
105
+ 'h':0,
106
+ 'H':0,
107
+ 'j':0,
108
+ 'J':0,
109
+ 'k':0,
110
+ 'K':0,
111
+ 'l':0,
112
+ 'L':0,
113
+ ';':0,
114
+ ':':0,
115
+ '\'':0,
116
+ '"':0,
117
+ '\\':0,
118
+ '|':0,
119
+ 'z':0,
120
+ 'Z':0,
121
+ 'x':0,
122
+ 'X':0,
123
+ 'c':0,
124
+ 'C':0,
125
+ 'v':0,
126
+ 'V':0,
127
+ 'b':0,
128
+ 'B':0,
129
+ 'n':0,
130
+ 'N':0,
131
+ 'm':0,
132
+ 'M':0,
133
+ ',':0,
134
+ '<':0,
135
+ '.':0,
136
+ '>':0,
137
+ '/':0,
138
+ '?':0,
139
+ ' ':0
140
+ };
141
+ }
142
+
143
+
144
+ function setCharSize(data){
145
+ for( var ch in data.chars ){
146
+ if( ch == ' ' ) $(data.clone).html("<span id='test-width_"+data.id+"' style='line-block'>&nbsp;</span>");
147
+ else $(data.clone).html("<span id='test-width_"+data.id+"' style='line-block'>"+ch+"</span>");
148
+ var testWidth = $("#test-width_"+data.id).width();
149
+ data.chars[ch] = testWidth;
150
+ }
151
+ }
152
+
153
+ var _data = {};
154
+ var _count = 0;
155
+ function makeAutoComplete(ta,obj){
156
+ _count++;
157
+ _data[_count] = {
158
+ id:"auto_"+_count,
159
+ ta:ta,
160
+ wordCount:obj.wordCount,
161
+ on:obj.on,
162
+ clone:null,
163
+ lineHeight:0,
164
+ list:null,
165
+ charInLines:{},
166
+ mode:obj.mode,
167
+ chars:getDefaultCharArray()};
168
+
169
+ var clone = createClone(_count);
170
+ _data[_count].clone = clone;
171
+ setCharSize(_data[_count]);
172
+ //_data[_count].lineHeight = $(ta).css("font-size");
173
+
174
+ var ta_id = $(ta).attr('id')
175
+ $(ta).after("<div class='areacomplete-dropdown' id='areacomplete_"+ta_id+"'><ul class='auto-list chzn-results'></ul></div>");
176
+ _data[_count].list = $('.auto-list').first()
177
+
178
+ registerEvents(_data[_count]);
179
+ }
180
+
181
+
182
+ function createClone(id){
183
+ var data = _data[id];
184
+ var div = document.createElement("div");
185
+ var offset = $(data.ta).offset();
186
+ offset.top = offset.top - parseInt($(data.ta).css("margin-top"));
187
+ offset.left = offset.left - parseInt($(data.ta).css("margin-left"));
188
+ //console.log("createClone: offset.top=",offset.top," offset.left=",offset.left);
189
+ $(div).css({
190
+ position:"absolute",
191
+ top: offset.top,
192
+ left: offset.left,
193
+ "border-collapse" : $(data.ta).css("border-collapse"),
194
+ "border-bottom-style" : $(data.ta).css("border-bottom-style"),
195
+ "border-bottom-width" : $(data.ta).css("border-bottom-width"),
196
+ "border-left-style" : $(data.ta).css("border-left-style"),
197
+ "border-left-width" : $(data.ta).css("border-left-width"),
198
+ "border-right-style" : $(data.ta).css("border-right-style"),
199
+ "border-right-width" : $(data.ta).css("border-right-width"),
200
+ "border-spacing" : $(data.ta).css("border-spacing"),
201
+ "border-top-style" : $(data.ta).css("border-top-style"),
202
+ "border-top-width" : $(data.ta).css("border-top-width"),
203
+ "direction" : $(data.ta).css("direction"),
204
+ "font-size-adjust" : $(data.ta).css("font-size-adjust"),
205
+ "font-size" : $(data.ta).css("font-size"),
206
+ "font-stretch" : $(data.ta).css("font-stretch"),
207
+ "font-style" : $(data.ta).css("font-style"),
208
+ "font-family" : $(data.ta).css("font-family"),
209
+ "font-variant" : $(data.ta).css("font-variant"),
210
+ "font-weight" : $(data.ta).css("font-weight"),
211
+ "width" : $(data.ta).css("width"),
212
+ "height" : $(data.ta).css("height"),
213
+ "letter-spacing" : $(data.ta).css("letter-spacing"),
214
+ "margin-bottom" : $(data.ta).css("margin-bottom"),
215
+ "margin-top" : $(data.ta).css("margin-top"),
216
+ "margin-right" : $(data.ta).css("margin-right"),
217
+ "margin-left" : $(data.ta).css("margin-left"),
218
+ "padding-bottom" : $(data.ta).css("padding-bottom"),
219
+ "padding-top" : $(data.ta).css("padding-top"),
220
+ "padding-right" : $(data.ta).css("padding-right"),
221
+ "padding-left" : $(data.ta).css("padding-left"),
222
+ "overflow-x" : "hidden",
223
+ "line-height" : $(data.ta).css("line-height"),
224
+ "overflow-y" : "hidden",
225
+ "z-index" : -10
226
+ });
227
+
228
+ //console.log("createClone: ta width=",$(data.ta).css("width")," ta clientWidth=",data.ta.clientWidth, "scrollWidth=",data.ta.scrollWidth," offsetWidth=",data.ta.offsetWidth," jquery.width=",$(data.ta).width());
229
+ //i don't know why by chrome adds some pixels to the clientWidth...
230
+ data.chromeWidthFix = (data.ta.clientWidth - $(data.ta).width());
231
+ data.lineHeight = $(data.ta).css("line-height");
232
+ if( isNaN(parseInt(data.lineHeight)) ) data.lineHeight = parseInt($(data.ta).css("font-size"))+2;
233
+ document.body.appendChild(div);
234
+ return div;
235
+ }
236
+
237
+
238
+ function getWords(data){
239
+ var selectionEnd = getTextAreaSelectionEnd(data.ta);//.selectionEnd;
240
+ var text = data.ta.value;
241
+ text = text.substr(0,selectionEnd);
242
+ if( text.charAt(text.length-1) == ' ' || text.charAt(text.length-1) == '\n' ) return "";
243
+ var ret = [];
244
+ var wordsFound = 0;
245
+ var pos = text.length-1;
246
+ while( wordsFound < data.wordCount && pos >= 0 && text.charAt(pos) != '\n'){
247
+ ret.unshift(text.charAt(pos));
248
+ pos--;
249
+ if( text.charAt(pos) == ' ' || pos < 0 ){
250
+ wordsFound++;
251
+ }
252
+ }
253
+ return ret.join("");
254
+ }
255
+
256
+
257
+ function showList(data, text, list){
258
+ if( !data.listVisible ){
259
+ data.listVisible = true;
260
+ var pos = getCursorPosition(data);
261
+ $(data.list).css({
262
+ left: pos.left+"px",
263
+ top: pos.top+"px",
264
+ display: "block"
265
+ });
266
+ }
267
+
268
+ var attrs, value, d;
269
+ var regEx = new RegExp("("+text+")", "i");
270
+ var taWidth = $(data.ta).width()-5;
271
+ var width = data.mode == "outter" ? taWidth - 3 : "";
272
+ $(data.list).empty();
273
+ for( var i=0; i< list.length; i++ )
274
+ {
275
+ d = {};
276
+ if(typeof list[i] == "string")
277
+ value = list[i];
278
+ else
279
+ {
280
+ value = list[i].value;
281
+ d = list[i].data;
282
+ }
283
+ $(data.list).append($('<li>').css('width', width + 'px').attr({'data-value': value}).html(value.replace(regEx,"<em>$1</em>")).data(d));
284
+ }
285
+ }
286
+
287
+ function breakLines(text,data){
288
+ var lines = [];
289
+
290
+ var width = $(data.clone).width();
291
+
292
+ var line1 = "";
293
+ var line1Width = 0;
294
+ var line2Width = 0;
295
+ var line2 = "";
296
+ var chSize = data.chars;
297
+
298
+
299
+ var len = text.length;
300
+ for( var i=0; i<len; i++){
301
+ var ch = text.charAt(i);
302
+ line2 += ch.replace(" ","&nbsp;");
303
+ var size = (typeof chSize[ch] == 'undefined' ) ? 0 : chSize[ch];
304
+ line2Width += size;
305
+ if( ch == ' '|| ch == '-' ){
306
+ if( line1Width + line2Width < width-1 ){
307
+ line1 = line1 + line2;
308
+ line1Width = line1Width + line2Width;
309
+ line2 = "";
310
+ line2Width = 0;
311
+ }
312
+ else{
313
+ lines.push(line1);
314
+ line1= line2;
315
+ line1Width = line2Width;
316
+ line2= "";
317
+ line2Width = 0;
318
+ }
319
+ }
320
+ if( ch == '\n'){
321
+ if( line1Width + line2Width < width-1 ){
322
+ lines.push(line1 + line2);
323
+ }
324
+ else{
325
+ lines.push(line1);
326
+ lines.push(line2);
327
+ }
328
+ line1 = "";
329
+ line2 = "";
330
+ line1Width = 0;
331
+ line2Width = 0;
332
+ }
333
+ //else{
334
+ //line2 += ch;
335
+ //}
336
+ }
337
+ if( line1Width + line2Width < width-1 ){
338
+ lines.push(line1 + line2);
339
+ }
340
+ else{
341
+ lines.push(line1);
342
+ lines.push(line2);
343
+ }
344
+ return lines;
345
+ }
346
+
347
+
348
+
349
+
350
+ function getCursorPosition(data){
351
+ if( data.mode == "outter" ){
352
+ return getOuterPosition(data);
353
+ }
354
+ //console.log("getCursorPosition: ta width=",$(data.ta).css("width")," ta clientWidth=",data.ta.clientWidth, "scrollWidth=",data.ta.scrollWidth," offsetWidth=",data.ta.offsetWidth," jquery.width=",$(data.ta).width());
355
+ if( browser.isChrome ){
356
+ $(data.clone).width(data.ta.clientWidth-data.chromeWidthFix);
357
+ }
358
+ else{
359
+ $(data.clone).width(data.ta.clientWidth);
360
+ }
361
+
362
+
363
+ var ta = data.ta;
364
+ var selectionEnd = getTextAreaSelectionEnd(data.ta);
365
+ var text = ta.value;//.replace(/ /g,"&nbsp;");
366
+
367
+ var subText = text.substr(0,selectionEnd);
368
+ var restText = text.substr(selectionEnd,text.length);
369
+
370
+ var lines = breakLines(subText,data);//subText.split("\n");
371
+ var miror = $(data.clone);
372
+
373
+ miror.html("");
374
+ for( var i=0; i< lines.length-1; i++){
375
+ miror.append("<div style='height:"+(parseInt(data.lineHeight))+"px"+";'>"+lines[i]+"</div>");
376
+ }
377
+ miror.append("<span id='"+data.id+"' style='display:inline-block;'>"+lines[lines.length-1]+"</span>");
378
+
379
+ miror.append("<span id='rest' style='max-width:'"+data.ta.clientWidth+"px'>"+restText.replace(/\n/g,"<br/>")+"&nbsp;</span>");
380
+
381
+ miror.get(0).scrollTop = ta.scrollTop;
382
+
383
+ var span = miror.children("#"+data.id);
384
+ var offset = span.offset();
385
+
386
+ return {top:offset.top+span.height(),left:offset.left+span.width()};
387
+
388
+ }
389
+
390
+ function getOuterPosition(data){
391
+ var offset = $(data.ta).offset();
392
+ return {top:offset.top+$(data.ta).height()+8,left:offset.left};
393
+ }
394
+
395
+ function hideList(data){
396
+ if( data.listVisible ){
397
+ $(data.list).css("display","none");
398
+ data.listVisible = false;
399
+ }
400
+ }
401
+
402
+ // Selects the first element if none are selected
403
+ function ensureSelected(data) {
404
+ var selected = $(data.list).find("[data-selected=true]");
405
+ if( selected.length != 1 ){
406
+ var new_selected = $(data.list).find("li:first-child")
407
+ new_selected.attr("data-selected","true");
408
+ }
409
+ if (new_selected[0]) { scrollIntoView(new_selected[0]); }
410
+ }
411
+
412
+ function setSelected(dir, data){
413
+ var selected = $(data.list).find("[data-selected=true]");
414
+ // Don't allow selection to wrap from bottom
415
+ if( selected.length != 1 && dir > 0 ){
416
+ var new_selected = $(data.list).find("li:first-child")
417
+ new_selected.attr("data-selected","true");
418
+ } else {
419
+ var new_selected = dir > 0 ? selected.next() : selected.prev();
420
+ // Don't unselect the last element when pressing down arrow,
421
+ // but DO unselect it if pressing UP from the first element
422
+ if (new_selected[0] || dir < 0) {
423
+ selected.attr("data-selected","false");
424
+ new_selected.attr("data-selected","true");
425
+ }
426
+ }
427
+ // Set scroll position on ul
428
+ if (new_selected[0]) { scrollIntoView(new_selected[0]); }
429
+ }
430
+
431
+ function scrollIntoView(element) {
432
+ var container = element.offsetParent;
433
+ var containerTop = $(container).scrollTop();
434
+
435
+ var containerBottom = containerTop + $(container).height();
436
+ var elemTop = element.offsetTop;
437
+ var elemBottom = elemTop + $(element).height() + 4;
438
+
439
+ if (elemTop < containerTop) {
440
+ $(container).scrollTop(elemTop);
441
+ } else if (elemBottom > containerBottom) {
442
+ $(container).scrollTop(elemBottom + 4 - $(container).height());
443
+ }
444
+ }
445
+
446
+
447
+ function getCurrentSelected(data){
448
+ var selected = $(data.list).find("[data-selected=true]");
449
+ if( selected.length == 1) return selected.get(0);
450
+ return null;
451
+ }
452
+
453
+ function onUserSelected(li,data){
454
+ var selectedText = $(li).attr("data-value");
455
+
456
+ var selectionEnd = getTextAreaSelectionEnd(data.ta);//.selectionEnd;
457
+ var text = data.ta.value;
458
+ text = text.substr(0,selectionEnd);
459
+ //if( text.charAt(text.length-1) == ' ' || text.charAt(text.length-1) == '\n' ) return "";
460
+ //var ret = [];
461
+ var wordsFound = 0;
462
+ var pos = text.length-1;
463
+
464
+ while( wordsFound < data.wordCount && pos >= 0 && text.charAt(pos) != '\n'){
465
+ pos--;
466
+ if( text.charAt(pos) == ' ' || pos < 0 ){
467
+ wordsFound++;
468
+ }
469
+ }
470
+ var a = data.ta.value.substr(0, pos + 1);
471
+ var c = data.ta.value.substr(selectionEnd, data.ta.value.length);
472
+ var scrollTop = data.ta.scrollTop;
473
+ if(data.on && data.on.selected)
474
+ var retText = data.on.selected(selectedText, $(li).data());
475
+ if(retText) selectedText = retText;
476
+ data.ta.value = a + selectedText + ' ' + c;
477
+ data.ta.scrollTop = scrollTop;
478
+ data.ta.selectionEnd = pos + 2 + selectedText.length;
479
+ hideList(data);
480
+ $(data.ta).focus();
481
+ }
482
+
483
+ function registerEvents(data){
484
+ $(data.list).delegate("li","click",function(e){
485
+ var li = this;
486
+ onUserSelected(li,data);
487
+ e.stopPropagation();
488
+ e.preventDefault();
489
+ return false;
490
+ });
491
+
492
+
493
+
494
+ $(data.ta).blur(function(e){
495
+ setTimeout(function(){
496
+ hideList(data);
497
+ }, 400);
498
+
499
+ });
500
+
501
+ $(data.ta).click(function(e){
502
+ hideList(data);
503
+ });
504
+
505
+ $(data.ta).keydown(function(e){
506
+ //console.log("keydown keycode="+e.keyCode);
507
+ if( data.listVisible ){
508
+ switch(e.keyCode){
509
+ case 27: //esc
510
+ hideList(data);
511
+ return false;
512
+ case 40: // up
513
+ setSelected(+1,data);
514
+ e.stopImmediatePropagation();
515
+ e.preventDefault();
516
+ return false;
517
+ case 38: // down
518
+ setSelected(-1,data);
519
+ e.stopImmediatePropagation();
520
+ e.preventDefault();
521
+ return false;
522
+ }
523
+
524
+ if( e.keyCode == 13 ){//enter key
525
+ var li = getCurrentSelected(data);
526
+ if( li ){
527
+ e.stopImmediatePropagation();
528
+ e.preventDefault();
529
+ hideList(data);
530
+ onUserSelected(li,data);
531
+ return false;
532
+ }
533
+ hideList(data);
534
+ }
535
+ }
536
+ });
537
+
538
+ $(data.ta).keyup(function(e){
539
+ if( data.listVisible ){
540
+ switch(e.keyCode){
541
+ // Ignore arrow key / delete / end / home events
542
+ case 37:
543
+ case 38:
544
+ case 39:
545
+ case 40:
546
+ e.stopImmediatePropagation();
547
+ e.preventDefault();
548
+ return false;
549
+ }
550
+ } else {
551
+ switch(e.keyCode){
552
+ // Ignore arrow key / delete / end / home events
553
+ case 13:
554
+ case 35:
555
+ case 36:
556
+ case 37:
557
+ case 38:
558
+ case 39:
559
+ case 40:
560
+ case 46:
561
+ e.stopImmediatePropagation();
562
+ e.preventDefault();
563
+ return true;
564
+ }
565
+ }
566
+
567
+ if (e.keyCode == 27) { return true }; // escape
568
+
569
+ var text = getWords(data);
570
+ //console.log("getWords return ",text);
571
+ if( text != "" ){
572
+ data.on.query(text,function(list, matching_text){
573
+ //console.log("got list = ",list);
574
+ if (typeof matching_text === "undefined") var matching_text = text;
575
+
576
+ if( list.length ){
577
+ showList(data, matching_text, list);
578
+ ensureSelected(data);
579
+ }
580
+ else{
581
+ hideList(data);
582
+ }
583
+ });
584
+ }
585
+ else{
586
+ hideList(data);
587
+ }
588
+ });
589
+
590
+
591
+ // Don't lose focus when scrolling the c with the mouse
592
+ $(data.list).mousedown(function(e){
593
+ e.stopImmediatePropagation();
594
+ e.preventDefault();
595
+ return false;
596
+ });
597
+
598
+
599
+ $(data.ta).scroll(function(e){
600
+ var ta = e.target;
601
+ var miror = $(data.clone);
602
+ miror.get(0).scrollTop = ta.scrollTop;
603
+ });
604
+ }
605
+ })(jQuery);