class TimelinesController < ApplicationController include ApplicationHelper before_action :authenticate_user!, except: [:show] before_action :set_timeline, only: [:show, :edit, :update, :destroy, :toggle_archive] before_action :set_navbar_color before_action :set_sidenav_expansion before_action :require_premium_plan, only: [:new, :create] before_action :require_timeline_read_permission, only: [:show] before_action :require_timeline_edit_permission, only: [:edit, :update] # GET /timelines def index cache_linkable_content_for_each_content_type # TODO: We SHOULD be just doing the below, but since it returns ContentPage stand-ins instead # of actual Timeline models, it's a bit wonky to get all the Timeline-specific logic in place # without reworking most of the views. For now, we're just grabbing timelines and contributable # timelines manually. # @timelines = @linkables_raw.fetch('Timeline', []) @timelines = current_user.timelines @page_title = "My timelines" if @universe_scope @timelines = Timeline.where(universe: @universe_scope) else # Add in all timelines from shared universes also @timelines += Timeline.where(universe_id: current_user.contributable_universe_ids) .where.not(id: @timelines.pluck(:id)) end @filtered_page_tags = [] @page_tags = PageTag.where( page_type: Timeline.name, page_id: @timelines.pluck(:id) ).order(:tag) if params.key?(:tag) @filtered_page_tags = @page_tags.where(slug: params[:tag]) @timelines = @timelines.select { |timeline| @filtered_page_tags.pluck(:page_id).include?(timeline.id) } end # if params.key?(:favorite_only) # @timelines.select!(&:favorite?) # end # New style, using content#index view (we can wipe unused stuff from above once this is finalized) @content_type_class = Timeline @content_type_name = @content_type_class.name @content = @timelines @total_content_count = @timelines.count # Set up pagination variables (no actual pagination for now, but template expects them) @current_page = 1 @total_pages = 1 @folders = [] render 'content/index' end def show @page_title = @timeline.name end # GET /timelines/new def new timeline = current_user.timelines.create(name: 'Untitled Timeline', universe: @universe_scope).reload redirect_to edit_timeline_path(timeline) end # GET /timelines/1/edit def edit @page_title = "Editing #{@timeline.name}" @suggested_page_tags = [] cache_linkable_content_for_each_content_type # Collect all unique tags from timeline events for filtering @timeline_event_tags = collect_timeline_event_tags # Get content already linked to other events in this timeline @timeline_linked_content = {} @timeline_content_summary = {} timeline_entity_ids = @timeline.timeline_events .joins(:timeline_event_entities) .pluck('timeline_event_entities.entity_type', 'timeline_event_entities.entity_id') .uniq timeline_entity_ids.group_by(&:first).each do |entity_type, entity_pairs| entity_ids = entity_pairs.map(&:last).uniq content_class = content_class_from_name(entity_type) next unless content_class # Original data for link modal @timeline_linked_content[entity_type] = content_class.where(id: entity_ids) .where(user: current_user) .limit(20) # Enhanced data for content summary sidebar all_content = content_class.where(id: entity_ids) .where(user: current_user) .order(:name) @timeline_content_summary[entity_type] = { count: all_content.count, items: all_content.limit(10), # Show first 10, with expansion option total_items: all_content.count, content_class: content_class } end end # POST /timelines def create @page_title = "Create a timeline" # TODO this endpoint is probably just API-only, right? @timeline = Timeline.new(timeline_params) if current_user.on_premium_plan? && @timeline.save redirect_to @timeline, notice: 'Timeline was successfully created.' else render :new end end # PATCH/PUT /timelines/1 def update if @timeline.update(timeline_params) update_page_tags render status: 200, json: @timeline.reload else render status: 501, json: @timeline.errors end end # DELETE /timelines/1 def destroy @timeline.destroy redirect_to timelines_url, notice: 'Timeline was successfully destroyed.' end # GET /timelines/1/toggle_archive def toggle_archive unless @timeline.updatable_by?(current_user) flash[:notice] = "You don't have permission to edit that!" return redirect_back fallback_location: @timeline end verb = nil success = if @timeline.archived? verb = "unarchived" @timeline.unarchive! else verb = "archived" @timeline.archive! end if success flash[:notice] = "Timeline #{verb} successfully!" else flash[:alert] = "Failed to #{verb.sub('ed', '')} timeline." end redirect_back fallback_location: edit_timeline_path(@timeline) end # GET /timelines/1/tag_suggestions def tag_suggestions # Get all unique tags from all of the current user's timelines user_timeline_tags = PageTag.where( user: current_user, page_type: 'Timeline' ).distinct.pluck(:tag).sort render json: { suggestions: user_timeline_tags } end private def require_timeline_read_permission return user_signed_in? && @timeline.readable_by?(current_user) end def require_timeline_edit_permission return user_signed_in? && @timeline.updatable_by?(current_user) end # TODO: move this (and the copy in ContentController) into the has_page_tags concern? def update_page_tags tag_list = page_tag_params.split(PageTag::SUBMISSION_DELIMITER) current_tags = @timeline.page_tags.pluck(:tag) tags_to_add = tag_list - current_tags tags_to_remove = current_tags - tag_list tags_to_add.each do |tag| @timeline.page_tags.find_or_create_by( tag: tag, slug: PageTagService.slug_for(tag), user: @timeline.user ) end tags_to_remove.each do |tag| @timeline.page_tags.find_by(tag: tag).destroy end end # Use callbacks to share common setup or constraints between actions. def set_timeline @timeline = Timeline.find(params[:id]) end # Only allow a trusted parameter "white list" through. def timeline_params params.require(:timeline).except(:page_tags).permit(:name, :subtitle, :description, :notes, :private_notes, :universe_id, :deleted_at, :archived_at, :privacy) end def page_tag_params params.require(:timeline).fetch(:page_tags, "") end def set_navbar_color @navbar_color = Timeline.hex_color.presence || '#2196F3' end def set_sidenav_expansion @sidenav_expansion = 'writing' end def collect_timeline_event_tags # Get all unique tags from timeline events with their usage counts tag_counts = @timeline.timeline_events .joins(:page_tags) .group('page_tags.tag') .count # Return array of hashes with tag name and count for easy access in view tag_counts.map { |tag, count| { name: tag, count: count } } .sort_by { |tag_data| tag_data[:name].downcase } end end