summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/assets_controller.rb2
-rw-r--r--app/controllers/pages_controller.rb20
-rw-r--r--app/controllers/related_assets_controller.rb17
-rw-r--r--app/controllers/shared_previews_controller.rb2
-rw-r--r--app/helpers/content_helper.rb33
-rw-r--r--app/models/asset.rb24
-rw-r--r--app/models/asset_license.rb15
-rw-r--r--app/models/concerns/file_attachment.rb55
-rw-r--r--app/models/node_action.rb8
-rw-r--r--app/models/page.rb26
-rw-r--r--app/models/related_asset.rb9
-rw-r--r--app/views/assets/edit.html.erb58
-rw-r--r--app/views/assets/index.html.erb2
-rw-r--r--app/views/assets/new.html.erb49
-rw-r--r--app/views/assets/show.html.erb37
-rw-r--r--app/views/content/_asset_credits.html.erb10
-rw-r--r--app/views/content/_headline_image.html.erb67
-rw-r--r--app/views/custom/page_templates/public/chapter_detail.html.erb1
-rw-r--r--app/views/custom/page_templates/public/no_date_and_author.html.erb1
-rw-r--r--app/views/custom/page_templates/public/no_title_abstract_date_and_author.html.erb1
-rw-r--r--app/views/custom/page_templates/public/standard_template.html.erb1
-rw-r--r--app/views/custom/page_templates/public/title_only.html.erb1
-rw-r--r--app/views/custom/page_templates/public/update.html.erb1
-rw-r--r--app/views/custom/partials/_chapter.html.erb4
-rw-r--r--app/views/layouts/application.html.erb2
-rw-r--r--app/views/layouts/events.html.erb17
-rw-r--r--app/views/node_actions/_change_details.html.erb16
-rw-r--r--app/views/nodes/edit.html.erb15
-rw-r--r--app/views/nodes/new.html.erb5
-rw-r--r--app/views/nodes/show.html.erb14
30 files changed, 388 insertions, 125 deletions
diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb
index d150e06..becfe13 100644
--- a/app/controllers/assets_controller.rb
+++ b/app/controllers/assets_controller.rb
@@ -89,6 +89,6 @@ class AssetsController < ApplicationController
89 private 89 private
90 90
91 def asset_params 91 def asset_params
92 params.require(:asset).permit(:name, :upload) 92 params.require(:asset).permit(:name, :upload, :creator, :source_url, :license_key)
93 end 93 end
94end 94end
diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb
index 2d08dea..326fbd4 100644
--- a/app/controllers/pages_controller.rb
+++ b/app/controllers/pages_controller.rb
@@ -6,6 +6,19 @@ class PagesController < ApplicationController
6 6
7 def preview 7 def preview
8 @page = Page.find(params[:id]) 8 @page = Page.find(params[:id])
9 unless @page.node
10 node = Node.find_by(autosave_id: @page.id) ||
11 Node.find_by(draft_id: @page.id) ||
12 Node.find_by(head_id: @page.id)
13 @page.node = node if node
14 end
15
16 node ||= @page.node
17 if node && node.draft_id == @page.id && node.autosave
18 @page = node.autosave
19 @page.node = node
20 end
21
9 22
10 if @page 23 if @page
11 template = @page.valid_template 24 template = @page.valid_template
@@ -15,11 +28,4 @@ class PagesController < ApplicationController
15 ) 28 )
16 end 29 end
17 end 30 end
18
19 def sort_images
20 page = Page.find(params[:id])
21 page.update_assets(params[:images])
22
23 head :ok
24 end
25end 31end
diff --git a/app/controllers/related_assets_controller.rb b/app/controllers/related_assets_controller.rb
index 5af73fc..ca894f2 100644
--- a/app/controllers/related_assets_controller.rb
+++ b/app/controllers/related_assets_controller.rb
@@ -5,10 +5,10 @@ class RelatedAssetsController < ApplicationController
5 def search 5 def search
6 term = params[:search_term].to_s.strip 6 term = params[:search_term].to_s.strip
7 attached_ids = @node.editable_page.related_assets.pluck(:asset_id) 7 attached_ids = @node.editable_page.related_assets.pluck(:asset_id)
8 scope = Asset.images.where.not(id: attached_ids) 8 scope = Asset.headline_eligible.where.not(id: attached_ids)
9 9
10 results = if term.present? 10 results = if term.present?
11 scope.where("name ILIKE ?", "%#{term}%").limit(10) 11 scope.where("name ILIKE :term OR upload_file_name ILIKE :term", term: "%#{term}%").limit(10)
12 else 12 else
13 scope.order(created_at: :desc).limit(5) 13 scope.order(created_at: :desc).limit(5)
14 end 14 end
@@ -26,6 +26,7 @@ class RelatedAssetsController < ApplicationController
26 id: related.id, 26 id: related.id,
27 asset_id: asset.id, 27 asset_id: asset.id,
28 name: asset.name, 28 name: asset.name,
29 has_credit: asset.show_credit?,
29 thumb_url: asset.upload.url(:thumb), 30 thumb_url: asset.upload.url(:thumb),
30 large_url: asset.upload.url(:large), 31 large_url: asset.upload.url(:large),
31 original_url: asset.upload.url, 32 original_url: asset.upload.url,
@@ -39,7 +40,17 @@ class RelatedAssetsController < ApplicationController
39 end 40 end
40 41
41 def update 42 def update
42 @node.editable_page.related_assets.find(params[:id]).insert_at(params[:position].to_i) 43 related = @node.editable_page.related_assets.find(params[:id])
44
45 if params.key?(:headline)
46 RelatedAsset.transaction do
47 @node.editable_page.related_assets.update_all(headline: false)
48 related.update!(headline: true) if params[:headline] == "true"
49 end
50 else
51 related.insert_at(params[:position].to_i)
52 end
53
43 head :ok 54 head :ok
44 end 55 end
45 56
diff --git a/app/controllers/shared_previews_controller.rb b/app/controllers/shared_previews_controller.rb
index f6fb45a..65f744d 100644
--- a/app/controllers/shared_previews_controller.rb
+++ b/app/controllers/shared_previews_controller.rb
@@ -8,7 +8,7 @@ class SharedPreviewsController < ApplicationController
8 is_draft = node.draft_id == @page.id 8 is_draft = node.draft_id == @page.id
9 9
10 currently_public = is_head && @page.public? 10 currently_public = is_head && @page.public?
11 superseded = !is_head && !is_draft 11 superseded = !is_head && !is_draft
12 12
13 if superseded || currently_public 13 if superseded || currently_public
14 redirect_to @page.public_link 14 redirect_to @page.public_link
diff --git a/app/helpers/content_helper.rb b/app/helpers/content_helper.rb
index 5810966..7a52d9c 100644
--- a/app/helpers/content_helper.rb
+++ b/app/helpers/content_helper.rb
@@ -53,11 +53,8 @@ module ContentHelper
53 end 53 end
54 54
55 def headline_image 55 def headline_image
56 @images = @page.assets.images 56 @headline_asset = @page.headline_asset
57 57 render :partial => 'content/headline_image' if @headline_asset || @page.assets.images.any? || @page.assets.pdfs.any?
58 unless @images.empty?
59 render :partial => 'content/headline_image'
60 end
61 end 58 end
62 59
63 # Returns the published_at attribute of a page if it is not nil, otherwise 60 # Returns the published_at attribute of a page if it is not nil, otherwise
@@ -154,4 +151,30 @@ module ContentHelper
154 ) 151 )
155 end 152 end
156 153
154 def asset_credit(asset)
155 return nil unless asset
156 return nil unless asset.has_credit?
157
158 license = AssetLicense.find(asset.license_key)
159
160 photo_label = t("asset_credits.photo", name: asset.name)
161 photo = asset.source_url.present? ? link_to(photo_label, asset.source_url) : photo_label
162
163 attribution_parts = [photo]
164 attribution_parts << t("asset_credits.by", creator: asset.creator) if asset.creator.present?
165 attribution = safe_join(attribution_parts, " ")
166
167 license_text = if license
168 name = t("asset_licenses.#{license.key}", default: license.key)
169 phrase = license.style == "license" ? t("asset_credits.licensed_under", license: name) : name
170 license.url.present? ? link_to(phrase, license.url) : phrase
171 end
172
173 full = license_text ? safe_join([attribution, license_text], ", ") : attribution
174 safe_join([full, "."])
175 end
176
177 def glightbox_data(image, title)
178 "title: #{title.to_s.tr(';', ',')};"
179 end
157end 180end
diff --git a/app/models/asset.rb b/app/models/asset.rb
index aca0ee8..ba9c2f0 100644
--- a/app/models/asset.rb
+++ b/app/models/asset.rb
@@ -1,12 +1,34 @@
1class Asset < ApplicationRecord 1class Asset < ApplicationRecord
2 IMAGE_CONTENT_TYPES = ["image/gif", "image/jpeg", "image/png", "image/webp"]
3 PDF_CONTENT_TYPE = "application/pdf"
2 4
3 include FileAttachment 5 include FileAttachment
4 6
5 has_many :related_assets, :dependent => :destroy 7 has_many :related_assets, :dependent => :destroy
6 has_many :pages, :through => :related_assets 8 has_many :pages, :through => :related_assets
7 9
8 scope :images, -> { where(:upload_content_type => ["image/gif", "image/jpeg", "image/png", "image/webp"]) } 10 scope :images, -> { where(:upload_content_type => IMAGE_CONTENT_TYPES) }
9 scope :documents, -> { where(:upload_content_type => ["application/pdf", "text/plain", "text/rtf"]) } 11 scope :documents, -> { where(:upload_content_type => ["application/pdf", "text/plain", "text/rtf"]) }
10 scope :audio, -> { where(:upload_content_type => ["audio/mpeg", "audio/x-m4a", "audio/wav", "audio/x-wav"]) } 12 scope :audio, -> { where(:upload_content_type => ["audio/mpeg", "audio/x-m4a", "audio/wav", "audio/x-wav"]) }
13 scope :pdfs, -> { where(:upload_content_type => PDF_CONTENT_TYPE) }
11 14
15 scope :headline_eligible, -> { where(:upload_content_type => IMAGE_CONTENT_TYPES + [PDF_CONTENT_TYPE]) }
16
17 validates :license_key, inclusion: { in: -> { AssetLicense.keys } }, allow_blank: true
18
19 def image?
20 IMAGE_CONTENT_TYPES.include?(upload_content_type)
21 end
22
23 def pdf?
24 upload_content_type == PDF_CONTENT_TYPE
25 end
26
27 def has_credit?
28 creator.present? || source_url.present? || license_key.present?
29 end
30
31 def show_credit?
32 image? && has_credit?
33 end
12end 34end
diff --git a/app/models/asset_license.rb b/app/models/asset_license.rb
new file mode 100644
index 0000000..ca66d40
--- /dev/null
+++ b/app/models/asset_license.rb
@@ -0,0 +1,15 @@
1class AssetLicense
2 Entry = Struct.new(:key, :url, :requires_attribution, :style, keyword_init: true)
3
4 DICTIONARY = YAML.load_file(Rails.root.join("config", "asset_licenses.yml")).freeze
5
6 def self.keys
7 DICTIONARY.keys
8 end
9
10 def self.find(key)
11 entry = DICTIONARY[key.to_s] if key.present?
12 return nil unless entry
13 Entry.new(key: key.to_s, url: entry["url"], requires_attribution: entry["requires_attribution"], style: entry["style"])
14 end
15end
diff --git a/app/models/concerns/file_attachment.rb b/app/models/concerns/file_attachment.rb
index 613a6f0..6221312 100644
--- a/app/models/concerns/file_attachment.rb
+++ b/app/models/concerns/file_attachment.rb
@@ -31,6 +31,7 @@ module FileAttachment
31 31
32 IMAGE_CONTENT_TYPES = %w[image/jpeg image/gif image/png image/webp].freeze 32 IMAGE_CONTENT_TYPES = %w[image/jpeg image/gif image/png image/webp].freeze
33 VECTOR_CONTENT_TYPES = %w[image/svg+xml].freeze 33 VECTOR_CONTENT_TYPES = %w[image/svg+xml].freeze
34 RASTERIZED_CONTENT_TYPES = %w[application/pdf].freeze
34 DISPLAYABLE_AS_IMAGE = IMAGE_CONTENT_TYPES + VECTOR_CONTENT_TYPES 35 DISPLAYABLE_AS_IMAGE = IMAGE_CONTENT_TYPES + VECTOR_CONTENT_TYPES
35 36
36 included do 37 included do
@@ -52,29 +53,49 @@ module FileAttachment
52 build_upload_proxy 53 build_upload_proxy
53 end 54 end
54 55
56 def has_variant?(style)
57 upload_file_name.present? && File.exist?(file_path(style))
58 end
59
60 def previewable?
61 has_variant?(:medium)
62 end
63
64 def variant_filename(style)
65 return upload_file_name if style == :original || !RASTERIZED_CONTENT_TYPES.include?(upload_content_type)
66 File.basename(upload_file_name, ".*") + ".png"
67 end
68
55 private 69 private
56 70
57 def build_upload_proxy 71 def build_upload_proxy
58 @upload = UploadProxy.new(self) 72 @upload = UploadProxy.new(self)
59 end 73 end
60 74
75 class_methods do
76 def upload_root
77 Rails.env.test? ? Rails.root.join("tmp", "test_uploads") : Rails.root.join("public", "system", "uploads")
78 end
79 end
80
81 def upload_root
82 self.class.upload_root
83 end
84
61 def process_upload 85 def process_upload
62 return unless @pending_upload 86 return unless @pending_upload
63 uploaded_file = @pending_upload 87 uploaded_file = @pending_upload
64 @pending_upload = nil 88 @pending_upload = nil
65 89
66 old_dir = Rails.root.join("public", "system", "uploads", id.to_s) 90 old_dir = upload_root.join(id.to_s)
91
67 FileUtils.rm_rf(old_dir) if Dir.exist?(old_dir) 92 FileUtils.rm_rf(old_dir) if Dir.exist?(old_dir)
68 93
69 original_path = file_path(:original) 94 original_path = file_path(:original)
70 FileUtils.mkdir_p(File.dirname(original_path)) 95 FileUtils.mkdir_p(File.dirname(original_path))
71 FileUtils.cp(uploaded_file.tempfile.path, original_path) 96 FileUtils.cp(uploaded_file.tempfile.path, original_path)
72 97
73 if IMAGE_CONTENT_TYPES.include?(upload_content_type) 98 generate_all_variants(original_path)
74 generate_variants(original_path)
75 elsif VECTOR_CONTENT_TYPES.include?(upload_content_type)
76 generate_svg_variants(original_path)
77 end
78 end 99 end
79 100
80 def generate_variants(original_path) 101 def generate_variants(original_path)
@@ -93,20 +114,28 @@ module FileAttachment
93 end 114 end
94 end 115 end
95 116
117 def generate_all_variants(original_path)
118 if IMAGE_CONTENT_TYPES.include?(upload_content_type)
119 generate_variants(original_path)
120 elsif VECTOR_CONTENT_TYPES.include?(upload_content_type)
121 generate_svg_variants(original_path)
122 elsif RASTERIZED_CONTENT_TYPES.include?(upload_content_type)
123 generate_variants("#{original_path}[0]")
124 end
125 end
126
96 def delete_upload_files 127 def delete_upload_files
97 dir = Rails.root.join("public", "system", "uploads", id.to_s) 128 dir = upload_root.join(id.to_s)
98 FileUtils.rm_rf(dir) if Dir.exist?(dir) 129 FileUtils.rm_rf(dir) if Dir.exist?(dir)
99 end 130 end
100 131
101 def file_path(style) 132 def file_path(style)
102 Rails.root.join( 133 upload_root.join(id.to_s, style.to_s, variant_filename(style)).to_s
103 "public", "system", "uploads",
104 id.to_s, style.to_s, upload_file_name
105 ).to_s
106 end 134 end
107 135
108 def sanitize_filename(filename) 136 def sanitize_filename(filename)
109 File.basename(filename).gsub(/[^\w\.\-]/, '_') 137 name = File.basename(filename).unicode_normalize(:nfc)
138 name.gsub(/(?u)[^\w\.\-]/, '_')
110 end 139 end
111 140
112 # Proxy object returned by asset.upload, providing the Paperclip-compatible 141 # Proxy object returned by asset.upload, providing the Paperclip-compatible
@@ -118,7 +147,7 @@ module FileAttachment
118 147
119 def url(style = :original) 148 def url(style = :original)
120 return "" if @record.upload_file_name.blank? 149 return "" if @record.upload_file_name.blank?
121 "/system/uploads/#{@record.id}/#{style}/#{@record.upload_file_name}" 150 "/system/uploads/#{@record.id}/#{style}/#{@record.variant_filename(style)}"
122 end 151 end
123 152
124 def content_type 153 def content_type
diff --git a/app/models/node_action.rb b/app/models/node_action.rb
index 13bd5ba..63a99ae 100644
--- a/app/models/node_action.rb
+++ b/app/models/node_action.rb
@@ -166,4 +166,12 @@ class NodeAction < ApplicationRecord
166 def subject_name 166 def subject_name
167 metadata["human_readable_node_name"] || node&.unique_name || "deleted node" 167 metadata["human_readable_node_name"] || node&.unique_name || "deleted node"
168 end 168 end
169
170 def diff_link_params
171 prev = NodeAction.where(node_id: node_id, action: "publish")
172 .where("id < ?", id)
173 .order(id: :desc).first
174 return nil unless prev&.page && page
175 { start_revision: prev.page.revision, end_revision: page.revision }
176 end
169end 177end
diff --git a/app/models/page.rb b/app/models/page.rb
index f33d88d..817e3bd 100644
--- a/app/models/page.rb
+++ b/app/models/page.rb
@@ -220,7 +220,12 @@ class Page < ApplicationRecord
220 end 220 end
221 221
222 # Clone asset references 222 # Clone asset references
223 self.assets = page.assets 223 self.related_assets.delete_all
224 page.related_assets.each do |related|
225 self.related_assets.create!(:asset_id => related.asset_id,
226 :position => related.position,
227 :headline => related.headline)
228 end
224 229
225 self.save 230 self.save
226 end 231 end
@@ -287,6 +292,10 @@ class Page < ApplicationRecord
287 end 292 end
288 end 293 end
289 294
295 def headline_asset
296 related_assets.find_by(headline: true)&.asset
297 end
298
290 # Returns true if a page has translations where one of them is significantly 299 # Returns true if a page has translations where one of them is significantly
291 # older than the other. 300 # older than the other.
292 # Takes the I18n.default locale and a second :locale to test if the 301 # Takes the I18n.default locale and a second :locale to test if the
@@ -314,21 +323,6 @@ class Page < ApplicationRecord
314 end 323 end
315 end 324 end
316 325
317 def update_assets image_ids
318
319 transaction do
320 self.related_assets.delete_all
321
322 if image_ids
323 image_ids.each_with_index do |id, index|
324 asset = Asset.find(id)
325 self.related_assets.create!(:asset_id => asset.id, :position => index+1)
326 end
327 end
328 end
329
330 end
331
332 # Installs (or re-installs) the trigger that keeps page_translations' 326 # Installs (or re-installs) the trigger that keeps page_translations'
333 # search_vector in sync. Idempotent, safe to call on every boot. 327 # search_vector in sync. Idempotent, safe to call on every boot.
334 # search_vector is populated by a raw Postgres trigger, not anything 328 # search_vector is populated by a raw Postgres trigger, not anything
diff --git a/app/models/related_asset.rb b/app/models/related_asset.rb
index 8f16460..8f8d49c 100644
--- a/app/models/related_asset.rb
+++ b/app/models/related_asset.rb
@@ -5,4 +5,13 @@ class RelatedAsset < ApplicationRecord
5 acts_as_list :scope => :page_id 5 acts_as_list :scope => :page_id
6 6
7 default_scope -> { order("position ASC") } 7 default_scope -> { order("position ASC") }
8
9 validate :headline_only_for_images
10
11 private
12
13 def headline_only_for_images
14 return unless asset
15 errors.add(:headline, "can only be set on image or PDF assets") if headline? && !(asset.image? || asset.pdf?)
16 end
8end 17end
diff --git a/app/views/assets/edit.html.erb b/app/views/assets/edit.html.erb
index f198600..3ef8fea 100644
--- a/app/views/assets/edit.html.erb
+++ b/app/views/assets/edit.html.erb
@@ -1,24 +1,44 @@
1<h1>Editing asset</h1> 1<div id="page_editor">
2 <h1>Editing asset</h1>
2 3
3<%= form_for(@asset, html: { multipart: true }) do |f| %> 4 <%= form_for(@asset, html: { multipart: true }) do |f| %>
4 <%= form_error_messages(f) %> 5 <%= form_error_messages(f) %>
5 6
6 <% if @asset.upload.present? %> 7 <div id="content">
7 <p> 8 <div class="node_description"><%= f.label :name %></div>
8 <strong>Current file:</strong> 9 <div class="node_content"><%= f.text_field :name %></div>
9 <%= @asset.upload.url %> 10
10 (<%= number_to_human_size(@asset.upload.size) %>) 11 <% if @asset.upload.present? %>
11 </p> 12 <div class="node_description">Current File</div>
12 <% end %> 13 <div class="node_content"><%= @asset.upload.url %> (<%= number_to_human_size(@asset.upload.size) %>)</div>
14 <% end %>
15
16 <div class="node_description"><%= f.label :upload, "Replace file" %></div>
17 <div class="node_content"><%= f.file_field :upload %></div>
13 18
14 <p> 19 <div class="node_description"><%= f.label :creator %></div>
15 <label>Replace file:</label><br> 20 <div class="node_content"><%= f.text_field :creator %></div>
16 <%= f.file_field :upload %>
17 </p>
18 21
19 <p> 22 <div class="node_description"><%= f.label :source_url, "Source URL" %></div>
20 <%= f.submit 'Update' %> 23 <div class="node_content"><%= f.text_field :source_url %></div>
21 </p>
22<% end %>
23 24
24<%= link_to 'Show', @asset %> | <%= link_to 'Back', assets_path %> 25 <div class="node_description"><%= f.label :license_key, "License" %></div>
26 <div class="node_content">
27 <%= f.select :license_key,
28 options_for_select(
29 [["— none —", ""]] + AssetLicense.keys.map { |key| [t("asset_licenses.#{key}"), key] },
30 @asset.license_key
31 ) %>
32 </div>
33
34 <div class="node_description">Actions</div>
35 <div class="node_content node_info_group">
36 <div class="node_info_group_items">
37 <div class="node_info_item"><%= f.submit 'Update' %></div>
38 <div class="node_info_item"><%= link_to 'Show', @asset %></div>
39 <div class="node_info_item"><%= link_to 'Back', assets_path %></div>
40 </div>
41 </div>
42 </div>
43 <% end %>
44</div>
diff --git a/app/views/assets/index.html.erb b/app/views/assets/index.html.erb
index 8c35561..ff1300a 100644
--- a/app/views/assets/index.html.erb
+++ b/app/views/assets/index.html.erb
@@ -17,7 +17,7 @@
17 </tr> 17 </tr>
18<% @assets.each do |asset| %> 18<% @assets.each do |asset| %>
19 <tr> 19 <tr>
20 <td><%= image_tag asset.upload.url(:thumb), style: "max-width: 100px; max-height: 100px;" %></td> 20 <td><% if asset.has_variant?(:thumb) %><%= image_tag asset.upload.url(:thumb), style: "max-width: 100px; max-height: 100px;" %><% end %></td>
21 <td><%= link_to asset.name, asset.upload.url %></td> 21 <td><%= link_to asset.name, asset.upload.url %></td>
22 <td><%= asset.upload.content_type %></td> 22 <td><%= asset.upload.content_type %></td>
23 <td><%= link_to 'Show', asset %></td> 23 <td><%= link_to 'Show', asset %></td>
diff --git a/app/views/assets/new.html.erb b/app/views/assets/new.html.erb
index 6c1310a..2cf8865 100644
--- a/app/views/assets/new.html.erb
+++ b/app/views/assets/new.html.erb
@@ -1,17 +1,38 @@
1<h1>New asset</h1> 1<div id="page_editor">
2 <h1>New asset</h1>
2 3
3<%= form_for(@asset, :html => { :multipart => true }) do |f| %> 4 <%= form_for(@asset, :html => { :multipart => true }) do |f| %>
4 <%= form_error_messages(f) %> 5 <%= form_error_messages(f) %>
5
6 <p>
7 <%= f.label :name %><br />
8 <%= f.text_field :name %>
9 </p>
10 6
11 <p> 7 <div id="content">
12 <%= f.file_field :upload %> 8 <div class="node_description"><%= f.label :name %></div>
13 <%= f.submit 'Create' %> 9 <div class="node_content"><%= f.text_field :name %></div>
14 </p>
15<% end %>
16 10
17<%= link_to 'Back', assets_path %> 11 <div class="node_description"><%= f.label :upload, "File" %></div>
12 <div class="node_content"><%= f.file_field :upload %></div>
13
14 <div class="node_description"><%= f.label :creator %></div>
15 <div class="node_content"><%= f.text_field :creator %></div>
16
17 <div class="node_description"><%= f.label :source_url, "Source URL" %></div>
18 <div class="node_content"><%= f.text_field :source_url %></div>
19
20 <div class="node_description"><%= f.label :license_key, "License" %></div>
21 <div class="node_content">
22 <%= f.select :license_key,
23 options_for_select(
24 [["— none —", ""]] + AssetLicense.keys.map { |key| [t("asset_licenses.#{key}"), key] },
25 @asset.license_key
26 ) %>
27 </div>
28
29 <div class="node_description">Actions</div>
30 <div class="node_content node_info_group">
31 <div class="node_info_group_items">
32 <div class="node_info_item"><%= f.submit 'Create' %></div>
33 <div class="node_info_item"><%= link_to 'Back', assets_path %></div>
34 </div>
35 </div>
36 </div>
37 <% end %>
38</div>
diff --git a/app/views/assets/show.html.erb b/app/views/assets/show.html.erb
index 5717dd9..e551e35 100644
--- a/app/views/assets/show.html.erb
+++ b/app/views/assets/show.html.erb
@@ -16,11 +16,42 @@
16 </div> 16 </div>
17 </div> 17 </div>
18 18
19 <div class="node_description">Thumbnail</div> 19 <% if @asset.has_variant?(:medium) %>
20 <div class="node_content"><%= image_tag @asset.upload.url(:medium), style: "max-width: 300px; max-height: 300px;" %></div> 20 <div class="node_description">Thumbnail</div>
21 <div class="node_content"><%= image_tag @asset.upload.url(:medium), style: "max-width: 300px; max-height: 300px;" %></div>
22 <% end %>
21 23
22 <div class="node_description">Public Path</div> 24 <div class="node_description">Public Path</div>
23 <div class="node_content"><%= @asset.upload.url.sub(/\?\d+$/, "") %></div> 25 <div class="node_content">
26 <% public_path = @asset.upload.url.sub(/\?\d+$/, "") %>
27 <%= link_to public_path, public_path, target: "_blank", rel: "noopener" %>
28 <button type="button" class="action_button copy_button"
29 data-copy-url="<%= request.base_url + public_path %>">
30 <%= icon("copy", library: "tabler", "aria-hidden": true) %>
31 <span class="copy_button_label">Copy URL</span>
32 </button>
33 </div>
34
35 <div class="node_description">Creator</div>
36 <div class="node_content"><%= @asset.creator.presence || "—" %></div>
37
38 <div class="node_description">Source</div>
39 <div class="node_content">
40 <% if @asset.source_url.present? %>
41 <%= link_to @asset.source_url, @asset.source_url %>
42 <% else %>
43
44 <% end %>
45 </div>
46
47 <div class="node_description">License</div>
48 <div class="node_content">
49 <% if (license = AssetLicense.find(@asset.license_key)) %>
50 <%= t("asset_licenses.#{license.key}") %>
51 <% else %>
52
53 <% end %>
54 </div>
24 55
25 <div class="node_description">Content Type</div> 56 <div class="node_description">Content Type</div>
26 <div class="node_content"><%= @asset.upload.content_type %></div> 57 <div class="node_content"><%= @asset.upload.content_type %></div>
diff --git a/app/views/content/_asset_credits.html.erb b/app/views/content/_asset_credits.html.erb
new file mode 100644
index 0000000..5959abd
--- /dev/null
+++ b/app/views/content/_asset_credits.html.erb
@@ -0,0 +1,10 @@
1<% if @page.assets.images.any? %>
2 <div id="asset_credits">
3 <% @page.assets.images.each do |image| %>
4 <div id="credit_for_asset_<%= image.id %>"
5 class="asset_credit_block<%= " headline_credit" if image == @headline_asset %>">
6 <%= asset_credit(image) %>
7 </div>
8 <% end %>
9 </div>
10<% end %>
diff --git a/app/views/content/_headline_image.html.erb b/app/views/content/_headline_image.html.erb
index 243be40..4a3dfdc 100644
--- a/app/views/content/_headline_image.html.erb
+++ b/app/views/content/_headline_image.html.erb
@@ -1,16 +1,59 @@
1<%= link_to( 1<% gallery_images = @page.assets.images %>
2 image_tag(@images[0].upload.url(:headline)), 2<% image_headline = @headline_asset if @headline_asset&.image? %>
3 @images[0].upload.url, 3<% other_pdfs = @page.assets.pdfs.where.not(id: @headline_asset&.id) %>
4 :class => "glightbox",
5 :data => { :gallery => "page-#{@page.node.id}" }
6) %>
7 4
8<% if 1 < @images.length %> 5<% if @headline_asset&.pdf? %>
9 <div class="right"> 6 <%= link_to @headline_asset.upload.url, :class => "headline_document_card", :target => "_blank", :rel => "noopener" do %>
10 <%= "#{@images.length} #{t(:images)}" %> 7 <%= image_tag @headline_asset.upload.url(:medium), :alt => "", :class => "headline_document_card_thumb" %>
11 </div> 8 <div class="headline_document_card_info">
9 <%= icon("file-text", library: "tabler", "aria-hidden": true) %>
10 <span class="headline_document_card_title"><%= @headline_asset.name %></span>
11 </div>
12 <% end %>
13<% end %>
14
15<% if image_headline %>
16 <%= link_to(
17 image_tag(image_headline.upload.url(:headline)),
18 image_headline.upload.url,
19 :class => "glightbox",
20 :data => { :gallery => "page-#{@page.node.id}",
21 :glightbox => glightbox_data(image_headline, image_headline.name),
22 :"credit-selector" => (image_headline.show_credit? ? "#credit_for_asset_#{image_headline.id}" : nil) }
23 ) %>
24 <% if gallery_images.size > 1 %>
25 <div class="right"><%= "#{gallery_images.size} #{t(:images)}" %></div>
26 <% end %>
27<% elsif gallery_images.any? %>
28 <%= link_to "#{gallery_images.size} #{t(:images)}, #{t(:open_gallery)}",
29 gallery_images.first.upload.url,
30 :class => "glightbox right",
31 :data => { :gallery => "page-#{@page.node.id}",
32 :glightbox => glightbox_data(gallery_images.first, gallery_images.first.name),
33 :"credit-selector" => (gallery_images.first.show_credit? ? "#credit_for_asset_#{gallery_images.first.id}" : nil) } %>
12<% end %> 34<% end %>
13 35
14<% @images[1..-1].each do |image| %> 36<% gallery_images.each do |image| %>
15 <%= link_to "", image.upload.url, :class => "glightbox", :style => "display: none", :data => { :gallery => "page-#{@page.node.id}" } %> 37 <% next if image == image_headline %>
38 <% next if !image_headline && image == gallery_images.first %>
39 <%= link_to "", image.upload.url, :class => "glightbox", :style => "display: none",
40 :data => { :gallery => "page-#{@page.node.id}",
41 :glightbox => glightbox_data(image, image.name),
42 :"credit-selector" => (image.show_credit? ? "#credit_for_asset_#{image.id}" : nil) } %>
43<% end %>
44
45<% if other_pdfs.any? %>
46 <div class="related_documents">
47 <div class="related_documents_label"><%= t(:related_documents) %></div>
48 <ul class="related_documents_list">
49 <% other_pdfs.each do |pdf| %>
50 <li>
51 <%= link_to pdf.upload.url, :class => "related_document_link", :target => "_blank", :rel => "noopener" do %>
52 <%= icon("file-text", library: "tabler", "aria-hidden": true) %>
53 <span><%= pdf.name %></span>
54 <% end %>
55 </li>
56 <% end %>
57 </ul>
58 </div>
16<% end %> 59<% end %>
diff --git a/app/views/custom/page_templates/public/chapter_detail.html.erb b/app/views/custom/page_templates/public/chapter_detail.html.erb
index c4c2598..18449b8 100644
--- a/app/views/custom/page_templates/public/chapter_detail.html.erb
+++ b/app/views/custom/page_templates/public/chapter_detail.html.erb
@@ -31,4 +31,5 @@
31 </ul> 31 </ul>
32 </div> 32 </div>
33 <% end %> 33 <% end %>
34 <%= render partial: 'content/asset_credits' %>
34</div> 35</div>
diff --git a/app/views/custom/page_templates/public/no_date_and_author.html.erb b/app/views/custom/page_templates/public/no_date_and_author.html.erb
index fa39e54..ec7e850 100644
--- a/app/views/custom/page_templates/public/no_date_and_author.html.erb
+++ b/app/views/custom/page_templates/public/no_date_and_author.html.erb
@@ -3,6 +3,7 @@
3 <p><%= sanitize( @page.abstract ) %></p> 3 <p><%= sanitize( @page.abstract ) %></p>
4 <div id="headline_image"><%= headline_image %></div> 4 <div id="headline_image"><%= headline_image %></div>
5 <%= aggregate?(@page.body) %> 5 <%= aggregate?(@page.body) %>
6 <%= render partial: 'content/asset_credits' %>
6</div> 7</div>
7 8
8<%= will_paginate(@content_collection) if @content_collection %> 9<%= will_paginate(@content_collection) if @content_collection %>
diff --git a/app/views/custom/page_templates/public/no_title_abstract_date_and_author.html.erb b/app/views/custom/page_templates/public/no_title_abstract_date_and_author.html.erb
index 64e2a7c..c486684 100644
--- a/app/views/custom/page_templates/public/no_title_abstract_date_and_author.html.erb
+++ b/app/views/custom/page_templates/public/no_title_abstract_date_and_author.html.erb
@@ -1,6 +1,7 @@
1<div class="article" lang="<%= @page.effective_lang %>"> 1<div class="article" lang="<%= @page.effective_lang %>">
2 <div id="headline_image"><%= headline_image %></div> 2 <div id="headline_image"><%= headline_image %></div>
3 <%= aggregate?(@page.body) %> 3 <%= aggregate?(@page.body) %>
4 <%= render partial: 'content/asset_credits' %>
4</div> 5</div>
5 6
6<%= will_paginate(@content_collection) if @content_collection %> 7<%= will_paginate(@content_collection) if @content_collection %>
diff --git a/app/views/custom/page_templates/public/standard_template.html.erb b/app/views/custom/page_templates/public/standard_template.html.erb
index bbc8cd0..e1e2eb0 100644
--- a/app/views/custom/page_templates/public/standard_template.html.erb
+++ b/app/views/custom/page_templates/public/standard_template.html.erb
@@ -3,6 +3,7 @@
3 <div class="abstract"><p><%= sanitize( @page.abstract )%></p></div> 3 <div class="abstract"><p><%= sanitize( @page.abstract )%></p></div>
4 <div id="headline_image"><%= headline_image %></div> 4 <div id="headline_image"><%= headline_image %></div>
5 <div class="body"><%= aggregate?(@page.body) %></div> 5 <div class="body"><%= aggregate?(@page.body) %></div>
6 <%= render partial: 'content/asset_credits' %>
6</div> 7</div>
7 8
8<%= will_paginate(@content_collection) if @content_collection %> 9<%= will_paginate(@content_collection) if @content_collection %>
diff --git a/app/views/custom/page_templates/public/title_only.html.erb b/app/views/custom/page_templates/public/title_only.html.erb
index 42fe9a6..d706e9d 100644
--- a/app/views/custom/page_templates/public/title_only.html.erb
+++ b/app/views/custom/page_templates/public/title_only.html.erb
@@ -2,6 +2,7 @@
2 <h2 class="headline"><%= @page.title %></h2> 2 <h2 class="headline"><%= @page.title %></h2>
3 <div id="headline_image"><%= headline_image %></div> 3 <div id="headline_image"><%= headline_image %></div>
4 <%= aggregate?(@page.body) %> 4 <%= aggregate?(@page.body) %>
5 <%= render partial: 'content/asset_credits' %>
5</div> 6</div>
6 7
7<%= will_paginate(@content_collection) if @content_collection %> 8<%= will_paginate(@content_collection) if @content_collection %>
diff --git a/app/views/custom/page_templates/public/update.html.erb b/app/views/custom/page_templates/public/update.html.erb
index d5995ac..0ff1bb2 100644
--- a/app/views/custom/page_templates/public/update.html.erb
+++ b/app/views/custom/page_templates/public/update.html.erb
@@ -4,6 +4,7 @@
4 <div class="abstract"><p><%= sanitize( @page.abstract )%></p></div> 4 <div class="abstract"><p><%= sanitize( @page.abstract )%></p></div>
5 <div id="headline_image"><%= headline_image %></div> 5 <div id="headline_image"><%= headline_image %></div>
6 <div class="body"><%= aggregate?(@page.body) %></div> 6 <div class="body"><%= aggregate?(@page.body) %></div>
7 <%= render partial: 'content/asset_credits' %>
7</div> 8</div>
8 9
9<%= will_paginate(@content_collection) if @content_collection %> 10<%= will_paginate(@content_collection) if @content_collection %>
diff --git a/app/views/custom/partials/_chapter.html.erb b/app/views/custom/partials/_chapter.html.erb
index 47f3950..5d47679 100644
--- a/app/views/custom/partials/_chapter.html.erb
+++ b/app/views/custom/partials/_chapter.html.erb
@@ -1,7 +1,7 @@
1<div class="article_partial chapter_partial" lang="<%= page.effective_lang %>"> 1<div class="article_partial chapter_partial" lang="<%= page.effective_lang %>">
2 <div class="chapter_partial_layout"> 2 <div class="chapter_partial_layout">
3 <% if page.assets.images.any? %> 3 <% if page.headline_asset %>
4 <%= link_to_path image_tag(page.assets.images.first.upload.url(:thumb), :alt => ""), page.node.unique_name, class: "chapter_thumbnail" %> 4 <%= link_to_path image_tag(page.headline_asset.upload.url(:thumb), :alt => ""), page.node.unique_name, class: "chapter_thumbnail" %>
5 <% end %> 5 <% end %>
6 <div class="chapter_partial_content"> 6 <div class="chapter_partial_content">
7 <h2 class="headline"><%= link_to_path page.title, page.node.unique_name %></h2> 7 <h2 class="headline"><%= link_to_path page.title, page.node.unique_name %></h2>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 352fb66..f981fc1 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -10,7 +10,7 @@
10 <meta property="og:description" content="Der Chaos Computer Club ist eine galaktische Gemeinschaft von Lebewesen für Informationsfreiheit und Technikfolgenabschätzung." /> 10 <meta property="og:description" content="Der Chaos Computer Club ist eine galaktische Gemeinschaft von Lebewesen für Informationsfreiheit und Technikfolgenabschätzung." />
11 11
12 <title><%= page_title %></title> 12 <title><%= page_title %></title>
13 <link rel="stylesheet" href="<%= mtime_busted_path('/stylesheets/ccc_new.css') %>"> 13 <link rel="stylesheet" href="<%= mtime_busted_path('/stylesheets/ccc.css') %>">
14 <link rel="stylesheet" href="<%= mtime_busted_path('/stylesheets/glightbox.min.css') %>"> 14 <link rel="stylesheet" href="<%= mtime_busted_path('/stylesheets/glightbox.min.css') %>">
15 <script src="<%= mtime_busted_path('/javascripts/glightbox.min.js') %>"></script> 15 <script src="<%= mtime_busted_path('/javascripts/glightbox.min.js') %>"></script>
16 <script src="<%= mtime_busted_path('/javascripts/public.js') %>"></script> 16 <script src="<%= mtime_busted_path('/javascripts/public.js') %>"></script>
diff --git a/app/views/layouts/events.html.erb b/app/views/layouts/events.html.erb
deleted file mode 100644
index 93a6c0c..0000000
--- a/app/views/layouts/events.html.erb
+++ /dev/null
@@ -1,17 +0,0 @@
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
4<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5<head>
6 <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
7 <title>Events: <%= controller.action_name %></title>
8 <%= stylesheet_link_tag 'scaffold' %>
9</head>
10<body>
11
12<p style="color: green"><%= flash[:notice] %></p>
13
14<%= yield %>
15
16</body>
17</html>
diff --git a/app/views/node_actions/_change_details.html.erb b/app/views/node_actions/_change_details.html.erb
index 2583e8b..066d0f3 100644
--- a/app/views/node_actions/_change_details.html.erb
+++ b/app/views/node_actions/_change_details.html.erb
@@ -5,9 +5,13 @@
5 <tr> 5 <tr>
6 <th><%= I18n.default_locale.to_s.upcase %></th> 6 <th><%= I18n.default_locale.to_s.upcase %></th>
7 <td> 7 <td>
8 <%= safe_join(default_items, ", ") %> 8 <%= safe_join(default_items, tag.br) %>
9 <% if action_entry.page && action_entry.node %> 9 <% if action_entry.page && action_entry.node %>
10 <%= link_to t("node_actions.view_revision"), node_revision_path(action_entry.node, action_entry.page) %> 10 <% if (diff_params = action_entry.diff_link_params) %>
11 <%= link_to t("node_actions.view_diff"), diff_node_revisions_path(action_entry.node, diff_params) %>
12 <% else %>
13 <%= link_to t("node_actions.view_revision"), node_revision_path(action_entry.node, action_entry.page) %>
14 <% end %>
11 <% end %> 15 <% end %>
12 </td> 16 </td>
13 </tr> 17 </tr>
@@ -16,9 +20,13 @@
16 <tr> 20 <tr>
17 <th><%= locale.upcase %></th> 21 <th><%= locale.upcase %></th>
18 <td> 22 <td>
19 <%= safe_join(translation_changes(diff), ", ") %> 23 <%= safe_join(translation_changes(diff), tag.br) %>
20 <% if action_entry.page && action_entry.node %> 24 <% if action_entry.page && action_entry.node %>
21 <%= link_to t("node_actions.view_revision"), node_revision_path(action_entry.node, action_entry.page, :locale => locale) %> 25 <% if (diff_params = action_entry.diff_link_params) %>
26 <%= link_to t("node_actions.view_diff"), diff_node_revisions_path(action_entry.node, diff_params.merge(:locale => locale)) %>
27 <% else %>
28 <%= link_to t("node_actions.view_revision"), node_revision_path(action_entry.node, action_entry.page, :locale => locale) %>
29 <% end %>
22 <% end %> 30 <% end %>
23 </td> 31 </td>
24 </tr> 32 </tr>
diff --git a/app/views/nodes/edit.html.erb b/app/views/nodes/edit.html.erb
index 25f15c2..12dc000 100644
--- a/app/views/nodes/edit.html.erb
+++ b/app/views/nodes/edit.html.erb
@@ -82,17 +82,27 @@
82 <ul id="related_asset_list" class="thumbnail_list"> 82 <ul id="related_asset_list" class="thumbnail_list">
83 <% @page.related_assets.includes(:asset).each do |related| %> 83 <% @page.related_assets.includes(:asset).each do |related| %>
84 <li data-url="<%= node_related_asset_path(@node, related) %>" 84 <li data-url="<%= node_related_asset_path(@node, related) %>"
85 data-asset-id="<%= related.asset.id %>"
85 data-large-url="<%= related.asset.upload.url(:large) %>" 86 data-large-url="<%= related.asset.upload.url(:large) %>"
86 data-original-url="<%= related.asset.upload.url %>" 87 data-original-url="<%= related.asset.upload.url %>"
87 data-name="<%= related.asset.name %>"> 88 data-name="<%= related.asset.name %>"
89 data-has-credit="<%= related.asset.show_credit? %>"
90 data-headline="<%= related.headline? %>"
91 class="<%= "is_headline" if related.headline? %>">
88 <span class="related_asset_handle"><%= icon("grip-vertical", library: "tabler", "aria-hidden": true) %></span> 92 <span class="related_asset_handle"><%= icon("grip-vertical", library: "tabler", "aria-hidden": true) %></span>
89 <%= image_tag related.asset.upload.url(:thumb) %> 93 <%= image_tag related.asset.upload.url(:thumb) %>
94 <button type="button" class="related_asset_set_headline"
95 aria-pressed="<%= related.headline? %>" aria-label="Set as headline image"
96 title="Use this photo as the page's headline image">
97 <%= icon("star", library: "tabler", "aria-hidden": true) %>
98 </button>
90 <button type="button" class="related_asset_remove" aria-label="Remove image"> 99 <button type="button" class="related_asset_remove" aria-label="Remove image">
91 <%= icon("x", library: "tabler", "aria-hidden": true) %> 100 <%= icon("x", library: "tabler", "aria-hidden": true) %>
92 </button> 101 </button>
93 </li> 102 </li>
94 <% end %> 103 <% end %>
95 </ul> 104 </ul>
105 <p class="field_hint">The starred image or PDF becomes this page's headline on the public site. If none is starred, visitors get a link to browse all attached images instead.</p>
96 <%= text_field_tag nil, nil, id: "related_asset_search_term", placeholder: "Search images to attach…", autocomplete: "off" %> 106 <%= text_field_tag nil, nil, id: "related_asset_search_term", placeholder: "Search images to attach…", autocomplete: "off" %>
97 <div id="related_asset_search_results" class="search_results"></div> 107 <div id="related_asset_search_results" class="search_results"></div>
98 </div> 108 </div>
@@ -102,6 +112,9 @@
102 <li> 112 <li>
103 <span class="related_asset_handle"><%= icon("grip-vertical", library: "tabler", "aria-hidden": true) %></span> 113 <span class="related_asset_handle"><%= icon("grip-vertical", library: "tabler", "aria-hidden": true) %></span>
104 <img src=""> 114 <img src="">
115 <button type="button" class="related_asset_set_headline" aria-pressed="false" aria-label="Set as headline image">
116 <%= icon("star", library: "tabler", "aria-hidden": true) %>
117 </button>
105 <button type="button" class="related_asset_remove" aria-label="Remove image"> 118 <button type="button" class="related_asset_remove" aria-label="Remove image">
106 <%= icon("x", library: "tabler", "aria-hidden": true) %> 119 <%= icon("x", library: "tabler", "aria-hidden": true) %>
107 </button> 120 </button>
diff --git a/app/views/nodes/new.html.erb b/app/views/nodes/new.html.erb
index 890d46e..805fbc9 100644
--- a/app/views/nodes/new.html.erb
+++ b/app/views/nodes/new.html.erb
@@ -41,7 +41,10 @@
41 <div class="node_description">Resulting path</div> 41 <div class="node_description">Resulting path</div>
42 <div class="node_content"> 42 <div class="node_content">
43 <span id="resulting_path">—</span> 43 <span id="resulting_path">—</span>
44 <button type="button" id="copy_resulting_path" class="unselected">copy</button> 44 <button type="button" id="copy_resulting_path" class="unselected copy_button" data-copy-target="#resulting_path">
45 <span class="copy_button_label">copy</span>
46 </button>
47
45 <span class="field_hint">This preview updates as you type. The final path can still be changed afterward by editing the title (for the last segment) or moving the node to a different parent (for everything before it).</span> 48 <span class="field_hint">This preview updates as you type. The final path can still be changed afterward by editing the title (for the last segment) or moving the node to a different parent (for everything before it).</span>
46 </div> 49 </div>
47 50
diff --git a/app/views/nodes/show.html.erb b/app/views/nodes/show.html.erb
index af05778..adf79dd 100644
--- a/app/views/nodes/show.html.erb
+++ b/app/views/nodes/show.html.erb
@@ -253,12 +253,20 @@
253 <% end %> 253 <% end %>
254 </div> 254 </div>
255 255
256 <% if @page.assets.images.any? %> 256 <% if @page.assets.any? %>
257 <% headline_asset_id = @page.headline_asset&.id %>
257 <div class="node_description">Images</div> 258 <div class="node_description">Images</div>
258 <div class="node_content node_info_group"> 259 <div class="node_content node_info_group">
259 <ul class="thumbnail_list"> 260 <ul class="thumbnail_list">
260 <% @page.assets.images.each do |asset| %> 261 <% @page.assets.each do |asset| %>
261 <li><%= link_to image_tag(asset.upload.url(:thumb)), asset_path(asset) %></li> 262 <li class="<%= "is_headline" if asset.id == headline_asset_id %>">
263 <%= link_to image_tag(asset.upload.url(:thumb)), asset_path(asset) %>
264 <% if asset.id == headline_asset_id %>
265 <span class="headline_indicator" title="This page's headline image">
266 <%= icon("star", library: "tabler", "aria-hidden": true) %>
267 </span>
268 <% end %>
269 </li>
262 <% end %> 270 <% end %>
263 </ul> 271 </ul>
264 </div> 272 </div>