Skip to content

Commit 4f1abc8

Browse files
committed
release polish
1 parent f3e4fab commit 4f1abc8

20 files changed

+276
-54
lines changed

app/authorizers/content_authorizer.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
class ContentAuthorizer < ApplicationAuthorizer
22
def readable_by? user
3-
return true if user && user.site_administrator?
4-
return true if ::PermissionService.user_owns_any_containing_universe?(user: user, content: resource)
5-
return true if ::PermissionService.user_owns_content?(user: user, content: resource)
3+
# Check public content first - these should be accessible to anyone even without a user account
64
return true if ::PermissionService.content_is_public?(content: resource)
75
return true if ::PermissionService.content_is_in_a_public_universe?(content: resource)
6+
7+
# If no user is provided (logged out), they can only access public content
8+
return false if user.nil?
9+
10+
# Otherwise check user-specific permissions
11+
return true if user.site_administrator?
12+
return true if ::PermissionService.user_owns_any_containing_universe?(user: user, content: resource)
13+
return true if ::PermissionService.user_owns_content?(user: user, content: resource)
814
return true if ::PermissionService.user_can_contribute_to_containing_universe?(user: user, content: resource)
915

1016
return false

app/authorizers/core_content_authorizer.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
class CoreContentAuthorizer < ContentAuthorizer
22
def self.readable_by?(user)
3-
return true if user && resource.user == user # PermissionService.user_owns_content?()
3+
# Check public content first - these should be accessible to anyone
44
return true if resource.privacy == 'public'
55
return true if resource.try(:universe).try(:privacy) == 'public'
6+
7+
# For private content, require a user
8+
return false if user.nil?
9+
10+
# Check user-specific permissions
11+
return true if resource.user == user # PermissionService.user_owns_content?()
612

713
false
814
end

app/controllers/browse_controller.rb

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ def tag
2727

2828
# Go through each content type and find public items with this tag
2929
Rails.application.config.content_types[:all].each do |content_type|
30-
# First find page IDs with this tag
31-
tag_page_ids = PageTag.where(page_type: content_type.name, slug: @tag_slug).pluck(:page_id)
30+
# First find page IDs with this tag - case insensitive matching for slug
31+
tag_page_ids = PageTag.where(page_type: content_type.name)
32+
.where("LOWER(slug) = ?", @tag_slug.downcase)
33+
.pluck(:page_id)
3234

3335
if tag_page_ids.any?
3436
# Use database-level randomization with the daily seed for caching potential
@@ -49,7 +51,9 @@ def tag
4951
end
5052

5153
# Add documents separately since they don't use the common content type structure
52-
document_tag_page_ids = PageTag.where(page_type: 'Document', slug: @tag_slug).pluck(:page_id)
54+
document_tag_page_ids = PageTag.where(page_type: 'Document')
55+
.where("LOWER(slug) = ?", @tag_slug.downcase)
56+
.pluck(:page_id)
5357
if document_tag_page_ids.any?
5458
documents = Document.where(id: document_tag_page_ids)
5559
.where(privacy: 'public')
@@ -65,7 +69,9 @@ def tag
6569
end
6670

6771
# Add timelines separately since they don't use the common content type structure
68-
timeline_tag_page_ids = PageTag.where(page_type: 'Timeline', slug: @tag_slug).pluck(:page_id)
72+
timeline_tag_page_ids = PageTag.where(page_type: 'Timeline')
73+
.where("LOWER(slug) = ?", @tag_slug.downcase)
74+
.pluck(:page_id)
6975
if timeline_tag_page_ids.any?
7076
timelines = Timeline.where(id: timeline_tag_page_ids)
7177
.where(privacy: 'public')

app/controllers/content_controller.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ def index
8989

9090
def show
9191
content_type = content_type_from_controller(self.class)
92-
return redirect_to(root_path, notice: "That page doesn't exist!") unless valid_content_types.include?(content_type.name)
92+
return redirect_to(root_path, notice: "That page doesn't exist!", status: :not_found) unless valid_content_types.include?(content_type.name)
9393

9494
@content = content_type.find_by(id: params[:id])
95-
return redirect_to(root_path, notice: "You don't have permission to view that content.") if @content.nil?
95+
return redirect_to(root_path, notice: "You don't have permission to view that content.", status: :not_found) if @content.nil?
9696

