decidim-core 0.7.4 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +9 -2
  3. data/app/assets/javascripts/decidim.js.es6 +1 -0
  4. data/app/assets/javascripts/decidim/editor.js.es6 +8 -2
  5. data/app/assets/javascripts/decidim/form_filter.component.js.es6 +2 -2
  6. data/app/assets/javascripts/decidim/foundation.js.es6 +18 -8
  7. data/app/assets/javascripts/decidim/select2.js.es6 +3 -0
  8. data/app/assets/stylesheets/decidim/_decidim.scss +1 -1
  9. data/app/assets/stylesheets/decidim/modules/_buttons.scss +0 -4
  10. data/app/assets/stylesheets/decidim/modules/_cards.scss +41 -1
  11. data/app/assets/stylesheets/decidim/modules/_messages.scss +35 -0
  12. data/app/assets/stylesheets/decidim/modules/_modules.scss +1 -0
  13. data/app/assets/stylesheets/decidim/modules/_navbar.scss +2 -1
  14. data/app/assets/stylesheets/decidim/utils/_settings.scss +363 -69
  15. data/app/commands/decidim/messaging/reply_to_conversation.rb +55 -0
  16. data/app/commands/decidim/messaging/start_conversation.rb +55 -0
  17. data/app/controllers/concerns/decidim/action_authorization.rb +6 -22
  18. data/app/controllers/concerns/decidim/user_profile.rb +5 -3
  19. data/app/controllers/decidim/application_controller.rb +1 -0
  20. data/app/controllers/decidim/devise/omniauth_registrations_controller.rb +2 -2
  21. data/app/controllers/decidim/devise/sessions_controller.rb +5 -3
  22. data/app/controllers/decidim/features/base_controller.rb +1 -0
  23. data/app/controllers/decidim/messaging/conversations_controller.rb +81 -0
  24. data/app/controllers/decidim/pages_controller.rb +2 -11
  25. data/app/forms/decidim/messaging/conversation_form.rb +19 -0
  26. data/app/forms/decidim/messaging/message_form.rb +14 -0
  27. data/app/forms/translatable_presence_validator.rb +7 -10
  28. data/app/helpers/decidim/authorization_form_helper.rb +1 -1
  29. data/app/helpers/decidim/cta_button_helper.rb +1 -1
  30. data/app/helpers/decidim/datetime_helper.rb +23 -0
  31. data/app/helpers/decidim/feature_path_helper.rb +4 -2
  32. data/app/helpers/decidim/layout_helper.rb +0 -2
  33. data/app/helpers/decidim/menu_helper.rb +10 -0
  34. data/app/helpers/decidim/messaging/conversation_helper.rb +36 -0
  35. data/app/helpers/decidim/sanitize_helper.rb +19 -0
  36. data/app/helpers/decidim/traceability_helper.rb +89 -0
  37. data/app/helpers/decidim/translations_helper.rb +4 -2
  38. data/app/helpers/decidim/view_hooks_helper.rb +15 -0
  39. data/app/jobs/decidim/email_notification_generator_job.rb +1 -1
  40. data/app/jobs/decidim/notification_generator_for_recipient_job.rb +1 -1
  41. data/app/jobs/decidim/notification_generator_job.rb +1 -1
  42. data/app/mailers/decidim/messaging/conversation_mailer.rb +47 -0
  43. data/app/mailers/decidim/newsletter_mailer.rb +1 -0
  44. data/app/models/decidim/abilities/base_ability.rb +16 -2
  45. data/app/models/decidim/authorization.rb +20 -2
  46. data/app/models/decidim/messaging/conversation.rb +129 -0
  47. data/app/models/decidim/messaging/message.rb +49 -0
  48. data/app/models/decidim/messaging/participation.rb +23 -0
  49. data/app/models/decidim/messaging/receipt.rb +27 -0
  50. data/app/models/decidim/moderation.rb +1 -1
  51. data/app/models/decidim/organization.rb +2 -2
  52. data/app/models/decidim/scope.rb +2 -1
  53. data/app/models/decidim/scope_type.rb +1 -1
  54. data/app/models/decidim/user.rb +11 -5
  55. data/app/models/decidim/user_group.rb +1 -1
  56. data/app/presenters/decidim/inline_menu_presenter.rb +10 -0
  57. data/app/presenters/decidim/menu_presenter.rb +1 -1
  58. data/app/queries/decidim/messaging/user_conversations.rb +29 -0
  59. data/app/scrubbers/decidim/user_input_scrubber.rb +31 -0
  60. data/app/services/decidim/action_authorizer.rb +48 -12
  61. data/app/services/decidim/traceability.rb +91 -0
  62. data/app/views/decidim/messaging/conversation_mailer/new_conversation.html.erb +9 -0
  63. data/app/views/decidim/messaging/conversation_mailer/new_message.html.erb +9 -0
  64. data/app/views/decidim/messaging/conversations/_message.html.erb +20 -0
  65. data/app/views/decidim/messaging/conversations/_reply.html.erb +11 -0
  66. data/app/views/decidim/messaging/conversations/_show.html.erb +21 -0
  67. data/app/views/decidim/messaging/conversations/_start.html.erb +12 -0
  68. data/app/views/decidim/messaging/conversations/create.js.erb +2 -0
  69. data/app/views/decidim/messaging/conversations/index.html.erb +51 -0
  70. data/app/views/decidim/messaging/conversations/new.html.erb +5 -0
  71. data/app/views/decidim/messaging/conversations/show.html.erb +9 -0
  72. data/app/views/decidim/messaging/conversations/update.js.erb +1 -0
  73. data/app/views/decidim/newsletter_mailer/newsletter.html.erb +1 -1
  74. data/app/views/decidim/notifications/_notification.html.erb +1 -1
  75. data/app/views/decidim/shared/_action_authorization_modal.html.erb +11 -1
  76. data/app/views/decidim/shared/_announcement.html.erb +1 -1
  77. data/app/views/decidim/shared/_version_author.html.erb +18 -0
  78. data/app/views/layouts/decidim/_wrapper.html.erb +3 -0
  79. data/app/views/layouts/decidim/user_profile.html.erb +1 -9
  80. data/app/views/pages/decidim_page.html.erb +1 -1
  81. data/app/views/pages/home.html.erb +0 -2
  82. data/app/views/pages/home/_footer_sub_hero.html.erb +5 -3
  83. data/app/views/pages/home/_hero.html.erb +1 -1
  84. data/app/views/pages/home/_highlighted_processes.html.erb +7 -37
  85. data/app/views/pages/home/_sub_hero.html.erb +6 -4
  86. data/config/locales/ca.yml +49 -21
  87. data/config/locales/en.yml +47 -19
  88. data/config/locales/es.yml +48 -20
  89. data/config/locales/eu.yml +51 -23
  90. data/config/locales/fi.yml +50 -22
  91. data/config/locales/fr.yml +50 -22
  92. data/config/locales/it.yml +89 -61
  93. data/config/locales/nl.yml +72 -44
  94. data/config/locales/pl.yml +49 -21
  95. data/config/locales/pt.yml +431 -0
  96. data/config/locales/ru.yml +4 -27
  97. data/config/locales/uk.yml +10 -23
  98. data/config/routes.rb +3 -5
  99. data/db/migrate/20170313095436_add_available_authorizations_to_organization.rb +2 -2
  100. data/db/migrate/20170713131308_migrate_user_roles_to_participatory_process_roles.rb +7 -3
  101. data/db/migrate/20170914092117_add_status_to_authorizations.rb +9 -0
  102. data/db/migrate/20171011194251_add_verification_metadata_to_authorizations.rb +11 -0
  103. data/db/migrate/20171013124505_add_verification_attachment_to_authorizations.rb +9 -0
  104. data/db/migrate/20171023123330_create_decidim_messaging.rb +23 -0
  105. data/db/migrate/20171107103253_create_versions.rb +18 -0
  106. data/db/migrate/20171107103254_add_object_changes_to_versions.rb +14 -0
  107. data/db/migrate/20171117100533_create_decidim_receipts.rb +13 -0
  108. data/db/seeds.rb +13 -3
  109. data/lib/decidim/core.rb +13 -6
  110. data/lib/decidim/core/engine.rb +37 -0
  111. data/lib/decidim/core/test/factories.rb +33 -21
  112. data/lib/decidim/core/test/shared_examples/follows_examples.rb +1 -1
  113. data/lib/decidim/core/test/shared_examples/manage_moderations_examples.rb +6 -11
  114. data/lib/decidim/core/test/shared_examples/process_announcements_examples.rb +2 -4
  115. data/lib/decidim/core/test/shared_examples/reportable.rb +33 -31
  116. data/lib/decidim/core/test/shared_examples/scope_helper_examples.rb +29 -31
  117. data/lib/decidim/core/version.rb +1 -1
  118. data/lib/decidim/engine_router.rb +4 -2
  119. data/lib/decidim/has_reference.rb +10 -2
  120. data/lib/decidim/messaging.rb +9 -0
  121. data/lib/decidim/participable.rb +1 -1
  122. data/lib/decidim/traceable.rb +31 -0
  123. data/lib/decidim/view_hooks.rb +108 -0
  124. data/vendor/assets/javascripts/datepicker-locales/foundation-datepicker.es.js +4 -2
  125. data/vendor/assets/javascripts/datepicker-locales/foundation-datepicker.pt.js +14 -0
  126. metadata +179 -113
  127. data/app/commands/decidim/authorize_user.rb +0 -59
  128. data/app/controllers/decidim/authorizations_controller.rb +0 -80
  129. data/app/services/decidim/authorization_handler.rb +0 -95
  130. data/app/views/decidim/authorizations/first_login.html.erb +0 -22
  131. data/app/views/decidim/authorizations/index.html.erb +0 -49
  132. data/app/views/decidim/authorizations/new.html.erb +0 -33
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Messaging
5
+ # A command with all the business logic for replying to a conversation
6
+ class ReplyToConversation < Rectify::Command
7
+ # Public: Initializes the command.
8
+ #
9
+ # conversation - The conversation to be updated.
10
+ # form - A form object with the params.
11
+ def initialize(conversation, form)
12
+ @conversation = conversation
13
+ @form = form
14
+ end
15
+
16
+ # Executes the command. Broadcasts these events:
17
+ #
18
+ # - :ok when everything is valid, together with the message.
19
+ # - :invalid if the form wasn't valid and we couldn't proceed.
20
+ #
21
+ # Returns nothing.
22
+ def call
23
+ return broadcast(:invalid) if form.invalid?
24
+
25
+ if message.save
26
+ notify_interlocutors
27
+
28
+ broadcast(:ok, message)
29
+ else
30
+ broadcast(:invalid)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def sender
37
+ form.current_user
38
+ end
39
+
40
+ def message
41
+ @message ||= conversation.add_message(sender: sender, body: form.body)
42
+ end
43
+
44
+ def notify_interlocutors
45
+ conversation.interlocutors(sender).each do |recipient|
46
+ if conversation.unread_count(recipient) == 1
47
+ ConversationMailer.new_message(sender, recipient, conversation).deliver_later
48
+ end
49
+ end
50
+ end
51
+
52
+ attr_reader :conversation, :form
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Messaging
5
+ # A command with all the business logic for replying to a conversation
6
+ class StartConversation < Rectify::Command
7
+ # Public: Initializes the command.
8
+ #
9
+ # form - A conversation form
10
+ def initialize(form)
11
+ @form = form
12
+ end
13
+
14
+ # Executes the command. Broadcasts these events:
15
+ #
16
+ # - :ok when everything is valid, together with the message.
17
+ # - :invalid if the form wasn't valid and we couldn't proceed.
18
+ #
19
+ # Returns nothing.
20
+ def call
21
+ return broadcast(:invalid) if form.invalid?
22
+
23
+ if conversation.save
24
+ notify_interlocutors
25
+
26
+ broadcast(:ok, conversation)
27
+ else
28
+ broadcast(:invalid)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def conversation
35
+ @conversation ||= Conversation.start(
36
+ originator: originator,
37
+ interlocutors: [form.recipient],
38
+ body: form.body
39
+ )
40
+ end
41
+
42
+ def notify_interlocutors
43
+ conversation.interlocutors(originator).each do |recipient|
44
+ ConversationMailer.new_conversation(originator, recipient, conversation).deliver_later
45
+ end
46
+ end
47
+
48
+ def originator
49
+ form.current_user
50
+ end
51
+
52
+ attr_reader :form
53
+ end
54
+ end
55
+ end
@@ -16,13 +16,12 @@ module Decidim
16
16
  # feature's permissions panel.
