enable mod_userdir

diff of `rails routes`:

12,13d11
<                              GET      /newest/:user(.:format)                        home#newest_by_user
<                              GET      /newest/:user/page/:page(.:format)             home#newest_by_user
32d29
<                 user_threads GET      /threads/:user(.:format)                       comments#user_threads
120,122c117,132
<                            u GET      /u(.:format)                                   users#tree
<                         user GET      /u/:username(.:format)                         users#show
<                user_standing GET      /u/:username/standing(.:format)                users#standing
---
>                   users_tree GET      /users(.:format)                               users#tree
>                         user GET      /~:username(.:format)                          users#show
>                user_standing GET      /~:username/standing(.:format)                 users#standing
>                              GET      /~:user/stories(/page/:page)(.:format)         home#newest_by_user
>                 user_threads GET      /~:user/threads(.:format)                      comments#user_threads
>                     user_ban POST     /~:username/ban(.:format)                      users#ban
>                   user_unban POST     /~:username/unban(.:format)                    users#unban
>          user_disable_invite POST     /~:username/disable_invitation(.:format)       users#disable_invitation
>           user_enable_invite POST     /~:username/enable_invitation(.:format)        users#enable_invitation
>                            u GET      /u(.:format)                                   redirect(302, /users)
>                              GET      /u/:username(.:format)                         redirect(302, /~%{username})
>                              GET      /@:username(.:format)                          redirect(302, /~%{username})
>                              GET      /u/:username/standing(.:format)                redirect(302, ~%{username}/standing)
>                              GET      /newest/:user(.:format)                        redirect(302, ~%{user}/stories)
>                              GET      /newest/:user(/page/:page)(.:format)           redirect(302, ~%{user}/stories/page/%{page})
>                              GET      /threads/:user(.:format)                       redirect(302, ~%{user}/threads)
125,128d134
<                     user_ban POST     /users/:username/ban(.:format)                 users#ban
<                   user_unban POST     /users/:username/unban(.:format)               users#unban
<          user_disable_invite POST     /users/:username/disable_invitation(.:format)  users#disable_invitation
<           user_enable_invite POST     /users/:username/enable_invitation(.:format)   users#enable_invitation
This commit is contained in:
Peter Bhat Harkins 2023-08-29 07:45:28 -05:00
parent f66f01c9fd
commit 9fff6ae927
30 changed files with 181 additions and 143 deletions

View File

