message_train 0.1.0

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.
Files changed (214) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.rspec +1 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +7 -0
  7. data/Gemfile +51 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.rdoc +83 -0
  10. data/Rakefile +71 -0
  11. data/VERSION +1 -0
  12. data/app/assets/javascripts/message_train.js +138 -0
  13. data/app/assets/stylesheets/message_train.scss +107 -0
  14. data/app/controllers/message_train/application_controller.rb +64 -0
  15. data/app/controllers/message_train/boxes_controller.rb +37 -0
  16. data/app/controllers/message_train/conversations_controller.rb +38 -0
  17. data/app/controllers/message_train/messages_controller.rb +74 -0
  18. data/app/controllers/message_train/participants_controller.rb +36 -0
  19. data/app/helpers/message_train/application_helper.rb +4 -0
  20. data/app/helpers/message_train/boxes_helper.rb +30 -0
  21. data/app/helpers/message_train/conversations_helper.rb +73 -0
  22. data/app/helpers/message_train/messages_helper.rb +58 -0
  23. data/app/models/message_train/attachment.rb +17 -0
  24. data/app/models/message_train/box.rb +268 -0
  25. data/app/models/message_train/conversation.rb +101 -0
  26. data/app/models/message_train/ignore.rb +18 -0
  27. data/app/models/message_train/message.rb +146 -0
  28. data/app/models/message_train/receipt.rb +70 -0
  29. data/app/views/application/404.html.haml +3 -0
  30. data/app/views/message_train/application/results.json.jbuilder +8 -0
  31. data/app/views/message_train/boxes/_dropdown_list.html.haml +5 -0
  32. data/app/views/message_train/boxes/_list_item.html.haml +5 -0
  33. data/app/views/message_train/boxes/_widget.html.haml +6 -0
  34. data/app/views/message_train/boxes/show.html.haml +47 -0
  35. data/app/views/message_train/conversations/_conversation.html.haml +9 -0
  36. data/app/views/message_train/conversations/_deleted_toggle.html.haml +1 -0
  37. data/app/views/message_train/conversations/_ignored_toggle.html.haml +4 -0
  38. data/app/views/message_train/conversations/_read_toggle.html.haml +4 -0
  39. data/app/views/message_train/conversations/_toggle.html.haml +1 -0
  40. data/app/views/message_train/conversations/_trashed_toggle.html.haml +4 -0
  41. data/app/views/message_train/conversations/show.html.haml +11 -0
  42. data/app/views/message_train/conversations/show.json.jbuilder +2 -0
  43. data/app/views/message_train/messages/_deleted_toggle.html.haml +1 -0
  44. data/app/views/message_train/messages/_form.html.haml +8 -0
  45. data/app/views/message_train/messages/_message.html.haml +35 -0
  46. data/app/views/message_train/messages/_read_toggle.html.haml +4 -0
  47. data/app/views/message_train/messages/_toggle.html.haml +1 -0
  48. data/app/views/message_train/messages/_trashed_toggle.html.haml +4 -0
  49. data/app/views/message_train/messages/edit.html.haml +2 -0
  50. data/app/views/message_train/messages/new.html.haml +2 -0
  51. data/app/views/message_train/messages/show.json.jbuilder +2 -0
  52. data/app/views/message_train/participants/_field.html.haml +4 -0
  53. data/app/views/message_train/participants/_participant.json.jbuilder +5 -0
  54. data/app/views/message_train/participants/index.json.jbuilder +3 -0
  55. data/app/views/message_train/participants/show.json.jbuilder +1 -0
  56. data/bin/rails +12 -0
  57. data/config/initializers/bootstrap_pager_config.rb +10 -0
  58. data/config/initializers/date_time.rb +5 -0
  59. data/config/locales/en.yml +50 -0
  60. data/config/routes.rb +11 -0
  61. data/db/migrate/20150721145319_create_message_train_conversations.rb +9 -0
  62. data/db/migrate/20150721160322_create_message_train_messages.rb +14 -0
  63. data/db/migrate/20150721161144_create_message_train_attachments.rb +10 -0
  64. data/db/migrate/20150721161940_create_message_train_receipts.rb +16 -0
  65. data/db/migrate/20150721163838_create_message_train_ignores.rb +11 -0
  66. data/lib/generators/message_train/install/install_generator.rb +30 -0
  67. data/lib/generators/message_train/install/templates/initializer.rb +10 -0
  68. data/lib/generators/message_train/utils.rb +16 -0
  69. data/lib/message_train/configuration.rb +35 -0
  70. data/lib/message_train/engine.rb +19 -0
  71. data/lib/message_train/localization.rb +31 -0
  72. data/lib/message_train/mixin.rb +83 -0
  73. data/lib/message_train/version.rb +7 -0
  74. data/lib/message_train.rb +20 -0
  75. data/lib/tasks/message_train_tasks.rake +0 -0
  76. data/message_train.gemspec +325 -0
  77. data/night_train.gemspec +274 -0
  78. data/spec/controllers/message_train/boxes_controller_spec.rb +82 -0
  79. data/spec/controllers/message_train/conversations_controller_spec.rb +86 -0
  80. data/spec/controllers/message_train/messages_controller_spec.rb +210 -0
  81. data/spec/controllers/message_train/participants_controller_spec.rb +35 -0
  82. data/spec/dummy/.rspec +2 -0
  83. data/spec/dummy/Rakefile +6 -0
  84. data/spec/dummy/app/assets/files/message_train/attachments/1917-Boys_Race_Above-Wiki.jpg +0 -0
  85. data/spec/dummy/app/assets/files/message_train/attachments/ACD_Silverbarn's_Mayumi.jpg +0 -0
  86. data/spec/dummy/app/assets/files/message_train/attachments/ACD_obedience.jpg +0 -0
  87. data/spec/dummy/app/assets/files/message_train/attachments/A_Man_and_His_Dog.jpg +0 -0
  88. data/spec/dummy/app/assets/files/message_train/attachments/Alaskan_Malamute_agility_a-frame.jpg +0 -0
  89. data/spec/dummy/app/assets/files/message_train/attachments/American_water_spaniel_02.jpg +0 -0
  90. data/spec/dummy/app/assets/files/message_train/attachments/Aport_konkurs_rybnik.jpg +0 -0
  91. data/spec/dummy/app/assets/files/message_train/attachments/Askerhytta.jpg +0 -0
  92. data/spec/dummy/app/assets/files/message_train/attachments/Bambisj.jpg +0 -0
  93. data/spec/dummy/app/assets/files/message_train/attachments/Cattle_dog_skating.jpg +0 -0
  94. data/spec/dummy/app/assets/files/message_train/attachments/Dog's_Olympics_training_ground_-_geograph.org.uk_-_660324.jpg +0 -0
  95. data/spec/dummy/app/assets/files/message_train/attachments/DogShowHierarchy.png +0 -0
  96. data/spec/dummy/app/assets/files/message_train/attachments/Dog_weight_pull.jpg +0 -0
  97. data/spec/dummy/app/assets/files/message_train/attachments/Draghuntpostcard.jpg +0 -0
  98. data/spec/dummy/app/assets/files/message_train/attachments/Esham_51.JPG +0 -0
  99. data/spec/dummy/app/assets/files/message_train/attachments/FastCourseExample.jpg +0 -0
  100. data/spec/dummy/app/assets/files/message_train/attachments/Fotos_joel_141.JPG +0 -0
  101. data/spec/dummy/app/assets/files/message_train/attachments/Galgo_Spanish_male_brindle.jpg +0 -0
  102. data/spec/dummy/app/assets/files/message_train/attachments/Golden-retriever-carlos-bei-der-dummyarbeit.jpg +0 -0
  103. data/spec/dummy/app/assets/files/message_train/attachments/Habavk.jpg +0 -0
  104. data/spec/dummy/app/assets/files/message_train/attachments/Haie_rci.svg +1714 -0
  105. data/spec/dummy/app/assets/files/message_train/attachments/Hanging_18.jpg +0 -0
  106. data/spec/dummy/app/assets/files/message_train/attachments/Hundewa.jpg +0 -0
  107. data/spec/dummy/app/assets/files/message_train/attachments/K9ProSports_AttackOnHandler.jpg +0 -0
  108. data/spec/dummy/app/assets/files/message_train/attachments/K9ProSports_heel.jpg +0 -0
  109. data/spec/dummy/app/assets/files/message_train/attachments/K9Pro_Civil_agitation.JPG +0 -0
  110. data/spec/dummy/app/assets/files/message_train/attachments/TrainingSchutzhundRetrieveOverWall.jpg +0 -0
  111. data/spec/dummy/app/assets/files/message_train/attachments/example.pdf +0 -0
  112. data/spec/dummy/app/assets/files/message_train/attachments/letterlegal5.doc +0 -0
  113. data/spec/dummy/app/assets/files/message_train/attachments/pdf-sample.pdf +0 -0
  114. data/spec/dummy/app/assets/files/message_train/attachments/sample.pdf +0 -0
  115. data/spec/dummy/app/assets/files/message_train/attachments/tips.doc +0 -0
  116. data/spec/dummy/app/assets/files/message_train/attachments/wd-spectools-word-sample-04.doc +0 -0
  117. data/spec/dummy/app/assets/images/logo.svg +140 -0
  118. data/spec/dummy/app/assets/javascripts/application.js +17 -0
  119. data/spec/dummy/app/assets/stylesheets/application.css.scss +35 -0
  120. data/spec/dummy/app/controllers/application_controller.rb +14 -0
  121. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  122. data/spec/dummy/app/mailers/.keep +0 -0
  123. data/spec/dummy/app/models/.keep +0 -0
  124. data/spec/dummy/app/models/group.rb +19 -0
  125. data/spec/dummy/app/models/role.rb +10 -0
  126. data/spec/dummy/app/models/user.rb +23 -0
  127. data/spec/dummy/app/views/layouts/_top_navigation.html.haml +5 -0
  128. data/spec/dummy/app/views/layouts/application.html.haml +29 -0
  129. data/spec/dummy/bin/bundle +3 -0
  130. data/spec/dummy/bin/rails +4 -0
  131. data/spec/dummy/bin/rake +4 -0
  132. data/spec/dummy/bin/setup +29 -0
  133. data/spec/dummy/config/application.rb +40 -0
  134. data/spec/dummy/config/boot.rb +5 -0
  135. data/spec/dummy/config/database.yml +25 -0
  136. data/spec/dummy/config/environment.rb +5 -0
  137. data/spec/dummy/config/environments/development.rb +41 -0
  138. data/spec/dummy/config/environments/production.rb +79 -0
  139. data/spec/dummy/config/environments/test.rb +42 -0
  140. data/spec/dummy/config/initializers/assets.rb +11 -0
  141. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  142. data/spec/dummy/config/initializers/bootstrap_leather.rb +7 -0
  143. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  144. data/spec/dummy/config/initializers/devise.rb +265 -0
  145. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  146. data/spec/dummy/config/initializers/inflections.rb +16 -0
  147. data/spec/dummy/config/initializers/message_train.rb +10 -0
  148. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  149. data/spec/dummy/config/initializers/paperclip.rb +2 -0
  150. data/spec/dummy/config/initializers/rolify.rb +7 -0
  151. data/spec/dummy/config/initializers/session_store.rb +3 -0
  152. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  153. data/spec/dummy/config/locales/devise.en.yml +60 -0
  154. data/spec/dummy/config/locales/en.yml +23 -0
  155. data/spec/dummy/config/routes.rb +5 -0
  156. data/spec/dummy/config/secrets.yml +22 -0
  157. data/spec/dummy/config/unused/temporary_i18n_debugging.rb +12 -0
  158. data/spec/dummy/config.ru +4 -0
  159. data/spec/dummy/db/migrate/20150721140013_devise_create_users.rb +42 -0
  160. data/spec/dummy/db/migrate/20150721141009_rolify_create_roles.rb +19 -0
  161. data/spec/dummy/db/migrate/20150721141128_create_groups.rb +11 -0
  162. data/spec/dummy/db/migrate/20150721150307_add_display_name_column_to_users.rb +6 -0
  163. data/spec/dummy/db/migrate/20150724142846_create_message_train_conversations.night_train.rb +10 -0
  164. data/spec/dummy/db/migrate/20150724142847_create_message_train_messages.night_train.rb +15 -0
  165. data/spec/dummy/db/migrate/20150724142848_create_message_train_attachments.night_train.rb +11 -0
  166. data/spec/dummy/db/migrate/20150724142849_create_message_train_receipts.night_train.rb +17 -0
  167. data/spec/dummy/db/migrate/20150724142850_create_message_train_ignores.night_train.rb +12 -0
  168. data/spec/dummy/db/schema.rb +121 -0
  169. data/spec/dummy/db/seeds/conversations.seeds.rb +86 -0
  170. data/spec/dummy/db/seeds/development/conversations.seeds.rb +9 -0
  171. data/spec/dummy/db/seeds/groups.seeds.rb +9 -0
  172. data/spec/dummy/db/seeds/test/conversations.seeds.rb +9 -0
  173. data/spec/dummy/db/seeds/users.seeds.rb +15 -0
  174. data/spec/dummy/db/seeds.rb +0 -0
  175. data/spec/dummy/db/test.sqlite3 +0 -0
  176. data/spec/dummy/lib/assets/.keep +0 -0
  177. data/spec/dummy/log/.keep +0 -0
  178. data/spec/dummy/public/404.html +67 -0
  179. data/spec/dummy/public/422.html +67 -0
  180. data/spec/dummy/public/500.html +66 -0
  181. data/spec/dummy/public/capybara.html +193 -0
  182. data/spec/dummy/public/favicon.ico +0 -0
  183. data/spec/factories/attachment.rb +6 -0
  184. data/spec/factories/group.rb +20 -0
  185. data/spec/factories/message.rb +68 -0
  186. data/spec/factories/user.rb +8 -0
  187. data/spec/features/boxes_spec.rb +50 -0
  188. data/spec/features/conversations_spec.rb +36 -0
  189. data/spec/features/messages_spec.rb +53 -0
  190. data/spec/helpers/message_train/boxes_helper_spec.rb +44 -0
  191. data/spec/message_train_spec.rb +7 -0
  192. data/spec/models/group_spec.rb +23 -0
  193. data/spec/models/message_train/attachment_spec.rb +12 -0
  194. data/spec/models/message_train/box_spec.rb +195 -0
  195. data/spec/models/message_train/conversation_spec.rb +98 -0
  196. data/spec/models/message_train/ignore_spec.rb +27 -0
  197. data/spec/models/message_train/message_spec.rb +83 -0
  198. data/spec/models/message_train/receipt_spec.rb +73 -0
  199. data/spec/models/user_spec.rb +68 -0
  200. data/spec/rails_helper.rb +83 -0
  201. data/spec/spec_helper.rb +92 -0
  202. data/spec/support/controller_behaviors.rb +88 -0
  203. data/spec/support/controller_macros.rb +13 -0
  204. data/spec/support/conversations.rb +10 -0
  205. data/spec/support/factory_girl.rb +3 -0
  206. data/spec/support/feature_behaviors.rb +62 -0
  207. data/spec/support/groups.rb +3 -0
  208. data/spec/support/loaded_site.rb +5 -0
  209. data/spec/support/shared_connection.rb +13 -0
  210. data/spec/support/users.rb +6 -0
  211. data/spec/support/utilities.rb +8 -0
  212. data/vendor/assets/javascripts/bootstrap-tags.js +623 -0
  213. data/vendor/assets/stylesheets/bootstrap-tags.css +78 -0
  214. 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,3 @@
