Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion app/controllers/conferences_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ def show

@splashpage = @conference.splashpage

redirect_to admin_conference_splashpage_path(@conference.short_title) && return unless @splashpage.present?
# NOTE: do not write `redirect_to(...) && return unless ...` — Ruby parses that as
# `redirect_to((...) && return) unless ...`, which evaluates `return` before `redirect_to`
# when the splashpage is missing, skipping the redirect and rendering show with a nil splashpage.
unless @splashpage.present?
redirect_to admin_conference_splashpage_path(@conference.short_title)
return
end

# User messages at the top of the page.
@unpaid_tickets = current_user_has_unpaid_tickets?
Expand Down
1 change: 1 addition & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# TODO-SNAPCON: Refactor this module. Move chunks to a dates_help, some events_helper
module ApplicationHelper
include Pagy::Frontend
include ConferenceTemplateHelper
# Returns a string build from the start and end date of the given conference.
#
# If the conference is only one day long
Expand Down
8 changes: 8 additions & 0 deletions app/helpers/conference_template_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

module ConferenceTemplateHelper
def conference_template_markdown(text, conference, escape_html: false)
interpolated = ConferenceMarkdownTemplate.interpolate(text, conference)
markdown(interpolated, escape_html)
end
end
112 changes: 112 additions & 0 deletions app/services/conference_markdown_template.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# frozen_string_literal: true

# Interpolates `{token}` placeholders in conference-facing Markdown fields.
#
# This intentionally mirrors the `{key}` substitution style used by
# `EmailTemplateParser`, but only exposes conference-scoped values (no user).
class ConferenceMarkdownTemplate
KNOWN_TEMPLATE_KEYS = %w[
conference conference_short start_date end_date timezone
cfp_start_date cfp_end_date
venue venue_address
registration_start_date registration_end_date
num_public_event_types
].freeze

def self.interpolate(text, conference)
return '' if text.blank?
return text if conference.blank?

values = build_values(conference)
substituted = EmailTemplateParser.parse_template(text, values)

# Aliases / friendlier names for issue descriptions
alias_map = {
'conference_name' => 'conference',
'short_name' => 'conference_short',
'cfp_deadline' => 'cfp_end_date'
}

alias_map.each do |from, to|
raw = values[to]
replacement =
if raw.is_a?(Date)
raw.strftime('%Y-%m-%d')
else
raw.presence || ''
end

substituted = substituted.gsub("{#{from}}", replacement)
end

# `EmailTemplateParser.parse_template` skips blank values, which leaves tokens
# like `{cfp_end_date}` in place when optional sections are empty. For
# conference-facing Markdown we prefer removing those placeholders entirely.
substituted = substitute_known_keys(substituted, values)

substituted
end

def self.substitute_known_keys(text, values)
return text if text.blank?

KNOWN_TEMPLATE_KEYS.each do |key|
next unless text.include?("{#{key}}")

raw = values[key]
replacement =
if raw.is_a?(Date)
raw.strftime('%Y-%m-%d')
else
raw.presence || ''
end

text = text.gsub("{#{key}}", replacement)
end

text
end

def self.build_values(conference)
h = {
'conference' => conference.title,
'conference_short' => conference.short_title,
'start_date' => conference.start_date,
'end_date' => conference.end_date,
'timezone' => conference.timezone
}

events_cfp = conference.program&.cfps&.find_by(cfp_type: 'events')
if events_cfp
h['cfp_start_date'] = events_cfp.start_date
h['cfp_end_date'] = events_cfp.end_date
else
h['cfp_start_date'] = ''
h['cfp_end_date'] = ''
end

if conference.venue
h['venue'] = conference.venue.name
h['venue_address'] = conference.venue.address
else
h['venue'] = ''
h['venue_address'] = ''
end

if conference.registration_period
h['registration_start_date'] = conference.registration_period.start_date
h['registration_end_date'] = conference.registration_period.end_date
else
h['registration_start_date'] = ''
h['registration_end_date'] = ''
end

if conference.program
h['num_public_event_types'] = conference.program.event_types.available_for_public.count.to_s
else
h['num_public_event_types'] = '0'
end