@ -1246,7 +1246,7 @@ ul.user_tree {
color: var(--color-fg-contrast-4-5); color: var(--color-fg-contrast-4-5);
} }
/* /u/:username/standing */ /* /~:username/standing */
/* https://codepen.io/sosuke/pen/Pjoqqp */ /* https://codepen.io/sosuke/pen/Pjoqqp */
.unwarned { .unwarned {
filter: invert(36%) sepia(52%) saturate(0%) hue-rotate(292deg) brightness(96%) contrast(85%); filter: invert(36%) sepia(52%) saturate(0%) hue-rotate(292deg) brightness(96%) contrast(85%);

View File

@ -44,8 +44,8 @@ class KeybaseProofsController < ApplicationController
@contacts = ["admin@#{Keybase.DOMAIN}"] @contacts = ["admin@#{Keybase.DOMAIN}"]
@prefill_url = "#{new_keybase_proof_url}?kb_username=%{kb_username}&" \ @prefill_url = "#{new_keybase_proof_url}?kb_username=%{kb_username}&" \
"kb_signature=%{sig_hash}&kb_ua=%{kb_ua}&username=%{username}" "kb_signature=%{sig_hash}&kb_ua=%{kb_ua}&username=%{username}"
@profile_url = "#{u_url}/%{username}" @profile_url = "/~%{username}"
@check_url = "#{u_url}/%{username}.json" @check_url = "/~%{username}.json"
@logo_black = "https://lobste.rs/small-black-logo.svg" @logo_black = "https://lobste.rs/small-black-logo.svg"
@logo_full = "https://lobste.rs/full-color.logo.svg" @logo_full = "https://lobste.rs/full-color.logo.svg"
@user_re = User.username_regex_s[1...-1] @user_re = User.username_regex_s[1...-1]

View File

@ -30,6 +30,35 @@ module UsersHelper
end end
end end
def styled_user_link user, content = nil, css_classes = []
if content.is_a?(Story) && content.user_is_author?
css_classes.push 'user_is_author'
end
if content.is_a?(Comment) && content.story &&
content.story.user_is_author? && content.story.user_id == user.id
css_classes.push 'user_is_author'
end
if !user.is_active?
css_classes.push 'inactive_user'
end
if user.is_new?
css_classes.push 'new_user'
end
link_to(user.username, user, class: css_classes)
end
def user_karma(user)
if user.is_admin?
"(administrator)"
elsif user.is_moderator?
"(moderator"
else
"(#{user.karma})"
end
end
private private
def user_is_moderator? def user_is_moderator?

View File

@ -353,20 +353,6 @@ class Comment < ApplicationRecord
self.updated_at && (self.updated_at - self.created_at > 1.minute) self.updated_at && (self.updated_at - self.created_at > 1.minute)
end end
def html_class_for_user
c = []
if !self.user.is_active?
c.push "inactive_user"
elsif self.user.is_new?
c.push "new_user"
elsif self.story && self.story.user_is_author? &&
self.story.user_id == self.user_id
c.push "user_is_author"
end
c.join("")
end
def is_deletable_by_user?(user) def is_deletable_by_user?(user)
if user && user.is_moderator? if user && user.is_moderator?
return true return true

View File

@ -478,19 +478,6 @@ class Story < ApplicationRecord
@hider_count ||= HiddenStory.where(:story_id => self.id).count @hider_count ||= HiddenStory.where(:story_id => self.id).count
end end
def html_class_for_user
c = []
if !self.user.is_active?
c.push "inactive_user"
elsif self.user.is_new?
c.push "new_user"
elsif self.user_is_author?
c.push "user_is_author"
end
c.join("")
end
def is_flaggable? def is_flaggable?
if self.created_at && self.score > FLAGGABLE_MIN_SCORE if self.created_at && self.score > FLAGGABLE_MIN_SCORE
Time.current - self.created_at <= FLAGGABLE_DAYS.days Time.current - self.created_at <= FLAGGABLE_DAYS.days

View File

@ -7,8 +7,8 @@
<p> <p>
Lobsters is a computing-focused community centered around link aggregation Lobsters is a computing-focused community centered around link aggregation
and discussion, launched on July 3rd, 2012. and discussion, launched on July 3rd, 2012.
The administrator is <a href="/u/pushcx">Peter Bhat Harkins</a> ("pushcx"), contact him with any support issues. The administrator is <a href="/~pushcx">Peter Bhat Harkins</a> ("pushcx"), contact him with any support issues.
Lobsters was created by <a href="/u/jcs">joshua stein</a> Lobsters was created by <a href="/~jcs">joshua stein</a>
with careful design touches to encourage a healthy community: with careful design touches to encourage a healthy community:
</p> </p>
@ -87,7 +87,7 @@
<h2 id="invitations">Invitation Tree</h2> <h2 id="invitations">Invitation Tree</h2>
<p> <p>
The full <a href="/u">user tree</a> is public and each user's profile The full <%= link_to 'user tree', users_tree_path %> is public and each user's profile
shows who invited them. This provides some degree of accountability and shows who invited them. This provides some degree of accountability and
helps identify voting rings. helps identify voting rings.
</p> </p>
@ -113,7 +113,7 @@
</p> </p>
<p> <p>
The quickest way to receive an invitation is to talk to someone you <a href="/u">recognize from the site</a>. The quickest way to receive an invitation is to talk to someone you <%= link_to 'recognize from the site', users_tree_path %>.
If you wrote a link that was posted, please reach out in <a href="/chat">chat</a>, we'd love to have you join the discussion. If you wrote a link that was posted, please reach out in <a href="/chat">chat</a>, we'd love to have you join the discussion.
Finally, if you can't find anyone you know in the invitation tree and didn't author something posted to the site, Finally, if you can't find anyone you know in the invitation tree and didn't author something posted to the site,
<% if Rails.application.allow_invitation_requests? %> <% if Rails.application.allow_invitation_requests? %>
@ -176,7 +176,7 @@
<h2 id="transparency">Transparency Policy</h2> <h2 id="transparency">Transparency Policy</h2>
<p> <p>
All <a href="/moderations">moderator actions</a> on this site are visible to All <a href="/moderations">moderator actions</a> on this site are visible to
everyone and the identities of those moderators are <a href="/u?moderators=1">public</a>. everyone and the identities of those moderators are <%= link_to 'public', moderators_path %>.
While the individual actions of a moderator may cause debate, there should be no question about if an action happened or who is responsible. While the individual actions of a moderator may cause debate, there should be no question about if an action happened or who is responsible.
</p> </p>

View File

@ -9,11 +9,11 @@
in <tt>#lobsters</tt>. in <tt>#lobsters</tt>.
This channel was originally This channel was originally
<a href="/s/2hdoop">created</a> by <a href="/s/2hdoop">created</a> by
<a href="/u/kristof">@kristof</a> on freenode, and <a href="/~kristof">@kristof</a> on freenode, and
<a href="https://lobste.rs/s/1z77ly/libera_chat#c_vwmpgx">stayed with the network</a> on 2021-05-19 after that domain was lost. <a href="https://lobste.rs/s/1z77ly/libera_chat#c_vwmpgx">stayed with the network</a> on 2021-05-19 after that domain was lost.
<a href="/u/pushcx">@pushcx</a>, <a href="/~pushcx">@pushcx</a>,
<a href="/u/355e3b">@355e3b</a> (<tt>c355e3b</tt> on IRC), and <a href="/~355e3b">@355e3b</a> (<tt>c355e3b</tt> on IRC), and
<a href="/u/aleph">@aleph</a> (<tt>Church</tt> on IRC) are channel operators. <a href="/~aleph">@aleph</a> (<tt>Church</tt> on IRC) are channel operators.
</p> </p>
<p> <p>

View File

@ -49,13 +49,10 @@ class="comment <%= comment.current_vote ? (comment.current_vote[:vote] == 1 ?
<% end %> <% end %>
<% if (@user && @user.show_avatars?) || !@user %> <% if (@user && @user.show_avatars?) || !@user %>
<a href="/u/<%= comment.user.username %>"><%= <%= link_to avatar_img(comment.user, 16), comment.user %>
avatar_img(comment.user, 16) %></a>
<% end %> <% end %>
<a href="/u/<%= comment.user.username %>" <%= styled_user_link comment.user, comment %>
class="<%= comment.html_class_for_user %>"><%=
comment.user.username %></a>
<% if comment.hat %> <% if comment.hat %>
<%= comment.hat.to_html_label %> <%= comment.hat.to_html_label %>

View File

@ -81,13 +81,10 @@ class="comment <%= comment.current_vote ? (comment.current_vote[:vote] == 1 ?
<% end %> <% end %>
<% if (@user && @user.show_avatars?) || !@user %> <% if (@user && @user.show_avatars?) || !@user %>
<a href="/u/<%= comment.user.username %>"><%= <%= link_to avatar_img(comment.user, 16), comment.user %>
avatar_img(comment.user, 16) %></a>
<% end %> <% end %>
<a href="/u/<%= comment.user.username %>" <%= styled_user_link comment.user, comment %>
class="<%= comment.html_class_for_user %>"><%=
comment.user.username %></a>
<% if comment.hat %> <% if comment.hat %>
<%= comment.hat.to_html_label %> <%= comment.hat.to_html_label %>

View File

@ -21,8 +21,7 @@
<% @hat_groups.keys.sort_by{|a| a.downcase }.each do |hg| %> <% @hat_groups.keys.sort_by{|a| a.downcase }.each do |hg| %>
<% @hat_groups[hg].sort_by{|hh| hh.user.username.downcase }.each do |hh| %> <% @hat_groups[hg].sort_by{|hh| hh.user.username.downcase }.each do |hh| %>
<tr> <tr>
<td><a href="/u/<%= hh.user.username %>"><%= hh.user.username <td><%= styled_user_link hh.user %></td>
%></a></td>
<td><%= hh.to_html_label %></td> <td><%= hh.to_html_label %></td>
<td> <td>
<% if hh.link.to_s.match(/^http/) %> <% if hh.link.to_s.match(/^http/) %>

View File

@ -11,7 +11,7 @@
<p> <p>
<div class="boxline"> <div class="boxline">
<%= f.label :user_id, "User:", :class => "required" %> <%= f.label :user_id, "User:", :class => "required" %>
<a href="/u/<%= hr.user.username %>"><%= hr.user.username %></a> <%= styled_user_link hr.user %>
<%= time_ago_in_words(hr.created_at) %> <%= time_ago_in_words(hr.created_at) %>
</div> </div>

View File

@ -1,6 +1,6 @@
<div class="box wide"> <div class="box wide">
<p> <p>
If you don't know (or can't find) an <a href="/u/">existing user</a> from If you don't know (or can't find) an <%= link_to 'existing_user', users_tree_path %> from
whom to request an invitation, you can make a public request for one. This whom to request an invitation, you can make a public request for one. This
will display your name and memo to all other logged-in users who can then will display your name and memo to all other logged-in users who can then
send you an invitation if they recognize you. send you an invitation if they recognize you.

