├── VERSION
├── spec
├── dummy
│ ├── log
│ │ └── .keep
│ ├── app
│ │ ├── mailers
│ │ │ └── .keep
│ │ ├── models
│ │ │ ├── .keep
│ │ │ ├── concerns
│ │ │ │ └── .keep
│ │ │ ├── post.rb
│ │ │ └── dummy_object.rb
│ │ ├── assets
│ │ │ ├── images
│ │ │ │ └── .keep
│ │ │ ├── javascripts
│ │ │ │ └── application.js
│ │ │ └── stylesheets
│ │ │ │ └── application.css
│ │ ├── controllers
│ │ │ ├── concerns
│ │ │ │ └── .keep
│ │ │ ├── site_controller.rb
│ │ │ └── application_controller.rb
│ │ ├── views
│ │ │ ├── notifly
│ │ │ │ └── templates
│ │ │ │ │ ├── mails
│ │ │ │ │ ├── _hello.html.erb
│ │ │ │ │ └── _destroy_smart.html.erb
│ │ │ │ │ └── notifications
│ │ │ │ │ ├── _foo.html
│ │ │ │ │ ├── _hello.html
│ │ │ │ │ ├── _save.html
│ │ │ │ │ ├── _create.html
│ │ │ │ │ ├── _destroy.html
│ │ │ │ │ ├── _publish.html
│ │ │ │ │ ├── _update.html
│ │ │ │ │ ├── _be_smart.html
│ │ │ │ │ ├── _change_title.html
│ │ │ │ │ ├── _destroy_dummy.html
│ │ │ │ │ └── _destroy_smart.html
│ │ │ ├── site
│ │ │ │ └── index.html.erb
│ │ │ └── layouts
│ │ │ │ └── application.html.erb
│ │ └── helpers
│ │ │ └── application_helper.rb
│ ├── lib
│ │ └── assets
│ │ │ └── .keep
│ ├── public
│ │ ├── favicon.ico
│ │ ├── 500.html
│ │ ├── 422.html
│ │ └── 404.html
│ ├── config
│ │ ├── initializers
│ │ │ ├── notifly.rb
│ │ │ ├── cookies_serializer.rb
│ │ │ ├── session_store.rb
│ │ │ ├── mime_types.rb
│ │ │ ├── filter_parameter_logging.rb
│ │ │ ├── assets.rb
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── wrap_parameters.rb
│ │ │ ├── inflections.rb
│ │ │ └── websocket_rails.rb
│ │ ├── database.travis.yml
│ │ ├── locales
│ │ │ ├── notifly.en.yml
│ │ │ └── en.yml
│ │ ├── environment.rb
│ │ ├── routes.rb
│ │ ├── boot.rb
│ │ ├── database.exemple.yml
│ │ ├── events.rb
│ │ ├── secrets.yml
│ │ ├── application.rb
│ │ └── environments
│ │ │ ├── development.rb
│ │ │ ├── test.rb
│ │ │ └── production.rb
│ ├── bin
│ │ ├── rake
│ │ ├── bundle
│ │ └── rails
│ ├── config.ru
│ ├── db
│ │ ├── migrate
│ │ │ ├── 20141104121318_add_dummy_to_post.rb
│ │ │ ├── 20141125105226_add_email_to_dummy_object.rb
│ │ │ ├── 20141031165049_create_dummy_objects.rb
│ │ │ └── 20141103191353_create_posts.rb
│ │ ├── seeds.rb
│ │ └── schema.rb
│ ├── Rakefile
│ ├── README.rdoc
│ └── spec
│ │ ├── features
│ │ └── notifly
│ │ │ ├── counter_spec.rb
│ │ │ ├── loading_notifications_spec.rb
│ │ │ └── read_notifications_spec.rb
│ │ └── models
│ │ ├── post_spec.rb
│ │ └── dummy_object_spec.rb
├── support
│ ├── email_helpers.rb
│ ├── suppress_log.rb
│ ├── shared_connection.rb
│ ├── websocket_setup.rb
│ └── wait_ajax.rb
├── rails_helper.rb
├── lib
│ └── notifly
│ │ └── models
│ │ └── flyable_spec.rb
├── mailers
│ └── notifly
│ │ ├── async_spec.rb
│ │ └── notification_mailer_spec.rb
├── spec_helper.rb
└── models
│ └── notifly
│ └── notification_spec.rb
├── app
├── assets
│ ├── images
│ │ └── notifly
│ │ │ └── .keep
│ ├── javascripts
│ │ ├── notifly_dropdown.js
│ │ ├── notifly.js.erb
│ │ └── notifly
│ │ │ ├── seen_notifications.js.erb
│ │ │ ├── more_notifications.js.erb
│ │ │ ├── real_time.js.erb
│ │ │ ├── get_notifications.js.erb
│ │ │ └── read_notifications.js.erb
│ └── stylesheets
│ │ ├── notifly.css
│ │ └── notifly
│ │ └── layout.css
├── views
│ └── notifly
│ │ ├── templates
│ │ ├── mails
│ │ │ └── _default.html.erb
│ │ └── notifications
│ │ │ └── _default.html.erb
│ │ ├── layouts
│ │ ├── _empty.html.erb
│ │ ├── _counter.html.erb
│ │ ├── _index.html.erb
│ │ ├── _actions.html.erb
│ │ ├── _notification.html.erb
│ │ └── _notifly.html.erb
│ │ ├── notification_mailer
│ │ └── notifly.html.erb
│ │ └── notifications
│ │ ├── read.js.erb
│ │ ├── seen.js.erb
│ │ ├── toggle_read.js.erb
│ │ └── index.js.erb
├── controllers
│ └── notifly
│ │ ├── application_controller.rb
│ │ └── notifications_controller.rb
├── helpers
│ └── notifly
│ │ └── view_helper.rb
├── mailers
│ └── notifly
│ │ └── notification_mailer.rb
└── models
│ └── notifly
│ └── notification.rb
├── .rspec
├── lib
├── tasks
│ └── notifly_tasks.rake
├── notifly
│ ├── engine.rb
│ ├── models
│ │ ├── notifiable.rb
│ │ ├── base.rb
│ │ ├── options
│ │ │ └── fly.rb
│ │ └── flyable.rb
│ └── railtie.rb
├── services
│ ├── notification_channel.rb
│ └── action_view_helper.rb
├── generators
│ └── notifly
│ │ ├── install
│ │ ├── templates
│ │ │ └── config
│ │ │ │ └── initializers
│ │ │ │ └── notifly.rb
│ │ ├── utils.rb
│ │ └── install_generator.rb
│ │ └── views
│ │ └── views_generator.rb
└── notifly.rb
├── db
└── migrate
│ ├── 20141104150224_add_data_to_notification.rb
│ ├── 20141125165636_add_mail_to_notifly_notifications.rb
│ ├── 20141117193436_add_seen_to_notifly_notification.rb
│ ├── 20141212122918_add_kind_to_notifly_notification.rb
│ └── 20141103170528_create_notifly_notifications.rb
├── .gitignore
├── config
├── routes.rb
└── locales
│ └── en.yml
├── .travis.yml
├── .editorconfig
├── bin
└── rails
├── Gemfile
├── Rakefile
├── MIT-LICENSE
├── vendor
└── assets
│ ├── javascripts
│ ├── twitter
│ │ └── bootstrap
│ │ │ └── dropdown.js
│ └── tinycon.js
│ └── stylesheets
│ └── twitter
│ └── bootstrap.css
├── notifly.gemspec
├── Gemfile.lock
└── README.md
/VERSION:
--------------------------------------------------------------------------------
1 | 0.3.6
--------------------------------------------------------------------------------
/spec/dummy/log/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/mailers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/lib/assets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/images/notifly/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/images/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifly/templates/mails/_hello.html.erb:
--------------------------------------------------------------------------------
1 | Hello mail
--------------------------------------------------------------------------------
/app/views/notifly/templates/mails/_default.html.erb:
--------------------------------------------------------------------------------
1 | Default notification mail
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifly/templates/mails/_destroy_smart.html.erb:
--------------------------------------------------------------------------------
1 | Destroy!
--------------------------------------------------------------------------------
/app/assets/javascripts/notifly_dropdown.js:
--------------------------------------------------------------------------------
1 | //= require 'twitter/bootstrap/dropdown'
--------------------------------------------------------------------------------
/spec/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifly/templates/notifications/_foo.html:
--------------------------------------------------------------------------------
1 | _foo notification
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifly/templates/notifications/_hello.html:
--------------------------------------------------------------------------------
1 | _hello notification
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifly/templates/notifications/_save.html:
--------------------------------------------------------------------------------
1 | save notification
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifly/templates/notifications/_create.html:
--------------------------------------------------------------------------------
1 | create notification
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifly/templates/notifications/_destroy.html:
--------------------------------------------------------------------------------
1 | destroy notification
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifly/templates/notifications/_publish.html:
--------------------------------------------------------------------------------
1 | publish notification
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifly/templates/notifications/_update.html:
--------------------------------------------------------------------------------
1 | _update notification
--------------------------------------------------------------------------------
/spec/support/email_helpers.rb:
--------------------------------------------------------------------------------
1 | def emails_sent
2 | ActionMailer::Base.deliveries
3 | end
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifly/templates/notifications/_be_smart.html:
--------------------------------------------------------------------------------
1 | _be_smart notification
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifly/templates/notifications/_change_title.html:
--------------------------------------------------------------------------------
1 | change title notification
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifly/templates/notifications/_destroy_dummy.html:
--------------------------------------------------------------------------------
1 | _destroy_dummy notification
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifly/templates/notifications/_destroy_smart.html:
--------------------------------------------------------------------------------
1 | _destroy_smart notification
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/notifly.rb:
--------------------------------------------------------------------------------
1 | Notifly.setup do |config|
2 | config.websocket = true
3 | end
--------------------------------------------------------------------------------
/spec/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/database.travis.yml:
--------------------------------------------------------------------------------
1 | test:
2 | adapter: postgresql
3 | database: travis_ci_test
4 | username: postgres
--------------------------------------------------------------------------------
/app/views/notifly/layouts/_empty.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= t('notifly.view.no_notifications') %>
3 |
4 |
--------------------------------------------------------------------------------
/lib/tasks/notifly_tasks.rake:
--------------------------------------------------------------------------------
1 | # desc "Explaining what the task does"
2 | # task :notifly do
3 | # # Task goes here
4 | # end
5 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/notifly.css:
--------------------------------------------------------------------------------
1 | /*
2 | *= require notifly/layout
3 | *= require twitter/bootstrap
4 | *= require font-awesome
5 | */
--------------------------------------------------------------------------------
/spec/support/suppress_log.rb:
--------------------------------------------------------------------------------
1 | # http://helabs.com.br/blog/2013/02/06/testes-mais-rapidos-no-rspec
2 |
3 | Rails.logger.level = 4 unless ENV['WITH_LOG']
--------------------------------------------------------------------------------
/spec/dummy/config/locales/notifly.en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | notifly:
3 | mail_subject:
4 | default: 'You have a new notification'
5 | hello: 'Hello!'
--------------------------------------------------------------------------------
/app/views/notifly/notification_mailer/notifly.html.erb:
--------------------------------------------------------------------------------
1 | <%= render partial: "notifly/templates/mails/#{@template}",
2 | locals: { notification: @notification } %>
--------------------------------------------------------------------------------
/spec/dummy/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/app/controllers/notifly/application_controller.rb:
--------------------------------------------------------------------------------
1 | module Notifly
2 | class ApplicationController < ::ApplicationController
3 | layout false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../../config/application', __FILE__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.action_dispatch.cookies_serializer = :json
--------------------------------------------------------------------------------
/spec/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/db/migrate/20141104150224_add_data_to_notification.rb:
--------------------------------------------------------------------------------
1 | class AddDataToNotification < ActiveRecord::Migration
2 | def change
3 | add_column :notifly_notifications, :data, :text
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.session_store :cookie_store, key: '_test_app_session'
4 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20141104121318_add_dummy_to_post.rb:
--------------------------------------------------------------------------------
1 | class AddDummyToPost < ActiveRecord::Migration
2 | def change
3 | add_reference :posts, :dummy_object, index: true
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20141125105226_add_email_to_dummy_object.rb:
--------------------------------------------------------------------------------
1 | class AddEmailToDummyObject < ActiveRecord::Migration
2 | def change
3 | add_column :dummy_objects, :email, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/app/views/notifly/layouts/_counter.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%= counter %>
4 |
--------------------------------------------------------------------------------
/db/migrate/20141125165636_add_mail_to_notifly_notifications.rb:
--------------------------------------------------------------------------------
1 | class AddMailToNotiflyNotifications < ActiveRecord::Migration
2 | def change
3 | add_column :notifly_notifications, :mail, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle/
2 | log/*.log
3 | pkg/
4 | spec/dummy/db/*.sqlite3
5 | spec/dummy/db/*.sqlite3-journal
6 | spec/dummy/log/*.log
7 | spec/dummy/tmp/
8 | spec/dummy/.sass-cache
9 | spec/dummy/config/database.yml
10 |
--------------------------------------------------------------------------------
/app/views/notifly/notifications/read.js.erb:
--------------------------------------------------------------------------------
1 | <% rendered_index = render partial: 'notifly/layouts/index',
2 | locals: { notifications: @notifications } %>
3 |
4 | $('#notifly-notifications-content').html("<%= j rendered_index %>");
5 |
--------------------------------------------------------------------------------
/app/views/notifly/templates/notifications/_default.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%= notification.receiver.try(:name) %>, you receive a new notification at <%= notification.created_at %>
4 |
--------------------------------------------------------------------------------
/db/migrate/20141117193436_add_seen_to_notifly_notification.rb:
--------------------------------------------------------------------------------
1 | class AddSeenToNotiflyNotification < ActiveRecord::Migration
2 | def change
3 | add_column :notifly_notifications, :seen, :boolean, default: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20141212122918_add_kind_to_notifly_notification.rb:
--------------------------------------------------------------------------------
1 | class AddKindToNotiflyNotification < ActiveRecord::Migration
2 | def change
3 | add_column :notifly_notifications, :kind, :string, default: :notification
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | mount Notifly::Engine => '/notifly', as: 'notifly'
3 |
4 | post 'create', to: 'site#create_notification', as: :create_notification
5 | root to: 'site#index'
6 | end
7 |
--------------------------------------------------------------------------------
/lib/notifly/engine.rb:
--------------------------------------------------------------------------------
1 | require 'jquery-rails'
2 |
3 | module Notifly
4 | class Engine < ::Rails::Engine
5 | isolate_namespace Notifly
6 |
7 | config.generators do |g|
8 | g.test_framework :rspec
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/site/index.html.erb:
--------------------------------------------------------------------------------
1 | Site#index
2 | <% if current_user %>
3 | User logged in: <%= current_user.name %>
4 | <% end %>
5 | <%= link_to 'create notification', create_notification_path, remote: true, method: :post %>
--------------------------------------------------------------------------------
/app/views/notifly/notifications/seen.js.erb:
--------------------------------------------------------------------------------
1 | $('#notifly-counter').replaceWith("<%= j(render 'notifly/layouts/counter', counter: @counter)%>");
2 | $('#notifly-icon').replaceWith("<%= j notifly_icon(@counter > 0) %>");
3 | Tinycon.setBubble(<%= @counter %>);
4 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20141031165049_create_dummy_objects.rb:
--------------------------------------------------------------------------------
1 | class CreateDummyObjects < ActiveRecord::Migration
2 | def change
3 | create_table :dummy_objects do |t|
4 | t.string :name
5 |
6 | t.timestamps
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/app/views/notifly/layouts/_index.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <% notifications.each do |notification| %>
4 | <%= render partial: 'notifly/layouts/notification',
5 | locals: { notification: notification } %>
6 | <% end %>
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/site_controller.rb:
--------------------------------------------------------------------------------
1 | class SiteController < ApplicationController
2 | def index
3 | end
4 |
5 | def create_notification
6 | Notifly::Notification.create! receiver: current_user
7 | render nothing: true
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/spec/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # Set up gems listed in the Gemfile.
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
3 |
4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/database.exemple.yml:
--------------------------------------------------------------------------------
1 | default: &default
2 | adapter: postgresql
3 | encoding: unicode
4 | user: postgres
5 | pool: 5
6 |
7 | development:
8 | <<: *default
9 | database: notifly_development
10 | test:
11 | <<: *default
12 | database: notifly_test
--------------------------------------------------------------------------------
/app/views/notifly/notifications/toggle_read.js.erb:
--------------------------------------------------------------------------------
1 | <% rendered_notification = render partial: 'notifly/layouts/notification',
2 | locals: { notification: @notification } %>
3 |
4 | $('<%= "#notifly-notification-#{@notification.id}" %>')
5 | .replaceWith('<%= j rendered_notification %>')
--------------------------------------------------------------------------------
/lib/notifly/models/notifiable.rb:
--------------------------------------------------------------------------------
1 | module Notifly
2 | module Models
3 | module Notifiable
4 | extend ActiveSupport::Concern
5 |
6 | def notifly!(args={})
7 | Notifly::Notification.create! args.merge(receiver: self)
8 | end
9 | end
10 | end
11 | end
--------------------------------------------------------------------------------
/spec/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require File.expand_path('../config/application', __FILE__)
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/app/views/notifly/layouts/_actions.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <% link_text = notification.read ? t('notifly.view.unread') : t('notifly.view.read') %>
4 | <%= link_to link_text, notification_toggle_read_path(notification), method: :put, remote: true %>
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20141103191353_create_posts.rb:
--------------------------------------------------------------------------------
1 | class CreatePosts < ActiveRecord::Migration
2 | def change
3 | create_table :posts do |t|
4 | t.string :author
5 | t.string :title
6 | t.text :content
7 | t.boolean :published, default: false
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/notifly/models/base.rb:
--------------------------------------------------------------------------------
1 | require_relative 'notifiable'
2 | require_relative 'flyable'
3 |
4 | module Notifly
5 | module Models
6 | module Base
7 | extend ActiveSupport::Concern
8 |
9 | include Notifly::Models::Notifiable
10 | include Notifly::Models::Flyable
11 | end
12 | end
13 | end
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Notifly::Engine.routes.draw do
2 | put '/notifications/(:notification_id)/toggle_read', to: 'notifications#toggle_read', as: :notification_toggle_read
3 |
4 | resources :notifications, only: [:index] do
5 | collection do
6 | put :read
7 | put :seen
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | notifly:
3 | mail_subject:
4 | default: 'You have a new notification'
5 | view:
6 | header: 'Notifications'
7 | mark_as_read: 'Mark as read'
8 | loading: 'Loading notifications...'
9 | more: 'More'
10 | read: 'read'
11 | unread: 'unread'
12 | no_notifications: 'You have no notifications'
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 |
3 | before_script:
4 | - cp spec/dummy/config/database.travis.yml spec/dummy/config/database.yml
5 | - psql -c 'create database travis_ci_test;' -U postgres
6 | - bundle install
7 | - bundle exec rake db:migrate
8 |
9 | rvm:
10 | - 2.1
11 | env:
12 | - WEBSOCKET=true
13 | - WEBSOCKET=false
14 |
15 | script: bundle exec rspec spec
16 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | TestApp
5 | <%= stylesheet_link_tag 'application', media: 'all' %>
6 | <%= javascript_include_tag 'application' %>
7 | <%= csrf_meta_tags %>
8 |
9 |
10 |
11 | <%= yield %>
12 | <%= notiflies if DummyObject.first %>
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | indent_style = space
3 | indent_size = 2
4 | charset = utf-8
5 | trim_trailing_whitespace = true
6 | insert_final_newline = true
7 |
8 | [*.rb]
9 | indent_style = space
10 | indent_size = 2
11 |
12 | [Rakefile]
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [Gemfile*]
17 | indent_style = space
18 | indent_size = 2
19 |
20 | [config.ru]
21 | indent_style = space
22 | indent_size = 2
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Precompile additional assets.
7 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
8 | # Rails.application.config.assets.precompile += %w( search.js )
9 |
--------------------------------------------------------------------------------
/lib/notifly/railtie.rb:
--------------------------------------------------------------------------------
1 | require_relative '../../app/helpers/notifly/view_helper'
2 | require_relative 'models/base'
3 |
4 | module Notifly
5 | class Railtie < Rails::Railtie
6 | initializer 'Notifly.view_helpers' do
7 | ActionView::Base.send :include, ViewHelper
8 | end
9 |
10 | initializer 'Notifly.active_model_helpers' do
11 | ActiveRecord::Base.send :include, Notifly::Models::Base
12 | end
13 | end
14 | end
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | # Prevent CSRF attacks by raising an exception.
3 | # For APIs, you may want to use :null_session instead.
4 | protect_from_forgery with: :exception
5 |
6 | def current_user
7 | DummyObject.first
8 | end
9 |
10 | ActiveSupport.on_load(:action_controller) do
11 | helper_method :current_user
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/db/migrate/20141103170528_create_notifly_notifications.rb:
--------------------------------------------------------------------------------
1 | class CreateNotiflyNotifications < ActiveRecord::Migration
2 | def change
3 | create_table :notifly_notifications do |t|
4 | t.string :template
5 | t.boolean :read, default: false
6 | t.references :target, index: true, polymorphic: true
7 | t.references :sender, index: true, polymorphic: true
8 | t.references :receiver, index: true, polymorphic: true
9 |
10 | t.timestamps
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/support/shared_connection.rb:
--------------------------------------------------------------------------------
1 | # http://helabs.com.br/blog/2013/02/06/testes-mais-rapidos-no-rspec
2 |
3 | class ActiveRecord::Base
4 | mattr_accessor :shared_connection
5 | @@shared_connection = nil
6 |
7 | def self.connection
8 | @@shared_connection || retrieve_connection
9 | end
10 | end
11 |
12 | # Forces all threads to share the same connection. This works on
13 | # Capybara because it starts the web server in a thread.
14 | ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection
--------------------------------------------------------------------------------
/app/helpers/notifly/view_helper.rb:
--------------------------------------------------------------------------------
1 | module Notifly
2 | module ViewHelper
3 | def notiflies
4 | notiflies_for current_user
5 | end
6 |
7 | def notiflies_for(receiver)
8 | render partial: 'notifly/layouts/notifly', locals: { receiver: receiver }
9 | end
10 |
11 | def notifly_icon(have_notifications=false)
12 | icon = have_notifications ? Notifly.icon : Notifly.icon_empty
13 | size = Notifly.icon_size
14 | fa_icon "#{icon} #{size}", id: 'notifly-icon'
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
3 |
4 | ENGINE_ROOT = File.expand_path('../..', __FILE__)
5 | ENGINE_PATH = File.expand_path('../../lib/notifly/engine', __FILE__)
6 |
7 | # Set up gems listed in the Gemfile.
8 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
9 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
10 |
11 | require 'rails/all'
12 | require 'rails/engine/commands'
13 |
--------------------------------------------------------------------------------
/spec/dummy/db/seeds.rb:
--------------------------------------------------------------------------------
1 | puts 'Seeding...'
2 | DummyObject.delete_all
3 | Post.delete_all
4 | Notifly::Notification.delete_all
5 |
6 | dummy = DummyObject.create! name: 'dummy', email: 'dummy@exemple.com'
7 | smart = DummyObject.create! name: 'smart', email: 'smart@exemple.com'
8 |
9 | 12.times do |n|
10 | post = Post.create! author: "author #{n}", published: false, dummy_object: dummy
11 |
12 | Notifly::Notification.create! template: :default, read: false, seen: false, target: post,
13 | sender: smart, receiver: dummy, mail: :never
14 | sleep(0.5)
15 | end
--------------------------------------------------------------------------------
/app/assets/javascripts/notifly.js.erb:
--------------------------------------------------------------------------------
1 | //= require 'jquery'
2 | //= require 'jquery_ujs'
3 | //= require 'tinycon'
4 | //= require 'notifly/get_notifications'
5 | //= require 'notifly/seen_notifications'
6 | //= require 'notifly/read_notifications'
7 | //= require 'notifly/more_notifications'
8 | <% require_asset 'notifly/real_time' if Notifly.websocket %>
9 |
10 | $(document).ready(function() {
11 | $(document).on('click', '#notifly-notifications-panel.dropdown-menu', function (e) {
12 | $('#notifly').hasClass('keep_open') && e.stopPropagation();
13 | });
14 |
15 | Tinycon.setBubble(0);
16 | });
--------------------------------------------------------------------------------
/spec/support/websocket_setup.rb:
--------------------------------------------------------------------------------
1 | Capybara.javascript_driver = :poltergeist
2 |
3 | Capybara.register_driver :chrome do |app|
4 | Capybara::Selenium::Driver.new(app, :browser => :chrome)
5 | end
6 |
7 | Capybara.run_server = true
8 | Capybara.server do |app, port|
9 | require 'rack/handler/thin'
10 | Rack::Handler::Thin.run(app, :Port => port)
11 | end
12 |
13 | RSpec.configure do |c|
14 | c.before(:each) do |example|
15 | Capybara.current_driver = example.metadata[:driver] || :poltergeist
16 | end
17 |
18 | c.before(:suite) { Notifly.websocket = true if ENV['WEBSOCKET'] == 'true' }
19 | end
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/spec/dummy/README.rdoc:
--------------------------------------------------------------------------------
1 | == README
2 |
3 | This README would normally document whatever steps are necessary to get the
4 | application up and running.
5 |
6 | Things you may want to cover:
7 |
8 | * Ruby version
9 |
10 | * System dependencies
11 |
12 | * Configuration
13 |
14 | * Database creation
15 |
16 | * Database initialization
17 |
18 | * How to run the test suite
19 |
20 | * Services (job queues, cache servers, search engines, etc.)
21 |
22 | * Deployment instructions
23 |
24 | * ...
25 |
26 |
27 | Please feel free to use a different markup language if you do not plan to run
28 | rake doc:app.
29 |
--------------------------------------------------------------------------------
/lib/services/notification_channel.rb:
--------------------------------------------------------------------------------
1 | module Notifly
2 | class NotificationChannel
3 | def initialize(user_id)
4 | @user_id = user_id
5 | @channel = WebsocketRails.users[@user_id.to_s]
6 | @action_view = Notifly::ActionViewHelper.new
7 | end
8 |
9 | def trigger(notification)
10 | @channel.send_message 'notifly.notifications.new',
11 | { message: render(notification), id: notification.id }
12 | end
13 |
14 | def render(notification)
15 | @action_view.render partial: 'layouts/notification',
16 | locals: { notification: notification }
17 | end
18 | end
19 | end
--------------------------------------------------------------------------------
/lib/generators/notifly/install/templates/config/initializers/notifly.rb:
--------------------------------------------------------------------------------
1 | Notifly.setup do |config|
2 | # Define how many notifications per page.
3 | config.per_page = 10
4 |
5 | # Define the time interval in miliseconfs between requests to check notifications.
6 | config.timeout = 10000
7 |
8 | # Define the notifly icon from font-awesome-rails
9 | config.icon = 'bell'
10 |
11 | # Define the notifly icon size
12 | config.icon_size = '2x'
13 |
14 | # Define your mailer sender
15 | config.mailer_sender = 'change-me-at-config-initializers-notifly@exemple.com'
16 |
17 | # Active websocket
18 | config.websocket = false
19 | end
--------------------------------------------------------------------------------
/app/assets/javascripts/notifly/seen_notifications.js.erb:
--------------------------------------------------------------------------------
1 | <% notifly = Notifly::Engine.routes.url_helpers %>
2 |
3 | var _notiflySeenNotifications = function () {
4 | var $trigger = $('#notifly-trigger');
5 |
6 | if($trigger.length !== 0) {
7 | $trigger.click(function () {
8 | $.ajax({
9 | url: '<%= notifly.seen_notifications_path %>',
10 | data: {
11 | first_notification_id: notiflyFirstNotification,
12 | last_notification_id: notiflyLastNotification
13 | },
14 | type: 'PUT'
15 | });
16 | });
17 | }
18 | };
19 |
20 | $(document).ready(function() {
21 | _notiflySeenNotifications();
22 | });
23 |
--------------------------------------------------------------------------------
/app/views/notifly/layouts/_notification.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <% read = notification.read ? '' : 'notifly-notification-not-read' %>
4 |
5 |
6 |
7 | <%= render partial: "notifly/templates/notifications/#{notification.template}",
8 | locals: { notification: notification } %>
9 |
10 |
11 |
12 | <%= render partial: 'notifly/layouts/actions', locals: { notification: notification } %>
13 |
14 |
--------------------------------------------------------------------------------
/app/assets/javascripts/notifly/more_notifications.js.erb:
--------------------------------------------------------------------------------
1 | <% notifly = Notifly::Engine.routes.url_helpers %>
2 |
3 | var _notiflyMoreNotifications = function () {
4 | var $notifly = $('#notifly-notifications-content');
5 |
6 | if($notifly.length !== 0) {
7 | $('#notifly-more-notifications-link').click(function () {
8 | $.ajax({
9 | url: '<%= notifly.notifications_path %>',
10 | data: {
11 | scope: 'older',
12 | reference_notification_id: notiflyLastNotification,
13 | mark_as_seen: true
14 | },
15 | type: 'GET'
16 | });
17 | });
18 | }
19 | };
20 |
21 | $(document).ready(function() {
22 | _notiflyMoreNotifications();
23 | });
24 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/post.rb:
--------------------------------------------------------------------------------
1 | class Post < ActiveRecord::Base
2 | belongs_to :dummy_object
3 |
4 | notifly default_values: { receiver: :dummy_object, target: :self }
5 |
6 | notifly before: :destroy, template: :destroy, data: :attributes
7 |
8 | notifly after: :publish!, template: :publish
9 | notifly before: :change_title, data: -> { { title_before_create: self.title } },
10 | unless: -> { self.title == 'TitleFoo' }
11 | notifly after: :change_title, data: :attributes, template: :change_title,
12 | if: -> { self.title == 'NewTitle' }
13 |
14 | def publish!
15 | update(published: true)
16 | end
17 |
18 | def change_title(title='NewTitle')
19 | self.title = title
20 | self.save
21 | end
22 | end
--------------------------------------------------------------------------------
/spec/dummy/config/events.rb:
--------------------------------------------------------------------------------
1 | WebsocketRails::EventMap.describe do
2 | # You can use this file to map incoming events to controller actions.
3 | # One event can be mapped to any number of controller actions. The
4 | # actions will be executed in the order they were subscribed.
5 | #
6 | # Uncomment and edit the next line to handle the client connected event:
7 | # subscribe :client_connected, :to => Controller, :with_method => :method_name
8 | #
9 | # Here is an example of mapping namespaced events:
10 | # namespace :product do
11 | # subscribe :new, :to => ProductController, :with_method => :new_product
12 | # end
13 | # The above will handle an event triggered on the client like `product.new`.
14 | end
15 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/spec/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 |
--------------------------------------------------------------------------------
/spec/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 vendor/assets/javascripts of plugins, if any, 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.
9 | //
10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require websocket_rails/main
14 | //= require notifly
15 | //= require notifly_dropdown
16 |
--------------------------------------------------------------------------------
/app/mailers/notifly/notification_mailer.rb:
--------------------------------------------------------------------------------
1 | module Notifly
2 | class NotificationMailer < ActionMailer::Base
3 | default from: Notifly.mailer_sender
4 |
5 | def notifly(to: nil, notification_id: nil, template: nil)
6 | if defined? Delayed::Job or defined? Sidekiq::Worker
7 | delay.send_notification(to, notification_id, template)
8 | else
9 | send_notification(to, notification_id, template).deliver
10 | end
11 | end
12 |
13 | private
14 | def send_notification(to, notification_id, template)
15 | @notification = Notifly::Notification.find(notification_id)
16 | @template = template
17 |
18 | mail to: to, subject: t("notifly.mail_subject.#{@template}")
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/spec/rails_helper.rb:
--------------------------------------------------------------------------------
1 | # This file is copied to spec/ when you run 'rails generate rspec:install'
2 | ENV["RAILS_ENV"] ||= 'test'
3 | require 'spec_helper'
4 | require File.expand_path("../dummy/config/environment", __FILE__)
5 | require 'rspec/rails'
6 | require 'shoulda/matchers'
7 | require 'capybara/rails'
8 | require 'capybara/rspec'
9 | Capybara.javascript_driver = :poltergeist
10 |
11 | Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each { |f| require f }
12 |
13 | # Checks for pending migrations before tests are run.
14 | # ActiveRecord::Migration.maintain_test_schema!
15 |
16 | RSpec.configure do |config|
17 | config.fixture_path = "#{::Rails.root}/spec/fixtures"
18 | config.use_transactional_fixtures = true
19 | config.infer_spec_type_from_file_location!
20 | end
21 |
--------------------------------------------------------------------------------
/lib/generators/notifly/install/utils.rb:
--------------------------------------------------------------------------------
1 | # github.com/sferik/rails_admin/blob/master/lib/generators/rails_admin/utils.rb
2 |
3 | module Notifly
4 | module Generators
5 | module Utils
6 | module InstanceMethods
7 | def display(output, color = :green)
8 | say(" - #{output}", color)
9 | end
10 |
11 | def ask_for(wording, default_value = nil, override_if_present_value = nil)
12 | if override_if_present_value.present?
13 | display("Using [#{override_if_present_value}] for question '#{wording}'") && override_if_present_value
14 | else
15 | ask(" ? #{wording} Press for [#{default_value}] >", :yellow).presence || default_value
16 | end
17 | end
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/spec/lib/notifly/models/flyable_spec.rb:
--------------------------------------------------------------------------------
1 | require "rails_helper"
2 |
3 | module Notifly
4 | RSpec.describe Notifly::Models::Flyable do
5 | let!(:dummy) { DummyObject.create name: 'Dummy', email: 'dummy@test.com' }
6 |
7 | before(:each) do
8 | allow(Notifly::Notification).to receive(:create).
9 | and_raise(ActiveRecord::RecordInvalid, Notifly::Notification.new)
10 | expect(Rails.logger).to receive(:error)
11 | end
12 |
13 | after(:each) { Rails.env = 'test' }
14 |
15 | it 'should not raise an error if something wrong happened in production' do
16 | Rails.env = 'production'
17 |
18 | expect { dummy.be_smart }.to_not raise_error
19 | end
20 |
21 | it 'should raise an error if something wrong happened' do
22 | expect { dummy.be_smart }.to raise_error
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/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 vendor/assets/stylesheets of plugins, if any, 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 styles
10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11 | * file per style scope.
12 | *
13 | *= require notifly
14 | *= require_tree .
15 | *= require_self
16 | */
17 |
18 | body {
19 | background: #f5f5f5;
20 | }
--------------------------------------------------------------------------------
/app/assets/javascripts/notifly/real_time.js.erb:
--------------------------------------------------------------------------------
1 | window.dispatcher = new WebSocketRails(location.host + '/websocket');
2 | var notiflyFirstNotification;
3 |
4 | $(function () {
5 | var $notifly = $('#notifly');
6 |
7 | if ($notifly !== undefined) {
8 | dispatcher.bind('notifly.notifications.new', _injectNotificationFrom);
9 | }
10 | });
11 |
12 | var _injectNotificationFrom = function (data) {
13 | if (data.message) {
14 | $('#notifly-notifications-content .notifly-empty').remove();
15 | notiflyFirstNotification = data.id;
16 |
17 | if (!$('#notifly').hasClass('open')) {
18 | $counter = $('#notifly-counter');
19 | $counter.html(eval($counter.html()) + 1);
20 | $counter.removeClass('hide');
21 | }
22 |
23 | return $('#notifly').find('#notifly-notifications-content')
24 | .prepend(data.message)
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/app/assets/javascripts/notifly/get_notifications.js.erb:
--------------------------------------------------------------------------------
1 | <% notifly = Notifly::Engine.routes.url_helpers %>
2 | var notiflyLastNotification, notiflyFirstNotification;
3 |
4 | $(document).ready(function() {
5 | _notiflyGetNotifications();
6 | });
7 |
8 | var _notiflyGetNotifications = function () {
9 | var $notifly = $('#notifly');
10 |
11 | if($notifly.length !== 0) {
12 | $.ajax({
13 | url: '<%= notifly.notifications_path %>',
14 | data: {
15 | scope: 'newer',
16 | reference_notification_id: notiflyFirstNotification || null,
17 | mark_as_seen: $notifly.hasClass('open')
18 | },
19 | type: 'GET',
20 | complete: _notiflyRepeatRequest
21 | });
22 | }
23 | };
24 |
25 | var _notiflyRepeatRequest = function () {
26 | <% if not Notifly.websocket %>
27 | setTimeout(_notiflyGetNotifications, <%= Notifly.timeout %>);
28 | <% end %>
29 | };
30 |
--------------------------------------------------------------------------------
/spec/mailers/notifly/async_spec.rb:
--------------------------------------------------------------------------------
1 | require "rails_helper"
2 |
3 | module Notifly
4 | RSpec.describe 'Async Mail' do
5 | let(:dummy) { DummyObject.create name: 'Dummy', email: 'dummy@test.com' }
6 |
7 | it 'should not call #delay with app does not use delayed_job or sidekiq' do
8 | expect_any_instance_of(NotificationMailer).not_to receive(:delay)
9 | dummy.be_smart
10 | end
11 |
12 | it 'should call #delay with app use delayed_job' do
13 | stub_const 'Delayed::Job', Module.new
14 |
15 | expect_any_instance_of(NotificationMailer).to receive_message_chain('delay.send_notification')
16 | dummy.be_smart
17 | end
18 |
19 | it 'should call #delay with app use delayed_job' do
20 | stub_const 'Sidekiq::Worker', Module.new
21 |
22 | expect_any_instance_of(NotificationMailer).to receive_message_chain('delay.send_notification')
23 | dummy.be_smart
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'rails', '4.1.8'
4 | gem 'jquery-rails', ['>= 3.0', '< 5']
5 | gem 'font-awesome-rails', '4.5.0'
6 | gem 'coffee-rails', '4.1.1'
7 |
8 | group :development, :test do
9 | gem 'pg', '0.18.2'
10 | gem 'pry-rails', '0.3.4'
11 | gem 'pry-rescue', '1.4.2'
12 | gem 'awesome_print', '1.6.1'
13 | gem 'websocket-rails', '0.7.0'
14 | gem 'thin', '1.6.3'
15 | end
16 |
17 | group :test do
18 | gem 'rspec-rails', '3.1.0'
19 | gem 'capybara', '2.4.4'
20 | gem 'poltergeist', '1.5.1'
21 | gem 'phantomjs', '1.9.8.0', require: 'phantomjs/poltergeist'
22 | gem 'shoulda-matchers', '2.7.0'
23 | gem 'launchy', '2.4.3'
24 | end
25 |
26 | group :development do
27 | gem 'better_errors', '2.1.1'
28 | gem 'binding_of_caller', '0.7.2'
29 | gem 'jeweler', '2.0.1', require: false
30 | end
31 |
--------------------------------------------------------------------------------
/app/views/notifly/layouts/_notifly.html.erb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/generators/notifly/install/install_generator.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../utils', __FILE__)
2 |
3 | module Notifly
4 | module Generators
5 | class InstallGenerator < Rails::Generators::Base
6 | include Generators::Utils::InstanceMethods
7 | source_root File.expand_path('../templates', __FILE__)
8 | argument :namespace, type: :string, required: false, desc: 'Notifly url namespace'
9 |
10 | desc 'Notifly installation generator'
11 |
12 | def mount_engine
13 | namespace = ask_for('Where do you want to mount Notifly?', 'notifly', namespace)
14 | route("mount Notifly::Engine => '/#{namespace}', as: 'notifly'")
15 | end
16 |
17 | def copy_config
18 | template 'config/initializers/notifly.rb'
19 | copy_file '../../../../../config/locales/en.yml', 'config/locales/notifly.en.yml'
20 | end
21 |
22 | def remember_to_install_migrations
23 | display 'Remember to install migrations: "rake notifly:install:migrations"', :red
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/notifly.rb:
--------------------------------------------------------------------------------
1 | require 'notifly/engine'
2 | require 'notifly/railtie'
3 | require 'services/notification_channel'
4 | require 'services/action_view_helper'
5 | require 'font-awesome-rails'
6 | require 'websocket-rails'
7 |
8 | module Notifly
9 | # How many notifications per page.
10 | mattr_accessor :per_page
11 | @@per_page = 10
12 |
13 | mattr_accessor :icon_size
14 | @@icon_size = '2x'
15 |
16 | mattr_accessor :icon
17 | @@icon = 'bell'
18 |
19 | mattr_accessor :icon_empty
20 | @@icon_empty = 'bell-o'
21 |
22 | mattr_accessor :mailer_sender
23 | @@mailer_sender = 'change-me-at-config-initializers-notifly@exemple.com'
24 |
25 | # Timeout used when notifly get new notifications per request
26 | mattr_accessor :timeout
27 | @@timeout = 10000
28 |
29 | # Active websocket
30 | mattr_accessor :websocket
31 | @@websocket = false
32 |
33 | # Default way to setup Notifly. Run rails generate notifly:install to create
34 | # a fresh initializer with all configuration values.
35 | def self.setup
36 | yield self
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/spec/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 `rake 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: 9444b7aeea2219e0b4bc563cb1129f0064e1caa65562545d7c851f32135adad914a90d111a5510f29552317c4171fde77e08e61ba4797d19f75693ef76d7f26e
15 |
16 | test:
17 | secret_key_base: 0012b892a47e20248312bce6225eb806629e22739bdea6e0cf91a57656085c05735cd02b4378495df903d6b31a0666f0f90ba5728aa41e8fe3d4b208cb24bb75
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 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require 'rubygems'
4 | require 'bundler'
5 |
6 | begin
7 | Bundler.setup(:default, :development)
8 | rescue Bundler::BundlerError => e
9 | $stderr.puts e.message
10 | $stderr.puts "Run `bundle install` to install missing gems"
11 | exit e.status_code
12 | end
13 |
14 | require 'rake'
15 |
16 | require 'jeweler'
17 | Jeweler::Tasks.new do |gem|
18 | gem.name = 'notifly'
19 | gem.authors = ['Pedro Passalini', 'Rafael Carvalho']
20 | gem.email = ['henrique.passalini@gmail.com', 'rafael@algorich.com.br']
21 | gem.homepage = 'https://github.com/algorich/notifly'
22 | gem.summary = 'A full notification system'
23 | gem.description = 'This project intend to offer a full notification system, back and front-end.'
24 | gem.license = 'MIT'
25 | gem.files = Dir['{app,config,db,lib,vendor}/**/*', 'MIT-LICENSE', 'Rakefile', 'README.md']
26 | end
27 | Jeweler::RubygemsDotOrgTasks.new
28 |
29 | APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
30 | load 'rails/tasks/engine.rake'
31 |
32 | Bundler::GemHelper.install_tasks
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2014 YOURNAME
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 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/notifly/layout.css:
--------------------------------------------------------------------------------
1 | .notifly-counter-label {
2 | padding: 3px 7px 3px 7px;
3 | background: #cc0000;
4 | color: #ffffff;
5 | font-weight: bold;
6 | border-radius: 9px;
7 | -moz-border-radius: 9px;
8 | -webkit-border-radius: 9px;
9 | position: absolute;
10 | margin-top: -8px;
11 | margin-left: 12px;
12 | font-size: 11px;
13 | }
14 |
15 | #notifly-notifications-panel {
16 | padding: 0px !important;
17 | border: 0px !important;
18 | }
19 |
20 | #notifly-notifications-header {
21 | padding: 9px;
22 | font-weight: bold;
23 | font-size: 13px;
24 | border-bottom: 1px solid #dddddd;
25 | }
26 |
27 | #notifly-notifications-footer {
28 | padding: 9px;
29 | text-align: center;
30 | font-weight: bold;
31 | font-size: 12px;
32 | border-top: 1px solid #dddddd;
33 | }
34 |
35 | .notifly-notification-not-read {
36 | background-color: #e9eaed;
37 | }
38 |
39 | .notifly-notification {
40 | padding: 9px;
41 | }
42 |
43 | #notifly-notifications-panel {
44 | width: 430px;
45 | }
46 |
47 | #notifly-notifications-content {
48 | overflow-y: scroll;
49 | max-height: 300px;
50 | }
--------------------------------------------------------------------------------
/lib/services/action_view_helper.rb:
--------------------------------------------------------------------------------
1 | module Notifly
2 | class ActionViewHelper
3 | attr_reader :action_view
4 | delegate *ActionView::Base.instance_methods.reject { |m| [:object_id, :__send__].include? m }, to: :action_view
5 |
6 | def initialize
7 | notifly_path = File.expand_path(File.dirname(File.dirname(__FILE__))) + '../../app/views/notifly'
8 | rails_path = File.join(Rails.root, 'app/views/notifly')
9 |
10 | ActionController::Base.prepend_view_path(notifly_path)
11 | ActionController::Base.prepend_view_path(rails_path)
12 |
13 | @action_view = ActionView::Base.new(ActionController::Base.view_paths)
14 | @action_view.extend ApplicationHelper
15 |
16 | @action_view.class_eval do
17 | include Notifly::Engine.routes.url_helpers
18 | Dir[File.join(Rails.root, 'app/helpers/**/*.rb')].each do |f|
19 | require f
20 | include f.split('/').last.split('.').first.camelize.constantize
21 | end
22 |
23 | def protect_against_forgery?
24 | false
25 | end
26 |
27 | def main_app
28 | Rails.application.routes.url_helpers
29 | end
30 | end
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/app/assets/javascripts/notifly/read_notifications.js.erb:
--------------------------------------------------------------------------------
1 | <% notifly = Notifly::Engine.routes.url_helpers %>
2 |
3 | $(document).ready(function() {
4 | _notiflyReadNotifications();
5 | _notiflyReadNotificationWithLink();
6 | });
7 |
8 | var _notiflyReadNotifications = function () {
9 | var $notifly = $('#notifly-mark-as-read-link');
10 |
11 | if($notifly.length !== 0) {
12 | $notifly.click(function () {
13 | $.ajax({
14 | url: '<%= notifly.read_notifications_path %>',
15 | data: {
16 | first_notification_id: notiflyFirstNotification,
17 | last_notification_id: notiflyLastNotification
18 | },
19 | type: 'PUT'
20 | });
21 | });
22 | }
23 | };
24 |
25 | var _notiflyReadNotificationWithLink = function () {
26 | if ($('#notifly').length > 0) {
27 | $('#notifly').on('click', '.notifly-notification-message a', function () {
28 | var notificationId = $(this).closest('div.notifly-notification').data('id');
29 |
30 | $.ajax({
31 | url: '<%= notifly.notification_toggle_read_path %>',
32 | data: {
33 | notification_id: notificationId,
34 | read: true
35 | },
36 | type: 'PUT'
37 | });
38 | });
39 | }
40 | };
--------------------------------------------------------------------------------
/spec/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | # Pick the frameworks you want:
4 | require "active_record/railtie"
5 | require "action_controller/railtie"
6 | require "action_mailer/railtie"
7 | require "action_view/railtie"
8 | require "sprockets/railtie"
9 | # require "rails/test_unit/railtie"
10 |
11 | Bundler.require(*Rails.groups)
12 | require "notifly"
13 |
14 | module TestApp
15 | class Application < Rails::Application
16 | # Settings in config/environments/* take precedence over those specified here.
17 | # Application configuration should go into files in config/initializers
18 | # -- all .rb files in that directory are automatically loaded.
19 |
20 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
21 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
22 | # config.time_zone = 'Central Time (US & Canada)'
23 |
24 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
25 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
26 | # config.i18n.default_locale = :de
27 | config.middleware.delete Rack::Lock
28 | end
29 | end
30 |
31 |
--------------------------------------------------------------------------------
/lib/generators/notifly/views/views_generator.rb:
--------------------------------------------------------------------------------
1 | module Notifly
2 | module Generators
3 | class ViewsGenerator < Rails::Generators::Base
4 | source_root File.expand_path('../../../../../app/views/notifly', __FILE__)
5 |
6 | class_option :layout, type: :boolean, default: false,
7 | desc: 'Include/Remove layout files.'
8 | class_option :mail, type: :boolean, default: false,
9 | desc: 'Include/Remove mails templates files.'
10 | class_option :notification, type: :boolean, default: false,
11 | desc: 'Include/Remove notifications templates files.'
12 |
13 | def copy_views
14 | notifications_templates if options.notification?
15 | notifications_mails_templates if options.mail?
16 | layout_files if options.layout?
17 | end
18 |
19 | private
20 | def main_app_path
21 | 'app/views/notifly'
22 | end
23 |
24 | def notifications_templates
25 | directory 'templates/notifications', "#{main_app_path}/templates/notifications"
26 | end
27 |
28 | def notifications_mails_templates
29 | directory 'templates/mails', "#{main_app_path}/templates/mails"
30 | end
31 |
32 | def layout_files
33 | directory 'layouts', "#{main_app_path}/layouts"
34 | end
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/notifly/models/options/fly.rb:
--------------------------------------------------------------------------------
1 | module Notifly
2 | module Models
3 | module Options
4 | class Fly
5 | attr_accessor :before, :after, :template, :sender, :receiver, :target,
6 | :if, :unless, :data, :mail, :kind, :then
7 |
8 | def initialize(options={})
9 | options = options.fetch(:default_values, options)
10 | options.each { |key, value| try "#{key}=", value }
11 | end
12 |
13 | def hook
14 | if @before.nil?
15 | :after
16 | else
17 | :before
18 | end
19 | end
20 |
21 | def method_name
22 | self.send(hook)
23 | end
24 |
25 | def attributes
26 | no_attrs = [hook, :if, :unless, :mail, :then]
27 | attrs = instance_values.reject { |key| no_attrs.include? key.to_sym }
28 | attrs.merge({mail: get_mail_type})
29 | end
30 |
31 | def merge(fly)
32 | raise TypeError, "#{fly} is not a Fly" unless fly.is_a? self.class
33 |
34 | Notifly::Models::Options::Fly.new instance_values.merge(fly.instance_values)
35 | end
36 |
37 | def get_mail_type
38 | if mail == true
39 | :always
40 | elsif mail.present? and mail[:only]
41 | :only
42 | else
43 | :never
44 | end
45 | end
46 | end
47 | end
48 | end
49 | end
--------------------------------------------------------------------------------
/spec/dummy/app/models/dummy_object.rb:
--------------------------------------------------------------------------------
1 | class DummyObject < ActiveRecord::Base
2 | has_many :posts
3 |
4 | notifly default_values: { receiver: :self }
5 | notifly after: :create, template: :create, if: :is_smart?
6 | notifly after: :save, template: :save, if: :is_smart?
7 | notifly after: :update, template: :update, if: :is_smart?
8 |
9 | notifly before: :destroy, template: :destroy_smart, mail: true, if: :is_smart?
10 |
11 | notifly before: :destroy, template: :destroy_dummy, mail: { only: true,
12 | template: :default }, unless: :is_smart?
13 | notifly after: :be_smart, template: :be_smart, mail: { only: true,
14 | template: :default }, if: :is_smart?
15 |
16 | notifly after: :buzz, if: :is_smart?
17 |
18 | notifly after: :test_kind, kind: :message
19 | notifly after: :test_kind, kind: :feed
20 |
21 | notifly after: :test_then, then: -> { self.update name: 'name_after_then' }
22 | notifly after: :test_then_using_notification, kind: :blastoise,
23 | then: ->(n) { self.update name: n.kind }
24 |
25 | def be_smart
26 | self.name = 'smart'
27 | end
28 |
29 | def buzz
30 | 'buzz'
31 | end
32 |
33 | def is_smart?
34 | name == 'smart'
35 | end
36 |
37 | def test_kind
38 | # code
39 | true
40 | end
41 |
42 | def test_then
43 | #code
44 | end
45 |
46 | def test_then_using_notification
47 | #code
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/spec/support/wait_ajax.rb:
--------------------------------------------------------------------------------
1 | # Ajax testing with ruby and capybara
2 | #
3 | # Add this to spec/support
4 | #
5 | # When a link or button starts an ajax request, instead of use Capybara
6 | # click_link, click_button and click_link_or_button methods use click_ajax_link,
7 | # click_ajax_button and click_ajax_link_or_button methods. You can still use
8 | # capybara methods and right after it, call wait_for_ajax method.
9 | #
10 | # This methods will wait until Capybara.default_wait_time for the ajax request
11 | # to finish before continue the normal tests flow.
12 | #
13 |
14 | require 'timeout'
15 |
16 | module WaitForAjax
17 | def click_ajax_link(locator, options = {})
18 | click_link(locator, options)
19 |
20 | wait_for_ajax
21 | end
22 |
23 | def click_ajax_button(locator, options = {})
24 | click_button(locator, options)
25 |
26 | wait_for_ajax
27 | end
28 |
29 | def click_ajax_link_or_button(locator, options = {})
30 | click_link_or_button(locator, options)
31 |
32 | wait_for_ajax
33 | end
34 |
35 | def wait_for_ajax(&block)
36 | block.call if block
37 |
38 | Timeout.timeout(Capybara.default_wait_time) do
39 | loop do
40 | sleep 0.1
41 | break if finished_all_ajax_requests?
42 | end
43 | end
44 | end
45 |
46 | def finished_all_ajax_requests?
47 | page.evaluate_script('jQuery.active').zero?
48 | end
49 | end
50 |
51 | RSpec.configure do |config|
52 | config.include WaitForAjax
53 | end
--------------------------------------------------------------------------------
/spec/mailers/notifly/notification_mailer_spec.rb:
--------------------------------------------------------------------------------
1 | require "rails_helper"
2 |
3 | module Notifly
4 | RSpec.describe NotificationMailer, :type => :mailer do
5 | let(:mail) { NotificationMailer.notifly(to: dummy.email, template: notification.template,
6 | notification_id: notification.id) }
7 | let(:notification) { Notifly::Notification.create! receiver: dummy,
8 | mail: :always }
9 | let(:dummy) { DummyObject.create name: 'Dummy', email: 'dummy@test.com' }
10 |
11 | it 'should guarantee that the receiver is correct' do
12 | expect(mail.to).to eq([dummy.email])
13 | end
14 |
15 | it 'should guarantee that the sender is correct' do
16 | expect(mail.from).to eq(['change-me-at-config-initializers-notifly@exemple.com'])
17 | end
18 |
19 | it 'should guarantee that the subject is correct' do
20 | expect(mail.subject).to include('You have a new notification')
21 | end
22 |
23 | it 'should guarantee that all information appears on the email body' do
24 | expect(mail.body).to include('Default notification mail')
25 | end
26 |
27 | context 'when using other templates' do
28 | let(:notification) { Notifly::Notification.create! receiver: dummy,
29 | mail: :only, template: :hello }
30 |
31 | it 'should guarantee that the subject is correct' do
32 | expect(mail.subject).to include('Hello!')
33 | end
34 |
35 | it 'should guarantee that all information appears on the email body' do
36 | expect(mail.body).to include('Hello mail')
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger.
20 | config.active_support.deprecation = :log
21 |
22 | # Raise an error on page load if there are pending migrations.
23 | config.active_record.migration_error = :page_load
24 |
25 | # Debug mode disables concatenation and preprocessing of assets.
26 | # This option may cause significant delays in view rendering with a large
27 | # number of complex assets.
28 | config.assets.debug = true
29 |
30 | # Adds additional error checking when serving assets at runtime.
31 | # Checks for improperly declared sprockets dependencies.
32 | # Raises helpful error messages.
33 | config.assets.raise_runtime_errors = true
34 |
35 | # Raises error for missing translations
36 | # config.action_view.raise_on_missing_translations = true
37 | end
38 |
--------------------------------------------------------------------------------
/app/views/notifly/notifications/index.js.erb:
--------------------------------------------------------------------------------
1 | $('#notifly-notifications-content .notifly-loading').remove();
2 | $('#notifly-notifications-content .notifly-empty').remove();
3 |
4 | <% if @notifications.any? %>
5 | <% rendered_notifications = render partial: 'notifly/layouts/index',
6 | locals: { notifications: @notifications } %>
7 | <% last_notification = @notifications.last.try(:id) %>
8 | <% first_notification = @notifications.first.try(:id) %>
9 |
10 | <% if @scope_param == 'older' %>
11 | $('#notifly-notifications-content').append("<%= j rendered_notifications %>");
12 | notiflyLastNotification = <%= last_notification %>;
13 |
14 | $('#notifly-notifications-content').scrollTop($('<%= "#notifly-notification-#{first_notification}" %>').offset().top);
15 | <% else %>
16 | $('#notifly-notifications-content').prepend("<%= j rendered_notifications %>");
17 | notiflyFirstNotification = <%= first_notification %>;
18 |
19 | if (notiflyLastNotification == undefined)
20 | notiflyLastNotification = <%= last_notification %>;
21 |
22 | <% end %>
23 |
24 | <% else %>
25 | if (notiflyLastNotification == undefined) {
26 | $('#notifly-more-notifications-link').remove();
27 | $('#notifly-notifications-content').append("<%= j(render 'notifly/layouts/empty')%>")
28 | }
29 |
30 | <% end %>
31 |
32 | <% if @scope_param == 'older' and @user.notifly_notifications.older(than: last_notification).blank? %>
33 | $('#notifly-more-notifications-link').remove();
34 | <% end %>
35 |
36 | $('#notifly-counter').replaceWith("<%= j(render 'notifly/layouts/counter', counter: @counter)%>");
37 | $('#notifly-icon').replaceWith("<%= j notifly_icon(@counter > 0) %>")
38 | Tinycon.setBubble(<%= @counter %>);
39 |
--------------------------------------------------------------------------------
/spec/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 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure static asset server for tests with Cache-Control for performance.
16 | config.serve_static_assets = true
17 | config.static_cache_control = 'public, max-age=3600'
18 |
19 | # Show full error reports and disable caching.
20 | config.consider_all_requests_local = true
21 | config.action_controller.perform_caching = false
22 |
23 | # Raise exceptions instead of rendering exception templates.
24 | config.action_dispatch.show_exceptions = false
25 |
26 | # Disable request forgery protection in test environment.
27 | config.action_controller.allow_forgery_protection = false
28 |
29 | # Tell Action Mailer not to deliver emails to the real world.
30 | # The :test delivery method accumulates sent emails in the
31 | # ActionMailer::Base.deliveries array.
32 | config.action_mailer.delivery_method = :test
33 |
34 | # Print deprecation notices to the stderr.
35 | config.active_support.deprecation = :stderr
36 |
37 | # Raises error for missing translations
38 | # config.action_view.raise_on_missing_translations = true
39 | end
40 |
--------------------------------------------------------------------------------
/app/models/notifly/notification.rb:
--------------------------------------------------------------------------------
1 | module Notifly
2 | class Notification < ActiveRecord::Base
3 | belongs_to :target, polymorphic: true
4 | belongs_to :sender, polymorphic: true
5 | belongs_to :receiver, polymorphic: true
6 |
7 | before_validation :set_defaults
8 | after_create :send_to_receiver, if: -> { Notifly.websocket }
9 |
10 | scope :all_from, -> (receiver) { where(receiver: receiver) }
11 | scope :unseen, -> { where(seen: false) }
12 | scope :not_only_mail, -> { where.not(mail: 'only') }
13 | scope :limited, -> { limit(Notifly.per_page) }
14 | scope :ordered, -> { order('notifly_notifications.created_at DESC') }
15 | scope :newer, ->(than: nil) do
16 | return ordered if than.blank?
17 |
18 | reference = find_by(id: than)
19 | ordered.where('notifly_notifications.created_at > ?', reference.created_at).where.not(id: reference)
20 | end
21 | scope :older, ->(than: nil) do
22 | reference = find_by(id: than)
23 |
24 | ordered.
25 | where('notifly_notifications.created_at < ?', reference.created_at).
26 | where.not(id: reference)
27 | end
28 | scope :between, ->(first, last) do
29 | notifications = where(id: [first, last])
30 | where(created_at: (notifications.first.created_at..notifications.last.created_at))
31 | end
32 |
33 | validates :receiver, :template, :mail, :kind, presence: true
34 |
35 | serialize :data, JSON
36 |
37 | private
38 | def set_defaults
39 | self.mail ||= :never
40 | self.kind ||= :notification
41 | self.template ||= :default
42 | end
43 |
44 | def send_to_receiver
45 | Notifly::NotificationChannel.new(self.receiver_id).trigger(self) if self.kind == 'notification'
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/spec/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 |
--------------------------------------------------------------------------------
/spec/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 |
--------------------------------------------------------------------------------
/spec/dummy/spec/features/notifly/counter_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe 'Notifly counter', :type => :feature, js: true do
4 | let(:notification) { Notifly::Notification }
5 | let(:receiver) { DummyObject.create! name: 'User' }
6 |
7 | before(:each) do
8 | notification.create! receiver: receiver, seen: true, read: false, mail: :never
9 | notification.create! receiver: receiver, seen: false, read: false, mail: :never
10 | notification.create! receiver: receiver, seen: false, read: false, mail: :always
11 | notification.create! receiver: receiver, seen: false, read: false, mail: :always
12 | notification.create! receiver: DummyObject.create!, seen: false, read: false,
13 | mail: :never
14 | notification.create! receiver: DummyObject.create!, seen: false, read: false,
15 | mail: :never
16 | Notifly.per_page = 2
17 | end
18 |
19 | after(:each) do
20 | Notifly.per_page = 10
21 | end
22 |
23 | scenario 'seeing notifications' do
24 | wait_for_ajax { visit root_path }
25 | within("#notifly-counter") do
26 | expect(page).to have_content '3'
27 | end
28 |
29 | wait_for_ajax { find('#notifly-icon').click }
30 |
31 | within("#notifly-counter") do
32 | expect(page).to have_content '1'
33 | end
34 |
35 | click_ajax_link 'More'
36 |
37 | within("#notifly-counter", visible: false) do
38 | expect(page).to have_content '0'
39 | end
40 | end
41 |
42 | context 'when notification is mail only' do
43 | scenario 'seeing only notifications without mail only' do
44 | notification.create! receiver: receiver, seen: false, read: false, mail: :only
45 | wait_for_ajax { visit root_path }
46 |
47 | within("#notifly-counter") do
48 | expect(page).to_not have_content '4'
49 | expect(page).to have_content '3'
50 | end
51 | end
52 | end
53 | end
--------------------------------------------------------------------------------
/app/controllers/notifly/notifications_controller.rb:
--------------------------------------------------------------------------------
1 | require_dependency "notifly/application_controller"
2 |
3 | module Notifly
4 | class NotificationsController < ApplicationController
5 | def index
6 | @notifications = scoped_notifications
7 | @notifications.update_all(seen: true) if params[:mark_as_seen] == 'true'
8 |
9 | @counter = count_unseen
10 | @scope_param = scope_param
11 | end
12 |
13 | def read
14 | if params[:first_notification_id].present? and params[:last_notification_id].present?
15 | @notifications = notifications_between
16 | @notifications.update_all(read: true)
17 | end
18 | end
19 |
20 | def toggle_read
21 | @notification = Notifly::Notification.find(params[:notification_id])
22 | @notification.update(read: params[:read] || !@notification.read)
23 | end
24 |
25 | def seen
26 | if params[:first_notification_id].present? and params[:last_notification_id].present?
27 | @notifications = notifications_between
28 | @notifications.update_all(seen: true)
29 | end
30 | @counter = count_unseen
31 | end
32 |
33 | private
34 | def scoped_notifications
35 | current_user_notifications.send(scope_param, than: params[:reference_notification_id]).limited
36 | end
37 |
38 | def scope_param
39 | return params[:scope] if ['older', 'newer'].include?(params[:scope])
40 | end
41 |
42 | def current_user_notifications
43 | @user = current_user
44 | @user.notifly_notifications(:notification).not_only_mail
45 | end
46 |
47 | def count_unseen
48 | current_user_notifications.unseen.count
49 | end
50 |
51 | def notifications_between
52 | current_user_notifications.between(params[:first_notification_id],
53 | params[:last_notification_id]).ordered
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/spec/dummy/spec/features/notifly/loading_notifications_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe 'Loading notifications', :type => :feature, js: true do
4 |
5 | before(:each) { @receiver = DummyObject.create! name: 'User' }
6 |
7 | context 'when have notifications' do
8 | before(:each) do
9 | Notifly::Notification.create! receiver: DummyObject.create!, template: :default,
10 | read: false, seen: false
11 |
12 | 3.times { notification_with_mail(:always) } # page 2 with the last 3
13 | @last_notification_from_page = notification_with_mail(:always) # page 1
14 |
15 | 2.times { notification_with_mail(:only) } # are not in a page
16 | 9.times { notification_with_mail(:never) } # page 1
17 |
18 | wait_for_ajax { visit root_path }
19 | end
20 |
21 | def notification_with_mail(occurrence)
22 | Notifly::Notification.create! receiver: @receiver, template: :default,
23 | read: false, mail: occurrence
24 | end
25 |
26 | scenario 'visiting page' do
27 | within("#notifly") do
28 | expect(page).to have_css('div.notifly-notification', count: 10, visible: false)
29 | end
30 |
31 | wait_for_ajax { find('#notifly-icon').click }
32 |
33 | within('#notifly') do
34 | expect(page).to have_css('div.notifly-notification', count: 10, visible: true)
35 | end
36 | end
37 |
38 | scenario 'loading next page link' do
39 | wait_for_ajax { find('#notifly-icon').click }
40 | expect(page).to have_css('div.notifly-notification', count: 10, visible: true)
41 |
42 | within('#notifly-notifications-footer') do
43 | expect(page).to have_link('More')
44 | end
45 |
46 | click_ajax_link 'More'
47 |
48 | expect(page).to have_css('div.notifly-notification', count: 13, visible: true)
49 | within('#notifly-notifications-footer') do
50 | expect(page).to_not have_link('More')
51 | end
52 | end
53 | end
54 |
55 | context 'when none notifications' do
56 | scenario 'loading next page link' do
57 | wait_for_ajax { visit root_path }
58 | wait_for_ajax { find('#notifly-icon').click }
59 |
60 | within('#notifly-notifications-footer') do
61 | expect(page).to_not have_link('More')
62 | end
63 | end
64 | end
65 | end
--------------------------------------------------------------------------------
/spec/dummy/db/schema.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | # This file is auto-generated from the current state of the database. Instead
3 | # of editing this file, please use the migrations feature of Active Record to
4 | # incrementally modify your database, and then regenerate this schema definition.
5 | #
6 | # Note that this schema.rb definition is the authoritative source for your
7 | # database schema. If you need to create the application database on another
8 | # system, you should be using db:schema:load, not running all the migrations
9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 | # you'll amass, the slower it'll run and the greater likelihood for issues).
11 | #
12 | # It's strongly recommended that you check this file into your version control system.
13 |
14 | ActiveRecord::Schema.define(version: 20141212122918) do
15 |
16 | # These are extensions that must be enabled in order to support this database
17 | enable_extension "plpgsql"
18 |
19 | create_table "dummy_objects", force: true do |t|
20 | t.string "name"
21 | t.datetime "created_at"
22 | t.datetime "updated_at"
23 | t.string "email"
24 | end
25 |
26 | create_table "notifly_notifications", force: true do |t|
27 | t.string "template"
28 | t.boolean "read", default: false
29 | t.integer "target_id"
30 | t.string "target_type"
31 | t.integer "sender_id"
32 | t.string "sender_type"
33 | t.integer "receiver_id"
34 | t.string "receiver_type"
35 | t.datetime "created_at"
36 | t.datetime "updated_at"
37 | t.text "data"
38 | t.boolean "seen", default: false
39 | t.string "mail"
40 | t.string "kind", default: "notification"
41 | end
42 |
43 | add_index "notifly_notifications", ["receiver_id", "receiver_type"], name: "index_notifly_notifications_on_receiver_id_and_receiver_type", using: :btree
44 | add_index "notifly_notifications", ["sender_id", "sender_type"], name: "index_notifly_notifications_on_sender_id_and_sender_type", using: :btree
45 | add_index "notifly_notifications", ["target_id", "target_type"], name: "index_notifly_notifications_on_target_id_and_target_type", using: :btree
46 |
47 | create_table "posts", force: true do |t|
48 | t.string "author"
49 | t.string "title"
50 | t.text "content"
51 | t.boolean "published", default: false
52 | t.integer "dummy_object_id"
53 | end
54 |
55 | add_index "posts", ["dummy_object_id"], name: "index_posts_on_dummy_object_id", using: :btree
56 |
57 | end
58 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
2 |
3 | RSpec.configure do |config|
4 | if config.files_to_run.one?
5 | config.default_formatter = 'doc'
6 | end
7 |
8 | config.expect_with :rspec do |expectations|
9 | expectations.syntax = :expect
10 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true
11 | end
12 |
13 | config.mock_with :rspec do |mocks|
14 | mocks.syntax = :expect
15 | mocks.verify_partial_doubles = false
16 | end
17 |
18 | config.order = :random
19 |
20 | # The settings below are suggested to provide a good initial experience
21 | # with RSpec, but feel free to customize to your heart's content.
22 | =begin
23 | # These two settings work together to allow you to limit a spec run
24 | # to individual examples or groups you care about by tagging them with
25 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples
26 | # get run.
27 | config.filter_run :focus
28 | config.run_all_when_everything_filtered = true
29 |
30 | # Limits the available syntax to the non-monkey patched syntax that is recommended.
31 | # For more details, see:
32 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
33 | # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
34 | # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
35 | config.disable_monkey_patching!
36 |
37 | # Many RSpec users commonly either run the entire suite or an individual
38 | # file, and it's useful to allow more verbose output when running an
39 | # individual spec file.
40 | if config.files_to_run.one?
41 | # Use the documentation formatter for detailed output,
42 | # unless a formatter has already been configured
43 | # (e.g. via a command-line flag).
44 | config.default_formatter = 'doc'
45 | end
46 |
47 | # Print the 10 slowest examples and example groups at the
48 | # end of the spec run, to help surface which specs are running
49 | # particularly slow.
50 | config.profile_examples = 10
51 |
52 | # Run specs in random order to surface order dependencies. If you find an
53 | # order dependency and want to debug it, you can fix the order by providing
54 | # the seed, which is printed after each run.
55 | # --seed 1234
56 | config.order = :random
57 |
58 | # Seed global randomization in this process using the `--seed` CLI option.
59 | # Setting this allows you to use `--seed` to deterministically reproduce
60 | # test failures related to randomization by passing the same `--seed` value
61 | # as the one that triggered the failure.
62 | Kernel.srand config.seed
63 | =end
64 | end
65 |
--------------------------------------------------------------------------------
/spec/dummy/spec/features/notifly/read_notifications_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe 'Read notification', :type => :feature, js: true do
4 | before do
5 | @receiver = DummyObject.create! name: 'User'
6 | @n_1 = Notifly::Notification.create! receiver: @receiver, read: false, mail: :never
7 | @n_2 = Notifly::Notification.create! receiver: @receiver, read: false, mail: :never
8 | @n_3 = Notifly::Notification.create! receiver: @receiver, read: false, mail: :never
9 | Notifly.per_page = 2
10 | end
11 |
12 | after(:each) do
13 | Notifly.per_page = 10
14 | end
15 |
16 | def open_notifly
17 | wait_for_ajax { visit root_path }
18 | wait_for_ajax { find('#notifly-icon').click }
19 | end
20 |
21 | context 'when read a specific notification' do
22 | scenario 'read a notification' do
23 | open_notifly
24 |
25 | notification_id = "#notifly-notification-#{@n_2.id}"
26 | expect(page).to have_selector ("#{notification_id}.notifly-notification-not-read")
27 |
28 | within(notification_id) do
29 | click_ajax_link 'read'
30 | end
31 |
32 | expect(page).to have_selector (notification_id)
33 | expect(page).to_not have_selector ("#{notification_id}.notifly-notification-not-read")
34 | end
35 |
36 | scenario 'unread a notification' do
37 | notification = Notifly::Notification.create! receiver: @receiver, read: true,
38 | mail: :never
39 | notification_id = "#notifly-notification-#{notification.id}"
40 | open_notifly
41 | expect(page).to have_selector (notification_id)
42 | expect(page).to_not have_selector ("#{notification_id}.notifly-notification-not-read")
43 |
44 | within(notification_id) do
45 | click_ajax_link 'unread'
46 | end
47 |
48 | expect(page).to have_selector ("#{notification_id}.notifly-notification-not-read")
49 | end
50 | end
51 |
52 | scenario 'mark all read' do
53 | open_notifly
54 |
55 | expect(page).to have_selector ("#notifly-notification-#{@n_3.id}.notifly-notification-not-read")
56 | expect(page).to have_selector ("#notifly-notification-#{@n_2.id}.notifly-notification-not-read")
57 | expect(page).to_not have_selector ("#notifly-notification-#{@n_1.id}")
58 |
59 | within('#notifly') do
60 | click_ajax_link 'Mark as read'
61 | click_ajax_link 'More'
62 | end
63 |
64 | expect(page).to have_selector ("#notifly-notification-#{@n_3.id}")
65 | expect(page).to have_selector ("#notifly-notification-#{@n_2.id}")
66 | expect(page).to have_selector ("#notifly-notification-#{@n_1.id}.notifly-notification-not-read")
67 |
68 | expect(page).to_not have_selector ("#notifly-notification-#{@n_3.id}.notifly-notification-not-read")
69 | expect(page).to_not have_selector ("#notifly-notification-#{@n_2.id}.notifly-notification-not-read")
70 | end
71 | end
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/websocket_rails.rb:
--------------------------------------------------------------------------------
1 | WebsocketRails.setup do |config|
2 |
3 | # Uncomment to override the default log level. The log level can be
4 | # any of the standard Logger log levels. By default it will mirror the
5 | # current Rails environment log level.
6 | # config.log_level = :debug
7 |
8 | # Uncomment to change the default log file path.
9 | # config.log_path = "#{Rails.root}/log/websocket_rails.log"
10 |
11 | # Set to true if you wish to log the internal websocket_rails events
12 | # such as the keepalive `websocket_rails.ping` event.
13 | # config.log_internal_events = false
14 |
15 | # Change to true to enable standalone server mode
16 | # Start the standalone server with rake websocket_rails:start_server
17 | # * Requires Redis
18 | config.standalone = false
19 |
20 | # Change to true to enable channel synchronization between
21 | # multiple server instances.
22 | # * Requires Redis.
23 | config.synchronize = false
24 |
25 | # Prevent Thin from daemonizing (default is true)
26 | # config.daemonize = false
27 |
28 | # Uncomment and edit to point to a different redis instance.
29 | # Will not be used unless standalone or synchronization mode
30 | # is enabled.
31 | # config.redis_options = {:host => 'localhost', :port => '6379'}
32 |
33 | # By default, all subscribers in to a channel will be removed
34 | # when that channel is made private. If you don't wish active
35 | # subscribers to be removed from a previously public channel
36 | # when making it private, set the following to true.
37 | # config.keep_subscribers_when_private = false
38 |
39 | # Set to true if you wish to broadcast channel subscriber_join and
40 | # subscriber_part events. All subscribers of a channel will be
41 | # notified when other clients join and part the channel. If you are
42 | # using the UserManager, the current_user object will be sent along
43 | # with the event.
44 | # config.broadcast_subscriber_events = true
45 |
46 | # Used as the key for the WebsocketRails.users Hash. This method
47 | # will be called on the `current_user` object in your controller
48 | # if one exists. If `current_user` does not exist or does not
49 | # respond to the identifier, the key will default to `connection.id`
50 | # config.user_identifier = :id
51 |
52 | # Uncomment and change this option to override the class associated
53 | # with your `current_user` object. This class will be used when
54 | # synchronization is enabled and you trigger events from background
55 | # jobs using the WebsocketRails.users UserManager.
56 | # config.user_class = User
57 |
58 | # Supporting HTTP streaming on Internet Explorer versions 8 & 9
59 | # requires CORS to be enabled for GET "/websocket" request.
60 | # List here the origin domains allowed to perform the request.
61 | # config.allowed_origins = ['http://localhost:3000']
62 |
63 | end
64 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
20 | # config.action_dispatch.rack_cache = true
21 |
22 | # Disable Rails's static asset server (Apache or nginx will already do this).
23 | config.serve_static_assets = false
24 |
25 | # Compress JavaScripts and CSS.
26 | config.assets.js_compressor = :uglifier
27 | # config.assets.css_compressor = :sass
28 |
29 | # Do not fallback to assets pipeline if a precompiled asset is missed.
30 | config.assets.compile = false
31 |
32 | # Generate digests for assets URLs.
33 | config.assets.digest = true
34 |
35 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
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 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
42 | # config.force_ssl = true
43 |
44 | # Set to :debug to see everything in the log.
45 | config.log_level = :info
46 |
47 | # Prepend all log lines with the following tags.
48 | # config.log_tags = [ :subdomain, :uuid ]
49 |
50 | # Use a different logger for distributed setups.
51 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
52 |
53 | # Use a different cache store in production.
54 | # config.cache_store = :mem_cache_store
55 |
56 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
57 | # config.action_controller.asset_host = "http://assets.example.com"
58 |
59 | # Ignore bad email addresses and do not raise email delivery errors.
60 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
61 | # config.action_mailer.raise_delivery_errors = false
62 |
63 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
64 | # the I18n.default_locale when a translation cannot be found).
65 | config.i18n.fallbacks = true
66 |
67 | # Send deprecation notices to registered listeners.
68 | config.active_support.deprecation = :notify
69 |
70 | # Disable automatic flushing of the log to improve performance.
71 | # config.autoflush_log = false
72 |
73 | # Use default logging formatter so that PID and timestamp are not suppressed.
74 | config.log_formatter = ::Logger::Formatter.new
75 |
76 | # Do not dump schema after migrations.
77 | config.active_record.dump_schema_after_migration = false
78 | end
79 |
--------------------------------------------------------------------------------
/spec/dummy/spec/models/post_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Post, :type => :model do
4 | it { is_expected.to belong_to(:dummy_object) }
5 |
6 | describe 'Notifly' do
7 | let(:dummy) { DummyObject.create! }
8 | let(:post) { Post.create! dummy_object: dummy, published: false }
9 | let(:notification) { Notifly::Notification.take }
10 |
11 | describe 'initialization' do
12 | it { expect(Post).to respond_to :notifly }
13 | end
14 |
15 | describe 'usage' do
16 | it 'should create a notification when is published' do
17 | expect(Notifly::Notification.count).to eql 0
18 |
19 | expect { post.publish! }.to change(Notifly::Notification, :count).from(0).to(1)
20 | expect(notification.receiver).to eql dummy
21 | expect(notification.target).to eql post
22 | expect(notification.template).to eql 'publish'
23 | end
24 |
25 | describe 'Notification fallback (data)' do
26 | it 'should use Notifly::Notification#data for missing informations' do
27 | post_attributes = post.attributes
28 | expect { post.destroy }.to change(Notifly::Notification, :count).from(0).to(1)
29 |
30 | expect(notification.receiver).to eql dummy
31 | expect(notification.template).to eql 'destroy'
32 | expect(notification.reload.data).to eql post_attributes
33 | end
34 |
35 | context 'when run notifly by order' do
36 | context 'when post title starts with nil' do
37 | it 'should send two notifications' do
38 | post_1 = Post.create dummy_object: dummy, published: true, title: 'TitleBar'
39 |
40 | expect { post_1.change_title }.to change(Notifly::Notification, :count).
41 | by(2)
42 |
43 | expect(Notifly::Notification.first.data['title_before_create']).to eql 'TitleBar'
44 | expect(Notifly::Notification.last.data['title']).to eql 'NewTitle'
45 | end
46 | end
47 |
48 | context 'when post title starts with TitleFoo' do
49 | it 'should send two notifications' do
50 | expect_any_instance_of(Post).to receive(:change_title) { nil }
51 | post = Post.create! dummy_object: dummy, title: 'TitleFoo'
52 |
53 | expect { post.change_title }.to_not change(Notifly::Notification, :count)
54 | end
55 | end
56 | end
57 | end
58 | end
59 |
60 | describe '#notifly_notifications' do
61 | it 'should show its notifications' do
62 | dummy_notification = Notifly::Notification.create! receiver: dummy,
63 | sender: post, mail: :never
64 | post_notifications = (1..3).map do
65 | Notifly::Notification.create! receiver: post, sender: dummy, mail: :never
66 | end
67 |
68 | expect(post.notifly_notifications).to include(*post_notifications)
69 | expect(post.notifly_notifications).to_not include(dummy_notification)
70 | end
71 |
72 | it 'should query its notifications' do
73 | dummy_2 = DummyObject.create
74 | notification_1 = Notifly::Notification.create! receiver: post,
75 | sender: dummy, template: 'destroy', mail: :never
76 | notification_2 = Notifly::Notification.create! receiver: post,
77 | sender: dummy, target: dummy_2, mail: :never
78 | notification_3 = Notifly::Notification.create! receiver: post,
79 | sender: dummy, target: dummy_2, mail: :never
80 |
81 | destroy_notifications = post.notifly_notifications.where(template: 'destroy')
82 | dummy_2_notifications = post.notifly_notifications.where(target: dummy_2)
83 |
84 | expect(destroy_notifications).to include(notification_1)
85 | expect(destroy_notifications).to_not include(notification_2, notification_3)
86 | expect(dummy_2_notifications).to include(notification_2, notification_3)
87 | expect(dummy_2_notifications).to_not include(notification_1)
88 | end
89 | end
90 | end
91 | end
92 |
--------------------------------------------------------------------------------
/spec/models/notifly/notification_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | module Notifly
4 | RSpec.describe Notification, :type => :model do
5 | describe 'validations' do
6 | it { is_expected.to validate_presence_of(:receiver) }
7 |
8 | it 'should validates presence of template' do
9 | is_expected.to_not validate_presence_of(:template)
10 |
11 | allow_any_instance_of(Notification).to receive(:set_defaults) { nil }
12 | is_expected.to validate_presence_of(:template)
13 | end
14 |
15 | it 'should validates presence of mail' do
16 | is_expected.to_not validate_presence_of(:mail)
17 |
18 | allow_any_instance_of(Notification).to receive(:set_defaults) { nil }
19 | is_expected.to validate_presence_of(:mail)
20 | end
21 |
22 | it 'should validates presence of kind' do
23 | is_expected.to_not validate_presence_of(:kind)
24 |
25 | allow_any_instance_of(Notification).to receive(:set_defaults) { nil }
26 | is_expected.to validate_presence_of(:kind)
27 | end
28 | end
29 |
30 | describe '#data' do
31 | it 'should save and repond with a hash' do
32 | hash = { 'post' => Post.create!.attributes }
33 |
34 | notification = Notifly::Notification.create! receiver: DummyObject.create,
35 | template: :foo, data: hash, mail: :never
36 |
37 | expect(notification.reload.data).to eql hash
38 | end
39 | end
40 |
41 | describe 'scopes' do
42 | def simple_notification
43 | Notifly::Notification.create! receiver: DummyObject.create!, mail: :never
44 | end
45 |
46 | before(:each) { Notifly.per_page = 4 }
47 |
48 | describe '.newer' do
49 | it 'should return first page if do not receive params' do
50 | page_1 = []
51 | first_notification_from_page_2 = simple_notification
52 | 3.times { page_1 << simple_notification }
53 | page_1 << first_notification_from_page_1 = simple_notification
54 |
55 | expect(Notifly::Notification.newer.limited).to match_array page_1
56 | end
57 |
58 | it 'should return newer notifications than a specific notification' do
59 | new_notification_1 = simple_notification
60 | new_notification_2 = simple_notification
61 | new_notification_3 = simple_notification
62 | new_notification_4 = simple_notification
63 |
64 | expect(Notifly::Notification.newer than: new_notification_2.id).
65 | to match_array [new_notification_3, new_notification_4]
66 | end
67 | end
68 |
69 | describe '.between' do
70 | it 'should return notifications between specific notifications' do
71 | notifications = []
72 | notification_1 = simple_notification
73 | notification_2 = simple_notification
74 | notification_3 = simple_notification
75 | notification_4 = simple_notification
76 | notification_5 = simple_notification
77 | notification_6 = simple_notification
78 |
79 | expect(Notifly::Notification.between notification_2, notification_5).
80 | to match_array [notification_2, notification_3, notification_4, notification_5]
81 | end
82 | end
83 |
84 | describe '.older' do
85 | let(:page_1) { [ ] }
86 | let(:page_2) { [ ] }
87 | let(:page_3) { [ ] }
88 |
89 | before(:each) do
90 | page_3 << @last_notification_from_page_3 = simple_notification
91 | 3.times { page_3 << simple_notification }
92 | page_2 << @last_notification_from_page_2 = simple_notification
93 | 3.times { page_2 << simple_notification }
94 | page_1 << @last_notification_from_page_1 = simple_notification
95 | 3.times { page_1 << simple_notification }
96 | end
97 |
98 | it { expect(Notifly::Notification.older(than: @last_notification_from_page_1.id).limited).
99 | to match_array page_2 }
100 | it { expect(Notifly::Notification.older(than: @last_notification_from_page_2.id).limited).
101 | to match_array page_3 }
102 | it { expect(Notifly::Notification.older(than: @last_notification_from_page_3.id).limited).
103 | to match_array [] }
104 | end
105 | end
106 | end
107 | end
108 |
--------------------------------------------------------------------------------
/lib/notifly/models/flyable.rb:
--------------------------------------------------------------------------------
1 | require_relative 'options/fly'
2 |
3 | module Notifly
4 | module Models
5 | module Flyable
6 | extend ActiveSupport::Concern
7 |
8 | module ClassMethods
9 | attr_reader :flies, :default_fly, :flyable_callbacks
10 |
11 | def notifly(options = {})
12 | @flies ||= []
13 | @flyable_callbacks ||= []
14 |
15 | fly = Notifly::Models::Options::Fly.new options
16 |
17 | if options[:default_values]
18 | @default_fly = fly
19 | else
20 | @flies << fly
21 |
22 | if [:create, :save, :update, :destroy].include? fly.method_name
23 | _create_callback_for_active_record_from fly
24 | end
25 | end
26 | end
27 |
28 | def method_added(method_name)
29 | _create_callbacks_for method_name
30 | super
31 | end
32 |
33 | private
34 | def _create_callbacks_for(method_name)
35 | method_flies = _flies_for method_name
36 |
37 | method_flies.each do |fly|
38 | _create_callback_for_instance_method_from(fly)
39 | end
40 | end
41 |
42 | def _flies_for(method_name)
43 | if flies.present?
44 | flies.select { |fly| fly.method_name == method_name }
45 | else
46 | []
47 | end
48 | end
49 |
50 | def _create_callback_for_active_record_from(fly)
51 | callback_name = "#{fly.hook}_#{fly.method_name}"
52 | flyable_callbacks << "#{callback_name}_#{fly.object_id}"
53 |
54 | send(callback_name, if: fly.if, unless: fly.unless) do |record|
55 | _create_notification_for(fly)
56 | end
57 | end
58 |
59 | def _create_callback_for_instance_method_from(fly)
60 | notifly_callback_name = _format_callback_name_for(fly)
61 |
62 | if not flyable_callbacks.include? notifly_callback_name
63 | flyable_callbacks << notifly_callback_name
64 |
65 | define_callbacks notifly_callback_name
66 | set_callback notifly_callback_name, fly.hook, if: fly.if, unless: fly.unless do |record|
67 | _create_notification_for(fly)
68 | end
69 |
70 | old_method = instance_method(fly.method_name)
71 |
72 | define_method(fly.method_name) do |*args|
73 | run_callbacks(notifly_callback_name) do
74 | old_method.bind(self).call(*args)
75 | end
76 | end
77 | end
78 | end
79 |
80 | def _format_callback_name_for(fly)
81 | ending_chars = {
82 | '!' => :_dangerous,
83 | '?' => :_question
84 | }
85 |
86 | method_name = fly.method_name.to_s.gsub(/(?[\?|\!])/, ending_chars)
87 |
88 | "notifly_#{fly.hook}_#{method_name}_#{fly.object_id}"
89 | end
90 | end
91 |
92 | def _create_notification_for(fly)
93 | new_fly = _default_fly.merge(fly)
94 |
95 | notification = Notifly::Notification.create _get_attributes_from(new_fly)
96 | _after_create_notification(notification, new_fly)
97 |
98 | rescue => e
99 | logger.error "Something goes wrong with Notifly, will ignore: #{e}"
100 | raise e if not Rails.env.production?
101 |
102 | end
103 |
104 | def notifly_notifications(kind=nil)
105 | notifications = Notifly::Notification.all_from(self)
106 | kind.present? ? notifications.where(kind: kind) : notifications
107 | end
108 |
109 | private
110 | def _default_fly
111 | self.class.default_fly || Notifly::Models::Options::Fly.new
112 | end
113 |
114 | def _get_attributes_from(fly)
115 | evaluated_attributes = {}
116 |
117 | fly.attributes.each do |key, value|
118 | evaluated_attributes[key] = _eval_for(key, value)
119 | end
120 |
121 | evaluated_attributes
122 | end
123 |
124 | def _eval_for(key, value)
125 | if [:template, :mail, :kind].include? key.to_sym
126 | value
127 | elsif value == :self
128 | self
129 | else
130 | if value.is_a? Proc
131 | instance_exec &value
132 | else
133 | send(value)
134 | end
135 | end
136 | end
137 |
138 | def _after_create_notification(notification, fly)
139 | if fly.then.present?
140 | block = fly.then;
141 | block.parameters.present? ? instance_exec(notification, &block) : instance_exec(&block)
142 | end
143 |
144 | if fly.mail.present?
145 | template = fly.mail.try(:fetch, :template) || notification.template
146 | Notifly::NotificationMailer.notifly to: instance_eval(fly.receiver.to_s).email, template: template,
147 | notification_id: notification.id
148 | end
149 | end
150 | end
151 | end
152 | end
--------------------------------------------------------------------------------
/spec/dummy/spec/models/dummy_object_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe DummyObject, :type => :model do
4 | it { is_expected.to have_many(:posts) }
5 |
6 | describe 'Notifly' do
7 | it 'should return the method return' do
8 | smart = DummyObject.create name: 'smart'
9 | dummy = DummyObject.create name: nil
10 |
11 | expect(smart.buzz).to eql('buzz')
12 | expect(dummy.buzz).to eql('buzz')
13 |
14 | expect(smart.update(name: 'smart')).to be_truthy
15 | expect(dummy.update(name: 'dummy')).to be_truthy
16 | end
17 |
18 | context 'when using ActiveRecord methods' do
19 | let(:notifications) { Notifly::Notification.not_only_mail }
20 |
21 | describe 'class methods' do
22 | describe '.create' do
23 | it { expect { DummyObject.create name: 'dummy' }.
24 | to_not change(Notifly::Notification.where(template: :create), :count) }
25 | it { expect { DummyObject.create name: 'smart' }.
26 | to change(Notifly::Notification.where(template: :create), :count).from(0).to(1) }
27 | end
28 | end
29 |
30 | describe 'instance methods' do
31 | let!(:smart) { DummyObject.create name: 'smart', email: 'smart@mail.com' }
32 | let!(:dummy) { DummyObject.create name: nil, email: 'dummy@mail.com' }
33 |
34 | before(:each) do
35 | notifications.delete_all
36 | end
37 |
38 | describe '#save' do
39 | it { expect { smart.save! }.to change(notifications.
40 | where(template: :save), :count).from(0).to(1) }
41 | it { expect { dummy.save! }.to_not change(notifications.
42 | where(template: :save), :count) }
43 | end
44 |
45 | describe '#update' do
46 | it { expect { dummy.update!(name: 'smart') }.to change(notifications.
47 | where(template: :update), :count).from(0).to(1) }
48 | it { expect { smart.update!(name: 'dummy') }.to_not change(notifications.
49 | where(template: :update), :count) }
50 | end
51 |
52 | describe '#destroy' do
53 | it { expect { smart.destroy! }.to change(notifications, :count).
54 | from(0).to(1) }
55 | it { expect { dummy.destroy! }.to_not change(notifications, :count) }
56 | end
57 | end
58 | end
59 |
60 | context 'when notifly send email' do
61 | let!(:smart) { DummyObject.create name: 'smart', email: 'smart@mail.com' }
62 | let!(:dummy) { DummyObject.create name: nil, email: 'dummy@mail.com' }
63 | let(:notifications) { Notifly::Notification }
64 |
65 | before(:each) do
66 | notifications.delete_all
67 | emails_sent.clear
68 | end
69 |
70 | it 'should create a visible notification with email' do
71 | expect { smart.destroy! }.to change(emails_sent, :size).from(0).to(1)
72 |
73 | expect(notifications.count).to eql(1)
74 | expect(smart.notifly_notifications.not_only_mail).to include notifications.take
75 | end
76 |
77 | it 'should create an invisible notification with email' do
78 | expect { dummy.be_smart }.to change(emails_sent, :size).from(0).to(1)
79 |
80 | expect(notifications.count).to eql(1)
81 | expect(dummy.notifly_notifications.not_only_mail).to_not include notifications.take
82 | end
83 | end
84 |
85 | context 'when using kind' do
86 | let!(:dummy) { DummyObject.create name: nil, email: 'dummy@mail.com' }
87 | let(:notifications) { Notifly::Notification }
88 |
89 | before(:each) do
90 | notifications.delete_all
91 | emails_sent.clear
92 | end
93 |
94 | describe '#notifly_notifications' do
95 | it 'should show notifications by kind' do
96 | expect { dummy.test_kind }.to change { notifications.count }.by(2)
97 |
98 | expect(dummy.notifly_notifications.count).to eql 2
99 |
100 | expect(dummy.notifly_notifications(:message).count).to eql 1
101 | expect(dummy.notifly_notifications(:message)).
102 | to match_array notifications.where(receiver: dummy, kind: :message)
103 |
104 | expect(dummy.notifly_notifications(:feed).count).to eql 1
105 | expect(dummy.notifly_notifications(:feed)).
106 | to match_array notifications.where(receiver: dummy, kind: :feed)
107 | end
108 | end
109 | end
110 |
111 | context 'when using "then"' do
112 | let!(:dummy) { DummyObject.create name: nil, email: 'dummy@mail.com' }
113 | let(:notifications) { Notifly::Notification }
114 |
115 | before(:each) { notifications.delete_all }
116 |
117 | it 'should run the code' do
118 | expect(dummy.name).to eql nil
119 |
120 | expect { dummy.test_then }.to change { notifications.count }.by(1)
121 | expect(dummy.reload.name).to eql 'name_after_then'
122 | end
123 |
124 | it 'should run the code using the created notification' do
125 | expect(dummy.name).to eql nil
126 |
127 | expect { dummy.test_then_using_notification }.to change { notifications.count }.by(1)
128 | expect(dummy.reload.name).to eql 'blastoise'
129 | end
130 | end
131 | end
132 | end
133 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/twitter/bootstrap/dropdown.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.1 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 |
7 | /*!
8 | * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=908146e6b02e08c0f522)
9 | * Config saved to config.json and https://gist.github.com/908146e6b02e08c0f522
10 | */
11 | if (typeof jQuery === 'undefined') {
12 | throw new Error('Bootstrap\'s JavaScript requires jQuery')
13 | }
14 | +function ($) {
15 | var version = $.fn.jquery.split(' ')[0].split('.')
16 | if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {
17 | throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher')
18 | }
19 | }(jQuery);
20 |
21 | /* ========================================================================
22 | * Bootstrap: dropdown.js v3.3.1
23 | * http://getbootstrap.com/javascript/#dropdowns
24 | * ========================================================================
25 | * Copyright 2011-2014 Twitter, Inc.
26 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
27 | * ======================================================================== */
28 |
29 |
30 | +function ($) {
31 | 'use strict';
32 |
33 | // DROPDOWN CLASS DEFINITION
34 | // =========================
35 |
36 | var backdrop = '.dropdown-backdrop'
37 | var toggle = '[data-toggle="dropdown"]'
38 | var Dropdown = function (element) {
39 | $(element).on('click.bs.dropdown', this.toggle)
40 | }
41 |
42 | Dropdown.VERSION = '3.3.1'
43 |
44 | Dropdown.prototype.toggle = function (e) {
45 | var $this = $(this)
46 |
47 | if ($this.is('.disabled, :disabled')) return
48 |
49 | var $parent = getParent($this)
50 | var isActive = $parent.hasClass('open')
51 |
52 | clearMenus()
53 |
54 | if (!isActive) {
55 | if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
56 | // if mobile we use a backdrop because click events don't delegate
57 | $('').insertAfter($(this)).on('click', clearMenus)
58 | }
59 |
60 | var relatedTarget = { relatedTarget: this }
61 | $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
62 |
63 | if (e.isDefaultPrevented()) return
64 |
65 | $this
66 | .trigger('focus')
67 | .attr('aria-expanded', 'true')
68 |
69 | $parent
70 | .toggleClass('open')
71 | .trigger('shown.bs.dropdown', relatedTarget)
72 | }
73 |
74 | return false
75 | }
76 |
77 | Dropdown.prototype.keydown = function (e) {
78 | if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
79 |
80 | var $this = $(this)
81 |
82 | e.preventDefault()
83 | e.stopPropagation()
84 |
85 | if ($this.is('.disabled, :disabled')) return
86 |
87 | var $parent = getParent($this)
88 | var isActive = $parent.hasClass('open')
89 |
90 | if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {
91 | if (e.which == 27) $parent.find(toggle).trigger('focus')
92 | return $this.trigger('click')
93 | }
94 |
95 | var desc = ' li:not(.divider):visible a'
96 | var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc)
97 |
98 | if (!$items.length) return
99 |
100 | var index = $items.index(e.target)
101 |
102 | if (e.which == 38 && index > 0) index-- // up
103 | if (e.which == 40 && index < $items.length - 1) index++ // down
104 | if (!~index) index = 0
105 |
106 | $items.eq(index).trigger('focus')
107 | }
108 |
109 | function clearMenus(e) {
110 | if (e && e.which === 3) return
111 | $(backdrop).remove()
112 | $(toggle).each(function () {
113 | var $this = $(this)
114 | var $parent = getParent($this)
115 | var relatedTarget = { relatedTarget: this }
116 |
117 | if (!$parent.hasClass('open')) return
118 |
119 | $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
120 |
121 | if (e.isDefaultPrevented()) return
122 |
123 | $this.attr('aria-expanded', 'false')
124 | $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
125 | })
126 | }
127 |
128 | function getParent($this) {
129 | var selector = $this.attr('data-target')
130 |
131 | if (!selector) {
132 | selector = $this.attr('href')
133 | selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
134 | }
135 |
136 | var $parent = selector && $(selector)
137 |
138 | return $parent && $parent.length ? $parent : $this.parent()
139 | }
140 |
141 |
142 | // DROPDOWN PLUGIN DEFINITION
143 | // ==========================
144 |
145 | function Plugin(option) {
146 | return this.each(function () {
147 | var $this = $(this)
148 | var data = $this.data('bs.dropdown')
149 |
150 | if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
151 | if (typeof option == 'string') data[option].call($this)
152 | })
153 | }
154 |
155 | var old = $.fn.dropdown
156 |
157 | $.fn.dropdown = Plugin
158 | $.fn.dropdown.Constructor = Dropdown
159 |
160 |
161 | // DROPDOWN NO CONFLICT
162 | // ====================
163 |
164 | $.fn.dropdown.noConflict = function () {
165 | $.fn.dropdown = old
166 | return this
167 | }
168 |
169 |
170 | // APPLY TO STANDARD DROPDOWN ELEMENTS
171 | // ===================================
172 |
173 | $(document)
174 | .on('click.bs.dropdown.data-api', clearMenus)
175 | .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
176 | .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
177 | .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
178 | .on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown)
179 | .on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown)
180 |
181 | }(jQuery);
182 |
--------------------------------------------------------------------------------
/notifly.gemspec:
--------------------------------------------------------------------------------
1 | # Generated by jeweler
2 | # DO NOT EDIT THIS FILE DIRECTLY
3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4 | # -*- encoding: utf-8 -*-
5 | # stub: notifly 0.3.6 ruby lib
6 |
7 | Gem::Specification.new do |s|
8 | s.name = "notifly"
9 | s.version = "0.3.6"
10 |
11 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12 | s.require_paths = ["lib"]
13 | s.authors = ["Pedro Passalini", "Rafael Carvalho"]
14 | s.date = "2015-02-26"
15 | s.description = "This project intend to offer a full notification system, back and front-end."
16 | s.email = ["henrique.passalini@gmail.com", "rafael@algorich.com.br"]
17 | s.executables = ["rails"]
18 | s.extra_rdoc_files = [
19 | "README.md"
20 | ]
21 | s.files = [
22 | "MIT-LICENSE",
23 | "README.md",
24 | "Rakefile",
25 | "app/assets/javascripts/notifly.js.erb",
26 | "app/assets/javascripts/notifly/get_notifications.js.erb",
27 | "app/assets/javascripts/notifly/more_notifications.js.erb",
28 | "app/assets/javascripts/notifly/read_notifications.js.erb",
29 | "app/assets/javascripts/notifly/real_time.js.erb",
30 | "app/assets/javascripts/notifly/seen_notifications.js.erb",
31 | "app/assets/javascripts/notifly_dropdown.js",
32 | "app/assets/stylesheets/notifly.css",
33 | "app/assets/stylesheets/notifly/layout.css",
34 | "app/controllers/notifly/application_controller.rb",
35 | "app/controllers/notifly/notifications_controller.rb",
36 | "app/helpers/notifly/view_helper.rb",
37 | "app/mailers/notifly/notification_mailer.rb",
38 | "app/models/notifly/notification.rb",
39 | "app/views/notifly/layouts/_actions.html.erb",
40 | "app/views/notifly/layouts/_counter.html.erb",
41 | "app/views/notifly/layouts/_empty.html.erb",
42 | "app/views/notifly/layouts/_index.html.erb",
43 | "app/views/notifly/layouts/_notification.html.erb",
44 | "app/views/notifly/layouts/_notifly.html.erb",
45 | "app/views/notifly/notification_mailer/notifly.html.erb",
46 | "app/views/notifly/notifications/index.js.erb",
47 | "app/views/notifly/notifications/read.js.erb",
48 | "app/views/notifly/notifications/seen.js.erb",
49 | "app/views/notifly/notifications/toggle_read.js.erb",
50 | "app/views/notifly/templates/mails/_default.html.erb",
51 | "app/views/notifly/templates/notifications/_default.html.erb",
52 | "config/locales/en.yml",
53 | "config/routes.rb",
54 | "db/migrate/20141103170528_create_notifly_notifications.rb",
55 | "db/migrate/20141104150224_add_data_to_notification.rb",
56 | "db/migrate/20141117193436_add_seen_to_notifly_notification.rb",
57 | "db/migrate/20141125165636_add_mail_to_notifly_notifications.rb",
58 | "db/migrate/20141212122918_add_kind_to_notifly_notification.rb",
59 | "lib/generators/notifly/install/install_generator.rb",
60 | "lib/generators/notifly/install/templates/config/initializers/notifly.rb",
61 | "lib/generators/notifly/install/utils.rb",
62 | "lib/generators/notifly/views/views_generator.rb",
63 | "lib/notifly.rb",
64 | "lib/notifly/engine.rb",
65 | "lib/notifly/models/base.rb",
66 | "lib/notifly/models/flyable.rb",
67 | "lib/notifly/models/notifiable.rb",
68 | "lib/notifly/models/options/fly.rb",
69 | "lib/notifly/railtie.rb",
70 | "lib/services/action_view_helper.rb",
71 | "lib/services/notification_channel.rb",
72 | "lib/tasks/notifly_tasks.rake",
73 | "vendor/assets/javascripts/tinycon.js",
74 | "vendor/assets/javascripts/twitter/bootstrap/dropdown.js",
75 | "vendor/assets/stylesheets/twitter/bootstrap.css"
76 | ]
77 | s.homepage = "https://github.com/algorich/notifly"
78 | s.licenses = ["MIT"]
79 | s.rubygems_version = "2.2.2"
80 | s.summary = "A full notification system"
81 |
82 | if s.respond_to? :specification_version then
83 | s.specification_version = 4
84 |
85 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
86 | s.add_runtime_dependency(%q, ["~> 4"])
87 | s.add_runtime_dependency(%q, ["< 5", ">= 3.0"])
88 | s.add_runtime_dependency(%q, ["~> 4.2.0"])
89 | s.add_runtime_dependency(%q, ["~> 4.0.0"])
90 | s.add_development_dependency(%q, [">= 0"])
91 | s.add_development_dependency(%q, [">= 0"])
92 | s.add_development_dependency(%q, [">= 0"])
93 | s.add_development_dependency(%q, [">= 0"])
94 | s.add_development_dependency(%q, ["~> 0.7.0"])
95 | s.add_development_dependency(%q, [">= 0"])
96 | s.add_development_dependency(%q, [">= 0"])
97 | s.add_development_dependency(%q, [">= 0"])
98 | s.add_development_dependency(%q, [">= 0"])
99 | else
100 | s.add_dependency(%q, ["~> 4"])
101 | s.add_dependency(%q, ["< 5", ">= 3.0"])
102 | s.add_dependency(%q, ["~> 4.2.0"])
103 | s.add_dependency(%q, ["~> 4.0.0"])
104 | s.add_dependency(%q, [">= 0"])
105 | s.add_dependency(%q, [">= 0"])
106 | s.add_dependency(%q, [">= 0"])
107 | s.add_dependency(%q, [">= 0"])
108 | s.add_dependency(%q, ["~> 0.7.0"])
109 | s.add_dependency(%q, [">= 0"])
110 | s.add_dependency(%q, [">= 0"])
111 | s.add_dependency(%q, [">= 0"])
112 | s.add_dependency(%q, [">= 0"])
113 | end
114 | else
115 | s.add_dependency(%q, ["~> 4"])
116 | s.add_dependency(%q, ["< 5", ">= 3.0"])
117 | s.add_dependency(%q, ["~> 4.2.0"])
118 | s.add_dependency(%q, ["~> 4.0.0"])
119 | s.add_dependency(%q, [">= 0"])
120 | s.add_dependency(%q, [">= 0"])
121 | s.add_dependency(%q, [">= 0"])
122 | s.add_dependency(%q, [">= 0"])
123 | s.add_dependency(%q, ["~> 0.7.0"])
124 | s.add_dependency(%q, [">= 0"])
125 | s.add_dependency(%q, [">= 0"])
126 | s.add_dependency(%q, [">= 0"])
127 | s.add_dependency(%q, [">= 0"])
128 | end
129 | end
130 |
131 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actionmailer (4.1.8)
5 | actionpack (= 4.1.8)
6 | actionview (= 4.1.8)
7 | mail (~> 2.5, >= 2.5.4)
8 | actionpack (4.1.8)
9 | actionview (= 4.1.8)
10 | activesupport (= 4.1.8)
11 | rack (~> 1.5.2)
12 | rack-test (~> 0.6.2)
13 | actionview (4.1.8)
14 | activesupport (= 4.1.8)
15 | builder (~> 3.1)
16 | erubis (~> 2.7.0)
17 | activemodel (4.1.8)
18 | activesupport (= 4.1.8)
19 | builder (~> 3.1)
20 | activerecord (4.1.8)
21 | activemodel (= 4.1.8)
22 | activesupport (= 4.1.8)
23 | arel (~> 5.0.0)
24 | activesupport (4.1.8)
25 | i18n (~> 0.6, >= 0.6.9)
26 | json (~> 1.7, >= 1.7.7)
27 | minitest (~> 5.1)
28 | thread_safe (~> 0.1)
29 | tzinfo (~> 1.1)
30 | addressable (2.3.8)
31 | arel (5.0.1.20140414130214)
32 | awesome_print (1.6.1)
33 | better_errors (2.1.1)
34 | coderay (>= 1.0.0)
35 | erubis (>= 2.6.6)
36 | rack (>= 0.9.0)
37 | binding_of_caller (0.7.2)
38 | debug_inspector (>= 0.0.1)
39 | builder (3.2.2)
40 | capybara (2.4.4)
41 | mime-types (>= 1.16)
42 | nokogiri (>= 1.3.3)
43 | rack (>= 1.0.0)
44 | rack-test (>= 0.5.4)
45 | xpath (~> 2.0)
46 | cliver (0.3.2)
47 | coderay (1.1.0)
48 | coffee-rails (4.1.1)
49 | coffee-script (>= 2.2.0)
50 | railties (>= 4.0.0, < 5.1.x)
51 | coffee-script (2.4.1)
52 | coffee-script-source
53 | execjs
54 | coffee-script-source (1.10.0)
55 | daemons (1.2.2)
56 | debug_inspector (0.0.2)
57 | descendants_tracker (0.0.4)
58 | thread_safe (~> 0.3, >= 0.3.1)
59 | diff-lcs (1.2.5)
60 | em-synchrony (1.0.4)
61 | eventmachine (>= 1.0.0.beta.1)
62 | erubis (2.7.0)
63 | eventmachine (1.0.7)
64 | execjs (2.6.0)
65 | faraday (0.9.1)
66 | multipart-post (>= 1.2, < 3)
67 | faye-websocket (0.9.2)
68 | eventmachine (>= 0.12.0)
69 | websocket-driver (>= 0.5.1)
70 | font-awesome-rails (4.5.0.0)
71 | railties (>= 3.2, < 5.0)
72 | git (1.2.9.1)
73 | github_api (0.12.3)
74 | addressable (~> 2.3)
75 | descendants_tracker (~> 0.0.4)
76 | faraday (~> 0.8, < 0.10)
77 | hashie (>= 3.3)
78 | multi_json (>= 1.7.5, < 2.0)
79 | nokogiri (~> 1.6.3)
80 | oauth2
81 | hashie (3.4.1)
82 | highline (1.7.2)
83 | hiredis (0.6.0)
84 | i18n (0.7.0)
85 | interception (0.5)
86 | jeweler (2.0.1)
87 | builder
88 | bundler (>= 1.0)
89 | git (>= 1.2.5)
90 | github_api
91 | highline (>= 1.6.15)
92 | nokogiri (>= 1.5.10)
93 | rake
94 | rdoc
95 | jquery-rails (3.1.2)
96 | railties (>= 3.0, < 5.0)
97 | thor (>= 0.14, < 2.0)
98 | json (1.8.2)
99 | jwt (1.5.0)
100 | launchy (2.4.3)
101 | addressable (~> 2.3)
102 | mail (2.6.3)
103 | mime-types (>= 1.16, < 3)
104 | method_source (0.8.2)
105 | mime-types (2.6.1)
106 | mini_portile (0.6.2)
107 | minitest (5.7.0)
108 | multi_json (1.11.0)
109 | multi_xml (0.5.5)
110 | multipart-post (2.0.0)
111 | nokogiri (1.6.6.2)
112 | mini_portile (~> 0.6.0)
113 | oauth2 (1.0.0)
114 | faraday (>= 0.8, < 0.10)
115 | jwt (~> 1.0)
116 | multi_json (~> 1.3)
117 | multi_xml (~> 0.5)
118 | rack (~> 1.2)
119 | pg (0.18.2)
120 | phantomjs (1.9.8.0)
121 | poltergeist (1.5.1)
122 | capybara (~> 2.1)
123 | cliver (~> 0.3.1)
124 | multi_json (~> 1.0)
125 | websocket-driver (>= 0.2.0)
126 | pry (0.10.1)
127 | coderay (~> 1.1.0)
128 | method_source (~> 0.8.1)
129 | slop (~> 3.4)
130 | pry-rails (0.3.4)
131 | pry (>= 0.9.10)
132 | pry-rescue (1.4.2)
133 | interception (>= 0.5)
134 | pry
135 | rack (1.5.3)
136 | rack-test (0.6.3)
137 | rack (>= 1.0)
138 | rails (4.1.8)
139 | actionmailer (= 4.1.8)
140 | actionpack (= 4.1.8)
141 | actionview (= 4.1.8)
142 | activemodel (= 4.1.8)
143 | activerecord (= 4.1.8)
144 | activesupport (= 4.1.8)
145 | bundler (>= 1.3.0, < 2.0)
146 | railties (= 4.1.8)
147 | sprockets-rails (~> 2.0)
148 | railties (4.1.8)
149 | actionpack (= 4.1.8)
150 | activesupport (= 4.1.8)
151 | rake (>= 0.8.7)
152 | thor (>= 0.18.1, < 2.0)
153 | rake (10.4.2)
154 | rdoc (4.2.0)
155 | redis (3.2.1)
156 | redis-objects (1.2.0)
157 | redis (>= 3.0.2)
158 | rspec-core (3.1.7)
159 | rspec-support (~> 3.1.0)
160 | rspec-expectations (3.1.2)
161 | diff-lcs (>= 1.2.0, < 2.0)
162 | rspec-support (~> 3.1.0)
163 | rspec-mocks (3.1.3)
164 | rspec-support (~> 3.1.0)
165 | rspec-rails (3.1.0)
166 | actionpack (>= 3.0)
167 | activesupport (>= 3.0)
168 | railties (>= 3.0)
169 | rspec-core (~> 3.1.0)
170 | rspec-expectations (~> 3.1.0)
171 | rspec-mocks (~> 3.1.0)
172 | rspec-support (~> 3.1.0)
173 | rspec-support (3.1.2)
174 | shoulda-matchers (2.7.0)
175 | activesupport (>= 3.0.0)
176 | slop (3.6.0)
177 | sprockets (3.1.0)
178 | rack (~> 1.0)
179 | sprockets-rails (2.3.1)
180 | actionpack (>= 3.0)
181 | activesupport (>= 3.0)
182 | sprockets (>= 2.8, < 4.0)
183 | thin (1.6.3)
184 | daemons (~> 1.0, >= 1.0.9)
185 | eventmachine (~> 1.0)
186 | rack (~> 1.0)
187 | thor (0.19.1)
188 | thread_safe (0.3.5)
189 | tzinfo (1.2.2)
190 | thread_safe (~> 0.1)
191 | websocket-driver (0.5.4)
192 | websocket-extensions (>= 0.1.0)
193 | websocket-extensions (0.1.2)
194 | websocket-rails (0.7.0)
195 | em-synchrony
196 | faye-websocket
197 | hiredis
198 | rack
199 | rails
200 | redis
201 | redis-objects
202 | thin
203 | xpath (2.0.0)
204 | nokogiri (~> 1.3)
205 |
206 | PLATFORMS
207 | ruby
208 |
209 | DEPENDENCIES
210 | awesome_print (= 1.6.1)
211 | better_errors (= 2.1.1)
212 | binding_of_caller (= 0.7.2)
213 | capybara (= 2.4.4)
214 | coffee-rails (= 4.1.1)
215 | font-awesome-rails (= 4.5.0)
216 | jeweler (= 2.0.1)
217 | jquery-rails (>= 3.0, < 5)
218 | launchy (= 2.4.3)
219 | pg (= 0.18.2)
220 | phantomjs (= 1.9.8.0)
221 | poltergeist (= 1.5.1)
222 | pry-rails (= 0.3.4)
223 | pry-rescue (= 1.4.2)
224 | rails (= 4.1.8)
225 | rspec-rails (= 3.1.0)
226 | shoulda-matchers (= 2.7.0)
227 | thin (= 1.6.3)
228 | websocket-rails (= 0.7.0)
229 |
230 | BUNDLED WITH
231 | 1.10.3
232 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/tinycon.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Tinycon - A small library for manipulating the Favicon
3 | * Tom Moor, http://tommoor.com
4 | * Copyright (c) 2012 Tom Moor
5 | * @license MIT Licensed
6 | * @version 0.6.3
7 | */
8 |
9 | (function(){
10 |
11 | var Tinycon = {};
12 | var currentFavicon = null;
13 | var originalFavicon = null;
14 | var faviconImage = null;
15 | var canvas = null;
16 | var options = {};
17 | var r = window.devicePixelRatio || 1;
18 | var size = 16 * r;
19 | var defaults = {
20 | width: 7,
21 | height: 9,
22 | font: 10 * r + 'px arial',
23 | colour: '#ffffff',
24 | background: '#F03D25',
25 | fallback: true,
26 | crossOrigin: true,
27 | abbreviate: true
28 | };
29 |
30 | var ua = (function () {
31 | var agent = navigator.userAgent.toLowerCase();
32 | // New function has access to 'agent' via closure
33 | return function (browser) {
34 | return agent.indexOf(browser) !== -1;
35 | };
36 | }());
37 |
38 | var browser = {
39 | ie: ua('msie'),
40 | chrome: ua('chrome'),
41 | webkit: ua('chrome') || ua('safari'),
42 | safari: ua('safari') && !ua('chrome'),
43 | mozilla: ua('mozilla') && !ua('chrome') && !ua('safari')
44 | };
45 |
46 | // private methods
47 | var getFaviconTag = function(){
48 |
49 | var links = document.getElementsByTagName('link');
50 |
51 | for(var i=0, len=links.length; i < len; i++) {
52 | if ((links[i].getAttribute('rel') || '').match(/\bicon\b/)) {
53 | return links[i];
54 | }
55 | }
56 |
57 | return false;
58 | };
59 |
60 | var removeFaviconTag = function(){
61 |
62 | var links = document.getElementsByTagName('link');
63 | var head = document.getElementsByTagName('head')[0];
64 |
65 | for(var i=0, len=links.length; i < len; i++) {
66 | var exists = (typeof(links[i]) !== 'undefined');
67 | if (exists && (links[i].getAttribute('rel') || '').match(/\bicon\b/)) {
68 | head.removeChild(links[i]);
69 | }
70 | }
71 | };
72 |
73 | var getCurrentFavicon = function(){
74 |
75 | if (!originalFavicon || !currentFavicon) {
76 | var tag = getFaviconTag();
77 | originalFavicon = currentFavicon = tag ? tag.getAttribute('href') : '/favicon.ico';
78 | }
79 |
80 | return currentFavicon;
81 | };
82 |
83 | var getCanvas = function (){
84 |
85 | if (!canvas) {
86 | canvas = document.createElement("canvas");
87 | canvas.width = size;
88 | canvas.height = size;
89 | }
90 |
91 | return canvas;
92 | };
93 |
94 | var setFaviconTag = function(url){
95 | removeFaviconTag();
96 |
97 | var link = document.createElement('link');
98 | link.type = 'image/x-icon';
99 | link.rel = 'icon';
100 | link.href = url;
101 | document.getElementsByTagName('head')[0].appendChild(link);
102 | };
103 |
104 | var log = function(message){
105 | if (window.console) window.console.log(message);
106 | };
107 |
108 | var drawFavicon = function(label, colour) {
109 |
110 | // fallback to updating the browser title if unsupported
111 | if (!getCanvas().getContext || browser.ie || browser.safari || options.fallback === 'force') {
112 | return updateTitle(label);
113 | }
114 |
115 | var context = getCanvas().getContext("2d");
116 | var colour = colour || '#000000';
117 | var src = getCurrentFavicon();
118 |
119 | faviconImage = document.createElement('img');
120 | faviconImage.onload = function() {
121 |
122 | // clear canvas
123 | context.clearRect(0, 0, size, size);
124 |
125 | // draw the favicon
126 | context.drawImage(faviconImage, 0, 0, faviconImage.width, faviconImage.height, 0, 0, size, size);
127 |
128 | // draw bubble over the top
129 | if ((label + '').length > 0) drawBubble(context, label, colour);
130 |
131 | // refresh tag in page
132 | refreshFavicon();
133 | };
134 |
135 | // allow cross origin resource requests if the image is not a data:uri
136 | // as detailed here: https://github.com/mrdoob/three.js/issues/1305
137 | if (!src.match(/^data/) && options.crossOrigin) {
138 | faviconImage.crossOrigin = 'anonymous';
139 | }
140 |
141 | faviconImage.src = src;
142 | };
143 |
144 | var updateTitle = function(label) {
145 |
146 | if (options.fallback) {
147 | // Grab the current title that we can prefix with the label
148 | var originalTitle = document.title;
149 |
150 | // Strip out the old label if there is one
151 | if (originalTitle[0] === '(') {
152 | originalTitle = originalTitle.slice(originalTitle.indexOf(' '));
153 | }
154 |
155 | if ((label + '').length > 0) {
156 | document.title = '(' + label + ') ' + originalTitle;
157 | } else {
158 | document.title = originalTitle;
159 | }
160 | }
161 | };
162 |
163 | var drawBubble = function(context, label, colour) {
164 |
165 | // automatic abbreviation for long (>2 digits) numbers
166 | if (typeof label == 'number' && label > 99 && options.abbreviate) {
167 | label = abbreviateNumber(label);
168 | }
169 |
170 | // bubble needs to be larger for double digits
171 | var len = (label + '').length-1;
172 |
173 | var width = options.width * r + (6 * r * len),
174 | height = options.height * r;
175 |
176 | var top = size - height,
177 | left = size - width - r,
178 | bottom = 16 * r,
179 | right = 16 * r,
180 | radius = 2 * r;
181 |
182 | // webkit seems to render fonts lighter than firefox
183 | context.font = (browser.webkit ? 'bold ' : '') + options.font;
184 | context.fillStyle = options.background;
185 | context.strokeStyle = options.background;
186 | context.lineWidth = r;
187 |
188 | // bubble
189 | context.beginPath();
190 | context.moveTo(left + radius, top);
191 | context.quadraticCurveTo(left, top, left, top + radius);
192 | context.lineTo(left, bottom - radius);
193 | context.quadraticCurveTo(left, bottom, left + radius, bottom);
194 | context.lineTo(right - radius, bottom);
195 | context.quadraticCurveTo(right, bottom, right, bottom - radius);
196 | context.lineTo(right, top + radius);
197 | context.quadraticCurveTo(right, top, right - radius, top);
198 | context.closePath();
199 | context.fill();
200 |
201 | // bottom shadow
202 | context.beginPath();
203 | context.strokeStyle = "rgba(0,0,0,0.3)";
204 | context.moveTo(left + radius / 2.0, bottom);
205 | context.lineTo(right - radius / 2.0, bottom);
206 | context.stroke();
207 |
208 | // label
209 | context.fillStyle = options.colour;
210 | context.textAlign = "right";
211 | context.textBaseline = "top";
212 |
213 | // unfortunately webkit/mozilla are a pixel different in text positioning
214 | context.fillText(label, r === 2 ? 29 : 15, browser.mozilla ? 7*r : 6*r);
215 | };
216 |
217 | var refreshFavicon = function(){
218 | // check support
219 | if (!getCanvas().getContext) return;
220 |
221 | setFaviconTag(getCanvas().toDataURL());
222 | };
223 |
224 | var abbreviateNumber = function(label) {
225 | var metricPrefixes = [
226 | ['G', 1000000000],
227 | ['M', 1000000],
228 | ['k', 1000]
229 | ];
230 |
231 | for(var i = 0; i < metricPrefixes.length; ++i) {
232 | if (label >= metricPrefixes[i][1]) {
233 | label = round(label / metricPrefixes[i][1]) + metricPrefixes[i][0];
234 | break;
235 | }
236 | }
237 |
238 | return label;
239 | };
240 |
241 | var round = function (value, precision) {
242 | var number = new Number(value);
243 | return number.toFixed(precision);
244 | };
245 |
246 | // public methods
247 | Tinycon.setOptions = function(custom){
248 | options = {};
249 |
250 | for(var key in defaults){
251 | options[key] = custom.hasOwnProperty(key) ? custom[key] : defaults[key];
252 | }
253 | return this;
254 | };
255 |
256 | Tinycon.setImage = function(url){
257 | currentFavicon = url;
258 | refreshFavicon();
259 | return this;
260 | };
261 |
262 | Tinycon.setBubble = function(label, colour) {
263 | label = label || '';
264 | drawFavicon(label, colour);
265 | return this;
266 | };
267 |
268 | Tinycon.reset = function(){
269 | setFaviconTag(originalFavicon);
270 | };
271 |
272 | Tinycon.setOptions(defaults);
273 | window.Tinycon = Tinycon;
274 |
275 | if(typeof define === 'function' && define.amd) {
276 | define(Tinycon);
277 | }
278 |
279 | })();
280 |
--------------------------------------------------------------------------------
/vendor/assets/stylesheets/twitter/bootstrap.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.1 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 |
7 | /*!
8 | * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=908146e6b02e08c0f522)
9 | * Config saved to config.json and https://gist.github.com/908146e6b02e08c0f522
10 | */
11 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */
12 | html {
13 | font-family: sans-serif;
14 | -ms-text-size-adjust: 100%;
15 | -webkit-text-size-adjust: 100%;
16 | }
17 | body {
18 | margin: 0;
19 | }
20 | article,
21 | aside,
22 | details,
23 | figcaption,
24 | figure,
25 | footer,
26 | header,
27 | hgroup,
28 | main,
29 | menu,
30 | nav,
31 | section,
32 | summary {
33 | display: block;
34 | }
35 | audio,
36 | canvas,
37 | progress,
38 | video {
39 | display: inline-block;
40 | vertical-align: baseline;
41 | }
42 | audio:not([controls]) {
43 | display: none;
44 | height: 0;
45 | }
46 | [hidden],
47 | template {
48 | display: none;
49 | }
50 | a {
51 | background-color: transparent;
52 | }
53 | a:active,
54 | a:hover {
55 | outline: 0;
56 | }
57 | abbr[title] {
58 | border-bottom: 1px dotted;
59 | }
60 | b,
61 | strong {
62 | font-weight: bold;
63 | }
64 | dfn {
65 | font-style: italic;
66 | }
67 | h1 {
68 | font-size: 2em;
69 | margin: 0.67em 0;
70 | }
71 | mark {
72 | background: #ff0;
73 | color: #000;
74 | }
75 | small {
76 | font-size: 80%;
77 | }
78 | sub,
79 | sup {
80 | font-size: 75%;
81 | line-height: 0;
82 | position: relative;
83 | vertical-align: baseline;
84 | }
85 | sup {
86 | top: -0.5em;
87 | }
88 | sub {
89 | bottom: -0.25em;
90 | }
91 | img {
92 | border: 0;
93 | }
94 | svg:not(:root) {
95 | overflow: hidden;
96 | }
97 | figure {
98 | margin: 1em 40px;
99 | }
100 | hr {
101 | -moz-box-sizing: content-box;
102 | -webkit-box-sizing: content-box;
103 | box-sizing: content-box;
104 | height: 0;
105 | }
106 | pre {
107 | overflow: auto;
108 | }
109 | code,
110 | kbd,
111 | pre,
112 | samp {
113 | font-family: monospace, monospace;
114 | font-size: 1em;
115 | }
116 | button,
117 | input,
118 | optgroup,
119 | select,
120 | textarea {
121 | color: inherit;
122 | font: inherit;
123 | margin: 0;
124 | }
125 | button {
126 | overflow: visible;
127 | }
128 | button,
129 | select {
130 | text-transform: none;
131 | }
132 | button,
133 | html input[type="button"],
134 | input[type="reset"],
135 | input[type="submit"] {
136 | -webkit-appearance: button;
137 | cursor: pointer;
138 | }
139 | button[disabled],
140 | html input[disabled] {
141 | cursor: default;
142 | }
143 | button::-moz-focus-inner,
144 | input::-moz-focus-inner {
145 | border: 0;
146 | padding: 0;
147 | }
148 | input {
149 | line-height: normal;
150 | }
151 | input[type="checkbox"],
152 | input[type="radio"] {
153 | -webkit-box-sizing: border-box;
154 | -moz-box-sizing: border-box;
155 | box-sizing: border-box;
156 | padding: 0;
157 | }
158 | input[type="number"]::-webkit-inner-spin-button,
159 | input[type="number"]::-webkit-outer-spin-button {
160 | height: auto;
161 | }
162 | input[type="search"] {
163 | -webkit-appearance: textfield;
164 | -moz-box-sizing: content-box;
165 | -webkit-box-sizing: content-box;
166 | box-sizing: content-box;
167 | }
168 | input[type="search"]::-webkit-search-cancel-button,
169 | input[type="search"]::-webkit-search-decoration {
170 | -webkit-appearance: none;
171 | }
172 | fieldset {
173 | border: 1px solid #c0c0c0;
174 | margin: 0 2px;
175 | padding: 0.35em 0.625em 0.75em;
176 | }
177 | legend {
178 | border: 0;
179 | padding: 0;
180 | }
181 | textarea {
182 | overflow: auto;
183 | }
184 | optgroup {
185 | font-weight: bold;
186 | }
187 | table {
188 | border-collapse: collapse;
189 | border-spacing: 0;
190 | }
191 | td,
192 | th {
193 | padding: 0;
194 | }
195 | * {
196 | -webkit-box-sizing: border-box;
197 | -moz-box-sizing: border-box;
198 | box-sizing: border-box;
199 | }
200 | *:before,
201 | *:after {
202 | -webkit-box-sizing: border-box;
203 | -moz-box-sizing: border-box;
204 | box-sizing: border-box;
205 | }
206 | html {
207 | font-size: 10px;
208 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
209 | }
210 | body {
211 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
212 | font-size: 14px;
213 | line-height: 1.42857143;
214 | color: #333333;
215 | background-color: #ffffff;
216 | }
217 | input,
218 | button,
219 | select,
220 | textarea {
221 | font-family: inherit;
222 | font-size: inherit;
223 | line-height: inherit;
224 | }
225 | a {
226 | color: #337ab7;
227 | text-decoration: none;
228 | }
229 | a:hover,
230 | a:focus {
231 | color: #23527c;
232 | text-decoration: underline;
233 | }
234 | a:focus {
235 | outline: thin dotted;
236 | outline: 5px auto -webkit-focus-ring-color;
237 | outline-offset: -2px;
238 | }
239 | figure {
240 | margin: 0;
241 | }
242 | img {
243 | vertical-align: middle;
244 | }
245 | .img-responsive {
246 | display: block;
247 | max-width: 100%;
248 | height: auto;
249 | }
250 | .img-rounded {
251 | border-radius: 6px;
252 | }
253 | .img-thumbnail {
254 | padding: 4px;
255 | line-height: 1.42857143;
256 | background-color: #ffffff;
257 | border: 1px solid #dddddd;
258 | border-radius: 4px;
259 | -webkit-transition: all 0.2s ease-in-out;
260 | -o-transition: all 0.2s ease-in-out;
261 | transition: all 0.2s ease-in-out;
262 | display: inline-block;
263 | max-width: 100%;
264 | height: auto;
265 | }
266 | .img-circle {
267 | border-radius: 50%;
268 | }
269 | hr {
270 | margin-top: 20px;
271 | margin-bottom: 20px;
272 | border: 0;
273 | border-top: 1px solid #eeeeee;
274 | }
275 | .sr-only {
276 | position: absolute;
277 | width: 1px;
278 | height: 1px;
279 | margin: -1px;
280 | padding: 0;
281 | overflow: hidden;
282 | clip: rect(0, 0, 0, 0);
283 | border: 0;
284 | }
285 | .sr-only-focusable:active,
286 | .sr-only-focusable:focus {
287 | position: static;
288 | width: auto;
289 | height: auto;
290 | margin: 0;
291 | overflow: visible;
292 | clip: auto;
293 | }
294 | .caret {
295 | display: inline-block;
296 | width: 0;
297 | height: 0;
298 | margin-left: 2px;
299 | vertical-align: middle;
300 | border-top: 4px solid;
301 | border-right: 4px solid transparent;
302 | border-left: 4px solid transparent;
303 | }
304 | .dropdown {
305 | position: relative;
306 | }
307 | .dropdown-toggle:focus {
308 | outline: 0;
309 | }
310 | .dropdown-menu {
311 | position: absolute;
312 | top: 100%;
313 | left: 0;
314 | z-index: 1000;
315 | display: none;
316 | float: left;
317 | min-width: 160px;
318 | padding: 5px 0;
319 | margin: 2px 0 0;
320 | list-style: none;
321 | font-size: 14px;
322 | text-align: left;
323 | background-color: #ffffff;
324 | border: 1px solid #cccccc;
325 | border: 1px solid rgba(0, 0, 0, 0.15);
326 | border-radius: 4px;
327 | -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
328 | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
329 | -webkit-background-clip: padding-box;
330 | background-clip: padding-box;
331 | }
332 | .dropdown-menu.pull-right {
333 | right: 0;
334 | left: auto;
335 | }
336 | .dropdown-menu .divider {
337 | height: 1px;
338 | margin: 9px 0;
339 | overflow: hidden;
340 | background-color: #e5e5e5;
341 | }
342 | .dropdown-menu > li > a {
343 | display: block;
344 | padding: 3px 20px;
345 | clear: both;
346 | font-weight: normal;
347 | line-height: 1.42857143;
348 | color: #333333;
349 | white-space: nowrap;
350 | }
351 | .dropdown-menu > li > a:hover,
352 | .dropdown-menu > li > a:focus {
353 | text-decoration: none;
354 | color: #262626;
355 | background-color: #f5f5f5;
356 | }
357 | .dropdown-menu > .active > a,
358 | .dropdown-menu > .active > a:hover,
359 | .dropdown-menu > .active > a:focus {
360 | color: #ffffff;
361 | text-decoration: none;
362 | outline: 0;
363 | background-color: #337ab7;
364 | }
365 | .dropdown-menu > .disabled > a,
366 | .dropdown-menu > .disabled > a:hover,
367 | .dropdown-menu > .disabled > a:focus {
368 | color: #777777;
369 | }
370 | .dropdown-menu > .disabled > a:hover,
371 | .dropdown-menu > .disabled > a:focus {
372 | text-decoration: none;
373 | background-color: transparent;
374 | background-image: none;
375 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
376 | cursor: not-allowed;
377 | }
378 | .open > .dropdown-menu {
379 | display: block;
380 | }
381 | .open > a {
382 | outline: 0;
383 | }
384 | .dropdown-menu-right {
385 | left: auto;
386 | right: 0;
387 | }
388 | .dropdown-menu-left {
389 | left: 0;
390 | right: auto;
391 | }
392 | .dropdown-header {
393 | display: block;
394 | padding: 3px 20px;
395 | font-size: 12px;
396 | line-height: 1.42857143;
397 | color: #777777;
398 | white-space: nowrap;
399 | }
400 | .dropdown-backdrop {
401 | position: fixed;
402 | left: 0;
403 | right: 0;
404 | bottom: 0;
405 | top: 0;
406 | z-index: 990;
407 | }
408 | .pull-right > .dropdown-menu {
409 | right: 0;
410 | left: auto;
411 | }
412 | .dropup .caret,
413 | .navbar-fixed-bottom .dropdown .caret {
414 | border-top: 0;
415 | border-bottom: 4px solid;
416 | content: "";
417 | }
418 | .dropup .dropdown-menu,
419 | .navbar-fixed-bottom .dropdown .dropdown-menu {
420 | top: auto;
421 | bottom: 100%;
422 | margin-bottom: 1px;
423 | }
424 | @media (min-width: 768px) {
425 | .navbar-right .dropdown-menu {
426 | left: auto;
427 | right: 0;
428 | }
429 | .navbar-right .dropdown-menu-left {
430 | left: 0;
431 | right: auto;
432 | }
433 | }
434 | .clearfix:before,
435 | .clearfix:after {
436 | content: " ";
437 | display: table;
438 | }
439 | .clearfix:after {
440 | clear: both;
441 | }
442 | .center-block {
443 | display: block;
444 | margin-left: auto;
445 | margin-right: auto;
446 | }
447 | .pull-right {
448 | float: right !important;
449 | }
450 | .pull-left {
451 | float: left !important;
452 | }
453 | .hide {
454 | display: none !important;
455 | }
456 | .show {
457 | display: block !important;
458 | }
459 | .invisible {
460 | visibility: hidden;
461 | }
462 | .text-hide {
463 | font: 0/0 a;
464 | color: transparent;
465 | text-shadow: none;
466 | background-color: transparent;
467 | border: 0;
468 | }
469 | .hidden {
470 | display: none !important;
471 | visibility: hidden !important;
472 | }
473 | .affix {
474 | position: fixed;
475 | }
476 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Notifly
2 | [](https://travis-ci.org/algorich/notifly) [](http://badge.fury.io/rb/notifly) [](https://gemnasium.com/algorich/notifly)
3 |
4 | This project is still under development and it intend to offer a full notification
5 | system, back and front-end. Questions and suggestions are welcome and you can
6 | use the [issues list](https://github.com/algorich/notifly/issues) on Github to
7 | provide that feedback.
8 |
9 | In actual version, notifications are composed by:
10 |
11 | - Receiver (**required**): the object that will receive the notification
12 | - Sender: who sent the notification
13 | - Target: object that you will be the subject between the sender and receiver
14 | - Data: hash where you can store more info about the notification
15 | - Template (**required**): html template used by notification
16 | - Read: flag that records if the receiver read the notification
17 | - Seen: flag that records if the receiver seen the notification
18 | - If and Unless: used to create notifications conditionally
19 | - Kind: an attribute to scope notifications
20 |
21 |
22 | # Install
23 |
24 | First we need to add the gem to `Gemfile`
25 |
26 | ```ruby
27 | gem 'notifly'
28 | ```
29 |
30 | Run the bundle command to install it. After that, you need to run the initializer
31 |
32 | ```shell
33 | $ rails generate notifly:install
34 | ```
35 |
36 | You can choose to change the namespace for Notifly routes, but the default is `notifly`.
37 | It will creates `config/initializers/notifly.rb` too. Also, in this file you can
38 | see/change the default configs
39 |
40 | Notifly **need** to storage the notifications and to do it you need to run the migrations
41 |
42 | ```shell
43 | $ rake notifly:install:migrations
44 | $ rake db:migrate
45 | ```
46 |
47 | # Usage
48 |
49 | ## Back-end
50 |
51 | We have two ways to create notifications:
52 |
53 | ### Using `#notifly` method in your classes (as callback)
54 |
55 | If you want to create notifications after (or before) **any** method call.
56 |
57 | ```ruby
58 | class TicketOrder < ActiveRecord::Base
59 | belongs_to :ticket
60 | belongs_to :buyer
61 | belongs_to :owner
62 |
63 | notifly default_values: { receiver: :owner }
64 |
65 | notifly before: :destroy, template: :destroy, sender: :buyer, data: :attributes
66 | notifly after: :send_gift!, template: :ticket_gift, sender: :buyer,
67 | target: :ticket, if: -> { completed? }
68 | notifly after: :accept_gift, sender: -> { self.owner }, receiver: :buyer, target: :ticket,
69 | then: ->(notification) { self.send_mail_with(notification) }
70 |
71 | def send_gift!
72 | # code here
73 | end
74 |
75 | def accept_gift
76 | # code here
77 | end
78 |
79 | def send_mail_with(notification)
80 | # code here
81 | end
82 | end
83 | ```
84 | Value explanation about each parameter:
85 |
86 | | Parameter | Value |
87 | | ------------------- | ------------- |
88 | | `before` or `after` | The method which will create notification before or after its call |
89 | | `receiver` | The method which returns the notification receiver object |
90 | | `sender` | The method which returns the notification sender object |
91 | | `template` | The symbol or string that indicates which partial will be rendered at views. The partial must be inside `app/views/notifly/templates/`. Default is `:default`. |
92 | | `target` | The method which returns the notification target object. It's a third actor of the notification. Example: In "Max sent you a ticket" notification, Max is the sender, you are the receiver and the **ticket is the target**. |
93 | | `data` | A method which returns a hash with usefull values to be persisted, like ticket price or whatever you want to persist. |
94 | | `kind` | String used to scope notifications, default is `:notification` and all notifications with default type will be shown in `current_user`'s notifications
95 | | `then` | Callback that will be executed **after** the notification creation. It can receive a notification as parameter. Right now it only works in the code above.
96 |
97 | Note that you can use the `default_values` parameter, it is specific to DRY your
98 | notiflies and set the values to all notiflies. If you need to overwrite some
99 | default value, just declare it again like the `:accept_gift` notifly above.
100 |
101 |
102 | ### Using `#notifly!` method on your receiver object
103 |
104 | If you need to create notifications without callbacks, even in the
105 | controller scope.
106 |
107 | ```ruby
108 | class TicketOrder < ActiveRecord::Base
109 | belongs_to :ticket
110 | belongs_to :buyer
111 | belongs_to :owner
112 |
113 | before_destroy do
114 | owner.notifly! template: :destroy, sender: buyer, data: attributes
115 | end
116 |
117 | def send_gift!
118 | # code here
119 |
120 | if completed?
121 | owner.notifly! template: :ticket_gift, sender: buyer, target: ticket
122 | end
123 | end
124 |
125 | def accept_gift
126 | # code here
127 |
128 | buyer.notifly! sender: owner, target: ticket
129 | end
130 | end
131 | ```
132 |
133 | The receiver will be always the object which you call `#notifly!`
134 |
135 | ### Mail
136 |
137 | Notifly can send mails too. To do it, just add the option `mail` to your notifly
138 | statement
139 |
140 | ```ruby
141 | class TicketOrder < ActiveRecord::Base
142 | belongs_to :ticket
143 | belongs_to :buyer
144 | belongs_to :owner
145 |
146 | notifly default_values: { receiver: :owner }
147 |
148 | notifly before: :destroy, template: :destroy_order_notification, sender: :buyer,
149 | data: :attributes, mail: { template: :destroy_order_mail }
150 | notifly after: :send_gift!, template: :ticket_gift, sender: :buyer,
151 | target: :ticket, mail: true, if: -> { completed? }
152 | notifly after: :accept_gift, sender: :owner, receiver: :buyer, target: :ticket,
153 | template: :accept_gift, mail: { only: true }
154 |
155 | def send_gift!
156 | # code here
157 | end
158 |
159 | def accept_gift
160 | # code here
161 | end
162 | end
163 | ```
164 |
165 | | Email | Description |
166 | | ---------------------------- | ----------- |
167 | | `true` | send email and notification using notifly template |
168 | | `only: true` | send only an email using notifly template |
169 | | `template: :foo` | send email using `foo` mail template and a notification using notifly template |
170 |
171 | Notiflies with `mail: { only: true }` will persist notifications, but them won't
172 | be in receivers notifications views. If you use
173 | [delayed_job](https://github.com/collectiveidea/delayed_job)
174 | or [sidekiq](https://github.com/mperham/sidekiq) mails will be sent async.
175 |
176 | ### Notifications access
177 |
178 | You can access the notifications using the following methods:
179 |
180 | - `receiver_object.notifly_notifications`
181 | - Querying `Notifly::Notifications`
182 | - Using our front-end helpers
183 |
184 | #### Useful scopes
185 |
186 | - all_from: used on `Notifly::Notifications` to show notifications from a specific receiver
187 | - unseen: used on `Notifly::Notifications` and `#notifly_notifications` to show **only** unseen notifications
188 | - not_only-mail: used on `Notifly::Notifications` and `#notifly_notifications` to remove notification that are **mail only**
189 |
190 | ### Websocket
191 |
192 | If you want to use websocket just install the gem [websocket-rails](https://github.com/websocket-rails/websocket-rails/wiki/Installation-and-Setup) and change the notifly's configuration
193 | at `config/initializers/notifly.rb`
194 |
195 | ## Front-end
196 |
197 | First, you need to have a `current_user`, if you use
198 | [Devise](https://github.com/plataformatec/devise) maybe it is already there. If you
199 | haven't a `current_user`, just define a method in `ApplicationController` and
200 | add it to the helpers methods. Your controller should look like this:
201 |
202 | ```ruby
203 | class ApplicationController < ActionController::Base
204 | def current_user
205 | current_talker
206 | end
207 |
208 | ActiveSupport.on_load(:action_controller) do
209 | helper_method :current_user
210 | end
211 | end
212 | ```
213 |
214 | After that you need our assets, add them to your `application.js` and `application.css`.
215 |
216 | ```javascript
217 | //= require notifly
218 | ```
219 |
220 | The `notifly` contain the code to do all requests and notifications injection, if
221 | you do not use [Twitter bootstrap](http://getbootstrap.com/) you will need
222 | to add `//= notifly_dropdown` to the code above.
223 |
224 | ```css
225 | /*
226 | *= require notifly
227 | */
228 | ```
229 |
230 | Now finally you can see the notifications view adding code bellow to your view
231 |
232 | ```html
233 | <%= notiflies %>
234 | ```
235 |
236 | This will inject our views and it will be like that
237 |
238 | 
239 |
240 | If you want to change something just use the code below
241 |
242 | ```shell
243 | $ rails generate notifly:views
244 | ```
245 |
246 | | Option | Description |
247 | | ---------------- | ----------- |
248 | | `--notification` | generates notifications templates files |
249 | | `--layout` | generates layout files |
250 | | `--mail` | generates mail templates files |
251 |
252 | Notifications and Mails are rendered with their templates. They use a simple default
253 | template but if you want to change or create new ones run the generate above
254 | with the option that you want or create them in `app/views/notifly/templates/`.
255 | Remember that notifications templates should be in `notifications` folder and
256 | mails templates in `mails` folder and with **both** you need to use the `main_app`
257 | to render links.
258 |
259 | If you already have a layout and just want add our features to it, take a look
260 | at [Adapting your layout](#adapting).
261 |
262 | ### I18n
263 |
264 | Notifly uses I18n to render mail's subject and views, if you run the install generator
265 | you can change it in `config/locales/notifly.en.yml` or create your own.
266 |
267 | ### Adapting your layout
268 |
269 | All partials that we insert in your layout are in the gem or if you generated them,
270 | they will be in `app/views/notifly/layouts/`
271 |
272 | Below are the elements that will loading the Notifly in your layout
273 |
274 | - **Counter**: this element will show how many notifications are not seen. It
275 | should have the id `#notifly-counter`, and it will be replaced by the
276 | `_counter.html.erb`
277 | - **Notifications icon**: this element is the trigger to load the notifications
278 | and you should have an icon to show when the user "have notifications" and
279 | "do not have notifications" this element should have the id `#notifly-icon`. The
280 | html icon is defined in our view helper `notifly_icon` you can overwrite it,
281 | just remember that this method should have the argument `have_notifications=false`
282 | and is this method that tell us which icon will be in the view.
283 | - **Notifications**: they will be inserted in `#notifly-notifications-container`,
284 | this element will contain all notifications (`_notification.html.erb`) rendered
285 | by `_index.html.erb`
286 | - **Next page link**: this link will append the next notifications page to the
287 | `#notifly-notifications-container`, it should be in the page and should have
288 | the id `#notifly-more-notifications-link`. This link should not have a href.
289 | - **Mark as read**: this link will mark all notifications in the page as read,
290 | it should be in the page and should have the id `#notifly-mark-as-read-link`.
291 | This link should not have a href.
292 | - **Loading**: html element that will be showing while the notifications request
293 | isn't completed. It should be in `#notifly-notifications-container` and should
294 | have the class `loading`
295 | - **Toggle read**: this link will be rendered by `_actions.html.erb' in
296 | `_notification.html.erb`
297 |
298 | Those elements should be inside an element with id `#notifly` and the dropdown
299 | trigger should have the id `#notifly-trigger`. For more info and examples, just
300 | take a look at `_notifly.html.erb`
301 |
302 | # Contributing
303 |
304 | Consider to use [zenhub](https://www.zenhub.io/), with it will know what issues
305 | and features are in "progress" or "to do". Also, I encourage you to use
306 | [git-flow](http://github.com/nvie/gitflow) and [EditorConfig](http://editorconfig.org).
307 |
308 | Fork the repository. Then, run:
309 |
310 | ```shell
311 | git clone git@github.com:/notifly.git
312 | cd notifly
313 | git branch master origin/master
314 | git flow init -d
315 | git flow feature start
316 | ```
317 |
318 | Then, do work and commit your changes.
319 |
320 | ```shell
321 | git flow feature publish
322 | ```
323 |
324 | When done, open a pull request to your feature branch.
325 |
--------------------------------------------------------------------------------