diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 359d66c..78cbb07 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -569,8 +569,6 @@ table.data th { background-color: #eaeaea; border-bottom: 1px solid #cacaca; border-top: 1px solid #cacaca; - padding: 2px; - padding-left: 3px; text-align: left; } table.data th img { @@ -583,10 +581,13 @@ table.data th.r, table.data td.r { padding-right: 3px; } +table.data th, table.data td { - padding-left: 3px; - padding-top: 4px; - padding-bottom: 3px; + padding: 0.25em 0.5em; +} + +table.data tr.bold td { + font-weight: bold; } table.thread td { @@ -600,11 +601,13 @@ table.thread td img { vertical-align: middle; } -table.data tr.row0 td { +table.data tr.row0 td, +table.data.zebra tr:nth-child(even) td { background-color: #f8f8f8; border-bottom: 1px solid #eaeaea; } -table.data tr.row1 td { +table.data tr.row1 td, +table.data.zebra tr:nth-child(odd) td { background-color: #f5f5f5; border-bottom: 1px solid #eaeaea; } @@ -642,6 +645,16 @@ table.data tr.void td, table.data tr.void td a { font-weight: bold; font-size: 11pt; } +.box .sublegend { + font-size: 10pt; + font-weight: normal; + font-style: italic; + color: gray; +} +.box .legend.right { + float: right; +} + .box .boxtitle { background-color: #f0f0f0; border-bottom: 1px solid #cacaca; diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index a502791..1fbe9f1 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -1,2 +1,73 @@ class MessagesController < ApplicationController + before_filter :require_logged_in_user + before_filter :find_message, :only => [ :show, :destroy, :keep_as_new ] + + def index + @new_message = Message.new + end + + def create + @new_message = Message.new(params[:message]) + @new_message.author_user_id = @user.id + + if @new_message.save + flash.now[:success] = "Your message has been sent to " << + @new_message.recipient.username + + @new_message = Message.new + end + + render :action => "index" + end + + def show + @new_message = Message.new + @new_message.recipient_username = (@message.author_user_id == @user.id ? + @message.recipient.username : @message.author.username) + + if @message.recipient_user_id == @user.id + @message.has_been_read = true + @message.save + end + + if @message.subject.match(/^re:/i) + @new_message.subject = @message.subject + else + @new_message.subject = "Re: #{@message.subject}" + end + end + + def destroy + if @message.author_user_id == @user.id + @message.deleted_by_author = true + end + + if @message.recipient_user_id == @user.id + @message.deleted_by_recipient = true + end + + @message.save + + flash[:success] = "Deleted message" + return redirect_to "/messages" + end + + def keep_as_new + @message.has_been_read = false + @message.save + + return redirect_to "/messages" + end + +private + def find_message + if @message = Message.find_by_short_id(params[:message_id ] || params[:id]) + if !(@message.author_user_id == @user.id || + @message.recipient_user_id == @user.id) + flash[:error] = "Could not find message" + redirect_to "/messages" + return false + end + end + end end diff --git a/app/mailers/email_message.rb b/app/mailers/email_message.rb new file mode 100644 index 0000000..75bb3be --- /dev/null +++ b/app/mailers/email_message.rb @@ -0,0 +1,12 @@ +class EmailMessage < ActionMailer::Base + default :from => "nobody@lobste.rs" + + def notify(message, user) + @message = message + @user = user + + mail(:to => user.email, :from => "Lobsters ", + :subject => "[Lobsters] Private Message from " << + "#{message.author.username}: #{message.subject}") + end +end diff --git a/app/models/comment.rb b/app/models/comment.rb index b4cb8a1..3a94c1f 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -12,7 +12,7 @@ class Comment < ActiveRecord::Base :indent_level, :highlighted before_create :assign_short_id_and_upvote - after_create :assign_votes, :mark_submitter, :email_reply + after_create :assign_votes, :mark_submitter, :deliver_reply_notifications after_destroy :unassign_votes MAX_EDIT_MINS = 45 @@ -32,12 +32,14 @@ class Comment < ActiveRecord::Base end def assign_short_id_and_upvote - (1...10).each do |tries| - if tries == 10 + 10.times do |try| + if try == 10 raise "too many hash collisions" end - if !Comment.find_by_short_id(self.short_id = Utils.random_str(6)) + self.short_id = Utils.random_str(6) + + if !Comment.find_by_short_id(self.short_id) break end end @@ -56,14 +58,14 @@ class Comment < ActiveRecord::Base Keystore.increment_value_for("user:#{self.user_id}:comments_posted") end - def email_reply + def deliver_reply_notifications begin if self.parent_comment_id && u = self.parent_comment.try(:user) if u.email_replies? EmailReply.reply(self, u).deliver end - if u.pushover_replies? + if u.pushover_replies? && u.pushover_user_key.present? Pushover.push(u.pushover_user_key, u.pushover_device, { :title => "Lobsters reply from #{self.user.username} on " << "#{self.story.title}", diff --git a/app/models/message.rb b/app/models/message.rb new file mode 100644 index 0000000..0b34758 --- /dev/null +++ b/app/models/message.rb @@ -0,0 +1,93 @@ +class Message < ActiveRecord::Base + belongs_to :recipient, + :class_name => "User", + :foreign_key => "recipient_user_id" + belongs_to :author, + :class_name => "User", + :foreign_key => "author_user_id" + + validates_presence_of :recipient + validates_presence_of :author + + attr_accessor :recipient_username + + attr_accessible :recipient_username, :subject, :body + + validates_length_of :subject, :in => 1..150 + validates_length_of :body, :maximum => (64 * 1024) + + before_create :assign_short_id + after_create :deliver_reply_notifications + after_save :check_for_both_deleted + after_save :update_unread_counts + + def assign_short_id + 10.times do |try| + if try == 10 + raise "too many hash collisions" + end + + self.short_id = Utils.random_str(6) + + if !Message.find_by_short_id(self.short_id) + break + end + end + end + + def check_for_both_deleted + if self.deleted_by_author && self.deleted_by_recipient + self.destroy + end + end + + def update_unread_counts + self.recipient.update_unread_message_count! + end + + def deliver_reply_notifications + begin + if self.recipient.email_messages? + EmailMessage.notify(self, self.recipient).deliver + end + + if self.recipient.pushover_messages? && + self.recipient.pushover_user_key.present? + Pushover.push(self.recipient.pushover_user_key, + self.recipient.pushover_device, { + :title => "Lobsters message from #{self.author.username}: " << + "#{self.subject}", + :message => self.plaintext_body, + :url => self.url, + :url_title => "Reply to #{self.author.username}", + }) + end + rescue + end + end + + def recipient_username=(username) + self.recipient_user_id = nil + + if u = User.find_by_username(username) + self.recipient_user_id = u.id + @recipient_username = username + else + errors.add(:recipient_username, "is not a valid user") + end + end + + def linkified_body + RDiscount.new(self.body.to_s, :smart, :autolink, :safelink, + :filter_html).to_html + end + + def plaintext_body + # TODO: linkify then strip tags and convert entities back + self.body.to_s + end + + def url + Rails.application.routes.url_helpers.root_url + "messages/#{self.short_id}" + end +end diff --git a/app/models/story.rb b/app/models/story.rb index e2f9882..18b240a 100644 --- a/app/models/story.rb +++ b/app/models/story.rb @@ -18,10 +18,9 @@ class Story < ActiveRecord::Base attr_accessor :vote, :story_type, :already_posted_story, :fetched_content attr_accessor :new_tags, :tags_to_add, :tags_to_delete - after_save :deal_with_tags - before_create :assign_short_id after_create :mark_submitter + after_save :deal_with_tags validate do if self.url.present? @@ -47,12 +46,13 @@ class Story < ActiveRecord::Base end def assign_short_id - (1...10).each do |tries| - if tries == 10 + 10.times do |try| + if try == 10 raise "too many hash collisions" end self.short_id = Utils.random_str(6) + if !Story.find_by_short_id(self.short_id) break end diff --git a/app/models/user.rb b/app/models/user.rb index a1fba25..2d40eeb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,6 +1,13 @@ class User < ActiveRecord::Base has_many :stories, :include => :user + has_many :comments + has_many :authored_messages, + :class_name => "Message", + :foreign_key => "author_user_id" + has_many :received_messages, + :class_name => "Message", + :foreign_key => "recipient_user_id" has_secure_password validates_format_of :username, :with => /\A[A-Za-z0-9][A-Za-z0-9_-]*\Z/ @@ -13,7 +20,7 @@ class User < ActiveRecord::Base attr_accessible :username, :email, :password, :password_confirmation, :about, :email_replies, :pushover_replies, :pushover_user_key, - :pushover_device + :pushover_device, :email_messages, :pushover_messages before_save :check_session_token @@ -24,8 +31,13 @@ class User < ActiveRecord::Base end def unread_message_count - 0 - #Message.where(:recipient_user_id => self.id, :has_been_read => 0).count + Keystore.value_for("user:#{self.id}:unread_messages").to_i + end + + def update_unread_message_count! + Keystore.put("user:#{self.id}:unread_messages", + Message.where(:recipient_user_id => self.id, + :has_been_read => false).count) end def karma diff --git a/app/views/email_message/notify.text.erb b/app/views/email_message/notify.text.erb new file mode 100644 index 0000000..6b2d768 --- /dev/null +++ b/app/views/email_message/notify.text.erb @@ -0,0 +1,3 @@ + <%= word_wrap(@comment.plaintext_body, :line_width => 72).gsub(/\n/, "\n ") %> + +Reply at <%= @message.url %> diff --git a/app/views/email_reply/reply.text.erb b/app/views/email_reply/reply.text.erb index 58e1400..176cf07 100644 --- a/app/views/email_reply/reply.text.erb +++ b/app/views/email_reply/reply.text.erb @@ -2,6 +2,4 @@ <%= word_wrap(@comment.plaintext_comment, :line_width => 72).gsub(/\n/, "\n ") %> -You can view this reply at: - -<%= @comment.url %> +Continue this discussion at <%= @comment.url %> diff --git a/app/views/global/_header.html.erb b/app/views/global/_header.html.erb index 1201140..2937f74 100644 --- a/app/views/global/_header.html.erb +++ b/app/views/global/_header.html.erb @@ -1,16 +1,15 @@