17
17
  # redirect_url - Url to be redirected to when the authorization is finished.
18
18
  def authorize_action!(action_name, redirect_url: nil)
19
- @action_authorizations ||= {}
20
- status = @action_authorizations[action_name] = action_authorization(action_name)
19
+ status = action_authorization(action_name)
21
20
 
22
21
  return if status.ok?
23
22
  raise Unauthorized if status.code == :invalid
24
23
 
25
- redirect_to authorize_action_path_from_status(status, redirect_url)
24
+ redirect_to authorize_action_path(action_name, redirect_url: redirect_url)
26
25
  end
27
26
 
28
27
  # Public: Returns the authorization object for an authorization.
@@ -31,7 +30,9 @@ module Decidim
31
30
  #
32
31
  # Returns an ActionAuthorizer::AuthorizationStatus
33
32
  def action_authorization(action_name)
34
- _action_authorizer(action_name).authorize
33
+ @action_authorizations ||= {}
34
+
35
+ @action_authorizations[action_name] = _action_authorizer(action_name).authorize
35
36
  end
36
37
 
37
38
  # Public: Returns the authorization path for a failed authorization with
@@ -44,24 +45,7 @@ module Decidim
44
45
  def authorize_action_path(action_name, redirect_url: nil)
45
46
  redirect_url ||= request.path
