diff --git a/Gemfile.lock b/Gemfile.lock index 8af71d00..630c57fb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -58,7 +58,7 @@ GEM ast (2.4.0) authority (3.3.0) activesupport (>= 3.0.0) - autoprefixer-rails (9.4.8) + autoprefixer-rails (9.4.10.2) execjs aws-eventstream (1.0.1) aws-partitions (1.131.0) @@ -123,7 +123,7 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.4) + concurrent-ruby (1.1.5) connection_pool (2.2.2) crack (0.4.3) safe_yaml (~> 1.0.0) @@ -174,7 +174,7 @@ GEM railties (>= 3.0.0) faraday (0.15.4) multipart-post (>= 1.2, < 3) - ffi (1.9.25) + ffi (1.10.0) filesize (0.2.0) flamegraph (0.9.5) font-awesome-rails (4.7.0.4) @@ -209,7 +209,7 @@ GEM activesupport (>= 2) nokogiri (>= 1.4) htmlentities (4.3.4) - i18n (1.5.3) + i18n (1.6.0) concurrent-ruby (~> 1.0) inline_svg (1.3.1) activesupport (>= 3.0) @@ -287,7 +287,7 @@ GEM notiffany (0.1.1) nenv (~> 0.1) shellany (~> 0.0) - onebox (1.8.78) + onebox (1.8.82) htmlentities (~> 4.3) moneta (~> 1.0) multi_json (~> 1.11) @@ -445,8 +445,8 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) - sassc (2.0.0) - ffi (~> 1.9.6) + sassc (2.0.1) + ffi (~> 1.9) rake sassc-rails (2.1.0) railties (>= 4.0.0) @@ -496,7 +496,7 @@ GEM climate_control (>= 0.0.3, < 1.0) thor (0.20.3) thread_safe (0.3.6) - thredded (0.16.8) + thredded (0.16.9) active_record_union (>= 1.3.0) autoprefixer-rails db_text_search (~> 0.3.0) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 94379ecf..f1612ee3 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -5,8 +5,8 @@ class UsersController < ApplicationController def show @sidenav_expansion = 'my account' - - @user = User.find_by(id: params[:id]) + + @user = User.find_by(user_params) return redirect_to(root_path, notice: 'That user does not exist.') if @user.nil? @content = @user.public_content.select { |type, list| list.any? } @@ -70,4 +70,10 @@ class UsersController < ApplicationController notifier.ping ":bomb: :bomb: :bomb: #{user.email.split('@').first}@ (##{user.id}) just deleted their account." end + + private + + def user_params + params.permit(:id, :username) + end end diff --git a/app/models/user.rb b/app/models/user.rb index 4b05723c..3bd5ee87 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -12,6 +12,8 @@ class User < ApplicationRecord include Authority::UserAbilities validates_uniqueness_of :username, allow_nil: true, allow_blank: true + validates_format_of :username, with: /\A[A-Za-z0-9\-_\$\+\!\*]+\z/, message: 'must be between 1 and 40 alphanumeric characters (-, _, $, +, !, and * also accepted)' + validates :username, length: { in: 0..40, message: 'must be between 1 and 40 alphanumeric characters (-, _, $, +, !, and * also accepted)' } has_many :subscriptions, dependent: :destroy has_many :billing_plans, through: :subscriptions @@ -189,6 +191,14 @@ class User < ApplicationRecord found_key.user end + def profile_url + if self.username.present? + Rails.application.routes.url_helpers.profile_by_username_path(username: self.username) + else + Rails.application.routes.url_helpers.user_path(id: self.id) + end + end + private # Attributes that are non-public, and should be blacklisted from any public diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index 9b5387c2..a2328d78 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -1,5 +1,7 @@ <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> - <%= devise_error_messages! %> +
+ <%= devise_error_messages! %> +
diff --git a/app/views/devise/registrations/panes/_information.html.erb b/app/views/devise/registrations/panes/_information.html.erb index 98dfc1a9..e89911a3 100644 --- a/app/views/devise/registrations/panes/_information.html.erb +++ b/app/views/devise/registrations/panes/_information.html.erb @@ -16,6 +16,8 @@
<%= f.label 'Username (users can @mention you with your username on the forums)' %>
<%= f.text_field :username %> + Your Notebook.ai profile will be available at https://www.notebook.ai/@username.
+ Up to 40 numbers, letters, and/or the following symbols are allowed: - _ $ + ! *
diff --git a/app/views/layouts/_sidenav.html.erb b/app/views/layouts/_sidenav.html.erb index 9fcd8fbd..a921efd7 100644 --- a/app/views/layouts/_sidenav.html.erb +++ b/app/views/layouts/_sidenav.html.erb @@ -135,7 +135,7 @@
- <%= link_to current_user do %> + <%= link_to current_user.profile_url do %>
diff --git a/app/views/thredded/posts/_post.html.erb b/app/views/thredded/posts/_post.html.erb new file mode 100644 index 00000000..48127ee4 --- /dev/null +++ b/app/views/thredded/posts/_post.html.erb @@ -0,0 +1,20 @@ +<% post, content = post_and_content if local_assigns.key?(:post_and_content) %> + +<% + muted_post = post.present? && post.to_model.content.split("\n").reject(&:empty?).all? { |paragraph| paragraph.strip.start_with?('(') && paragraph.strip.end_with?(')') } + muted_post_classes = 'grey lighten-4 grey-text text-darken-2' +%> + +<%= render 'thredded/posts_common/before_first_unread_post', post: post if post.first_unread_in_page? %> +<%= content_tag :article, id: dom_id(post), class: "thredded--post thredded--#{post.read_state}--post #{muted_post ? muted_post_classes : 'card'}" do %> + <%= render 'thredded/posts_common/actions', post: post, actions: local_assigns[:actions] %> + <%= render 'thredded/posts_common/header', post: post %> + <%= content || render('thredded/posts/content', post: post) %> + <% if post.pending_moderation? && !Thredded.content_visible_while_pending_moderation %> +

