fat_free_crm 0.11.1 → 0.11.2

Sign up to get free protection for your applications and to get access to all the features.

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);