module WorksHelper # Average reading speed in words per minute READING_WPM = 265 def estimated_reading_time(word_count) return "" if word_count.blank? || word_count <= 0 minutes = (word_count.to_f / READING_WPM).round if minutes.zero? "< 1 min" elsif minutes < 60 "#{minutes} min" else hours = minutes / 60 remaining = minutes % 60 if remaining.zero? "#{hours} hr" elsif remaining == 1 "#{hours} hr 1 min" else "#{hours} hr #{remaining} min" end end end # Optional: nicer inline version with title attribute for exact value def reading_time_with_title(word_count) time_str = estimated_reading_time(word_count) return "" if time_str.blank? exact_min = (word_count.to_f / READING_WPM).round(1) title_text = "#{exact_min} minutes @ #{READING_WPM} wpm" content_tag(:span, time_str, title: title_text, class: "reading-time") end end # List of date, chapter and length info for the work show page def work_meta_list(work, chapter = nil) # if we're previewing, grab the unsaved date, else take the saved first chapter date published_date = (chapter && work.preview_mode) ? chapter.published_at : work.first_chapter.published_at list = [[ts("Published:"), "published", localize(published_date)], [ts("Words:"), "words", number_with_delimiter(work.word_count)], [ts("Chapters:"), "chapters", chapter_total_display(work)]] if (comment_count = work.count_visible_comments) > 0 list.concat([[ts("Comments:"), "comments", number_with_delimiter(work.count_visible_comments)]]) end if work.all_kudos_count > 0 list.concat([[ts("Applause:"), "kudos", number_with_delimiter(work.all_kudos_count)]]) end if (bookmark_count = work.public_bookmarks_count) > 0 list.concat([[ts("Bookmarks:"), "bookmarks", link_to(number_with_delimiter(bookmark_count), work_bookmarks_path(work))]]) end list.concat([[ts("Plays:"), "hits", number_with_delimiter(work.hits)]]) if work.chaptered? && work.revised_at prefix = work.is_wip ? ts('Updated:') : ts('Completed:') latest_date = (work.preview_mode && work.backdate) ? published_date : date_in_user_time_zone(work.revised_at).to_date list.insert(1, [prefix, 'status', localize(latest_date)]) end list = list.map { |list_item| content_tag(:dt, list_item.first, class: list_item.second) + content_tag(:dd, list_item.last.to_s, class: list_item.second) }.join.html_safe content_tag(:dl, list.to_s, class: 'stats').html_safe end def recipients_link(work) # join doesn't maintain html_safe, so mark the join safe work.gifts.not_rejected.includes(:pseud).map { |gift| link_to(h(gift.recipient), gift.pseud ? user_gifts_path(gift.pseud.user) : gifts_path(recipient: gift.recipient_name)) }.join(", ").html_safe end # select default rating if this is a new work def rating_selected(work) work.nil? || work.rating_string.empty? ? ArchiveConfig.RATING_DEFAULT_TAG_NAME : work.rating_string end # Determines whether or not to expand the related work association fields when the work form loads def check_parent_box(work) work.parents_after_saving.present? end # Determines whether or not "manage series" dropdown should appear def check_series_box(work) work.series.present? || work_series_value(:id).present? || work_series_value(:title).present? end # Passes value of fields for work series back to form when an error occurs on posting def work_series_value(field) params.dig :work, :series_attributes, field end def language_link(work) if work.respond_to?(:language) && work.language link_to work.language.name, work.language, lang: work.language.short else "N/A" end end # Check whether this non-admin user has permission to view the unrevealed work def can_access_unrevealed_work(work, user) # Creators and invited can see their works return true if work.user_is_owner_or_invited?(user) # Moderators can see unrevealed works: work.collections.each do |collection| return true if collection.user_is_maintainer?(user) end false end def marked_for_later?(work) return unless current_user reading = Reading.find_by(work_id: work.id, user_id: current_user.id) reading && reading.toread? end def mark_as_read_link(work) link_to ts("Mark as Read"), mark_as_read_work_path(work) end def mark_for_later_link(work) link_to ts("Mark for Later"), mark_for_later_work_path(work) end def get_endnotes_link(work) return "#work_endnotes" unless current_page?({ controller: "chapters", action: "show" }) if work.posted? && work.last_posted_chapter chapter_path(work.last_posted_chapter.id, anchor: "work_endnotes") else chapter_path(work.last_chapter.id, anchor: "work_endnotes") end end def get_related_works_url current_page?({ controller: "chapters", action: "show" }) ? chapter_path(@work.last_posted_chapter.id, anchor: 'children') : "#children" end def get_inspired_by(work) work.approved_related_works.where(translation: false) end def related_work_note(related_work, relation, download: false) work_link = link_to related_work.title, polymorphic_url(related_work) language = tag.span(related_work.language.name, lang: related_work.language.short) if related_work.language default_locale = download ? :en : nil creator_link = if download byline(related_work, visibility: "public", only_path: false) else byline(related_work) end if related_work.respond_to?(:unrevealed?) && related_work.unrevealed? if relation == "translated_to" t(".#{relation}.unrevealed_html", language: language) else t(".#{relation}.unrevealed", locale: default_locale) end elsif related_work.restricted? && (download || !logged_in?) t(".#{relation}.restricted_html", language: language, locale: default_locale, creator_link: creator_link) else t(".#{relation}.revealed_html", language: language, locale: default_locale, work_link: work_link, creator_link: creator_link) end end # Can the work be downloaded, i.e. is it posted and visible to all registered # users. def downloadable? @work.posted? && !@work.hidden_by_admin && !@work.in_unrevealed_collection? end def download_url_for_work(work, format) path = Download.new(work, format: format).public_path url_for("#{path}?updated_at=#{work.updated_at.to_i}").gsub(' ', '%20') end # Generates a list of a work's tags and details for use in feeds def feed_summary(work) tags = work.tags.group_by(&:type) text = "
by #{byline(work, { visibility: 'public', full_path: true })}
" text << work.summary if work.summary text << "Words: #{work.word_count}, Chapters: #{chapter_total_display(work)}, Language: #{work.language ? work.language.name : 'English'}
" unless work.series.count == 0 text << "Series: #{series_list_for_feeds(work)}
" end # Create list of tags text << "