h
end
end
56 changes: 56 additions & 0 deletions app/views/admin/conferences/_form_fields.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,66 @@
= f.label :description
= f.text_area :description, rows: 5, data: { provide: 'markdown' }, class: 'form-control'
.help-block= markdown_hint('Splash page content')
%a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'conference_description_template_help' } Show available template variables
.template-help{ id: 'conference_description_template_help' }
%p
You can embed placeholders like
%code {conference}
which will be replaced on the public splash page using the current conference data.
%ul
%li
%code {conference}
(alias:
%code {conference_name}
) — conference title
%li
%code {conference_short}
(alias:
%code {short_name}
) — conference short title
%li
%code {start_date}
/
%code {end_date}
— conference dates (formatted as YYYY-MM-DD)
%li
%code {cfp_start_date}
/
%code {cfp_end_date}
— CFP for events dates (if configured)
%li
%code {cfp_deadline}
— alias for
%code {cfp_end_date}
%li
%code {venue}
— venue name (if configured)
%li
%code {venue_address}
— venue address (if configured)
%li
%code {registration_start_date}
/
%code {registration_end_date}
— registration period dates (if configured)
%li
%code {num_public_event_types}
— count of event types that allow public submission
%li
%code {timezone}
— conference timezone string
.form-group
= f.label :registered_attendees_message, 'Message for Registered Attendees'
= f.text_area :registered_attendees_message, rows: 5, data: { provide: 'markdown' }, class: 'form-control'
.help-block= markdown_hint('Splash page content')
%a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'conference_registered_attendees_template_help' } Show available template variables
.template-help{ id: 'conference_registered_attendees_template_help' }
%p
Same placeholders as the conference description (for example
%code {conference}
and
%code {start_date}
).
.form-group
= f.color_field :color, size: 6, class: 'form-control'
%span.help-block
Expand Down
4 changes: 2 additions & 2 deletions app/views/conferences/_about_and_happening_now.haml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
= content_for :about do
#about
-if @user_registered && conference.registered_attendees_message.present?
= markdown(conference.registered_attendees_message, false)
= markdown(conference.description, false)
= conference_template_markdown(conference.registered_attendees_message, conference, escape_html: false)
= conference_template_markdown(conference.description, conference, escape_html: false)

%section#about-and-happening-now
.container
Expand Down
8 changes: 4 additions & 4 deletions app/views/conferences/_header.haml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
- cache [conference, venue, '#splash#header'] do
#banner{ style: ("background-image: url(#{splashpage.banner_photo_url})" if splashpage.banner_photo_url) }
- cache [conference, venue, splashpage, '#splash#header'] do
#banner{ style: ("background-image: url(#{splashpage.banner_photo_url})" if splashpage&.banner_photo_url) }
.container
.row
- picture_present = splashpage.banner_photo? || conference.picture?
- picture_present = splashpage&.banner_photo? || conference.picture?
.col-md-6.col-md-offset-3{ id: (picture_present ? "header-image" : "header-no-image") }
.row
- if conference.picture? && !splashpage.banner_photo?
- if conference.picture? && !splashpage&.banner_photo?
.col-md-4
= image_tag(conference.picture_url,
class: 'img-responsive img-center',
Expand Down
11 changes: 11 additions & 0 deletions spec/controllers/conferences_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@
end
end

context 'when the conference has no splashpage' do
let(:conference_without_splash) { create(:conference) }
let!(:organizer) { create(:organizer, resource: conference_without_splash) }

it 'redirects organizers to admin splashpage setup (regression: redirect must not be skipped by Ruby parsing)' do
sign_in organizer
get :show, params: { id: conference_without_splash.short_title }
expect(response).to redirect_to(admin_conference_splashpage_path(conference_without_splash.short_title))
end
end

context 'accessing conference via custom domain' do
before do
conference.update_attribute(:custom_domain, 'lvh.me')
Expand Down
61 changes: 61 additions & 0 deletions spec/services/conference_markdown_template_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe ConferenceMarkdownTemplate do
describe '.interpolate' do
it 'returns empty string for blank text' do
conference = build(:conference)
expect(described_class.interpolate('', conference)).to eq('')
expect(described_class.interpolate(nil, conference)).to eq('')
end

it 'returns original text when conference is blank' do
expect(described_class.interpolate('{conference}', nil)).to eq('{conference}')
end

it 'substitutes conference values, CFP (events), venue, registration, and public event type count' do
conference = create(:full_conference)
conference.program.event_types.first.update!(enable_public_submission: false)
create(:event_type, program: conference.program, title: 'Poster', enable_public_submission: true)

events_cfp = conference.program.cfps.find_by!(cfp_type: 'events')
template = <<~TEXT.squish
{conference} / {conference_short}
{conference_name} / {short_name}
{start_date}–{end_date} {timezone}
CFP {cfp_start_date}–{cfp_end_date} deadline {cfp_deadline}
{venue} — {venue_address}
Reg {registration_start_date}–{registration_end_date}
Public types: {num_public_event_types}
TEXT

result = described_class.interpolate(template, conference)

expect(result).to include(conference.title)
expect(result).to include(conference.short_title)
expect(result).to include("#{conference.start_date.strftime('%Y-%m-%d')}–#{conference.end_date.strftime('%Y-%m-%d')}")
expect(result).to include(conference.timezone)
expect(result).to include("#{events_cfp.start_date.strftime('%Y-%m-%d')}–#{events_cfp.end_date.strftime('%Y-%m-%d')}")
expect(result).to include(conference.venue.name)
expect(result).to include(conference.venue.address)
expect(result).to include(
"#{conference.registration_period.start_date.strftime('%Y-%m-%d')}–#{conference.registration_period.end_date.strftime('%Y-%m-%d')}"
)
expect(result).to include('Public types: 2')
end

it 'uses empty strings for optional sections when not configured (but still counts default public event types)' do
conference = create(:conference)
conference.program.cfps.destroy_all
conference.venue&.destroy
conference.registration_period&.destroy
conference.reload

template = 'CFP:{cfp_end_date}|Venue:{venue}|Reg:{registration_end_date}|Types:{num_public_event_types}'
result = described_class.interpolate(template, conference)

expect(result).to eq('CFP:|Venue:|Reg:|Types:2')
end
end
end
Loading