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