46
47
 
47
- authorize_action_path_from_status(
48
- action_authorization(action_name),
49
- redirect_url: redirect_url
50
- )
51
- end
52
-
53
- # Public: Returns the authorization path for a failed authorization with
54
- # the populated redirect url.
55
- #
56
- # status - The status after an authorization check.
57
- # redirect_url - The url to redirect to when finished.
58
- #
59
- # Returns a String.
60
- def authorize_action_path_from_status(status, redirect_url: nil)
61
- decidim.new_authorization_path(
62
- handler: status.handler_name,
63
- redirect_url: redirect_url
64
- )
48
+ action_authorization(action_name).current_path(redirect_url: redirect_url)
65
49
  end
66
50
 
67
51
  def _action_authorizer(action_name)
@@ -17,7 +17,7 @@ module Decidim
17
17
  helper Decidim::UserProfileHelper
18
18
  layout "layouts/decidim/user_profile"
19
19
 
20
- helper_method :available_authorization_handlers,
20
+ helper_method :available_verification_workflows,
21
21
  :user_groups
22
22
 
23
23
  before_action :current_user
@@ -26,8 +26,10 @@ module Decidim
26
26
 
27
27
  # Public: Available authorization handlers in order to conditionally
28
28
  # show the menu element.
29
- def available_authorization_handlers
30
- current_organization.available_authorizations.map(&:constantize)
29
+ def available_verification_workflows
30
+ Verifications::Adapter.from_collection(
31
+ current_organization.available_authorizations & Decidim.authorization_workflows.map(&:name)
32
+ )
31
33
  end
