├── .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='{alt}':'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 | --------------------------------------------------------------------------------