mirror of
https://github.com/indentlabs/notebook.git
synced 2025-10-26 11:19:22 +00:00
release polish
This commit is contained in:
parent
f3e4fab02e
commit
4f1abc82cd
@ -1,10 +1,16 @@
|
||||
class ContentAuthorizer < ApplicationAuthorizer
|
||||
def readable_by? user
|
||||
return true if user && user.site_administrator?
|
||||
return true if ::PermissionService.user_owns_any_containing_universe?(user: user, content: resource)
|
||||
return true if ::PermissionService.user_owns_content?(user: user, content: resource)
|
||||
# Check public content first - these should be accessible to anyone even without a user account
|
||||
return true if ::PermissionService.content_is_public?(content: resource)
|
||||
return true if ::PermissionService.content_is_in_a_public_universe?(content: resource)
|
||||
|
||||
# If no user is provided (logged out), they can only access public content
|
||||
return false if user.nil?
|
||||
|
||||
# Otherwise check user-specific permissions
|
||||
return true if user.site_administrator?
|
||||
return true if ::PermissionService.user_owns_any_containing_universe?(user: user, content: resource)
|
||||
return true if ::PermissionService.user_owns_content?(user: user, content: resource)
|
||||
return true if ::PermissionService.user_can_contribute_to_containing_universe?(user: user, content: resource)
|
||||
|
||||
return false
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
class CoreContentAuthorizer < ContentAuthorizer
|
||||
def self.readable_by?(user)
|
||||
return true if user && resource.user == user # PermissionService.user_owns_content?()
|
||||
# Check public content first - these should be accessible to anyone
|
||||
return true if resource.privacy == 'public'
|
||||
return true if resource.try(:universe).try(:privacy) == 'public'
|
||||
|
||||
# For private content, require a user
|
||||
return false if user.nil?
|
||||
|
||||
# Check user-specific permissions
|
||||
return true if resource.user == user # PermissionService.user_owns_content?()
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
@ -27,8 +27,10 @@ class BrowseController < ApplicationController
|
||||
|
||||
# Go through each content type and find public items with this tag
|
||||
Rails.application.config.content_types[:all].each do |content_type|
|
||||
# First find page IDs with this tag
|
||||
tag_page_ids = PageTag.where(page_type: content_type.name, slug: @tag_slug).pluck(:page_id)
|
||||
# First find page IDs with this tag - case insensitive matching for slug
|
||||
tag_page_ids = PageTag.where(page_type: content_type.name)
|
||||
.where("LOWER(slug) = ?", @tag_slug.downcase)
|
||||
.pluck(:page_id)
|
||||
|
||||
if tag_page_ids.any?
|
||||
# Use database-level randomization with the daily seed for caching potential
|
||||
@ -49,7 +51,9 @@ class BrowseController < ApplicationController
|
||||
end
|
||||
|
||||
# Add documents separately since they don't use the common content type structure
|
||||
document_tag_page_ids = PageTag.where(page_type: 'Document', slug: @tag_slug).pluck(:page_id)
|
||||
document_tag_page_ids = PageTag.where(page_type: 'Document')
|
||||
.where("LOWER(slug) = ?", @tag_slug.downcase)
|
||||
.pluck(:page_id)
|
||||
if document_tag_page_ids.any?
|
||||
documents = Document.where(id: document_tag_page_ids)
|
||||
.where(privacy: 'public')
|
||||
@ -65,7 +69,9 @@ class BrowseController < ApplicationController
|
||||
end
|
||||
|
||||
# Add timelines separately since they don't use the common content type structure
|
||||
timeline_tag_page_ids = PageTag.where(page_type: 'Timeline', slug: @tag_slug).pluck(:page_id)
|
||||
timeline_tag_page_ids = PageTag.where(page_type: 'Timeline')
|
||||
.where("LOWER(slug) = ?", @tag_slug.downcase)
|
||||
.pluck(:page_id)
|
||||
if timeline_tag_page_ids.any?
|
||||
timelines = Timeline.where(id: timeline_tag_page_ids)
|
||||
.where(privacy: 'public')
|
||||
|
||||
@ -89,10 +89,10 @@ class ContentController < ApplicationController
|
||||
|
||||
def show
|
||||
content_type = content_type_from_controller(self.class)
|
||||
return redirect_to(root_path, notice: "That page doesn't exist!") unless valid_content_types.include?(content_type.name)
|
||||
return redirect_to(root_path, notice: "That page doesn't exist!", status: :not_found) unless valid_content_types.include?(content_type.name)
|
||||
|
||||
@content = content_type.find_by(id: params[:id])
|
||||
return redirect_to(root_path, notice: "You don't have permission to view that content.") if @content.nil?
|
||||
return redirect_to(root_path, notice: "You don't have permission to view that content.", status: :not_found) if @content.nil?
|
||||
|
||||
return redirect_to(root_path) if @content.user.nil? # deleted user's content
|
||||
return if ENV.key?('CONTENT_BLACKLIST') && ENV['CONTENT_BLACKLIST'].split(',').include?(@content.user.try(:email))
|
||||
|
||||
@ -47,7 +47,7 @@ class DocumentAnalysesController < ApplicationController
|
||||
|
||||
def authorize_user_for_document
|
||||
unless @document.present? && (current_user || User.new).can_read?(@document)
|
||||
redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.")
|
||||
redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.", status: :not_found)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
@ -68,11 +68,11 @@ class DocumentsController < ApplicationController
|
||||
|
||||
def show
|
||||
unless @document.present? && (current_user || User.new).can_read?(@document)
|
||||
return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.")
|
||||
return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.", status: :not_found)
|
||||
end
|
||||
|
||||
if @document.user.thredded_user_detail.moderation_state == "blocked"
|
||||
return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.")
|
||||
return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.", status: :not_found)
|
||||
end
|
||||
|
||||
# Put the focus on the document by removing Notebook.ai actions
|
||||
@ -81,7 +81,7 @@ class DocumentsController < ApplicationController
|
||||
|
||||
def analysis
|
||||
unless @document.present? && (current_user || User.new).can_read?(@document)
|
||||
redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.")
|
||||
redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.", status: :not_found)
|
||||
end
|
||||
|
||||
@analysis = @document.document_analysis.where.not(queued_at: nil).order('updated_at DESC').first
|
||||
@ -98,7 +98,7 @@ class DocumentsController < ApplicationController
|
||||
end
|
||||
|
||||
def queue_analysis
|
||||
return redirect_back(fallback_location: documents_path, notice: "That document doesn't exist!") unless @document.present?
|
||||
return redirect_back(fallback_location: documents_path, notice: "That document doesn't exist!", status: :not_found) unless @document.present?
|
||||
return redirect_back(fallback_location: documents_path, notice: "Document analysis is a feature for Premium users.") unless @document.user.on_premium_plan?
|
||||
return redirect_back(fallback_location: documents_path, notice: "You don't have permission to do that!") unless @document.user == current_user
|
||||
|
||||
@ -214,7 +214,7 @@ class DocumentsController < ApplicationController
|
||||
|
||||
def plaintext
|
||||
unless @document.present? && (current_user || User.new).can_read?(@document)
|
||||
return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.")
|
||||
return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.", status: :not_found)
|
||||
end
|
||||
|
||||
render layout: 'plaintext'
|
||||
@ -335,7 +335,7 @@ class DocumentsController < ApplicationController
|
||||
@document = Document.find_by(id: params[:id])
|
||||
|
||||
unless @document
|
||||
redirect_to root_path, notice: "Either that document doesn't exist or you don't have permission to view it!"
|
||||
redirect_to root_path, notice: "Either that document doesn't exist or you don't have permission to view it!", status: :not_found
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
@ -7,7 +7,7 @@ class UniversesController < ContentController
|
||||
@content_type = content_type_name.to_s.singularize.capitalize.constantize
|
||||
|
||||
@universe = Universe.find_by(id: params[:id])
|
||||
return redirect_to(root_path, notice: "That universe doesn't exist!") unless @universe.present?
|
||||
return redirect_to(root_path, notice: "That universe doesn't exist!", status: :not_found) unless @universe.present?
|
||||
@content_list = @universe.send(content_type_name)
|
||||
|
||||
# todo just use current_user.can_view?(@universe) and/or individual filtering
|
||||
|
||||
@ -99,7 +99,10 @@ class UsersController < ApplicationController
|
||||
@tag_slug = params[:tag_slug]
|
||||
@tag = PageTag.find_by(user_id: @user.id, slug: @tag_slug)
|
||||
|
||||
return redirect_to(profile_by_username_path(username: @user.username), notice: 'That tag does not exist.') if @tag.nil?
|
||||
if @tag.nil?
|
||||
redirect_url = @user.username.present? ? profile_by_username_path(username: @user.username) : user_path(@user)
|
||||
return redirect_to(redirect_url, notice: 'That tag does not exist.', status: :not_found)
|
||||
end
|
||||
|
||||
# Find all public content with this tag
|
||||
@tagged_content = []
|
||||
@ -181,7 +184,7 @@ class UsersController < ApplicationController
|
||||
|
||||
def set_user
|
||||
@user = User.find_by(user_params)
|
||||
return redirect_to(root_path, notice: 'That user does not exist.') if @user.nil?
|
||||
return redirect_to(root_path, notice: 'That user does not exist.', status: :not_found) if @user.nil?
|
||||
return redirect_to(root_path, notice: 'That user has chosen to hide their profile.') if @user.private_profile?
|
||||
return redirect_to(root_path, notice: 'That user has had their profile hidden.') if @user.thredded_user_detail.moderation_state == 'blocked'
|
||||
|
||||
|
||||
@ -25,11 +25,18 @@ class PermissionService < Service
|
||||
end
|
||||
|
||||
def self.user_can_contribute_to_containing_universe?(user:, content:)
|
||||
# Early return if no user is provided
|
||||
return false if user.nil?
|
||||
|
||||
# Special case for attribute-related content
|
||||
return true if [AttributeCategory, AttributeField, Attribute].include?(content.class) #todo audit this
|
||||
|
||||
return true if user.contributable_universe_ids.include?(content.universe_id)
|
||||
return true if user.universes.pluck(:id).include?(content.universe_id)
|
||||
# Handle cases where content might not have a universe_id
|
||||
return false if content.universe_id.nil?
|
||||
|
||||
# Check if user can contribute to this universe
|
||||
return true if user.respond_to?(:contributable_universe_ids) && user.contributable_universe_ids.include?(content.universe_id)
|
||||
return true if user.respond_to?(:universes) && user.universes.pluck(:id).include?(content.universe_id)
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
@ -80,7 +80,7 @@
|
||||
|
||||
<div class="chip z-depth-1 white" style="border: none; margin: 0; font-weight: 500;">
|
||||
<i class="material-icons left <%= @accent_color %>-text">category</i>
|
||||
<%= pluralize(content_types_count, 'category') %>
|
||||
<%= pluralize(content_types_count, 'page type') %>
|
||||
</div>
|
||||
|
||||
<div class="chip z-depth-1 white" style="border: none; margin: 0; font-weight: 500;">
|
||||
@ -106,20 +106,20 @@
|
||||
|
||||
<!-- Category quick filters -->
|
||||
<div class="col s12">
|
||||
<div class="category-filters z-depth-1" style="background-color: #fff; border-radius: 8px; padding: 16px; margin-bottom: 25px;">
|
||||
<div class="category-filters z-depth-1" style="border-radius: 8px; padding: 16px; margin-bottom: 25px;">
|
||||
<div class="category-filter-title" style="margin-bottom: 10px; display: flex; align-items: center;">
|
||||
<i class="material-icons <%= @accent_color %>-text" style="margin-right: 8px;">filter_list</i>
|
||||
<span style="font-weight: 500; font-size: 16px;">Filter by category:</span>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
|
||||
<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;">
|
||||
<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;">
|
||||
<i class="material-icons <%= @accent_color %>-text left" style="font-size: 18px; margin-right: 6px;">layers</i>
|
||||
All (<%= total_items %>)
|
||||
</a>
|
||||
|
||||
<% @tagged_content.each do |content_group| %>
|
||||
<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;">
|
||||
<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;">
|
||||
<i class="material-icons <%= content_group[:color] %>-text left" style="font-size: 18px; margin-right: 6px;"><%= content_group[:icon] %></i>
|
||||
<%= content_group[:type].pluralize %> (<%= content_group[:content].count %>)
|
||||
</a>
|
||||
@ -453,12 +453,23 @@
|
||||
.modal-how-to-join {
|
||||
border-radius: 8px;
|
||||
max-width: 500px;
|
||||
transition: background-color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .modal-how-to-join {
|
||||
background-color: #2D2D31 !important;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.modal-how-to-join h4 {
|
||||
margin-top: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
transition: color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .modal-how-to-join h4 {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.modal-how-to-join ol {
|
||||
@ -473,6 +484,17 @@
|
||||
background-color: #f3e5f5;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
transition: background-color 1s ease, color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .modal-how-to-join .highlight {
|
||||
background-color: rgba(156, 39, 176, 0.2);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Fix modal content border */
|
||||
body.dark .modal-content {
|
||||
border-color: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
:root {
|
||||
@ -545,6 +567,39 @@
|
||||
}
|
||||
|
||||
/* Category filters styling */
|
||||
.category-filters {
|
||||
background-color: #fff;
|
||||
transition: background-color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .category-filters {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.category-filter {
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
transition: background-color 1s ease, color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .category-filter {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Ensure filter text is always readable regardless of theme */
|
||||
.category-filter {
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
/* Fix the filter title text in dark mode */
|
||||
body.dark .category-filter-title span {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
body.dark .category-filter.active {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.category-filters {
|
||||
overflow-x: auto;
|
||||
@ -580,10 +635,16 @@
|
||||
|
||||
/* Better card styling */
|
||||
.hoverable.card {
|
||||
transition: box-shadow 0.25s, transform 0.25s;
|
||||
transition: box-shadow 0.25s, transform 0.25s, background-color 1s ease;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
body.dark .hoverable.card {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.hoverable.card:hover {
|
||||
@ -591,11 +652,20 @@
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
body.dark .hoverable.card:hover {
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
/* Position relative for absolute positioning within */
|
||||
.hoverable.card {
|
||||
position: relative;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 4px solid #eeeeee;
|
||||
transition: border-color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .hoverable.card {
|
||||
border-bottom: 4px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.js-content-card-container > a,
|
||||
@ -623,6 +693,11 @@
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
padding-bottom: 20px !important;
|
||||
transition: background-color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .card .card-content {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.tags-container {
|
||||
@ -653,6 +728,11 @@
|
||||
height: 15px;
|
||||
background: linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 100%);
|
||||
pointer-events: none;
|
||||
transition: background 1s ease;
|
||||
}
|
||||
|
||||
body.dark .card .card-content:after {
|
||||
background: linear-gradient(to bottom, rgba(40,40,45,0) 0%, rgba(40,40,45,1) 100%);
|
||||
}
|
||||
|
||||
/* Card footer line - more visible */
|
||||
@ -666,5 +746,24 @@
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 0 0 8px 8px;
|
||||
z-index: 1;
|
||||
transition: background-color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .card::after {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Dark mode text styles */
|
||||
body.dark .card .card-content span[style*="color: #424242"] {
|
||||
color: #ddd !important;
|
||||
}
|
||||
|
||||
body.dark .grey-text {
|
||||
color: #aaa !important;
|
||||
}
|
||||
|
||||
body.dark .tag-pill[style*="background-color: #f0f0f0"] {
|
||||
background-color: rgba(255, 255, 255, 0.1) !important;
|
||||
color: #aaa !important;
|
||||
}
|
||||
</style>
|
||||
@ -8,7 +8,7 @@
|
||||
<% raw_model.image_uploads.each do |image| %>
|
||||
<div class="row">
|
||||
<div class="col s12 m3">
|
||||
<%= link_to image.src(:original), class: 'z-depth-1 hoverable right', target: '_new' do %>
|
||||
<%= link_to image.src(:original), class: 'z-depth-1 hoverable right card', target: '_new' do %>
|
||||
<%= image_tag image.src(:small) %>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -20,9 +20,9 @@
|
||||
</small>
|
||||
</h5>
|
||||
<div>
|
||||
<%= link_to 'Open', image.src(:original), class: 'btn white black-text', target: '_new' %>
|
||||
<%= link_to 'Open', image.src(:original), class: 'btn black-text', target: '_new' %>
|
||||
<%= link_to 'Delete', image_deletion_path(image.id),
|
||||
class: 'btn white black-text js-remove-image',
|
||||
class: 'btn black-text js-remove-image',
|
||||
method: 'delete',
|
||||
remote: true,
|
||||
data: { confirm: "Are you sure? This can't be undone." } %>
|
||||
@ -35,7 +35,7 @@
|
||||
<% @basil_images.each do |commission| %>
|
||||
<div class="row">
|
||||
<div class="col s12 m3">
|
||||
<%= link_to commission.image, class: 'z-depth-1 hoverable right', target: '_new' do %>
|
||||
<%= link_to commission.image, class: 'z-depth-1 hoverable right card', target: '_new' do %>
|
||||
<%= image_tag commission.image, style: 'max-width: 100%' %>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -47,9 +47,9 @@
|
||||
</small>
|
||||
</h5>
|
||||
<div>
|
||||
<%= link_to 'Open', commission.image, class: 'btn white black-text', target: '_new' %>
|
||||
<%= link_to 'Open', commission.image, class: 'btn black-text', target: '_new' %>
|
||||
<%= link_to 'Delete', basil_delete_path(commission),
|
||||
class: 'btn white black-text js-remove-image',
|
||||
class: 'btn black-text js-remove-image',
|
||||
method: 'delete',
|
||||
remote: true,
|
||||
data: { confirm: "Are you sure? This can't be undone." } %>
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
<div class="row">
|
||||
<% all_images.each do |image_item| %>
|
||||
<div class="col s12 m6 l4">
|
||||
<div class="gallery-preview-card">
|
||||
<div class="gallery-preview-card card">
|
||||
<% if image_item[:type] == 'regular' %>
|
||||
<%= link_to image_item[:data].src(:original), target: "_blank" do %>
|
||||
<%= image_tag image_item[:data].src(:medium), class: 'gallery-preview-image' %>
|
||||
@ -44,7 +44,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="empty-gallery-message">
|
||||
<div class="empty-gallery-message card-panel">
|
||||
<p>No images have been added to this <%= content.class.name.downcase %> yet.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
@ -53,8 +53,8 @@
|
||||
<div class="card-panel <%= content.class.color %> lighten-5 gallery-cta-panel">
|
||||
<div class="row">
|
||||
<div class="col s12 m8">
|
||||
<h5 class="<%= content.class.text_color %> text-darken-2">View Full Gallery Experience</h5>
|
||||
<p>Check out our newly designed gallery page with larger images, lightbox viewing, and an overview section – perfect for sharing with others!</p>
|
||||
<h5 class="<%= content.class.text_color %> text-darken-2 black-text">View Full Gallery Experience</h5>
|
||||
<p class="black-text">Check out our newly designed gallery page with larger images, lightbox viewing, and an overview section – perfect for sharing with others!</p>
|
||||
</div>
|
||||
<div class="col s12 m4 center-align cta-button-container">
|
||||
<%= link_to send("gallery_#{content.class.name.downcase}_path", content), class: "btn-large waves-effect waves-light #{content.class.color}" do %>
|
||||
@ -81,6 +81,7 @@
|
||||
margin-bottom: 15px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.gallery-preview-card:hover {
|
||||
|
||||
@ -520,6 +520,11 @@
|
||||
.gallery-container {
|
||||
padding: 50px 0;
|
||||
background-color: #f9f9f9;
|
||||
transition: background-color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .gallery-container {
|
||||
background-color: #202123;
|
||||
}
|
||||
|
||||
.gallery-grid {
|
||||
@ -542,11 +547,16 @@
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.08);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease, background-color 1s ease;
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body.dark .gallery-card {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.gallery-card:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: 0 15px 30px rgba(0,0,0,0.15);
|
||||
@ -565,6 +575,11 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: border-color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .gallery-card-info {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.image-date {
|
||||
@ -572,6 +587,11 @@
|
||||
align-items: center;
|
||||
color: #757575;
|
||||
font-size: 0.9rem;
|
||||
transition: color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .image-date {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.image-date i {
|
||||
@ -643,11 +663,21 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 30px;
|
||||
transition: background-color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .empty-icon {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.empty-icon i {
|
||||
font-size: 50px;
|
||||
color: #9e9e9e;
|
||||
transition: color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .empty-icon i {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.empty-gallery h2 {
|
||||
@ -655,22 +685,42 @@
|
||||
margin-bottom: 15px;
|
||||
font-weight: 300;
|
||||
color: #424242;
|
||||
transition: color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .empty-gallery h2 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.empty-gallery p {
|
||||
margin-bottom: 30px;
|
||||
color: #757575;
|
||||
transition: color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .empty-gallery p {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
/* ===== OVERVIEW SECTION ===== */
|
||||
.overview-section {
|
||||
background-color: white;
|
||||
padding-bottom: 80px;
|
||||
transition: background-color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .overview-section {
|
||||
background-color: #2D2D31;
|
||||
}
|
||||
|
||||
.overview-background {
|
||||
background-color: #ffffff;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='%23<%= @content.class.hex_color.sub('#', '') %>' fill-opacity='0.03'%3E%3Cpath opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/%3E%3Cpath d='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
||||
transition: background-color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .overview-background {
|
||||
background-color: #2D2D31;
|
||||
}
|
||||
|
||||
.overview-header {
|
||||
@ -696,6 +746,11 @@
|
||||
font-weight: 300;
|
||||
margin: 0 0 15px;
|
||||
color: #212121;
|
||||
transition: color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .overview-title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.overview-subtitle {
|
||||
@ -724,6 +779,12 @@
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.04);
|
||||
transition: border-color 1s ease, box-shadow 1s ease;
|
||||
}
|
||||
|
||||
body.dark .overview-fields {
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.overview-field {
|
||||
@ -739,6 +800,11 @@
|
||||
right: 25px;
|
||||
height: 1px;
|
||||
background: #f5f5f5;
|
||||
transition: background-color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .overview-field:not(:last-child)::after {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.field-label {
|
||||
@ -748,11 +814,21 @@
|
||||
font-weight: 500;
|
||||
color: #9e9e9e;
|
||||
margin-bottom: 8px;
|
||||
transition: color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .field-label {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.field-value {
|
||||
color: #212121;
|
||||
line-height: 1.6;
|
||||
transition: color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .field-value {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.field-value p {
|
||||
@ -769,6 +845,12 @@
|
||||
background: #f9f9f9;
|
||||
border-radius: 10px;
|
||||
color: #757575;
|
||||
transition: background-color 1s ease, color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .no-overview-message {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.overview-cta {
|
||||
@ -780,6 +862,11 @@
|
||||
.related-section {
|
||||
background: #f5f5f5;
|
||||
padding: 70px 0;
|
||||
transition: background-color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .related-section {
|
||||
background: #202123;
|
||||
}
|
||||
|
||||
.related-header {
|
||||
@ -794,6 +881,11 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: color 1s ease;
|
||||
}
|
||||
|
||||
body.dark .related-title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.related-title i {
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
<div style="margin-right: 30px;">
|
||||
<strong style="font-size: 1.05rem;">Participating in Art Fight 2025?</strong>
|
||||
<div style="margin-top: 4px;">
|
||||
<p>
|
||||
<p class="black-text">
|
||||
Click to add the tag below and help others find your <%= page.class.name.downcase %>!
|
||||
</p>
|
||||
<div style="margin-top: 8px;">
|
||||
@ -19,7 +19,7 @@
|
||||
<span class="new badge purple white-text hoverable" data-badge-caption="ArtFight2025" style="margin: 0;"></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<p style="margin-top: 10px; font-size: 0.9rem; color: #222;">
|
||||
<p style="margin-top: 10px; font-size: 0.9rem;" class="black-text">
|
||||
<i class="material-icons tiny">lock</i>
|
||||
Remember to set your <%= page.class.name.downcase %> to <strong>public</strong> in the privacy toggle if you want others to see it. All pages are private by default.
|
||||
</p>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="collection-item" style="padding: 14px">
|
||||
<% @popular_tags.each do |tag| %>
|
||||
<%= link_to user_tag_path(username: @user.username, tag_slug: tag.slug) do %>
|
||||
<%= link_to @user.username.present? ? user_tag_path(username: @user.username, tag_slug: tag.slug) : user_id_tag_path(id: @user.id, tag_slug: tag.slug) do %>
|
||||
<span class="new badge blue lighten-1 left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
|
||||
<div style="margin-left: 15px;">
|
||||
<div>
|
||||
<%= link_to profile_by_username_path(username: @user.username), class: "white-text" do %>
|
||||
<%= link_to @user.username.present? ? profile_by_username_path(username: @user.username) : user_path(@user), class: "white-text" do %>
|
||||
<span style="font-size: 20px; font-weight: 500; text-shadow: 0 1px 3px rgba(0,0,0,0.3); line-height: 1.2; display: block;">
|
||||
<%= @user.display_name %>
|
||||
</span>
|
||||
@ -65,10 +65,10 @@
|
||||
|
||||
<div class="chip z-depth-0 <%= @accent_color %> lighten-3" style="border: none; margin: 0;">
|
||||
<i class="material-icons left">category</i>
|
||||
<%= pluralize(content_types_count, 'category') %>
|
||||
<%= pluralize(content_types_count, 'page type') %>
|
||||
</div>
|
||||
|
||||
<%= link_to profile_by_username_path(username: @user.username), class: "chip grey lighten-4" do %>
|
||||
<%= link_to @user.username.present? ? profile_by_username_path(username: @user.username) : user_path(@user), class: "chip grey lighten-4" do %>
|
||||
<i class="material-icons left">person</i>
|
||||
View profile
|
||||
<% end %>
|
||||
@ -147,7 +147,7 @@
|
||||
<% if tag.tag == @tag.tag %>
|
||||
<span class="new badge <%= content_group[:color] %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% else %>
|
||||
<%= link_to user_tag_path(username: content.user.username, tag_slug: tag.slug) do %>
|
||||
<%= link_to content.user.username.present? ? user_tag_path(username: content.user.username, tag_slug: tag.slug) : user_id_tag_path(id: content.user.id, tag_slug: tag.slug) do %>
|
||||
<span class="new badge grey left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@ -183,7 +183,7 @@
|
||||
<% if tag.tag == @tag.tag %>
|
||||
<span class="new badge <%= content_group[:color] %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% else %>
|
||||
<%= link_to user_tag_path(username: content.user.username, tag_slug: tag.slug) do %>
|
||||
<%= link_to content.user.username.present? ? user_tag_path(username: content.user.username, tag_slug: tag.slug) : user_id_tag_path(id: content.user.id, tag_slug: tag.slug) do %>
|
||||
<span class="new badge grey left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@ -110,6 +110,9 @@ Rails.application.routes.draw do
|
||||
get '/@:username/followers', to: 'users#followers'
|
||||
get '/@:username/following', to: 'users#following'
|
||||
get '/@:username/tag/:tag_slug', to: 'users#tag', as: :user_tag
|
||||
|
||||
# ID-based alternative routes for users without usernames
|
||||
get '/users/:id/tag/:tag_slug', to: 'users#tag', as: :user_id_tag
|
||||
|
||||
resources :documents do
|
||||
# Document Analysis routes
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
require 'test_helper'
|
||||
|
||||
class NoticeDismissalControllerTest < ActionDispatch::IntegrationTest
|
||||
# test "should get dismiss" do
|
||||
# get notice_dismissal_dismiss_url
|
||||
# assert_response :success
|
||||
# end
|
||||
|
||||
# Tests are disabled until properly implemented
|
||||
def test_should_get_root_url
|
||||
skip("Test not implemented yet")
|
||||
end
|
||||
end
|
||||
|
||||
4
test/fixtures/user.yml
vendored
4
test/fixtures/user.yml
vendored
@ -1,11 +1,11 @@
|
||||
starter:
|
||||
id: 1
|
||||
id: 3
|
||||
name: Starter User
|
||||
email: test@test.test
|
||||
selected_billing_plan_id: 1
|
||||
|
||||
premium:
|
||||
id: 2
|
||||
id: 4
|
||||
name: Premium User
|
||||
email: test@test.test
|
||||
selected_billing_plan_id: 3
|
||||
|
||||
@ -9,7 +9,7 @@ require "rails/test_help"
|
||||
|
||||
class ActiveSupport::TestCase
|
||||
# Run tests in parallel with specified workers
|
||||
parallelize(workers: :number_of_processors)
|
||||
# parallelize(workers: :number_of_processors) # Temporarily disabled to fix test issues
|
||||
|
||||
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
|
||||
fixtures :all
|
||||
|
||||
Loading…
Reference in New Issue
Block a user