32
34
  end
33
35
  end
@@ -18,6 +18,7 @@ module Decidim
18
18
  helper Decidim::AriaSelectedLinkToHelper
19
19
  helper Decidim::MenuHelper
20
20
  helper Decidim::FeaturePathHelper
21
+ helper Decidim::ViewHooksHelper
21
22
 
22
23
  protect_from_forgery with: :exception, prepend: true
23
24
  after_action :add_vary_header
@@ -45,14 +45,14 @@ module Decidim
45
45
 
46
46
  def after_sign_in_path_for(user)
47
47
  if first_login_and_not_authorized?(user)
48
- authorizations_path
48
+ decidim_verifications.authorizations_path
49
49
  else
50
50
  super
51
51
  end
52
52
  end
53
53
 
54
54
  def first_login_and_not_authorized?(user)
55
- user.is_a?(User) && user.sign_in_count == 1 && Decidim.authorization_handlers.any?
55
+ user.is_a?(User) && user.sign_in_count == 1 && Decidim::Verifications.workflows.any?
56
56
  end
57
57
 
58
58
  def action_missing(action_name)
@@ -7,9 +7,11 @@ module Decidim
7
7
  include Decidim::DeviseControllers
8
8
 
9
9
  def after_sign_in_path_for(user)
10
- return first_login_authorizations_path if first_login_and_not_authorized?(user) &&
11
- !user.admin?
12
- super
10
+ if first_login_and_not_authorized?(user) && !user.admin?
11
+ decidim_verifications.first_login_authorizations_path
12
+ else
13
+ super
14
+ end
13
15
  end