View File

@ -36,7 +36,7 @@
<p> <p>
Not a user yet? Not a user yet?
Read about <a href="/about#invitations">how invitations work</a> and see if you know Read about <a href="/about#invitations">how invitations work</a> and see if you know
<a href="/u">a current user</a> of the site. <%= link_to 'a current user', users_tree_path %> of the site.
The <a href="/chat">chat room</a> does not require an invitation. The <a href="/chat">chat room</a> does not require an invitation.
</p> </p>
<% end %> <% end %>

View File

@ -17,15 +17,13 @@
<div style="white-space:nowrap;"> <div style="white-space:nowrap;">
<% if @direction == :in %> <% if @direction == :in %>
<% if message.author %> <% if message.author %>
<a href="/u/<%= message.author.username %>"><%= <%= styled_user_link message.author %>
message.author.username %></a>
<% else %> <% else %>
<%= message.author_username %> <%= message.author_username %>
<% end %> <% end %>
<%= message.hat.to_html_label if message.hat %> <%= message.hat.to_html_label if message.hat %>
<% else %> <% else %>
<a href="/u/<%= message.recipient.username %>"><%= <%= styled_user_link message.recipient %>
message.recipient.username %></a>
<% end %> <% end %>
</div> </div>
</td> </td>

View File

@ -4,14 +4,13 @@
<h2> <h2>
From From
<% if @message.author %> <% if @message.author %>
<a href="/u/<%= @message.author.username %>"><%= @message.author.username %></a> <%= styled_user_link @message.author %>
<% else %> <% else %>
<%= @message.author_username %> <%= @message.author_username %>
<% end %> <% end %>
<%= @message.hat.to_html_label if @message.hat %> <%= @message.hat.to_html_label if @message.hat %>
to to
<a href="/u/<%= @message.recipient.username %>"><%= <%= styled_user_link @message.recipient %>
@message.recipient.username %></a>
<%= time_ago_in_words_label(@message.created_at) %> <%= time_ago_in_words_label(@message.created_at) %>
</h2> </h2>
@ -57,6 +56,6 @@
<%= render partial: 'form', locals: { new_message: @new_message, replying: true } %> <%= render partial: 'form', locals: { new_message: @new_message, replying: true } %>
<% else %> <% else %>
For help with this message, contact <a href="/u?moderators=1">a moderator</a>. For help with this message, contact <%= link_to 'a moderator', moderators_path %>.
<% end %> <% end %>
</div> </div>

