├── .gitignore
├── .rubocop.yml
├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── app
├── assets
│ ├── config
│ │ └── chat_manifest.js
│ ├── images
│ │ └── chat
│ │ │ └── .keep
│ ├── javascripts
│ │ ├── chat.js
│ │ └── chat
│ │ │ ├── cable.js
│ │ │ ├── channels
│ │ │ ├── message.coffee
│ │ │ ├── notification.coffee
│ │ │ └── status.coffee
│ │ │ ├── emojionearea.min.js
│ │ │ └── messages.coffee
│ └── stylesheets
│ │ ├── chat.css
│ │ └── chat
│ │ ├── avatar_grid.sass
│ │ ├── chat.sass
│ │ ├── checkbox.sass
│ │ ├── emojionearea.min.css
│ │ ├── header.sass
│ │ ├── launch.sass
│ │ ├── list.sass
│ │ ├── message_form.sass
│ │ ├── new.sass
│ │ └── transcript.sass
├── channels
│ └── chat
│ │ ├── messages_channel.rb
│ │ ├── notification_channel.rb
│ │ └── status_channel.rb
├── controllers
│ └── chat
│ │ ├── application_controller.rb
│ │ ├── conversations_controller.rb
│ │ └── messages_controller.rb
├── helpers
│ └── chat
│ │ └── application_helper.rb
├── jobs
│ └── chat
│ │ ├── application_job.rb
│ │ ├── message_relay_job.rb
│ │ ├── notification_relay_job.rb
│ │ └── status_relay_job.rb
├── mailers
│ └── chat
│ │ └── application_mailer.rb
├── models
│ └── chat
│ │ ├── application_record.rb
│ │ ├── conversation.rb
│ │ ├── dot_command.rb
│ │ ├── dot_command
│ │ ├── gif.rb
│ │ ├── shrug.rb
│ │ └── validator.rb
│ │ ├── message.rb
│ │ └── session.rb
└── views
│ ├── chat
│ ├── _chat.html.haml
│ ├── conversations
│ │ ├── _conversation.html.haml
│ │ ├── _index.html.haml
│ │ ├── _new.html.haml
│ │ ├── _show.html.haml
│ │ ├── create.js.erb
│ │ └── show.js.erb
│ └── messages
│ │ └── _message.haml
│ └── layouts
│ └── chat
│ └── application.html.erb
├── bin
├── rails
└── test
├── chat.gemspec
├── config
└── routes.rb
├── lib
├── chat.rb
├── chat
│ ├── engine.rb
│ ├── user.rb
│ └── version.rb
├── generators
│ └── chat
│ │ └── install
│ │ ├── install_generator.rb
│ │ └── templates
│ │ ├── add_chat_to_users.rb
│ │ ├── chat.rb
│ │ └── create_chat.rb
└── tasks
│ └── chat_tasks.rake
└── test
├── chat_test.rb
├── dummy
├── Rakefile
├── app
│ ├── assets
│ │ ├── config
│ │ │ └── manifest.js
│ │ ├── images
│ │ │ └── .keep
│ │ ├── javascripts
│ │ │ ├── application.js
│ │ │ ├── cable.js
│ │ │ ├── channels
│ │ │ │ └── .keep
│ │ │ ├── material-components-web.min.js
│ │ │ └── material_design_components.js
│ │ └── stylesheets
│ │ │ ├── application.css
│ │ │ ├── home.sass
│ │ │ ├── material-components-web.min.css
│ │ │ └── sessions.sass
│ ├── channels
│ │ └── application_cable
│ │ │ ├── channel.rb
│ │ │ └── connection.rb
│ ├── controllers
│ │ ├── application_controller.rb
│ │ ├── concerns
│ │ │ └── .keep
│ │ ├── home_controller.rb
│ │ └── users_controller.rb
│ ├── helpers
│ │ ├── application_helper.rb
│ │ └── snackbar_helper.rb
│ ├── jobs
│ │ └── application_job.rb
│ ├── mailers
│ │ └── application_mailer.rb
│ ├── models
│ │ ├── application_record.rb
│ │ ├── concerns
│ │ │ └── .keep
│ │ └── user.rb
│ └── views
│ │ ├── clearance
│ │ └── users
│ │ │ └── _form.html.haml
│ │ ├── home
│ │ └── index.html.haml
│ │ ├── layouts
│ │ ├── application.html.haml
│ │ ├── mailer.html.erb
│ │ └── mailer.text.erb
│ │ ├── sessions
│ │ └── new.html.haml
│ │ └── users
│ │ ├── edit.html.haml
│ │ └── new.html.haml
├── bin
│ ├── bundle
│ ├── rails
│ ├── rake
│ ├── setup
│ └── update
├── config.ru
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── cable.yml
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── application_controller_renderer.rb
│ │ ├── assets.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── chat.rb
│ │ ├── clearance.rb
│ │ ├── cookies_serializer.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── mime_types.rb
│ │ ├── new_framework_defaults.rb
│ │ ├── session_store.rb
│ │ └── wrap_parameters.rb
│ ├── locales
│ │ └── en.yml
│ ├── puma.rb
│ ├── routes.rb
│ ├── secrets.yml
│ ├── sidekiq.yml
│ └── spring.rb
├── db
│ ├── migrate
│ │ ├── 20160911032725_create_users.rb
│ │ ├── 20160919005427_add_chat_to_users.rb
│ │ └── 20160919005428_create_chat.rb
│ ├── schema.rb
│ └── seeds.rb
├── lib
│ └── assets
│ │ └── .keep
└── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ ├── apple-touch-icon-precomposed.png
│ ├── apple-touch-icon.png
│ └── favicon.ico
├── integration
└── navigation_test.rb
├── lib
└── generators
│ └── chat
│ └── install_generator_test.rb
└── test_helper.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /Gemfile.lock
4 | /_yardoc/
5 | /coverage/
6 | /doc/
7 | /pkg/
8 | /spec/reports/
9 | /tmp/
10 | /test/dummy/tmp/
11 | /test/dummy/log/
12 | /test/dummy/db/*.sqlite3
13 | *.gem
14 | /test/dummy/public/system
15 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | ---
2 | AllCops:
3 | Exclude:
4 | - vendor/**/*
5 | - Gemfile.lock
6 | - db/schema.rb
7 | - test/dummy/db/schema.rb
8 | TargetRubyVersion: 2.4
9 | Lint/AmbiguousOperator:
10 | Description: Checks for ambiguous operators in the first argument of a method invocation
11 | without parentheses.
12 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#parens-as-args
13 | Enabled: true
14 | Lint/AmbiguousBlockAssociation:
15 | Enabled: false
16 | Lint/AmbiguousRegexpLiteral:
17 | Description: Checks for ambiguous regexp literals in the first argument of a method
18 | invocation without parenthesis.
19 | Enabled: true
20 | Lint/AssignmentInCondition:
21 | Description: Don't use assignment in conditions.
22 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition
23 | Enabled: true
24 | Lint/BlockAlignment:
25 | Description: Align block ends correctly.
26 | Enabled: true
27 | Lint/CircularArgumentReference:
28 | Description: Don't refer to the keyword argument in the default value.
29 | Enabled: true
30 | Lint/ConditionPosition:
31 | Description: Checks for condition placed in a confusing position relative to the
32 | keyword.
33 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#same-line-condition
34 | Enabled: true
35 | Lint/Debugger:
36 | Description: Check for debugger calls.
37 | Enabled: true
38 | Lint/DefEndAlignment:
39 | Description: Align ends corresponding to defs correctly.
40 | Enabled: true
41 | Lint/DeprecatedClassMethods:
42 | Description: Check for deprecated class method calls.
43 | Enabled: true
44 | Lint/DuplicateMethods:
45 | Description: Check for duplicate methods calls.
46 | Enabled: true
47 | Lint/EachWithObjectArgument:
48 | Description: Check for immutable argument given to each_with_object.
49 | Enabled: true
50 | Lint/ElseLayout:
51 | Description: Check for odd code arrangement in an else block.
52 | Enabled: true
53 | Lint/EmptyEnsure:
54 | Description: Checks for empty ensure block.
55 | Enabled: true
56 | Lint/EmptyInterpolation:
57 | Description: Checks for empty string interpolation.
58 | Enabled: true
59 | Lint/EndAlignment:
60 | Description: Align ends correctly.
61 | Enabled: true
62 | Lint/EndInMethod:
63 | Description: END blocks should not be placed inside method definitions.
64 | Enabled: true
65 | Lint/EnsureReturn:
66 | Description: Do not use return in an ensure block.
67 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-return-ensure
68 | Enabled: true
69 | Lint/FormatParameterMismatch:
70 | Description: The number of parameters to format/sprint must match the fields.
71 | Enabled: true
72 | Lint/HandleExceptions:
73 | Description: Don't suppress exception.
74 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions
75 | Enabled: true
76 | Lint/InvalidCharacterLiteral:
77 | Description: Checks for invalid character literals with a non-escaped whitespace
78 | character.
79 | Enabled: true
80 | Lint/LiteralInCondition:
81 | Description: Checks of literals used in conditions.
82 | Enabled: true
83 | Lint/LiteralInInterpolation:
84 | Description: Checks for literals used in interpolation.
85 | Enabled: true
86 | Lint/Loop:
87 | Description: Use Kernel#loop with break rather than begin/end/until or begin/end/while
88 | for post-loop tests.
89 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#loop-with-break
90 | Enabled: true
91 | Lint/NestedMethodDefinition:
92 | Description: Do not use nested method definitions.
93 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-methods
94 | Enabled: true
95 | Lint/NonLocalExitFromIterator:
96 | Description: Do not use return in iterator to cause non-local exit.
97 | Enabled: true
98 | Lint/ParenthesesAsGroupedExpression:
99 | Description: Checks for method calls with a space before the opening parenthesis.
100 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#parens-no-spaces
101 | Enabled: true
102 | Lint/RequireParentheses:
103 | Description: Use parentheses in the method call to avoid confusion about precedence.
104 | Enabled: true
105 | Lint/RescueException:
106 | Description: Avoid rescuing the Exception class.
107 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-blind-rescues
108 | Enabled: true
109 | Lint/ShadowingOuterLocalVariable:
110 | Description: Do not use the same name as outer local variable for block arguments
111 | or block local variables.
112 | Enabled: true
113 | Lint/StringConversionInInterpolation:
114 | Description: Checks for Object#to_s usage in string interpolation.
115 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-to-s
116 | Enabled: true
117 | Lint/UnderscorePrefixedVariableName:
118 | Description: Do not use prefix `_` for a variable that is used.
119 | Enabled: true
120 | Lint/UnneededDisable:
121 | Description: 'Checks for rubocop:disable comments that can be removed. Note: this
122 | cop is not disabled when disabling all cops. It must be explicitly disabled.'
123 | Enabled: true
124 | Lint/UnusedBlockArgument:
125 | Description: Checks for unused block arguments.
126 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars
127 | Enabled: true
128 | Lint/UnusedMethodArgument:
129 | Description: Checks for unused method arguments.
130 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars
131 | Enabled: true
132 | Lint/UnreachableCode:
133 | Description: Unreachable code.
134 | Enabled: true
135 | Lint/UselessAccessModifier:
136 | Description: Checks for useless access modifiers.
137 | Enabled: true
138 | Lint/UselessAssignment:
139 | Description: Checks for useless assignment to a local variable.
140 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars
141 | Enabled: true
142 | Lint/UselessComparison:
143 | Description: Checks for comparison of something with itself.
144 | Enabled: true
145 | Lint/UselessElseWithoutRescue:
146 | Description: Checks for useless `else` in `begin..end` without `rescue`.
147 | Enabled: true
148 | Lint/UselessSetterCall:
149 | Description: Checks for useless setter call to a local variable.
150 | Enabled: true
151 | Lint/Void:
152 | Description: Possible use of operator/literal/variable in void context.
153 | Enabled: true
154 | Metrics/AbcSize:
155 | Description: A calculated magnitude based on number of assignments, branches, and
156 | conditions.
157 | Reference: http://c2.com/cgi/wiki?AbcMetric
158 | Enabled: true
159 | Max: 15
160 | Metrics/BlockNesting:
161 | Description: Avoid excessive block nesting
162 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count
163 | Enabled: true
164 | Max: 4
165 | Metrics/ClassLength:
166 | Description: Avoid classes longer than 100 lines of code.
167 | Enabled: true
168 | Max: 100
169 | Metrics/CyclomaticComplexity:
170 | Description: A complexity metric that is strongly correlated to the number of test
171 | cases needed to validate a method.
172 | Enabled: true
173 | Metrics/LineLength:
174 | Description: Limit lines to 80 characters.
175 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#80-character-limits
176 | Enabled: true
177 | Metrics/MethodLength:
178 | Description: Avoid methods longer than 10 lines of code.
179 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#short-methods
180 | Enabled: true
181 | Max: 10
182 | Metrics/ModuleLength:
183 | Description: Avoid modules longer than 100 lines of code.
184 | Enabled: true
185 | Max: 100
186 | Metrics/ParameterLists:
187 | Description: Avoid parameter lists longer than three or four parameters.
188 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#too-many-params
189 | Enabled: false
190 | Metrics/PerceivedComplexity:
191 | Description: A complexity metric geared towards measuring complexity for a human
192 | reader.
193 | Enabled: true
194 | Metrics/BlockLength:
195 | Exclude:
196 | - Rakefile
197 | - "**/*.rake"
198 | - spec/**/*.rb
199 | Performance/Count:
200 | Description: Use `count` instead of `select...size`, `reject...size`, `select...count`,
201 | `reject...count`, `select...length`, and `reject...length`.
202 | Enabled: true
203 | Performance/Detect:
204 | Description: Use `detect` instead of `select.first`, `find_all.first`, `select.last`,
205 | and `find_all.last`.
206 | Reference: https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code
207 | Enabled: true
208 | Performance/FlatMap:
209 | Description: Use `Enumerable#flat_map` instead of `Enumerable#map...Array#flatten(1)`
210 | or `Enumberable#collect..Array#flatten(1)`
211 | Reference: https://github.com/JuanitoFatas/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code
212 | Enabled: true
213 | EnabledForFlattenWithoutParams: false
214 | Performance/ReverseEach:
215 | Description: Use `reverse_each` instead of `reverse.each`.
216 | Reference: https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code
217 | Enabled: true
218 | Performance/Sample:
219 | Description: Use `sample` instead of `shuffle.first`, `shuffle.last`, and `shuffle[Fixnum]`.
220 | Reference: https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code
221 | Enabled: true
222 | Performance/Size:
223 | Description: Use `size` instead of `count` for counting the number of elements in
224 | `Array` and `Hash`.
225 | Reference: https://github.com/JuanitoFatas/fast-ruby#arraycount-vs-arraysize-code
226 | Enabled: true
227 | Performance/StringReplacement:
228 | Description: Use `tr` instead of `gsub` when you are replacing the same number of
229 | characters. Use `delete` instead of `gsub` when you are deleting characters.
230 | Reference: https://github.com/JuanitoFatas/fast-ruby#stringgsub-vs-stringtr-code
231 | Enabled: true
232 | Rails/ActionFilter:
233 | Description: Enforces consistent use of action filter methods.
234 | Enabled: false
235 | Rails/Date:
236 | Description: Checks the correct usage of date aware methods, such as Date.today,
237 | Date.current etc.
238 | Enabled: true
239 | Rails/Delegate:
240 | Description: Prefer delegate method for delegations.
241 | Enabled: true
242 | Rails/FindBy:
243 | Description: Prefer find_by over where.first.
244 | Enabled: true
245 | Rails/FindEach:
246 | Description: Prefer all.find_each over all.find.
247 | Enabled: true
248 | Rails/HasAndBelongsToMany:
249 | Description: Prefer has_many :through to has_and_belongs_to_many.
250 | Enabled: true
251 | Rails/Output:
252 | Description: Checks for calls to puts, print, etc.
253 | Enabled: true
254 | Rails/ReadWriteAttribute:
255 | Description: Checks for read_attribute(:attr) and write_attribute(:attr, val).
256 | Enabled: false
257 | Rails/ScopeArgs:
258 | Description: Checks the arguments of ActiveRecord scopes.
259 | Enabled: true
260 | Rails/TimeZone:
261 | Description: Checks the correct usage of time zone aware methods.
262 | StyleGuide: https://github.com/bbatsov/rails-style-guide#time
263 | Reference: http://danilenko.org/2012/7/6/rails_timezones
264 | Enabled: true
265 | Rails/Validation:
266 | Description: Use validates :attribute, hash of validations.
267 | Enabled: true
268 | Layout/AccessModifierIndentation:
269 | Description: Check indentation of private/protected visibility modifiers.
270 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#indent-public-private-protected
271 | Enabled: false
272 | Style/AccessorMethodName:
273 | Description: Check the naming of accessor methods for get_/set_.
274 | Enabled: false
275 | Style/Alias:
276 | Description: Use alias_method instead of alias.
277 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#alias-method
278 | Enabled: false
279 | Layout/AlignArray:
280 | Description: Align the elements of an array literal if they span more than one line.
281 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#align-multiline-arrays
282 | Enabled: true
283 | Layout/AlignHash:
284 | Description: Align the elements of a hash literal if they span more than one line.
285 | Enabled: true
286 | Layout/AlignParameters:
287 | Description: Align the parameters of a method call if they span more than one line.
288 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-double-indent
289 | Enabled: true
290 | Style/AndOr:
291 | Description: Use &&/|| instead of and/or.
292 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-and-or-or
293 | Enabled: true
294 | Style/ArrayJoin:
295 | Description: Use Array#join instead of Array#*.
296 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#array-join
297 | Enabled: false
298 | Style/AsciiComments:
299 | Description: Use only ascii symbols in comments.
300 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#english-comments
301 | Enabled: false
302 | Style/AsciiIdentifiers:
303 | Description: Use only ascii symbols in identifiers.
304 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#english-identifiers
305 | Enabled: false
306 | Style/Attr:
307 | Description: Checks for uses of Module#attr.
308 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#attr
309 | Enabled: true
310 | Style/BeginBlock:
311 | Description: Avoid the use of BEGIN blocks.
312 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-BEGIN-blocks
313 | Enabled: true
314 | Style/BarePercentLiterals:
315 | Description: Checks if usage of %() or %Q() matches configuration.
316 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-q-shorthand
317 | Enabled: true
318 | Style/BlockComments:
319 | Description: Do not use block comments.
320 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-block-comments
321 | Enabled: false
322 | Layout/BlockEndNewline:
323 | Description: Put end statement of multiline block on its own line.
324 | Enabled: true
325 | Style/BlockDelimiters:
326 | Description: Avoid using {...} for multi-line blocks (multiline chaining is always
327 | ugly). Prefer {...} over do...end for single-line blocks.
328 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#single-line-blocks
329 | Enabled: true
330 | Style/BracesAroundHashParameters:
331 | Description: Enforce braces style around hash parameters.
332 | Enabled: false
333 | Style/CaseEquality:
334 | Description: Avoid explicit use of the case equality operator(===).
335 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-case-equality
336 | Enabled: false
337 | Layout/CaseIndentation:
338 | Description: Indentation of when in a case/when/[else/]end.
339 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#indent-when-to-case
340 | Enabled: true
341 | Style/CharacterLiteral:
342 | Description: Checks for uses of character literals.
343 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-character-literals
344 | Enabled: false
345 | Style/ClassAndModuleCamelCase:
346 | Description: Use CamelCase for classes and modules.
347 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#camelcase-classes
348 | Enabled: true
349 | Style/ClassAndModuleChildren:
350 | Description: Checks style of children classes and modules.
351 | Enabled: false
352 | Style/ClassCheck:
353 | Description: Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.
354 | Enabled: true
355 | Style/ClassMethods:
356 | Description: Use self when defining module/class methods.
357 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#def-self-class-methods
358 | Enabled: false
359 | Style/ClassVars:
360 | Description: Avoid the use of class variables.
361 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-class-vars
362 | Enabled: false
363 | Layout/ClosingParenthesisIndentation:
364 | Description: Checks the indentation of hanging closing parentheses.
365 | Enabled: false
366 | Style/ColonMethodCall:
367 | Description: 'Do not use :: for method call.'
368 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#double-colons
369 | Enabled: true
370 | Style/CommandLiteral:
371 | Description: Use `` or %x around command literals.
372 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-x
373 | Enabled: false
374 | Style/CommentAnnotation:
375 | Description: Checks formatting of annotation comments.
376 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#annotate-keywords
377 | Enabled: false
378 | Layout/CommentIndentation:
379 | Description: Indentation of comments.
380 | Enabled: false
381 | Style/ConstantName:
382 | Description: Constants should use SCREAMING_SNAKE_CASE.
383 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#screaming-snake-case
384 | Enabled: true
385 | Style/DefWithParentheses:
386 | Description: Use def with parentheses when there are arguments.
387 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#method-parens
388 | Enabled: true
389 | Style/Documentation:
390 | Description: Document classes and non-namespace modules.
391 | Enabled: false
392 | Layout/DotPosition:
393 | Description: Checks the position of the dot in multi-line method calls.
394 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains
395 | Enabled: true
396 | Style/DoubleNegation:
397 | Description: Checks for uses of double negation (!!).
398 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-bang-bang
399 | Enabled: true
400 | Style/EachWithObject:
401 | Description: Prefer `each_with_object` over `inject` or `reduce`.
402 | Enabled: false
403 | Layout/ElseAlignment:
404 | Description: Align elses and elsifs correctly.
405 | Enabled: true
406 | Style/EmptyElse:
407 | Description: Avoid empty else-clauses.
408 | Enabled: true
409 | Layout/EmptyLineBetweenDefs:
410 | Description: Use empty lines between defs.
411 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods
412 | Enabled: true
413 | Layout/EmptyLines:
414 | Description: Don't use several empty lines in a row.
415 | Enabled: true
416 | Layout/EmptyLinesAroundAccessModifier:
417 | Description: Keep blank lines around access modifiers.
418 | Enabled: false
419 | Layout/EmptyLinesAroundBlockBody:
420 | Description: Keeps track of empty lines around block bodies.
421 | Enabled: false
422 | Layout/EmptyLinesAroundClassBody:
423 | Description: Keeps track of empty lines around class bodies.
424 | Enabled: false
425 | Layout/EmptyLinesAroundModuleBody:
426 | Description: Keeps track of empty lines around module bodies.
427 | Enabled: false
428 | Layout/EmptyLinesAroundMethodBody:
429 | Description: Keeps track of empty lines around method bodies.
430 | Enabled: false
431 | Style/EmptyLiteral:
432 | Description: Prefer literals to Array.new/Hash.new/String.new.
433 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#literal-array-hash
434 | Enabled: false
435 | Style/EndBlock:
436 | Description: Avoid the use of END blocks.
437 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-END-blocks
438 | Enabled: false
439 | Layout/EndOfLine:
440 | Description: Use Unix-style line endings.
441 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#crlf
442 | Enabled: false
443 | Style/EvenOdd:
444 | Description: Favor the use of Fixnum#even? && Fixnum#odd?
445 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#predicate-methods
446 | Enabled: true
447 | Layout/ExtraSpacing:
448 | Description: Do not use unnecessary spacing.
449 | Enabled: true
450 | Style/FileName:
451 | Description: Use snake_case for source file names.
452 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#snake-case-files
453 | Enabled: true
454 | Layout/InitialIndentation:
455 | Description: Checks the indentation of the first non-blank non-comment line in a
456 | file.
457 | Enabled: false
458 | Layout/FirstParameterIndentation:
459 | Description: Checks the indentation of the first parameter in a method call.
460 | Enabled: false
461 | Style/FlipFlop:
462 | Description: Checks for flip flops
463 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-flip-flops
464 | Enabled: true
465 | Style/For:
466 | Description: Checks use of for or each in multiline loops.
467 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-for-loops
468 | Enabled: true
469 | Style/FormatString:
470 | Description: Enforce the use of Kernel#sprintf, Kernel#format or String#%.
471 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#sprintf
472 | Enabled: false
473 | Style/GlobalVars:
474 | Description: Do not introduce global variables.
475 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#instance-vars
476 | Reference: http://www.zenspider.com/Languages/Ruby/QuickRef.html
477 | Enabled: true
478 | Style/GuardClause:
479 | Description: Check for conditionals that can be replaced with guard clauses
480 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals
481 | Enabled: true
482 | Style/HashSyntax:
483 | Description: 'Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax { :a =>
484 | 1, :b => 2 }.'
485 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#hash-literals
486 | Enabled: true
487 | Style/IfUnlessModifier:
488 | Description: Favor modifier if/unless usage when you have a single-line body.
489 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier
490 | Enabled: true
491 | Style/IfWithSemicolon:
492 | Description: Do not use if x; .... Use the ternary operator instead.
493 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs
494 | Enabled: true
495 | Layout/IndentationConsistency:
496 | Description: Keep indentation straight.
497 | Enabled: false
498 | Layout/IndentationWidth:
499 | Description: Use 2 spaces for indentation.
500 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-indentation
501 | Enabled: true
502 | Layout/IndentArray:
503 | Description: Checks the indentation of the first element in an array literal.
504 | Enabled: false
505 | Layout/IndentHash:
506 | Description: Checks the indentation of the first key in a hash literal.
507 | Enabled: false
508 | Style/InfiniteLoop:
509 | Description: Use Kernel#loop for infinite loops.
510 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#infinite-loop
511 | Enabled: true
512 | Style/Lambda:
513 | Description: Use the new lambda literal syntax for single-line blocks.
514 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#lambda-multi-line
515 | Enabled: true
516 | Style/LambdaCall:
517 | Description: Use lambda.call(...) instead of lambda.(...).
518 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#proc-call
519 | Enabled: false
520 | Layout/LeadingCommentSpace:
521 | Description: Comments should start with a space.
522 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#hash-space
523 | Enabled: true
524 | Style/LineEndConcatenation:
525 | Description: Use \ instead of + or << to concatenate two string literals at line
526 | end.
527 | Enabled: true
528 | Style/MethodCallWithoutArgsParentheses:
529 | Description: Do not use parentheses for method calls with no arguments.
530 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-args-no-parens
531 | Enabled: true
532 | Style/MethodDefParentheses:
533 | Description: Checks if the method definitions have or don't have parentheses.
534 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#method-parens
535 | Enabled: true
536 | Style/MethodName:
537 | Description: Use the configured style when naming methods.
538 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars
539 | Enabled: false
540 | Style/ModuleFunction:
541 | Description: Checks for usage of `extend self` in modules.
542 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#module-function
543 | Enabled: true
544 | Style/MultilineBlockChain:
545 | Description: Avoid multi-line chains of blocks.
546 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#single-line-blocks
547 | Enabled: false
548 | Layout/MultilineBlockLayout:
549 | Description: Ensures newlines after multiline block do statements.
550 | Enabled: true
551 | Style/MultilineIfThen:
552 | Description: Do not use then for multi-line if/unless.
553 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-then
554 | Enabled: true
555 | Layout/MultilineOperationIndentation:
556 | Description: Checks indentation of binary operations that span more than one line.
557 | Enabled: false
558 | Style/MultilineTernaryOperator:
559 | Description: 'Avoid multi-line ?: (the ternary operator); use if/unless instead.'
560 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-multiline-ternary
561 | Enabled: true
562 | Style/NegatedIf:
563 | Description: Favor unless over if for negative conditions (or control flow or).
564 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#unless-for-negatives
565 | Enabled: true
566 | Style/NegatedWhile:
567 | Description: Favor until over while for negative conditions.
568 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#until-for-negatives
569 | Enabled: true
570 | Style/NestedTernaryOperator:
571 | Description: Use one expression per branch in a ternary operator.
572 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-ternary
573 | Enabled: true
574 | Style/Next:
575 | Description: Use `next` to skip iteration instead of a condition at the end.
576 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals
577 | Enabled: true
578 | Style/NilComparison:
579 | Description: Prefer x.nil? to x == nil.
580 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#predicate-methods
581 | Enabled: true
582 | Style/NonNilCheck:
583 | Description: Checks for redundant nil checks.
584 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-non-nil-checks
585 | Enabled: true
586 | Style/Not:
587 | Description: Use ! instead of not.
588 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#bang-not-not
589 | Enabled: true
590 | Style/NumericLiterals:
591 | Description: Add underscores to large numeric literals to improve their readability.
592 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics
593 | Enabled: true
594 | Style/OneLineConditional:
595 | Description: Favor the ternary operator(?:) over if/then/else/end constructs.
596 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#ternary-operator
597 | Enabled: false
598 | Style/OpMethod:
599 | Description: When defining binary operators, name the argument other.
600 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#other-arg
601 | Enabled: false
602 | Style/OptionalArguments:
603 | Description: Checks for optional arguments that do not appear at the end of the
604 | argument list
605 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#optional-arguments
606 | Enabled: false
607 | Style/ParallelAssignment:
608 | Description: Check for simple usages of parallel assignment. It will only warn when
609 | the number of variables matches on both sides of the assignment. This also provides
610 | performance benefits
611 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#parallel-assignment
612 | Enabled: false
613 | Style/ParenthesesAroundCondition:
614 | Description: Don't use parentheses around the condition of an if/unless/while.
615 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-parens-if
616 | Enabled: true
617 | Style/PercentLiteralDelimiters:
618 | Description: Use `%`-literal delimiters consistently
619 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-literal-braces
620 | Enabled: true
621 | PreferredDelimiters:
622 | default: "()"
623 | "%i": "()"
624 | "%I": "()"
625 | "%r": "{}"
626 | "%w": "()"
627 | "%W": "()"
628 | Style/PercentQLiterals:
629 | Description: Checks if uses of %Q/%q match the configured preference.
630 | Enabled: true
631 | Style/PerlBackrefs:
632 | Description: Avoid Perl-style regex back references.
633 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers
634 | Enabled: false
635 | Style/PredicateName:
636 | Description: Check the names of predicate methods.
637 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark
638 | Enabled: true
639 | Style/Proc:
640 | Description: Use proc instead of Proc.new.
641 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#proc
642 | Enabled: true
643 | Style/RaiseArgs:
644 | Description: Checks the arguments passed to raise/fail.
645 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#exception-class-messages
646 | Enabled: false
647 | Style/RedundantBegin:
648 | Description: Don't use begin blocks when they are not needed.
649 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#begin-implicit
650 | Enabled: true
651 | Style/RedundantException:
652 | Description: Checks for an obsolete RuntimeException argument in raise/fail.
653 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-explicit-runtimeerror
654 | Enabled: true
655 | Style/RedundantReturn:
656 | Description: Don't use return where it's not required.
657 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-explicit-return
658 | Enabled: true
659 | Style/RedundantSelf:
660 | Description: Don't use self where it's not needed.
661 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-self-unless-required
662 | Enabled: true
663 | Style/RegexpLiteral:
664 | Description: Use / or %r around regular expressions.
665 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-r
666 | Enabled: false
667 | Layout/RescueEnsureAlignment:
668 | Description: Align rescues and ensures correctly.
669 | Enabled: false
670 | Style/RescueModifier:
671 | Description: Avoid using rescue in its modifier form.
672 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-rescue-modifiers
673 | Enabled: false
674 | Style/SelfAssignment:
675 | Description: Checks for places where self-assignment shorthand should have been
676 | used.
677 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#self-assignment
678 | Enabled: true
679 | Style/Semicolon:
680 | Description: Don't use semicolons to terminate expressions.
681 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-semicolon
682 | Enabled: true
683 | Style/SignalException:
684 | Description: Checks for proper usage of fail and raise.
685 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#fail-method
686 | Enabled: true
687 | Style/SingleLineBlockParams:
688 | Description: Enforces the names of some block params.
689 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#reduce-blocks
690 | Enabled: true
691 | Style/SingleLineMethods:
692 | Description: Avoid single-line methods.
693 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-single-line-methods
694 | Enabled: false
695 | Layout/SpaceBeforeFirstArg:
696 | Description: Checks that exactly one space is used between a method name and the
697 | first argument for method calls without parentheses.
698 | Enabled: true
699 | Layout/SpaceAfterColon:
700 | Description: Use spaces after colons.
701 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-operators
702 | Enabled: true
703 | Layout/SpaceAfterComma:
704 | Description: Use spaces after commas.
705 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-operators
706 | Enabled: true
707 | Layout/SpaceAroundKeyword:
708 | Description: Use spaces around keywords.
709 | Enabled: true
710 | Layout/SpaceAfterMethodName:
711 | Description: Do not put a space between a method name and the opening parenthesis
712 | in a method definition.
713 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#parens-no-spaces
714 | Enabled: true
715 | Layout/SpaceAfterNot:
716 | Description: Tracks redundant space after the ! operator.
717 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-space-bang
718 | Enabled: false
719 | Layout/SpaceAfterSemicolon:
720 | Description: Use spaces after semicolons.
721 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-operators
722 | Enabled: true
723 | Layout/SpaceBeforeBlockBraces:
724 | Description: Checks that the left block brace has or doesn't have space before it.
725 | Enabled: false
726 | Layout/SpaceBeforeComma:
727 | Description: No spaces before commas.
728 | Enabled: false
729 | Layout/SpaceBeforeComment:
730 | Description: Checks for missing space between code and a comment on the same line.
731 | Enabled: false
732 | Layout/SpaceBeforeSemicolon:
733 | Description: No spaces before semicolons.
734 | Enabled: false
735 | Layout/SpaceInsideBlockBraces:
736 | Description: Checks that block braces have or don't have surrounding space. For
737 | blocks taking parameters, checks that the left brace has or doesn't have trailing
738 | space.
739 | Enabled: false
740 | Layout/SpaceAroundBlockParameters:
741 | Description: Checks the spacing inside and after block parameters pipes.
742 | Enabled: true
743 | Layout/SpaceAroundEqualsInParameterDefault:
744 | Description: Checks that the equals signs in parameter default assignments have
745 | or don't have surrounding space depending on configuration.
746 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-around-equals
747 | Enabled: true
748 | Layout/SpaceAroundOperators:
749 | Description: Use a single space around operators.
750 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-operators
751 | Enabled: true
752 | Layout/SpaceInsideBrackets:
753 | Description: No spaces after [ or before ].
754 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-spaces-braces
755 | Enabled: false
756 | Layout/SpaceInsideHashLiteralBraces:
757 | Description: Use spaces inside hash literal braces - or don't.
758 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-operators
759 | Enabled: true
760 | Layout/SpaceInsideParens:
761 | Description: No spaces after ( or before ).
762 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-spaces-braces
763 | Enabled: true
764 | Layout/SpaceInsideRangeLiteral:
765 | Description: No spaces inside range literals.
766 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-space-inside-range-literals
767 | Enabled: true
768 | Layout/SpaceInsideStringInterpolation:
769 | Description: Checks for padding/surrounding spaces inside string interpolation.
770 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#string-interpolation
771 | Enabled: false
772 | Style/SpecialGlobalVars:
773 | Description: Avoid Perl-style global variables.
774 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms
775 | Enabled: false
776 | Style/StringLiterals:
777 | Description: Checks if uses of quotes match the configured preference.
778 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-string-literals
779 | EnforcedStyle: double_quotes
780 | Enabled: true
781 | Style/StringLiteralsInInterpolation:
782 | Description: Checks if uses of quotes inside expressions in interpolated strings
783 | match the configured preference.
784 | Enabled: true
785 | Style/StructInheritance:
786 | Description: Checks for inheritance from Struct.new.
787 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-extend-struct-new
788 | Enabled: false
789 | Style/SymbolLiteral:
790 | Description: Use plain symbols instead of string symbols when possible.
791 | Enabled: false
792 | Style/SymbolProc:
793 | Description: Use symbols as procs instead of blocks when possible.
794 | Enabled: false
795 | Layout/Tab:
796 | Description: No hard tabs.
797 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-indentation
798 | Enabled: false
799 | Layout/TrailingBlankLines:
800 | Description: Checks trailing blank lines and final newline.
801 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#newline-eof
802 | Enabled: false
803 | Style/TrailingCommaInArguments:
804 | Description: Checks for trailing comma in parameter lists.
805 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-trailing-params-comma
806 | Enabled: false
807 | Style/TrailingCommaInLiteral:
808 | Description: Checks for trailing comma in literals.
809 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas
810 | Enabled: false
811 | Layout/TrailingWhitespace:
812 | Description: Avoid trailing whitespace.
813 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace
814 | Enabled: false
815 | Style/TrivialAccessors:
816 | Description: Prefer attr_* methods to trivial readers/writers.
817 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#attr_family
818 | Enabled: false
819 | Style/UnlessElse:
820 | Description: Do not use unless with else. Rewrite these with the positive case first.
821 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-else-with-unless
822 | Enabled: true
823 | Style/UnneededCapitalW:
824 | Description: Checks for %W when interpolation is not needed.
825 | Enabled: false
826 | Style/UnneededPercentQ:
827 | Description: Checks for %q/%Q when single quotes or double quotes would do.
828 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-q
829 | Enabled: false
830 | Style/TrailingUnderscoreVariable:
831 | Description: Checks for the usage of unneeded trailing underscores at the end of
832 | parallel variable assignment.
833 | Enabled: false
834 | Style/VariableInterpolation:
835 | Description: Don't interpolate global, instance and class variables directly in
836 | strings.
837 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#curlies-interpolate
838 | Enabled: true
839 | Style/VariableName:
840 | Description: Use the configured style when naming variables.
841 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars
842 | Enabled: true
843 | Style/WhenThen:
844 | Description: Use when x then ... for one-line cases.
845 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#one-line-cases
846 | Enabled: true
847 | Style/WhileUntilDo:
848 | Description: Checks for redundant do after while or until.
849 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-multiline-while-do
850 | Enabled: true
851 | Style/WhileUntilModifier:
852 | Description: Favor modifier while/until usage when you have a single-line body.
853 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier
854 | Enabled: true
855 | Style/MutableConstant:
856 | Enabled: true
857 | Style/FrozenStringLiteralComment:
858 | Enabled: true
859 | Style/WordArray:
860 | Description: Use %w or %W for arrays of words.
861 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-w
862 | Enabled: true
863 | Style/EmptyMethod:
864 | Enabled: false
865 | Security/Eval:
866 | Description: The use of eval represents a serious security risk.
867 | Enabled: true
868 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | # Declare your gem's dependencies in chat.gemspec.
6 | # Bundler will treat runtime dependencies like base dependencies, and
7 | # development dependencies will be added by default to the :development group.
8 | gemspec
9 |
10 | # Declare any dependencies that are still in development here instead of in
11 | # your gemspec. These might include edge Rails or gems from your path or
12 | # Git. Remember to move these dependencies to your gemspec before releasing
13 | # your gem to rubygems.org.
14 |
15 | # To use a debugger
16 | # gem 'byebug', group: [:development, :test]
17 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2016 npezza93
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Chat
2 | Simple Rails chat plugin that allows users to talk to one or more people at a
3 | time that leverages ActionCable.
4 |
5 | A demo of functionality can be found: [https://chatgem.herokuapp.com/](https://chatgem.herokuapp.com/)
6 |
7 | ## Getting Started
8 |
9 | #### Prerequisites
10 | 1. This gem relies on ActionCable therefore Rails 5.0.0 is a minimum.
11 | 1. For help setting up ActionCable when coming from an older version of Rails, view
12 | [this](https://github.com/npezza93/chat/wiki/Setting-up-ActionCable).
13 | 1. An authentication system must be setup before installing `chat`, like
14 | Devise, Clearance, or your own custom solution. The generator will only run
15 | unless a users table exists.
16 | 1. Chat assumes that a `current_user` view helper
17 | method exists.
18 | 1. Your connection should be identified by `current_user`. You can learn
19 | more about how to set this up by reading
20 | [here](http://edgeguides.rubyonrails.org/action_cable_overview.html#server-side-components-connections).
21 |
22 | #### Installation
23 | Add Chat to your application's Gemfile:
24 |
25 | ```ruby
26 | gem 'chat'
27 | ```
28 |
29 | And then execute:
30 | ```bash
31 | ❯ bundle
32 | ```
33 |
34 | Run the installer to setup migrations and helpers and then migrate:
35 | ```bash
36 | ❯ rails generate chat:install
37 | ❯ rails db:migrate
38 | ```
39 |
40 | Require chat in your app/assets/javascripts/application.js file:
41 |
42 | ```js
43 | //= require chat
44 | ```
45 |
46 | Require chat in your app/assets/stylesheets/application.css file
47 |
48 | ```css
49 | *= require chat
50 | ```
51 |
52 | Finally, add the following line to your application.html.erb file or any view file
53 | you'd like Chat available from:
54 | ```erb
55 | <%= render_chat %>
56 | ```
57 |
58 | ### Dot Commands
59 |
60 | Dot commands behave differently than regular messages and begin with a dot(.).
61 | The following commands are available:
62 | - .shrug followed by an optional message outputs a srugging emoticon
63 | - .gif following by a label fetches a random gif from giphy with the given label
64 |
65 | ## Contributing
66 | Bug reports and pull requests are welcome on GitHub at https://github.com/npezza93/chat.
67 |
68 | ## License
69 | The gem is available as open source under the terms of the
70 | [MIT License](http://opensource.org/licenses/MIT).
71 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | begin
4 | require "bundler/setup"
5 | rescue LoadError
6 | puts "You must `gem install bundler` and `bundle install` to run rake tasks"
7 | end
8 |
9 | require "rdoc/task"
10 |
11 | RDoc::Task.new(:rdoc) do |rdoc|
12 | rdoc.rdoc_dir = "rdoc"
13 | rdoc.title = "Chat"
14 | rdoc.options << "--line-numbers"
15 | rdoc.rdoc_files.include("README.md")
16 | rdoc.rdoc_files.include("lib/**/*.rb")
17 | end
18 |
19 | require "bundler/gem_tasks"
20 |
21 | require "rake/testtask"
22 |
23 | Rake::TestTask.new(:test) do |t|
24 | t.libs << "lib"
25 | t.libs << "test"
26 | t.pattern = "test/**/*_test.rb"
27 | t.verbose = false
28 | end
29 |
30 | task default: :test
31 |
--------------------------------------------------------------------------------
/app/assets/config/chat_manifest.js:
--------------------------------------------------------------------------------
1 | //= link_directory ../javascripts/chat .js
2 | //= link_directory ../stylesheets/chat .css
3 |
--------------------------------------------------------------------------------
/app/assets/images/chat/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/npezza93/chat/c8462876bd068532f6c4ebcf1d32570ea60f1dec/app/assets/images/chat/.keep
--------------------------------------------------------------------------------
/app/assets/javascripts/chat.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require jquery
14 | //= require jquery_ujs
15 | //= require jquery-fileupload/basic
16 | //= require_tree './chat'
17 |
--------------------------------------------------------------------------------
/app/assets/javascripts/chat/cable.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the rails generate channel command.
3 | //
4 | //= require action_cable
5 | //= require_self
6 | //= require_tree ./channels
7 |
8 | (function() {
9 | this.App || (this.App = {});
10 |
11 | App.cable = ActionCable.createConsumer();
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/app/assets/javascripts/chat/channels/message.coffee:
--------------------------------------------------------------------------------
1 | App.chat_message =
2 | App.cable.subscriptions.create 'Chat::MessagesChannel',
3 | collection: -> $("[data-channel='messages']")
4 |
5 | connected: ->
6 | @followCurrentChat()
7 |
8 | received: (data) ->
9 | @collection().append @generateMessage(data)
10 | height = $('.current_chat').prop('scrollHeight')
11 | $('.current_chat').animate { scrollTop: height }, 'slow'
12 |
13 | followCurrentChat: ->
14 | if chatId = @current_chat()
15 | @perform 'follow', chat_id: chatId
16 | else
17 | @perform 'unsubscribed'
18 |
19 | current_user: ->
20 | if @collection().length > 0
21 | @collection().data().currentUser
22 |
23 | current_chat: ->
24 | if @collection().length > 0
25 | @collection().data().currentChat
26 |
27 | div: (classes = "")->
28 | element = document.createElement('div')
29 | element.classList += classes if classes.length > 0
30 | element
31 |
32 | render_container: (user) ->
33 | @div("message-container #{if user == @current_user() then 'right' else ''}")
34 |
35 | render_avatar: (data) ->
36 | last_message_classes = @collection().children().last()[0]
37 | last_message_classes =
38 | if last_message_classes == undefined
39 | []
40 | else
41 | last_message_classes.classList
42 |
43 | if data.user == @current_user() &&
44 | $.inArray("right", last_message_classes) >= 0
45 | @div("transcript_placeholder_avatar").outerHTML
46 | else
47 | data.avatar
48 |
49 | render_content: (data) ->
50 | el = @div("message #{if data.image then 'image' else ''}")
51 | if data.image
52 | el.innerHTML = data.message
53 | else
54 | content = @div("content")
55 | content.innerHTML = data.message
56 | el.innerHTML = content.outerHTML
57 |
58 | el
59 |
60 | generateMessage: (data) ->
61 | container = @render_container(data.user)
62 | container.innerHTML =
63 | @render_avatar(data) + @render_content(data).outerHTML
64 |
65 | container
66 |
--------------------------------------------------------------------------------
/app/assets/javascripts/chat/channels/notification.coffee:
--------------------------------------------------------------------------------
1 | App.chat_notification =
2 | App.cable.subscriptions.create "Chat::NotificationChannel",
3 | connected: ->
4 | @perform 'follow', user_id: @current_user()
5 |
6 | disconnected: ->
7 |
8 | received: (data) ->
9 | if @current_chat() != data.chat_id || !@viewing_transcript()
10 | @add_notification(data.chat_id)
11 | $("#chat__show-#{data.chat_id}").addClass("chat__has_new_message")
12 |
13 | current_user: ->
14 | if $(".chat__launch").length > 0
15 | $(".chat__launch").data().currentUser
16 |
17 | current_chat: ->
18 | App.chat_message.current_chat()
19 |
20 | viewing_transcript: ->
21 | $(".current_chat").css("display") == "flex"
22 |
23 | add_notification: (id) ->
24 | classes = "#chat .chat__settings, .chat__launch"
25 | $(classes).addClass('chat__badge')
26 | $(classes).attr('data-badge', @new_notification_count(id))
27 |
28 | new_notification_count: (id) ->
29 | @notification_count() + @notification_increment(id)
30 |
31 | notification_count: ->
32 | parseInt($("#chat .chat__settings").data('badge') || 0)
33 |
34 | notification_increment: (id) ->
35 | if $("#chat__show-#{id}").hasClass('chat__has_new_message')
36 | 0
37 | else
38 | 1
39 |
40 | decrement: ->
41 | classes = "#chat .chat__settings, .chat__launch"
42 | if @notification_count() == 1
43 | $(classes).removeClass('chat__badge')
44 | $(classes).attr('data-badge', @notification_count() - 1)
45 |
--------------------------------------------------------------------------------
/app/assets/javascripts/chat/channels/status.coffee:
--------------------------------------------------------------------------------
1 | App.chat_status =
2 | App.cable.subscriptions.create "Chat::StatusChannel",
3 | connected: ->
4 | @perform "online", user_id: App.chat_notification.current_user()
5 |
6 | disconnected: ->
7 | @perform "unsubscribed", user_id: App.chat_notification.current_user()
8 |
9 | received: (data) ->
10 | label = $("label[for='conversation_user_ids_#{data.user_id}'] .chat__status")
11 | label.removeClass("offline")
12 | label.removeClass("online")
13 | label.addClass(data.status)
14 |
--------------------------------------------------------------------------------
/app/assets/javascripts/chat/emojionearea.min.js:
--------------------------------------------------------------------------------
1 | !function(a,b,c){"use strict";function i(a){g?a():h.push(a)}function o(a,b,d){var f=!0,g=1;if(b){b=b.toLowerCase();do{var h=1==g?"@"+b:b;e[a.id][h]&&e[a.id][h].length&&c.each(e[a.id][h],function(b,c){return f=c.apply(a,d||[])!==!1})}while(f&&g--)}return f}function p(a,b,d,e){e=e||function(a,b){return c(b.currentTarget)},c.each(d,function(g,h){g=c.isArray(d)?h:g,(f[a.id][h]||(f[a.id][h]=[])).push([b,g,e])})}function q(a,b,c){var e,d=g.imageType;return e="svg"==d?g.imagePathSVG:g.imagePathPNG,a.replace("{name}",c||"").replace("{img}",e+(m<2?b.toUpperCase():b)+"."+d).replace("{uni}",b).replace("{alt}",g.convert(b))}function r(a,b,c){return a.replace(/:?\+?[\w_\-]+:?/g,function(a){a=":"+a.replace(/:$/,"").replace(/^:/,"")+":";var d=g.emojioneList[a];return d?(m>3&&(d=d.unicode),q(b,d[d.length-1],a)):c?"":a})}function s(c){var d,e;if(b.getSelection){if(d=b.getSelection(),d.getRangeAt&&d.rangeCount){e=d.getRangeAt(0),e.deleteContents();var f=a.createElement("div");f.innerHTML=c;for(var h,i,g=a.createDocumentFragment();h=f.firstChild;)i=g.appendChild(h);e.insertNode(g),i&&(e=e.cloneRange(),e.setStartAfter(i),e.collapse(!0),d.removeAllRanges(),d.addRange(e))}}else a.selection&&"Control"!=a.selection.type&&a.selection.createRange().pasteHTML(c)}function u(a){return"object"==typeof a}function v(a){var b=t();if(a&&a.filters){var d=b.filters;c.each(a.filters,function(a,b){return!u(b)||c.isEmptyObject(b)?void delete d[a]:void c.each(b,function(b,c){d[a][b]=c})}),a.filters=d}return c.extend({},b,a)}function z(a,b){return a.replace(y,function(a){var c=g[0===m?"jsecapeMap":"jsEscapeMap"];return"undefined"!=typeof a&&a in c?q(b,c[a]):a})}function A(a,b){return a=a.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'").replace(/`/g,"`").replace(/(?:\r\n|\r|\n)/g,"\n").replace(/(\n+)/g,"
$1
").replace(/\n/g,"
").replace(/
<\/div>/g,""),b.shortnames&&(a=g.shortnameToUnicode(a)),z(a,b.emojiTemplate).replace(/\t/g," ").replace(/ /g," ")}function B(a,b){switch(a=a.replace(/
]*alt="([^"]+)"[^>]*>/gi,"$1").replace(/\n|\r/g,"").replace(/
]*>/gi,"\n").replace(/(?:<(?:div|p|ol|ul|li|pre|code|object)[^>]*>)+/gi,"").replace(/(?:<\/(?:div|p|ol|ul|li|pre|code|object)>)+/gi,"
").replace(/\n<\/div>/gi,"\n").replace(/
<\/div>\n/gi,"\n").replace(/(?:
)+<\/div>/gi,"\n").replace(/([^\n])<\/div>
/gi,"$1\n").replace(/(?:<\/div>)+/gi,"
").replace(/([^\n])<\/div>([^\n])/gi,"$1\n$2").replace(/<\/div>/gi,"").replace(/([^\n])
/gi,"$1\n").replace(/\n
/gi,"\n").replace(/
\n/gi,"\n\n").replace(/<(?:[^>]+)?>/g,"").replace(new RegExp(n,"g"),"").replace(/ /g," ").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/`/g,"`").replace(/&/g,"&"),b.saveEmojisAs){case"image":a=z(a,b.emojiTemplate);break;case"shortname":a=g.toShort(a)}return a}function C(){var a=this,b=a.editor[0].offsetWidth-a.editor[0].clientWidth,c=parseInt(a.button.css("marginRight"));c!==b&&(a.button.css({marginRight:b}),a.floatingPicker&&a.picker.css({right:parseInt(a.picker.css("right"))-c+b}))}function D(){var a=this;if(!a.sprite&&a.lasyEmoji[0]){var b=a.picker.offset().top,d=b+a.picker.height()+20;a.lasyEmoji.each(function(){var a=c(this),e=a.offset().top;e>b&&e
",u(a)?a:{class:E(a,!0)});return c.each(k.call(arguments).slice(1),function(a,d){c.isFunction(d)&&(d=d.call(b)),d&&c(d).appendTo(b)}),b}function G(){return localStorage.getItem("recent_emojis")||""}function H(a){var b=G();if(!a.recent||a.recent!==b){if(b.length){var e,f,d=a.scrollArea.is(".skinnable");d||(e=a.scrollArea.scrollTop(),f=a.recentCategory.is(":visible")?a.recentCategory.height():0);var g=r(b,a.emojiBtnTemplate,!0).split("|").join("");if(a.recentCategory.children(".emojibtn").remove(),c(g).insertAfter(a.recentCategory.children("h1")),a.recentCategory.children(".emojibtn").on("click",function(){a.trigger("emojibtn.click",c(this))}),a.recentFilter.show(),!d){a.recentCategory.show();var h=a.recentCategory.height();f!==h&&a.scrollArea.scrollTop(e+h-f)}}else a.recentFilter.hasClass("active")&&a.recentFilter.removeClass("active").next().addClass("active"),a.recentCategory.hide(),a.recentFilter.hide();a.recent=b}}function I(a,b){var c=G(),d=c.split("|"),e=d.indexOf(b);e!==-1&&d.splice(e,1),d.unshift(b),d.length>9&&d.pop(),localStorage.setItem("recent_emojis",d.join("|")),H(a)}function J(){var a="test";try{return localStorage.setItem(a,a),localStorage.removeItem(a),!0}catch(a){return!1}}function K(d,e,f){f=v(f),d.sprite=f.sprite&&m<3,d.inline=null===f.inline?e.is("INPUT"):f.inline,d.shortnames=f.shortnames,d.saveEmojisAs=f.saveEmojisAs,d.standalone=f.standalone,d.emojiTemplate='
':'emoji" src="{img}"/>'),d.emojiTemplateAlt=d.sprite?'':'
',d.emojiBtnTemplate=''+d.emojiTemplateAlt+"",d.recentEmojis=f.recentEmojis&&J();var h=f.pickerPosition;d.floatingPicker="top"===h||"bottom"===h;var k,q,t,y,z,K,L,M,N,O,i=e.is("TEXTAREA")||e.is("INPUT")?"val":"text",P=F({class:l+(d.standalone?" "+l+"-standalone ":" ")+(e.attr("class")||""),role:"application"},k=d.editor=F("editor").attr({contenteditable:!d.standalone,placeholder:f.placeholder||e.data("placeholder")||e.attr("placeholder")||"",tabindex:0}),N=d.bar=F("bar"),q=d.button=F("button",F("button-open"),F("button-close")).attr("title",f.buttonTitle),t=d.picker=F("picker",F("wrapper",z=F("filters"),O=F("scroll-area",L=F("emojis-list"),y=F("tones",function(){if(f.tones){this.addClass(E("tones-"+f.tonesStyle,!0));for(var a=0;a<=5;a++)this.append(c("",{class:"btn-tone btn-tone-"+a+(a?"":" active"),"data-skin":a,role:"button"}))}})))).addClass(E("picker-position-"+f.pickerPosition,!0)).addClass(E("filters-position-"+f.filtersPosition,!0)).addClass("hidden"));if(k.data(e.data()),c.each(f.attributes,function(a,b){k.attr(a,b)}),c.each(f.filters,function(a,b){var e=0;if("recent"!==a||d.recentEmojis){if("tones"!==a)c("",{class:E("filter",!0)+" "+E("filter-"+a,!0),"data-filter":a,title:b.title}).wrapInner(r(b.icon,d.emojiTemplateAlt)).appendTo(z);else{if(!f.tones)return;e=5}do{var g=F("category").attr({name:a,"data-tone":e}).appendTo(L),h=b.emoji.replace(/[\s,;]+/g,"|");e>0&&(g.hide(),h=h.split("|").join("_tone"+e+"|")+"_tone"+e),"recent"===a&&(h=G()),h=r(h,d.sprite?'':'
',!0).split("|").join(""),g.html(h),c("").text(b.title).prependTo(g)}while(--e>0)}}),f.filters=null,d.sprite||(d.lasyEmoji=L.find(".lazy-emoji")),K=z.find(E("filter")),K.eq(0).addClass("active"),M=L.find(E("category")),d.recentFilter=K.filter('[data-filter="recent"]'),d.recentCategory=M.filter("[name=recent]"),d.scrollArea=O,f.container?c(f.container).wrapInner(P):P.insertAfter(e),f.hideSource&&e.hide(),d.setText(e[i]()),e[i](d.getText()),C.apply(d),d.standalone&&!d.getText().length){var Q=c(e).data("emoji-placeholder")||f.emojiPlaceholder;d.setText(Q),k.addClass("has-placeholder")}p(d,L.find(".emojibtn"),{click:"emojibtn.click"}),p(d,b,{resize:"!resize"}),p(d,y.children(),{click:"tone.click"}),p(d,[t,q],{mousedown:"!mousedown"},k),p(d,q,{click:"button.click"}),p(d,k,{paste:"!paste"},k),p(d,k,["focus","blur"],function(){return!d.stayFocused&&k}),p(d,t,{mousedown:"picker.mousedown",mouseup:"picker.mouseup",click:"picker.click",keyup:"picker.keyup",keydown:"picker.keydown",keypress:"picker.keypress"}),p(d,k,["mousedown","mouseup","click","keyup","keydown","keypress"]),p(d,t.find(".emojionearea-filter"),{click:"filter.click"});var R=!1;if(O.on("scroll",function(){if(!R&&(D.call(d),O.is(":not(.skinnable)"))){var a=M.eq(0),b=O.offset().top;M.each(function(d,e){return!(c(e).offset().top-b>=10)&&void(a=c(e))});var e=K.filter('[data-filter="'+a.attr("name")+'"]');e[0]&&!e.is(".active")&&(K.removeClass("active"),e.addClass("active"))}}),d.on("@filter.click",function(a){var b=a.is(".active");if(O.is(".skinnable")){if(b)return;y.children().eq(0).click()}R=!0,b||(K.filter(".active").removeClass("active"),a.addClass("active"));var c=M.filter('[name="'+a.data("filter")+'"]').offset().top,e=O.scrollTop(),f=O.offset().top;O.stop().animate({scrollTop:c+e-f-2},200,"swing",function(){D.call(d),R=!1})}).on("@picker.show",function(){d.recentEmojis&&H(d),D.call(this)}).on("@tone.click",function(a){y.children().removeClass("active");var b=a.addClass("active").data("skin");b?(O.addClass("skinnable"),M.hide().filter("[data-tone="+b+"]").show(),K.eq(0).is('.active[data-filter="recent"]')&&K.eq(0).removeClass("active").next().addClass("active")):(O.removeClass("skinnable"),M.hide().filter("[data-tone=0]").show(),K.eq(0).click()),D.call(d)}).on("@button.click",function(a){a.is(".active")?d.hidePicker():d.showPicker()}).on("@!paste",function(a,e){var f=function(b){var e="caret-"+(new Date).getTime(),f=A(b,d);s(f),s(''),a.scrollTop(i);var g=c("#"+e),h=g.offset().top-a.offset().top,j=a.height();(i+h>=j||i>h)&&a.scrollTop(i+h-2*j/3),g.remove(),d.stayFocused=!1,C.apply(d),o(d,"paste",[a,b,f])};if(e.originalEvent.clipboardData){var g=e.originalEvent.clipboardData.getData("text/plain");return f(g),e.preventDefault?e.preventDefault():e.stop(),e.returnValue=!1,e.stopPropagation(),!1}d.stayFocused=!0,s(""+n+"");var h=w(a[0]),i=a.scrollTop(),j=c("",{contenteditable:!0}).css({position:"fixed",left:"-999px",width:"1px",height:"1px",top:"20px",overflow:"hidden"}).appendTo(c("BODY")).focus();b.setTimeout(function(){a.focus(),x(a[0],h);var b=B(j.html().replace(/\r\n|\n|\r/g,"
"),d);j.remove(),f(b)},200)}).on("@emojibtn.click",function(a){k.removeClass("has-placeholder"),P.is(".focused")||k.focus(),d.standalone?(k.html(r(a.data("name"),d.emojiTemplate)),d.trigger("blur")):(w(k[0]),s(r(a.data("name"),d.emojiTemplate))),d.recentEmojis&&I(d,a.data("name"))}).on("@!resize @keyup @emojibtn.click",C).on("@!mousedown",function(a,b){return P.is(".focused")||a.focus(),b.preventDefault(),!1}).on("@change",function(){var a=d.editor.html().replace(/<\/?(?:div|span|p)[^>]*>/gi,"");a.length&&!/^
]*>$/i.test(a)||d.editor.html(d.content=""),e[i](d.getText())}).on("@focus",function(){P.addClass("focused")}).on("@blur",function(){P.removeClass("focused"),f.hidePickerOnBlur&&d.hidePicker();var a=d.editor.html();d.content!==a?(d.content=a,o(d,"change",[d.editor]),e.blur().trigger("change")):e.blur()}),f.shortcuts&&d.on("@keydown",function(a,b){b.ctrlKey||(9==b.which?(b.preventDefault(),q.click()):27==b.which&&(b.preventDefault(),q.is(".active")&&d.hidePicker()))}),u(f.events)&&!c.isEmptyObject(f.events)&&c.each(f.events,function(a,b){d.on(a.replace(/_/g,"."),b)}),f.autocomplete){var S=function(){var a={maxCount:f.textcomplete.maxCount,placement:f.textcomplete.placement};f.shortcuts&&(a.onKeydown=function(a,b){if(!a.ctrlKey&&13==a.which)return b.KEY_ENTER});var b=c.map(g.emojioneList,function(a,b){return f.autocompleteTones?b:/_tone[12345]/.test(b)?null:b});b.sort(),k.textcomplete([{id:l,match:/\B(:[\-+\w]*)$/,search:function(a,d){d(c.map(b,function(b){return 0===b.indexOf(a)?b:null}))},template:function(a){return r(a,d.emojiTemplate)+" "+a.replace(/:/g,"")},replace:function(a){return r(a,d.emojiTemplate)},cache:!0,index:1}],a),f.textcomplete.placement&&"static"==k.data("textComplete").option.appendTo.css("position")&&k.data("textComplete").option.appendTo.css("position","relative")};c.fn.textcomplete?S():c.getScript("https://cdn.rawgit.com/yuku-t/jquery-textcomplete/v1.3.4/dist/jquery.textcomplete.js",S)}d.inline&&(P.addClass(E("inline",!0)),d.on("@keydown",function(a,b){13==b.which&&b.preventDefault()})),/firefox/i.test(navigator.userAgent)&&a.execCommand("enableObjectResizing",!1,!1)}function N(d){function e(a){var b=a.cacheBustParam;return u(a.jsEscapeMap)?"?v=1.2.4"===b?"2.0.0":"?v=2.0.1"===b?"2.1.0":"?v=2.1.1"===b?"2.1.1":"?v=2.1.2"===b?"2.1.2":"?v=2.1.3"===b?"2.1.3":"2.1.4":"1.5.2"}function f(a){switch(a){case"1.5.2":return 0;case"2.0.0":return 1;case"2.1.0":case"2.1.1":return 2;case"2.1.2":return 3;case"2.1.3":case"2.1.4":default:return 4}}d=v(d),M.isLoading||(!g||f(e(g))<2?(M.isLoading=!0,c.getScript(M.defaultBase+L+"/lib/js/emojione.min.js",function(){if(g=b.emojione,L=e(g),m=f(L),M.base=M.defaultBase+L+"/assets",d.sprite){var i=M.base+"/sprites/emojione.sprites.css";a.createStyleSheet?a.createStyleSheet(i):c("",{rel:"stylesheet",href:i}).appendTo("head")}for(;h.length;)h.shift().call();M.isLoading=!1})):(L=e(g),m=f(L),M.base=M.defaultBase+L+"/assets")),i(function(){d.useInternalCDN&&(g.imagePathPNG=M.base+"/png/",g.imagePathSVG=M.base+"/svg/",g.imagePathSVGSprites=M.base+"/sprites/emojione.sprites.svg",g.imageType=d.imageType),y=new RegExp("|]*>.*?|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|("+g.unicodeRegexp+")","gi")})}function P(a,b){b=b.replace(/^@/,"");var d=a.id;f[d][b]&&(c.each(f[d][b],function(d,e){c.each(c.isArray(e[0])?e[0]:[e[0]],function(d,f){c(f).on(e[1],function(){var d=k.call(arguments),f=c.isFunction(e[2])?e[2].apply(a,[b].concat(d)):e[2];f&&o(a,b,[f].concat(d))})})}),f[d][b]=null)}var w,x,d=0,e={},f={},g=b.emojione,h=[],j="data:image/gif;base64,R0lGODlhAQABAJH/AP///wAAAMDAwAAAACH5BAEAAAIALAAAAAABAAEAAAICVAEAOw==",k=[].slice,l="emojionearea",m=0,n="",t=function(){return c.fn.emojioneArea&&c.fn.emojioneArea.defaults?c.fn.emojioneArea.defaults:{attributes:{dir:"ltr",spellcheck:!1,autocomplete:"off",autocorrect:"off",autocapitalize:"off"},placeholder:null,emojiPlaceholder:":smiley:",container:null,hideSource:!0,shortnames:!0,sprite:!0,pickerPosition:"top",filtersPosition:"top",hidePickerOnBlur:!0,buttonTitle:"Use the TAB key to insert emoji faster",tones:!0,tonesStyle:"bullet",inline:null,saveEmojisAs:"unicode",shortcuts:!0,autocomplete:!0,autocompleteTones:!1,standalone:!1,useInternalCDN:!0,imageType:"png",recentEmojis:!0,textcomplete:{maxCount:15,placement:null},filters:{tones:{title:"Diversity",emoji:"santa runner surfer swimmer lifter ear nose point_up_2 point_down point_left point_right punch wave ok_hand thumbsup thumbsdown clap open_hands boy girl man woman cop bride_with_veil person_with_blond_hair man_with_gua_pi_mao man_with_turban older_man grandma baby construction_worker princess angel information_desk_person guardsman dancer nail_care massage haircut muscle spy hand_splayed middle_finger vulcan no_good ok_woman bow raising_hand raised_hands person_frowning person_with_pouting_face pray rowboat bicyclist mountain_bicyclist walking bath metal point_up basketball_player fist raised_hand v writing_hand"},recent:{icon:"clock3",title:"Recent",emoji:""},smileys_people:{icon:"yum",title:"Smileys & People",emoji:"grinning grimacing grin joy smiley smile sweat_smile laughing innocent wink blush slight_smile upside_down relaxed yum relieved heart_eyes kissing_heart kissing kissing_smiling_eyes kissing_closed_eyes stuck_out_tongue_winking_eye stuck_out_tongue_closed_eyes stuck_out_tongue money_mouth nerd sunglasses hugging smirk no_mouth neutral_face expressionless unamused rolling_eyes thinking flushed disappointed worried angry rage pensive confused slight_frown frowning2 persevere confounded tired_face weary triumph open_mouth scream fearful cold_sweat hushed frowning anguished cry disappointed_relieved sleepy sweat sob dizzy_face astonished zipper_mouth mask thermometer_face head_bandage sleeping zzz poop smiling_imp imp japanese_ogre japanese_goblin skull ghost alien robot smiley_cat smile_cat joy_cat heart_eyes_cat smirk_cat kissing_cat scream_cat crying_cat_face pouting_cat raised_hands clap wave thumbsup thumbsdown punch fist v ok_hand raised_hand open_hands muscle pray point_up point_up_2 point_down point_left point_right middle_finger hand_splayed metal vulcan writing_hand nail_care lips tongue ear nose eye eyes bust_in_silhouette busts_in_silhouette speaking_head baby boy girl man woman person_with_blond_hair older_man older_woman man_with_gua_pi_mao man_with_turban cop construction_worker guardsman spy santa angel princess bride_with_veil walking runner dancer dancers couple two_men_holding_hands two_women_holding_hands bow information_desk_person no_good ok_woman raising_hand person_with_pouting_face person_frowning haircut massage couple_with_heart couple_ww couple_mm couplekiss kiss_ww kiss_mm family family_mwg family_mwgb family_mwbb family_mwgg family_wwb family_wwg family_wwgb family_wwbb family_wwgg family_mmb family_mmg family_mmgb family_mmbb family_mmgg womans_clothes shirt jeans necktie dress bikini kimono lipstick kiss footprints high_heel sandal boot mans_shoe athletic_shoe womans_hat tophat helmet_with_cross mortar_board crown school_satchel pouch purse handbag briefcase eyeglasses dark_sunglasses ring closed_umbrella"},animals_nature:{icon:"hamster",title:"Animals & Nature",emoji:"dog cat mouse hamster rabbit bear panda_face koala tiger lion_face cow pig pig_nose frog octopus monkey_face see_no_evil hear_no_evil speak_no_evil monkey chicken penguin bird baby_chick hatching_chick hatched_chick wolf boar horse unicorn bee bug snail beetle ant spider scorpion crab snake turtle tropical_fish fish blowfish dolphin whale whale2 crocodile leopard tiger2 water_buffalo ox cow2 dromedary_camel camel elephant goat ram sheep racehorse pig2 rat mouse2 rooster turkey dove dog2 poodle cat2 rabbit2 chipmunk feet dragon dragon_face cactus christmas_tree evergreen_tree deciduous_tree palm_tree seedling herb shamrock four_leaf_clover bamboo tanabata_tree leaves fallen_leaf maple_leaf ear_of_rice hibiscus sunflower rose tulip blossom cherry_blossom bouquet mushroom chestnut jack_o_lantern shell spider_web earth_americas earth_africa earth_asia full_moon waning_gibbous_moon last_quarter_moon waning_crescent_moon new_moon waxing_crescent_moon first_quarter_moon waxing_gibbous_moon new_moon_with_face full_moon_with_face first_quarter_moon_with_face last_quarter_moon_with_face sun_with_face crescent_moon star star2 dizzy sparkles comet sunny white_sun_small_cloud partly_sunny white_sun_cloud white_sun_rain_cloud cloud cloud_rain thunder_cloud_rain cloud_lightning zap fire boom snowflake cloud_snow snowman2 snowman wind_blowing_face dash cloud_tornado fog umbrella2 umbrella droplet sweat_drops ocean"},food_drink:{icon:"pizza",title:"Food & Drink",emoji:"green_apple apple pear tangerine lemon banana watermelon grapes strawberry melon cherries peach pineapple tomato eggplant hot_pepper corn sweet_potato honey_pot bread cheese poultry_leg meat_on_bone fried_shrimp egg hamburger fries hotdog pizza spaghetti taco burrito ramen stew fish_cake sushi bento curry rice_ball rice rice_cracker oden dango shaved_ice ice_cream icecream cake birthday custard candy lollipop chocolate_bar popcorn doughnut cookie beer beers wine_glass cocktail tropical_drink champagne sake tea coffee baby_bottle fork_and_knife fork_knife_plate"},activity:{icon:"basketball",title:"Activity",emoji:"soccer basketball football baseball tennis volleyball rugby_football 8ball golf golfer ping_pong badminton hockey field_hockey cricket ski skier snowboarder ice_skate bow_and_arrow fishing_pole_and_fish rowboat swimmer surfer bath basketball_player lifter bicyclist mountain_bicyclist horse_racing levitate trophy running_shirt_with_sash medal military_medal reminder_ribbon rosette ticket tickets performing_arts art circus_tent microphone headphones musical_score musical_keyboard saxophone trumpet guitar violin clapper video_game space_invader dart game_die slot_machine bowling"},travel_places:{icon:"rocket",title:"Travel & Places",emoji:"red_car taxi blue_car bus trolleybus race_car police_car ambulance fire_engine minibus truck articulated_lorry tractor motorcycle bike rotating_light oncoming_police_car oncoming_bus oncoming_automobile oncoming_taxi aerial_tramway mountain_cableway suspension_railway railway_car train monorail bullettrain_side bullettrain_front light_rail mountain_railway steam_locomotive train2 metro tram station helicopter airplane_small airplane airplane_departure airplane_arriving sailboat motorboat speedboat ferry cruise_ship rocket satellite_orbital seat anchor construction fuelpump busstop vertical_traffic_light traffic_light checkered_flag ship ferris_wheel roller_coaster carousel_horse construction_site foggy tokyo_tower factory fountain rice_scene mountain mountain_snow mount_fuji volcano japan camping tent park motorway railway_track sunrise sunrise_over_mountains desert beach island city_sunset city_dusk cityscape night_with_stars bridge_at_night milky_way stars sparkler fireworks rainbow homes european_castle japanese_castle stadium statue_of_liberty house house_with_garden house_abandoned office department_store post_office european_post_office hospital bank hotel convenience_store school love_hotel wedding classical_building church mosque synagogue kaaba shinto_shrine"},objects:{icon:"bulb",title:"Objects",emoji:"watch iphone calling computer keyboard desktop printer mouse_three_button trackball joystick compression minidisc floppy_disk cd dvd vhs camera camera_with_flash video_camera movie_camera projector film_frames telephone_receiver telephone pager fax tv radio microphone2 level_slider control_knobs stopwatch timer alarm_clock clock hourglass_flowing_sand hourglass satellite battery electric_plug bulb flashlight candle wastebasket oil money_with_wings dollar yen euro pound moneybag credit_card gem scales wrench hammer hammer_pick tools pick nut_and_bolt gear chains gun bomb knife dagger crossed_swords shield smoking skull_crossbones coffin urn amphora crystal_ball prayer_beads barber alembic telescope microscope hole pill syringe thermometer label bookmark toilet shower bathtub key key2 couch sleeping_accommodation bed door bellhop frame_photo map beach_umbrella moyai shopping_bags balloon flags ribbon gift confetti_ball tada dolls wind_chime crossed_flags izakaya_lantern envelope envelope_with_arrow incoming_envelope e-mail love_letter postbox mailbox_closed mailbox mailbox_with_mail mailbox_with_no_mail package postal_horn inbox_tray outbox_tray scroll page_with_curl bookmark_tabs bar_chart chart_with_upwards_trend chart_with_downwards_trend page_facing_up date calendar calendar_spiral card_index card_box ballot_box file_cabinet clipboard notepad_spiral file_folder open_file_folder dividers newspaper2 newspaper notebook closed_book green_book blue_book orange_book notebook_with_decorative_cover ledger books book link paperclip paperclips scissors triangular_ruler straight_ruler pushpin round_pushpin triangular_flag_on_post flag_white flag_black closed_lock_with_key lock unlock lock_with_ink_pen pen_ballpoint pen_fountain black_nib pencil pencil2 crayon paintbrush mag mag_right"},symbols:{icon:"heartpulse",title:"Symbols",emoji:"heart yellow_heart green_heart blue_heart purple_heart broken_heart heart_exclamation two_hearts revolving_hearts heartbeat heartpulse sparkling_heart cupid gift_heart heart_decoration peace cross star_and_crescent om_symbol wheel_of_dharma star_of_david six_pointed_star menorah yin_yang orthodox_cross place_of_worship ophiuchus aries taurus gemini cancer leo virgo libra scorpius sagittarius capricorn aquarius pisces id atom u7a7a u5272 radioactive biohazard mobile_phone_off vibration_mode u6709 u7121 u7533 u55b6 u6708 eight_pointed_black_star vs accept white_flower ideograph_advantage secret congratulations u5408 u6e80 u7981 a b ab cl o2 sos no_entry name_badge no_entry_sign x o anger hotsprings no_pedestrians do_not_litter no_bicycles non-potable_water underage no_mobile_phones exclamation grey_exclamation question grey_question bangbang interrobang 100 low_brightness high_brightness trident fleur-de-lis part_alternation_mark warning children_crossing beginner recycle u6307 chart sparkle eight_spoked_asterisk negative_squared_cross_mark white_check_mark diamond_shape_with_a_dot_inside cyclone loop globe_with_meridians m atm sa passport_control customs baggage_claim left_luggage wheelchair no_smoking wc parking potable_water mens womens baby_symbol restroom put_litter_in_its_place cinema signal_strength koko ng ok up cool new free zero one two three four five six seven eight nine ten 1234 arrow_forward pause_button play_pause stop_button record_button track_next track_previous fast_forward rewind twisted_rightwards_arrows repeat repeat_one arrow_backward arrow_up_small arrow_down_small arrow_double_up arrow_double_down arrow_right arrow_left arrow_up arrow_down arrow_upper_right arrow_lower_right arrow_lower_left arrow_upper_left arrow_up_down left_right_arrow arrows_counterclockwise arrow_right_hook leftwards_arrow_with_hook arrow_heading_up arrow_heading_down hash asterisk information_source abc abcd capital_abcd symbols musical_note notes wavy_dash curly_loop heavy_check_mark arrows_clockwise heavy_plus_sign heavy_minus_sign heavy_division_sign heavy_multiplication_x heavy_dollar_sign currency_exchange copyright registered tm end back on top soon ballot_box_with_check radio_button white_circle black_circle red_circle large_blue_circle small_orange_diamond small_blue_diamond large_orange_diamond large_blue_diamond small_red_triangle black_small_square white_small_square black_large_square white_large_square small_red_triangle_down black_medium_square white_medium_square black_medium_small_square white_medium_small_square black_square_button white_square_button speaker sound loud_sound mute mega loudspeaker bell no_bell black_joker mahjong spades clubs hearts diamonds flower_playing_cards thought_balloon anger_right speech_balloon clock1 clock2 clock3 clock4 clock5 clock6 clock7 clock8 clock9 clock10 clock11 clock12 clock130 clock230 clock330 clock430 clock530 clock630 clock730 clock830 clock930 clock1030 clock1130 clock1230 eye_in_speech_bubble"},flags:{icon:"flag_gb",title:"Flags",emoji:"ac af al dz ad ao ai ag ar am aw au at az bs bh bd bb by be bz bj bm bt bo ba bw br bn bg bf bi cv kh cm ca ky cf td flag_cl cn co km cg flag_cd cr hr cu cy cz dk dj dm do ec eg sv gq er ee et fk fo fj fi fr pf ga gm ge de gh gi gr gl gd gu gt gn gw gy ht hn hk hu is in flag_id ir iq ie il it ci jm jp je jo kz ke ki xk kw kg la lv lb ls lr ly li lt lu mo mk mg mw my mv ml mt mh mr mu mx fm md mc mn me ms ma mz mm na nr np nl nc nz ni ne flag_ng nu kp no om pk pw ps pa pg py pe ph pl pt pr qa ro ru rw sh kn lc vc ws sm st flag_sa sn rs sc sl sg sk si sb so za kr es lk sd sr sz se ch sy tw tj tz th tl tg to tt tn tr flag_tm flag_tm ug ua ae gb us vi uy uz vu va ve vn wf eh ye zm zw re ax ta io bq cx cc gg im yt nf pn bl pm gs tk bv hm sj um ic ea cp dg as aq vg ck cw eu gf tf gp mq mp sx ss tc "}}}};b.getSelection&&a.createRange?(w=function(a){var c=b.getSelection&&b.getSelection();if(c&&c.rangeCount>0){var d=c.getRangeAt(0),e=d.cloneRange();return e.selectNodeContents(a),e.setEnd(d.startContainer,d.startOffset),e.toString().length}},x=function(c,d){var e=0,f=a.createRange();f.setStart(c,0),f.collapse(!0);for(var h,g=[c],i=!1,j=!1;!j&&(h=g.pop());)if(3==h.nodeType){var k=e+h.length;!i&&d>=e&&d<=k&&(f.setStart(h,d-e),f.setEnd(h,d-e),j=!0),e=k}else for(var l=h.childNodes.length;l--;)g.push(h.childNodes[l]);d=b.getSelection(),d.removeAllRanges(),d.addRange(f)}):a.selection&&a.body.createTextRange&&(w=function(b){var c=a.selection.createRange(),d=a.body.createTextRange();d.moveToElementText(b),d.setEndPoint("EndToStart",c);var e=d.text.length;return e+c.text.length},x=function(b,c){var d=a.body.createTextRange();d.moveToElementText(b),d.collapse(!0),d.moveEnd("character",c),d.moveStart("character",c),d.select()});var y,L=b.emojioneVersion||"2.1.4",M={defaultBase:"https://cdnjs.cloudflare.com/ajax/libs/emojione/",base:null,isLoading:!1},O=function(a,b){var c=this;N(b),e[c.id=++d]={},f[c.id]={},i(function(){K(c,a,b)})};O.prototype.on=function(a,b){if(a&&c.isFunction(b)){var d=this;c.each(a.toLowerCase().split(" "),function(a,c){P(d,c),(e[d.id][c]||(e[d.id][c]=[])).push(b)})}return this},O.prototype.off=function(a,b){if(a){var d=this.id;c.each(a.toLowerCase().replace(/_/g,".").split(" "),function(a,f){e[d][f]&&!/^@/.test(f)&&(b?c.each(e[d][f],function(a,c){c===b&&(e[d][f]=e[d][f].splice(a,1))}):e[d][f]=[])})}return this},O.prototype.trigger=function(){var a=k.call(arguments),b=[this].concat(a.slice(0,1));return b.push(a.slice(1)),o.apply(this,b)},O.prototype.setFocus=function(){var a=this;return i(function(){a.editor.focus()}),a},O.prototype.setText=function(a){var b=this;return i(function(){b.editor.html(A(a,b)),b.content=b.editor.html(),o(b,"change",[b.editor]),C.apply(b)}),b},O.prototype.getText=function(){return B(this.editor.html(),this)},O.prototype.showPicker=function(){var a=this;return a._sh_timer&&b.clearTimeout(a._sh_timer),a.picker.removeClass("hidden"),a._sh_timer=b.setTimeout(function(){a.button.addClass("active")},50),o(a,"picker.show",[a.picker]),a},O.prototype.hidePicker=function(){var a=this;return a._sh_timer&&b.clearTimeout(a._sh_timer),a.button.removeClass("active"),a._sh_timer=b.setTimeout(function(){a.picker.addClass("hidden")},500),o(a,"picker.hide",[a.picker]),a},c.fn.emojioneArea=function(a){return this.each(function(){return this.emojioneArea?this.emojioneArea:(c.data(this,"emojioneArea",this.emojioneArea=new O(c(this),a)),this.emojioneArea)})},c.fn.emojioneArea.defaults=t()}(document,window,jQuery);
2 |
--------------------------------------------------------------------------------
/app/assets/javascripts/chat/messages.coffee:
--------------------------------------------------------------------------------
1 | event =
2 | if typeof(Turbolinks) == "undefined"
3 | 'DOMContentLoaded'
4 | else
5 | 'turbolinks:load'
6 |
7 | document.addEventListener event, ->
8 | $('.chat__launch').click ->
9 | $('#chat').addClass('active')
10 | $('.chat__launch').addClass('chat__disappear')
11 | return
12 |
13 | $('.chat__close').click ->
14 | $('#chat').removeClass('active')
15 | $('.chat__launch').removeClass('chat__disappear')
16 | return
17 |
18 | $('.chat__settings').click ->
19 | $('.current_chat').removeClass("slide_left")
20 | $('.chat__list').removeClass("slide_left")
21 | $('.chat__list').removeClass("slide_right")
22 | $('.chat__start-new').removeClass("chat__disappear")
23 | $('.new_conversation').removeClass("slide_right")
24 | return
25 |
26 | $(document).on 'click', '.chat__start-new', (e) ->
27 | e.preventDefault()
28 | $('.chat__start-new').addClass("chat__disappear")
29 | $('.chat__list').addClass("slide_right")
30 | $('.new_conversation').addClass("slide_right")
31 | return
32 |
33 | $(document).on 'click', '.chat__insert-image', ->
34 | $(".new_message input[type='file']").click()
35 | return
36 |
37 | $(document).on 'keyup', '.new_message .emojionearea-editor', (e) ->
38 | if e.keyCode == 13 && !e.shiftKey
39 | str = $('.new_message .emojionearea-editor').html()
40 | $('.new_message textarea').val(str)
41 | $('.new_message').submit()
42 | $('#message_text').prop('value', '')
43 | el = $('.new_message textarea').emojioneArea()
44 | el[0].emojioneArea.setText('')
45 | return
46 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/chat.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require material_icons
14 | *= require_tree ./chat
15 | */
16 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/chat/avatar_grid.sass:
--------------------------------------------------------------------------------
1 | #chat .chat__list .avatar_container
2 | width: 50px
3 | height: 50px
4 | position: relative
5 | margin: 10px 0
6 | color: #212121
7 |
8 | &.count_1
9 | img
10 | width: 100%
11 | height: 100%
12 | border-radius: 50%
13 | object-fit: cover
14 |
15 | &.count_2, &.count_default
16 | img
17 | width: 50%
18 | height: 50%
19 | border-radius: 50%
20 | object-fit: cover
21 | img:nth-of-type(2)
22 | position: absolute
23 | bottom: 0
24 | right: 0
25 | img:nth-of-type(1)
26 | position: absolute
27 | left: 0
28 | top: 0
29 | div
30 | width: 50%
31 | height: 50%
32 | border-radius: 50%
33 | background-color: #fff
34 | position: absolute
35 | bottom: 0
36 | right: 0
37 | span
38 | position: absolute
39 | left: 33%
40 | top: 6%
41 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/chat/chat.sass:
--------------------------------------------------------------------------------
1 | @import "https://fonts.googleapis.com/css?family=Lato:300,400,700,900"
2 |
3 | #chat
4 | font-family: "Lato" !important
5 | letter-spacing: 1.5px !important
6 | width: 375px
7 | height: 100%
8 | position: fixed
9 | right: 0
10 | top: 0
11 | opacity: 0
12 | background: transparent
13 | visibility: hidden
14 | transform: scale(0)
15 | transform-origin: bottom right
16 | transition: transform .3s ease-in-out, opacity .3s ease-in-out
17 | overflow: hidden
18 | @media (max-width: 450px)
19 | width: 100%
20 | &.active
21 | visibility: visible
22 | opacity: 1
23 | transform: scale(1)
24 | .chat__flex
25 | flex: 1
26 | #chat__background
27 | height: 100%
28 | width: 100%
29 | position: absolute
30 | background-color: rgba(40,44,52, 0.75)
31 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/chat/checkbox.sass:
--------------------------------------------------------------------------------
1 | .new_conversation
2 | [type="checkbox"]:not(:checked), [type="checkbox"]:checked
3 | position: absolute
4 | left: -9999px
5 | opacity: 0
6 |
7 | [type="checkbox"] + label
8 | position: relative
9 | padding-left: 35px
10 | cursor: pointer
11 | display: inline-block
12 | height: 25px
13 | line-height: 25px
14 | font-size: 1rem
15 | -webkit-user-select: none
16 | -moz-user-select: none
17 | -khtml-user-select: none
18 | -ms-user-select: none
19 |
20 | [type="checkbox"] + label:before, [type="checkbox"]:not(.filled-in) + label:after
21 | content: ''
22 | position: absolute
23 | top: -2px
24 | left: 0
25 | width: 21px
26 | height: 21px
27 | z-index: 0
28 | border: 2px solid #fff
29 | border-radius: 1px
30 | margin-top: 2px
31 | transition: .2s
32 |
33 | [type="checkbox"]:not(.filled-in) + label:after
34 | border: 0
35 | -webkit-transform: scale(0)
36 | transform: scale(0)
37 |
38 | [type="checkbox"]:not(:checked):disabled + label:before
39 | border: none
40 | background-color: rgba(0, 0, 0, 0.26)
41 |
42 | [type="checkbox"]:checked + label:before
43 | top: -8px
44 | left: -5px
45 | width: 14px
46 | height: 26px
47 | border-top: 2px solid transparent
48 | border-left: 2px solid transparent
49 | border-right: 2px solid #A3EEA0
50 | border-bottom: 2px solid #A3EEA0
51 | -webkit-transform: rotate(40deg)
52 | transform: rotate(40deg)
53 | -webkit-backface-visibility: hidden
54 | backface-visibility: hidden
55 | -webkit-transform-origin: 100% 100%
56 | transform-origin: 100% 100%
57 |
58 | [type="checkbox"]:checked:disabled + label:before
59 | border-right: 2px solid rgba(0, 0, 0, 0.26)
60 | border-bottom: 2px solid rgba(0, 0, 0, 0.26)
61 |
62 | [type="checkbox"]:indeterminate + label:before
63 | top: -11px
64 | left: -12px
65 | width: 10px
66 | height: 22px
67 | border-top: none
68 | border-left: none
69 | border-right: 2px solid #e50000
70 | border-bottom: none
71 | -webkit-transform: rotate(90deg)
72 | transform: rotate(90deg)
73 | -webkit-backface-visibility: hidden
74 | backface-visibility: hidden
75 | -webkit-transform-origin: 100% 100%
76 | transform-origin: 100% 100%
77 |
78 | [type="checkbox"]:indeterminate:disabled + label:before
79 | border-right: 2px solid rgba(0, 0, 0, 0.26)
80 | background-color: transparent
81 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/chat/emojionearea.min.css:
--------------------------------------------------------------------------------
1 | .dropdown-menu.textcomplete-dropdown[data-strategy=emojionearea]{position:absolute;z-index:1000;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;-moz-box-shadow:0 6px 12px rgba(0,0,0,.175);-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.textcomplete-dropdown[data-strategy=emojionearea] li.textcomplete-item{font-size:14px;padding:1px 3px;border:0}.dropdown-menu.textcomplete-dropdown[data-strategy=emojionearea] li.textcomplete-item a{text-decoration:none;display:block;height:100%;line-height:1.8em;padding:0 1.54em 0 .615em;color:#4f4f4f}.dropdown-menu.textcomplete-dropdown[data-strategy=emojionearea] li.textcomplete-item.active,.dropdown-menu.textcomplete-dropdown[data-strategy=emojionearea] li.textcomplete-item:hover{background-color:#e4e4e4}.dropdown-menu.textcomplete-dropdown[data-strategy=emojionearea] li.textcomplete-item.active a,.dropdown-menu.textcomplete-dropdown[data-strategy=emojionearea] li.textcomplete-item:hover a{color:#333}.dropdown-menu.textcomplete-dropdown[data-strategy=emojionearea] li.textcomplete-item .emojioneemoji{font-size:inherit;height:2ex;width:2.1ex;min-height:20px;min-width:20px;display:inline-block;margin:0 5px .2ex 0;line-height:normal;vertical-align:middle;max-width:100%;top:0}.emojionearea,.emojionearea *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.emojionearea,.emojionearea.form-control{display:block;position:relative!important;width:100%;height:auto;padding:0;font-size:14px}.emojionearea .emojionearea-editor{display:block;height:auto;max-height:80px;overflow:auto;padding:6px 57px 6px 0;line-height:1.42857143;font-size:17px;color:#212121;background-color:transparent;border:0;cursor:text;margin-right:1px;border-bottom:1px solid rgba(0,0,0,.26);word-wrap:break-word}.emojionearea .emojionearea-editor:empty:before{content:attr(placeholder);display:block;color:#bbb}.emojionearea .emojionearea-editor:focus{border:0;outline:0;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none}.emojionearea .emojionearea-editor .emojioneemoji,.emojionearea .emojionearea-editor [class*=emojione-]{font-size:inherit;height:2ex;width:2.1ex;min-height:20px;min-width:20px;display:inline-block;margin:-.2ex .15em .2ex;line-height:normal;vertical-align:middle;max-width:100%;top:0}.emojionearea.emojionearea-inline{height:34px}.emojionearea.emojionearea-inline>.emojionearea-editor{height:32px;min-height:20px;overflow:hidden;white-space:nowrap;position:absolute;top:0;left:12px;right:24px;padding:6px 0}.emojionearea.emojionearea-inline>.emojionearea-button{top:4px}.emojionearea .emojionearea-button{z-index:5;position:absolute;right:3px;top:3px;width:24px;height:24px;cursor:pointer;-moz-transition:opacity .3s ease-in-out;-o-transition:opacity .3s ease-in-out;-webkit-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out}.emojionearea .emojionearea-button:hover{opacity:1}.emojionearea .emojionearea-button>div{display:block;width:24px;height:24px;position:absolute;-moz-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;-webkit-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.emojionearea .emojionearea-button>div.emojionearea-button-open{background-position:0 -24px;filter:alpha(enabled=false);opacity:1}.emojionearea .emojionearea-button>div.emojionearea-button-close{background-position:0 0;}.emojionearea .emojionearea-button.active>div.emojionearea-button-open{filter:alpha(Opacity=0);opacity:0}.emojionearea .emojionearea-button.active>div.emojionearea-button-close{filter:alpha(enabled=false);}.emojionearea .emojionearea-picker{background:#fff;position:absolute;-moz-box-shadow:0 1px 5px rgba(0,0,0,.32);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.32);box-shadow:0 1px 5px rgba(0,0,0,.32);-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;height:236px;width:316px;top:-15px;right:-15px;z-index:90;-moz-transition:all .25s ease-in-out;-o-transition:all .25s ease-in-out;-webkit-transition:all .25s ease-in-out;transition:all .25s ease-in-out;filter:alpha(Opacity=0);opacity:0;-moz-user-select:-moz-none;-ms-user-select:none;-webkit-user-select:none;user-select:none}.emojionearea .emojionearea-picker.hidden{display:none}.emojionearea .emojionearea-picker .emojionearea-wrapper{position:relative;height:236px;width:316px}.emojionearea .emojionearea-picker .emojionearea-wrapper:after{content:"";display:block;position:absolute;background-repeat:no-repeat;z-index:91}.emojionearea .emojionearea-picker .emojionearea-filters{width:100%;position:absolute;background:#F5F7F9;padding:0 0 0 7px;height:40px;z-index:95}.emojionearea .emojionearea-picker .emojionearea-filters .emojionearea-filter{display:block;float:left;height:40px;width:32px;padding:7px 1px 0;cursor:pointer;-webkit-filter:grayscale(1);filter:grayscale(1)}.emojionearea .emojionearea-picker .emojionearea-filters .emojionearea-filter.active{background:#fff}.emojionearea .emojionearea-picker .emojionearea-filters .emojionearea-filter.active,.emojionearea .emojionearea-picker .emojionearea-filters .emojionearea-filter:hover{-webkit-filter:grayscale(0);filter:grayscale(0)}.emojionearea .emojionearea-picker .emojionearea-filters .emojionearea-filter>i{width:24px;height:24px;top:0}.emojionearea .emojionearea-picker .emojionearea-filters .emojionearea-filter>img{width:24px;height:24px;margin:0 3px}.emojionearea .emojionearea-picker .emojionearea-scroll-area{height:196px;overflow:auto;overflow-x:hidden;width:100%;position:absolute;padding:0 0 5px}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-emojis-list{z-index:1}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones{position:absolute;top:6px;right:10px;height:22px;z-index:2}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones>.btn-tone{display:inline-block;padding:0;border:0;vertical-align:middle;outline:0;background:0 0;cursor:pointer;position:relative}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones>.btn-tone.btn-tone-0,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones>.btn-tone.btn-tone-0:after{background-color:#ffcf3e}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones>.btn-tone.btn-tone-1,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones>.btn-tone.btn-tone-1:after{background-color:#fae3c5}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones>.btn-tone.btn-tone-2,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones>.btn-tone.btn-tone-2:after{background-color:#e2cfa5}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones>.btn-tone.btn-tone-3,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones>.btn-tone.btn-tone-3:after{background-color:#daa478}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones>.btn-tone.btn-tone-4,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones>.btn-tone.btn-tone-4:after{background-color:#a78058}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones>.btn-tone.btn-tone-5,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones>.btn-tone.btn-tone-5:after{background-color:#5e4d43}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-bullet>.btn-tone,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-square>.btn-tone{width:20px;height:20px;margin:0;background-color:transparent}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-bullet>.btn-tone:after,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-square>.btn-tone:after{content:"";position:absolute;display:block;top:4px;left:4px;width:12px;height:12px}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-bullet>.btn-tone.active:after,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-square>.btn-tone.active:after{top:0;left:0;width:20px;height:20px}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-checkbox>.btn-tone,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-radio>.btn-tone{width:16px;height:16px;margin:0 2px}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-checkbox>.btn-tone.active:after,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-radio>.btn-tone.active:after{content:"";position:absolute;display:block;background-color:transparent;border:2px solid #fff;width:8px;height:8px;top:2px;left:2px}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-category:after,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-category:before,.emojionearea .emojionearea-picker .emojionearea-scroll-area h1:after,.emojionearea .emojionearea-picker .emojionearea-scroll-area h1:before{content:" ";clear:both;display:block}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-bullet>.btn-tone,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-bullet>.btn-tone:after,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-radio>.btn-tone,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-radio>.btn-tone:after{-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-checkbox>.btn-tone,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-checkbox>.btn-tone:after,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-square>.btn-tone,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-tones.emojionearea-tones-square>.btn-tone:after{-moz-border-radius:1px;-webkit-border-radius:1px;border-radius:1px}.emojionearea .emojionearea-picker .emojionearea-scroll-area h1{display:block;font-family:Arial,'Helvetica Neue',Helvetica,sans-serif;font-size:13px;font-weight:400;color:#b2b2b2;background:#fff;line-height:20px;margin:0;padding:7px 0 5px 6px}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojionearea-category{padding:0 0 0 7px}.emojionearea .emojionearea-picker .emojionearea-scroll-area [class*=emojione-]{-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;margin:0;width:24px;height:24px;top:0}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojibtn{-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;width:24px;height:24px;float:left;display:block;margin:1px;padding:3px}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojibtn:hover{-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;background-color:#e4e4e4;cursor:pointer}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojibtn i,.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojibtn img{float:left;display:block;width:24px;height:24px}.emojionearea .emojionearea-picker .emojionearea-scroll-area .emojibtn img.lazy-emoji{filter:alpha(Opacity=0);opacity:0}.emojionearea .emojionearea-picker.emojionearea-filters-position-top .emojionearea-filters{top:0;-moz-border-radius-topleft:5px;-webkit-border-top-left-radius:5px;border-top-left-radius:5px;-moz-border-radius-topright:5px;-webkit-border-top-right-radius:5px;border-top-right-radius:5px}.emojionearea .emojionearea-picker.emojionearea-filters-position-top .emojionearea-scroll-area{bottom:0}.emojionearea .emojionearea-picker.emojionearea-filters-position-bottom .emojionearea-filters{bottom:0;-moz-border-radius-bottomleft:5px;-webkit-border-bottom-left-radius:5px;border-bottom-left-radius:5px;-moz-border-radius-bottomright:5px;-webkit-border-bottom-right-radius:5px;border-bottom-right-radius:5px}.emojionearea .emojionearea-picker.emojionearea-filters-position-bottom .emojionearea-scroll-area{top:0}.emojionearea .emojionearea-picker.emojionearea-picker-position-top{margin-top:-246px;right:-14px}.emojionearea .emojionearea-picker.emojionearea-picker-position-top .emojionearea-wrapper:after{width:19px;height:10px;background-position:-2px -49px;bottom:-10px;right:20px}.emojionearea .emojionearea-picker.emojionearea-picker-position-top.emojionearea-filters-position-bottom .emojionearea-wrapper:after{background-position:-2px -80px}.emojionearea .emojionearea-picker.emojionearea-picker-position-left,.emojionearea .emojionearea-picker.emojionearea-picker-position-right{margin-right:-326px;top:-8px}.emojionearea .emojionearea-picker.emojionearea-picker-position-left .emojionearea-wrapper:after,.emojionearea .emojionearea-picker.emojionearea-picker-position-right .emojionearea-wrapper:after{width:10px;height:19px;background-position:0 -60px;top:13px;left:-10px}.emojionearea .emojionearea-picker.emojionearea-picker-position-left.emojionearea-filters-position-bottom .emojionearea-wrapper:after,.emojionearea .emojionearea-picker.emojionearea-picker-position-right.emojionearea-filters-position-bottom .emojionearea-wrapper:after{background-position:right -60px}.emojionearea .emojionearea-picker.emojionearea-picker-position-bottom{margin-top:10px;right:-14px;top:47px}.emojionearea .emojionearea-picker.emojionearea-picker-position-bottom .emojionearea-wrapper:after{width:19px;height:10px;background-position:-2px -100px;top:-10px;right:20px}.emojionearea .emojionearea-picker.emojionearea-picker-position-bottom.emojionearea-filters-position-bottom .emojionearea-wrapper:after{background-position:-2px -90px}.emojionearea .emojionearea-button.active+.emojionearea-picker{filter:alpha(enabled=false);opacity:1}.emojionearea .emojionearea-button.active+.emojionearea-picker-position-top{margin-top:-229px}.emojionearea .emojionearea-button.active+.emojionearea-picker-position-left,.emojionearea .emojionearea-button.active+.emojionearea-picker-position-right{margin-right:-309px}.emojionearea .emojionearea-button.active+.emojionearea-picker-position-bottom{margin-top:-7px}.emojionearea.emojionearea-standalone{display:inline-block;width:auto;box-shadow:none}.emojionearea.emojionearea-standalone .emojionearea-editor{min-height:33px;position:relative;padding:6px 42px 6px 6px}.emojionearea.emojionearea-standalone .emojionearea-editor::before{content:"";position:absolute;top:4px;left:50%;bottom:4px;border-left:1px solid #e6e6e6}.emojionearea.emojionearea-standalone .emojionearea-editor.has-placeholder .emojioneemoji{opacity:.4}.emojionearea.emojionearea-standalone .emojionearea-button{top:0;right:0;bottom:0;left:0;width:auto;height:auto}.emojionearea.emojionearea-standalone .emojionearea-button>div{right:6px;top:5px}.emojionearea.emojionearea-standalone .emojionearea-picker.emojionearea-picker-position-bottom .emojionearea-wrapper:after,.emojionearea.emojionearea-standalone .emojionearea-picker.emojionearea-picker-position-top .emojionearea-wrapper:after{right:23px}.emojionearea.emojionearea-standalone .emojionearea-picker.emojionearea-picker-position-left .emojionearea-wrapper:after,.emojionearea.emojionearea-standalone .emojionearea-picker.emojionearea-picker-position-right .emojionearea-wrapper:after{top:15px}.emojionearea .emojionearea-button>div,.emojionearea .emojionearea-picker .emojionearea-wrapper:after{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNiZGJkYmQiIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDQ4IDQ4Ij48cGF0aCBkPSJNMjMuOTkgNEMxMi45NCA0IDQgMTIuOTUgNCAyNHM4Ljk0IDIwIDE5Ljk5IDIwQzM1LjA0IDQ0IDQ0IDM1LjA1IDQ0IDI0UzM1LjA0IDQgMjMuOTkgNHpNMjQgNDBjLTguODQgMC0xNi03LjE2LTE2LTE2UzE1LjE2IDggMjQgOHMxNiA3LjE2IDE2IDE2LTcuMTYgMTYtMTYgMTZ6bTctMThjMS42NiAwIDMtMS4zNCAzLTNzLTEuMzQtMy0zLTMtMyAxLjM0LTMgMyAxLjM0IDMgMyAzem0tMTQgMGMxLjY2IDAgMy0xLjM0IDMtM3MtMS4zNC0zLTMtMy0zIDEuMzQtMyAzIDEuMzQgMyAzIDN6bTcgMTNjNC42NiAwIDguNjEtMi45MSAxMC4yMS03SDEzLjc5YzEuNiA0LjA5IDUuNTUgNyAxMC4yMSA3eiIvPjwvc3ZnPg==)!important;background-size:contain;}.emojionearea .emojionearea-button > div:hover, .emojionearea .emojionearea-button:hover, .emojionearea .emojionearea-button.active > div {background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNmZmYiIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDQ4IDQ4Ij48cGF0aCBkPSJNMjMuOTkgNEMxMi45NCA0IDQgMTIuOTUgNCAyNHM4Ljk0IDIwIDE5Ljk5IDIwQzM1LjA0IDQ0IDQ0IDM1LjA1IDQ0IDI0UzM1LjA0IDQgMjMuOTkgNHpNMjQgNDBjLTguODQgMC0xNi03LjE2LTE2LTE2UzE1LjE2IDggMjQgOHMxNiA3LjE2IDE2IDE2LTcuMTYgMTYtMTYgMTZ6bTctMThjMS42NiAwIDMtMS4zNCAzLTNzLTEuMzQtMy0zLTMtMyAxLjM0LTMgMyAxLjM0IDMgMyAzem0tMTQgMGMxLjY2IDAgMy0xLjM0IDMtM3MtMS4zNC0zLTMtMy0zIDEuMzQtMyAzIDEuMzQgMyAzIDN6bTcgMTNjNC42NiAwIDguNjEtMi45MSAxMC4yMS03SDEzLjc5YzEuNiA0LjA5IDUuNTUgNyAxMC4yMSA3eiIvPjwvc3ZnPg==)!important;background-size:contain;}.emojionearea.emojionearea-standalone .emojionearea-editor.has-placeholder{background-repeat:no-repeat;background-position:20px 4px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMAQMAAABsu86kAAAABlBMVEUAAAC/v79T5hyIAAAAAXRSTlMAQObYZgAAABNJREFUCNdjYGNgQEb/P4AQqiAASiUEG6Vit44AAAAASUVORK5CYII=)!important}
2 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/chat/header.sass:
--------------------------------------------------------------------------------
1 | @import "https://fonts.googleapis.com/css?family=Lato:300,400,700,900"
2 |
3 | #chat .chat__header
4 | font-family: 'Lato' !important
5 | letter-spacing: 1.5px !important
6 | height: 50px
7 | display: flex
8 | flex-direction: row
9 | align-items: center
10 | position: relative
11 | z-index: 11
12 | .chat__settings
13 | width: 20px
14 | height: 20px
15 | margin: 0 25px
16 | cursor: pointer
17 | div
18 | height: 2px
19 | background: #fff
20 | border-radius: 2px
21 | div:nth-of-type(1)
22 | width: 94%
23 | margin-bottom: 4px
24 | margin-top: 5px
25 | div:nth-of-type(2)
26 | width: 57%
27 |
28 | h6
29 | font-family: 'Lato' !important
30 | letter-spacing: 1.5px !important
31 | margin: 0
32 | font-size: 19px
33 | color: #fff
34 | font-weight: 400
35 | i
36 | margin: 0 25px
37 | color: #fff
38 | transition: all .3s ease
39 | cursor: pointer
40 | &:hover
41 | color: #eee
42 |
43 | #chat .chat__badge
44 | position: relative
45 | #chat .chat__badge[data-badge]:after, .chat__launch.chat__badge[data-badge]:after
46 | content: attr(data-badge)
47 | display: flex
48 | flex-direction: row
49 | flex-wrap: wrap
50 | justify-content: center
51 | align-content: center
52 | align-items: center
53 | position: absolute
54 | top: -7px
55 | right: -10px
56 | font-size: 12px
57 | width: 22px
58 | height: 22px
59 | border-radius: 50%
60 | background: #ff4081
61 | color: #fff
62 | font-family: 'Lato' !important
63 | .chat__launch.chat__badge[data-badge]:after
64 | position: fixed
65 | bottom: 51px
66 | right: 14px
67 | top: initial
68 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/chat/launch.sass:
--------------------------------------------------------------------------------
1 | .chat__launch
2 | position: fixed
3 | right: 15px
4 | bottom: 15px
5 | border-radius: 50%
6 | height: 56px
7 | margin: auto
8 | min-width: 56px
9 | width: 56px
10 | padding: 0
11 | overflow: hidden
12 | box-shadow: 0 1px 1.5px 0 rgba(0, 0, 0, 0.12), 0 1px 1px 0 rgba(0, 0, 0, 0.24)
13 | line-height: normal
14 | border: none
15 | will-change: box-shadow
16 | transition: box-shadow 0.2s cubic-bezier(0.4, 0, 1, 1), background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1), color 0.2s cubic-bezier(0.4, 0, 0.2, 1)
17 | outline: none
18 | cursor: pointer
19 | text-decoration: none
20 | transition: 0.5s
21 | transform: scale(1)
22 | background: #fff
23 | i
24 | font-size: 30px
25 | &:active
26 | box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.2)
27 |
28 |
29 | .chat__start-new
30 | position: fixed
31 | right: 10px
32 | bottom: 15px
33 | border-radius: 50%
34 | height: 50px
35 | margin: auto
36 | min-width: 50px
37 | width: 50px
38 | padding: 0
39 | overflow: hidden
40 | box-shadow: 0 1px 1.5px 0 rgba(0, 0, 0, 0.12), 0 1px 2px 0 rgba(0, 0, 0, 0.24)
41 | line-height: normal
42 | border: none
43 | will-change: box-shadow
44 | transition: box-shadow 0.2s cubic-bezier(0.4, 0, 1, 1), background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1), color 0.2s cubic-bezier(0.4, 0, 0.2, 1)
45 | outline: none
46 | cursor: pointer
47 | text-decoration: none
48 | transition: 0.5s
49 | transform: scale(1)
50 | z-index: 15
51 | background: #fff
52 | i
53 | position: absolute
54 | left: 12px
55 | top: 12px
56 | font-size: 25px
57 | color: #212121
58 | &:active
59 | box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.2)
60 |
61 | .chat__disappear
62 | transform: scale(0)
63 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/chat/list.sass:
--------------------------------------------------------------------------------
1 | @keyframes pulse
2 | 50%
3 | background-color: #C8E6C9
4 | #chat .slide_left
5 | transform: translate3d(-100%,0,0)
6 | #chat .slide_right
7 | transform: translate3d(100%,0,0)
8 | #chat .chat__list
9 | position: absolute
10 | width: 100%
11 | height: calc(100% - 50px)
12 | left: 0px
13 | opacity: 1
14 | transition: 0.5s
15 | overflow-y: scroll
16 | z-index: 13
17 | &::-webkit-scrollbar
18 | visibility: hidden
19 | a:not(.chat__start-new)
20 | padding: 10px 25px
21 | text-align: center
22 | transition: all 0.3s ease
23 | display: flex
24 | .chat__has_new_message
25 | animation: pulse 1s ease-out infinite
26 | .chat__glance
27 | display: flex
28 | flex-direction: column
29 | flex: 1
30 | .chat__glance-name-date
31 | display: flex
32 | flex-direction: row
33 | flex: 1
34 | margin-left: 15px
35 | margin-top: 15px
36 | color: #fff
37 | font-weight: 600
38 | .flex
39 | flex: 1
40 | .name
41 | font-size: 15px
42 | .date
43 | font-size: 11px
44 | padding-top: 3px
45 | letter-spacing: 0.5px
46 | .chat__glance-last-message
47 | margin-left: 15px
48 | text-align: left
49 | margin-top: 7px
50 | font-size: 13px
51 | color: #fff
52 | word-break: break-all
53 | font-weight: 300
54 | img
55 | font-size: inherit
56 | height: 2ex
57 | width: 2.1ex
58 | min-height: 20px
59 | min-width: 20px
60 | display: inline-block
61 | margin: -.2ex .15em .2ex
62 | line-height: normal
63 | vertical-align: middle
64 | max-width: 100%
65 | top: 0
66 | #chat .chat__none-available
67 | height: 100%
68 | display: flex
69 | flex-direction: column
70 | align-items: center
71 | justify-content: center
72 | color: #bdbdbd
73 | h5
74 | font-weight: 400
75 | font-size: 15px
76 | margin: 5px 0px 5px 0px
77 | i
78 | font-size: 60px
79 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/chat/message_form.sass:
--------------------------------------------------------------------------------
1 | @import "https://fonts.googleapis.com/css?family=Lato:300,400,700,900"
2 |
3 | #chat .new_message
4 | margin: 0px 20px 25px 20px
5 | flex-direction: column
6 | display: flex
7 | position: relative
8 | .chat__insert-image
9 | position: absolute
10 | right: 28px
11 | top: 4px
12 | color: #bdbdbd
13 | transition: all 0.3s ease
14 | z-index: 10
15 | cursor: pointer
16 | font-size: 22px
17 | &:hover
18 | color: #fff
19 | input[type='text'], textarea
20 | font-size: 17px
21 | font-family: "Lato" !important
22 | letter-spacing: 1.5px !important
23 | background: transparent
24 | border: none
25 | font-weight: 300
26 | border-bottom: 1px solid rgba(0,0,0,.26)
27 | height: 28px
28 | outline: none
29 | resize: none
30 | .bar, .emojionearea-bar
31 | position: relative
32 | &:before, &:after
33 | content: ''
34 | height: 2px
35 | width: 0
36 | bottom: 0px
37 | position: absolute
38 | background: #fff
39 | transition: 0.2s ease all
40 | -moz-transition: 0.2s ease all
41 | -webkit-transition: 0.2s ease all
42 | &:before
43 | left: 50%
44 | &:after
45 | right: 50%
46 | input[type='text']:focus ~ .bar:before, input[type='text']:focus ~ .bar:after,
47 | textarea:focus ~ .bar:before, textarea:focus ~ .bar:after, .emojionearea-editor:focus ~ .emojionearea-bar:before, .emojionearea-editor:focus ~ .emojionearea-bar:after
48 | width: 50%
49 | input[type='file']
50 | display: none !important
51 | .emojionearea .emojionearea-editor
52 | border-bottom: 1px solid #bdbdbd !important
53 | color: #fff !important
54 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/chat/new.sass:
--------------------------------------------------------------------------------
1 | @import "https://fonts.googleapis.com/css?family=Lato:300,400,700,900"
2 |
3 | #chat .new_conversation
4 | height: calc(100% - 50px)
5 | overflow-y: scroll
6 | position: absolute
7 | right: 100%
8 | transition: 0.5s
9 | width: 100%
10 | z-index: 13
11 | display: flex
12 | flex-direction: column
13 | &::-webkit-scrollbar
14 | visibility: hidden
15 | label
16 | position: relative
17 | margin: 1rem
18 | cursor: pointer
19 | font-family: "Lato" !important
20 | letter-spacing: 1.5px !important
21 | padding-left: 40px
22 | color: #fff
23 | width: calc(100% - 40px - 1rem)
24 | .chat__status
25 | &:after
26 | content: "\2022"
27 | &.offline
28 | &:after
29 | color: hsl(355, 65%, 65%)
30 | &.online
31 | &:after
32 | color: #AED581
33 |
34 | .chat__submit-container
35 | margin: 1rem
36 | padding: 10px 0
37 | width: calc(100% - 1rem)
38 | display: flex
39 | flex-direction: column
40 | justify-content: center
41 | align-items: center
42 |
43 | input[type='submit']
44 | border: none
45 | font-family: "Lato" !important
46 | letter-spacing: 1.5px !important
47 | font-size: 17px
48 | background: transparent
49 | padding: 0
50 | cursor: pointer
51 | transition: all 0.3s ease
52 | z-index: 10
53 | color: #fff
54 | margin: 0px
55 |
56 | #chat .chat__errors
57 | text-align: center
58 | color: #F44336
59 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/chat/transcript.sass:
--------------------------------------------------------------------------------
1 | #chat .current_chat
2 | display: flex
3 | flex-direction: column
4 | overflow-y: scroll
5 | position: absolute
6 | width: 100%
7 | height: calc(100% - 50px)
8 | left: 100%
9 | opacity: 1
10 | transition: 0.5s
11 | z-index: 13
12 | &::-webkit-scrollbar
13 | visibility: hidden
14 | img.chat__user-avatar
15 | width: 50px
16 | height: 50px
17 | object-fit: cover
18 | border-radius: 50%
19 | .transcript_placeholder_avatar
20 | width: 50px
21 | height: 50px
22 | i.chat__user-avatar
23 | font-size: 50px
24 | .chat__transcript
25 | margin-left: 25px
26 | margin-right: 25px
27 | .message-container
28 | display: flex
29 | flex-direction: row
30 | &.right
31 | text-align: right
32 | flex-direction: row-reverse
33 | .message
34 | flex-direction: row-reverse
35 | .content
36 | border-top-right-radius: 0 !important
37 | border-top-left-radius: 10px !important
38 | &.image img
39 | border-top-right-radius: 0 !important
40 | border-top-left-radius: 10px !important
41 | .message
42 | min-height: 50px
43 | display: flex
44 | margin: 0 10px 5px 10px
45 | flex: 1
46 | flex-direction: row
47 | max-width: 100%
48 | &.image
49 | display: block
50 | img
51 | max-width: 100%
52 | border-radius: 10px
53 | border-top-left-radius: 0
54 | .content
55 | background: #fff
56 | padding: 10px
57 | border-radius: 10px
58 | color: #212121
59 | border-top-left-radius: 0
60 | word-break: break-all
61 | img
62 | font-size: inherit
63 | height: 2ex
64 | width: 2.1ex
65 | min-height: 20px
66 | min-width: 20px
67 | display: inline-block
68 | margin: -.2ex .15em .2ex
69 | line-height: normal
70 | vertical-align: middle
71 | max-width: 100%
72 | top: 0
73 |
--------------------------------------------------------------------------------
/app/channels/chat/messages_channel.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Chat::MessagesChannel < ApplicationCable::Channel
4 | def follow(data)
5 | stop_all_streams
6 | stream_from "chats::#{data['chat_id']}::messages"
7 | end
8 |
9 | def unsubscribed
10 | stop_all_streams
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/app/channels/chat/notification_channel.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Chat::NotificationChannel < ApplicationCable::Channel
4 | def follow(data)
5 | stop_all_streams
6 | stream_from "users::#{data['user_id']}::chats"
7 | end
8 |
9 | def unsubscribed
10 | stop_all_streams
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/app/channels/chat/status_channel.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Chat::StatusChannel < ApplicationCable::Channel
4 | def online
5 | stop_all_streams
6 | stream_from "chat::status"
7 | current_user.online
8 | end
9 |
10 | def unsubscribed
11 | current_user.offline
12 | stop_all_streams
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/app/controllers/chat/application_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Chat
4 | class ApplicationController < ::ApplicationController
5 | protect_from_forgery with: :exception
6 |
7 | before_action Chat.logged_in_check
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/app/controllers/chat/conversations_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Chat
4 | class ConversationsController < ApplicationController
5 | before_action :set_conversation, only: :show
6 |
7 | def show
8 | end
9 |
10 | def create
11 | @conversation = Chat::Conversation.create(conversation_params)
12 |
13 | if @conversation.persisted? ||
14 | @conversation.errors.messages[:sessions].present?
15 | render template: "chat/conversations/create"
16 | else
17 | @conversation = find_existing_conversation
18 | render template: "chat/conversations/show"
19 | end
20 | end
21 |
22 | private
23 |
24 | def set_conversation
25 | @conversation = Chat::Conversation.includes(
26 | :users, messages: :user
27 | ).find(params[:id])
28 | end
29 |
30 | def conversation_params
31 | chat_params = params.require(:conversation).permit(user_ids: [])
32 | if chat_params[:user_ids].reject!(&:blank?).present?
33 | chat_params[:user_ids] << current_user.id
34 | chat_params[:user_ids].map!(&:to_i)
35 | end
36 |
37 | chat_params
38 | end
39 |
40 | def find_existing_conversation
41 | Chat::Conversation.includes(
42 | :users, messages: :user
43 | ).find(
44 | Chat::Conversation.joins(:users).having(
45 | "COUNT(DISTINCT users.id) = ?", conversation_params[:user_ids].count
46 | ).group(:id).find_by(
47 | users: { id: conversation_params[:user_ids] }
48 | ).id
49 | )
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/app/controllers/chat/messages_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Chat
4 | class MessagesController < ApplicationController
5 | before_action :set_conversation
6 | before_action :set_session
7 |
8 | def create
9 | @conversation.messages.create(message_params)
10 |
11 | head :ok
12 | end
13 |
14 | def destroy
15 | @conversation.destroy
16 | end
17 |
18 | private
19 |
20 | def set_conversation
21 | @conversation = Chat::Conversation.find(params[:conversation_id])
22 | end
23 |
24 | def set_session
25 | @session = @conversation.sessions.find_by(user: current_user)
26 | end
27 |
28 | def message_params
29 | params.require(:message).permit(:text, :image).merge(
30 | user_id: current_user.id, session_id: @session.id
31 | )
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/app/helpers/chat/application_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Chat
4 | module ApplicationHelper
5 | def render_chat
6 | return unless send Chat.signed_in
7 |
8 | render "chat/chat"
9 | end
10 |
11 | def launch_chat_fab
12 | attrs = {
13 | class: "chat__launch", data: { "current-user" => current_user.id }
14 | }
15 | button_tag(attrs) do
16 | material_icon.forum.css_class("md-dark").to_s.html_safe
17 | end
18 | end
19 |
20 | def chatable_users
21 | @chatable_users ||= ::User.where.not(id: current_user.id)
22 | end
23 |
24 | def chatable_user_check_boxes(f)
25 | f.collection_check_boxes(:user_ids, chatable_users, :id, :name) do |b|
26 | b.check_box + b.label(class: b.object.chat_status) do
27 | content_tag(
28 | :div, b.text,
29 | class: "chat__status #{b.object.chat_status}"
30 | )
31 | end
32 | end
33 | end
34 |
35 | def chat_avatar(user)
36 | if user.chat_avatar?
37 | image_tag(user.chat_avatar, class: "chat__user-avatar")
38 | else
39 | MaterialIcon.new.css_class("chat__user-avatar").person.to_s
40 | end
41 | end
42 |
43 | def chat_list
44 | @chat_list ||= current_user.conversations.includes(:users).order(
45 | "chat_conversations.created_at desc"
46 | )
47 | end
48 |
49 | def chat_avatars(conversation)
50 | (conversation.users - [current_user]).first(2).map do |u|
51 | chat_avatar(u)
52 | end
53 | end
54 |
55 | def chat_avatar_count(conversation)
56 | if (count = conversation_user_count(conversation)) <= 2
57 | "count_#{count}"
58 | else
59 | "count_default"
60 | end
61 | end
62 |
63 | def conversation_user_count(conversation)
64 | conversation.users.to_a.size - 1
65 | end
66 |
67 | def message_classes(message)
68 | css_class = message.user == current_user ? "right" : "left"
69 | css_class += message.image? ? " image" : ""
70 | css_class
71 | end
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/app/jobs/chat/application_job.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Chat
4 | class ApplicationJob < ::ApplicationJob
5 | queue_as :default
6 |
7 | private
8 |
9 | def broadcast(channel, payload)
10 | ActionCable.server.broadcast(channel, payload)
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/jobs/chat/message_relay_job.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Chat
4 | class MessageRelayJob < ApplicationJob
5 | def perform(message_id)
6 | message = Chat::Message.find(message_id)
7 | broadcast(
8 | "chats::#{message.conversation_id}::messages", render_message(message)
9 | )
10 | end
11 |
12 | def render_message(message)
13 | {
14 | user: message.user_id, avatar: renderer.chat_avatar(message.user).to_s
15 | }.merge(content(message))
16 | end
17 |
18 | def content(message)
19 | if message.image?
20 | { message: renderer.image_tag(message.image.url), image: true }
21 | else
22 | { message: message.text, image: false }
23 | end
24 | end
25 |
26 | private
27 |
28 | def renderer
29 | ApplicationController.helpers
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/app/jobs/chat/notification_relay_job.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Chat
4 | class NotificationRelayJob < ApplicationJob
5 | def perform(message)
6 | user_ids(message).each do |user_id|
7 | broadcast("users::#{user_id}::chats", chat_id: message.conversation_id)
8 | end
9 | end
10 |
11 | private
12 |
13 | def user_ids(message)
14 | message.conversation.users.where.not(id: message.user_id).ids
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/jobs/chat/status_relay_job.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Chat
4 | class StatusRelayJob < ApplicationJob
5 | def perform(user)
6 | broadcast("chat::status", user_id: user.id, status: user.chat_status)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/app/mailers/chat/application_mailer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Chat
4 | class ApplicationMailer < ActionMailer::Base
5 | default from: "from@example.com"
6 | layout "mailer"
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/app/models/chat/application_record.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Chat::ApplicationRecord < ActiveRecord::Base
4 | self.abstract_class = true
5 | end
6 |
--------------------------------------------------------------------------------
/app/models/chat/conversation.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Chat::Conversation < ApplicationRecord
4 | has_many :messages
5 | has_many :sessions, dependent: :destroy
6 | has_many :users, through: :sessions, class_name: "::User"
7 |
8 | has_one :last_message, -> { order(created_at: :desc) },
9 | class_name: "Chat::Message"
10 |
11 | validates :sessions, presence: {
12 | message: "At least one user must be selected"
13 | }
14 | validate do
15 | next unless Chat::Conversation.joins(:users).where.not(id: id).where(
16 | users: { id: user_ids }
17 | ).group(:id).having("COUNT(DISTINCT users.id) = ?", user_ids.count).exists?
18 |
19 | errors.add(:conversation, "already taking place")
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/app/models/chat/dot_command.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Chat
4 | class DotCommand
5 | attr_reader :text, :message
6 |
7 | def initialize(message, text)
8 | @message = message
9 | @text = text
10 | end
11 |
12 | def command
13 | @command ||= text[/^\.[a-zA-z]*/]
14 | end
15 |
16 | def perform
17 | return false unless valid?
18 |
19 | ("Chat::DotCommand::" + command[1..-1].classify).constantize.new(
20 | message, text[/[^\.[a-zA-z]*].*/]&.squish
21 | ).perform
22 | end
23 |
24 | def valid?
25 | Chat::DotCommand::Validator.new(command).perform
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/app/models/chat/dot_command/gif.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Chat
4 | class DotCommand
5 | class Gif
6 | attr_reader :message, :text
7 |
8 | def initialize(message, text)
9 | @message = message
10 | @text = text
11 | end
12 |
13 | def perform
14 | message.image = open(::Giphy.random(text).image_url)
15 | message.text = nil
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/app/models/chat/dot_command/shrug.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Chat
4 | class DotCommand
5 | class Shrug
6 | attr_reader :message, :text
7 |
8 | def initialize(message, text)
9 | @message = message
10 | @text = text
11 | end
12 |
13 | def perform
14 | message.text =
15 | if text.blank?
16 | shruggie
17 | else
18 | "#{text} #{shruggie}"
19 | end
20 | end
21 |
22 | private
23 |
24 | def shruggie
25 | '¯\_(ツ)_/¯'
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/app/models/chat/dot_command/validator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Chat
4 | class DotCommand
5 | class Validator
6 | attr_reader :command
7 |
8 | def initialize(command)
9 | @command = command
10 | end
11 |
12 | def perform
13 | (commands + custom_commands).include? command
14 | end
15 |
16 | def commands
17 | %w(.gif .shrug)
18 | end
19 |
20 | def custom_commands
21 | []
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/app/models/chat/message.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Chat::Message < ApplicationRecord
4 | has_attached_file :image
5 | validates_attachment :image, content_type: {
6 | content_type: ["image/jpg", "image/jpeg", "image/png", "image/gif"]
7 | }
8 |
9 | belongs_to :user, class_name: "::User"
10 | belongs_to :conversation
11 | belongs_to :session
12 |
13 | delegate :name, to: :user
14 | before_save :remove_extra_new_line
15 | before_save :execute_dot_command
16 |
17 | validates :text, presence: true, unless: :image?
18 |
19 | after_create_commit do
20 | Chat::MessageRelayJob.send(Chat.perform_method.to_sym, id)
21 | Chat::NotificationRelayJob.send(Chat.perform_method.to_sym, self)
22 | end
23 |
24 | private
25 |
26 | def execute_dot_command
27 | return unless dot_command.valid?
28 |
29 | dot_command.perform
30 | end
31 |
32 | def dot_command
33 | @dot_command ||= Chat::DotCommand.new(self, text)
34 | end
35 |
36 | def remove_extra_new_line
37 | self.text = text[0..-16]
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/app/models/chat/session.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Chat::Session < ApplicationRecord
4 | belongs_to :conversation
5 | belongs_to :user, class_name: "::User"
6 |
7 | has_many :messages, dependent: :destroy
8 |
9 | validates :user_id, uniqueness: { scope: :conversation_id }
10 | end
11 |
--------------------------------------------------------------------------------
/app/views/chat/_chat.html.haml:
--------------------------------------------------------------------------------
1 | = launch_chat_fab
2 |
3 | #chat
4 | #chat__background
5 | .chat__header
6 | .chat__settings
7 | %div
8 | %div
9 | .chat__flex
10 | %h6 Chat
11 | .chat__flex
12 | = material_icon.close.css_class("chat__close")
13 | = render "chat/conversations/new"
14 | .chat__list
15 | = render "chat/conversations/index"
16 | = link_to nil, class: "chat__start-new" do
17 | = material_icon.add
18 | .current_chat
19 |
--------------------------------------------------------------------------------
/app/views/chat/conversations/_conversation.html.haml:
--------------------------------------------------------------------------------
1 | = link_to [chat, conversation], remote: true, id: "chat__show-#{conversation.id}" do
2 | .avatar_container{ class: chat_avatar_count(conversation) }
3 | - if chat_avatar_count(conversation) == "count_default"
4 | = chat_avatars(conversation).sample.html_safe
5 | .chat_count
6 | %span
7 | = conversation_user_count(conversation)
8 | - else
9 | = chat_avatars(conversation).join("").html_safe
10 | .chat__glance
11 | .chat__glance-name-date
12 | .name
13 | - if conversation.users.to_a.size > 2
14 | Group Chat
15 | - else
16 | = conversation.users.where.not(id: current_user.id).first.first_name
17 | .flex
18 | .date
19 | - if conversation.last_message.present?
20 | = time_ago_in_words conversation.last_message&.created_at
21 | ago
22 | .chat__glance-last-message
23 | = raw conversation.last_message&.text
24 |
--------------------------------------------------------------------------------
/app/views/chat/conversations/_index.html.haml:
--------------------------------------------------------------------------------
1 | - if chat_list.present?
2 | = render chat_list
3 | - else
4 | .chat__none-available
5 | = material_icon.chat_bubble_outline
6 | %h5 No Chats
7 |
--------------------------------------------------------------------------------
/app/views/chat/conversations/_new.html.haml:
--------------------------------------------------------------------------------
1 | = form_for [chat, Chat::Conversation.new], remote: true do |f|
2 | - if chatable_users.present?
3 | = chatable_user_check_boxes(f)
4 | .chat__submit-container= f.submit "Start Chatting"
5 | - else
6 | .chat__none-available
7 | = material_icon.people
8 | %h5 No Chatable Users
9 |
--------------------------------------------------------------------------------
/app/views/chat/conversations/_show.html.haml:
--------------------------------------------------------------------------------
1 | .chat__transcript{ data: { channel: 'messages', current: { chat: @conversation.id, user: current_user.id }}}
2 | - @conversation.messages.each_with_index do |msg, i|
3 | = render "chat/messages/message", message: msg, previous: (i > 0 ? @conversation.messages[i-1] : nil)
4 | .chat__flex
5 | %div
6 | = form_for [chat, @conversation, Chat::Message.new], remote: true do |f|
7 | = material_icon.insert_photo.css_class("chat__insert-image")
8 | = f.text_area :text, placeholder: 'Send message...', autocomplete: 'off'
9 | %span.bar
10 | = f.file_field :image
11 |
--------------------------------------------------------------------------------
/app/views/chat/conversations/create.js.erb:
--------------------------------------------------------------------------------
1 | <% if @conversation.errors.messages[:sessions].blank? %>
2 | $('#chat .chat__list').html("<%= j render 'chat/conversations/index' %>");
3 | $('.new_conversation').replaceWith("<%= j render 'chat/conversations/new' %>");
4 | $('.current_chat').html("<%= j render 'chat/conversations/show' %>");
5 | $('.new_message textarea').emojioneArea();
6 | App.chat_message.followCurrentChat();
7 | $("#new_message").fileupload();
8 | if($("#chat__show-<%= @conversation.id %>").hasClass('chat__has_new_message')) {
9 | App.chat_notification.decrement();
10 | $("#chat__show-<%= @conversation.id %>").removeClass('chat__has_new_message')
11 | }
12 | $('.current_chat').toggleClass("slide_left");
13 | $('.chat__list').addClass("slide_left");
14 | $('.chat__start-new').addClass("chat__disappear");
15 | $('.current_chat').scrollTop($('.current_chat').prop('scrollHeight'));
16 | <% else %>
17 | error_msg =
18 | `
19 | <%= j @conversation.errors.values.flatten.join(', ') %>
20 |
`;
21 | if($('.chat__errors').length == 0) {
22 | $('.new_conversation').scrollTop(0);
23 | $('.new_conversation').prepend(error_msg);
24 | }
25 | <% end %>
26 |
--------------------------------------------------------------------------------
/app/views/chat/conversations/show.js.erb:
--------------------------------------------------------------------------------
1 | $('.new_conversation').replaceWith("<%= j render 'chat/conversations/new' %>");
2 | $('.current_chat').html("<%= j render 'chat/conversations/show' %>");
3 | $('.new_message textarea').emojioneArea();
4 | App.chat_message.followCurrentChat();
5 | $("#new_message").fileupload();
6 | if($("#chat__show-<%= @conversation.id %>").hasClass('chat__has_new_message')) {
7 | App.chat_notification.decrement();
8 | $("#chat__show-<%= @conversation.id %>").removeClass('chat__has_new_message')
9 | }
10 | $('.current_chat').toggleClass("slide_left");
11 | $('.chat__list').removeClass("slide_right");
12 | $('.chat__list').addClass("slide_left");
13 | $('.chat__start-new').addClass("chat__disappear");
14 | $('.current_chat').scrollTop($('.current_chat').prop('scrollHeight'));
15 | $(".new_conversation").removeClass("slide_right");
16 |
--------------------------------------------------------------------------------
/app/views/chat/messages/_message.haml:
--------------------------------------------------------------------------------
1 | .message-container{ class: message.user == current_user ? "right" : "left" }
2 | - if message.user == previous&.user
3 | .transcript_placeholder_avatar
4 | - else
5 | = chat_avatar(message.user)
6 | .message{ class: ("image" if message.image?).to_s }
7 | - if message.image?
8 | = image_tag message.image.url
9 | - else
10 | .content= raw(message.text)
11 |
--------------------------------------------------------------------------------
/app/views/layouts/chat/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Chat
5 | <%= stylesheet_link_tag "chat/application", media: "all" %>
6 | <%= javascript_include_tag "chat/application" %>
7 | <%= csrf_meta_tags %>
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | # This command will automatically be run when you run "rails" with Rails gems
5 | # installed from the root of your application.
6 |
7 | ENGINE_ROOT = File.expand_path("../..", __FILE__)
8 | ENGINE_PATH = File.expand_path("../../lib/chat/engine", __FILE__)
9 |
10 | # Set up gems listed in the Gemfile.
11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
12 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
13 |
14 | require "rails/all"
15 | require "rails/engine/commands"
16 |
--------------------------------------------------------------------------------
/bin/test:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | $: << File.expand_path(File.expand_path("../../test", __FILE__))
5 |
6 | require "bundler/setup"
7 | require "rails/test_unit/minitest_plugin"
8 |
9 | Rails::TestUnitReporter.executable = "bin/test"
10 |
11 | exit Minitest.run(ARGV)
12 |
--------------------------------------------------------------------------------
/chat.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | $LOAD_PATH.push File.expand_path("../lib", __FILE__)
4 |
5 | require "chat/version"
6 |
7 | Gem::Specification.new do |s|
8 | s.add_dependency "material_icons"
9 | s.add_dependency "paperclip"
10 | s.add_dependency "jquery-fileupload-rails"
11 | s.add_dependency "rails", "~> 5.1"
12 | s.add_dependency "coffee-rails"
13 | s.add_dependency "jquery-rails"
14 | s.add_dependency "giphy"
15 | s.add_dependency "sidekiq"
16 |
17 | s.name = "chat"
18 | s.version = Chat::VERSION
19 | s.authors = ["npezza93"]
20 | s.email = ["npezza93@gmail.com"]
21 | s.summary = "A simple rails chat gem that leverages ActionCable"
22 | s.homepage = "https://github.com/npezza93/chat"
23 | s.license = "MIT"
24 |
25 | s.files =
26 | Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
27 |
28 | s.add_development_dependency "haml"
29 | s.add_development_dependency "pry-rails"
30 | s.add_development_dependency "clearance"
31 | s.add_development_dependency "faker"
32 | s.add_development_dependency "puma"
33 | s.add_development_dependency "sqlite3"
34 | s.add_development_dependency "rubocop"
35 | s.add_development_dependency "turbolinks"
36 | end
37 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Chat::Engine.routes.draw do
4 | resources :conversations, only: %i(show create) do
5 | resources :messages, only: %i(create destroy)
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/chat.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "chat/engine"
4 | require "chat/version"
5 | require "chat/user"
6 |
7 | require "paperclip"
8 | require "material_icons"
9 | require "coffee-rails"
10 | require "jquery-rails"
11 | require "jquery-fileupload-rails"
12 | require "giphy"
13 | require "sidekiq"
14 |
15 | module Chat
16 | # The helper method used to distinguish if a user is logged in
17 | mattr_accessor :signed_in
18 |
19 | # The before_action method to validate users are logged in
20 | mattr_accessor :logged_in_check
21 |
22 | # The field that contains the users avatar
23 | mattr_accessor :user_avatar
24 |
25 | # Can be perform_now or perform_later. Called on jobs
26 | mattr_accessor :perform_method
27 |
28 | def self.table_name_prefix
29 | "chat_"
30 | end
31 |
32 | def self.setup
33 | yield self
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/lib/chat/engine.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Chat
4 | class Engine < ::Rails::Engine
5 | isolate_namespace Chat
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/chat/user.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "active_support/concern"
4 |
5 | module Chat
6 | module User
7 | extend ActiveSupport::Concern
8 |
9 | included do
10 | has_many :sessions, dependent: :destroy, class_name: "::Chat::Session"
11 | has_many :conversations, through: :sessions,
12 | class_name: "::Chat::Conversation"
13 |
14 | validates :chat_status, inclusion: { in: %w(online offline) }
15 |
16 | after_commit :broadcast_status,
17 | if: proc { |u| u.previous_changes.key?(:chat_status) }
18 | end
19 |
20 | def name
21 | "#{first_name} #{last_name}"
22 | end
23 |
24 | def online
25 | update(chat_status: "online")
26 | end
27 |
28 | def offline
29 | update(chat_status: "offline")
30 | end
31 |
32 | def chat_avatar
33 | send(Chat.user_avatar)
34 | end
35 |
36 | def chat_avatar?
37 | send("#{Chat.user_avatar}?")
38 | end
39 |
40 | private
41 |
42 | def broadcast_status
43 | Chat::StatusRelayJob.send(Chat.perform_method, self)
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/chat/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Chat
4 | VERSION = "1.0.0"
5 | end
6 |
--------------------------------------------------------------------------------
/lib/generators/chat/install/install_generator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rails/generators/base"
4 | require "rails/generators/active_record"
5 | require "rails/generators/migration"
6 |
7 | module Chat
8 | module Generators
9 | class InstallGenerator < Rails::Generators::Base
10 | include Rails::Generators::Migration
11 | source_root File.expand_path("../templates", __FILE__)
12 |
13 | desc "Adds view helpers, user methods, and migrations for chat"
14 |
15 | def check_user_table
16 | raise UsersTableNotImplemented unless users_table_exists?
17 | end
18 |
19 | def create_chat_initializer
20 | copy_file "chat.rb", "config/initializers/chat.rb"
21 | end
22 |
23 | def add_helper
24 | file = "app/controllers/application_controller.rb"
25 | after = "class ApplicationController < ActionController::Base\n"
26 | inject_into_file file, after: after do
27 | " helper Chat::Engine.helpers\n"
28 | end
29 | end
30 |
31 | def inject_chat_into_user_model
32 | file = "app/models/user.rb"
33 | after = "class User < ApplicationRecord\n"
34 | inject_into_file file, after: after do
35 | " include Chat::User\n"
36 | end
37 | end
38 |
39 | def add_chat_status_migration
40 | migration_template "add_chat_to_users.rb",
41 | "db/migrate/add_chat_to_users.rb"
42 | migration_template "create_chat.rb", "db/migrate/create_chat.rb"
43 | end
44 |
45 | def add_chat_route
46 | route "mount Chat::Engine => \"/chat\", as: \"chat\""
47 | end
48 |
49 | class UsersTableNotImplemented < StandardError
50 | def initialize(msg = "User model not yet implemented")
51 | super
52 | end
53 | end
54 |
55 | def self.next_migration_number(dirname)
56 | ActiveRecord::Generators::Base.next_migration_number(dirname)
57 | end
58 |
59 | private
60 |
61 | def users_table_exists?
62 | if ActiveRecord::Base.connection.respond_to?(:data_source_exists?)
63 | ActiveRecord::Base.connection.data_source_exists?(:users)
64 | else
65 | ActiveRecord::Base.connection.table_exists?(:users)
66 | end
67 | end
68 | end
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/lib/generators/chat/install/templates/add_chat_to_users.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddChatToUsers < ActiveRecord::Migration[5.1]
4 | def self.up
5 | add_column :users, :chat_status, :string, default: "offline"
6 | end
7 |
8 | def self.down
9 | remove_column :users, :chat_status, :string, default: "offline"
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/generators/chat/install/templates/chat.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Chat.setup do |config|
4 | # Clearance => :signed_in?
5 | # Devise => :user_signed_in?
6 | config.signed_in = :signed_in?
7 |
8 | # Clearance => :require_login
9 | # Devise => :authenticate_user!
10 | config.logged_in_check = :require_login
11 |
12 | config.user_avatar = :image
13 |
14 | config.perform_method = :perform_now
15 | end
16 |
--------------------------------------------------------------------------------
/lib/generators/chat/install/templates/create_chat.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateChat < ActiveRecord::Migration[5.1]
4 | def self.up
5 | create_table :chat_conversations do |t|
6 | t.timestamps
7 | end
8 |
9 | create_sessions
10 | create_messages
11 | end
12 |
13 | def self.down
14 | drop_table :chat_conversations
15 | drop_table :chat_sessions
16 | drop_table :chat_messages
17 | end
18 |
19 | private
20 |
21 | def create_sessions
22 | create_table :chat_sessions do |t|
23 | t.belongs_to :conversation
24 | t.belongs_to :user
25 |
26 | t.timestamps
27 | end
28 | end
29 |
30 | def create_messages
31 | create_table :chat_messages do |t|
32 | t.belongs_to :user
33 | t.text :text
34 | t.belongs_to :conversation
35 | t.belongs_to :session
36 | t.attachment :image
37 |
38 | t.timestamps
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/tasks/chat_tasks.rake:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # desc "Explaining what the task does"
3 | # task :chat do
4 | # # Task goes here
5 | # end
6 |
--------------------------------------------------------------------------------
/test/chat_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "test_helper"
4 |
5 | class Chat::Test < ActiveSupport::TestCase
6 | test "truth" do
7 | assert_kind_of Chat, Module
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/test/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Add your own tasks in files placed in lib/tasks ending in .rake,
4 | # for example lib/tasks/capistrano.rake, and they will automatically be
5 | # available to Rake.
6 |
7 | require_relative "config/application"
8 |
9 | Rails.application.load_tasks
10 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 |
2 | //= link_tree ../images
3 | //= link_directory ../javascripts .js
4 | //= link_directory ../stylesheets .css
5 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/npezza93/chat/c8462876bd068532f6c4ebcf1d32570ea60f1dec/test/dummy/app/assets/images/.keep
--------------------------------------------------------------------------------
/test/dummy/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require jquery
14 | //= require jquery_ujs
15 | //= require turbolinks
16 | //= require chat
17 | //= require_tree .
18 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/javascripts/cable.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the rails generate channel command.
3 | //
4 | //= require action_cable
5 | //= require_self
6 | //= require_tree ./channels
7 |
8 | (function() {
9 | this.App || (this.App = {});
10 |
11 | App.cable = ActionCable.createConsumer();
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/javascripts/channels/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/npezza93/chat/c8462876bd068532f6c4ebcf1d32570ea60f1dec/test/dummy/app/assets/javascripts/channels/.keep
--------------------------------------------------------------------------------
/test/dummy/app/assets/javascripts/material_design_components.js:
--------------------------------------------------------------------------------
1 | /*global mdc $:true*/
2 |
3 | $(document).on("turbolinks:load", function() {
4 | initialize_mdc();
5 | });
6 |
7 | function initialize_mdc() {
8 | mdc.autoInit();
9 |
10 | $.each($(".mdc-textfield"), function(index, element) {
11 | mdc.textfield.MDCTextfield.attachTo(element);
12 | });
13 | $.each($(".mdc-fab, .mdc-button, .mdc-ripple-surface, .mdc-list-item.with-ripple"), function(index, element) {
14 | mdc.ripple.MDCRipple.attachTo(element);
15 | });
16 | $.each($(".mdc-select"), function(index, element) {
17 | mdc.select.MDCSelect.attachTo(element).listen('MDCSelect:change', function(e) {
18 | $(this).find("input[type='hidden']").val(e.detail.value);
19 | });
20 | });
21 | $.each($(".mdc-simple-menu"), function(index, element) {
22 | var menu = mdc.menu.MDCSimpleMenu.attachTo(element);
23 | $(element).parent().find(".menu-toggle").click(function() {
24 | menu.open = !menu.open;
25 | });
26 | });
27 |
28 | if ($(".mdc-snackbar").length > 0) {
29 | var snackbar = mdc.snackbar.MDCSnackbar.attachTo($(".mdc-snackbar")[0]);
30 | snackbar.show({ message: $(".mdc-snackbar").find(".mdc-snackbar__text").text() });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | *= require chat
16 | */
17 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/stylesheets/home.sass:
--------------------------------------------------------------------------------
1 | @import "https://fonts.googleapis.com/css?family=Roboto:300,400,500"
2 |
3 | body, html
4 | height: 100%
5 | width: 100%
6 | margin: 0
7 | background-image: linear-gradient( 135deg, #5EFCE8 0%, #736EFE 100%)
8 | a
9 | color: var(--mdc-theme-text-primary-on-primary,#fff)
10 | text-decoration: none
11 |
12 | #homeContainer
13 | width: 100%
14 | height: 100%
15 | display: flex
16 | flex-direction: column
17 | align-items: center
18 | justify-content: center
19 | a
20 | font-weight: 300
21 | margin-top: 24px
22 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/stylesheets/sessions.sass:
--------------------------------------------------------------------------------
1 | @import "https://fonts.googleapis.com/css?family=Lato:300,400,700,900"
2 |
3 | \:root
4 | --mdc-theme-primary: #FAFAFA
5 |
6 | h1
7 | font-weight: 400
8 | .link-back
9 | position: absolute
10 | top: 0
11 | left: 0
12 | margin: 10px
13 | color: var(--mdc-theme-text-primary-on-primary,#fff)
14 | .sign_up_links
15 | margin-top: 30px
16 | #signed_in_user
17 | color: var(--mdc-theme-text-primary-on-primary,#fff)
18 | padding: 10px
19 | display: flex
20 | flex-direction: row
21 | align-items: flex-end
22 | position: absolute
23 | top: 0
24 | right: 0
25 | span
26 | margin: 0 10px
27 | .container
28 | display: flex
29 | flex-direction: column
30 | align-items: center
31 | justify-content: center
32 | height: 100%
33 |
34 | a
35 | margin-top: 32px
36 |
37 | form
38 | display: flex
39 | flex-direction: column
40 |
41 | button
42 | display: flex
43 | flex-direction: row
44 | justify-content: center
45 | label
46 | color: var(--mdc-theme-text-hint-on-dark, rgba(0, 0, 0, 0.5))
47 | input[type="file"]
48 | margin: 10px 0
49 | color: var(--mdc-theme-text-primary-on-primary,#fff)
50 |
--------------------------------------------------------------------------------
/test/dummy/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationCable
4 | class Channel < ActionCable::Channel::Base
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/test/dummy/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationCable
4 | class Connection < ActionCable::Connection::Base
5 | identified_by :current_user
6 |
7 | def connect
8 | self.current_user = find_verified_user
9 | end
10 |
11 | protected
12 |
13 | def find_verified_user
14 | if (current_user = User.find_by(remember_token: token))
15 | current_user
16 | else
17 | reject_unauthorized_connection
18 | end
19 | end
20 |
21 | def token
22 | request.cookie_jar[:remember_token]
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/test/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationController < ActionController::Base
4 | helper Chat::Engine.helpers
5 | include Clearance::Controller
6 | protect_from_forgery with: :exception
7 | end
8 |
--------------------------------------------------------------------------------
/test/dummy/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/npezza93/chat/c8462876bd068532f6c4ebcf1d32570ea60f1dec/test/dummy/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/test/dummy/app/controllers/home_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class HomeController < ApplicationController
4 | def index
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/test/dummy/app/controllers/users_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class UsersController < ApplicationController
4 | before_action :require_login
5 | before_action :set_user, only: %i(edit update)
6 |
7 | def edit
8 | end
9 |
10 | def update
11 | if @user.update(user_params)
12 | redirect_to root_path, notice: "User was successfully updated."
13 | else
14 | render :edit
15 | end
16 | end
17 |
18 | private
19 |
20 | def set_user
21 | @user = current_user
22 | end
23 |
24 | def user_params
25 | params.require(:user).permit(:first, :last, :image, :email, :password)
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/test/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationHelper
4 | end
5 |
--------------------------------------------------------------------------------
/test/dummy/app/helpers/snackbar_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SnackbarHelper
4 | def snackbar_notice(message, opts = nil)
5 | return if message.blank?
6 |
7 | content_tag(
8 | :div, snackbar_text(message),
9 | class: "mdc-snackbar #{opts}",
10 | aria: { live: :assertive, atomic: "", hidden: "" }
11 | )
12 | end
13 |
14 | def snackbar_text(message)
15 | content_tag(:div, message, class: "mdc-snackbar__text") + snackbar_button
16 | end
17 |
18 | def snackbar_button
19 | content_tag(:div, class: "mdc-snackbar__action-wrapper") do
20 | content_tag(
21 | :button, nil,
22 | class: "mdc-button mdc-snackbar__action-button", type: :button
23 | )
24 | end.html_safe
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/test/dummy/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationJob < ActiveJob::Base
4 | end
5 |
--------------------------------------------------------------------------------
/test/dummy/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationMailer < ActionMailer::Base
4 | default from: "from@example.com"
5 | layout "mailer"
6 | end
7 |
--------------------------------------------------------------------------------
/test/dummy/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationRecord < ActiveRecord::Base
4 | self.abstract_class = true
5 | end
6 |
--------------------------------------------------------------------------------
/test/dummy/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/npezza93/chat/c8462876bd068532f6c4ebcf1d32570ea60f1dec/test/dummy/app/models/concerns/.keep
--------------------------------------------------------------------------------
/test/dummy/app/models/user.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class User < ApplicationRecord
4 | include Chat::User
5 | include Clearance::User
6 |
7 | has_attached_file :image
8 | validates_attachment :image, content_type: {
9 | content_type: ["image/jpg", "image/jpeg", "image/png", "image/gif"]
10 | }
11 | end
12 |
--------------------------------------------------------------------------------
/test/dummy/app/views/clearance/users/_form.html.haml:
--------------------------------------------------------------------------------
1 | .mdc-textfield
2 | = f.email_field :email, autofocus: true,
3 | class: "mdc-textfield__input mdc-theme--text-primary-on-primary",
4 | required: true
5 | = f.label :email, "Email", class: "mdc-textfield__label"
6 |
7 | .mdc-textfield
8 | = f.password_field :password,
9 | class: "mdc-textfield__input mdc-theme--text-primary-on-primary",
10 | required: true
11 | = f.label :password, "Password", class: "mdc-textfield__label"
12 |
--------------------------------------------------------------------------------
/test/dummy/app/views/home/index.html.haml:
--------------------------------------------------------------------------------
1 | #homeContainer.mdc-theme--text-primary-on-primary
2 | %h1 Chat
3 | - unless signed_in?
4 | = link_to "Sign In", sign_in_path, class: "mdc-button mdc-button--primary"
5 |
6 |
7 | = render_chat
8 |
--------------------------------------------------------------------------------
/test/dummy/app/views/layouts/application.html.haml:
--------------------------------------------------------------------------------
1 | !!!
2 | %html
3 | %head
4 | %title Dummy
5 | = csrf_meta_tags
6 | = stylesheet_link_tag 'application',
7 | media: 'all', 'data-turbolinks-track': 'reload'
8 | = javascript_include_tag 'application',
9 | 'data-turbolinks-track': 'reload'
10 | = action_cable_meta_tag
11 |
12 | %meta{ content: "width=device-width, initial-scale=1.0", name: "viewport"}/
13 | %body.mdc-typography
14 | = snackbar_notice(notice || alert)
15 |
16 | - if signed_in?
17 | #signed_in_user
18 | = link_to 'Settings', edit_users_path
19 | %span= "|"
20 | = link_to 'Sign out', sign_out_path, method: :delete
21 |
22 | = yield
23 |
--------------------------------------------------------------------------------
/test/dummy/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/dummy/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/test/dummy/app/views/sessions/new.html.haml:
--------------------------------------------------------------------------------
1 | = link_to root_path, class: "link-back" do
2 | = material_icon.arrow_back
3 |
4 | .container
5 | %h1.mdc-theme--text-primary-on-primary= t('.title')
6 | = form_for :session, url: session_path do |f|
7 | .mdc-textfield
8 | = f.email_field :email, autofocus: true,
9 | class: "mdc-textfield__input mdc-theme--text-primary-on-primary",
10 | required: true
11 | = f.label :email, "Email", class: "mdc-textfield__label"
12 |
13 | .mdc-textfield
14 | = f.password_field :password,
15 | class: "mdc-textfield__input mdc-theme--text-primary-on-primary",
16 | required: true
17 | = f.label :password, "Password", class: "mdc-textfield__label"
18 |
19 | %button.mdc-button.mdc-button--primary{ type: :submit } Sign In
20 |
21 | - if Clearance.configuration.allow_sign_up?
22 | = link_to t('.sign_up'), sign_up_path,
23 | class: "mdc-button mdc-button--primary"
24 |
--------------------------------------------------------------------------------
/test/dummy/app/views/users/edit.html.haml:
--------------------------------------------------------------------------------
1 | = link_to root_path, class: "link-back" do
2 | = material_icon.arrow_back
3 |
4 | .container
5 | %h1.mdc-theme--text-primary-on-primary Settings
6 | = form_for @user, url: users_path, method: :put do |f|
7 | = render 'clearance/users/form', f: f
8 |
9 | .mdc-textfield
10 | = f.text_field :first_name,
11 | class: "mdc-textfield__input mdc-theme--text-primary-on-primary",
12 | required: true
13 | = f.label :first_name, "First Name", class: "mdc-textfield__label"
14 |
15 | .mdc-textfield
16 | = f.text_field :last_name,
17 | class: "mdc-textfield__input mdc-theme--text-primary-on-primary",
18 | required: true
19 | = f.label :last_name, "Last Name", class: "mdc-textfield__label"
20 |
21 | = f.file_field :image, placeholder: 'Image'
22 |
23 | %button.mdc-button.mdc-button--primary{ type: :submit } Save
24 |
--------------------------------------------------------------------------------
/test/dummy/app/views/users/new.html.haml:
--------------------------------------------------------------------------------
1 | = link_to sign_in_path, class: "link-back" do
2 | = material_icon.arrow_back
3 |
4 | .container
5 | %h1.mdc-theme--text-primary-on-primary= t('.title')
6 | = form_for @user do |f|
7 | = render 'clearance/users/form', f: f
8 |
9 | %button.mdc-button.mdc-button--primary{ type: :submit } Sign Up
10 |
--------------------------------------------------------------------------------
/test/dummy/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
5 | load Gem.bin_path("bundler", "bundle")
6 |
--------------------------------------------------------------------------------
/test/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | APP_PATH = File.expand_path("../config/application", __dir__)
5 | require_relative "../config/boot"
6 | require "rails/commands"
7 |
--------------------------------------------------------------------------------
/test/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require_relative "../config/boot"
5 | require "rake"
6 | Rake.application.run
7 |
--------------------------------------------------------------------------------
/test/dummy/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require "pathname"
5 | require "fileutils"
6 | include FileUtils
7 |
8 | # path to your application root.
9 | APP_ROOT = Pathname.new File.expand_path("../../", __FILE__)
10 |
11 | def system!(*args)
12 | system(*args) || abort("\n== Command #{args} failed ==")
13 | end
14 |
15 | chdir APP_ROOT do
16 | # This script is a starting point to setup your application.
17 | # Add necessary setup steps to this file.
18 |
19 | puts "== Installing dependencies =="
20 | system! "gem install bundler --conservative"
21 | system("bundle check") || system!("bundle install")
22 |
23 | # puts "\n== Copying sample files =="
24 | # unless File.exist?('config/database.yml')
25 | # cp 'config/database.yml.sample', 'config/database.yml'
26 | # end
27 |
28 | puts "\n== Preparing database =="
29 | system! "bin/rails db:setup"
30 |
31 | puts "\n== Removing old logs and tempfiles =="
32 | system! "bin/rails log:clear tmp:clear"
33 |
34 | puts "\n== Restarting application server =="
35 | system! "bin/rails restart"
36 | end
37 |
--------------------------------------------------------------------------------
/test/dummy/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require "pathname"
5 | require "fileutils"
6 | include FileUtils
7 |
8 | # path to your application root.
9 | APP_ROOT = Pathname.new File.expand_path("../../", __FILE__)
10 |
11 | def system!(*args)
12 | system(*args) || abort("\n== Command #{args} failed ==")
13 | end
14 |
15 | chdir APP_ROOT do
16 | # This script is a way to update your development environment automatically.
17 | # Add necessary update steps to this file.
18 |
19 | puts "== Installing dependencies =="
20 | system! "gem install bundler --conservative"
21 | system("bundle check") || system!("bundle install")
22 |
23 | puts "\n== Updating database =="
24 | system! "bin/rails db:migrate"
25 |
26 | puts "\n== Removing old logs and tempfiles =="
27 | system! "bin/rails log:clear tmp:clear"
28 |
29 | puts "\n== Restarting application server =="
30 | system! "bin/rails restart"
31 | end
32 |
--------------------------------------------------------------------------------
/test/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # This file is used by Rack-based servers to start the application.
4 |
5 | require_relative "config/environment"
6 |
7 | run Rails.application
8 |
--------------------------------------------------------------------------------
/test/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative "boot"
4 |
5 | require "rails/all"
6 |
7 | Bundler.require(*Rails.groups)
8 | require "chat"
9 |
10 | module Dummy
11 | class Application < Rails::Application
12 | # Settings in config/environments/* take precedence over those specified
13 | # here.
14 | # Application configuration should go into files in config/initializers
15 | # -- all .rb files in that directory are automatically loaded.
16 | config.active_job.queue_adapter = :sidekiq
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Set up gems listed in the Gemfile.
4 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__)
5 |
6 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
7 | $LOAD_PATH.unshift File.expand_path("../../../lib", __dir__)
8 |
--------------------------------------------------------------------------------
/test/dummy/config/cable.yml:
--------------------------------------------------------------------------------
1 | default: &default
2 | adapter: redis
3 | url: redis://localhost:6379/1
4 |
5 | development:
6 | <<: *default
7 |
8 | test:
9 | <<: *default
10 |
11 | production:
12 | <<: *default
13 |
--------------------------------------------------------------------------------
/test/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: 5
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/test/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Load the Rails application.
4 | require_relative "application"
5 |
6 | # Initialize the Rails application.
7 | Rails.application.initialize!
8 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in
5 | # config/application.rb.
6 |
7 | # In the development environment your application's code is reloaded on
8 | # every request. This slows down response time but is perfect for development
9 | # since you don't have to restart the web server when you make code changes.
10 | config.cache_classes = false
11 |
12 | # Do not eager load code on boot.
13 | config.eager_load = false
14 |
15 | # Show full error reports.
16 | config.consider_all_requests_local = true
17 |
18 | # Enable/disable caching. By default caching is disabled.
19 | if Rails.root.join("tmp/caching-dev.txt").exist?
20 | config.action_controller.perform_caching = true
21 |
22 | config.cache_store = :memory_store
23 | config.public_file_server.headers = {
24 | "Cache-Control" => "public, max-age=172800"
25 | }
26 | else
27 | config.action_controller.perform_caching = false
28 |
29 | config.cache_store = :null_store
30 | end
31 |
32 | # Don't care if the mailer can't send.
33 | config.action_mailer.raise_delivery_errors = false
34 |
35 | config.action_mailer.perform_caching = false
36 |
37 | # Print deprecation notices to the Rails logger.
38 | config.active_support.deprecation = :log
39 |
40 | # Raise an error on page load if there are pending migrations.
41 | config.active_record.migration_error = :page_load
42 |
43 | # Debug mode disables concatenation and preprocessing of assets.
44 | # This option may cause significant delays in view rendering with a large
45 | # number of complex assets.
46 | config.assets.debug = true
47 |
48 | # Suppress logger output for asset requests.
49 | config.assets.quiet = true
50 |
51 | # Raises error for missing translations
52 | # config.action_view.raise_on_missing_translations = true
53 |
54 | # Use an evented file watcher to asynchronously detect changes in source code,
55 | # routes, locales, etc. This feature depends on the listen gem.
56 | # config.file_watcher = ActiveSupport::EventedFileUpdateChecker
57 | end
58 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in
5 | # config/application.rb.
6 |
7 | # Code is not reloaded between requests.
8 | config.cache_classes = true
9 |
10 | # Eager load code on boot. This eager loads most of Rails and
11 | # your application in memory, allowing both threaded web servers
12 | # and those relying on copy on write to perform better.
13 | # Rake tasks automatically ignore this option for performance.
14 | config.eager_load = true
15 |
16 | # Full error reports are disabled and caching is turned on.
17 | config.consider_all_requests_local = false
18 | config.action_controller.perform_caching = true
19 |
20 | # Disable serving static files from the `/public` folder by default since
21 | # Apache or NGINX already handles this.
22 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
23 |
24 | # Compress JavaScripts and CSS.
25 | config.assets.js_compressor = :uglifier
26 | # config.assets.css_compressor = :sass
27 |
28 | # Do not fallback to assets pipeline if a precompiled asset is missed.
29 | config.assets.compile = false
30 |
31 | # `config.assets.precompile` and `config.assets.version` have moved to
32 | # config/initializers/assets.rb
33 |
34 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
35 | # config.action_controller.asset_host = 'http://assets.example.com'
36 |
37 | # Specifies the header that your server uses for sending files.
38 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
40 |
41 | # Mount Action Cable outside main process or domain
42 | # config.action_cable.mount_path = nil
43 | # config.action_cable.url = 'wss://example.com/cable'
44 | # config.action_cable.allowed_request_origins = [ 'http://example.com',
45 | # /http:\/\/example.*/ ]
46 |
47 | # Force all access to the app over SSL, use Strict-Transport-Security,
48 | # and use secure cookies.
49 | # config.force_ssl = true
50 |
51 | # Use the lowest log level to ensure availability of diagnostic information
52 | # when problems arise.
53 | config.log_level = :debug
54 |
55 | # Prepend all log lines with the following tags.
56 | config.log_tags = [ :request_id ]
57 |
58 | # Use a different cache store in production.
59 | # config.cache_store = :mem_cache_store
60 |
61 | # Use a real queuing backend for Active Job
62 | # (and separate queues per environment)
63 | # config.active_job.queue_adapter = :resque
64 | # config.active_job.queue_name_prefix = "dummy_#{Rails.env}"
65 | config.action_mailer.perform_caching = false
66 |
67 | # Ignore bad email addresses and do not raise email delivery errors.
68 | # Set this to true and configure the email server for immediate delivery to
69 | # raise delivery errors.
70 | # config.action_mailer.raise_delivery_errors = false
71 |
72 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
73 | # the I18n.default_locale when a translation cannot be found).
74 | config.i18n.fallbacks = true
75 |
76 | # Send deprecation notices to registered listeners.
77 | config.active_support.deprecation = :notify
78 |
79 | # Use default logging formatter so that PID and timestamp are not suppressed.
80 | config.log_formatter = ::Logger::Formatter.new
81 |
82 | # Use a different logger for distributed setups.
83 | # require 'syslog/logger'
84 | # config.logger =
85 | # ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
86 |
87 | if ENV["RAILS_LOG_TO_STDOUT"].present?
88 | logger = ActiveSupport::Logger.new(STDOUT)
89 | logger.formatter = config.log_formatter
90 | config.logger = ActiveSupport::TaggedLogging.new(logger)
91 | end
92 |
93 | # Do not dump schema after migrations.
94 | config.active_record.dump_schema_after_migration = false
95 | end
96 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in
5 | # config/application.rb.
6 |
7 | # The test environment is used exclusively to run your application's
8 | # test suite. You never need to work with it otherwise. Remember that
9 | # your test database is "scratch space" for the test suite and is wiped
10 | # and recreated between test runs. Don't rely on the data there!
11 | config.cache_classes = true
12 |
13 | # Do not eager load code on boot. This avoids loading your whole application
14 | # just for the purpose of running a single test. If you are using a tool that
15 | # preloads Rails for running tests, you may have to set it to true.
16 | config.eager_load = false
17 |
18 | # Configure public file server for tests with Cache-Control for performance.
19 | config.public_file_server.enabled = true
20 | config.public_file_server.headers = {
21 | "Cache-Control" => "public, max-age=3600"
22 | }
23 |
24 | # Show full error reports and disable caching.
25 | config.consider_all_requests_local = true
26 | config.action_controller.perform_caching = false
27 |
28 | # Raise exceptions instead of rendering exception templates.
29 | config.action_dispatch.show_exceptions = false
30 |
31 | # Disable request forgery protection in test environment.
32 | config.action_controller.allow_forgery_protection = false
33 | config.action_mailer.perform_caching = false
34 |
35 | # Tell Action Mailer not to deliver emails to the real world.
36 | # The :test delivery method accumulates sent emails in the
37 | # ActionMailer::Base.deliveries array.
38 | config.action_mailer.delivery_method = :test
39 |
40 | # Print deprecation notices to the stderr.
41 | config.active_support.deprecation = :stderr
42 |
43 | # Raises error for missing translations
44 | # config.action_view.raise_on_missing_translations = true
45 | end
46 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # ApplicationController.renderer.defaults.merge!(
5 | # http_host: 'example.org',
6 | # https: false
7 | # )
8 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Version of your assets, change this if you want to expire all your assets.
6 | Rails.application.config.assets.version = "1.0"
7 |
8 | # Add additional assets to the asset load path
9 | # Rails.application.config.assets.paths << Emoji.images_path
10 |
11 | # Precompile additional assets.
12 | # application.js, application.css, and all non-JS/CSS in app/assets folder are
13 | # already added.
14 | # Rails.application.config.assets.precompile += %w( search.js )
15 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # You can add backtrace silencers for libraries that you're using but don't wish
5 | # to see in your backtraces.
6 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
7 |
8 | # You can also remove all the silencers if you're trying to debug a problem that
9 | # might stem from framework code.
10 | # Rails.backtrace_cleaner.remove_silencers!
11 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/chat.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Chat.setup do |config|
4 | # Clearance => :signed_in?
5 | # Devise => :user_signed_in?
6 | config.signed_in = :signed_in?
7 |
8 | # Clearance => :require_login
9 | # Devise => :authenticate_user!
10 | config.logged_in_check = :require_login
11 |
12 | config.user_avatar = :image
13 |
14 | config.perform_method = :perform_now
15 | end
16 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/clearance.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Clearance.configure do |config|
4 | config.mailer_sender = "reply@example.com"
5 | end
6 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Specify a serializer for the signed and encrypted cookie jars.
6 | # Valid options are :json, :marshal, and :hybrid.
7 | Rails.application.config.action_dispatch.cookies_serializer = :json
8 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Configure sensitive parameters which will be filtered from the log file.
6 | Rails.application.config.filter_parameters += [:password]
7 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # Add new inflection rules using the following format. Inflections
5 | # are locale specific, and you may define rules for as many different
6 | # locales as you wish. All of these examples are active by default:
7 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
8 | # inflect.plural /^(ox)$/i, '\1en'
9 | # inflect.singular /^(ox)en/i, '\1'
10 | # inflect.irregular 'person', 'people'
11 | # inflect.uncountable %w( fish sheep )
12 | # end
13 |
14 | # These inflection rules are supported but not enabled by default:
15 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
16 | # inflect.acronym 'RESTful'
17 | # end
18 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # Add new mime types for use in respond_to blocks:
5 | # Mime::Type.register "text/richtext", :rtf
6 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/new_framework_defaults.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 | #
5 | # This file contains migration options to ease your Rails 5.0 upgrade.
6 | #
7 | # Read the Rails 5.0 release notes for more info on each option.
8 |
9 | # Enable per-form CSRF tokens. Previous versions had false.
10 | Rails.application.config.action_controller.per_form_csrf_tokens = true
11 |
12 | # Enable origin-checking CSRF mitigation. Previous versions had false.
13 | Rails.application.config.action_controller.forgery_protection_origin_check =
14 | true
15 |
16 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`.
17 | # Previous versions had false.
18 | ActiveSupport.to_time_preserves_timezone = true
19 |
20 | # Require `belongs_to` associations by default. Previous versions had false.
21 | Rails.application.config.active_record.belongs_to_required_by_default = true
22 |
23 | # Do not halt callback chains when a callback returns false. Previous versions
24 | # had true.
25 | ActiveSupport.halt_callback_chains_on_return_false = false
26 |
27 | # Configure SSL options to enable HSTS with subdomains. Previous versions had
28 | # false.
29 | Rails.application.config.ssl_options = { hsts: { subdomains: true } }
30 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | Rails.application.config.session_store :cookie_store, key: "_dummy_session"
6 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # This file contains settings for ActionController::ParamsWrapper which
6 | # is enabled by default.
7 |
8 | # Enable parameter wrapping for JSON. You can disable this by setting :format
9 | # to an empty array.
10 | ActiveSupport.on_load(:action_controller) do
11 | wrap_parameters format: [:json]
12 | end
13 |
14 | # To enable root element in JSON for ActiveRecord objects.
15 | # ActiveSupport.on_load(:active_record) do
16 | # self.include_root_in_json = true
17 | # end
18 |
--------------------------------------------------------------------------------
/test/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/test/dummy/config/puma.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Puma can serve each request in a thread from an internal thread pool.
4 | # The `threads` method setting takes two numbers a minimum and maximum.
5 | # Any libraries that use thread pools should be configured to match
6 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
7 | # and maximum, this matches the default thread size of Active Record.
8 | #
9 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
10 | threads threads_count, threads_count
11 |
12 | # Specifies the `port` that Puma will listen on to receive requests, default is
13 | # 3000.
14 | port ENV.fetch("PORT") { 3000 }
15 |
16 | # Specifies the `environment` that Puma will run in.
17 | #
18 | environment ENV.fetch("RAILS_ENV") { "development" }
19 |
20 | # Specifies the number of `workers` to boot in clustered mode.
21 | # Workers are forked webserver processes. If using threads and workers together
22 | # the concurrency of the application would be max `threads` * `workers`.
23 | # Workers do not work on JRuby or Windows (both of which do not support
24 | # processes).
25 | #
26 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
27 |
28 | # Use the `preload_app!` method when specifying a `workers` number.
29 | # This directive tells Puma to first boot the application and load code
30 | # before forking the application. This takes advantage of Copy On Write
31 | # process behavior so workers use less memory. If you use this option
32 | # you need to make sure to reconnect any threads in the `on_worker_boot`
33 | # block.
34 | #
35 | # preload_app!
36 |
37 | # The code in the `on_worker_boot` will be called if you are using
38 | # clustered mode by specifying a number of `workers`. After each worker
39 | # process is booted this block will be run, if you are using `preload_app!`
40 | # option you will want to use this block to reconnect to any threads
41 | # or connections that may have been created at application boot, Ruby
42 | # cannot share connections between processes.
43 | #
44 | # on_worker_boot do
45 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
46 | # end
47 |
48 | # Allow puma to be restarted by `rails restart` command.
49 | plugin :tmp_restart
50 |
--------------------------------------------------------------------------------
/test/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.routes.draw do
4 | mount Chat::Engine => "/chat", as: "chat"
5 | root to: "home#index", id: "home"
6 |
7 | resource :users, only: %i(edit update)
8 |
9 | mount ActionCable.server => "/cable"
10 | end
11 |
--------------------------------------------------------------------------------
/test/dummy/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rails secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development:
14 | secret_key_base: a295d0eb38e63f477b38a7d5abac7dbf4f72c046d7994d9bb7f7eec6a877c3a90d0073033ea78f8d1277df07462a843f056e4cea112c7ef4368fdd5d7bf13b41
15 |
16 | test:
17 | secret_key_base: 4a7c0cf250513a95464864600a5cc56fce804622997f4fd5767ed77dce9cef2ace66c809941f87ab70470bb9df0c619ff1a86a550536c7fa74509fb3bcdfc621
18 |
19 | # Do not keep production secrets in the repository,
20 | # instead read values from the environment.
21 | production:
22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
23 |
--------------------------------------------------------------------------------
/test/dummy/config/sidekiq.yml:
--------------------------------------------------------------------------------
1 | ---
2 | :concurrency: 25
3 | development:
4 | :concurrency: 2
5 | staging:
6 | :concurrency: 10
7 | :queues:
8 | - default
9 |
--------------------------------------------------------------------------------
/test/dummy/config/spring.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | %w(
4 | .ruby-version
5 | .rbenv-vars
6 | tmp/restart.txt
7 | tmp/caching-dev.txt
8 | ).each { |path| Spring.watch(path) }
9 |
--------------------------------------------------------------------------------
/test/dummy/db/migrate/20160911032725_create_users.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateUsers < ActiveRecord::Migration[5.1]
4 | def change
5 | create_table :users do |t|
6 | t.timestamps null: false
7 | t.string :email, null: false
8 | t.string :first_name
9 | t.string :last_name
10 | t.attachment :image
11 | add_tokens_password(t)
12 | end
13 |
14 | add_index :users, :email
15 | add_index :users, :remember_token
16 | end
17 |
18 | private
19 |
20 | def add_tokens_password(table)
21 | table.string :encrypted_password, limit: 128, null: false
22 | table.string :confirmation_token, limit: 128
23 | table.string :remember_token, limit: 128, null: false
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/test/dummy/db/migrate/20160919005427_add_chat_to_users.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddChatToUsers < ActiveRecord::Migration[5.0]
4 | def self.up
5 | add_column :users, :chat_status, :string, default: "offline"
6 | end
7 |
8 | def self.down
9 | remove_column :users, :chat_status, :string, default: "offline"
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/test/dummy/db/migrate/20160919005428_create_chat.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateChat < ActiveRecord::Migration[5.0]
4 | def self.up
5 | create_table :chat_conversations do |t|
6 | t.timestamps
7 | end
8 |
9 | create_sessions
10 | create_messages
11 | end
12 |
13 | def self.down
14 | drop_table :chat_conversations
15 | drop_table :chat_sessions
16 | drop_table :chat_messages
17 | end
18 |
19 | private
20 |
21 | def create_sessions
22 | create_table :chat_sessions do |t|
23 | t.belongs_to :conversation
24 | t.belongs_to :user
25 |
26 | t.timestamps
27 | end
28 | end
29 |
30 | def create_messages
31 | create_table :chat_messages do |t|
32 | t.belongs_to :user
33 | t.text :text
34 | t.belongs_to :conversation
35 | t.belongs_to :session
36 | t.attachment :image
37 |
38 | t.timestamps
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/test/dummy/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 20160919005428) do
14 |
15 | create_table "chat_conversations", force: :cascade do |t|
16 | t.datetime "created_at", null: false
17 | t.datetime "updated_at", null: false
18 | end
19 |
20 | create_table "chat_messages", force: :cascade do |t|
21 | t.integer "user_id"
22 | t.text "text"
23 | t.integer "conversation_id"
24 | t.integer "session_id"
25 | t.string "image_file_name"
26 | t.string "image_content_type"
27 | t.integer "image_file_size"
28 | t.datetime "image_updated_at"
29 | t.datetime "created_at", null: false
30 | t.datetime "updated_at", null: false
31 | t.index ["conversation_id"], name: "index_chat_messages_on_conversation_id"
32 | t.index ["session_id"], name: "index_chat_messages_on_session_id"
33 | t.index ["user_id"], name: "index_chat_messages_on_user_id"
34 | end
35 |
36 | create_table "chat_sessions", force: :cascade do |t|
37 | t.integer "conversation_id"
38 | t.integer "user_id"
39 | t.datetime "created_at", null: false
40 | t.datetime "updated_at", null: false
41 | t.index ["conversation_id"], name: "index_chat_sessions_on_conversation_id"
42 | t.index ["user_id"], name: "index_chat_sessions_on_user_id"
43 | end
44 |
45 | create_table "users", force: :cascade do |t|
46 | t.datetime "created_at", null: false
47 | t.datetime "updated_at", null: false
48 | t.string "email", null: false
49 | t.string "first_name"
50 | t.string "last_name"
51 | t.string "image_file_name"
52 | t.string "image_content_type"
53 | t.integer "image_file_size"
54 | t.datetime "image_updated_at"
55 | t.string "encrypted_password", limit: 128, null: false
56 | t.string "confirmation_token", limit: 128
57 | t.string "remember_token", limit: 128, null: false
58 | t.string "chat_status", default: "offline"
59 | t.index ["email"], name: "index_users_on_email"
60 | t.index ["remember_token"], name: "index_users_on_remember_token"
61 | end
62 |
63 | end
64 |
--------------------------------------------------------------------------------
/test/dummy/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | admin = User.create(
4 | first_name: "Nick",
5 | last_name: "Pezza",
6 | password: "password",
7 | email: "admin@admin.com",
8 | image: open(Faker::Avatar.image)
9 | )
10 |
11 | def create_user
12 | name = Faker::StarWars.character
13 |
14 | User.create(
15 | first_name: name.split(" ")[0],
16 | last_name: name.split(" ")[1],
17 | password: "password",
18 | email: "#{name.parameterize}@chat.com",
19 | image: open(Faker::Avatar.image)
20 | ).id
21 | end
22 |
23 | 5.times do |i|
24 | conversation = Chat::Conversation.create(user_ids: [create_user, admin.id])
25 |
26 | i.times do
27 | conversation.reload.sessions.create(user_id: create_user)
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/test/dummy/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/npezza93/chat/c8462876bd068532f6c4ebcf1d32570ea60f1dec/test/dummy/lib/assets/.keep
--------------------------------------------------------------------------------
/test/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/test/dummy/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/npezza93/chat/c8462876bd068532f6c4ebcf1d32570ea60f1dec/test/dummy/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/test/dummy/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/npezza93/chat/c8462876bd068532f6c4ebcf1d32570ea60f1dec/test/dummy/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/test/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/npezza93/chat/c8462876bd068532f6c4ebcf1d32570ea60f1dec/test/dummy/public/favicon.ico
--------------------------------------------------------------------------------
/test/integration/navigation_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "test_helper"
4 |
5 | class NavigationTest < ActionDispatch::IntegrationTest
6 | # test "the truth" do
7 | # assert true
8 | # end
9 | end
10 |
--------------------------------------------------------------------------------
/test/lib/generators/chat/install_generator_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "test_helper"
4 | require "generators/install/install_generator"
5 |
6 | module Chat
7 | class InstallGeneratorTest < Rails::Generators::TestCase
8 | tests InstallGenerator
9 | destination Rails.root.join("tmp/generators")
10 | setup :prepare_destination
11 |
12 | # test "generator runs without errors" do
13 | # assert_nothing_raised do
14 | # run_generator ["arguments"]
15 | # end
16 | # end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Configure Rails Environment
4 | ENV["RAILS_ENV"] = "test"
5 |
6 | require File.expand_path("../../test/dummy/config/environment.rb", __FILE__)
7 | ActiveRecord::Migrator.migrations_paths =
8 | [File.expand_path("../../test/dummy/db/migrate", __FILE__)]
9 | ActiveRecord::Migrator.migrations_paths <<
10 | File.expand_path("../../db/migrate", __FILE__)
11 | require "rails/test_help"
12 |
13 | # Filter out Minitest backtrace while allowing backtrace from other libraries
14 | # to be shown.
15 | Minitest.backtrace_filter = Minitest::BacktraceFilter.new
16 |
17 | # Load fixtures from the engine
18 | if ActiveSupport::TestCase.respond_to?(:fixture_path=)
19 | ActiveSupport::TestCase.fixture_path =
20 | File.expand_path("../fixtures", __FILE__)
21 | ActionDispatch::IntegrationTest.fixture_path =
22 | ActiveSupport::TestCase.fixture_path
23 | ActiveSupport::TestCase.file_fixture_path =
24 | ActiveSupport::TestCase.fixture_path + "/files"
25 | ActiveSupport::TestCase.fixtures :all
26 | end
27 |
--------------------------------------------------------------------------------