14
16
 
15
17
  def first_login_and_not_authorized?(user)
@@ -18,6 +18,7 @@ module Decidim
18
18
  helper Decidim::ScopesHelper
19
19
  helper Decidim::ActionAuthorizationHelper
20
20
  helper Decidim::AttachmentsHelper
21
+ helper Decidim::SanitizeHelper
21
22
 
22
23
  helper_method :current_feature,
23
24
  :current_participatory_space,
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Messaging
5
+ # The controller to handle the user's conversations.
6
+ class ConversationsController < Decidim::ApplicationController
7
+ include FormFactory
8
+
9
+ helper Decidim::DatetimeHelper
10
+
11
+ before_action :authenticate_user!
12
+
13
+ helper_method :username_list, :conversation
14
+
15
+ def new
16
+ authorize! :create, Conversation
17
+
18
+ @form = form(ConversationForm).from_params(params)
19
+ end
20
+
21
+ def create
22
+ authorize! :create, Conversation
23
+
24
+ @form = form(ConversationForm).from_params(params)
25
+
26
+ StartConversation.call(@form) do
27
+ on(:ok) do |conversation|
28
+ render action: :create, locals: {
29
+ conversation: conversation,
30
+ form: MessageForm.new
31
+ }
32
+ end
33
+
34
+ on(:invalid) do
35
+ render json: { error: I18n.t("messaging.conversations.create.error", scope: "decidim") }, status: 422
36
+ end
37
+ end
38
+ end
39
+
40
+ def index
41
+ authorize! :index, Conversation
42
+
43
+ @conversations = UserConversations.for(current_user)
44
+ end
45
+
46
+ def show
47
+ authorize! :show, conversation
48
+
49
+ @conversation.mark_as_read(current_user)
50
+
51
+ @form = MessageForm.new
52
+ end
53
+
54
+ def update
55
+ authorize! :update, conversation
56
+
57
+ @form = form(MessageForm).from_params(params)
58
+
59
+ ReplyToConversation.call(conversation, @form) do
60
+ on(:ok) do |message|
61
+ render action: :update, locals: { message: message }
62
+ end
63
+
64
+ on(:invalid) do
65
+ render json: { error: I18n.t("messaging.conversations.update.error", scope: "decidim") }, status: 422
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def conversation
73
+ @conversation ||= Conversation.find(params[:id])
74
+ end
75
+
76
+ def username_list(users)
77
+ users.pluck(:name).join(", ")
78
+ end
79
+ end
80
+ end
81
+ end
@@ -11,8 +11,9 @@ module Decidim
11
11
 
12
12
  authorize_resource :public_pages, class: false
13
13
  delegate :page, to: :page_finder
14
- helper_method :page, :promoted_participatory_processes, :highlighted_participatory_processes, :stats
14
+ helper_method :page, :stats
15
15
  helper CtaButtonHelper
16
+ helper Decidim::SanitizeHelper
16
17
 
17
18
  def index
18
19
  @pages = current_organization.static_pages.all.to_a.sort do |a, b|
@@ -24,16 +25,6 @@ module Decidim
24
25
  @page_finder ||= Decidim::PageFinder.new(params[:id], current_organization)
25
26
  end
26
27
 
