Skip to content
Open
Show file tree
Hide file tree
Changes from 10 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
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
38 changes: 29 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,44 @@ 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
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 }
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
2 changes: 0 additions & 2 deletions config/locales/strings/es.posts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ es:
No se puede reabrir esta publicación ahora mismo. Inténtelo de nuevo más tarde.
no_edit_changes: >
Ningún cambio ha sido salvado porque usted no ha editado la publicación.
post_has_been_featured: >

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know Spanish and refuse to use genai-in-a-trenchcoat services for "translation", so if anyone can help creating es.pinned_links.yml and adjusting es.posts.yml, it'd be appreciated.

La publicación ha sido destacada. Debido a cacheo, podría llevar algo de tiempo hasta que los cambios estén aplicados.
cant_delete_post: >
No se puede eliminar esta publicación ahora mismo. Inténtelo de nuevo más tarde.
already_deleted: >
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
post ':id/lock', to: 'posts#lock', as: :post_lock
post ':id/unlock', to: 'posts#unlock', as: :post_unlock
post ':id/feature', to: 'posts#feature', as: :post_feature
delete ':id/feature', to: 'posts#unfeature', as: :post_unfeature
post ':id/promote', to: 'moderator#nominate_promotion', as: :promote_post
delete ':id/promote', to: 'moderator#remove_promotion', as: :remove_post_promotion
delete ':id/legal-delete', to: 'posts#legal_delete', as: :legal_delete_post
Expand Down
Loading
Loading