Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ gem 'premailer-rails', '~> 1.11'
group :test do
gem 'minitest', '~> 5.25.2'
gem 'minitest-ci', '~> 3.4.0'
gem 'minitest-stub_any_instance', '~> 1.0.3'
gem 'rails-controller-testing', '~> 1.0'
gem 'simplecov', '~> 0.21'
gem 'simplecov_json_formatter', '~> 0.1'
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ GEM
minitest (5.25.5)
minitest-ci (3.4.0)
minitest (>= 5.0.6)
minitest-stub_any_instance (1.0.3)
mize (0.6.1)
msgpack (1.8.3)
mutex_m (0.3.0)
Expand Down Expand Up @@ -581,6 +582,7 @@ DEPENDENCIES
mime-types (~> 3.7)
minitest (~> 5.25.2)
minitest-ci (~> 3.4.0)
minitest-stub_any_instance (~> 1.0.3)
msgpack (~> 1.8)
mutex_m (~> 0.3)
mysql2 (~> 0.5.4)
Expand Down
2 changes: 1 addition & 1 deletion app/assets/javascripts/moderator.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
$(() => {
$('.js-convert-to-comment, .js-toggle-comments, .js-feature-post, .js-lock').on('ajax:success', (_ev) => {
$('.js-convert-to-comment, .js-toggle-comments, .js-feature-post, .js-lock, .js-unfeature-post').on('ajax:success', (_ev) => {
location.reload();
});

Expand Down
9 changes: 6 additions & 3 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def setup_user
def pull_pinned_links_and_hot_questions
@pinned_links = Rails.cache.fetch_collection('pinned_links', expires_in: 2.hours) do
Rack::MiniProfiler.step 'pinned_links: cache miss' do
PinnedLink.where(active: true).where('shown_before IS NULL OR shown_before > NOW()').all
PinnedLink.where(active: true).current.all
end
end

Expand Down Expand Up @@ -398,11 +398,14 @@ def authenticate_user!(_fav = nil, **_opts)
unless user_signed_in?
respond_to do |format|
format.html do
flash[:error] = 'You need to sign in or sign up to continue.'
flash[:error] = I18n.t('users.errors.unauthorized')
redirect_to_sign_in
end
format.json do
render json: { error: 'You need to sign in or sign up to continue.' }, status: 401
render json: { status: 'failed',
success: false,
error: I18n.t('users.errors.unauthorized') },
status: :unauthorized
end
end
end
Expand Down
23 changes: 5 additions & 18 deletions app/controllers/pinned_links_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,8 @@ def create
@link = PinnedLink.new(pinned_link_params)

if @link.save
attr = @link.attributes_print

AuditLog.moderator_audit(event_type: 'pinned_link_create',
related: @link,
user: current_user,
comment: "<<PinnedLink #{attr}>>")

flash[:success] =
'Your pinned link has been created. Due to caching, it may take some time until it is shown.'
PinnedLink.audit_create(current_user, @link)
flash[:success] = helpers.i18ns('pinned_links.success.create')
redirect_to pinned_links_path
else
render 'pinned_links/new', status: :bad_request
Expand All @@ -63,17 +56,11 @@ def create
def edit; end

def update
before = @link.attributes_print
before = @link.dup

if @link.update(pinned_link_params)
after = @link.attributes_print

AuditLog.moderator_audit(event_type: 'pinned_link_update',
related: @link,
user: current_user,
comment: "from <<PinnedLink #{before}>>\nto <<PinnedLink #{after}>>")

flash[:success] = 'The pinned link has been updated. Due to caching, it may take some time until it is shown.'
PinnedLink.audit_update(current_user, before, @link)
flash[:success] = helpers.i18ns('pinned_links.success.update')
redirect_to pinned_links_path
else
render 'pinned_links/edit', status: :bad_request
Expand Down
41 changes: 32 additions & 9 deletions app/controllers/posts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ class PostsController < ApplicationController
include DraftManagement

before_action :authenticate_user!, except: [:document, :help_center, :show]
before_action :set_post, only: [:toggle_comments, :feature, :lock, :unlock, :legal_delete]
before_action :set_post, only: [:toggle_comments, :feature, :unfeature, :lock, :unlock, :legal_delete]
before_action :set_scoped_post, only: [:change_category, :show, :edit, :update, :close, :reopen, :delete, :restore]
before_action :verify_moderator, only: [:toggle_comments]
before_action :verify_moderator, only: [:feature, :toggle_comments, :unfeature]
before_action :edit_checks, only: [:edit, :update]
before_action :unless_locked, only: [:edit, :update, :close, :reopen, :delete, :restore]
before_action :verify_staff, only: [:legal_delete]
Expand Down Expand Up @@ -612,24 +612,47 @@ def unlock
end

def feature
return not_found!(errors: ['no_privilege']) unless current_user&.at_least_moderator?

data = {
label: @post.parent.nil? ? @post.title : @post.parent.title,
link: helpers.generic_show_link(@post),
post: @post,
active: true,
community: RequestContext.community
}
@link = PinnedLink.create data

attr = @link.attributes_print
AuditLog.moderator_audit(event_type: 'pinned_link_create', related: @link, user: current_user,
comment: "<<PinnedLink #{attr}>>\n(using moderator tools on post)")
flash[:success] = helpers.i18ns('posts.post_has_been_featured')
@link = PinnedLink.create(data)

PinnedLink.audit_create(current_user, @link)
flash[:success] = helpers.i18ns('posts.success.feature')
render json: { status: 'success', success: true }
end

def unfeature
success = true

ApplicationRecord.transaction do
PinnedLink.where(active: true, post: @post).each do |pinned_link|
before = pinned_link.dup
success = pinned_link.update(active: false)

if success
PinnedLink.audit_update(current_user, before, pinned_link)
else
raise ActiveRecord::Rollback
end
end
end

flash_key = success ? :success : :danger
res_status = success ? :ok : :internal_server_error
i18ns_key = success ? 'success.unfeature' : 'errors.unfeature_generic'
flash[flash_key] = helpers.i18ns("posts.#{i18ns_key}")
status = success ? 'success' : 'failed'

render json: { status: status, success: success },
status: res_status
end

def save_draft
base_key = do_save_draft(current_user, params[:path])
render json: { status: 'success', success: true, key: base_key }
Expand Down
28 changes: 28 additions & 0 deletions app/models/pinned_link.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,34 @@ class PinnedLink < ApplicationRecord
validate :check_period
validate :check_post_or_url

# Creates an audit log for a pinned link create event
# @param user [User] user that created the link
# @param created [PinnedLink] created link
def self.audit_create(user, created)
attrs = created.attributes_print
comment = "<<PinnedLink #{attrs}>>"

AuditLog.moderator_audit(event_type: 'pinned_link_create',
related: created,
user: user,
comment: comment)
end

# Creates an audit log for a pinned link update event
# @param user [User] user that updated the link
# @param before [PinnedLink] state before the update
# @param after [PinnedLink] state after the update
def self.audit_update(user, before, after)
old_attrs = before.attributes_print
new_attrs = after.attributes_print
comment = "from <<PinnedLink #{old_attrs}>>\nto <<PinnedLink #{new_attrs}>>"

AuditLog.moderator_audit(event_type: 'pinned_link_update',
related: after,
user: user,
comment: comment)
end

# Is the link not timed or started in the past & hasn't ended yet?
# @param now [DateTime, nil] timestamp to compare to
# @return [Boolean] check result
Expand Down
7 changes: 7 additions & 0 deletions app/models/post.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class Post < ApplicationRecord
include SoftDeletable
include Timestamped
include UserSortable
include Identity

belongs_to :user, optional: true
belongs_to :post_type
Expand Down Expand Up @@ -243,6 +244,12 @@ def deleted_by_owner?
deleted_by&.same_as?(user)
end

# Is the post currently featured?
# @return [Boolean] check result
def featured?
PinnedLink.current.where(post: self, active: true).any?
end

# Is a given user following the post?
# @param user [User] user to check
# @return [Boolean] check result
Expand Down
33 changes: 1 addition & 32 deletions app/views/layouts/_sidebar.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,8 @@
<% end %>

<% unless @community.is_fake %>
<%# Featured widget %>
<% if Rails.env.development? || @pinned_links.to_a.size > 0 %>
<% has_timed = @pinned_links.any?(&:timed?) %>
<% cache_unless has_timed, @pinned_links do %>
<div class="widget has-margin-4 is-teal">
<% if Rails.env.development? || @pinned_links.to_a.size > 0 %>
<div class="widget--header">Featured</div>
<% @pinned_links.each do |pl| %>
<div class="widget--body">
<% pl_link = pl.post.nil? ? pl.link : generic_share_link(pl.post) %>
<% pl_label = pl.post.nil? ? pl.label : (pl.post.parent.nil? ? pl.post.title : pl.post.parent.title) %>
<%= link_to pl_link, class: 'h-fw-bold' do %>
<%= pl_label %>
<% end %>
<% unless pl.shown_before.nil? %>
<div>
&mdash;
<% if !pl.shown_after.nil? %>
<% if pl.shown_after < DateTime.now %>
ends in <%= time_ago_in_words(pl.shown_before) %>
<% else %>
starts in <%= time_ago_in_words(pl.shown_after) %>
<% end %>
<% else %>
in <%= time_ago_in_words(pl.shown_before) %>
<% end %>
</div>
<% end %>
</div>
<% end %>
<% end %>
</div>
<% end %>
<%= render 'pinned_links/widget', links: @pinned_links %>
<% end %>

<%# Related Posts widget %>
Expand Down
37 changes: 37 additions & 0 deletions app/views/pinned_links/_widget.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<%#
Renders a widget listing currently pinned links

Variables:
links : ActiveRecord::Relation<PinnedLink> collection of pinned links to display
%>

<% has_timed = links.any?(&:timed?) %>

<% cache_unless has_timed, links do %>
<div class="widget has-margin-4 is-teal">
<div class="widget--header">Featured</div>
<% links.each do |pl| %>
<div class="widget--body">
<% pl_link = pl.post.nil? ? pl.link : generic_share_link(pl.post) %>
<% pl_label = pl.post.nil? ? pl.label : (pl.post.parent.nil? ? pl.post.title : pl.post.parent.title) %>
<%= link_to pl_link, class: 'h-fw-bold' do %>
<%= pl_label %>
<% end %>
<% unless pl.shown_before.nil? %>
<div>
&mdash;
<% if !pl.shown_after.nil? %>
<% if pl.shown_after < DateTime.now %>
ends in <%= time_ago_in_words(pl.shown_before) %>
<% else %>
starts in <%= time_ago_in_words(pl.shown_after) %>
<% end %>
<% else %>
in <%= time_ago_in_words(pl.shown_before) %>
<% end %>
</div>
<% end %>
</div>
<% end %>
</div>
<% end %>
20 changes: 20 additions & 0 deletions app/views/posts/_post_tool.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<%#
Renders a single post tool

Variables:
post : the Post instance to render the tool for
name : unique name of the tool
method : HTTP method to use for the tool's action
%>

<details>
<summary><%= t("posts.tools.#{name}") %></summary>
<p><%= t("posts.tools.#{name}_blurb") %></p>
<%= form_tag send("post_#{name}_url", post),
method: method,
remote: true,
class: "js-#{name}-post" do %>
<%= submit_tag t("posts.tools.#{name}_action"),
class: 'button is-filled' %>
<% end %>
</details>
13 changes: 4 additions & 9 deletions app/views/posts/_post_tools.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,10 @@
</details>
<% end %>
<% if at_least_moderator? %>
<details>
<summary>Feature post</summary>
<p>
You can feature this post by linking it in the sidebar. You can edit the link options later in the moderator tools.
</p>
<%= form_tag post_feature_url(post), remote: true, class: 'js-feature-post' do %>
<%= submit_tag 'Feature post', class: 'button is-filled' %>
<% end %>
</details>
<%= render 'post_tool', name: 'feature', method: :post, post: post %>
<% if post.featured? %>
<%= render 'post_tool', name: 'unfeature', method: :delete, post: post %>
<% end %>
<% end %>
<% if current_user&.privilege?('flag_curate') && top_level_post_types.include?(post.post_type_id) %>
<details>
Expand Down
9 changes: 9 additions & 0 deletions config/locales/strings/en.pinned_links.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
en:
pinned_links:
success:
create: >
Your pinned link has been created.
It may take some time for the changes to take effect due to caching.
update: >
The pinned link has been updated.
It may take some time for the changes to take effect due to caching.
26 changes: 24 additions & 2 deletions config/locales/strings/en.posts.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
en:
posts:
# TODO: align the rest with other local strings' structure
success:
feature: >
The post has been featured.
It may take some time for the changes to take effect due to caching.
unfeature: >
The post has been unfeatured.
It may take some time for the changes to take effect due to caching.
errors:
feature_generic: >
Failed to feature the post.
unfeature_generic: >
Failed to unfeature the post.
tools:
feature: Feature post
feature_action: Feature
feature_blurb: >
You can feature this post by linking it in the sidebar.
You can edit the link options later in the moderator tools.
unfeature: Unfeature post
unfeature_action: Unfeature
unfeature_blurb: >
This post will no longer be linked in the sidebar.
You can always feature it again in the moderator tools.
#alert/notice
create_requires_post: >
Can't create a post without content. Please try again later or contact us.
Expand All @@ -21,8 +45,6 @@ en:
Can't reopen this post right now. Try again later.
no_edit_changes: >
No changes were saved because you didn't edit the post.
post_has_been_featured: >
Post has been featured. Due to caching, it may take some time until the changes apply.
cant_delete_post: >
Can't delete this post right now. Try again later.
already_deleted: >
Expand Down
2 changes: 2 additions & 0 deletions config/locales/strings/en.users.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ en:
Accounts using 2FA cannot be self-deleted. Disable 2FA first.
self_delete_wrong_username: >
The username you entered was incorrect.
unauthorized: >
You need to sign in or sign up to continue
labels:
view_abilities_self: View your abilities
view_answers_self: View your answers
Expand Down
Loading
Loading