Merge branch 'master' into notifications-tooltips

This commit is contained in:
nilsding 2014-12-27 01:46:34 +01:00
commit a37cd95f46
45 changed files with 1009 additions and 186 deletions

1
.capistrano/metrics Normal file
View File

@ -0,0 +1 @@
full

24
Capfile Normal file
View File

@ -0,0 +1,24 @@
# Load DSL and set up stages
require 'capistrano/setup'
# Include default deployment tasks
require 'capistrano/deploy'
# Include tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
# https://github.com/capistrano/rvm
# https://github.com/capistrano/rbenv
# https://github.com/capistrano/chruby
# https://github.com/capistrano/bundler
# https://github.com/capistrano/rails
# https://github.com/capistrano/passenger
#
require 'capistrano/rvm'
require 'capistrano/bundler'
require 'capistrano/rails'
require 'capistrano/console'
# Load custom tasks from `lib/capistrano/tasks' if you have any defined
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }

View File

@ -48,6 +48,11 @@ gem 'foreman'
group :development do group :development do
gem 'spring' gem 'spring'
# Capistrano for deployment
gem 'capistrano', '~> 3.1'
gem 'capistrano-rvm', group: :rvm
gem 'capistrano-rails', '~> 1.1'
end end
group :production do group :production do

View File

@ -44,6 +44,21 @@ GEM
railties (>= 3.1) railties (>= 3.1)
buftok (0.2.0) buftok (0.2.0)
builder (3.2.2) builder (3.2.2)
capistrano (3.3.5)
capistrano-stats (~> 1.1.0)
i18n
rake (>= 10.0.0)
sshkit (~> 1.3)
capistrano-bundler (1.1.3)
capistrano (~> 3.1)
sshkit (~> 1.2)
capistrano-rails (1.1.2)
capistrano (~> 3.1)
capistrano-bundler (~> 1.1)
capistrano-rvm (0.1.2)
capistrano (~> 3.0)
sshkit (~> 1.2)
capistrano-stats (1.1.1)
capybara (2.4.4) capybara (2.4.4)
mime-types (>= 1.16) mime-types (>= 1.16)
nokogiri (>= 1.3.3) nokogiri (>= 1.3.3)
@ -60,6 +75,7 @@ GEM
coffee-script-source coffee-script-source
execjs execjs
coffee-script-source (1.8.0) coffee-script-source (1.8.0)
colorize (0.7.5)
connection_pool (2.1.0) connection_pool (2.1.0)
daemons (1.1.9) daemons (1.1.9)
database_cleaner (1.3.0) database_cleaner (1.3.0)
@ -131,6 +147,9 @@ GEM
mysql2 (0.3.17) mysql2 (0.3.17)
naught (1.0.0) naught (1.0.0)
nested_form (0.3.2) nested_form (0.3.2)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-ssh (2.9.1)
nokogiri (1.6.5) nokogiri (1.6.5)
mini_portile (~> 0.6.0) mini_portile (~> 0.6.0)
nprogress-rails (0.1.6.3) nprogress-rails (0.1.6.3)
@ -254,6 +273,10 @@ GEM
actionpack (>= 3.0) actionpack (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0) sprockets (>= 2.8, < 4.0)
sshkit (1.6.1)
colorize (>= 0.7.0)
net-scp (>= 1.1.2)
net-ssh (>= 2.8.0)
thin (1.6.3) thin (1.6.3)
daemons (~> 1.0, >= 1.0.9) daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0) eventmachine (~> 1.0)
@ -302,6 +325,9 @@ DEPENDENCIES
bootstrap-sass (~> 3.2.0.1) bootstrap-sass (~> 3.2.0.1)
bootstrap_form bootstrap_form
bootswatch-rails bootswatch-rails
capistrano (~> 3.1)
capistrano-rails (~> 1.1)
capistrano-rvm
capybara capybara
coffee-rails (~> 4.1.0) coffee-rails (~> 4.1.0)
database_cleaner database_cleaner

View File

@ -79,4 +79,22 @@ namespace :justask do
puts "#{sprintf "%3d", u.id}. #{u.screen_name}" puts "#{sprintf "%3d", u.id}. #{u.screen_name}"
end end
end end
desc "Fixes the notifications"
task fix_notifications: :environment do
format = '%t (%c/%C) [%b>%i] %e'
total = Notification.count
progress = ProgressBar.create title: 'Processing notifications', format: format, starting_at: 0, total: total
destroyed_count = 0
Notification.all.each do |n|
if n.target.nil?
n.destroy
destroyed_count += 1
end
progress.increment
end
puts "Purged #{destroyed_count} dead notifications."
end
end end

View File

@ -6,6 +6,7 @@
#= require nprogress #= require nprogress
#= require nprogress-turbolinks #= require nprogress-turbolinks
#= require growl #= require growl
#= require cheet
#= require_tree . #= require_tree .
NProgress.configure NProgress.configure

View File