View File

@ -27,8 +27,7 @@
<%= link_to("Category: #{mod.category.category}", mod.category) %> <%= link_to("Category: #{mod.category.category}", mod.category) %>
<% elsif mod.user_id %> <% elsif mod.user_id %>
<% if mod.user %> <% if mod.user %>
<a href="/u/<%= mod.user.username %>">User <%= link_to "User #{mod.user.username}", mod.user %>
<%= mod.user.username %></a>
<% else %> <% else %>
User <%= mod.user_id %> (Deleted) User <%= mod.user_id %> (Deleted)
<% end %> <% end %>

View File

@ -1,5 +1,5 @@
<% content_for :subnav do %> <% content_for :subnav do %>
<a href="/u/<%= @user.username %>">Public Profile</a> <%= link_to 'Public Profile', @user %>
<a href="/filters">Filtered Tags</a> | <a href="/filters">Filtered Tags</a> |
<%= link_post 'Logout', logout_path, confirm: 'Are you sure you want to logout?' %> <%= link_post 'Logout', logout_path, confirm: 'Are you sure you want to logout?' %>
<% end %> <% end %>
@ -311,7 +311,7 @@
<li> <li>
Your comments with negative scores will be deleted, and you can check "disown comments" Your comments with negative scores will be deleted, and you can check "disown comments"
below if you want all of your stories and comments to change to list below if you want all of your stories and comments to change to list
<a href="/u/inactive-user">inactive-user</a> instead of your username. <a href="/~inactive-user">inactive-user</a> instead of your username.
</li> </li>
</ul> </ul>

View File

@ -6,8 +6,8 @@
<% else %> <% else %>
<p> <p>
Signup is currently by invitation only to combat spam and increase Signup is currently by invitation only to combat spam and increase
accountability. If you know <a href="/u/">a current user</a> of the site, accountability. If you know <%= link_to 'a current user', users_tree_path %>
ask them for an invitation, or of the site, ask them for an invitation, or
<% if Rails.application.allow_invitation_requests? %> <% if Rails.application.allow_invitation_requests? %>
<a href="/invitations/request">request one publicly</a>. <a href="/invitations/request">request one publicly</a>.
<% else %> <% else %>

