diff --git a/app/controllers/browse_controller.rb b/app/controllers/browse_controller.rb new file mode 100644 index 00000000..5df0d98a --- /dev/null +++ b/app/controllers/browse_controller.rb @@ -0,0 +1,150 @@ +class BrowseController < ApplicationController + # This controller handles browsing global content across the site + + def tag + @tag_slug = params[:tag_slug] + # Debug logging + Rails.logger.info "BrowseController#tag - Looking for content with tag slug: #{@tag_slug}" + + # For now, only allow ArtFight2025 tag to be browsed (case insensitive) + unless @tag_slug.downcase == 'artfight2025' + redirect_to root_path, notice: 'This tag is not available for browsing.' + return + end + + @tag = 'ArtFight2025' + @tag_slug = 'artfight2025' # Use the correctly slugified version + @tagged_content = [] + + # Directly check database for any pages with this tag + tag_exists = PageTag.exists?(slug: @tag_slug) + Rails.logger.info "Tag exists in PageTag table? #{tag_exists}" + + if tag_exists + sample_tag = PageTag.find_by(slug: @tag_slug) + Rails.logger.info "Sample tag: #{sample_tag.inspect}" + end + + # Go through each content type and find public items with this tag + Rails.application.config.content_types[:all].each do |content_type| + # Try a different query approach - first find page IDs with this tag + tag_page_ids = PageTag.where(page_type: content_type.name, slug: @tag_slug).pluck(:page_id) + + if tag_page_ids.any? + Rails.logger.info "Found #{tag_page_ids.count} #{content_type.name} page IDs with tag: #{tag_page_ids}" + content_pages = content_type.where(id: tag_page_ids).where(privacy: 'public').order(:name) + else + Rails.logger.info "No #{content_type.name} pages found with tag" + content_pages = content_type.none + end + + @tagged_content << { + type: content_type.name, + icon: content_type.icon, + color: content_type.color, + content: content_pages + } if content_pages.any? + 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) + if document_tag_page_ids.any? + Rails.logger.info "Found #{document_tag_page_ids.count} Document page IDs with tag: #{document_tag_page_ids}" + documents = Document.where(id: document_tag_page_ids).where(privacy: 'public').order(:title) + else + Rails.logger.info "No Document pages found with tag" + documents = Document.none + end # Documents use 'title' instead of 'name' + + @tagged_content << { + type: 'Document', + icon: 'description', + color: 'blue', + content: documents + } if documents.any? + + # 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) + if timeline_tag_page_ids.any? + Rails.logger.info "Found #{timeline_tag_page_ids.count} Timeline page IDs with tag: #{timeline_tag_page_ids}" + timelines = Timeline.where(id: timeline_tag_page_ids).where(privacy: 'public').order(:name) + else + Rails.logger.info "No Timeline pages found with tag" + timelines = Timeline.none + end + + @tagged_content << { + type: 'Timeline', + icon: 'timeline', + color: 'blue', + content: timelines + } if timelines.any? + + # Get images for content cards from all users + content_ids_by_type = {} + user_ids = [] + + @tagged_content.each do |content_group| + content_group[:content].each do |content| + content_type = content.class.name + content_ids_by_type[content_type] ||= [] + content_ids_by_type[content_type] << content.id + user_ids << content.user_id + end + end + + # Get unique user IDs + user_ids.uniq! + + # Get all relevant images + @random_image_pool_cache = {} + if content_ids_by_type.any? + content_ids_by_type.each do |content_type, ids| + images = ImageUpload.where(content_type: content_type, content_id: ids) + + images.each do |image| + key = [image.content_type, image.content_id] + @random_image_pool_cache[key] ||= [] + @random_image_pool_cache[key] << image + end + end + end + + # Initialize basil commissions if there are any content items + if content_ids_by_type.any? + entity_types = [] + entity_ids = [] + + content_ids_by_type.each do |content_type, ids| + entity_type = content_type.downcase.pluralize + entity_types.concat([entity_type] * ids.length) + entity_ids.concat(ids) + end + + @saved_basil_commissions = BasilCommission.where( + entity_type: entity_types, + entity_id: entity_ids + ).where.not(saved_at: nil) + .group_by { |commission| [commission.entity_type, commission.entity_id] } + end + + # Set a default accent color for the page + @accent_color = 'purple' + + # Get usernames for display with content + @users_cache = User.where(id: user_ids).index_by(&:id) + + # Sort content types so Characters always appear first + @tagged_content = @tagged_content.sort_by do |content_group| + if content_group[:type] == 'Character' + # Characters first + "0_#{content_group[:type]}" + else + # Everything else alphabetically + "1_#{content_group[:type]}" + end + end + + @sidenav_expansion = 'community' + end +end \ No newline at end of file diff --git a/app/services/page_tag_service.rb b/app/services/page_tag_service.rb index 531638e2..ec0147a0 100644 --- a/app/services/page_tag_service.rb +++ b/app/services/page_tag_service.rb @@ -6,55 +6,80 @@ class PageTagService < Service def self.suggested_tags_for(class_name) case class_name when Building.name - ["School", "Business"] + ["School", "Business", "Residential", "Religious", "Government", "Military", "Historical", "Abandoned", "Underground", "Magical", "Headquarters", "Landmark", "WIP", "Complete", "Stub", + "Ancient", "Modern", "Futuristic", "Secret", "Well-known", "Mysterious", "Imposing", "Central to plot", "Background detail", "Needs expansion"] when Character.name - ["Main character", "Side character", "Background character"] + ["Main character", "Side character", "Background character", "Villain", "Hero", "Mentor", "Love interest", "Ally", "Rival", "Family member", "Historical figure", "Child", "Elder", "WIP", "Complete", "Stub", + "Protagonist", "Antagonist", "Supporting", "Main plot", "Subplot", "Origin story", "High priority", "Needs development", "Tragic", "Comedic"] when Condition.name - ["Disease", "Blessing", "Curse"] + ["Disease", "Blessing", "Curse", "Magical affliction", "Mental condition", "Physical disability", "Temporary", "Permanent", "Contagious", "Hereditary", "Supernatural", "Rare", "Common", "WIP", "Complete", "Stub", + "Unknown", "Legendary", "Tragic", "Beneficial", "Plot device", "Background detail", "Mysterious", "Needs research", "Science based", "Mythology inspired"] when Country.name - ["Kingdom", "Country", "Region"] + ["Kingdom", "Country", "Region", "Empire", "Republic", "City-state", "Colony", "Island nation", "Confederation", "Disputed territory", "Fallen kingdom", "Hidden realm", "Tribal lands", "WIP", "Complete", "Stub", + "Ancient", "Modern", "Developing", "Major power", "Minor nation", "Well-known", "Isolated", "Political entity", "Cultural focus", "Based on history"] when Creature.name - ["Domesticated", "Wild", "Humanoid"] + ["Domesticated", "Wild", "Humanoid", "Mythical", "Magical", "Predator", "Aquatic", "Flying", "Subterranean", "Extinct", "Intelligent", "Hybrid", "Shapeshifter", "WIP", "Complete", "Stub", + "Common", "Rare", "Legendary", "Keystone species", "Background fauna", "Terrifying", "Majestic", "Cute", "Mythology inspired", "Needs illustration"] when Deity.name - ["Good", "Evil", "Neutral"] + ["Good", "Evil", "Neutral", "Creator", "Destroyer", "Nature deity", "War deity", "Love deity", "Death deity", "Trickster", "Ancient", "Forgotten", "Patron", "WIP", "Complete", "Stub", + "Worshipped", "Obscure", "Legendary", "Major deity", "Minor deity", "Metaphysical", "Mythology inspired", "Central to plot", "Cultural element", "Needs expansion"] when Flora.name - ["Floral", "Weed", "Tree", "Bush"] + ["Floral", "Weed", "Tree", "Bush", "Magical", "Medicinal", "Poisonous", "Edible", "Rare", "Cultivated", "Aquatic", "Carnivorous", "Luminescent", "Extinct", "WIP", "Complete", "Stub", + "Common", "Legendary", "Plot device", "Background detail", "Physical world", "Needs illustration", "Science based", "Mysterious", "Prehistoric", "Modern"] when Government.name - ["Republic", "Democracy", "Monarchy", "Dictatorship"] + ["Republic", "Democracy", "Monarchy", "Dictatorship", "Oligarchy", "Theocracy", "Feudal", "Tribal", "Anarchy", "Colonial", "Magocracy", "Puppet state", "Technocracy", "Confederation", "WIP", "Complete", "Stub", + "Stable", "Unstable", "Corrupt", "Benevolent", "Social structure", "Political entity", "Based on history", "Central to plot", "Background detail", "Needs expansion"] when Group.name - ["Good", "Evil", "Secret"] + ["Good", "Evil", "Secret", "Military", "Religious", "Political", "Criminal", "Mercenary", "Merchant", "Nomadic", "Resistance", "Magical", "Ancient", "WIP", "Complete", "Stub", + "Well-known", "Obscure", "Powerful", "Minor", "Main plot", "Subplot", "Social structure", "Cultural element", "Needs development", "Based on history"] when Item.name - ["Weapon", "Armor", "Artifact", "Relic"] + ["Weapon", "Armor", "Artifact", "Relic", "Magical", "Cursed", "Legendary", "Tool", "Jewelry", "Clothing", "Consumable", "Book", "Technology", "Ritual object", "WIP", "Complete", "Stub", + "Common", "Rare", "Unique", "Plot device", "MacGuffin", "Ancient", "Modern", "Futuristic", "Needs illustration", "Mythology inspired"] when Job.name - ["Military", "Government", "Private sector"] + ["Military", "Government", "Private sector", "Artisan", "Merchant", "Criminal", "Religious", "Magical", "Academic", "Entertainment", "Nomadic", "Seasonal", "Hereditary", "WIP", "Complete", "Stub", + "Common", "Rare", "Prestigious", "Lowly", "Social structure", "Economic system", "Cultural element", "Based on history", "Background detail", "Needs expansion"] when Landmark.name - ["Cave", "Mountain", "Temple", "Ruins"] + ["Cave", "Mountain", "Temple", "Ruins", "Waterfall", "Forest", "Monument", "Battlefield", "Sacred site", "Natural wonder", "Magical location", "Bridge", "Graveyard", "Portal", "WIP", "Complete", "Stub", + "Famous", "Hidden", "Ancient", "Modern", "Physical world", "Plot location", "Mysterious", "Majestic", "Needs illustration", "Based on geography"] when Language.name - ["Modern", "Ancient"] + ["Modern", "Ancient", "Dead", "Sacred", "Trade", "Secret", "Regional", "Magical", "Written only", "Spoken only", "Pidgin", "Artificial", "WIP", "Complete", "Stub", + "Common", "Rare", "Scholarly", "Cultural element", "Based on linguistics", "Needs development", "Conlang", "Background detail", "Low priority", "Needs examples"] when Location.name - ["Town", "City", "River", "Mountains", "Desert"] + ["Town", "City", "River", "Mountains", "Desert", "Forest", "Ocean", "Lake", "Island", "Swamp", "Tundra", "Valley", "Plateau", "Jungle", "Wasteland", "WIP", "Complete", "Stub", + "Inhabited", "Uninhabited", "Explored", "Unexplored", "Dangerous", "Safe", "Physical world", "Plot location", "Needs map", "Based on geography"] when Magic.name - ["Spell", "Ability", "Superpower"] + ["Spell", "Ability", "Superpower", "Ritual", "Enchantment", "Elemental", "Necromancy", "Divination", "Illusion", "Healing", "Summoning", "Forbidden", "Innate", "WIP", "Complete", "Stub", + "Common", "Rare", "Legendary", "Powerful", "Weak", "Metaphysical", "System-defining", "Plot device", "High priority", "Needs rules"] when Planet.name - ["Habitable", "Inhabitable"] + ["Habitable", "Inhabitable", "Earth-like", "Gas giant", "Desert world", "Ocean world", "Ice world", "Jungle world", "Artificial", "Moon", "Dying", "Ancient", "WIP", "Complete", "Stub", + "Explored", "Unexplored", "Homeworld", "Colony", "Physical world", "Science based", "Needs map", "Central setting", "Background detail", "Needs development"] when Race.name - ["Race", "Species", "Humanoid"] + ["Race", "Species", "Humanoid", "Ancient", "Magical", "Hybrid", "Extinct", "Subterranean", "Aquatic", "Aerial", "Nomadic", "Tribal", "Technological", "WIP", "Complete", "Stub", + "Common", "Rare", "Dominant", "Minority", "Cultural focus", "Social structure", "Needs illustration", "Main characters", "Background people", "High priority"] when Religion.name - ["Modern", "Ancient", "Monotheistic", "Polytheistic"] + ["Modern", "Ancient", "Monotheistic", "Polytheistic", "State religion", "Cult", "Animistic", "Shamanic", "Ancestor worship", "Nature worship", "Mystical", "Heretical", "Secretive", "Widespread", "WIP", "Complete", "Stub", + "Dominant", "Minority", "Cultural element", "Metaphysical", "Based on history", "Mythology inspired", "Plot relevant", "Background detail", "Needs expansion"] when Scene.name - ["Atmospheric", "Exposition", "Transition"] + ["Atmospheric", "Exposition", "Transition", "Action", "Dialogue", "Flashback", "Dream sequence", "Climactic", "Introduction", "Resolution", "Worldbuilding", "Character development", "Plot twist", "WIP", "Complete", "Stub", + "Main plot", "Subplot", "High tension", "Low tension", "Mysterious", "Romantic", "Tragic", "Comedic", "High priority", "Needs revision"] when Technology.name - ["Military", "Consumer", "Theoretical"] + ["Military", "Consumer", "Theoretical", "Ancient", "Futuristic", "Magical", "Medical", "Transportation", "Communication", "Energy", "Forbidden", "Experimental", "Everyday", "WIP", "Complete", "Stub", + "Common", "Rare", "Revolutionary", "Obsolete", "Science based", "Plot device", "Physical world", "Needs illustration", "Background detail", "High priority"] when Town.name - ["Village", "Town", "City", "Metropolis"] + ["Village", "Town", "City", "Metropolis", "Hamlet", "Outpost", "Port", "Mining settlement", "Trading hub", "Fortress", "Religious center", "Abandoned", "Hidden", "Capital", "WIP", "Complete", "Stub", + "Prosperous", "Poor", "Ancient", "Modern", "Plot location", "Character hometown", "Cultural focus", "Needs map", "Based on history", "Background detail"] when Tradition.name - ["Holiday", "Practice"] + ["Holiday", "Practice", "Festival", "Ceremony", "Ritual", "Coming of age", "Seasonal", "Religious", "Cultural", "Family", "Military", "Ancient", "WIP", "Complete", "Stub", + "Common", "Rare", "Widespread", "Regional", "Cultural element", "Social structure", "Based on history", "Mythology inspired", "Background detail", "Needs expansion"] when Universe.name - ["Favorite"] + ["Favorite", "Science fiction", "Fantasy", "Historical", "Post-apocalyptic", "Dystopian", "Utopian", "Alternate history", "Urban fantasy", "Steampunk", "Magical realism", "WIP", "Complete", "Stub", + "Main project", "Side project", "Collaborative", "Personal", "High priority", "Low priority", "Needs worldbuilding", "Core concept", "Experimental", "Based on literature"] when Vehicle.name - ["Automobile", "Train", "Plane", "Spaceship"] + ["Automobile", "Train", "Plane", "Spaceship", "Ship", "Submarine", "Magical", "Animal-drawn", "Military", "Civilian", "Ancient", "Futuristic", "Unique", "Experimental", "WIP", "Complete", "Stub", + "Common", "Rare", "Legendary", "Personal", "Public", "Plot device", "Needs illustration", "Science based", "Background detail", "Technology focus"] when Document.name - ['Idea', 'Draft', 'In Progress', 'Done'] + ['Idea', 'Draft', 'In Progress', 'Done', 'Historical', 'Religious', 'Legal', 'Personal', 'Scientific', 'Map', 'Journal', 'Letter', 'Prophecy', 'Secret', 'WIP', 'Complete', 'Stub', + 'Plot relevant', 'Background lore', 'Ancient', 'Modern', 'Cultural element', 'Needs revision', 'High priority', 'Open for collaboration', 'Based on literature', 'Worldbuilding'] else [] end diff --git a/app/views/browse/tag.html.erb b/app/views/browse/tag.html.erb new file mode 100644 index 00000000..bcc31e0f --- /dev/null +++ b/app/views/browse/tag.html.erb @@ -0,0 +1,200 @@ +
+ No one has created any public content with the "<%= @tag %>" tag yet. +
++ <%= sanitize ContentFormatterService.show(text: truncate(content_description, length: 420, escape: false), viewing_user: current_user) %> +
+ <% end %> + + <% if content.respond_to?(:page_tags) && content.page_tags.any? %> + + + <% end %> + + <% user = @users_cache[content.user_id] %> + <% if user %> ++ Creator: + <%= link_to profile_by_username_path(username: user.username) do %> + <%= user.display_name %> + <% end %> +
+ <% end %> + ++ <%= content.created_at == content.updated_at ? 'created' : 'last updated' %> + <%= time_ago_in_words content.updated_at %> + ago +
+