@ -8,18 +8,51 @@
success: (data, status, jqxhr) -> success: (data, status, jqxhr) ->
if data.success if data.success
($ "div#entries").prepend(data.render) # TODO: slideDown or something ($ "div#entries").prepend(data.render) # TODO: slideDown or something
# GitHub issue #26:
del_all_btn = ($ "button#ib-delete-all")
del_all_btn.removeAttr 'disabled'
del_all_btn[0].dataset.ibCount = 1
error: (jqxhr, status, error) -> error: (jqxhr, status, error) ->
console.log jqxhr, status, error console.log jqxhr, status, error
showNotification "An error occurred, a developer should check the console for details", false showNotification "An error occurred, a developer should check the console for details", false
complete: (jqxhr, status) -> complete: (jqxhr, status) ->
btn.button "reset" btn.button "reset"
($ document).on "click", "button#ib-delete-all", ->
btn = ($ this)
count = btn[0].dataset.ibCount
if confirm "Really delete #{count} questions?"
btn.button "loading"
succ = no
$.ajax
url: '/ajax/delete_all_inbox'
type: 'POST'
dataType: 'json'
success: (data, status, jqxhr) ->
if data.success
succ = yes
entries = ($ "div#entries")
entries.slideUp 400, ->
entries.html("Nothing to see here.")
entries.fadeIn()
error: (jqxhr, status, error) ->
console.log jqxhr, status, error
showNotification "An error occurred, a developer should check the console for details", false
complete: (jqxhr, status) ->
btn.button "reset"
if succ
btn.attr "disabled", "disabled" # this doesn't really work like I wanted it to…
btn[0].dataset.ibCount = 0
$(document).on "keydown", "textarea[name=ib-answer]", (evt) -> $(document).on "keydown", "textarea[name=ib-answer]", (evt) ->
iid = $(this)[0].dataset.id iid = $(this)[0].dataset.id
if evt.keyCode == 13 and evt.ctrlKey if evt.keyCode == 13 and evt.ctrlKey
# trigger warning: # trigger warning:
$("button[name=ib-answer][data-ib-id=#{iid}]").trigger 'click' $("button[name=ib-answer][data-ib-id=#{iid}]").trigger 'click'
$(document).on "click", "button[name=ib-answer]", -> $(document).on "click", "button[name=ib-answer]", ->
btn = $(this) btn = $(this)
btn.button "loading" btn.button "loading"
@ -48,6 +81,7 @@ $(document).on "click", "button[name=ib-answer]", ->
btn.button "reset" btn.button "reset"
$("textarea[name=ib-answer][data-id=#{iid}]").removeAttr "readonly" $("textarea[name=ib-answer][data-id=#{iid}]").removeAttr "readonly"
$(document).on "click", "button[name=ib-destroy]", -> $(document).on "click", "button[name=ib-destroy]", ->
if confirm 'Are you sure?' if confirm 'Are you sure?'
btn = $(this) btn = $(this)

View File

@ -0,0 +1,3 @@
cheet 'up up down down left right left right b a', ->
($ "body").addClass 'fa-spin'
($ "p.answerbox--question-text").each (i) -> ($ this).html ":^)"

View File

@ -53,4 +53,8 @@ body {
.smiles { .smiles {
margin-bottom: 7px; margin-bottom: 7px;
}
.j2-lh {
color: #fff;
} }

View File

@ -1,4 +1,4 @@
.answerbox .text-muted a, .answerbox .text-muted a:hover { .text-muted a, .answerbox .text-muted a:hover {
color: $gray-dark; color: $gray-dark;
text-decoration: none; text-decoration: none;
} }

View File

@ -33,4 +33,33 @@
height: 40vh; height: 40vh;
background-color: darken($navbar-inverse-bg, 10%); background-color: darken($navbar-inverse-bg, 10%);
background-size: cover; background-size: cover;
}
.profile--panel .panel-heading {
color: $brand-primary;
border-bottom: 2px solid $brand-primary;
background-color: #fff;
text-transform: uppercase;
}
.profile--panel .panel-body {
padding-top: 0px;
}
.inbox--panel .panel-heading {
color: $brand-info;
border-bottom: 2px solid $brand-info;
background-color: #fff;
text-transform: uppercase;
}
.warning--panel .panel-heading {
color: $brand-danger;
border-bottom: 2px solid $brand-danger;
background-color: #fff;
text-transform: uppercase;
}
.profile--follow-btn {
margin-top: 5px;
} }

View File

@ -85,4 +85,20 @@ class Ajax::InboxController < ApplicationController
@message = "Successfully deleted question." @message = "Successfully deleted question."
@success = true @success = true
end end
def remove_all
begin
Inbox.where(user: current_user).each { |i| i.remove }
rescue
@status = :err
@message = "An error occurred"
@success = false
return
end
@status = :okay
@message = "Successfully deleted questions."
@success = true
render 'ajax/inbox/remove'
end
end end

View File

@ -1,6 +1,6 @@
class UserController < ApplicationController class UserController < ApplicationController
def show def show
@user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first @user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first!
@answers = @user.answers.reverse_order.paginate(page: params[:page]) @answers = @user.answers.reverse_order.paginate(page: params[:page])
respond_to do |format| respond_to do |format|
format.html format.html
@ -23,7 +23,7 @@ class UserController < ApplicationController
def followers def followers
@title = 'Followers' @title = 'Followers'
@user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first @user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first!
@users = @user.followers.reverse_order.paginate(page: params[:page]) @users = @user.followers.reverse_order.paginate(page: params[:page])
@type = :friend @type = :friend
render 'show_follow' render 'show_follow'
@ -31,9 +31,15 @@ class UserController < ApplicationController
def friends def friends
@title = 'Following' @title = 'Following'
@user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first @user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first!
@users = @user.friends.reverse_order.paginate(page: params[:page]) @users = @user.friends.reverse_order.paginate(page: params[:page])
@type = :friend @type = :friend
render 'show_follow' render 'show_follow'
end end
def questions
@title = 'Questions'
@user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first!
@questions = @user.questions.where(author_is_anonymous: false).reverse_order.paginate(page: params[:page])
end
end end