View File

@ -14,8 +14,7 @@
<p> <p>
<%= f.label :invitation, "Invited By:", :class => "required" %> <%= f.label :invitation, "Invited By:", :class => "required" %>
<span class="d"> <span class="d">
<a href="/u/<%= @invitation.user.username %>" target="_blank"><%= <%= link_to @invitation.user.username, user_path(@invitation.user), target: '_blank' %>
@invitation.user.username %></a>
</span> </span>
</p> </p>
<% end %> <% end %>

View File

@ -66,16 +66,14 @@ class="story <%= story.vote && story.vote[:vote] == 1 ? "upvoted" : "" %>
<% end %> <% end %>
<span class="byline"> <span class="byline">
<% if (@user && @user.show_avatars?) || !@user %> <% if (@user && @user.show_avatars?) || !@user %>
<a href="/u/<%= ms.user.username %>"><%= <%= link_to avatar_img(ms.user, 16), ms.user %>
avatar_img(ms.user, 16) %></a>
<% end %> <% end %>
<% if ms.user_is_author? %> <% if ms.user_is_author? %>
<span> authored by </span> <span> authored by </span>
<% else %> <% else %>
<span> via </span> <span> via </span>
<% end %> <% end %>
<a href="/u/<%= ms.user.username %>" class="u-author h-card <%= <%= styled_user_link ms.user, story, ['u-author', 'h-card'] %>
ms.html_class_for_user %>"><%= ms.user.username %></a>
<%= time_ago_in_words_label(ms.created_at) %> <%= time_ago_in_words_label(ms.created_at) %>
<% if ms.is_editable_by_user?(@user) %> <% if ms.is_editable_by_user?(@user) %>
@ -98,8 +96,7 @@ class="story <%= story.vote && story.vote[:vote] == 1 ? "upvoted" : "" %>
<div class="byline"> <div class="byline">
<% if (@user && @user.show_avatars?) || !@user %> <% if (@user && @user.show_avatars?) || !@user %>
<a href="/u/<%= story.user.username %>"><%= <%= link_to avatar_img(story.user, 16), story.user %>
avatar_img(story.user, 16) %></a>
<% end %> <% end %>
<% if story.previewing %> <% if story.previewing %>
<% if story.user_is_author? %> <% if story.user_is_author? %>
@ -107,8 +104,8 @@ class="story <%= story.vote && story.vote[:vote] == 1 ? "upvoted" : "" %>
<% else %> <% else %>
<span> via </span> <span> via </span>
<% end %> <% end %>
<a class="u-author h-card <%= story.html_class_for_user %>"><%=
story.user.username %></a> <%= styled_user_link story.user, story, ['u-author', 'h-card'] %>
<span> just now </span> <span> just now </span>
<% else %> <% else %>
<% if story.user_is_author? %> <% if story.user_is_author? %>
@ -116,8 +113,7 @@ class="story <%= story.vote && story.vote[:vote] == 1 ? "upvoted" : "" %>
<% else %> <% else %>
<span> via </span> <span> via </span>
<% end %> <% end %>
<a href="/u/<%= story.user.username %>" class="u-author h-card <%= <%= styled_user_link story.user, story, ['u-author', 'h-card'] %>
story.html_class_for_user %>"><%= story.user.username %></a>
<%= time_ago_in_words_label(story.created_at) %> <%= time_ago_in_words_label(story.created_at) %>

View File

@ -3,7 +3,7 @@
<li> <li>
<a href="<%= story.url %>" target="_blank"><%= story.title %></a> <a href="<%= story.url %>" target="_blank"><%= story.title %></a>
<%= story.user_is_author? ? "authored by" : "via" %> <%= story.user_is_author? ? "authored by" : "via" %>
<a href="/u/<%= story.user.username %>" class="<%= story.html_class_for_user %>"><%= story.user.username %></a> <%= styled_user_link story.user, story %>
<%= time_ago_in_words_label(story.created_at) %> <%= time_ago_in_words_label(story.created_at) %>
<span> <span>
| |

View File

@ -1,8 +1,8 @@
<p> <p>
Invitations are unlimited, but persons you invite will be associated with your Invitations are unlimited, but persons you invite will be associated with your account
account in the <a href="/u">user tree</a> and you may be responsible for them in the <%= link_to 'user tree', users_tree_path %>
if they cause problems. Please use your discretion when inviting persons you and you may be responsible for them if they cause problems.
don't personally know. Please use your discretion when inviting persons you don't personally know.
</p> </p>
<%= form_with url: invitations_path, method: :post do |f| %> <%= form_with url: invitations_path, method: :post do |f| %>

