spina-admin-journal 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +99 -0
- data/Rakefile +36 -0
- data/app/assets/config/spina_admin_journal_manifest.js +3 -0
- data/app/assets/javascripts/spina/admin/journal/application.es6 +72 -0
- data/app/assets/stylesheets/spina/admin/journal/application.css +27 -0
- data/app/controllers/spina/admin/journal/application_controller.rb +26 -0
- data/app/controllers/spina/admin/journal/articles_controller.rb +125 -0
- data/app/controllers/spina/admin/journal/authors_controller.rb +83 -0
- data/app/controllers/spina/admin/journal/institutions_controller.rb +71 -0
- data/app/controllers/spina/admin/journal/issues_controller.rb +129 -0
- data/app/controllers/spina/admin/journal/journals_controller.rb +73 -0
- data/app/controllers/spina/admin/journal/volumes_controller.rb +79 -0
- data/app/models/spina/admin/journal.rb +11 -0
- data/app/models/spina/admin/journal/affiliation.rb +62 -0
- data/app/models/spina/admin/journal/article.rb +55 -0
- data/app/models/spina/admin/journal/author.rb +47 -0
- data/app/models/spina/admin/journal/authorship.rb +19 -0
- data/app/models/spina/admin/journal/institution.rb +32 -0
- data/app/models/spina/admin/journal/issue.rb +49 -0
- data/app/models/spina/admin/journal/journal.rb +50 -0
- data/app/models/spina/admin/journal/volume.rb +37 -0
- data/app/validators/spina/admin/journal/uri_validator.rb +24 -0
- data/app/views/layouts/spina/admin/journal/articles.html.haml +10 -0
- data/app/views/layouts/spina/admin/journal/authors.html.haml +10 -0
- data/app/views/layouts/spina/admin/journal/institutions.html.haml +10 -0
- data/app/views/layouts/spina/admin/journal/issues.html.haml +10 -0
- data/app/views/layouts/spina/admin/journal/journals.html.haml +10 -0
- data/app/views/layouts/spina/admin/journal/volumes.html.haml +10 -0
- data/app/views/spina/admin/hooks/journal/_head.html.haml +2 -0
- data/app/views/spina/admin/hooks/journal/_primary_navigation.html.haml +27 -0
- data/app/views/spina/admin/hooks/journal/_website_secondary_navigation.html.haml +0 -0
- data/app/views/spina/admin/journal/affiliations/_affiliation.html.haml +8 -0
- data/app/views/spina/admin/journal/application/_empty_list.html.haml +3 -0
- data/app/views/spina/admin/journal/articles/_article.html.haml +10 -0
- data/app/views/spina/admin/journal/articles/_form.html.haml +29 -0
- data/app/views/spina/admin/journal/articles/_form_authors.html.haml +10 -0
- data/app/views/spina/admin/journal/articles/_form_details.html.haml +52 -0
- data/app/views/spina/admin/journal/articles/edit.html.haml +1 -0
- data/app/views/spina/admin/journal/articles/index.html.haml +19 -0
- data/app/views/spina/admin/journal/articles/new.html.haml +1 -0
- data/app/views/spina/admin/journal/authors/_author.html.haml +8 -0
- data/app/views/spina/admin/journal/authors/_form.html.haml +29 -0
- data/app/views/spina/admin/journal/authors/_form_affiliation.html.haml +20 -0
- data/app/views/spina/admin/journal/authors/_form_articles.html.haml +18 -0
- data/app/views/spina/admin/journal/authors/_form_details.html.haml +6 -0
- data/app/views/spina/admin/journal/authors/edit.html.haml +1 -0
- data/app/views/spina/admin/journal/authors/index.html.haml +17 -0
- data/app/views/spina/admin/journal/authors/new.html.haml +1 -0
- data/app/views/spina/admin/journal/institutions/_form.html.haml +29 -0
- data/app/views/spina/admin/journal/institutions/_form_details.html.haml +9 -0
- data/app/views/spina/admin/journal/institutions/_form_view_affiliations.html.haml +10 -0
- data/app/views/spina/admin/journal/institutions/_institution.html.haml +9 -0
- data/app/views/spina/admin/journal/institutions/edit.html.haml +1 -0
- data/app/views/spina/admin/journal/institutions/index.html.haml +16 -0
- data/app/views/spina/admin/journal/institutions/new.html.haml +1 -0
- data/app/views/spina/admin/journal/issues/_form.html.haml +29 -0
- data/app/views/spina/admin/journal/issues/_form_articles.html.haml +14 -0
- data/app/views/spina/admin/journal/issues/_form_details.html.haml +32 -0
- data/app/views/spina/admin/journal/issues/_issue.html.haml +9 -0
- data/app/views/spina/admin/journal/issues/edit.html.haml +1 -0
- data/app/views/spina/admin/journal/issues/index.html.haml +18 -0
- data/app/views/spina/admin/journal/issues/new.html.haml +1 -0
- data/app/views/spina/admin/journal/journals/_form.html.haml +35 -0
- data/app/views/spina/admin/journal/journals/edit.html.haml +1 -0
- data/app/views/spina/admin/journal/journals/index.html.haml +27 -0
- data/app/views/spina/admin/journal/journals/new.html.haml +1 -0
- data/app/views/spina/admin/journal/volumes/_form.html.haml +15 -0
- data/app/views/spina/admin/journal/volumes/_form_details.html.haml +10 -0
- data/app/views/spina/admin/journal/volumes/_form_issues.html.haml +13 -0
- data/app/views/spina/admin/journal/volumes/_volume.html.haml +7 -0
- data/app/views/spina/admin/journal/volumes/edit.html.haml +1 -0
- data/app/views/spina/admin/journal/volumes/index.html.haml +17 -0
- data/app/views/spina/admin/journal/volumes/new.html.haml +1 -0
- data/config/locales/en.yml +184 -0
- data/config/routes.rb +21 -0
- data/db/migrate/20201216152147_create_spina_admin_journal_journals.rb +13 -0
- data/db/migrate/20201216153822_create_spina_admin_journal_volumes.rb +13 -0
- data/db/migrate/20201216155113_create_spina_admin_journal_issues.rb +15 -0
- data/db/migrate/20201216161122_create_spina_admin_journal_articles.rb +16 -0
- data/db/migrate/20201216205633_create_spina_admin_journal_institutions.rb +11 -0
- data/db/migrate/20201216211224_create_spina_admin_journal_authors.rb +7 -0
- data/db/migrate/20201216221146_create_spina_admin_journal_affiliations.rb +15 -0
- data/db/migrate/20201216230816_create_spina_admin_journal_authorships.rb +14 -0
- data/db/migrate/20201231165231_create_spina_admin_journal_parts.rb +12 -0
- data/db/migrate/20210424123450_add_json_attributes_to_spina_admin_journal_journals.rb +7 -0
- data/db/migrate/20210424123521_add_json_attributes_to_spina_admin_journal_issues.rb +7 -0
- data/db/migrate/20210424123555_add_json_attributes_to_spina_admin_journal_articles.rb +7 -0
- data/lib/spina/admin/journal.rb +18 -0
- data/lib/spina/admin/journal/engine.rb +17 -0
- data/lib/spina/admin/journal/version.rb +9 -0
- data/lib/tasks/spina/admin/journal_tasks.rake +5 -0
- data/vendor/assets/javascripts/spina/admin/journal/html5sortable.js +1295 -0
- metadata +388 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spina
|
4
|
+
module Admin
|
5
|
+
module Journal
|
6
|
+
# Authors are collections of {Affiliation}s.
|
7
|
+
#
|
8
|
+
# Since people can have multiple names, titles, institutions, etc., these details are all
|
9
|
+
# handled by the {Affiliation} record. This record groups said records together, in order
|
10
|
+
# that a coherent oeuvre of a single author be identified, which is more user-friendly.
|
11
|
+
#
|
12
|
+
# @note An author must have at least one Affiliation with +status: primary+, else validation
|
13
|
+
# will fail.
|
14
|
+
#
|
15
|
+
# @see Affiliation
|
16
|
+
class Author < ApplicationRecord
|
17
|
+
# @!attribute [rw] affiliations
|
18
|
+
# @return [ActiveRecord::Relation] Directly associated {Affiliation}s.
|
19
|
+
has_many :affiliations, inverse_of: :author, dependent: :destroy
|
20
|
+
accepts_nested_attributes_for :affiliations
|
21
|
+
# @!attribute [rw] institutions
|
22
|
+
# @return [ActiveRecord::Relation] The {Institution}s corresponding to this Author's affiliations.
|
23
|
+
has_many :institutions, through: :affiliations
|
24
|
+
# @!attribute [rw] articles
|
25
|
+
# @return [ActiveRecord::Relation] {Article}s associated through authorships.
|
26
|
+
has_many :articles, through: :affiliations
|
27
|
+
|
28
|
+
validate :must_have_one_primary_affiliation
|
29
|
+
|
30
|
+
# @!attribute [r] primary_affiliation
|
31
|
+
# @return [ActiveRecord::Relation] The author's primary affiliation.
|
32
|
+
def primary_affiliation
|
33
|
+
affiliations.primary.first || Affiliation.new(status: :primary)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def must_have_one_primary_affiliation
|
39
|
+
return if !affiliations.nil? && affiliations.any? \
|
40
|
+
&& (affiliations.count { |affiliation| affiliation.status == 'primary' } == 1)
|
41
|
+
|
42
|
+
errors.add :affiliations, 'must have at least one primary affiliation'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spina
|
4
|
+
module Admin
|
5
|
+
module Journal
|
6
|
+
# Joins Articles and Affiliations
|
7
|
+
# @see Article
|
8
|
+
# @see Affiliation
|
9
|
+
class Authorship < ApplicationRecord
|
10
|
+
# @!attribute [rw] article
|
11
|
+
# @return [ActiveRecord::Relation] the associated article
|
12
|
+
belongs_to :article
|
13
|
+
# @!attribute [rw] author_name
|
14
|
+
# @return [ActiveRecord::Relation] the associated affiliation of an author of the article
|
15
|
+
belongs_to :affiliation
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spina
|
4
|
+
module Admin
|
5
|
+
module Journal
|
6
|
+
# Record for an institution. Associated with {Author}s via {Affiliation}s.
|
7
|
+
#
|
8
|
+
# === Validators
|
9
|
+
# Presence:: {#name}
|
10
|
+
# Uniqueness:: {#name}
|
11
|
+
#
|
12
|
+
# === Scopes
|
13
|
+
# sorted:: sort institutions in alphabetical order
|
14
|
+
#
|
15
|
+
# @see Affiliation
|
16
|
+
class Institution < ApplicationRecord
|
17
|
+
# @!attribute [rw] name
|
18
|
+
# @return [String]
|
19
|
+
# @!attribute [rw] affiliations
|
20
|
+
# @return [ActiveRecord::Relation] the affiliations held at the institution
|
21
|
+
has_many :affiliations, dependent: :destroy
|
22
|
+
# @!attribute [rw] authors
|
23
|
+
# @return [ActiveRecord::Relation] associated authors
|
24
|
+
has_many :authors, through: :affiliations
|
25
|
+
|
26
|
+
validates :name, presence: true, uniqueness: { case_sensitive: false }
|
27
|
+
|
28
|
+
scope :sorted, -> { order(name: :asc) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spina
|
4
|
+
module Admin
|
5
|
+
module Journal
|
6
|
+
# Record for an issue of a journal.
|
7
|
+
# Belongs to a particular volume of a journal. Has many articles.
|
8
|
+
#
|
9
|
+
# === Validators
|
10
|
+
# Presence:: {#number}, {#date}
|
11
|
+
# Uniqueness:: {#number} (scope: volume)
|
12
|
+
#
|
13
|
+
# === Scopes
|
14
|
+
# sorted_asc:: sorted in order of increasing number
|
15
|
+
# sorted_desc:: sorted highest number first
|
16
|
+
#
|
17
|
+
# @see Article
|
18
|
+
# @see Volume
|
19
|
+
class Issue < ApplicationRecord
|
20
|
+
include AttrJson::Record
|
21
|
+
include AttrJson::NestedAttributes
|
22
|
+
include Spina::Partable
|
23
|
+
include Spina::TranslatedContent
|
24
|
+
# @!attribute [rw] number
|
25
|
+
# @return [Integer] The number
|
26
|
+
# @!attribute [rw] title
|
27
|
+
# @return [String] The title of the issue (optional)
|
28
|
+
# @!attribute [rw] date
|
29
|
+
# @return [Date] The (planned) publication date of the issue.
|
30
|
+
# @!attribute [rw] volume
|
31
|
+
# @return [ActiveRecord::Relation] the volume that contains this issue
|
32
|
+
belongs_to :volume
|
33
|
+
# @!attribute [rw] cover_img
|
34
|
+
# @return [Spina::Image, nil] the issue's cover image
|
35
|
+
belongs_to :cover_img, class_name: 'Spina::Image', optional: true
|
36
|
+
|
37
|
+
# @!attribute [rw] issue
|
38
|
+
# @return [ActiveRecord::Relation] the articles within this issue
|
39
|
+
has_many :articles, dependent: :destroy
|
40
|
+
|
41
|
+
validates :number, presence: true, uniqueness: { scope: :volume_id }
|
42
|
+
validates :date, presence: true
|
43
|
+
|
44
|
+
scope :sorted_asc, -> { includes(:volume).order('spina_admin_journal_volumes.number ASC', number: :asc) }
|
45
|
+
scope :sorted_desc, -> { includes(:volume).order('spina_admin_journal_volumes.number DESC', number: :desc) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spina
|
4
|
+
module Admin
|
5
|
+
module Journal
|
6
|
+
# Journal records. The top level of the database hierarchy.
|
7
|
+
#
|
8
|
+
# There should only ever be a single Journal record, which must always be accessed
|
9
|
+
# using {Journal.instance}
|
10
|
+
#
|
11
|
+
# === Validatorss
|
12
|
+
# Presence:: {#name}, {#singleton_guard}
|
13
|
+
# Uniqueness:: {#name}, {#singleton_guard}
|
14
|
+
class Journal < ApplicationRecord
|
15
|
+
include AttrJson::Record
|
16
|
+
include AttrJson::NestedAttributes
|
17
|
+
include Spina::Partable
|
18
|
+
include Spina::TranslatedContent
|
19
|
+
# @!attribute [rw] name
|
20
|
+
# @return [String] The name of the journal.
|
21
|
+
# @!attribute [rw] singleton_guard
|
22
|
+
# @return [Integer] Used to guarantee that a record is unique.
|
23
|
+
# @!attribute [rw] logo
|
24
|
+
# @return [Spina::Image, nil] The Spina::Image representing the logo of the journal.
|
25
|
+
belongs_to :logo, class_name: 'Spina::Image', optional: true
|
26
|
+
|
27
|
+
# @!attribute [rw] volumes
|
28
|
+
# @return [ActiveRecord::Relation] directly associated volumes
|
29
|
+
has_many :volumes, dependent: :destroy
|
30
|
+
|
31
|
+
validates :name, presence: true, uniqueness: true
|
32
|
+
validates :singleton_guard, presence: true, uniqueness: true
|
33
|
+
|
34
|
+
# Access the journal record.
|
35
|
+
#
|
36
|
+
# It is possible that Journal.instance gets called from two threads simultaneously.
|
37
|
+
# If this occurs, +Journal.create!+ will throw a validation error, since both records will
|
38
|
+
# be assigned the same value for +singleton_guard+. This is handled automatically, guaranteeing
|
39
|
+
# that there only ever be a single record.
|
40
|
+
def self.instance
|
41
|
+
Journal.first || Journal.create!(name: I18n.t('spina.admin.journal.unnamed_journal'), singleton_guard: 0)
|
42
|
+
rescue ActiveRecord::RecordNotUnique
|
43
|
+
# prevent race conditions leading to multiple records being created
|
44
|
+
logger.error 'Error when retrieving Journal instance. Retrying...'
|
45
|
+
retry
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spina
|
4
|
+
module Admin
|
5
|
+
module Journal
|
6
|
+
# Volumes of a journal.
|
7
|
+
# A journal has multiple volumes, a volume can have multiple issues.
|
8
|
+
#
|
9
|
+
# === Validators
|
10
|
+
# Presence:: {#number}
|
11
|
+
# Uniqueness:: {#number} (scope: journal)
|
12
|
+
#
|
13
|
+
# === Scopes
|
14
|
+
# sorted_asc:: sorted in order of increasing number
|
15
|
+
# sorted_desc:: sorted highest number first
|
16
|
+
#
|
17
|
+
# @see Issue
|
18
|
+
# @see Journal
|
19
|
+
class Volume < ApplicationRecord
|
20
|
+
# @!attribute [rw] number
|
21
|
+
# @return [Integer]
|
22
|
+
# @!attribute [rw] journal
|
23
|
+
# @return [ActiveRecord::Relation] the journal that contains this volume
|
24
|
+
belongs_to :journal
|
25
|
+
|
26
|
+
# @!attribute [rw] journal
|
27
|
+
# @return [ActiveRecord::Relation] the issues that comprise this volume
|
28
|
+
has_many :issues, dependent: :destroy
|
29
|
+
|
30
|
+
validates :number, presence: true, uniqueness: { scope: :journal_id }
|
31
|
+
|
32
|
+
scope :sorted_asc, -> { order(number: :asc) }
|
33
|
+
scope :sorted_desc, -> { order(number: :desc) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Spina
|
6
|
+
module Admin
|
7
|
+
module Journal
|
8
|
+
# A simple validator for URIs.
|
9
|
+
class UriValidator < ActiveModel::EachValidator
|
10
|
+
def validate_each(record, attribute, value)
|
11
|
+
return if value == '' || value.nil?
|
12
|
+
|
13
|
+
uri = URI.parse(value)
|
14
|
+
|
15
|
+
return if uri.is_a?(URI::HTTP) && !uri.host.nil?
|
16
|
+
|
17
|
+
record.errors.add attribute, :invalid_uri
|
18
|
+
rescue URI::InvalidURIError
|
19
|
+
record.errors.add attribute, :invalid_uri
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
%li{ class: ('active' if %w[articles issues volumes].include? controller_name) }
|
2
|
+
= link_to admin_journal_volumes_path do
|
3
|
+
= icon 'pages'
|
4
|
+
= ::Spina::Admin::Journal::Journal.instance.name
|
5
|
+
= icon 'caret-right'
|
6
|
+
|
7
|
+
%ul
|
8
|
+
%li{ class: ('active' if %w[volumes].include? controller_name) }
|
9
|
+
= link_to ::Spina::Admin::Journal::Volume.model_name.human(count: :many), admin_journal_volumes_path
|
10
|
+
%li{ class: ('active' if %w[issues].include? controller_name) }
|
11
|
+
= link_to ::Spina::Admin::Journal::Issue.model_name.human(count: :many), admin_journal_issues_path
|
12
|
+
%li{ class: ('active' if %w[articles].include? controller_name) }
|
13
|
+
= link_to ::Spina::Admin::Journal::Article.model_name.human(count: :many), admin_journal_articles_path
|
14
|
+
|
15
|
+
%li{ class: ('active' if %w[journals authors institutions].include? controller_name) }
|
16
|
+
= link_to edit_admin_journal_journal_path(::Spina::Admin::Journal::Journal.instance.id) do
|
17
|
+
= icon 'dots'
|
18
|
+
= t 'spina.admin.journal.navigation_title'
|
19
|
+
= icon 'caret-right'
|
20
|
+
|
21
|
+
%ul
|
22
|
+
%li{ class: ('active' if %w[journals].include? controller_name) }
|
23
|
+
= link_to ::Spina::Admin::Journal::Journal.model_name.human(count: :one), edit_admin_journal_journal_path(::Spina::Admin::Journal::Journal.instance.id)
|
24
|
+
%li{ class: ('active' if %w[authors].include? controller_name) }
|
25
|
+
= link_to ::Spina::Admin::Journal::Author.model_name.human(count: :many), admin_journal_authors_path
|
26
|
+
%li{ class: ('active' if %w[institutions].include? controller_name) }
|
27
|
+
= link_to ::Spina::Admin::Journal::Institution.model_name.human(count: :many), admin_journal_institutions_path
|
File without changes
|
@@ -0,0 +1,10 @@
|
|
1
|
+
%tr{ data: { id: article.id } }
|
2
|
+
%td= article.issue.volume.number
|
3
|
+
%td= article.issue.number
|
4
|
+
%td.position-display= article.number
|
5
|
+
%td= time_tag article.issue.date, format: :iso8601
|
6
|
+
%td= article.title
|
7
|
+
%td.nowrap.align-right
|
8
|
+
= link_to edit_admin_journal_article_path(article.id), class: 'button button-link' do
|
9
|
+
= icon 'pencil-outline'
|
10
|
+
= t '.view'
|
@@ -0,0 +1,29 @@
|
|
1
|
+
- if @article.errors.any?
|
2
|
+
- content_for :notifications do
|
3
|
+
.notification.notification-danger.animated.fadeInRight
|
4
|
+
= icon 'exclamation'
|
5
|
+
.notification-message
|
6
|
+
= t 'spina.notifications.alert'
|
7
|
+
%small= @article.errors.full_messages.join('<br />').html_safe
|
8
|
+
= link_to '#', data: { close_notification: true } do
|
9
|
+
= icon 'cross'
|
10
|
+
|
11
|
+
= form_for @article, html: { autocomplete: "off" } do |f|
|
12
|
+
%header#header
|
13
|
+
= render partial: 'spina/admin/shared/breadcrumbs'
|
14
|
+
|
15
|
+
#header_actions
|
16
|
+
%button.button.button-primary{ type: 'submit', style: 'margin-right: 0', data: { disable_with: t('spina.pages.saving') } }
|
17
|
+
= icon 'check'
|
18
|
+
= t '.save'
|
19
|
+
|
20
|
+
= link_to t('spina.cancel'), admin_journal_articles_path, class: 'button', style: 'margin-right: 0;'
|
21
|
+
|
22
|
+
%nav#secondary.tabs
|
23
|
+
%ul
|
24
|
+
- @tabs.each_with_index do |tab, i|
|
25
|
+
%li{ class: ('active' if i == 0) }
|
26
|
+
= link_to t(".#{tab}"), "##{tab}"
|
27
|
+
|
28
|
+
#details.tab-content.active= render 'form_details', f: f
|
29
|
+
#authors.tab-content= render 'form_authors', f: f
|
@@ -0,0 +1,10 @@
|
|
1
|
+
.table-container
|
2
|
+
%table.table
|
3
|
+
%thead
|
4
|
+
%tr
|
5
|
+
%th= ::Spina::Admin::Journal::Affiliation.human_attribute_name :name
|
6
|
+
%th= ::Spina::Admin::Journal::Affiliation.human_attribute_name :institution
|
7
|
+
%th= t '.number_of_articles'
|
8
|
+
%th
|
9
|
+
%tbody
|
10
|
+
= render @article.affiliations.any? ? @article.affiliations : 'empty_list', message: t('.no_authors')
|