1
+ - add_hero_unit do
2
+ %h1
3
+ 404 - Page Not Found
@@ -0,0 +1,8 @@
1
+ json.message @box.message
2
+ unless @box.results.all.empty?
3
+ json.results @box.results.all do |item|
4
+ json.css_id item[:css_id]
5
+ json.path item[:path]
6
+ json.message item[:message]
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ - unless boxes.empty?
2
+ = dropdown_nav_item "#{:box_title_in.l} #{badge boxes.first.unread_count.to_s}".html_safe, '#' do
3
+ - boxes.each do |box|
4
+ = box_nav_item box
5
+ = nav_item :compose.l, message_train.new_box_message_path(:in)
@@ -0,0 +1,5 @@
1
+ - if box.present?
2
+ %li{ html_options }
3
+ = link_to box.title, message_train.box_path(box.division)
4
+ - if box.unread_count > 0
5
+ = badge box.unread_count.to_s
@@ -0,0 +1,6 @@
1
+ - unless boxes.empty?
2
+ %h3= :messages.l
3
+ .text-center= link_to :compose.l, message_train.new_box_message_path(:in), class: 'btn btn-lg btn-primary btn-compose'
4
+ %ul.list-group
5
+ - boxes.each do |box|
6
+ = box_list_item box, class: 'list-group-item'
@@ -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,4 @@
1
+ - if conversation.includes_unread_for?(@box.parent)
2
+ = conversation_toggle conversation, 'eye-open', :read, :put, :mark_as_name.l(name: :read.l)
3
+ - else
4
+ = conversation_toggle conversation, 'eye-close', :unread, :put, :mark_as_name.l(name: :unread.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,2 @@
1
+ json.id @conversation.id
2
+ json.html render partial: 'conversation', formats: [:html], locals: { conversation: @conversation }
@@ -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,4 @@
1
+ - if message.is_unread_for?(@box.parent)
2
+ = message_toggle message, 'eye-open', :read, :mark_as_name.l(name: :read.l)
3
+ - else
4
+ = message_toggle message, 'eye-close', :unread, :mark_as_name.l(name: :unread.l)
@@ -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
+ - if message.is_untrashed_for?(@box.parent)
2
+ = message_toggle message, 'trash', :trash, :mark_as_name.l(name: :trashed.l), data: { confirm: :are_you_sure.l}
3
+ - else
4
+ = message_toggle message, 'inbox', :untrash, :mark_as_name.l(name: :untrashed.l)
@@ -0,0 +1,2 @@
1
+ - add_title 'Edit Draft'
2
+ = render partial: 'form', locals: { message: @message }
@@ -0,0 +1,2 @@
1
+ - add_title 'New Message'
2
+ = render partial: 'form', locals: { message: @message }
@@ -0,0 +1,2 @@
1
+ json.id @message.id
2
+ json.html render partial: 'message', formats: [:html], locals: { message: @message }
@@ -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,5 @@
1
+ json.id participant.id
2
+ json.model_name participant.class.name
3
+ json.slug box_participant_slug(participant)
4
+ json.name box_participant_name(participant)
5
+ json.path message_train.box_participant_path(@box, participant.id)
@@ -0,0 +1,3 @@
1
+ json.participants @participants do |participant|
2
+ json.partial! partial: 'participant', locals: { participant: participant }
3
+ end
@@ -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,5 @@
1
+ # Date
2
+ Date::DATE_FORMATS[:default] = "%m/%d/%Y"
3
+
4
+ # Time
5
+ Time::DATE_FORMATS[:default] = "%m/%d/%Y %I:%M %p"
@@ -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,9 @@
1
+ class CreateMessageTrainConversations < ActiveRecord::Migration
2
+ def change
3
+ create_table :message_train_conversations do |t|
4
+ t.string :subject
5
+
6
+ t.timestamps null: false
7
+ end
8
+ end
9
+ 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,10 @@
1
+ class CreateMessageTrainAttachments < ActiveRecord::Migration
2
+ def change
3
+ create_table :message_train_attachments do |t|
4
+ t.references :message, index: true, foreign_key: true
5
+ t.attachment :attachment
6
+
7
+ t.timestamps null: false
8
+ end
9
+ end
10
+ 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