9797
return redirect_to(root_path) if @content.user.nil? # deleted user's content
9898
return if ENV.key?('CONTENT_BLACKLIST') && ENV['CONTENT_BLACKLIST'].split(',').include?(@content.user.try(:email))

app/controllers/document_analyses_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def sentiment
4747

4848
def authorize_user_for_document
4949
unless @document.present? && (current_user || User.new).can_read?(@document)
50-
redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.")
50+
redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.", status: :not_found)
5151
return false
5252
end
5353
end

app/controllers/documents_controller.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ def index
6868

6969
def show
7070
unless @document.present? && (current_user || User.new).can_read?(@document)
71-
return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.")
71+
return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.", status: :not_found)
7272
end
7373

7474
if @document.user.thredded_user_detail.moderation_state == "blocked"
75-
return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.")
75+
return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.", status: :not_found)
7676
end
7777

7878
# Put the focus on the document by removing Notebook.ai actions
@@ -81,7 +81,7 @@ def show
8181

8282
def analysis
8383
unless @document.present? && (current_user || User.new).can_read?(@document)
84-
redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.")
84+
redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.", status: :not_found)
8585
end
8686

8787
@analysis = @document.document_analysis.where.not(queued_at: nil).order('updated_at DESC').first
@@ -98,7 +98,7 @@ def analysis
9898
end
9999

100100
def queue_analysis
101-
return redirect_back(fallback_location: documents_path, notice: "That document doesn't exist!") unless @document.present?
101+
return redirect_back(fallback_location: documents_path, notice: "That document doesn't exist!", status: :not_found) unless @document.present?
102102
return redirect_back(fallback_location: documents_path, notice: "Document analysis is a feature for Premium users.") unless @document.user.on_premium_plan?
103103
return redirect_back(fallback_location: documents_path, notice: "You don't have permission to do that!") unless @document.user == current_user
104104

@@ -214,7 +214,7 @@ def update
214214

215215
def plaintext
216216
unless @document.present? && (current_user || User.new).can_read?(@document)
217-
return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.")
217+
return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.", status: :not_found)
218218
end
219219

220220
render layout: 'plaintext'
@@ -335,7 +335,7 @@ def set_document
335335
@document = Document.find_by(id: params[:id])
336336

337337
unless @document
338-
redirect_to root_path, notice: "Either that document doesn't exist or you don't have permission to view it!"
338+
redirect_to root_path, notice: "Either that document doesn't exist or you don't have permission to view it!", status: :not_found
339339
return
340340
end
341341
end

app/controllers/universes_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class UniversesController < ContentController
77
@content_type = content_type_name.to_s.singularize.capitalize.constantize
88

99
@universe = Universe.find_by(id: params[:id])
10-
return redirect_to(root_path, notice: "That universe doesn't exist!") unless @universe.present?
10+
return redirect_to(root_path, notice: "That universe doesn't exist!", status: :not_found) unless @universe.present?
1111
@content_list = @universe.send(content_type_name)
1212

1313
# todo just use current_user.can_view?(@universe) and/or individual filtering

app/controllers/users_controller.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,10 @@ def tag
9999
@tag_slug = params[:tag_slug]
100100
@tag = PageTag.find_by(user_id: @user.id, slug: @tag_slug)
101101

102-
return redirect_to(profile_by_username_path(username: @user.username), notice: 'That tag does not exist.') if @tag.nil?
102+
if @tag.nil?
103+
redirect_url = @user.username.present? ? profile_by_username_path(username: @user.username) : user_path(@user)
104+
return redirect_to(redirect_url, notice: 'That tag does not exist.', status: :not_found)
105+
end
103106

104107
# Find all public content with this tag
105108
@tagged_content = []
@@ -181,7 +184,7 @@ def tag
181184

182185
def set_user
183186
@user = User.find_by(user_params)
184-
return redirect_to(root_path, notice: 'That user does not exist.') if @user.nil?
187+
return redirect_to(root_path, notice: 'That user does not exist.', status: :not_found) if @user.nil?
185188
return redirect_to(root_path, notice: 'That user has chosen to hide their profile.') if @user.private_profile?
186189
return redirect_to(root_path, notice: 'That user has had their profile hidden.') if @user.thredded_user_detail.moderation_state == 'blocked'
187190

app/services/permission_service.rb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,18 @@ def self.content_is_in_a_public_universe?(content:)
2525
end
2626

2727
def self.user_can_contribute_to_containing_universe?(user:, content:)
28+
# Early return if no user is provided
2829
return false if user.nil?
30+
31+
# Special case for attribute-related content
2932
return true if [AttributeCategory, AttributeField, Attribute].include?(content.class) #todo audit this
3033

