summaryrefslogtreecommitdiff
path: root/app/helpers
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-13 23:33:11 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-13 23:33:11 +0200
commit8eab68f2c5a3c126e2fec2ecdfa7ace87ce0937b (patch)
treef446ebc26a7707c7b64a937aa51a155df146c80a /app/helpers
parent42714c697273a7117c6b355fab26c8c35e336ad1 (diff)
parentcdf5d9941ca866d437612d2f863eac6eb0b3db12 (diff)
Merge branch 'erdgeist-revive-events'
Diffstat (limited to 'app/helpers')
-rw-r--r--app/helpers/admin_helper.rb12
-rw-r--r--app/helpers/application_helper.rb10
-rw-r--r--app/helpers/content_helper.rb28
-rw-r--r--app/helpers/datetime_helper.rb53
-rw-r--r--app/helpers/events_helper.rb11
-rw-r--r--app/helpers/link_helper.rb2
-rw-r--r--app/helpers/nodes_helper.rb60
-rw-r--r--app/helpers/revisions_helper.rb5
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 @@
1module AdminHelper 1module 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
11end 7end
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
24end 18end
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 @@
1module 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
53end
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 @@
1require 'cgi'
2
1module EventsHelper 3module 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
2end 13end
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
47end 81end
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 @@
1module RevisionsHelper 1module 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
2end 7end