View File

@ -33,6 +33,15 @@ class User < ActiveRecord::Base
# validates :website, format: { with: WEBSITE_REGEX } # validates :website, format: { with: WEBSITE_REGEX }
before_save do
self.display_name = 'WRYYYYYYYY' if display_name == 'Dio Brando'
self.website = if website.match %r{\Ahttps?://}
website
else
"http://#{website}"
end unless website.blank?
end
def login=(login) def login=(login)
@login = login @login = login
end end

View File

@ -1,19 +1,26 @@
.container.j2-page .container.j2-page
= render 'layouts/messages' .row
.alert.alert-info .col-md-3.col-xs-12.col-sm-3
.row .panel.panel-default.inbox--panel
.col-md-9.col-sm-8.col-xs-12 .panel-heading
Out of questions? %h3.panel-title Out of questions?
.col-md-3.col-sm-5.col-xs-12 .panel-body
%button.btn.btn-block.btn-info{type: :button, id: 'ib-generate-question'} Get new question %button.btn.btn-block.btn-info{type: :button, id: 'ib-generate-question'} Get new question
%a.btn.btn-block.btn-primary{target: '_blank', href: "https://twitter.com/intent/tweet?text=Ask%20me%20anything%21&url=#{show_user_profile_url(current_user.screen_name)}"} Share on Twitter
.panel.panel-default.warning--panel
.panel-heading
%h3.panel-title Actions
.panel-body
%button.btn.btn-block.btn-danger{type: :button, id: 'ib-delete-all', disabled: (@inbox.empty? ? 'disabled' : nil), data: { ib_count: @inbox.count }} Delete all questions
.col-md-9.col-xs-12.col-sm-9
= render 'layouts/messages'
#entries
- @inbox.each do |i|
= render 'inbox/entry', i: i
#entries - if @inbox.empty?
- @inbox.each do |i|
= render 'inbox/entry', i: i
- if @inbox.empty? Nothing to see here.
Nothing to see here.
= render "shared/links" = render "shared/links"
- @inbox.update_all(new: false) - @inbox.update_all(new: false)

View File

@ -4,7 +4,7 @@
%button.navbar-toggle{"data-target" => "#j2-main-navbar-collapse", "data-toggle" => "collapse", type: "button"} %button.navbar-toggle{"data-target" => "#j2-main-navbar-collapse", "data-toggle" => "collapse", type: "button"}
%span.sr-only Toggle navigation %span.sr-only Toggle navigation
- if user_signed_in? - if user_signed_in?
- unless inbox_count.nil? - unless inbox_count.nil? or notification_count.nil?
%span.icon-bar.navbar--inbox-animation %span.icon-bar.navbar--inbox-animation
%span.icon-bar.navbar--inbox-animation %span.icon-bar.navbar--inbox-animation
%span.icon-bar.navbar--inbox-animation %span.icon-bar.navbar--inbox-animation

View File

@ -9,7 +9,11 @@
%h6.text-muted.media-heading.answerbox--question-user %h6.text-muted.media-heading.answerbox--question-user
= user_screen_name a.question.user, a.question.author_is_anonymous = user_screen_name a.question.user, a.question.author_is_anonymous
asked asked
= time_ago_in_words(a.question.created_at) - if @user.nil? or a.question.author_is_anonymous
= time_ago_in_words(a.question.created_at)
- else
%a{href: show_user_question_path(a.question.user.screen_name, a.question.id)}
= time_ago_in_words(a.question.created_at)
ago ago
- unless a.question.author_is_anonymous - unless a.question.author_is_anonymous
- if a.question.answer_count > 1 - if a.question.answer_count > 1
@ -45,6 +49,6 @@
.row .row
.col-md-6.col-md-offset-6.col-sm-8.col-sm-offset-4.col-xs-6.col-xs-offset-6.text-right .col-md-6.col-md-offset-6.col-sm-8.col-sm-offset-4.col-xs-6.col-xs-offset-6.text-right
= render 'shared/answerbox_buttons', a: a = render 'shared/answerbox_buttons', a: a
.panel-footer{id: "ab-comments-section-#{a.id}", style: 'display: none'} .panel-footer{id: "ab-comments-section-#{a.id}", style: @display_all.nil? ? 'display: none' : nil }
%div{id: "ab-smiles-#{a.id}"}= render 'shared/smiles', a: a %div{id: "ab-smiles-#{a.id}"}= render 'shared/smiles', a: a
%div{id: "ab-comments-#{a.id}"}= render 'shared/comments', a: a %div{id: "ab-comments-#{a.id}"}= render 'shared/comments', a: a

View File

@ -12,9 +12,10 @@
%button.btn.btn-info.btn-sm{type: :button, name: 'ab-smile', data: { a_id: a.id, action: :smile }} %button.btn.btn-info.btn-sm{type: :button, name: 'ab-smile', data: { a_id: a.id, action: :smile }}
%i.fa.fa-smile-o %i.fa.fa-smile-o
%span{id: "ab-smile-count-#{a.id}"}= a.smile_count %span{id: "ab-smile-count-#{a.id}"}= a.smile_count
%button.btn.btn-primary.btn-sm{type: :button, name: 'ab-comments', data: { a_id: a.id, state: :hidden }} - unless @display_all
%i.fa.fa-comments %button.btn.btn-primary.btn-sm{type: :button, name: 'ab-comments', data: { a_id: a.id, state: :hidden }}
%span{id: "ab-comment-count-#{a.id}"}= a.comment_count %i.fa.fa-comments
%span{id: "ab-comment-count-#{a.id}"}= a.comment_count
- if privileged? a.user - if privileged? a.user
%button.btn.btn-danger.btn-sm{name: 'ab-destroy', data: { a_id: a.id }} %button.btn.btn-danger.btn-sm{name: 'ab-destroy', data: { a_id: a.id }}
%i.fa.fa-trash-o %i.fa.fa-trash-o

View File

@ -0,0 +1,16 @@
.panel.panel-default
.panel-body
.media
.media-body
%h6.media-heading.text-muted.answerbox--question-user
= user_screen_name q.user
asked
%a{href: show_user_question_path(q.user.screen_name, q.id)}
= time_ago_in_words(q.created_at)
ago
- if q.answer_count > 1
·
%a{href: show_user_question_path(q.user.screen_name, q.id)}
#{q.answer_count} answers
%p.answerbox--question-text
= q.content

View File

@ -23,7 +23,7 @@
- unless user_signed_in? - unless user_signed_in?
#question-box-promote.row{:style => "display: none;"} #question-box-promote.row{:style => "display: none;"}
.row .row
.col-xs-12 .col-xs-12.text-center
%strong Your question has been sent. %strong Your question has been sent.
.row .row
.col-sm-1 .col-sm-1

View File

@ -1,11 +1,11 @@
- if user_signed_in? - if user_signed_in?
- type ||= :nil - type ||= :nil
- if user == current_user - if user == current_user
%a.btn.btn-default.btn-block{href: edit_user_profile_path} Edit profile %a.btn.btn-default.btn-block.profile--follow-btn{href: edit_user_profile_path} Edit profile
- else - else
- if current_user.following? user - if current_user.following? user
%button#editprofile.btn.btn-default.btn-block{type: :button, name: 'user-action', data: { action: :unfollow, type: type, target: user.screen_name }} %button#editprofile.btn.btn-default.btn-block.profile--follow-btn{type: :button, name: 'user-action', data: { action: :unfollow, type: type, target: user.screen_name }}
Unfollow Unfollow
- else - else
%button#editprofile.btn.btn-primary.btn-block{type: :button, name: 'user-action', data: { action: :follow, type: type, target: user.screen_name }} %button#editprofile.btn.btn-primary.btn-block.profile--follow-btn{type: :button, name: 'user-action', data: { action: :follow, type: type, target: user.screen_name }}
Follow Follow

View File

@ -23,20 +23,5 @@
%p.profile--text %p.profile--text
%i.fa.fa-location-arrow %i.fa.fa-location-arrow
= @user.location = @user.location
.row = render 'user/actions', user: @user, type: :follower
%a{href: show_user_followers_path(@user.screen_name)} = render 'user/stats', user: @user
.col-md-6.col-sm-6.col-xs-6
%h4.entry-text#follower-count= @user.follower_count
%h6.entry-subtext Followers
%a{href: show_user_friends_path(@user.screen_name)}
.col-md-6.col-sm-6.col-xs-6
%h4.entry-text#friend-count= @user.friend_count
%h6.entry-subtext Following
.row
.col-md-6.col-sm-6.col-xs-6
%h4.entry-text#asked-count= @user.asked_count
%h6.entry-subtext Questions
.col-md-6.col-sm-6.col-xs-6
%h4.entry-text#answered-count= @user.answered_count
%h6.entry-subtext Answers
= render 'user/actions', user: @user, type: :follower

View File

@ -0,0 +1,22 @@
.panel.panel-default.profile--panel
.panel-heading
%h3.panel-title Stats
.panel-body
.row
%a{href: show_user_followers_path(@user.screen_name)}
.col-md-6.col-sm-6.col-xs-6
%h4.entry-text#follower-count= @user.follower_count
%h6.entry-subtext Followers
%a{href: show_user_friends_path(@user.screen_name)}
.col-md-6.col-sm-6.col-xs-6
%h4.entry-text#friend-count= @user.friend_count
%h6.entry-subtext Following
.row
%a{href: show_user_questions_path(@user.screen_name)}
.col-md-6.col-sm-6.col-xs-6
%h4.entry-text#asked-count= @user.asked_count
%h6.entry-subtext Questions
%a{href: show_user_profile_path(@user.screen_name)}
.col-md-6.col-sm-6.col-xs-6
%h4.entry-text#answered-count= @user.answered_count
%h6.entry-subtext Answers

View File

@ -0,0 +1,18 @@
.profile--header
.container.j2-page
.col-md-4.col-xs-12.col-sm-4
= render 'user/profile_info'
.hidden-xs= render 'shared/links'
.col-md-8.col-xs-12.col-sm-8
%h1.j2-lh.hidden-xs= @title
%h1.visible-xs= @title
#questions
- @questions.each do |q|
= render 'shared/question', q: q
#pagination= will_paginate @questions, renderer: BootstrapPagination::Rails, page_links: false
- if @questions.next_page
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @questions.current_page }}
Load more
.visible-xs= render 'shared/links'