31-
return true if user.contributable_universe_ids.include?(content.universe_id)
32-
return true if user.universes.pluck(:id).include?(content.universe_id)
34+
# Handle cases where content might not have a universe_id
35+
return false if content.universe_id.nil?
36+
37+
# Check if user can contribute to this universe
38+
return true if user.respond_to?(:contributable_universe_ids) && user.contributable_universe_ids.include?(content.universe_id)
39+
return true if user.respond_to?(:universes) && user.universes.pluck(:id).include?(content.universe_id)
3340

3441
return false
3542
end

app/views/browse/tag.html.erb

Lines changed: 104 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080

8181
<div class="chip z-depth-1 white" style="border: none; margin: 0; font-weight: 500;">
8282
<i class="material-icons left <%= @accent_color %>-text">category</i>
83-
<%= pluralize(content_types_count, 'category') %>
83+
<%= pluralize(content_types_count, 'page type') %>
8484
</div>
8585

8686
<div class="chip z-depth-1 white" style="border: none; margin: 0; font-weight: 500;">
@@ -106,20 +106,20 @@
106106

107107
<!-- Category quick filters -->
108108
<div class="col s12">
109-
<div class="category-filters z-depth-1" style="background-color: #fff; border-radius: 8px; padding: 16px; margin-bottom: 25px;">
109+
<div class="category-filters z-depth-1" style="border-radius: 8px; padding: 16px; margin-bottom: 25px;">
110110
<div class="category-filter-title" style="margin-bottom: 10px; display: flex; align-items: center;">
111111
<i class="material-icons <%= @accent_color %>-text" style="margin-right: 8px;">filter_list</i>
112112
<span style="font-weight: 500; font-size: 16px;">Filter by category:</span>
113113
</div>
114114

115115
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
116-
<a href="javascript:void(0);" onclick="return filterByCategory('all');" class="category-filter active" data-category="all" style="padding: 6px 12px; border-radius: 4px; background-color: #f5f5f5; text-decoration: none; color: #333; font-weight: 500; transition: all 0.2s ease; display: flex; align-items: center;">
116+
<a href="javascript:void(0);" onclick="return filterByCategory('all');" class="category-filter active black-text" data-category="all" style="padding: 6px 12px; border-radius: 4px; background-color: #f5f5f5; text-decoration: none; font-weight: 500; transition: all 0.2s ease; display: flex; align-items: center;">
117117
<i class="material-icons <%= @accent_color %>-text left" style="font-size: 18px; margin-right: 6px;">layers</i>
118118
All (<%= total_items %>)
119119
</a>
120120

121121
<% @tagged_content.each do |content_group| %>
122-
<a href="javascript:void(0);" onclick="return filterByCategory('<%= content_group[:type].downcase %>');" class="category-filter" data-category="<%= content_group[:type].downcase %>" style="padding: 6px 12px; border-radius: 4px; background-color: #f5f5f5; text-decoration: none; color: #333; transition: all 0.2s ease; display: flex; align-items: center;">
122+
<a href="javascript:void(0);" onclick="return filterByCategory('<%= content_group[:type].downcase %>');" class="category-filter black-text" data-category="<%= content_group[:type].downcase %>" style="padding: 6px 12px; border-radius: 4px; background-color: #f5f5f5; text-decoration: none; transition: all 0.2s ease; display: flex; align-items: center;">
123123
<i class="material-icons <%= content_group[:color] %>-text left" style="font-size: 18px; margin-right: 6px;"><%= content_group[:icon] %></i>
124124
<%= content_group[:type].pluralize %> (<%= content_group[:content].count %>)
125125
</a>
@@ -453,12 +453,23 @@
453453
.modal-how-to-join {
454454
border-radius: 8px;
455455
max-width: 500px;
456+
transition: background-color 1s ease;
457+
}
458+
459+
body.dark .modal-how-to-join {
460+
background-color: #2D2D31 !important;
461+
color: #ddd;
456462
}
457463

458464
.modal-how-to-join h4 {
459465
margin-top: 0;
460466
font-size: 20px;
461467
font-weight: 500;
468+
transition: color 1s ease;
469+
}
470+
471+
body.dark .modal-how-to-join h4 {
472+
color: white;
462473
}
463474