27
- def promoted_participatory_processes
28
- @promoted_participatory_processes ||=
29
- ParticipatoryProcesses::OrganizationPrioritizedParticipatoryProcesses.new(current_organization) | ParticipatoryProcesses::PromotedParticipatoryProcesses.new
30
- end
31
-
32
- def highlighted_participatory_processes
33
- @highlighted_participatory_processes ||=
34
- ParticipatoryProcesses::OrganizationPublishedParticipatoryProcesses.new(current_organization) | ParticipatoryProcesses::HighlightedParticipatoryProcesses.new
35
- end
36
-
37
28
  private
38
29
 
39
30
  def stats
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Messaging
5
+ # A form object to be used when users want to follow a followable resource.
6
+ class ConversationForm < Decidim::Form
7
+ mimic :conversation
8
+
9
+ attribute :body, String
10
+ attribute :recipient_id, Integer
11
+
12
+ validates :body, :recipient, presence: true
13
+
14
+ def recipient
15
+ Decidim::User.find_by(id: recipient_id)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Messaging
5
+ # A form object to be used when sending a message
6
+ class MessageForm < Decidim::Form
7
+ mimic :message
8
+
9
+ attribute :body, String
10
+
11
+ validates :body, presence: true
12
+ end
13
+ end
14
+ end
@@ -5,24 +5,21 @@
5
5
  #
6
6
  # validates :my_i18n_field, translatable_presence: true
7
7
  #
8
- # This will automatically check for presence for each of the
9
- # `available_locales` of the form object (or the `available_locales` of the
10
- # form's organization) for the given field.
8
+ # This will automatically check for presence for the default locale of the form
9
+ # object (or the `default_locale` of the form's organization) for the given field.
11
10
  class TranslatablePresenceValidator < ActiveModel::EachValidator
12
11
  def validate_each(record, attribute, _value)
13
- available_locales_for(record).each do |locale|
14
- translated_attr = "#{attribute}_#{locale}"
15
- record.errors.add(translated_attr, :blank) if record.send(translated_attr).blank?
16
- end
12
+ translated_attr = "#{attribute}_#{default_locale_for(record)}"
13
+ record.errors.add(translated_attr, :blank) if record.send(translated_attr).blank?
17
14
  end
18
15
 
19
16
  private
20
17
 
21
- def available_locales_for(record)
22
- return record.available_locales if record.respond_to?(:available_locales)
18
+ def default_locale_for(record)
19
+ return record.default_locale if record.respond_to?(:default_locale)
23
20
 
24
21
  if record.current_organization
25
- record.current_organization.available_locales
22
+ record.current_organization.default_locale
26
23
  else
27
24
  record.errors.add(:current_organization, :blank)
28
25
  []
@@ -16,7 +16,7 @@ module Decidim
16
16
  default_options = {
17
17
  builder: AuthorizationFormBuilder,
18
18
  as: "authorization_handler",
19
- url: authorizations_path
19
+ url: decidim_verifications.authorizations_path
20
20
  }
21
21
 
22
22
  options = default_options.merge(options)
@@ -4,7 +4,7 @@ module Decidim
4
4
  # A Helper to render the Call To Action button.
5
5
  module CtaButtonHelper
6
6
  # Renders the Call To Action button. Link and text can be configured
7
- # per organizationn.
7
+ # per organization.
8
8
  def cta_button
9
9
  button_text = translated_attribute(current_organization.cta_button_text).presence || t("pages.home.hero.participate")
10
10
 
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/date_time/calculations"
4
+
5
+ module Decidim
6
+ # A Helper to render dates
7
+ module DatetimeHelper
8
+ # Renders a date in the simplest possible format.
9
+ def simple_date(datetime)
10
+ current_datetime = Time.zone.now
11
+
12
+ if datetime > current_datetime.beginning_of_day
13
+ I18n.l(datetime, format: :time_of_day)
14
+ elsif datetime > current_datetime.beginning_of_week
15
+ I18n.l(datetime, format: :day_of_week)
16
+ elsif datetime > current_datetime.beginning_of_year
17
+ I18n.l(datetime, format: :day_of_month)
18
+ else
19
+ I18n.l(datetime, format: :day_of_year)
20
+ end
21
+ end
22
+ end
23
+ end