diff options
| author | erdgeist <erdgeist@erdgeist.org> | 2026-07-13 23:33:11 +0200 |
|---|---|---|
| committer | erdgeist <erdgeist@erdgeist.org> | 2026-07-13 23:33:11 +0200 |
| commit | 8eab68f2c5a3c126e2fec2ecdfa7ace87ce0937b (patch) | |
| tree | f446ebc26a7707c7b64a937aa51a155df146c80a /app/helpers | |
| parent | 42714c697273a7117c6b355fab26c8c35e336ad1 (diff) | |
| parent | cdf5d9941ca866d437612d2f863eac6eb0b3db12 (diff) | |
Merge branch 'erdgeist-revive-events'
Diffstat (limited to 'app/helpers')
| -rw-r--r-- | app/helpers/admin_helper.rb | 12 | ||||
| -rw-r--r-- | app/helpers/application_helper.rb | 10 | ||||
| -rw-r--r-- | app/helpers/content_helper.rb | 28 | ||||
| -rw-r--r-- | app/helpers/datetime_helper.rb | 53 | ||||
| -rw-r--r-- | app/helpers/events_helper.rb | 11 | ||||
| -rw-r--r-- | app/helpers/link_helper.rb | 2 | ||||
| -rw-r--r-- | app/helpers/nodes_helper.rb | 60 | ||||
| -rw-r--r-- | app/helpers/revisions_helper.rb | 5 |
8 files changed, 149 insertions, 32 deletions
diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb index 08336ef..afed245 100644 --- a/app/helpers/admin_helper.rb +++ b/app/helpers/admin_helper.rb | |||
| @@ -1,11 +1,7 @@ | |||
| 1 | module AdminHelper | 1 | module AdminHelper |
| 2 | 2 | def mtime_busted_path(path) | |
| 3 | def language_selector | 3 | file = Rails.public_path.join(path.sub(%r{\A/}, "")) |
| 4 | case I18n.locale | 4 | raise "Static asset not found for cache-busting: #{path} (looked for #{file})" unless File.exist?(file) |
| 5 | when :de | 5 | "#{path}?v=#{File.mtime(file).to_i}" |
| 6 | link_to raw('<span class="inactive">English</span>'), url_for(params.permit(:locale, :page_path).to_h.merge('locale' => 'en')) | ||
| 7 | when :en | ||
| 8 | link_to raw('<span class="inactive">English</span>'), url_for(params.permit(:locale, :page_path).to_h.merge('locale' => 'de')) | ||
| 9 | end | ||
| 10 | end | 6 | end |
| 11 | end | 7 | end |
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 72b76b8..87aa82c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb | |||
| @@ -12,13 +12,7 @@ module ApplicationHelper | |||
| 12 | end | 12 | end |
| 13 | end | 13 | end |
| 14 | 14 | ||
| 15 | def safe_return_to(url) | 15 | def resolve_kind_text(value) |
| 16 | return events_path if url.blank? | 16 | value.respond_to?(:call) ? value.call : value |
| 17 | uri = URI.parse(url) | ||
| 18 | return events_path if uri.host.present? | ||
| 19 | return events_path unless url.start_with?('/') | ||
| 20 | url | ||
| 21 | rescue URI::InvalidURIError | ||
| 22 | events_path | ||
| 23 | end | 17 | end |
| 24 | end | 18 | end |
diff --git a/app/helpers/content_helper.rb b/app/helpers/content_helper.rb index 21cc579..5810966 100644 --- a/app/helpers/content_helper.rb +++ b/app/helpers/content_helper.rb | |||
| @@ -23,6 +23,26 @@ module ContentHelper | |||
| 23 | ) | 23 | ) |
| 24 | end | 24 | end |
| 25 | 25 | ||
| 26 | def open_erfas_today | ||
| 27 | occurrences = Occurrence | ||
| 28 | .find_in_range(Time.now.beginning_of_day, Time.now.end_of_day) | ||
| 29 | .joins(event: :tags) | ||
| 30 | .where(tags: { name: 'open-day' }) | ||
| 31 | .reject { |o| o.node.nil? || o.node.head.nil? } | ||
| 32 | .sample(3) | ||
| 33 | |||
| 34 | return if occurrences.empty? | ||
| 35 | |||
| 36 | render( | ||
| 37 | :partial => 'content/open_erfas_today', | ||
| 38 | :locals => { :occurrences => occurrences } | ||
| 39 | ) | ||
| 40 | end | ||
| 41 | |||
| 42 | def weekday_abbr(time) | ||
| 43 | RruleHumanizer.wday_abbr(time, I18n.locale) | ||
| 44 | end | ||
| 45 | |||
| 26 | def tags | 46 | def tags |
| 27 | render :partial => 'content/tags' | 47 | render :partial => 'content/tags' |
| 28 | end | 48 | end |
| @@ -67,11 +87,14 @@ module ContentHelper | |||
| 67 | # Syntax of the [aggregate ] short code: | 87 | # Syntax of the [aggregate ] short code: |
| 68 | # | 88 | # |
| 69 | # [aggregate | 89 | # [aggregate |
| 70 | # flags="update, pressemitteilung" | 90 | # children="all" | children="direct" # optional, at least one of children |
| 91 | # tags="update, pressemitteilung" # or tags is required | ||
| 71 | # limit="20" | 92 | # limit="20" |
| 72 | # order_by="published_at" | 93 | # order_by="published_at" |
| 73 | # order_direction="DESC" | 94 | # order_direction="DESC" |
| 74 | # ] | 95 | # ] |
| 96 | |||
| 97 | |||
| 75 | def aggregate? content | 98 | def aggregate? content |
| 76 | options = {} | 99 | options = {} |
| 77 | 100 | ||
| @@ -80,7 +103,7 @@ module ContentHelper | |||
| 80 | begin | 103 | begin |
| 81 | if content =~ /\[aggregate([^\]]*)\]/ | 104 | if content =~ /\[aggregate([^\]]*)\]/ |
| 82 | tag = $~.to_s | 105 | tag = $~.to_s |
| 83 | matched_data = $1.scan(/\w+\="[a-zA-Z\s\/_\d,.=]*"/) | 106 | matched_data = $1.scan(/\w+\="[a-zA-Z\s\/_\d,.=-]*"/) |
| 84 | 107 | ||
| 85 | matched_data.each do |data| | 108 | matched_data.each do |data| |
| 86 | splitted_data = data.split("=", 2) | 109 | splitted_data = data.split("=", 2) |
| @@ -88,6 +111,7 @@ module ContentHelper | |||
| 88 | end | 111 | end |
| 89 | 112 | ||
| 90 | options[:partial] = select_partial(options[:partial]) | 113 | options[:partial] = select_partial(options[:partial]) |
| 114 | options[:node] = @page.node if options[:children].present? | ||
| 91 | 115 | ||
| 92 | sanitize(content.sub(tag, render_collection(options)), :attributes => cccms_attributes) | 116 | sanitize(content.sub(tag, render_collection(options)), :attributes => cccms_attributes) |
| 93 | else | 117 | else |
diff --git a/app/helpers/datetime_helper.rb b/app/helpers/datetime_helper.rb new file mode 100644 index 0000000..8497b1c --- /dev/null +++ b/app/helpers/datetime_helper.rb | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | module DatetimeHelper | ||
| 2 | |||
| 3 | def relative_distance(time, now = Time.current) | ||
| 4 | seconds = (now - time).abs | ||
| 5 | |||
| 6 | case seconds | ||
| 7 | when 0...90 | ||
| 8 | [1, :minute] | ||
| 9 | when 90...45.minutes | ||
| 10 | [(seconds / 60).round, :minute] | ||
| 11 | when 45.minutes...24.hours | ||
| 12 | [(seconds / 1.hour).round, :hour] | ||
| 13 | when 24.hours...30.days | ||
| 14 | [(seconds / 1.day).round, :day] | ||
| 15 | when 30.days...365.days | ||
| 16 | [(seconds / 30.days).round, :month] | ||
| 17 | else | ||
| 18 | [(seconds / 365.days).round, :year] | ||
| 19 | end | ||
| 20 | end | ||
| 21 | |||
| 22 | RELATIVE_TIME_UNIT_FORMS = { | ||
| 23 | en: { | ||
| 24 | minute: { one: "minute", other: "minutes" }, | ||
| 25 | hour: { one: "hour", other: "hours" }, | ||
| 26 | day: { one: "day", other: "days" }, | ||
| 27 | month: { one: "month", other: "months" }, | ||
| 28 | year: { one: "year", other: "years" }, | ||
| 29 | }, | ||
| 30 | de: { | ||
| 31 | # "other" here is the DATIVE plural needed after "vor" -- not the | ||
| 32 | # nominative plural you'd use standing alone ("3 Tage" vs "vor 3 | ||
| 33 | # Tagen"). All five happen to be the nominative plural + "n", since | ||
| 34 | # none of them already end in -n or -s -- a property of these five | ||
| 35 | # specific words, not a rule to extend to new units without checking. | ||
| 36 | minute: { one: "Minute", other: "Minuten" }, | ||
| 37 | hour: { one: "Stunde", other: "Stunden" }, | ||
| 38 | day: { one: "Tag", other: "Tagen" }, | ||
| 39 | month: { one: "Monat", other: "Monaten" }, | ||
| 40 | year: { one: "Jahr", other: "Jahren" }, | ||
| 41 | }, | ||
| 42 | }.freeze | ||
| 43 | |||
| 44 | def relative_time_phrase(time, now = Time.current) | ||
| 45 | count, unit = relative_distance(time, now) | ||
| 46 | locale = I18n.locale.to_sym | ||
| 47 | forms = RELATIVE_TIME_UNIT_FORMS.fetch(locale, RELATIVE_TIME_UNIT_FORMS[:en]) | ||
| 48 | word = forms.fetch(unit).fetch(count == 1 ? :one : :other) | ||
| 49 | |||
| 50 | locale == :de ? "vor #{count} #{word}" : "#{count} #{word} ago" | ||
| 51 | end | ||
| 52 | |||
| 53 | end | ||
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 8a9a878..5e84f53 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb | |||
| @@ -1,2 +1,13 @@ | |||
| 1 | require 'cgi' | ||
| 2 | |||
| 1 | module EventsHelper | 3 | module EventsHelper |
| 4 | # Insert a zero-width break opportunity after each semicolon, so a long | ||
| 5 | # RRULE can wrap at a clause boundary instead of overflowing its column. | ||
| 6 | # Deliberately <wbr>, not ellipsis -- unlike a URL, an RRULE's trailing | ||
| 7 | # characters (BYDAY, BYMONTH, etc.) are usually the most specific part, | ||
| 8 | # and truncating them would hide exactly the wrong end of the string. | ||
| 9 | def rrule_with_break_opportunities(rrule) | ||
| 10 | return "" if rrule.blank? | ||
| 11 | raw(rrule.split(';', -1).map { |part| CGI.escapeHTML(part) }.join(';<wbr>')) | ||
| 12 | end | ||
| 2 | end | 13 | end |
diff --git a/app/helpers/link_helper.rb b/app/helpers/link_helper.rb index 878e8e4..9b0b4e1 100644 --- a/app/helpers/link_helper.rb +++ b/app/helpers/link_helper.rb | |||
| @@ -44,7 +44,7 @@ module LinkHelper | |||
| 44 | "Last modified #{@page.updated_at.to_fs(:db)}" | 44 | "Last modified #{@page.updated_at.to_fs(:db)}" |
| 45 | button_to 'Unlock', unlock_node_path(@node), | 45 | button_to 'Unlock', unlock_node_path(@node), |
| 46 | method: :put, | 46 | method: :put, |
| 47 | form: { data: { confirm: message } } | 47 | form: { data: { confirm: message }, class: 'button_to state_changing' } |
| 48 | end | 48 | end |
| 49 | 49 | ||
| 50 | def content_path(page_path = nil, options = {}) | 50 | def content_path(page_path = nil, options = {}) |
diff --git a/app/helpers/nodes_helper.rb b/app/helpers/nodes_helper.rb index a054a2e..5884c8c 100644 --- a/app/helpers/nodes_helper.rb +++ b/app/helpers/nodes_helper.rb | |||
| @@ -12,7 +12,6 @@ module NodesHelper | |||
| 12 | end | 12 | end |
| 13 | end | 13 | end |
| 14 | 14 | ||
| 15 | |||
| 16 | def truncated_title_for_node node | 15 | def truncated_title_for_node node |
| 17 | if (title = title_for_node node) && title.size > 20 | 16 | if (title = title_for_node node) && title.size > 20 |
| 18 | "<span title='#{title}'>#{truncate(title, 40)}</span>" | 17 | "<span title='#{title}'>#{truncate(title, 40)}</span>" |
| @@ -29,19 +28,54 @@ module NodesHelper | |||
| 29 | User.all.map {|u| [u.login, u.id]} | 28 | User.all.map {|u| [u.login, u.id]} |
| 30 | end | 29 | end |
| 31 | 30 | ||
| 32 | def event_information | 31 | def node_last_editor(node) |
| 33 | if @node.event | 32 | editor = node.draft&.editor || node.head&.editor |
| 34 | safe_join([ | 33 | return nil unless editor |
| 35 | "#{@node.event.start_time.to_fs(:db)} - #{@node.event.end_time.to_fs(:db)} > ", | 34 | editor == current_user ? t("editor_self") : editor.login |
| 36 | link_to('show', event_path(@node.event)), | 35 | end |
| 37 | ' ', | 36 | |
| 38 | link_to('edit', edit_event_path(@node.event)) | 37 | def node_head_editor(node) |
| 39 | ]) | 38 | editor = node.head&.editor |
| 39 | return nil unless editor | ||
| 40 | editor == current_user ? t("publisher_self") : editor.login | ||
| 41 | end | ||
| 42 | |||
| 43 | DEFAULT_EVENT_TAG_BY_PAGE_TAG = { | ||
| 44 | 'erfa-detail' => 'open-day', | ||
| 45 | 'chaostreff-detail' => 'open-day' | ||
| 46 | }.freeze | ||
| 47 | |||
| 48 | def default_event_tag_mapping(page) | ||
| 49 | page_tags = page.tag_list | ||
| 50 | DEFAULT_EVENT_TAG_BY_PAGE_TAG.find { |page_tag, _| page_tags.include?(page_tag) } | ||
| 51 | end | ||
| 52 | |||
| 53 | def default_event_tag_list(page) | ||
| 54 | default_event_tag_mapping(page)&.last | ||
| 55 | end | ||
| 56 | |||
| 57 | def event_schedule_text(event) | ||
| 58 | if event.rrule.present? | ||
| 59 | recurrence = event.humanize_rrule(I18n.locale) | ||
| 60 | if recurrence | ||
| 61 | time = event.start_time&.strftime("%H:%M") | ||
| 62 | time ? "#{recurrence} #{t(:event_schedule_time, time: time)}" : recurrence | ||
| 63 | else | ||
| 64 | "#{event.rrule} (#{t(:event_schedule_unrecognized)})" | ||
| 65 | end | ||
| 66 | elsif event.start_time | ||
| 67 | I18n.l(event.start_time, format: :long) | ||
| 40 | else | 68 | else |
| 41 | safe_join([ | 69 | t(:event_schedule_none) |
| 42 | 'no event attached > ', | ||
| 43 | link_to('add', new_event_path(:node_id => @node.id)) | ||
| 44 | ]) | ||
| 45 | end | 70 | end |
| 46 | end | 71 | end |
| 72 | |||
| 73 | def matching_node_kinds(node) | ||
| 74 | path = node.unique_path | ||
| 75 | CccConventions::NODE_KINDS.select { |_, config| config[:parent_match]&.call(path) } | ||
| 76 | end | ||
| 77 | |||
| 78 | def sitemap_node_open?(node) | ||
| 79 | !CccConventions::SITEMAP_COLLAPSED_PATHS.include?(node.unique_name) | ||
| 80 | end | ||
| 47 | end | 81 | end |
diff --git a/app/helpers/revisions_helper.rb b/app/helpers/revisions_helper.rb index fdb51f8..a629013 100644 --- a/app/helpers/revisions_helper.rb +++ b/app/helpers/revisions_helper.rb | |||
| @@ -1,2 +1,7 @@ | |||
| 1 | module RevisionsHelper | 1 | module RevisionsHelper |
| 2 | # Human-readable label for a diff endpoint -- "head"/"draft"/"autosave" | ||
| 3 | # get their name; anything else is a revision number. | ||
| 4 | def describe_page_reference(ref) | ||
| 5 | %w[head draft autosave].include?(ref.to_s) ? ref.to_s.capitalize : "revision #{ref}" | ||
| 6 | end | ||
| 2 | end | 7 | end |
