mirror of
https://github.com/indentlabs/notebook.git
synced 2025-10-26 11:19:22 +00:00
- Initialize eventTags object from server-rendered DOM data on page load - Add fallback in getEventTags() to scrape tags from DOM when cache is undefined - Optimize updateMainPanelTags with DOM query caching and content change detection - Remove redundant tag update calls during filtering to improve performance - Add proper cache invalidation when tags are added/removed - Only update highlight styling when actually needed during filter mode changes This resolves the issue where tags would disappear when filtering timeline events by ensuring the JavaScript tag data is always synchronized with the server-rendered content. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
211 lines
6.7 KiB
Ruby
211 lines
6.7 KiB
Ruby
class TimelinesController < ApplicationController
|
|
include ApplicationHelper
|
|
|
|
before_action :authenticate_user!, except: [:show]
|
|
before_action :set_timeline, only: [:show, :edit, :update, :destroy]
|
|
|
|
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
|
|
|
|
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
|