mirror of
https://github.com/indentlabs/notebook.git
synced 2025-10-26 11:19:22 +00:00
add new tags page to profiles
This commit is contained in:
parent
0cc79606ee
commit
83197ced56
131
CLAUDE.md
Normal file
131
CLAUDE.md
Normal file
@ -0,0 +1,131 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Common Commands
|
||||
|
||||
### Setup and Installation
|
||||
```bash
|
||||
# Install dependencies
|
||||
bundle install
|
||||
|
||||
# Install JavaScript dependencies
|
||||
yarn install
|
||||
|
||||
# Setup database
|
||||
rake db:setup # Creates and seeds the database
|
||||
rake db:migrate # Apply all pending migrations
|
||||
```
|
||||
|
||||
### Development
|
||||
```bash
|
||||
# Start the Rails server
|
||||
rails server
|
||||
# Or
|
||||
rails s
|
||||
|
||||
# Start Sidekiq for background jobs
|
||||
bundle exec sidekiq
|
||||
|
||||
# Start the development server with both web and worker processes
|
||||
RAILS_GROUPS=web,worker rails server
|
||||
```
|
||||
|
||||
### Testing
|
||||
```bash
|
||||
# Run all tests
|
||||
rails test
|
||||
|
||||
# Run a specific test file
|
||||
rails test path/to/test.rb
|
||||
|
||||
# Run specific test
|
||||
rails test path/to/test.rb:LINE_NUMBER
|
||||
```
|
||||
|
||||
### Database Operations
|
||||
```bash
|
||||
# Reset database (CAUTION: Destroys all data)
|
||||
rake db:reset
|
||||
|
||||
# Migrate database
|
||||
rake db:migrate
|
||||
|
||||
# Rollback last migration
|
||||
rake db:rollback
|
||||
```
|
||||
|
||||
### Asset Management
|
||||
```bash
|
||||
# Compile assets
|
||||
rails assets:precompile
|
||||
```
|
||||
|
||||
### Deployment
|
||||
```bash
|
||||
# Run in production mode
|
||||
RAILS_ENV=production rails server
|
||||
```
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Notebook.ai is a Rails application for writers and roleplayers to create and manage fictional universes and their components. The application follows standard Rails MVC architecture with some specific design patterns.
|
||||
|
||||
### Key Concepts
|
||||
|
||||
1. **Content Types**: The application revolves around different content types like Universe, Character, Location, etc. Each represents a different entity within a user's fictional world.
|
||||
|
||||
2. **Content Pages**: All content types inherit from a shared ContentPage concern, which provides common functionality like attributes, privacy settings, and image uploads.
|
||||
|
||||
3. **Universes**: The top-level organizational unit. Users create universes and add various content types within them.
|
||||
|
||||
4. **Attributes System**: The application uses a flexible attributes system to store custom fields for each content type, allowing for extensibility without schema changes.
|
||||
|
||||
5. **Privacy & Sharing**: Content can be private, public, or shared with specific collaborators.
|
||||
|
||||
### Core Components
|
||||
|
||||
#### Content Structure
|
||||
- **Universe**: The top-level container for all world-building elements
|
||||
- **Character**, **Location**, **Item**: Core content types that are available to all users
|
||||
- **Premium Content Types**: Many additional content types (Creature, Planet, Religion, etc.) available to premium users
|
||||
|
||||
#### Key Modules and Concerns
|
||||
- `BelongsToUniverse`: Associates content with universes
|
||||
- `IsContentPage`: Provides shared content page functionality
|
||||
- `HasAttributes`: Handles dynamic attributes for content types
|
||||
- `HasPrivacy`: Manages content privacy settings
|
||||
- `HasImageUploads`: Handles image attachment functionality
|
||||
- `ContentPage`: Base behavior for all content pages
|
||||
|
||||
#### Background Processing
|
||||
- Sidekiq is used for background job processing
|
||||
- Document analysis, exports, and other intensive tasks run asynchronously
|
||||
|
||||
#### Data Flow
|
||||
1. Users create universes to contain their fictional worlds
|
||||
2. Within universes, users create various content types (characters, locations, etc.)
|
||||
3. Content types can be linked together with relationships (e.g., a character can be linked to locations)
|
||||
4. Users can collaborate on universes, allowing multiple people to contribute to the same world
|
||||
|
||||
### Directory Structure
|
||||
|
||||
Beyond the standard Rails structure, notable directories include:
|
||||
- `app/models/page_types/`: Contains all content type models
|
||||
- `app/models/page_groupers/`: Contains relationship models between content types
|
||||
- `config/attributes/`: Configuration for content type attributes
|
||||
- `app/authorizers/`: Authorization logic for content access
|
||||
|
||||
### Key Files
|
||||
- `config/initializers/content_types.rb`: Defines all available content types
|
||||
- `app/models/content_page.rb`: Base functionality for all content pages
|
||||
- `app/controllers/content_controller.rb`: Base controller for all content types
|
||||
|
||||
## Content Type System
|
||||
|
||||
The application uses a content type system with these types of pages:
|
||||
- Universe (top-level container)
|
||||
- Character, Location, Item (core types)
|
||||
- Many premium content types like Creature, Planet, Religion, etc.
|
||||
|
||||
Creating a new content type requires following the process in `docs/content_types.md`.
|
||||
@ -1,5 +1,5 @@
|
||||
class UsersController < ApplicationController
|
||||
before_action :set_user, only: [:show, :followers, :following]
|
||||
before_action :set_user, only: [:show, :followers, :following, :tag]
|
||||
|
||||
def index
|
||||
redirect_to new_session_path(User)
|
||||
@ -15,6 +15,9 @@ class UsersController < ApplicationController
|
||||
|
||||
@content = @user.public_content.select { |type, list| list.any? }
|
||||
@tabs = @content.keys
|
||||
|
||||
# Get popular tags for this user's public content
|
||||
@popular_tags = get_popular_public_tags_for_user(@user)
|
||||
|
||||
@favorite_content = @user.favorite_page_type? ? @user.send(@user.favorite_page_type.downcase.pluralize).is_public : []
|
||||
@stream = @user.recent_content_list(limit: 20)
|
||||
@ -91,6 +94,88 @@ class UsersController < ApplicationController
|
||||
|
||||
def following
|
||||
end
|
||||
|
||||
def tag
|
||||
@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?
|
||||
|
||||
# Find all public content with this tag
|
||||
@tagged_content = []
|
||||
|
||||
# Go through each content type and find items with this tag
|
||||
Rails.application.config.content_types[:all].each do |content_type|
|
||||
content_pages = content_type.joins(:page_tags)
|
||||
.where(privacy: 'public')
|
||||
.where(user_id: @user.id)
|
||||
.where(page_tags: { slug: @tag_slug })
|
||||
.order(:name)
|
||||
|
||||
@tagged_content << {
|
||||
type: content_type.name,
|
||||
icon: content_type.icon,
|
||||
color: content_type.color,
|
||||
content: content_pages
|
||||
} if content_pages.any?
|
||||
end
|
||||
|
||||
# Add documents and timelines if they have the tag
|
||||
# Handle documents separately since they don't use the common content type structure
|
||||
documents = Document.joins(:page_tags)
|
||||
.where(privacy: 'public')
|
||||
.where(user_id: @user.id)
|
||||
.where(page_tags: { slug: @tag_slug })
|
||||
.order(:title) # Documents use 'title' instead of 'name'
|
||||
|
||||
@tagged_content << {
|
||||
type: 'Document',
|
||||
icon: 'description',
|
||||
color: 'blue',
|
||||
content: documents
|
||||
} if documents.any?
|
||||
|
||||
# Handle timelines separately since they don't use the common content type structure
|
||||
timelines = Timeline.joins(:page_tags)
|
||||
.where(privacy: 'public')
|
||||
.where(user_id: @user.id)
|
||||
.where(page_tags: { slug: @tag_slug })
|
||||
.order(:name)
|
||||
|
||||
@tagged_content << {
|
||||
type: 'Timeline',
|
||||
icon: 'timeline',
|
||||
color: 'blue',
|
||||
content: timelines
|
||||
} if timelines.any?
|
||||
|
||||
# Get images for content cards
|
||||
@random_image_including_private_pool_cache = ImageUpload.where(
|
||||
user_id: @user.id,
|
||||
).group_by { |image| [image.content_type, image.content_id] }
|
||||
|
||||
# Collect all content IDs and types for fetching basil commissions
|
||||
basil_entity_types = []
|
||||
basil_entity_ids = []
|
||||
|
||||
@tagged_content.each do |content_group|
|
||||
content_group[:content].each do |content|
|
||||
basil_entity_types << content.class.name
|
||||
basil_entity_ids << content.id
|
||||
end
|
||||
end
|
||||
|
||||
# Initialize @saved_basil_commissions if there are any content items
|
||||
if basil_entity_types.any?
|
||||
@saved_basil_commissions = BasilCommission.where(
|
||||
entity_type: basil_entity_types,
|
||||
entity_id: basil_entity_ids
|
||||
).where.not(saved_at: nil)
|
||||
.group_by { |commission| [commission.entity_type, commission.entity_id] }
|
||||
end
|
||||
|
||||
@sidenav_expansion = 'community'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@ -107,4 +192,49 @@ class UsersController < ApplicationController
|
||||
def user_params
|
||||
params.permit(:id, :username)
|
||||
end
|
||||
|
||||
# Get most popular tags for a user's public content
|
||||
def get_popular_public_tags_for_user(user, limit: 10)
|
||||
# Find page tags attached to public content
|
||||
public_page_tags = []
|
||||
|
||||
# For each content type, find public pages with tags
|
||||
Rails.application.config.content_types[:all].each do |content_type|
|
||||
# Skip if a user has no pages of this type
|
||||
next unless user.respond_to?(content_type.name.downcase.pluralize)
|
||||
|
||||
# Get all public pages of this content type
|
||||
public_pages = user.send(content_type.name.downcase.pluralize).is_public
|
||||
|
||||
# Skip if there are no public pages
|
||||
next if public_pages.empty?
|
||||
|
||||
# Find all page tags for these pages
|
||||
tag_ids = PageTag.where(page_type: content_type.name, page_id: public_pages.pluck(:id)).pluck(:id)
|
||||
public_page_tags.concat(tag_ids) if tag_ids.any?
|
||||
end
|
||||
|
||||
# Also include Document and Timeline tags if they're public
|
||||
public_documents = user.documents.where(privacy: 'public')
|
||||
if public_documents.any?
|
||||
document_tag_ids = PageTag.where(page_type: 'Document', page_id: public_documents.pluck(:id)).pluck(:id)
|
||||
public_page_tags.concat(document_tag_ids) if document_tag_ids.any?
|
||||
end
|
||||
|
||||
public_timelines = user.timelines.where(privacy: 'public')
|
||||
if public_timelines.any?
|
||||
timeline_tag_ids = PageTag.where(page_type: 'Timeline', page_id: public_timelines.pluck(:id)).pluck(:id)
|
||||
public_page_tags.concat(timeline_tag_ids) if timeline_tag_ids.any?
|
||||
end
|
||||
|
||||
# If we have tags, find the most popular ones
|
||||
return [] if public_page_tags.empty?
|
||||
|
||||
# Get the actual tags
|
||||
PageTag.where(id: public_page_tags)
|
||||
.select('tag, slug, COUNT(*) as usage_count')
|
||||
.group(:tag, :slug)
|
||||
.order('usage_count DESC')
|
||||
.limit(limit)
|
||||
end
|
||||
end
|
||||
|
||||
@ -15,7 +15,9 @@
|
||||
<span class="new badge <%= raw_model.class.color %> left" data-badge-caption="<%= tag %>"></span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<span class="new badge <%= raw_model.class.color %> left" data-badge-caption="<%= tag %>"></span>
|
||||
<%= link_to user_tag_path(username: content.user.username, tag_slug: PageTagService.slug_for(tag)) do %>
|
||||
<span class="new badge <%= raw_model.class.color %> left" data-badge-caption="<%= tag %>"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@ -33,7 +33,9 @@
|
||||
<span class="new badge <%= params[:tag] == tag.slug ? content_type.color : 'grey' %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<span class="new badge <%= params[:tag] == tag.slug ? content_type.color : 'grey' %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<%= link_to user_tag_path(username: content.user.username, tag_slug: tag.slug) do %>
|
||||
<span class="new badge <%= params[:tag] == tag.slug ? content_type.color : 'grey' %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</p>
|
||||
@ -80,7 +82,9 @@
|
||||
<span class="new badge <%= params[:tag] == tag.slug ? content_type.color : 'grey' %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<span class="new badge <%= params[:tag] == tag.slug ? content_type.color : 'grey' %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<%= link_to user_tag_path(username: content.user.username, tag_slug: tag.slug) do %>
|
||||
<span class="new badge <%= params[:tag] == tag.slug ? content_type.color : 'grey' %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
@ -31,7 +31,9 @@
|
||||
<span class="new badge <%= params[:tag] == tag.slug ? 'orange' : 'grey' %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<span class="new badge <%= params[:tag] == tag.slug ? 'orange' : 'grey' %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<%= link_to user_tag_path(username: content.user.username, tag_slug: tag.slug) do %>
|
||||
<span class="new badge <%= params[:tag] == tag.slug ? 'orange' : 'grey' %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
@ -61,7 +61,15 @@
|
||||
<% if page_tags.any? %>
|
||||
<p class="tags-container">
|
||||
<% page_tags.each do |tag| %>
|
||||
<span class="new badge <%= params[:tag] == tag.slug ? 'orange' : Document.color %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% if user_signed_in? && document.user == current_user %>
|
||||
<%= link_to params.permit(:tag).merge({ tag: tag.slug }) do %>
|
||||
<span class="new badge <%= params[:tag] == tag.slug ? 'orange' : Document.color %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= link_to user_tag_path(username: document.user.username, tag_slug: tag.slug) do %>
|
||||
<span class="new badge <%= params[:tag] == tag.slug ? 'orange' : Document.color %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
@ -61,7 +61,9 @@
|
||||
<span class="new badge <%= params[:tag] == tag.slug ? content.class.color : 'grey' %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<span class="new badge <%= params[:tag] == tag.slug ? content.class.color : 'grey' %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<%= link_to user_tag_path(username: content.user.username, tag_slug: tag.slug) do %>
|
||||
<span class="new badge <%= params[:tag] == tag.slug ? content.class.color : 'grey' %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
17
app/views/users/profile/_tags.html.erb
Normal file
17
app/views/users/profile/_tags.html.erb
Normal file
@ -0,0 +1,17 @@
|
||||
<% if @popular_tags.any? %>
|
||||
<div class="collection with-header hoverable">
|
||||
<div class="collection-header blue lighten-1 white-text">
|
||||
<div style="padding: 5px 10px">
|
||||
Tags
|
||||
</div>
|
||||
</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 %>
|
||||
<span class="new badge blue lighten-1 left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div style="clear: both"></div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
@ -39,9 +39,9 @@
|
||||
</div>
|
||||
<div class="card-tabs">
|
||||
<ul class="tabs tabs-fixed-width">
|
||||
<li class="tab col s3"><a class="active blue-text" href="#tab-about-me">About Me</a></li>
|
||||
<li class="tab col s3"><a class="active blue-text" href="#tab-universes">Notebook</a></li>
|
||||
<li class="tab col s3"><a class="blue-text" href="#tab-about-me">About Me</a></li>
|
||||
<li class="tab col s3"><a class="blue-text" href="#tab-recent-activity">Recent Activity</a></li>
|
||||
<li class="tab col s3"><a class="blue-text" href="#tab-universes">Universes</a></li>
|
||||
<!--<li class="tab col s3"><a class="blue-text" href="#tab-documents">Documents</a></li>-->
|
||||
<% if show_collections_tab %>
|
||||
<li class="tab col s3"><a class="blue-text" href="#tab-collections">Collections</a></li>
|
||||
@ -53,7 +53,43 @@
|
||||
|
||||
<div class="row user-profile-ui">
|
||||
<div class="col s12">
|
||||
|
||||
<div id="tab-universes">
|
||||
<% if user_signed_in? && @user.blocked_by?(current_user) %>
|
||||
<p class="card-panel red center lighten-5 black-text">
|
||||
You've blocked this user.
|
||||
</p>
|
||||
<% else %>
|
||||
<div class="row" style="margin-top: 100px">
|
||||
<% @user.universes.is_public.each do |universe| %>
|
||||
<div class="col s12 m12 l6">
|
||||
<%= link_to universe do %>
|
||||
<div class="hoverable card">
|
||||
<div class="card-image">
|
||||
<%= image_tag universe.first_public_image %>
|
||||
<span class="card-title"><%= universe.name %></span>
|
||||
</div>
|
||||
<div class="card-content <%= Universe.color %> white-text fixed-card-content">
|
||||
<p><%= truncate(universe.description, length: 140) %></p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col s12 m4 l4">
|
||||
You can also browse this user's public pages directly.
|
||||
<% if @content.empty? %>
|
||||
However, they haven't made anything public yet!
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="col s12 m8 l8">
|
||||
<%= render partial: 'users/profile/public_pages' %>
|
||||
<%= render partial: 'users/profile/tags' %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div id="tab-about-me">
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
@ -89,42 +125,6 @@
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div id="tab-universes">
|
||||
<% if user_signed_in? && @user.blocked_by?(current_user) %>
|
||||
<p class="card-panel red center lighten-5 black-text">
|
||||
You've blocked this user.
|
||||
</p>
|
||||
<% else %>
|
||||
<div class="row">
|
||||
<% @user.universes.is_public.each do |universe| %>
|
||||
<div class="col s12 m12 l6">
|
||||
<%= link_to universe do %>
|
||||
<div class="hoverable card">
|
||||
<div class="card-image">
|
||||
<%= image_tag universe.first_public_image %>
|
||||
<span class="card-title"><%= universe.name %></span>
|
||||
</div>
|
||||
<div class="card-content <%= Universe.color %> white-text fixed-card-content">
|
||||
<p><%= truncate(universe.description, length: 140) %></p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col s12 m4 l4">
|
||||
You can also browse this user's public pages directly.
|
||||
<% if @content.empty? %>
|
||||
However, they haven't made anything public yet!
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="col s12 m8 l8">
|
||||
<%= render partial: 'users/profile/public_pages' %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div id="tab-collections">
|
||||
<% if user_signed_in? && @user.blocked_by?(current_user) %>
|
||||
<p class="card-panel red center lighten-5 black-text">
|
||||
|
||||
208
app/views/users/tag.html.erb
Normal file
208
app/views/users/tag.html.erb
Normal file
@ -0,0 +1,208 @@
|
||||
<div class="row" style="margin-bottom: 0;">
|
||||
<div class="col s12">
|
||||
<%
|
||||
# Generate a consistent pattern URL for the background based on tag name
|
||||
pattern_seed = @tag.tag.bytes.sum % 5 + 1
|
||||
pattern_url = asset_path("card-headers/patterns/pattern#{pattern_seed}.png")
|
||||
|
||||
# Calculate total items for the stats display
|
||||
total_items = @tagged_content.sum { |group| group[:content].count }
|
||||
content_types_count = @tagged_content.size
|
||||
%>
|
||||
|
||||
<div class="card z-depth-1" style="border-radius: 4px; overflow: hidden;">
|
||||
<!-- Header with hero image background -->
|
||||
<div class="<%= @accent_color %>" style="position: relative; background: url('<%= pattern_url %>') center center; background-size: cover; height: 180px;">
|
||||
<!-- Dark overlay for better text contrast -->
|
||||
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.3);"></div>
|
||||
|
||||
<!-- Tag floating badge -->
|
||||
<div style="position: absolute; bottom: -24px; left: 24px; z-index: 2;">
|
||||
<div style="display: inline-block; background-color: white; padding: 14px 22px; border-radius: 4px; box-shadow: 0 2px 10px 0 rgba(0,0,0,0.2);">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<i class="material-icons <%= @accent_color %>-text" style="font-size: 28px; margin-right: 12px;"><%= PageTag.icon %></i>
|
||||
<span style="font-size: 28px; font-weight: 500;" class="<%= @accent_color %>-text"><%= @tag.tag %></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User info - prominent but elegant -->
|
||||
<div style="position: absolute; top: 20px; left: 24px; z-index: 1; display: flex; align-items: center;">
|
||||
<% if @user.image_url %>
|
||||
<%= image_tag @user.image_url.html_safe, style: 'width: 56px; height: 56px; border-radius: 50%; border: 2px solid white; box-shadow: 0 2px 8px rgba(0,0,0,0.2);' %>
|
||||
<% else %>
|
||||
<div style="width: 56px; height: 56px; background-color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0,0,0,0.2);">
|
||||
<i class="material-icons" style="font-size: 32px; color: #757575;">person</i>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div style="margin-left: 15px;">
|
||||
<div>
|
||||
<%= link_to profile_by_username_path(username: @user.username), 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>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats bar - clean and elegant -->
|
||||
<div class="<%= @accent_color %> lighten-5" style="padding: 30px 24px 16px 24px;">
|
||||
<div style="display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center;">
|
||||
<!-- Left section (for title) -->
|
||||
<div style="margin-bottom: 10px;">
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Right section (for stats) -->
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 10px;">
|
||||
<div class="chip z-depth-0 <%= @accent_color %> lighten-3" style="border: none; margin: 0;">
|
||||
<i class="material-icons left"><%= PageTag.icon %></i>
|
||||
<%= pluralize(total_items, 'item') %>
|
||||
</div>
|
||||
|
||||
<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') %>
|
||||
</div>
|
||||
|
||||
<%= link_to profile_by_username_path(username: @user.username), class: "chip grey lighten-4" do %>
|
||||
<i class="material-icons left">person</i>
|
||||
View profile
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12">
|
||||
<% if @tagged_content.empty? %>
|
||||
<div class="center-align" style="margin-top: 80px">
|
||||
<h5 class="grey-text">No public content with this tag</h5>
|
||||
<p class="grey-text">
|
||||
Either <%= @user.display_name %> has not tagged any public content with "<%= @tag.tag %>" or this content has been deleted.
|
||||
</p>
|
||||
</div>
|
||||
<% else %>
|
||||
<% @tagged_content.each do |content_group| %>
|
||||
<div class="section">
|
||||
<h5 class="<%= content_group[:color] %>-text">
|
||||
<i class="material-icons left"><%= content_group[:icon] %></i>
|
||||
<%= content_group[:type].pluralize %> (<%= content_group[:content].count %>)
|
||||
|
||||
<span class="grey-text right" style="font-size: 14px; font-weight: normal; margin-top: 5px;">
|
||||
<%
|
||||
content_type = content_group[:type]
|
||||
if content_type == "Document"
|
||||
content_type_path = profile_by_username_path(username: @user.username)
|
||||
elsif content_type == "Timeline"
|
||||
content_type_path = nil # No specific timeline route in user profile
|
||||
else
|
||||
content_type_path = send("#{content_type.downcase.pluralize}_user_path", id: @user.id) rescue nil
|
||||
end
|
||||
%>
|
||||
<% if content_type_path %>
|
||||
<%= link_to content_type_path, class: "grey-text" do %>
|
||||
<i class="material-icons tiny" style="position: relative; top: 2px;">visibility</i>
|
||||
View all <%= content_group[:type].downcase.pluralize %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</span>
|
||||
</h5>
|
||||
|
||||
<div class="row js-content-cards-list">
|
||||
<% content_group[:content].each do |content| %>
|
||||
<div class="col s12 m6 l4 js-content-card-container">
|
||||
<div class="hoverable card sticky-action" style="margin-bottom: 2px">
|
||||
<div class="card-image waves-effect waves-block waves-light">
|
||||
<%
|
||||
# Find image for this content following the same pattern as in _cards.html.erb
|
||||
content_image = @random_image_including_private_pool_cache.fetch([content.class.name, content.id], [])
|
||||
.sample
|
||||
.try(:src, :medium)
|
||||
|
||||
if @saved_basil_commissions
|
||||
content_image ||= @saved_basil_commissions.fetch([content.class.name, content.id], [])
|
||||
.sample
|
||||
.try(:image)
|
||||
.try(:url)
|
||||
end
|
||||
|
||||
content_image ||= asset_path("card-headers/#{content.class.name.downcase.pluralize}.jpg")
|
||||
%>
|
||||
<div class="activator" style="height: 265px; background: url('<%= content_image %>'); background-size: cover;"></div>
|
||||
|
||||
<span class="card-title js-content-name activator">
|
||||
<div class="bordered-text">
|
||||
<% content_name = content.respond_to?(:name) ? content.name : content.title %>
|
||||
<%= ContentFormatterService.show(text: content_name.presence || 'Untitled', viewing_user: current_user) %>
|
||||
</div>
|
||||
|
||||
<% if content.respond_to?(:page_tags) %>
|
||||
<p class="tags-container">
|
||||
<% content.page_tags.each do |tag| %>
|
||||
<% 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 %>
|
||||
<span class="new badge grey left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</p>
|
||||
<% end %>
|
||||
</span>
|
||||
</div>
|
||||
<% if user_signed_in? %>
|
||||
<div class="card-action">
|
||||
<% if content.is_a?(Document) %>
|
||||
<%= link_to 'View', content, class: 'blue-text text-lighten-1', target: '_blank' %>
|
||||
<% else %>
|
||||
<%= link_to 'View', content, class: 'blue-text text-lighten-1' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="card-reveal">
|
||||
<span class="card-title">
|
||||
<%= content_name.presence || 'Untitled' %>
|
||||
<i class="material-icons right">close</i>
|
||||
</span>
|
||||
<% content_description = content.try(:description) || content.try(:synopsis) %>
|
||||
<% if content_description.present? %>
|
||||
<p>
|
||||
<%= sanitize ContentFormatterService.show(text: truncate(content_description, length: 420, escape: false), viewing_user: current_user) %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<% if content.respond_to?(:page_tags) %>
|
||||
<p class="tags-container">
|
||||
<% content.page_tags.each do |tag| %>
|
||||
<% 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 %>
|
||||
<span class="new badge grey left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</p>
|
||||
<% end %>
|
||||
<div class="clearfix"></div>
|
||||
<p class="grey-text clearfix">
|
||||
<%= content.created_at == content.updated_at ? 'created' : 'last updated' %>
|
||||
<%= time_ago_in_words content.updated_at %>
|
||||
ago
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
@ -102,6 +102,7 @@ Rails.application.routes.draw do
|
||||
get '/@:username', to: 'users#show', as: :profile_by_username
|
||||
get '/@:username/followers', to: 'users#followers'
|
||||
get '/@:username/following', to: 'users#following'
|
||||
get '/@:username/tag/:tag_slug', to: 'users#tag', as: :user_tag
|
||||
|
||||
resources :documents do
|
||||
# Document Analysis routes
|
||||
|
||||
Loading…
Reference in New Issue
Block a user