View File

@ -0,0 +1,8 @@
$('#questions').append('<% @questions.each do |q|
%><%= j render 'shared/question', q: q
%><% end %>');
<% if @questions.next_page %>
$('#pagination').html('<%= j will_paginate @questions, renderer: BootstrapPagination::Rails, page_links: false %>');
<% else %>
$('#pagination, #load-more-btn').remove();
<% end %>

View File

@ -1,9 +1,9 @@
.profile--header .profile--header
.container.j2-page .container.j2-page
.col-md-3.col-xs-12.col-sm-3 .col-md-4.col-xs-12.col-sm-4
= render 'user/profile_info' = render 'user/profile_info'
.hidden-xs= render 'shared/links' .hidden-xs= render 'shared/links'
.col-md-9.col-xs-12.col-sm-9 .col-md-8.col-xs-12.col-sm-8
= render 'shared/questionbox' = render 'shared/questionbox'
#answers #answers
- @answers.each do |a| - @answers.each do |a|

View File

@ -1,9 +1,11 @@
.profile--header
.container.j2-page .container.j2-page
.col-md-3.col-xs-12.col-sm-3 .col-md-4.col-xs-12.col-sm-4
= render 'user/profile_info' = render 'user/profile_info'
.hidden-xs= render 'shared/links' .hidden-xs= render 'shared/links'
.col-md-9.col-xs-12.col-sm-9 .col-md-8.col-xs-12.col-sm-8
%h1= @title %h1.j2-lh.hidden-xs= @title
%h1.visible-xs= @title
#users #users
- @users.each do |user| - @users.each do |user|
.col-sm-6 .col-sm-6

16
bin/cap Executable file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env ruby
#
# This file was generated by Bundler.
#
# The application 'cap' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'pathname'
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
require 'rubygems'
require 'bundler/setup'
load Gem.bin_path('capistrano', 'cap')

16
bin/capify Executable file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env ruby
#
# This file was generated by Bundler.
#
# The application 'capify' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'pathname'
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
require 'rubygems'
require 'bundler/setup'
load Gem.bin_path('capistrano', 'capify')

31
config/deploy.rb Normal file
View File

@ -0,0 +1,31 @@
# config valid only for current version of Capistrano
lock '3.3.5'
set :application, 'justask'
set :repo_url, 'git@git.rrerr.net:justask/justask.git'
ask :branch, :master
set :deploy_to, '/home/justask/cap/'
set :scm, :git
set :format, :pretty
set :log_level, :debug
# RVM
set :rvm_type, :user
set :rvm_ruby_version, '2.0.0'
# Rails
set :conditionally_migrate, true
namespace :deploy do
after :updated do
end
after :restart, :clear_cache do
on roles(:web), in: :groups, limit: 3, wait: 10 do
end
end
end

View File

@ -0,0 +1,7 @@
server 'rrerr.net', user: 'justask', roles: %w{web app}
set :ssh_options, {
keys: %w(~/.ssh/id_rsa),
forward_agent: false,
auth_methods: %w(publickey)
}

45
config/deploy/staging.rb Normal file
View File

@ -0,0 +1,45 @@
# Simple Role Syntax
# ==================
# Supports bulk-adding hosts to roles, the primary server in each group
# is considered to be the first unless any hosts have the primary
# property set. Don't declare `role :all`, it's a meta role.
role :app, %w{deploy@example.com}
role :web, %w{deploy@example.com}
role :db, %w{deploy@example.com}
# Extended Server Syntax
# ======================
# This can be used to drop a more detailed server definition into the
# server list. The second argument is a, or duck-types, Hash and is
# used to set extended properties on the server.
server 'example.com', user: 'deploy', roles: %w{web app}, my_property: :my_value
# Custom SSH Options
# ==================
# You may pass any option but keep in mind that net/ssh understands a
# limited set of options, consult[net/ssh documentation](http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start).
#
# Global options
# --------------
# set :ssh_options, {
# keys: %w(/home/rlisowski/.ssh/id_rsa),
# forward_agent: false,
# auth_methods: %w(password)
# }
#
# And/or per server (overrides global)
# ------------------------------------
# server 'example.com',
# user: 'user_name',
# roles: %w{web app},
# ssh_options: {
# user: 'user_name', # overrides user setting above
# keys: %w(/home/user_name/.ssh/id_rsa),
# forward_agent: false,
# auth_methods: %w(publickey password)
# # password: 'please use keys'
# }

View File

@ -46,6 +46,7 @@ Rails.application.routes.draw do
match '/answer', to: 'inbox#destroy', via: :post, as: :answer match '/answer', to: 'inbox#destroy', via: :post, as: :answer
match '/generate_question', to: 'inbox#create', via: :post, as: :generate_question match '/generate_question', to: 'inbox#create', via: :post, as: :generate_question
match '/delete_inbox', to: 'inbox#remove', via: :post, as: :delete_inbox match '/delete_inbox', to: 'inbox#remove', via: :post, as: :delete_inbox
match '/delete_all_inbox', to: 'inbox#remove_all', via: :post, as: :delete_all_inbox
match '/destroy_answer', to: 'answer#destroy', via: :post, as: :destroy_answer match '/destroy_answer', to: 'answer#destroy', via: :post, as: :destroy_answer
match '/create_friend', to: 'friend#create', via: :post, as: :create_friend match '/create_friend', to: 'friend#create', via: :post, as: :create_friend
match '/destroy_friend', to: 'friend#destroy', via: :post, as: :destroy_friend match '/destroy_friend', to: 'friend#destroy', via: :post, as: :destroy_friend
@ -71,4 +72,5 @@ Rails.application.routes.draw do
match '/:username/q/:id', to: 'question#show', via: 'get', as: :show_user_question_alt match '/:username/q/:id', to: 'question#show', via: 'get', as: :show_user_question_alt
match '/:username/followers(/p/:page)', to: 'user#followers', via: 'get', as: :show_user_followers_alt, defaults: {page: 1} match '/:username/followers(/p/:page)', to: 'user#followers', via: 'get', as: :show_user_followers_alt, defaults: {page: 1}
match '/:username/friends(/p/:page)', to: 'user#friends', via: 'get', as: :show_user_friends_alt, defaults: {page: 1} match '/:username/friends(/p/:page)', to: 'user#friends', via: 'get', as: :show_user_friends_alt, defaults: {page: 1}
match '/:username/questions(/p/:page)', to: 'user#questions', via: 'get', as: :show_user_questions, defaults: {page: 1}
end end

View File

@ -0,0 +1,279 @@
/*
The MIT License (MIT)
Copyright (c) 2013 Louis Acresti
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
(function (global) {
'use strict';
var cheet,
sequences = {},
keys = {
backspace: 8,
tab: 9,
enter: 13,
'return': 13,
shift: 16,
'⇧': 16,
control: 17,
ctrl: 17,
'⌃': 17,
alt: 18,
option: 18,
'⌥': 18,
pause: 19,
capslock: 20,
esc: 27,
space: 32,
pageup: 33,
pagedown: 34,
end: 35,
home: 36,
left: 37,
L: 37,
'←': 37,
up: 38,
U: 38,
'↑': 38,
right: 39,
R: 39,
'→': 39,
down: 40,
D: 40,
'↓': 40,
insert: 45,
'delete': 46,
'0': 48,
'1': 49,
'2': 50,
'3': 51,
'4': 52,
'5': 53,
'6': 54,
'7': 55,
'8': 56,
'9': 57,
a: 65,
b: 66,
c: 67,
d: 68,
e: 69,
f: 70,
g: 71,
h: 72,
i: 73,
j: 74,
k: 75,
l: 76,
m: 77,
n: 78,
o: 79,
p: 80,
q: 81,
r: 82,
s: 83,
t: 84,
u: 85,
v: 86,
w: 87,
x: 88,
y: 89,
z: 90,
'⌘': 91,
command: 91,
kp_0: 96,
kp_1: 97,
kp_2: 98,
kp_3: 99,
kp_4: 100,
kp_5: 101,
kp_6: 102,
kp_7: 103,
kp_8: 104,
kp_9: 105,
kp_multiply: 106,
kp_plus: 107,
kp_minus: 109,
kp_decimal: 110,
kp_divide: 111,
f1: 112,
f2: 113,
f3: 114,
f4: 115,
f5: 116,
f6: 117,
f7: 118,
f8: 119,
f9: 120,
f10: 121,
f11: 122,
f12: 123,
equal: 187,
'=': 187,
comma: 188,
',': 188,
minus: 189,
'-': 189,
period: 190,
'.': 190
},
Sequence,
NOOP = function NOOP() {},
held = {};
Sequence = function Sequence (str, next, fail, done) {
var i;
this.str = str;
this.next = next ? next : NOOP;
this.fail = fail ? fail : NOOP;
this.done = done ? done : NOOP;
this.seq = str.split(' ');
this.keys = [];
for (i=0; i<this.seq.length; ++i) {
this.keys.push(keys[this.seq[i]]);
}
this.idx = 0;
};
Sequence.prototype.keydown = function keydown (keyCode) {
var i = this.idx;
if (keyCode !== this.keys[i]) {
if (i > 0) {
this.reset();
this.fail(this.str);
cheet.__fail(this.str);
}
return;
}
this.next(this.str, this.seq[i], i, this.seq);
cheet.__next(this.str, this.seq[i], i, this.seq);
if (++this.idx === this.keys.length) {
this.done(this.str);
cheet.__done(this.str);
this.reset();
}
};
Sequence.prototype.reset = function () {
this.idx = 0;
};
cheet = function cheet (str, handlers) {
var next, fail, done;
if (typeof handlers === 'function') {
done = handlers;
} else if (handlers !== null && handlers !== undefined) {
next = handlers.next;
fail = handlers.fail;
done = handlers.done;
}
sequences[str] = new Sequence(str, next, fail, done);
};
cheet.disable = function disable (str) {
delete sequences[str];
};
function keydown (e) {
var id,
k = e ? e.keyCode : event.keyCode;
if (held[k]) return;
held[k] = true;
for (id in sequences) {
sequences[id].keydown(k);
}
}
function keyup (e) {
var k = e ? e.keyCode : event.keyCode;
held[k] = false;
}
function resetHeldKeys (e) {
var k;
for (k in held) {
held[k] = false;
}
}
function on (obj, type, fn) {
if (obj.addEventListener) {
obj.addEventListener(type, fn, false);
} else if (obj.attachEvent) {
obj['e' + type + fn] = fn;
obj[type + fn] = function () {
obj['e' + type + fn](window.event);
};
obj.attachEvent('on' + type, obj[type + fn]);
}
}
on(window, 'keydown', keydown);
on(window, 'keyup', keyup);
on(window, 'blur', resetHeldKeys);
on(window, 'focus', resetHeldKeys);
cheet.__next = NOOP;
cheet.next = function next (fn) {
cheet.__next = fn === null ? NOOP : fn;
};
cheet.__fail = NOOP;
cheet.fail = function fail (fn) {
cheet.__fail = fn === null ? NOOP : fn;
};
cheet.__done = NOOP;
cheet.done = function done (fn) {
cheet.__done = fn === null ? NOOP : fn;
};
cheet.reset = function reset (id) {
var seq = sequences[id];
if (!(seq instanceof Sequence)) {
console.warn('cheet: Unknown sequence: ' + id);
return;
}
seq.reset();
};
global.cheet = cheet;
if (typeof define === 'function' && define.amd) {
define([], function () { return cheet; });
} else if (typeof module !== 'undefined' && module !== null) {
module.exports = cheet;
}
})(this);

View File

@ -1,59 +1,75 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>The page you were looking for doesn't exist (404)</title> <title>Page not found!!1! (404)</title>
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<style> <style>
body { body {
background-color: #EFEFEF; background-color: #EFEFEF;
color: #2E2F30; color: #2E2F30;
text-align: center; text-align: center;
font-family: arial, sans-serif; font-family: arial, sans-serif;
margin: 0; margin: 0;
} }
div.dialog { a { color: #4183c4; text-decoration: none; }
width: 95%; a:hover { text-decoration: underline; }
max-width: 33em;
margin: 4em auto 0;
}
div.dialog > div { div.dialog {
border: 1px solid #CCC; width: 95%;
border-right-color: #999; max-width: 33em;
border-left-color: #999; margin: 4em auto 0;
border-bottom-color: #BBB; }
border-top: #00A0F0 solid 4px;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
background-color: white;
padding: 7px 12% 0;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
h1 { div.dialog > div {
font-size: 100%; border: 1px solid #CCC;
color: #0b79C5; border-right-color: #999;
line-height: 1.5em; border-left-color: #999;
} border-bottom-color: #BBB;
border-top: #00A0F0 solid 4px;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
background-color: white;
padding: 7px 12% 0;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
div.dialog > p { h1 {
margin: 0 0 1em; font-size: 100%;
padding: 1em; color: #0b79C5;
background-color: #F7F7F7; line-height: 1.5em;
border: 1px solid #CCC; }
border-right-color: #999;
border-left-color: #999; div.dialog img { margin-bottom: 0.5em; }
border-bottom-color: #999;
border-bottom-left-radius: 4px; div.dialog > p {
border-bottom-right-radius: 4px; margin: 0 0 1em;
border-top-color: #DADADA; padding: 1em;
color: #666; background-color: #F7F7F7;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); border: 1px solid #CCC;
} border-right-color: #999;
border-left-color: #999;
border-bottom-color: #999;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
div.links {
margin-top: 2em;
color: #999;
}
div.links a {
color: #666;
font-weight: 200;
font-size: 14px;
margin: 0 10px;
}
</style> </style>
</head> </head>
<body> <body>
<div class="dialog"> <div class="dialog">
<div> <div>
@ -62,5 +78,9 @@
</div> </div>
<p>If you think you found a bug, please let us know.</p> <p>If you think you found a bug, please let us know.</p>
</div> </div>
<div class="links">
<a href="https://github.com/justask/justask-bugs/issues">Bug tracker</a> &middot;
<a href="https://twitter.com/justask_2">Twitter</a>
</div>
</body> </body>
</html> </html>

View File

@ -4,63 +4,83 @@
<title>The change you wanted was rejected (422)</title> <title>The change you wanted was rejected (422)</title>
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<style> <style>
body { body {
background-color: #EFEFEF; background-color: #EFEFEF;
color: #2E2F30; color: #2E2F30;
text-align: center; text-align: center;
font-family: arial, sans-serif; font-family: arial, sans-serif;
margin: 0; margin: 0;
} }
div.dialog { a { color: #4183c4; text-decoration: none; }
width: 95%; a:hover { text-decoration: underline; }
max-width: 33em;
margin: 4em auto 0;
}
div.dialog > div { div.dialog {
border: 1px solid #CCC; width: 95%;
border-right-color: #999; max-width: 33em;
border-left-color: #999; margin: 4em auto 0;
border-bottom-color: #BBB; }
border-top: #00A0F0 solid 4px;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
background-color: white;
padding: 7px 12% 0;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
h1 { div.dialog > div {
font-size: 100%; border: 1px solid #CCC;
color: #0b79C5; border-right-color: #999;
line-height: 1.5em; border-left-color: #999;
} border-bottom-color: #BBB;
border-top: #00A0F0 solid 4px;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
background-color: white;
padding: 7px 12% 0;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
div.dialog > p { h1 {
margin: 0 0 1em; font-size: 100%;
padding: 1em; color: #0b79C5;
background-color: #F7F7F7; line-height: 1.5em;
border: 1px solid #CCC; }
border-right-color: #999;
border-left-color: #999; div.dialog img { margin-bottom: 0.5em; }
border-bottom-color: #999;
border-bottom-left-radius: 4px; div.dialog > p {
border-bottom-right-radius: 4px; margin: 0 0 1em;
border-top-color: #DADADA; padding: 1em;
color: #666; background-color: #F7F7F7;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); border: 1px solid #CCC;
} border-right-color: #999;
border-left-color: #999;
border-bottom-color: #999;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
div.links {
margin-top: 2em;
color: #999;
}
div.links a {
color: #666;
font-weight: 200;
font-size: 14px;
margin: 0 10px;
}
</style> </style>
</head> </head>
<body> <body>
<div class="dialog"> <div class="dialog">
<div> <div>
<h1>The change you wanted was rejected.</h1> <h1>The change you wanted was rejected.</h1>
<p>Maybe you tried to change something you didn't have access to.</p> <p>Maybe you tried to change something you didn't have access to.</p>
</div> </div>
<p>If you think you found a bug, please let us know.</p> <p>If you think you found a bug, please <a href="https://github.com/justask/justask-bugs/issues">please let us know</a>.</p>
</div>
<div class="links">
<a href="https://github.com/justask/justask-bugs/issues">Bug tracker</a> &middot;
<a href="https://twitter.com/justask_2">Twitter</a>
</div> </div>
</body> </body>
</html> </html>

View File

@ -4,62 +4,82 @@
<title>We're sorry, but something went wrong (500)</title> <title>We're sorry, but something went wrong (500)</title>
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<style> <style>
body { body {
background-color: #EFEFEF; background-color: #EFEFEF;
color: #2E2F30; color: #2E2F30;
text-align: center; text-align: center;
font-family: arial, sans-serif; font-family: arial, sans-serif;
margin: 0; margin: 0;
} }
div.dialog { a { color: #4183c4; text-decoration: none; }
width: 95%; a:hover { text-decoration: underline; }
max-width: 33em;
margin: 4em auto 0;
}
div.dialog > div { div.dialog {
border: 1px solid #CCC; width: 95%;
border-right-color: #999; max-width: 33em;
border-left-color: #999; margin: 4em auto 0;
border-bottom-color: #BBB; }
border-top: #00A0F0 solid 4px;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
background-color: white;
padding: 7px 12% 0;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
h1 { div.dialog > div {
font-size: 100%; border: 1px solid #CCC;
color: #0b79C5; border-right-color: #999;
line-height: 1.5em; border-left-color: #999;
} border-bottom-color: #BBB;
border-top: #00A0F0 solid 4px;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
background-color: white;
padding: 7px 12% 0;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
div.dialog > p { h1 {
margin: 0 0 1em; font-size: 100%;
padding: 1em; color: #0b79C5;
background-color: #F7F7F7; line-height: 1.5em;
border: 1px solid #CCC; }
border-right-color: #999;
border-left-color: #999; div.dialog img { margin-bottom: 0.5em; }
border-bottom-color: #999;
border-bottom-left-radius: 4px; div.dialog > p {
border-bottom-right-radius: 4px; margin: 0 0 1em;
border-top-color: #DADADA; padding: 1em;
color: #666; background-color: #F7F7F7;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); border: 1px solid #CCC;
} border-right-color: #999;
border-left-color: #999;
border-bottom-color: #999;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
div.links {
margin-top: 2em;
color: #999;
}
div.links a {
color: #666;
font-weight: 200;
font-size: 14px;
margin: 0 10px;
}
</style> </style>
</head> </head>
<body> <body>
<div class="dialog"> <div class="dialog">
<div> <div>
<h1>We're sorry, but something went wrong.</h1> <h1>Looks like something went wrong!</h1>
</div> </div>
<p>If you think you found a bug, please let us know.</p> <p>This usually happens due to an error in our code. If you think you found a bug, <a href="https://github.com/justask/justask-bugs/issues">please let us know</a>.</p>
</div>
<div class="links">
<a href="https://github.com/justask/justask-bugs/issues">Bug tracker</a> &middot;
<a href="https://twitter.com/justask_2">Twitter</a>
</div> </div>
</body> </body>
</html> </html>

86
public/502.html Normal file
View File

@ -0,0 +1,86 @@
<!DOCTYPE html>
<html>
<head>
<title>Unicorn! (502)</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
body {
background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
font-family: arial, sans-serif;
margin: 0;
}
a { color: #4183c4; text-decoration: none; }
a:hover { text-decoration: underline; }
div.dialog {
width: 95%;
max-width: 33em;
margin: 4em auto 0;
}
div.dialog > div {
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #BBB;
border-top: #00A0F0 solid 4px;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
background-color: white;
padding: 7px 12% 0;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
h1 {
font-size: 100%;
color: #0b79C5;
line-height: 1.5em;
}
div.dialog img { margin-bottom: 0.5em; }
div.dialog > p {
margin: 0 0 1em;
padding: 1em;
background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #999;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
div.links {
margin-top: 2em;
color: #999;
}
div.links a {
color: #666;
font-weight: 200;
font-size: 14px;
margin: 0 10px;
}
</style>
</head>
<body>
<div class="dialog">
<div>
<h1>This page takes way too long to load!</h1>
<img src="images/angry_unicorn.png" alt="Unicorn">
</div>
<p>Sorry about that. Please try refreshing and contact us if the problem persists.</p>
</div>
<div class="links">
<a href="https://github.com/justask/justask-bugs/issues">Bug tracker</a> &middot;
<a href="https://twitter.com/justask_2">Twitter</a>
</div>
</body>
</html>

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 0 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -0,0 +1,9 @@
FactoryGirl.define do
factory :notification do
target_type "MyString"
target_id 1
recipient_id 1
new false
end
end

View File

@ -0,0 +1,28 @@
include Warden::Test::Helpers
Warden.test_mode!
feature "User profile page", :devise do
after :each do
Warden.test_reset!
end
scenario "user gets followed", js: true do
me = FactoryGirl.create(:user)
other = FactoryGirl.create(:user)
login_as me, scope: :user
visit show_user_profile_path(other.screen_name)
page.driver.render Rails.root.join("tmp/#{Time.now.to_i}_1.png"), full: true
click_button "Follow"
wait_for_ajax
page.driver.render Rails.root.join("tmp/#{Time.now.to_i}_2.png"), full: true
expect(page).to have_text("FOLLOWING")
click_link 'Followers'
page.driver.render Rails.root.join("tmp/#{Time.now.to_i}_3.png"), full: true
expect(page).to have_text(me.screen_name)
end
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe Notification, :type => :model do
pending "add some examples to (or delete) #{__FILE__}"
end