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.
- data/Gemfile +30 -12
- data/Gemfile.lock +131 -119
- data/Procfile +1 -1
- data/README.md +1 -1
- data/app/assets/images/notifications.png +0 -0
- data/app/assets/javascripts/application.js.erb +3 -0
- data/app/assets/javascripts/crm_textarea_autocomplete.js +44 -0
- data/app/assets/stylesheets/application.css.erb +2 -0
- data/app/assets/stylesheets/common.scss +7 -11
- data/app/assets/stylesheets/textarea_autocomplete.scss +42 -0
- data/app/controllers/admin/application_controller.rb +5 -5
- data/app/controllers/admin/field_groups_controller.rb +11 -51
- data/app/controllers/admin/fields_controller.rb +13 -59
- data/app/controllers/admin/plugins_controller.rb +1 -4
- data/app/controllers/admin/settings_controller.rb +0 -4
- data/app/controllers/admin/tags_controller.rb +11 -66
- data/app/controllers/admin/users_controller.rb +20 -83
- data/app/controllers/application_controller.rb +83 -69
- data/app/controllers/comments_controller.rb +12 -29
- data/app/controllers/emails_controller.rb +1 -5
- data/app/controllers/entities/accounts_controller.rb +13 -32
- data/app/controllers/entities/campaigns_controller.rb +17 -32
- data/app/controllers/entities/contacts_controller.rb +20 -38
- data/app/controllers/entities/leads_controller.rb +33 -55
- data/app/controllers/entities/opportunities_controller.rb +26 -42
- data/app/controllers/entities_controller.rb +92 -83
- data/app/controllers/home_controller.rb +1 -10
- data/app/controllers/lists_controller.rb +1 -4
- data/app/controllers/{entities/tasks_controller.rb → tasks_controller.rb} +21 -32
- data/app/controllers/users_controller.rb +6 -5
- data/app/helpers/accounts_helper.rb +32 -9
- data/app/helpers/application_helper.rb +15 -1
- data/app/helpers/campaigns_helper.rb +1 -1
- data/app/helpers/comments_helper.rb +11 -1
- data/app/helpers/leads_helper.rb +1 -1
- data/app/helpers/opportunities_helper.rb +1 -1
- data/app/{models/mailers/notifier.rb → mailers/dropbox_mailer.rb} +5 -16
- data/app/mailers/subscription_mailer.rb +37 -0
- data/{lib/tasks/dropbox.rake → app/mailers/user_mailer.rb} +11 -13
- data/app/models/entities/account.rb +3 -1
- data/app/models/entities/campaign.rb +3 -1
- data/app/models/entities/contact.rb +3 -1
- data/app/models/entities/lead.rb +6 -5
- data/app/models/entities/opportunity.rb +3 -1
- data/app/models/fields/field.rb +1 -1
- data/app/models/polymorphic/comment.rb +34 -0
- data/app/models/{entities → polymorphic}/task.rb +16 -3
- data/app/models/setting.rb +15 -15
- data/app/models/users/ability.rb +12 -5
- data/app/models/users/user.rb +7 -2
- data/app/views/accounts/index.html.haml +1 -1
- data/app/views/accounts/index.js.rjs +1 -1
- data/app/views/admin/plugins/index.html.haml +1 -7
- data/app/views/{shared/auto_complete.html.haml → application/_auto_complete.html.haml} +0 -0
- data/app/views/{shared → application}/index.atom.builder +1 -1
- data/app/views/{shared → application}/index.rss.builder +1 -1
- data/app/views/campaigns/index.html.haml +1 -1
- data/app/views/campaigns/index.js.rjs +1 -1
- data/app/views/comments/_new.html.haml +6 -0
- data/app/views/comments/_subscription_links.html.haml +13 -0
- data/app/views/comments/new.js.rjs +2 -0
- data/app/views/contacts/_top_section.html.haml +3 -13
- data/app/views/contacts/index.html.haml +1 -1
- data/app/views/contacts/index.js.rjs +1 -1
- data/app/views/{notifier/dropbox_ack_notification.html.haml → dropbox_mailer/dropbox_notification.html.haml} +2 -2
- data/app/views/{shared → entities}/attach.js.rjs +1 -1
- data/app/views/entities/contacts.js.rjs +1 -1
- data/app/views/{shared/discard.rjs → entities/discard.js.rjs} +0 -0
- data/app/views/entities/leads.js.rjs +1 -1
- data/app/views/entities/opportunities.js.rjs +1 -1
- data/app/views/entities/subscription_update.js.rjs +4 -0
- data/app/views/entities/versions.js.rjs +1 -1
- data/app/views/layouts/_footer.html.haml +1 -1
- data/app/views/layouts/application.html.haml +3 -0
- data/app/views/leads/_contact.html.haml +1 -0
- data/app/views/leads/index.html.haml +1 -1
- data/app/views/leads/index.js.rjs +1 -1
- data/app/views/opportunities/_top_section.html.haml +4 -14
- data/app/views/opportunities/index.html.haml +1 -1
- data/app/views/opportunities/index.js.rjs +1 -1
- data/app/views/subscription_mailer/comment_notification.text.erb +7 -0
- data/app/views/{notifier → user_mailer}/password_reset_instructions.html.haml +0 -0
- data/config/application.rb +3 -1
- data/config/environments/development.rb +1 -1
- data/config/environments/test.rb +3 -0
- data/config/initializers/action_mailer.rb +8 -5
- data/config/initializers/cancan.rb +151 -0
- data/config/initializers/constants.rb +1 -0
- data/config/initializers/locale.rb +20 -0
- data/config/initializers/paper_trail.rb +4 -5
- data/config/initializers/relative_url_root.rb +0 -1
- data/config/initializers/squeel.rb +5 -0
- data/config/locales/cz_fat_free_crm.yml +3 -3
- data/config/locales/de.yml +2 -2
- data/config/locales/de_fat_free_crm.yml +651 -596
- data/config/locales/en-GB_fat_free_crm.yml +3 -3
- data/config/locales/en-US_fat_free_crm.yml +13 -3
- data/config/locales/es_fat_free_crm.yml +3 -3
- data/config/locales/fr-CA_fat_free_crm.yml +3 -3
- data/config/locales/fr_fat_free_crm.yml +3 -3
- data/config/locales/it_fat_free_crm.yml +3 -3
- data/config/locales/pl_fat_free_crm.yml +3 -3
- data/config/locales/pt-BR_fat_free_crm.yml +3 -3
- data/config/locales/ru_fat_free_crm.yml +3 -3
- data/config/locales/sv-SE_fat_free_crm.yml +3 -3
- data/config/locales/th_fat_free_crm.yml +3 -3
- data/config/routes.rb +10 -0
- data/config/settings.default.yml +29 -10
- data/config/unicorn.rb +4 -0
- data/db/migrate/20111201030535_add_field_groups_klass_name.rb +3 -1
- data/db/migrate/20120314080441_add_subscribed_users_to_entities.rb +23 -0
- data/db/migrate/20120405080727_change_subscribed_users_to_set.rb +24 -0
- data/db/migrate/20120405080742_change_further_subscribed_users_to_set.rb +27 -0
- data/db/migrate/20120413034923_add_index_on_versions_item_type.rb +5 -0
- data/db/schema.rb +109 -126
- data/fat_free_crm.gemspec +12 -18
- data/lib/fat_free_crm.rb +0 -1
- data/lib/fat_free_crm/core_ext/array.rb +1 -0
- data/lib/fat_free_crm/gem_dependencies.rb +1 -0
- data/lib/fat_free_crm/mail_processor/base.rb +226 -0
- data/lib/fat_free_crm/mail_processor/comment_replies.rb +86 -0
- data/lib/fat_free_crm/mail_processor/dropbox.rb +288 -0
- data/lib/fat_free_crm/permissions.rb +6 -19
- data/lib/fat_free_crm/renderers.rb +0 -8
- data/lib/fat_free_crm/tabs.rb +1 -1
- data/lib/fat_free_crm/version.rb +1 -1
- data/lib/plugins/country_select/lib/country_select.rb +2 -2
- data/lib/tasks/mail_processing.rake +60 -0
- data/spec/controllers/admin/users_controller_spec.rb +0 -2
- data/spec/controllers/{accounts_controller_spec.rb → entities/accounts_controller_spec.rb} +7 -9
- data/spec/controllers/{campaigns_controller_spec.rb → entities/campaigns_controller_spec.rb} +7 -7
- data/spec/controllers/{contacts_controller_spec.rb → entities/contacts_controller_spec.rb} +5 -9
- data/spec/controllers/{leads_controller_spec.rb → entities/leads_controller_spec.rb} +7 -9
- data/spec/controllers/{opportunities_controller_spec.rb → entities/opportunities_controller_spec.rb} +8 -15
- data/spec/controllers/tasks_controller_spec.rb +1 -5
- data/spec/controllers/users_controller_spec.rb +5 -9
- data/spec/factories/subscription_factories.rb +6 -0
- data/spec/lib/mail_processor/base_spec.rb +164 -0
- data/spec/lib/mail_processor/comment_replies_spec.rb +63 -0
- data/spec/lib/{dropbox_spec.rb → mail_processor/dropbox_spec.rb} +73 -181
- data/spec/lib/mail_processor/sample_emails/dropbox.rb +167 -0
- data/spec/mailers/subscription_mailer_spec.rb +17 -0
- data/spec/models/{base → entities}/account_contact_spec.rb +0 -0
- data/spec/models/{base → entities}/account_opportunity_spec.rb +0 -0
- data/spec/models/{base → entities}/account_spec.rb +4 -0
- data/spec/models/{base → entities}/campaign_spec.rb +4 -0
- data/spec/models/{base → entities}/contact_opportunity_spec.rb +0 -0
- data/spec/models/{base → entities}/contact_spec.rb +4 -0
- data/spec/models/{base → entities}/lead_spec.rb +4 -0
- data/spec/models/{base → entities}/opportunity_spec.rb +4 -0
- data/spec/models/polymorphic/comment_spec.rb +15 -0
- data/spec/models/{base → polymorphic}/task_spec.rb +124 -30
- data/spec/models/polymorphic/version_spec.rb +1 -1
- data/spec/shared/controllers.rb +5 -7
- data/spec/shared/models.rb +46 -0
- data/spec/spec_helper.rb +3 -4
- data/spec/support/mail_processor_mocks.rb +30 -0
- data/spec/support/uploaded_file.rb +3 -0
- data/spec/views/{common → application}/auto_complete.haml_spec.rb +1 -1
- data/vendor/assets/images/jquery-ui/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_flat_10_000000_40x100.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-icons_222222_256x240.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-icons_228ef1_256x240.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-icons_ef8c08_256x240.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-icons_ffd27a_256x240.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-icons_ffffff_256x240.png +0 -0
- data/vendor/assets/javascripts/textarea_autocomplete.js +605 -0
- data/vendor/assets/stylesheets/jquery-ui.custom.css.erb +565 -0
- metadata +234 -154
- data/config/locales/simple_form.en.yml +0 -24
- data/lib/fat_free_crm/dropbox.rb +0 -439
- 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
|
data/spec/shared/controllers.rb
CHANGED
@@ -21,9 +21,9 @@ module SharedControllerSpecs
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
it "should render
|
24
|
+
it "should render application/_auto_complete template" do
|
25
25
|
post :auto_complete, :auto_complete_query => @query
|
26
|
-
response.should render_template("
|
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("
|
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("
|
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("
|
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
|
-
|
data/spec/shared/models.rb
CHANGED
@@ -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
|
-
|
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
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -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'> </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(" "," ");
|
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," ");
|
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/>")+" </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);
|