464475
.modal-how-to-join ol {
@@ -473,6 +484,17 @@
473484
background-color: #f3e5f5;
474485
padding: 2px 5px;
475486
border-radius: 3px;
487+
transition: background-color 1s ease, color 1s ease;
488+
}
489+
490+
body.dark .modal-how-to-join .highlight {
491+
background-color: rgba(156, 39, 176, 0.2);
492+
color: white;
493+
}
494+
495+
/* Fix modal content border */
496+
body.dark .modal-content {
497+
border-color: rgba(255, 255, 255, 0.1) !important;
476498
}
477499

478500
:root {
@@ -545,6 +567,39 @@
545567
}
546568

547569
/* Category filters styling */
570+
.category-filters {
571+
background-color: #fff;
572+
transition: background-color 1s ease;
573+
}
574+
575+
body.dark .category-filters {
576+
background-color: rgba(255, 255, 255, 0.1);
577+
}
578+
579+
.category-filter {
580+
background-color: #f5f5f5;
581+
color: #333;
582+
transition: background-color 1s ease, color 1s ease;
583+
}
584+
585+
body.dark .category-filter {
586+
background-color: rgba(255, 255, 255, 0.05);
587+
}
588+
589+
/* Ensure filter text is always readable regardless of theme */
590+
.category-filter {
591+
color: #333 !important;
592+
}
593+
594+
/* Fix the filter title text in dark mode */
595+
body.dark .category-filter-title span {
596+
color: #ddd;
597+
}
598+
599+
body.dark .category-filter.active {
600+
color: #333;
601+
}
602+
548603
@media only screen and (max-width: 600px) {
549604
.category-filters {
550605
overflow-x: auto;
@@ -580,22 +635,37 @@
580635

581636
/* Better card styling */
582637
.hoverable.card {
583-
transition: box-shadow 0.25s, transform 0.25s;
638+
transition: box-shadow 0.25s, transform 0.25s, background-color 1s ease;
584639
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
585640
border-radius: 8px;
586641
margin-bottom: 8px;
642+
background-color: #fff;
643+
}
644+
645+
body.dark .hoverable.card {
646+
background-color: rgba(255, 255, 255, 0.1);
647+
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
587648
}
588649

589650
.hoverable.card:hover {
590651
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
591652
transform: translateY(-2px);
592653
}
593654

655+
body.dark .hoverable.card:hover {
656+
box-shadow: 0 5px 15px rgba(0,0,0,0.4);
657+
}
658+
594659
/* Position relative for absolute positioning within */
595660
.hoverable.card {
596661
position: relative;
597662
padding-bottom: 8px;
598663
border-bottom: 4px solid #eeeeee;
664+
transition: border-color 1s ease;
665+
}
666+
667+
body.dark .hoverable.card {
668+
border-bottom: 4px solid rgba(255, 255, 255, 0.1);
599669
}
600670

601671
.js-content-card-container > a,
@@ -623,6 +693,11 @@
623693
overflow: hidden;
624694
position: relative;
625695
padding-bottom: 20px !important;
696+
transition: background-color 1s ease;
697+
}
698+
699+
body.dark .card .card-content {
700+
background-color: rgba(255, 255, 255, 0.1);
626701
}
627702

628703
.tags-container {
@@ -653,6 +728,11 @@
653728
height: 15px;
654729
background: linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 100%);
655730
pointer-events: none;
731+
transition: background 1s ease;
732+
}
733+
734+
body.dark .card .card-content:after {
735+
background: linear-gradient(to bottom, rgba(40,40,45,0) 0%, rgba(40,40,45,1) 100%);
656736
}
657737

658738
/* Card footer line - more visible */
@@ -666,5 +746,24 @@
666746
background-color: #f0f0f0;
667747
border-radius: 0 0 8px 8px;
668748
z-index: 1;
749+
transition: background-color 1s ease;
750+
}
751+
752+
body.dark .card::after {
753+
background-color: rgba(255, 255, 255, 0.05);
754+
}
755+
756+
/* Dark mode text styles */
757+
body.dark .card .card-content span[style*="color: #424242"] {
758+
color: #ddd !important;
759+
}
760+
761+
body.dark .grey-text {
762+
color: #aaa !important;
763+
}
764+
765+
body.dark .tag-pill[style*="background-color: #f0f0f0"] {
766+
background-color: rgba(255, 255, 255, 0.1) !important;
767+
color: #aaa !important;
669768
}
670769
</style>

0 commit comments

Comments
 (0)