<%= t 'thredded.posts.pending_moderation_notice' %>

+ <% elsif post.blocked? && post.can_moderate? %> +

+ <%= render 'thredded/shared/content_moderation_blocked_state', moderation_record: post.last_moderation_record %> +

+ <% end %> +<% end %> diff --git a/app/views/users/profile/_info.html.erb b/app/views/users/profile/_info.html.erb index d3107644..1cacbdb8 100644 --- a/app/views/users/profile/_info.html.erb +++ b/app/views/users/profile/_info.html.erb @@ -28,7 +28,7 @@ Username
- <%= link_to "@#{@user.username}", thredded_path %> + <%= link_to "@#{@user.username}", profile_by_username_path(username: @user.username) %>

<% end %> diff --git a/app/views/users/profile/_public_pages.html.erb b/app/views/users/profile/_public_pages.html.erb index 1a26f6e4..8af92018 100644 --- a/app/views/users/profile/_public_pages.html.erb +++ b/app/views/users/profile/_public_pages.html.erb @@ -7,7 +7,7 @@
<% @tabs.each do |tab| %> <% content_type_class = @user.send(tab).build.class %> - <%= link_to send("#{tab}_user_path"), class: "collection-item #{content_type_class.color}-text" do %> + <%= link_to send("#{tab}_user_path", { id: @user.id }), class: "collection-item #{content_type_class.color}-text" do %> <%= pluralize @content[tab].length, tab.to_s.singularize %> <%= content_type_class.icon %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 7376faeb..0fc9ba2d 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -5,7 +5,7 @@ image_src: @user.image_url(120) content_jsonld = { - '@id': user_url, + '@id': user_url(id: @user.id), '@type': 'http://schema.org/Person', 'http://schema.org/name': @user.name, 'http://schema.org/description': "#{@user.name}’s profile on notebook.ai", diff --git a/config/routes.rb b/config/routes.rb index 8e3c7f04..c7425271 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,6 +13,8 @@ Rails.application.routes.draw do # todo page tags here end end + get '/@:username', to: 'users#show', as: :profile_by_username + scope '/my' do get '/content', to: 'main#dashboard', as: :dashboard get '/content/recent', to: 'main#recent_content', as: :recent_content