diff --git a/Gemfile b/Gemfile
index ae8dea2..5797428 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,12 +8,10 @@ gemspec
# used for dummy rails app integration
gem "devise"
gem "puma"
-gem "sprockets-rails"
-
-# testing against sqlite3 db
-gem "sqlite3", "~> 1.7"
# testing
gem "appraisal"
gem "standardrb"
gem "font-awesome-sass", "~> 5.13.1"
+gem "sqlite3"
+gem "sprockets-rails"
diff --git a/README.md b/README.md
index 0433edc..e07c58b 100644
--- a/README.md
+++ b/README.md
@@ -132,6 +132,10 @@ By default, SimpleDiscussion will attempt to send email and slack notifications
SimpleDiscussion.setup do |config|
config.send_email_notifications = false # Default: true
config.send_slack_notifications = false # Default: true
+
+ config.markdown_circuit_embed = false # Default: false
+ config.markdown_user_tagging = false # Default: false
+ config.markdown_video_embed = false # Default false
end
```
diff --git a/app/assets/stylesheets/simple_discussion.scss b/app/assets/stylesheets/simple_discussion.scss
index c63f794..b82fb80 100644
--- a/app/assets/stylesheets/simple_discussion.scss
+++ b/app/assets/stylesheets/simple_discussion.scss
@@ -248,8 +248,13 @@
border: 1px solid #80808029;
.card-body {
+ overflow-x: auto;
margin-top: 16px;
}
+ .card-body iframe {
+ border-radius: $post-body-border-radius;
+ border: 2px solid #80808029;
+ }
}
// Formatting the listtile for user details
@@ -296,3 +301,41 @@
.thread-page-container {
padding: 24px;
}
+
+.preview::before {
+ content: "Preview";
+ width: 80px;
+}
+
+p {
+ font-size: 18px;
+}
+
+blockquote {
+ border-left: 5px solid #e9ecef;
+ padding: 10px 20px;
+ margin: 10px 0;
+ font-size: 18px;
+ font-weight: 500;
+ color: #6c757d;
+}
+
+.CodeMirror-line span {
+ font-size: 18px;
+}
+
+.simple_discussion .leaderboard .user-avatar {
+ border-radius: 50%;
+ width: 56px;
+ height: 56px;
+ overflow: hidden;
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+}
+
+.simple_discussion .leaderboard {
+ margin-top: 24px;
+}
diff --git a/app/controllers/simple_discussion/application_controller.rb b/app/controllers/simple_discussion/application_controller.rb
index e464f26..4d1a8cc 100644
--- a/app/controllers/simple_discussion/application_controller.rb
+++ b/app/controllers/simple_discussion/application_controller.rb
@@ -34,6 +34,12 @@ def require_mod_or_author_for_thread!
end
end
+ def require_mod!
+ unless is_moderator?
+ redirect_to_root
+ end
+ end
+
private
def redirect_to_root
diff --git a/app/controllers/simple_discussion/forum_posts_controller.rb b/app/controllers/simple_discussion/forum_posts_controller.rb
index 23e59e9..9507ac2 100644
--- a/app/controllers/simple_discussion/forum_posts_controller.rb
+++ b/app/controllers/simple_discussion/forum_posts_controller.rb
@@ -5,15 +5,26 @@ class SimpleDiscussion::ForumPostsController < SimpleDiscussion::ApplicationCont
before_action :require_mod_or_author_for_post!, only: [:edit, :update, :destroy]
before_action :require_mod_or_author_for_thread!, only: [:solved, :unsolved]
+ POINTS = {
+ create_post: 10, # on forum post creation
+ delete_post: -10, # on forum post deletion
+ marked_as_solution: 100, # if forum thread author/moderator marked the post as solved
+ unmarked_as_solution: -100, # undoing the marked as solution
+ delete_reported_post: -100 # if moderator deletes the post hence it is spam post
+ }
+
def create
@forum_post = @forum_thread.forum_posts.new(forum_post_params)
@forum_post.user_id = current_user.id
- if @forum_post.save
- SimpleDiscussion::ForumPostNotificationJob.perform_later(@forum_post)
- redirect_to simple_discussion.forum_thread_path(@forum_thread, anchor: "forum_post_#{@forum_post.id}")
- else
- render template: "simple_discussion/forum_threads/show", status: :unprocessable_entity
+ ActiveRecord::Base.transaction do
+ if @forum_post.save
+ update_leaderboard(current_user, POINTS[:create_post])
+ SimpleDiscussion::ForumPostNotificationJob.perform_later(@forum_post)
+ redirect_to simple_discussion.forum_thread_path(@forum_thread, anchor: "forum_post_#{@forum_post.id}")
+ else
+ render template: "simple_discussion/forum_threads/show", status: :unprocessable_entity
+ end
end
end
@@ -29,29 +40,73 @@ def update
end
def destroy
- @forum_post.destroy!
- redirect_to simple_discussion.forum_thread_path(@forum_thread)
+ # if @forum_post is first post of forum_thread then we need to destroy forum_thread
+ is_first_post = @forum_thread.forum_posts.first == @forum_post
+
+ ActiveRecord::Base.transaction do
+ if is_first_post
+ @forum_thread.destroy!
+ else
+ @forum_post.destroy!
+ end
+
+ # leaderboard points distribution
+ if is_moderator? && (@forum_post.user != current_user)
+ update_leaderboard(@forum_post.user, POINTS[:delete_reported_post])
+ # further we can distribute points if needed to the user who reported the post
+
+ # spam_report = SpamReport.find_by(forum_post: @forum_post)
+ # update_leaderboard(spam_report.user, POINTS[:report_spam]) if spam_report
+ else
+ update_leaderboard(@forum_post.user, POINTS[:delete_post])
+ end
+ end
+
+ redirect_to redirect_after_delete_path(is_first_post)
end
def solved
- @forum_post = @forum_thread.forum_posts.find(params[:id])
+ ActiveRecord::Base.transaction do
+ @forum_post = @forum_thread.forum_posts.find(params[:id])
+
+ # update the previously solved post's author's leaderboard points
+ previously_solved_posts = @forum_thread.forum_posts.where(solved: true)
+ previously_solved_posts.each do |post|
+ update_user_leaderboard(post.user, POINTS[:unmarked_as_solution])
+ end
- @forum_thread.forum_posts.update_all(solved: false)
- @forum_post.update_column(:solved, true)
- @forum_thread.update_column(:solved, true)
+ # update the current post's author leaderboard points
+ update_leaderboard(@forum_post.user, POINTS[:marked_as_solution])
+ @forum_thread.forum_posts.update_all(solved: false)
+ @forum_post.update_column(:solved, true)
+ @forum_thread.update_column(:solved, true)
+ end
redirect_to simple_discussion.forum_thread_path(@forum_thread, anchor: ActionView::RecordIdentifier.dom_id(@forum_post))
end
def unsolved
- @forum_post = @forum_thread.forum_posts.find(params[:id])
-
- @forum_thread.forum_posts.update_all(solved: false)
- @forum_thread.update_column(:solved, false)
+ ActiveRecord::Base.transaction do
+ @forum_post = @forum_thread.forum_posts.find(params[:id])
+ update_leaderboard(@forum_post.user, POINTS[:unmarked_as_solution])
+ @forum_thread.forum_posts.update_all(solved: false)
+ @forum_thread.update_column(:solved, false)
+ end
redirect_to simple_discussion.forum_thread_path(@forum_thread, anchor: ActionView::RecordIdentifier.dom_id(@forum_post))
end
+ def report_spam
+ @forum_post = @forum_thread.forum_posts.find(params[:id])
+ @spam_report = SpamReport.new(forum_post: @forum_post, user: current_user, reason: params[:reason], details: params[:details])
+
+ if @spam_report.save
+ redirect_to simple_discussion.forum_thread_path(@forum_thread, anchor: ActionView::RecordIdentifier.dom_id(@forum_post))
+ else
+ render template: "simple_discussion/forum_threads/show"
+ end
+ end
+
private
def set_forum_thread
@@ -69,4 +124,20 @@ def set_forum_post
def forum_post_params
params.require(:forum_post).permit(:body)
end
+
+ def update_leaderboard(user, points)
+ leaderboard = user.forum_leaderboard || user.build_forum_leaderboard
+ leaderboard.points += points
+ leaderboard.save!
+ end
+
+ def redirect_after_delete_path(is_first_post)
+ if params[:from] == "moderators_page"
+ simple_discussion.spam_reports_forum_threads_path
+ elsif is_first_post
+ simple_discussion.root_path
+ else
+ simple_discussion.forum_thread_path(@forum_thread)
+ end
+ end
end
diff --git a/app/controllers/simple_discussion/forum_threads_controller.rb b/app/controllers/simple_discussion/forum_threads_controller.rb
index 570597a..eb8d981 100644
--- a/app/controllers/simple_discussion/forum_threads_controller.rb
+++ b/app/controllers/simple_discussion/forum_threads_controller.rb
@@ -2,6 +2,13 @@ class SimpleDiscussion::ForumThreadsController < SimpleDiscussion::ApplicationCo
before_action :authenticate_user!, only: [:mine, :participating, :new, :create]
before_action :set_forum_thread, only: [:show, :edit, :update, :destroy]
before_action :require_mod_or_author_for_thread!, only: [:edit, :update, :destroy]
+ before_action :require_mod!, only: [:spam_reports]
+
+ POINTS = {
+ create_thread: 20, # on forum thread creation
+ delete_thread: -20, # on forum thread deletion
+ delete_reported_thread_by_moderator: -100 # if moderator deletes the thread hence it is spam post
+ }
def index
@forum_threads = ForumThread.pinned_first.sorted.includes(:user, :forum_category).paginate(page: page_number)
@@ -27,6 +34,15 @@ def participating
render action: :index
end
+ def spam_reports
+ @spam_reports = SpamReport.includes(:forum_post).paginate(page: page_number)
+ render action: :spam_reports
+ end
+
+ def leaderboard
+ @ranked_users = ForumLeaderboard.order(points: :desc).paginate(page: page_number)
+ end
+
def show
@forum_post = ForumPost.new
@forum_post.user = current_user
@@ -41,11 +57,14 @@ def create
@forum_thread = current_user.forum_threads.new(forum_thread_params)
@forum_thread.forum_posts.each { |post| post.user_id = current_user.id }
- if @forum_thread.save
- SimpleDiscussion::ForumThreadNotificationJob.perform_later(@forum_thread)
- redirect_to simple_discussion.forum_thread_path(@forum_thread)
- else
- render action: :new, status: :unprocessable_entity
+ ActiveRecord::Base.transaction do
+ if @forum_thread.save
+ update_leaderboard(current_user, POINTS[:create_thread])
+ SimpleDiscussion::ForumThreadNotificationJob.perform_later(@forum_thread)
+ redirect_to simple_discussion.forum_thread_path(@forum_thread)
+ else
+ render action: :new, status: :unprocessable_entity
+ end
end
end
@@ -61,7 +80,15 @@ def update
end
def destroy
- @forum_thread.destroy!
+ ActiveRecord::Base.transaction do
+ @forum_thread.destroy!
+ if is_moderator? && (@forum_thread.user != current_user)
+ update_leaderboard(@forum_thread.user, POINTS[:delete_reported_thread_by_moderator])
+ else
+ update_leaderboard(@forum_thread.user, POINTS[:delete_thread])
+ end
+ end
+
redirect_to simple_discussion.forum_threads_path
end
@@ -74,4 +101,10 @@ def set_forum_thread
def forum_thread_params
params.require(:forum_thread).permit(:title, :forum_category_id, forum_posts_attributes: [:body])
end
+
+ def update_leaderboard(user, points)
+ leaderboard = user.forum_leaderboard || user.build_forum_leaderboard
+ leaderboard.points += points
+ leaderboard.save!
+ end
end
diff --git a/app/helpers/simple_discussion/forum_posts_helper.rb b/app/helpers/simple_discussion/forum_posts_helper.rb
index c63c4a6..f642614 100644
--- a/app/helpers/simple_discussion/forum_posts_helper.rb
+++ b/app/helpers/simple_discussion/forum_posts_helper.rb
@@ -1,3 +1,46 @@
+require "redcarpet"
+class CustomRenderer < Redcarpet::Render::HTML
+ def initialize(circuit_embed: false, video_embed: false, user_tagging: false)
+ @circuit_embed = circuit_embed
+ @video_embed = video_embed
+ @user_tagging = user_tagging
+ super()
+ end
+
+ def image(url, title, alt_text)
+ case alt_text
+ when "Circuit"
+ if @circuit_embed
+ "
"
+ else
+ "
"
+ end
+ when "Video"
+ if @video_embed
+ video_id = url.split("v=")[1].split("&")[0]
+ "
"
+ else
+ "
"
+ end
+ else
+ # default image rendering
+ "
"
+ end
+ end
+
+ def link(link, _title, content)
+ if @user_tagging && link.start_with?("/users/")
+ uri = URI.parse(link)
+ uri.path =~ %r{^/users/\d+/?$}
+ # remove the brackets from the content
+ content = content.gsub(/[()]/, "")
+ "#{content}"
+ else
+ "#{content}"
+ end
+ end
+end
+
module SimpleDiscussion::ForumPostsHelper
def category_link(category)
link_to category.name, simple_discussion.forum_category_forum_threads_path(category),
@@ -6,7 +49,20 @@ def category_link(category)
# Override this method to provide your own content formatting like Markdown
def formatted_content(text)
- simple_format(text)
+ options = {
+ hard_wrap: true,
+ filter_html: true,
+ autolink: true,
+ tables: true
+ }
+
+ renderer = CustomRenderer.new(
+ circuit_embed: SimpleDiscussion.markdown_circuit_embed,
+ video_embed: SimpleDiscussion.markdown_video_embed,
+ user_tagging: SimpleDiscussion.markdown_user_tagging
+ )
+ markdown = Redcarpet::Markdown.new(renderer, options)
+ markdown.render(text).html_safe
end
def forum_post_classes(forum_post)
diff --git a/app/models/forum_leaderboard.rb b/app/models/forum_leaderboard.rb
new file mode 100644
index 0000000..c7aa296
--- /dev/null
+++ b/app/models/forum_leaderboard.rb
@@ -0,0 +1,3 @@
+class ForumLeaderboard < ApplicationRecord
+ belongs_to :user
+end
diff --git a/app/models/forum_post.rb b/app/models/forum_post.rb
index dfd9202..80ccced 100644
--- a/app/models/forum_post.rb
+++ b/app/models/forum_post.rb
@@ -2,6 +2,7 @@
class ForumPost < ApplicationRecord
belongs_to :forum_thread, counter_cache: true, touch: true
belongs_to :user
+ has_many :spam_reports, dependent: :destroy
validates :user_id, :body, presence: true
validate :clean_body, if: -> { SimpleDiscussion.profanity_filter }
diff --git a/app/models/spam_report.rb b/app/models/spam_report.rb
new file mode 100644
index 0000000..bc9b113
--- /dev/null
+++ b/app/models/spam_report.rb
@@ -0,0 +1,15 @@
+class SpamReport < ApplicationRecord
+ belongs_to :forum_post
+ belongs_to :user
+
+ validates :forum_post_id, :user_id, :reason, presence: true
+ validates :details, presence: true, if: -> { reason == "others" }
+
+ enum reason: {
+ sexual_content: 0,
+ violent_content: 1,
+ irrelevant_content: 2,
+ misleading_content: 3,
+ others: 4
+ }
+end
diff --git a/app/views/layouts/simple_discussion.html.erb b/app/views/layouts/simple_discussion.html.erb
index 80f1e73..b98ec6e 100644
--- a/app/views/layouts/simple_discussion.html.erb
+++ b/app/views/layouts/simple_discussion.html.erb
@@ -41,6 +41,22 @@
<%= t('.unanswered') %>
<% end %>
+
+ <%= forum_link_to simple_discussion.leaderboard_forum_threads_path do %>
+
+ <%= spam_report.forum_post.user.name %> <%= forum_user_badge(spam_report.forum_post.user) %> +
+<%= t('on') %> <%= spam_report.forum_post.created_at.strftime("%b %d, %Y") %>
+Forum Post | +Reason | +Reported by | +View in Thread | +Delete | +
---|