message_train 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +51 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +83 -0
- data/Rakefile +71 -0
- data/VERSION +1 -0
- data/app/assets/javascripts/message_train.js +138 -0
- data/app/assets/stylesheets/message_train.scss +107 -0
- data/app/controllers/message_train/application_controller.rb +64 -0
- data/app/controllers/message_train/boxes_controller.rb +37 -0
- data/app/controllers/message_train/conversations_controller.rb +38 -0
- data/app/controllers/message_train/messages_controller.rb +74 -0
- data/app/controllers/message_train/participants_controller.rb +36 -0
- data/app/helpers/message_train/application_helper.rb +4 -0
- data/app/helpers/message_train/boxes_helper.rb +30 -0
- data/app/helpers/message_train/conversations_helper.rb +73 -0
- data/app/helpers/message_train/messages_helper.rb +58 -0
- data/app/models/message_train/attachment.rb +17 -0
- data/app/models/message_train/box.rb +268 -0
- data/app/models/message_train/conversation.rb +101 -0
- data/app/models/message_train/ignore.rb +18 -0
- data/app/models/message_train/message.rb +146 -0
- data/app/models/message_train/receipt.rb +70 -0
- data/app/views/application/404.html.haml +3 -0
- data/app/views/message_train/application/results.json.jbuilder +8 -0
- data/app/views/message_train/boxes/_dropdown_list.html.haml +5 -0
- data/app/views/message_train/boxes/_list_item.html.haml +5 -0
- data/app/views/message_train/boxes/_widget.html.haml +6 -0
- data/app/views/message_train/boxes/show.html.haml +47 -0
- data/app/views/message_train/conversations/_conversation.html.haml +9 -0
- data/app/views/message_train/conversations/_deleted_toggle.html.haml +1 -0
- data/app/views/message_train/conversations/_ignored_toggle.html.haml +4 -0
- data/app/views/message_train/conversations/_read_toggle.html.haml +4 -0
- data/app/views/message_train/conversations/_toggle.html.haml +1 -0
- data/app/views/message_train/conversations/_trashed_toggle.html.haml +4 -0
- data/app/views/message_train/conversations/show.html.haml +11 -0
- data/app/views/message_train/conversations/show.json.jbuilder +2 -0
- data/app/views/message_train/messages/_deleted_toggle.html.haml +1 -0
- data/app/views/message_train/messages/_form.html.haml +8 -0
- data/app/views/message_train/messages/_message.html.haml +35 -0
- data/app/views/message_train/messages/_read_toggle.html.haml +4 -0
- data/app/views/message_train/messages/_toggle.html.haml +1 -0
- data/app/views/message_train/messages/_trashed_toggle.html.haml +4 -0
- data/app/views/message_train/messages/edit.html.haml +2 -0
- data/app/views/message_train/messages/new.html.haml +2 -0
- data/app/views/message_train/messages/show.json.jbuilder +2 -0
- data/app/views/message_train/participants/_field.html.haml +4 -0
- data/app/views/message_train/participants/_participant.json.jbuilder +5 -0
- data/app/views/message_train/participants/index.json.jbuilder +3 -0
- data/app/views/message_train/participants/show.json.jbuilder +1 -0
- data/bin/rails +12 -0
- data/config/initializers/bootstrap_pager_config.rb +10 -0
- data/config/initializers/date_time.rb +5 -0
- data/config/locales/en.yml +50 -0
- data/config/routes.rb +11 -0
- data/db/migrate/20150721145319_create_message_train_conversations.rb +9 -0
- data/db/migrate/20150721160322_create_message_train_messages.rb +14 -0
- data/db/migrate/20150721161144_create_message_train_attachments.rb +10 -0
- data/db/migrate/20150721161940_create_message_train_receipts.rb +16 -0
- data/db/migrate/20150721163838_create_message_train_ignores.rb +11 -0
- data/lib/generators/message_train/install/install_generator.rb +30 -0
- data/lib/generators/message_train/install/templates/initializer.rb +10 -0
- data/lib/generators/message_train/utils.rb +16 -0
- data/lib/message_train/configuration.rb +35 -0
- data/lib/message_train/engine.rb +19 -0
- data/lib/message_train/localization.rb +31 -0
- data/lib/message_train/mixin.rb +83 -0
- data/lib/message_train/version.rb +7 -0
- data/lib/message_train.rb +20 -0
- data/lib/tasks/message_train_tasks.rake +0 -0
- data/message_train.gemspec +325 -0
- data/night_train.gemspec +274 -0
- data/spec/controllers/message_train/boxes_controller_spec.rb +82 -0
- data/spec/controllers/message_train/conversations_controller_spec.rb +86 -0
- data/spec/controllers/message_train/messages_controller_spec.rb +210 -0
- data/spec/controllers/message_train/participants_controller_spec.rb +35 -0
- data/spec/dummy/.rspec +2 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/files/message_train/attachments/1917-Boys_Race_Above-Wiki.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/ACD_Silverbarn's_Mayumi.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/ACD_obedience.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/A_Man_and_His_Dog.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Alaskan_Malamute_agility_a-frame.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/American_water_spaniel_02.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Aport_konkurs_rybnik.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Askerhytta.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Bambisj.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Cattle_dog_skating.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Dog's_Olympics_training_ground_-_geograph.org.uk_-_660324.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/DogShowHierarchy.png +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Dog_weight_pull.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Draghuntpostcard.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Esham_51.JPG +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/FastCourseExample.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Fotos_joel_141.JPG +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Galgo_Spanish_male_brindle.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Golden-retriever-carlos-bei-der-dummyarbeit.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Habavk.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Haie_rci.svg +1714 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Hanging_18.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/Hundewa.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/K9ProSports_AttackOnHandler.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/K9ProSports_heel.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/K9Pro_Civil_agitation.JPG +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/TrainingSchutzhundRetrieveOverWall.jpg +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/example.pdf +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/letterlegal5.doc +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/pdf-sample.pdf +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/sample.pdf +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/tips.doc +0 -0
- data/spec/dummy/app/assets/files/message_train/attachments/wd-spectools-word-sample-04.doc +0 -0
- data/spec/dummy/app/assets/images/logo.svg +140 -0
- data/spec/dummy/app/assets/javascripts/application.js +17 -0
- data/spec/dummy/app/assets/stylesheets/application.css.scss +35 -0
- data/spec/dummy/app/controllers/application_controller.rb +14 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/group.rb +19 -0
- data/spec/dummy/app/models/role.rb +10 -0
- data/spec/dummy/app/models/user.rb +23 -0
- data/spec/dummy/app/views/layouts/_top_navigation.html.haml +5 -0
- data/spec/dummy/app/views/layouts/application.html.haml +29 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config/application.rb +40 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/bootstrap_leather.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/devise.rb +265 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/message_train.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/paperclip.rb +2 -0
- data/spec/dummy/config/initializers/rolify.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/devise.en.yml +60 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +5 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config/unused/temporary_i18n_debugging.rb +12 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20150721140013_devise_create_users.rb +42 -0
- data/spec/dummy/db/migrate/20150721141009_rolify_create_roles.rb +19 -0
- data/spec/dummy/db/migrate/20150721141128_create_groups.rb +11 -0
- data/spec/dummy/db/migrate/20150721150307_add_display_name_column_to_users.rb +6 -0
- data/spec/dummy/db/migrate/20150724142846_create_message_train_conversations.night_train.rb +10 -0
- data/spec/dummy/db/migrate/20150724142847_create_message_train_messages.night_train.rb +15 -0
- data/spec/dummy/db/migrate/20150724142848_create_message_train_attachments.night_train.rb +11 -0
- data/spec/dummy/db/migrate/20150724142849_create_message_train_receipts.night_train.rb +17 -0
- data/spec/dummy/db/migrate/20150724142850_create_message_train_ignores.night_train.rb +12 -0
- data/spec/dummy/db/schema.rb +121 -0
- data/spec/dummy/db/seeds/conversations.seeds.rb +86 -0
- data/spec/dummy/db/seeds/development/conversations.seeds.rb +9 -0
- data/spec/dummy/db/seeds/groups.seeds.rb +9 -0
- data/spec/dummy/db/seeds/test/conversations.seeds.rb +9 -0
- data/spec/dummy/db/seeds/users.seeds.rb +15 -0
- data/spec/dummy/db/seeds.rb +0 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/capybara.html +193 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/attachment.rb +6 -0
- data/spec/factories/group.rb +20 -0
- data/spec/factories/message.rb +68 -0
- data/spec/factories/user.rb +8 -0
- data/spec/features/boxes_spec.rb +50 -0
- data/spec/features/conversations_spec.rb +36 -0
- data/spec/features/messages_spec.rb +53 -0
- data/spec/helpers/message_train/boxes_helper_spec.rb +44 -0
- data/spec/message_train_spec.rb +7 -0
- data/spec/models/group_spec.rb +23 -0
- data/spec/models/message_train/attachment_spec.rb +12 -0
- data/spec/models/message_train/box_spec.rb +195 -0
- data/spec/models/message_train/conversation_spec.rb +98 -0
- data/spec/models/message_train/ignore_spec.rb +27 -0
- data/spec/models/message_train/message_spec.rb +83 -0
- data/spec/models/message_train/receipt_spec.rb +73 -0
- data/spec/models/user_spec.rb +68 -0
- data/spec/rails_helper.rb +83 -0
- data/spec/spec_helper.rb +92 -0
- data/spec/support/controller_behaviors.rb +88 -0
- data/spec/support/controller_macros.rb +13 -0
- data/spec/support/conversations.rb +10 -0
- data/spec/support/factory_girl.rb +3 -0
- data/spec/support/feature_behaviors.rb +62 -0
- data/spec/support/groups.rb +3 -0
- data/spec/support/loaded_site.rb +5 -0
- data/spec/support/shared_connection.rb +13 -0
- data/spec/support/users.rb +6 -0
- data/spec/support/utilities.rb +8 -0
- data/vendor/assets/javascripts/bootstrap-tags.js +623 -0
- data/vendor/assets/stylesheets/bootstrap-tags.css +78 -0
- metadata +608 -0
@@ -0,0 +1,146 @@
|
|
1
|
+
module MessageTrain
|
2
|
+
class Message < ActiveRecord::Base
|
3
|
+
# Serializations
|
4
|
+
serialize :recipients_to_save, Hash
|
5
|
+
|
6
|
+
# Relationships
|
7
|
+
belongs_to :conversation
|
8
|
+
belongs_to :sender, polymorphic: true
|
9
|
+
has_many :attachments
|
10
|
+
has_many :receipts
|
11
|
+
|
12
|
+
# Validations
|
13
|
+
validates_presence_of :sender, :subject
|
14
|
+
|
15
|
+
# Callbacks
|
16
|
+
before_create :create_conversation_if_blank
|
17
|
+
after_create :generate_sender_receipt
|
18
|
+
after_save :generate_receipts_or_set_draft
|
19
|
+
after_save :set_conversation_subject_if_alone
|
20
|
+
|
21
|
+
# Scopes
|
22
|
+
default_scope { order('updated_at DESC') }
|
23
|
+
scope :filter_by_receipt_method_ids, ->(receipt_method, participant) {
|
24
|
+
all.collect { |x| x.receipts.send(receipt_method, participant).message_ids }.flatten
|
25
|
+
}
|
26
|
+
scope :filter_by_receipt_method, ->(receipt_method, participant) {
|
27
|
+
where('id IN (?)', filter_by_receipt_method_ids(receipt_method, participant))
|
28
|
+
}
|
29
|
+
scope :filter_out_by_receipt_method, ->(receipt_method, participant) {
|
30
|
+
where('NOT(id IN (?))', filter_by_receipt_method_ids(receipt_method, participant))
|
31
|
+
}
|
32
|
+
scope :ready, -> { where('draft = ?', false) }
|
33
|
+
scope :drafts, -> { where('draft = ?', true) }
|
34
|
+
scope :by, ->(participant) { where('sender_type = ? AND sender_id = ?', participant.class.name, participant.id) }
|
35
|
+
scope :drafts_by, ->(participant) { drafts.by(participant) }
|
36
|
+
|
37
|
+
def mark(mark_to_set, participant)
|
38
|
+
receipt_to_mark = receipts.for(participant).first
|
39
|
+
unless receipt_to_mark.nil?
|
40
|
+
receipt_to_mark.mark(mark_to_set)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.mark(mark_to_set, participant)
|
45
|
+
all.each do |message|
|
46
|
+
message.mark(mark_to_set, participant)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def recipients
|
51
|
+
receipts.recipient_receipt.collect { |x| x.recipient }
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.conversation_ids
|
55
|
+
all.collect { |y| y.conversation_id }
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.conversations
|
59
|
+
MessageTrain::Conversation.where('id IN (?)', conversation_ids )
|
60
|
+
end
|
61
|
+
|
62
|
+
def method_missing(method_sym, *arguments, &block)
|
63
|
+
# the first argument is a Symbol, so you need to_s it if you want to pattern match
|
64
|
+
if method_sym.to_s =~ /^is_((.*)_(by|to|for))\?$/
|
65
|
+
!receipts.send($1.to_sym, arguments.first).empty?
|
66
|
+
elsif method_sym.to_s =~ /^mark_(.*)_for$/
|
67
|
+
receipts.for(arguments.first).first.mark($1.to_sym)
|
68
|
+
else
|
69
|
+
super
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.method_missing(method_sym, *arguments, &block)
|
74
|
+
# the first argument is a Symbol, so you need to_s it if you want to pattern match
|
75
|
+
if method_sym.to_s =~ /^with_(.*_(by|to|for))$/
|
76
|
+
filter_by_receipt_method($1.to_sym, arguments.first)
|
77
|
+
else
|
78
|
+
super
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# It's important to know Object defines respond_to to take two parameters: the method to check, and whether to include private methods
|
83
|
+
# http://www.ruby-doc.org/core/classes/Object.html#M000333
|
84
|
+
def respond_to?(method_sym, include_private = false)
|
85
|
+
if method_sym.to_s =~ /^is_.*_(by|to|for)\?$/ || method_sym.to_s =~ /^mark_.*_for\?$/
|
86
|
+
true
|
87
|
+
else
|
88
|
+
super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.respond_to?(method_sym, include_private = false)
|
93
|
+
if method_sym.to_s =~ /^.*_(by|to|for)$/
|
94
|
+
true
|
95
|
+
else
|
96
|
+
super
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
def create_conversation_if_blank
|
102
|
+
if conversation.nil?
|
103
|
+
self.conversation = Conversation.create(subject: subject)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def generate_sender_receipt
|
108
|
+
receipts.first_or_create!(recipient_type: sender.class.name, recipient_id: sender.id, sender: true)
|
109
|
+
end
|
110
|
+
|
111
|
+
def generate_receipts_or_set_draft
|
112
|
+
unless draft
|
113
|
+
recipients_to_save.each do |table, slugs|
|
114
|
+
model_name = table.classify
|
115
|
+
model = model_name.constantize
|
116
|
+
|
117
|
+
slugs.split(',').each do |slug|
|
118
|
+
slug = slug.strip
|
119
|
+
slug_column = MessageTrain.configuration.slug_columns[table.to_sym] || :slug
|
120
|
+
if model.exists?(slug_column => slug)
|
121
|
+
recipient = model.find_by(slug_column => slug)
|
122
|
+
unless conversation.is_ignored?(recipient)
|
123
|
+
receipts.create!(recipient_type: model_name, recipient_id: recipient.id)
|
124
|
+
end
|
125
|
+
else
|
126
|
+
errors.add :recipients_to_save, :name_not_found.l(name: slug)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
reload
|
131
|
+
if recipients.empty?
|
132
|
+
update_attribute :draft, true
|
133
|
+
else
|
134
|
+
conversation.update_attribute(:updated_at, Time.now)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def set_conversation_subject_if_alone
|
140
|
+
if conversation.messages.count == 1
|
141
|
+
conversation.update_attribute(:subject, subject)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module MessageTrain
|
2
|
+
class Receipt < ActiveRecord::Base
|
3
|
+
belongs_to :recipient, polymorphic: true
|
4
|
+
belongs_to :message
|
5
|
+
validates_presence_of :recipient, :message
|
6
|
+
|
7
|
+
default_scope { order("created_at DESC") }
|
8
|
+
scope :sender_receipt, -> { where('sender = ?', true) }
|
9
|
+
scope :recipient_receipt, -> { where('sender = ?', false) }
|
10
|
+
scope :by, ->(sender) { sender_receipt.for(sender) }
|
11
|
+
scope :for, ->(recipient) { where('recipient_type = ? AND recipient_id = ?', recipient.class.name, recipient.id) }
|
12
|
+
scope :to, ->(recipient) { recipient_receipt.for(recipient) }
|
13
|
+
scope :trashed, ->(setting = true) { where('marked_trash = ?', setting) }
|
14
|
+
scope :read, ->(setting = true) { where('marked_read = ?', setting) }
|
15
|
+
scope :deleted, ->(setting = true) { where('marked_deleted = ?', setting) }
|
16
|
+
|
17
|
+
def mark(mark_to_set)
|
18
|
+
if mark_to_set.to_s =~ /^un/
|
19
|
+
setting = false
|
20
|
+
suffix = mark_to_set.to_s.gsub(/^un/, '')
|
21
|
+
else
|
22
|
+
setting = true
|
23
|
+
suffix = mark_to_set.to_s
|
24
|
+
end
|
25
|
+
column = "marked_#{suffix}".to_sym
|
26
|
+
update_attribute(column, setting)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.message_ids
|
30
|
+
all.collect { |y| y.message_id }
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.messages
|
34
|
+
MessageTrain::Message.where('id IN (?)', message_ids )
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.conversation_ids
|
38
|
+
all.collect { |y| y.message.conversation_id }
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.conversations
|
42
|
+
MessageTrain::Conversation.where('id IN (?)', conversation_ids )
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.method_missing(method_sym, *arguments, &block)
|
46
|
+
# the first argument is a Symbol, so you need to_s it if you want to pattern match
|
47
|
+
if method_sym.to_s =~ /^receipts_(by|to|for)$/
|
48
|
+
send($1.to_sym, arguments.first)
|
49
|
+
elsif method_sym.to_s =~ /^(.*)_(by|to|for)$/
|
50
|
+
send($1.to_sym).send($2.to_sym, arguments.first)
|
51
|
+
elsif method_sym.to_s =~ /^mark_(.*)$/
|
52
|
+
mark($1.to_sym)
|
53
|
+
elsif method_sym.to_s =~ /^un(.*)$/
|
54
|
+
send($1.to_sym, false)
|
55
|
+
else
|
56
|
+
super
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# It's important to know Object defines respond_to to take two parameters: the method to check, and whether to include private methods
|
61
|
+
# http://www.ruby-doc.org/core/classes/Object.html#M000333
|
62
|
+
def self.respond_to?(method_sym, include_private = false)
|
63
|
+
if method_sym.to_s =~ /^(.*)_(by|to|for)$/ || method_sym.to_s =~ /^(un|mark_)(.*)$/
|
64
|
+
true
|
65
|
+
else
|
66
|
+
super
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
- add_title @box.title
|
2
|
+
= form_tag message_train.box_path(@box.division), method: :put, remote: true, id: 'box', data: { type: :json } do
|
3
|
+
- unless @conversations.empty?
|
4
|
+
#box-actions
|
5
|
+
.btn-group.check-all
|
6
|
+
%button.btn.btn-default{ type: 'button' }
|
7
|
+
= check_box_tag 'check_all', 'all', false
|
8
|
+
%button.btn.btn-default.dropdown-toggle{ aria: { expanded: 'false', haspopup: 'true' }, data: { toggle: 'dropdown'}, type: 'button' }
|
9
|
+
%span.caret
|
10
|
+
%span.sr-only= :toggle_dropdown.l
|
11
|
+
%ul.dropdown-menu
|
12
|
+
%li
|
13
|
+
= link_to :all.l, '#0', class: 'check', data: { selector: '.message_train_conversation'}
|
14
|
+
= link_to :none.l, '#0', class: 'check', data: { selector: ''}
|
15
|
+
= link_to :read.l, '#0', class: 'check', data: { selector: '.message_train_conversation.read'}
|
16
|
+
= link_to :unread.l, '#0', class: 'check', data: { selector: '.message_train_conversation.unread'}
|
17
|
+
.btn-group
|
18
|
+
%button.btn.btn-info.dropdown-toggle{ aria: { expanded: 'false', haspopup: 'true' }, data: { toggle: 'dropdown'}, type: 'button' }
|
19
|
+
= :mark.l
|
20
|
+
%span.caret
|
21
|
+
%ul.dropdown-menu
|
22
|
+
%li
|
23
|
+
= link_to :mark_as_name.l(name: :read.l), '#0', class: 'mark', id: 'mark-read', data: { mark: 'read'}
|
24
|
+
%li
|
25
|
+
= link_to :mark_as_name.l(name: :unread.l), '#0', class: 'mark', id: 'mark-unread', data: { mark: 'unread'}
|
26
|
+
- if @box.division == :trash
|
27
|
+
%li
|
28
|
+
= link_to :mark_as_name.l(name: :untrashed.l), '#0', class: 'mark', id: 'mark-untrash', data: { mark: 'untrash'}
|
29
|
+
%li.divider{ role: 'separator' }
|
30
|
+
%li
|
31
|
+
= link_to :mark_as_name.l(name: :deleted.l), '#0', class: 'mark', id: 'mark-deleted', data: { mark: 'deleted'}
|
32
|
+
- else
|
33
|
+
%li
|
34
|
+
= link_to :mark_as_name.l(name: :trashed.l), '#0', class: 'mark', id: 'mark-trash', data: { mark: 'trash'}
|
35
|
+
%li.divider{ role: 'separator' }
|
36
|
+
- if @box.division == :ignored
|
37
|
+
%li
|
38
|
+
= link_to :mark_as_name.l(name: :unignored.l), '#0', class: 'mark', id: 'mark-unignored', data: { mark: 'unignore'}
|
39
|
+
- else
|
40
|
+
%li
|
41
|
+
= link_to :mark_as_name.l(name: :ignored.l), '#0', class: 'mark', id: 'mark-ignored', data: { mark: 'ignore'}
|
42
|
+
= link_to :compose.l, message_train.new_box_message_path(@box.division), class: 'btn btn-primary'
|
43
|
+
%span#spinner.hide= icon 'refresh spinning'
|
44
|
+
= hidden_field_tag :mark_to_set
|
45
|
+
%table#message_train_conversations.table.table-condensed
|
46
|
+
= render @conversations
|
47
|
+
= paginate @conversations
|
@@ -0,0 +1,9 @@
|
|
1
|
+
%tr.message_train_conversation{ class: conversation_class(@box, conversation), id: 'message_train_conversation_' + conversation.id.to_s }
|
2
|
+
%td.col-xs-1= check_box_tag "objects[conversations][#{conversation.id.to_s}]", conversation.id
|
3
|
+
%td.col-xs-2= link_to conversation_senders(conversation), message_train.box_conversation_path(@box.division, conversation)
|
4
|
+
%td.col-xs-8= link_to conversation.subject, message_train.box_conversation_path(@box.division, conversation)
|
5
|
+
%td.col-xs-1.conversation-actions
|
6
|
+
= conversation_read_toggle conversation
|
7
|
+
= conversation_trashed_toggle conversation
|
8
|
+
- if @box.division == :trash
|
9
|
+
= conversation_deleted_toggle conversation
|
@@ -0,0 +1 @@
|
|
1
|
+
= conversation_toggle conversation, 'remove', :deleted, :put, :mark_as_name.l(name: :deleted.l), data: { confirm: :delete_forever_this_cannot_be_undone.l }
|
@@ -0,0 +1,4 @@
|
|
1
|
+
- if conversation.is_ignored?(@box.parent)
|
2
|
+
= conversation_toggle conversation, 'volume-up', :unignore, :delete, :mark_as_name.l(name: :unignored.l)
|
3
|
+
- else
|
4
|
+
= conversation_toggle conversation, 'volume-off', :ignore, :delete, :mark_as_name.l(name: :ignored.l), data: { confirm: :are_you_sure.l}
|
@@ -0,0 +1 @@
|
|
1
|
+
= icon_link_to icon, '', message_train.box_path(@box.division, objects: { 'conversations' => {conversation.id.to_s => conversation.id.to_s}}, mark_to_set: mark_to_set, format: :json), options
|
@@ -0,0 +1,4 @@
|
|
1
|
+
- if conversation.includes_untrashed_for?(@box.parent)
|
2
|
+
= conversation_toggle conversation, 'trash', :trash, :put, :mark_as_name.l(name: :trashed.l), data: { confirm: :are_you_sure.l}
|
3
|
+
- else
|
4
|
+
= conversation_toggle conversation, 'inbox', :untrash, :put, :mark_as_name.l(name: :untrashed.l)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
- add_title @conversation.subject
|
2
|
+
- add_subtitle :started_at_time.l(time: @conversation.created_at)
|
3
|
+
|
4
|
+
#box
|
5
|
+
#conversation-actions.pull-right
|
6
|
+
.btn.btn-default.btn-lg= conversation_ignored_toggle(@conversation)
|
7
|
+
#message_train_conversation.clear-both
|
8
|
+
- unless @messages.empty?
|
9
|
+
#message_train_messages.messages.panel-group{aria: { multiselectable: 'true' }, role: "tablist"}
|
10
|
+
= render @messages
|
11
|
+
= paginate @messages
|
@@ -0,0 +1 @@
|
|
1
|
+
= message_toggle message, 'remove', :deleted, :mark_as_name.l(name: :deleted.l)
|
@@ -0,0 +1,8 @@
|
|
1
|
+
- resource = message.new_record? ? message_train.box_messages_path(@box) : message_train.box_message_path(@box, message)
|
2
|
+
= bootstrap_form_for message, url: resource do |f|
|
3
|
+
- MessageTrain.configuration.recipient_tables.each do |table_sym|
|
4
|
+
= render partial: 'message_train/participants/field', locals: { message: message, field_name: table_sym }
|
5
|
+
= f.text_field :subject
|
6
|
+
= f.text_area :body
|
7
|
+
= f.check_box :draft
|
8
|
+
= f.submit :send.l
|
@@ -0,0 +1,35 @@
|
|
1
|
+
- if message.draft
|
2
|
+
.panel.panel-default{ class: message_class(@box, message), id: "message_train_message_#{message.id}", data: { mark_path: message_train.box_conversation_path(@box.division, id: message.conversation_id, objects: { 'messages' => {message.id.to_s => message.id.to_s} }) } }
|
3
|
+
.panel-heading{role: "tab", id: "message_#{message.id}_heading"}
|
4
|
+
.message-actions.pull-right
|
5
|
+
= message_read_toggle message
|
6
|
+
= message_trashed_toggle message
|
7
|
+
- if @box.division == :trash
|
8
|
+
= message_deleted_toggle message
|
9
|
+
%h4.panel-title
|
10
|
+
= link_to "#message_#{message.id}_collapse", aria: { controls: "message_#{message.id}_collapse", expanded: 'true' }, data: { parent: '#accordion', toggle: 'collapse' }, role: "button" do
|
11
|
+
%span.caret
|
12
|
+
= :message_draft.l
|
13
|
+
%small= message.created_at
|
14
|
+
.panel-collapse.collapse.in{aria: { labelledby: "message_#{message.id}_heading" }, role: "tabpanel", id: "message_#{message.id}_collapse"}
|
15
|
+
.panel-body
|
16
|
+
= render partial: 'message_train/messages/form', locals: { message: message }
|
17
|
+
|
18
|
+
- else
|
19
|
+
.panel.panel-default{ class: message_class(@box, message), id: "message_train_message_#{message.id}", data: { mark_path: message_train.box_conversation_path(@box.division, id: message.conversation_id, objects: { 'messages' => {message.id.to_s => message.id.to_s} }) } }
|
20
|
+
.panel-heading{role: "tab", id: "message_#{message.id}_heading"}
|
21
|
+
.message-actions.pull-right
|
22
|
+
= message_read_toggle message
|
23
|
+
= message_trashed_toggle message
|
24
|
+
- if @box.division == :trash
|
25
|
+
= message_deleted_toggle message
|
26
|
+
%h4.panel-title
|
27
|
+
= link_to "#message_#{message.id}_collapse", aria: { controls: "message_#{message.id}_collapse", expanded: 'true' }, data: { parent: '#accordion', toggle: 'collapse' }, role: "button" do
|
28
|
+
%span.caret
|
29
|
+
= :from_sender.l(sender: message.sender.display_name)
|
30
|
+
%small= message.created_at
|
31
|
+
.panel-collapse.collapse.in{aria: { labelledby: "message_#{message.id}_heading" }, role: "tabpanel", id: "message_#{message.id}_collapse"}
|
32
|
+
.panel-body
|
33
|
+
%p= :to_recipient.l(recipient: message_recipients(message))
|
34
|
+
%p.lead= message.subject
|
35
|
+
= message.body
|
@@ -0,0 +1 @@
|
|
1
|
+
= icon_link_to icon, '', message_train.box_path(@box.division, objects: { 'messages' => {message.id.to_s => message.id.to_s}}, mark_to_set: mark_to_set, format: :json), options
|
@@ -0,0 +1,4 @@
|
|
1
|
+
.form-group
|
2
|
+
%label.control-label= "#{field_name.to_s.singularize.humanize.titleize} Recipients"
|
3
|
+
.recipient-input.tag-list{ data: { field_name: "message[recipients_to_save][#{field_name}]" }, id: "message_recipients_to_save_#{field_name}" }
|
4
|
+
.tag-data= message.recipients_to_save[field_name]
|
@@ -0,0 +1 @@
|
|
1
|
+
json.participant render partial: 'participant', locals: { participant: @participant }
|
data/bin/rails
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
|
3
|
+
|
4
|
+
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
5
|
+
ENGINE_PATH = File.expand_path('../../lib/message_train/engine', __FILE__)
|
6
|
+
|
7
|
+
# Set up gems listed in the Gemfile.
|
8
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
9
|
+
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
10
|
+
|
11
|
+
require 'rails/all'
|
12
|
+
require 'rails/engine/commands'
|
@@ -0,0 +1,10 @@
|
|
1
|
+
BootstrapPager.configure do |config|
|
2
|
+
config.default_per_page = 10
|
3
|
+
# config.max_per_page = nil
|
4
|
+
# config.window = 4
|
5
|
+
# config.outer_window = 0
|
6
|
+
# config.left = 0
|
7
|
+
# config.right = 0
|
8
|
+
# config.page_method_name = :page
|
9
|
+
# config.param_name = :page
|
10
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
en:
|
2
|
+
activerecord:
|
3
|
+
attributes:
|
4
|
+
'message_train/message':
|
5
|
+
recipients_to_save: 'Recipient'
|
6
|
+
access_to_conversation_id_denied: "Access to Conversation %{id} denied"
|
7
|
+
access_to_message_id_denied: "Access to Message %{id} denied"
|
8
|
+
access_to_receipt_id_denied: "Access to Receipt %{id} denied"
|
9
|
+
all: 'All'
|
10
|
+
are_you_sure: 'Are you sure?'
|
11
|
+
box_title_in: 'Inbox'
|
12
|
+
box_title_sent: 'Sent Messages'
|
13
|
+
box_title_all: 'All Messages'
|
14
|
+
box_title_drafts: 'Drafts'
|
15
|
+
box_title_trash: 'Trash'
|
16
|
+
box_title_ignored: 'Ignored Conversations'
|
17
|
+
cannot_mark_type: "Cannot mark %{type}"
|
18
|
+
cannot_ignore_type: "Cannot ignore %{type}"
|
19
|
+
check_all: 'Check All'
|
20
|
+
class_id_not_found_in_box: "%{class} %{id} not found in box"
|
21
|
+
conversation_id_not_found: "Conversation %{id} not found"
|
22
|
+
compose: 'Compose'
|
23
|
+
delete_forever_this_cannot_be_undone: 'Delete forever? This cannot be undone.'
|
24
|
+
deleted: 'Deleted'
|
25
|
+
from_sender: "From: %{sender}"
|
26
|
+
ignored: 'Ignored'
|
27
|
+
mark: 'Mark'
|
28
|
+
mark_as_name: "Mark as %{name}" #e.g. Read, Unread etc.
|
29
|
+
message_draft: 'Message Draft'
|
30
|
+
message_saved_as_draft: 'Message saved as draft.'
|
31
|
+
message_sent: 'Message sent.'
|
32
|
+
messages: 'Messages'
|
33
|
+
name_not_found: "%{name} not found"
|
34
|
+
none: 'None'
|
35
|
+
nothing_to_do: 'Nothing to do'
|
36
|
+
read: 'Read'
|
37
|
+
recipient_not_found: "Recipient Not Found"
|
38
|
+
recipients: 'Recipients'
|
39
|
+
send: 'Send'
|
40
|
+
started_at_time: "Started at %{time}"
|
41
|
+
table_recipients: "%{table} Recipients"
|
42
|
+
to_recipient: 'To: %{recipient}'
|
43
|
+
toggle_dropdown: 'Toggle Dropdown'
|
44
|
+
trashed: 'Trashed'
|
45
|
+
unignored: 'Unignored'
|
46
|
+
unread: 'Unread'
|
47
|
+
untrashed: 'Untrashed'
|
48
|
+
update_successful: 'Update successful'
|
49
|
+
wrong_number_of_arguments_for_box_expected_right_got_wrong: "Wrong number of arguments for box (expected %{right}, got %{wrong})"
|
50
|
+
you_must_sign_in_or_sign_up_to_continue: 'You must sign in or sign up to continue.'
|
data/config/routes.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
MessageTrain::Engine.routes.draw do
|
2
|
+
authenticated MessageTrain.configuration.user_route_authentication_method do
|
3
|
+
resources :boxes, path: 'box', param: :division, only: [:show, :update, :destroy] do
|
4
|
+
resources :conversations, only: [:show, :update, :destroy]
|
5
|
+
resources :messages, except: [:index, :destroy]
|
6
|
+
resources :participants, only: [:index, :show]
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
match '/box(/*path)', to: redirect(MessageTrain.configuration.user_sign_in_path), via: [:get, :put, :delete]
|
11
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateMessageTrainMessages < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :message_train_messages do |t|
|
4
|
+
t.references :conversation, index: true, foreign_key: true
|
5
|
+
t.references :sender, polymorphic: true, index: true
|
6
|
+
t.text :recipients_to_save
|
7
|
+
t.string :subject
|
8
|
+
t.text :body
|
9
|
+
t.boolean :draft, default: false
|
10
|
+
|
11
|
+
t.timestamps null: false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CreateMessageTrainReceipts < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :message_train_receipts do |t|
|
4
|
+
t.references :recipient, polymorphic: true
|
5
|
+
t.references :message, index: true, foreign_key: true
|
6
|
+
t.boolean :marked_read, default: false
|
7
|
+
t.boolean :marked_trash, default: false
|
8
|
+
t.boolean :marked_deleted, default: false
|
9
|
+
t.boolean :sender, default: false
|
10
|
+
|
11
|
+
t.timestamps null: false
|
12
|
+
end
|
13
|
+
|
14
|
+
add_index :message_train_receipts, [:recipient_type, :recipient_id], name: :index_message_train_receipts_on_recipient
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class CreateMessageTrainIgnores < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :message_train_ignores do |t|
|
4
|
+
t.references :participant, polymorphic: true
|
5
|
+
t.references :conversation, index: true, foreign_key: true
|
6
|
+
|
7
|
+
t.timestamps null: false
|
8
|
+
end
|
9
|
+
add_index :message_train_ignores, [:participant_type, :participant_id], name: 'participant_index'
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module MessageTrain
|
2
|
+
class InstallGenerator < Rails::Generators::Base
|
3
|
+
source_root File.expand_path("../templates", __FILE__)
|
4
|
+
require File.expand_path('../../utils', __FILE__)
|
5
|
+
include Generators::Utils
|
6
|
+
include Rails::Generators::Migration
|
7
|
+
|
8
|
+
def hello
|
9
|
+
output "MessageTrain Installer will now install itself", :magenta
|
10
|
+
end
|
11
|
+
|
12
|
+
# all public methods in here will be run in order
|
13
|
+
|
14
|
+
def add_initializer
|
15
|
+
output "First, you'll need an initializer. This is where you put your configuration options.", :magenta
|
16
|
+
template "initializer.rb", "config/initializers/message_train.rb"
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_migrations
|
20
|
+
output "Next come migrations.", :magenta
|
21
|
+
rake 'message_train:install:migrations'
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_route
|
25
|
+
output "Adding MessageTrain to your routes.rb file", :magenta
|
26
|
+
gsub_file "config/routes.rb", /authenticated :[a-z_0-9]+ do\s+mount MessageTrain::Engine => '\/.*', :as => 'message_train'\s+end/, ''
|
27
|
+
route("authenticated :user do\n\t\tmount MessageTrain::Engine => '/', :as => 'message_train'\n\tend")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|