View File

@ -6,20 +6,8 @@
<ul class="user_tree"> <ul class="user_tree">
<% @users.each do |user| %> <% @users.each do |user| %>
<li> <li>
<a href="/u/<%= user.username %>" <%= styled_user_link(user) %>
<% if !user.is_active? %> <%= user_karma(user) %>
class="inactive_user"
<% elsif user.is_new? %>
class="new_user"
<% end %>
><%= user.username %></a>
<% if user.is_admin? %>
(administrator)
<% elsif user.is_moderator? %>
(moderator)
<% else %>
(<%= user.karma %>)
<% end %>
</li> </li>
<% end %> <% end %>
</ul> </ul>

View File

@ -39,9 +39,8 @@
<span class="d"> <span class="d">
<%= time_ago_in_words_label(@showing_user.created_at) %> <%= time_ago_in_words_label(@showing_user.created_at) %>
<% if @showing_user.invited_by_user %> <% if @showing_user.invited_by_user %>
by <a href="/u#<%= @showing_user.username %>">invitation</a> from by <%= link_to 'invitation', users_tree_path(anchor: @showing_user.username) %> from
<%= link_to @showing_user.invited_by_user.try(:username), <%= link_to @showing_user.invited_by_user.try(:username), @showing_user.invited_by_user %>
@showing_user.invited_by_user %>
<% end %> <% end %>
</span> </span>
<br> <br>
@ -215,14 +214,14 @@
<% @showing_user.votes_for_others.limit(15).each do |v| %><tr> <% @showing_user.votes_for_others.limit(15).each do |v| %><tr>
<td><%= v.vote == 1 ? '+' : v.reason %></td> <td><%= v.vote == 1 ? '+' : v.reason %></td>
<% if v.comment_id %> <% if v.comment_id %>
<td><a href="/u/<%= v.comment.user.try(:username) %>"><%= v.comment.user.try(:username) %></a></td> <td><a href="/~<%= v.comment.user.try(:username) %>"><%= v.comment.user.try(:username) %></a></td>
<td> <td>
<%= v.story.title %> <%= v.story.title %>
<a href="<%= v.comment.short_id_url %>">comment</a>:<br> <a href="<%= v.comment.short_id_url %>">comment</a>:<br>
<%= v.comment.comment.split[0..10].join(' ') %> <%= v.comment.comment.split[0..10].join(' ') %>
</td> </td>
<% elsif v.story_id && !v.comment_id %> <% elsif v.story_id && !v.comment_id %>
<td><a href="/u/<%= v.story.user.try(:username) %>"><%= v.story.user.try(:username) %></a></td> <td><a href="/~<%= v.story.user.try(:username) %>"><%= v.story.user.try(:username) %></a></td>
<td><a href="<%= v.story.short_id_url %>"><%= v.story.title %></a></td> <td><a href="<%= v.story.short_id_url %>"><%= v.story.title %></a></td>
<% end %> <% end %>
</p> </p>

View File

@ -4,9 +4,10 @@
<% if @newest %> <% if @newest %>
<p> <p>
Newest users: Newest users:
<%= raw @newest.map{|u| "<a href=\"##{u.username}\" class=\"" << <% @newest.each do |user| %>
(u.is_new?? "new_user" : "") << "\">#{u.username}</a> " << <%= styled_user_link user %>
"(#{u.karma})" }.join(", ") %> <%= user_karma(user) %><%= ',' if user != @newest.last %>
<% end %>
</p> </p>
<% end %> <% end %>
@ -18,20 +19,9 @@
<% while subtree %> <% while subtree %>
<% if (user = subtree.pop) %> <% if (user = subtree.pop) %>
<li class="<%= user.invited_by_user_id ? "" : "noparent" %>"> <li class="<%= user.invited_by_user_id ? "" : "noparent" %>">
<a name="<%= user.username %>" href="/u/<%= user.username %>" <%= styled_user_link user %>
<% if !user.is_active? %> <%= user_karma(user) %>
class="inactive_user"
<% elsif user.is_new? %>
class="new_user"
<% end %>
><%= user.username %></a>
<% if user.is_admin? %>
(administrator)
<% elsif user.is_moderator? %>
(moderator)
<% else %>
(<%= user.karma %>)
<% end %>
<% if (children = @users_by_parent[user.id]) %> <% if (children = @users_by_parent[user.id]) %>
<% # drill down deeper in the tree %> <% # drill down deeper in the tree %>
<% ancestors << subtree %> <% ancestors << subtree %>

View File

@ -14,8 +14,6 @@ Rails.application.routes.draw do
get "/active/page/:page" => "home#active" get "/active/page/:page" => "home#active"
get "/newest" => "home#newest" get "/newest" => "home#newest"
get "/newest/page/:page" => "home#newest" get "/newest/page/:page" => "home#newest"
get "/newest/:user" => "home#newest_by_user"
get "/newest/:user/page/:page" => "home#newest_by_user"
get "/recent" => "home#recent" get "/recent" => "home#recent"
get "/recent/page/:page" => "home#recent" get "/recent/page/:page" => "home#recent"
get "/hidden" => "home#hidden" get "/hidden" => "home#hidden"
@ -37,7 +35,6 @@ Rails.application.routes.draw do
get "/top/:length/page/:page" => "home#top" get "/top/:length/page/:page" => "home#top"
get "/threads" => "comments#user_threads" get "/threads" => "comments#user_threads"
get "/threads/:user" => "comments#user_threads", :as => "user_threads"
get "/replies" => "replies#all" get "/replies" => "replies#all"
get "/replies/page/:page" => "replies#all" get "/replies/page/:page" => "replies#all"
@ -133,20 +130,32 @@ Rails.application.routes.draw do
get "/s/:id/(:title)" => "stories#show" get "/s/:id/(:title)" => "stories#show"
get "/u" => "users#tree" get "/users" => "users#tree", :as => "users_tree"
get "/u/:username" => "users#show", :as => "user" get "/~:username" => "users#show", :as => "user"
get "/u/:username/standing" => "users#standing", :as => "user_standing" get "/~:username/standing" => "users#standing", :as => "user_standing"
get "/~:user/stories(/page/:page)" => "home#newest_by_user"
get "/~:user/threads" => "comments#user_threads", :as => "user_threads"
post "/~:username/ban" => "users#ban", :as => "user_ban"
post "/~:username/unban" => "users#unban", :as => "user_unban"
post "/~:username/disable_invitation" => "users#disable_invitation",
:as => "user_disable_invite"
post "/~:username/enable_invitation" => "users#enable_invitation",
:as => "user_enable_invite"
# 2023-07 redirect /u to /~username and /users (for tree)
get "/u", to: redirect("/users", status: 302)
get "/u/:username", to: redirect("/~%{username}", status: 302)
# we don't do /@alice but easy mistake with comments autolinking @alice
get "/@:username", to: redirect("/~%{username}", status: 302)
get "/u/:username/standing", to: redirect("~%{username}/standing", status: 302)
get "/newest/:user", to: redirect("~%{user}/stories", status: 302)
get "/newest/:user(/page/:page)", to: redirect("~%{user}/stories/page/%{page}", status: 302)
get "/threads/:user", to: redirect("~%{user}/threads", status: 302)
get "/avatars/:username_size.png" => "avatars#show" get "/avatars/:username_size.png" => "avatars#show"
post "/avatars/expire" => "avatars#expire" post "/avatars/expire" => "avatars#expire"
post "/users/:username/ban" => "users#ban", :as => "user_ban"
post "/users/:username/unban" => "users#unban", :as => "user_unban"
post "/users/:username/disable_invitation" => "users#disable_invitation",
:as => "user_disable_invite"
post "/users/:username/enable_invitation" => "users#enable_invitation",
:as => "user_enable_invite"
get "/settings" => "settings#index" get "/settings" => "settings#index"
post "/settings" => "settings#update" post "/settings" => "settings#update"
post "/settings/delete_account" => "settings#delete_account", post "/settings/delete_account" => "settings#delete_account",

View File

@ -51,7 +51,7 @@ class Markdowner
if User.exists?(:username => user[1..-1]) if User.exists?(:username => user[1..-1])
link = CommonMarker::Node.new(:link) link = CommonMarker::Node.new(:link)
link.url = Rails.application.root_url + "u/#{user[1..-1]}" link.url = Rails.application.root_url + "~#{user[1..-1]}"
node.insert_after(link) node.insert_after(link)
link_text = CommonMarker::Node.new(:text) link_text = CommonMarker::Node.new(:text)

View File

@ -10,7 +10,7 @@ describe Markdowner do
create(:user, :username => "blahblah") create(:user, :username => "blahblah")
expect(Markdowner.to_html("hi @blahblah test")) expect(Markdowner.to_html("hi @blahblah test"))
.to eq("<p>hi <a href=\"https://lobste.rs/u/blahblah\" rel=\"ugc\">" + .to eq("<p>hi <a href=\"https://lobste.rs/~blahblah\" rel=\"ugc\">" +
"@blahblah</a> test</p>\n") "@blahblah</a> test</p>\n")
expect(Markdowner.to_html("hi @flimflam test")) expect(Markdowner.to_html("hi @flimflam test"))
@ -46,8 +46,8 @@ describe Markdowner do
.to eq("<p><a href=\"//example.com\" rel=\"ugc\">" + .to eq("<p><a href=\"//example.com\" rel=\"ugc\">" +
"ex</a></p>\n") "ex</a></p>\n")
expect(Markdowner.to_html("[ex](/u/abc)")) expect(Markdowner.to_html("[ex](/~abc)"))
.to eq("<p><a href=\"/u/abc\">ex</a></p>\n") .to eq("<p><a href=\"/~abc\">ex</a></p>\n")
end end
context "when images are not allowed" do context "when images are not allowed" do

View File

@ -5,7 +5,7 @@ describe 'users controller' do
it 'displays the username' do it 'displays the username' do
user = create(:user) user = create(:user)
get "/u/#{user.username}" get "/~#{user.username}"
expect(response.body).to include(user.username) expect(response.body).to include(user.username)
end end
@ -29,7 +29,7 @@ describe 'users controller' do
it "displays to the user" do it "displays to the user" do
sign_in bad_user sign_in bad_user
get "/u/#{bad_user.username}/standing" get "/~#{bad_user.username}/standing"
expect(response.body).to include("flags") expect(response.body).to include("flags")
expect(response.body).to include("You") expect(response.body).to include("You")
end end
@ -38,12 +38,12 @@ describe 'users controller' do
user2 = create(:user) user2 = create(:user)
sign_in user2 sign_in user2
get "/u/#{bad_user.username}/standing" get "/~#{bad_user.username}/standing"
expect(response.status).to eq(302) expect(response.status).to eq(302)
end end
it "doesn't display to logged-out users" do it "doesn't display to logged-out users" do
get "/u/#{bad_user.username}/standing" get "/~#{bad_user.username}/standing"
expect(response.status).to eq(302) expect(response.status).to eq(302)
end end
@ -51,7 +51,7 @@ describe 'users controller' do
mod = create(:user, :moderator) mod = create(:user, :moderator)
sign_in mod sign_in mod
get "/u/#{bad_user.username}/standing" get "/~#{bad_user.username}/standing"
expect(response.body).to include("flags") expect(response.body).to include("flags")
end end
end end

66
spec/routing/user_spec.rb Normal file
View File

@ -0,0 +1,66 @@
require 'rails_helper'
# rubocop:disable RSpec/MultipleDescribes
describe 'user routing' do
it 'users#tree' do
assert_routing(
'/users',
controller: 'users', action: 'tree'
)
end
it 'users#show' do
assert_routing(
'/~alice',
controller: 'users', action: 'show', username: 'alice'
)
end
it 'home#stories' do
assert_routing(
'/~alice/stories',
controller: 'home', action: 'newest_by_user', user: 'alice'
)
assert_routing(
'/~alice/stories/page/2',
controller: 'home', action: 'newest_by_user', user: 'alice', page: '2'
)
end
it 'comments#user_threads' do
assert_routing(
'/~alice/threads',
controller: 'comments', action: 'user_threads', user: 'alice'
)
end
end
# odd Rails limitation: you can redirect from routes but not test those from routing tests
describe 'user redirects', type: :request do
it 'old-style to tilde' do
expect(get('/u/alice')).to redirect_to('/~alice')
end
it 'user tree' do
expect(get('/u')).to redirect_to('/users')
end
it 'newest stories' do
expect(get('/newest/alice')).to redirect_to('/~alice/stories')
expect(get('/newest/alice/page/2')).to redirect_to('/~alice/stories/page/2')
end
it 'threads' do
expect(get('/threads/alice')).to redirect_to('/~alice/threads')
end
# 2023-07: I'm deploying with 302 redirects so I don't have to worry about 301s getting
# indefinitely cached in case of error, but after some time to build confidence, it's appropriate
# to 301. I'm putting a time bomb test in here to remind me to finalize.
it 'is temporarily temporary' do
expect(get('/u/alice')).to eq(302)
expect(Time.zone.today).to be_before(Date.new(2024, 1, 1))
end
end
# rubocop:enable RSpec/MultipleDescribes