Skip to content
Merged
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
4 changes: 2 additions & 2 deletions app/controllers/resources_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def create
end

if success
redirect_to resources_path
redirect_to resource_path(@resource)
else
@resource = @resource.decorate
set_form_variables
Expand All @@ -106,7 +106,7 @@ def update

if success
flash[:notice] = "Resource updated."
redirect_to resources_path
redirect_to resource_path(@resource)
else
set_form_variables
flash[:alert] = "Failed to update Resource."
Expand Down
97 changes: 88 additions & 9 deletions app/frontend/javascript/controllers/file_preview_controller.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,109 @@
import { Controller } from "@hotwired/stimulus"

const FILE_TYPE_ICONS = {
"application/pdf": "fa-regular fa-file-pdf",
"application/zip": "fa-regular fa-file-zipper",
"application/msword": "fa-regular fa-file-word",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "fa-regular fa-file-word",
"application/vnd.oasis.opendocument.text": "fa-regular fa-file-lines",
"text/html": "fa-regular fa-file-code",
}

export default class extends Controller {
static targets = ["input", "preview", "placeholder", "filename"]

connect() {
this.handleUploadError = this.onUploadError.bind(this)
this.handleUploadEnd = this.onUploadEnd.bind(this)
this.element.addEventListener("direct-upload:error", this.handleUploadError)
this.element.addEventListener("direct-upload:end", this.handleUploadEnd)
}

disconnect() {
this.element.removeEventListener("direct-upload:error", this.handleUploadError)
this.element.removeEventListener("direct-upload:end", this.handleUploadEnd)
}

Copy link
Collaborator Author

@maebeale maebeale Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

show an icon as in a form on save

update(event) {
const file = event.target.files[0]
if (!file) return

this.clearError()

// Update filename
if (this.hasFilenameTarget) {
this.filenameTarget.textContent = file.name
}

// Update preview image (if you have one)
if (this.hasPreviewTarget) {
const reader = new FileReader()
reader.onload = e => {
this.previewTarget.src = e.target.result
this.previewTarget.classList.remove("hidden")
}
reader.readAsDataURL(file)
if (file.type.startsWith("image/")) {
this.showImagePreview(file)
} else {
this.showFileIcon(file.type)
}
}

// Ensure a placeholder div exists (server may not render one when a persisted file exists)
ensurePlaceholder() {
if (this.hasPlaceholderTarget) return this.placeholderTarget

const wrapper = this.hasPreviewTarget
? this.previewTarget.parentElement
: this.element.querySelector(".flex.flex-col")

const placeholder = document.createElement("div")
placeholder.dataset.filePreviewTarget = "placeholder"
placeholder.className = "absolute inset-0 flex items-center justify-center border border-gray-300 shadow-sm bg-gray-50 text-gray-400 text-6xl"
wrapper.appendChild(placeholder)
return placeholder
}

showImagePreview(file) {
if (!this.hasPreviewTarget) return

const reader = new FileReader()
reader.onload = e => {
this.previewTarget.src = e.target.result
this.previewTarget.classList.remove("hidden")
}
reader.readAsDataURL(file)

// Hide placeholder if present
if (this.hasPlaceholderTarget) {
this.placeholderTarget.classList.add("hidden")
}
}

showFileIcon(mimeType) {
const placeholder = this.ensurePlaceholder()
const iconClass = FILE_TYPE_ICONS[mimeType] || "fa-regular fa-file"
placeholder.innerHTML = `<i class="${iconClass} text-5xl"></i>`
placeholder.classList.remove("hidden")

if (this.hasPreviewTarget) {
this.previewTarget.classList.add("hidden")
}
}

onUploadError(event) {
event.preventDefault()
const { error } = event.detail
this.showError(`Upload failed: ${error}`)
}

onUploadEnd(event) {
this.clearError()
}

showError(message) {
this.clearError()
const errorEl = document.createElement("p")
errorEl.className = "text-red-500 text-sm mt-1"
errorEl.dataset.filePreviewTarget = "error"
errorEl.textContent = message
this.element.appendChild(errorEl)
}

clearError() {
const existing = this.element.querySelector('[data-file-preview-target="error"]')
if (existing) existing.remove()
}
}
36 changes: 28 additions & 8 deletions app/models/asset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ class Asset < ApplicationRecord
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
"image/heic",
"image/heif",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add new filetypes

"application/pdf",
"application/zip",
"application/msword", # Word .doc
Expand Down Expand Up @@ -39,6 +42,29 @@ def self.allowed_types_for_owner(owner)
end
end

CONTENT_TYPE_LABELS = {
"image/jpeg" => "JPG",
"image/png" => "PNG",
"image/gif" => "GIF",
"image/webp" => "WebP",
"image/heic" => "HEIC",
"image/heif" => "HEIF",
"application/pdf" => "PDF",
"application/zip" => "ZIP",
"application/msword" => "DOC",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document" => "DOCX",
"application/vnd.oasis.opendocument.text" => "ODT",
"text/html" => "HTML"
}.freeze

def self.accept_attribute
self::ACCEPTED_CONTENT_TYPES.join(",")
end

def self.accepted_types_label
self::ACCEPTED_CONTENT_TYPES.map { |ct| CONTENT_TYPE_LABELS[ct] || ct }.join(", ")
end

belongs_to :owner, polymorphic: true, optional: true, touch: true
belongs_to :report, optional: true

Expand All @@ -55,16 +81,10 @@ def self.allowed_types_for_owner(owner)
def file_type
return unless file.attached?

allowed_types =
case type
when "PrimaryAsset"
PrimaryAsset::ACCEPTED_CONTENT_TYPES
else
ACCEPTED_CONTENT_TYPES
end
allowed_types = self.class::ACCEPTED_CONTENT_TYPES

unless allowed_types.include?(file.content_type)
errors.add(:file, "type is not allowed for #{type.underscore.humanize}")
errors.add(:file, "type not accepted")
end
end
end
3 changes: 3 additions & 0 deletions app/models/downloadable_asset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ class DownloadableAsset < Asset
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
"image/heic",
"image/heif",
"application/pdf",
"application/zip",
"application/msword", # Word .doc
Expand Down
8 changes: 7 additions & 1 deletion app/models/primary_asset.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
class PrimaryAsset < Asset
ACCEPTED_CONTENT_TYPES = [ "image/jpeg", "image/png" ].freeze
ACCEPTED_CONTENT_TYPES = [
"image/jpeg",
"image/png",
"image/webp",
"image/heic",
"image/heif"
].freeze
end
3 changes: 3 additions & 0 deletions app/models/rich_text_asset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ class RichTextAsset < Asset
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
"image/heic",
"image/heif",
"application/pdf",
"application/zip",
"application/msword", # Word .doc
Expand Down
4 changes: 2 additions & 2 deletions app/views/assets/_display_assets.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="assets">
<%= render "assets/display_image", resource: resource, file: (defined?(file) ? file : nil), variant: (defined?(variant) ? variant : :hero) %>
<%= render "assets/display_gallery_media", resource: resource %>
<%= render "assets/display_image", resource: resource, file: (defined?(file) ? file : nil), variant: (defined?(variant) ? variant : :hero), link: (defined?(link) ? link : nil) %>
<%= render "assets/display_gallery_media", resource: resource, link: (defined?(link) ? link : nil) %>
</div>
2 changes: 1 addition & 1 deletion app/views/assets/_display_gallery_media.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="<%= resource.class.table_name %>-gallery text-center mb-4">
<div class="flex flex-wrap justify-center gap-4 mx-auto max-w-4xl">
<% gallery_assets.each_with_index do |gallery_assets, idx| %>
<%= render "assets/display_image", item: gallery_assets, idx: idx, variant: :gallery %>
<%= render "assets/display_image", item: gallery_assets, idx: idx, variant: :gallery, link: (defined?(link) ? link : nil) %>
<% end %>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/views/assets/_display_image.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<% file ||= item&.file || resource&.try(field_name)&.file || file %>
<% variant ||= :gallery %>
<% link_to_object ||= false %>
<% link ||= link_to_object %>
<% link = (defined?(link) && !link.nil?) ? link : link_to_object %>
<% display_open_pdf_link ||= false %>
<% idx ||= 0 %>
<% item_type ||= "PrimaryAsset" %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/community_news/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</div>

<!-- Assets -->
<%= render "assets/display_assets", resource: @community_news %>
<%= render "assets/display_assets", resource: @community_news, link: true %>

<!-- community_news Body Text -->
<div class="mb-10">
Expand Down
2 changes: 1 addition & 1 deletion app/views/events/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
<!-- Right Column -->
<div class="md:col-span-2 space-y-6">
<!-- Assets -->
<%= render "assets/display_assets", resource: @event %>
<%= render "assets/display_assets", resource: @event, link: true %>
</div>
</div>
<!-- FULL-WIDTH DESCRIPTION -->
Expand Down
7 changes: 6 additions & 1 deletion app/views/resources/_resource_card.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@
<% end %>
</div>
<!-- ACTION BUTTONS -->
<div class="absolute bottom-3 right-3 flex items-center space-x-2 z-30">
<div class="absolute bottom-3 left-4 right-3 flex items-center z-30">
<% if allowed_to?(:edit?, resource) %>
<p class="admin-only text-[10px] text-blue-600 bg-blue-50 rounded px-1.5 py-0.5 whitespace-nowrap"><%= resource.created_at.strftime("%b %-d, %Y") %></p>
<% end %>
<div class="ml-auto flex items-center space-x-2">
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

show created at in admin styling on resource card (this pr is wrt resource editing, mainly and this was a stakeholder request)

<% if resource.downloadable_asset&.file&.attached? %>
<%= link_to(
content_tag(:span, "", class: "fa fa-download"),
Expand All @@ -69,6 +73,7 @@
data: {turbo_frame: "_top", turbo_prefetch: false }
) %>
<% end %>
</div>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/views/resources/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
</div>
<!-- Assets -->
<div id="hero-image">
<%= render "assets/display_assets", resource: @resource, file: @resource.display_image, variant: :hero %>
<%= render "assets/display_assets", resource: @resource, file: @resource.display_image, variant: :hero, link: true %>
</div>
</div>
<!-- Mentions -->
Expand Down
16 changes: 15 additions & 1 deletion app/views/shared/_form_image_field.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
<% use_profile_placeholder ||= false %>
<% show_file_field = show_file_field.to_s.present? ? show_file_field : true %>
<% show_existing = show_existing.to_s.present? ? show_existing : true %>
<% accept_attr = asset.class.respond_to?(:accept_attribute) ? asset.class.accept_attribute : nil %>
<% accepted_label = asset.class.respond_to?(:accepted_types_label) ? asset.class.accepted_types_label : nil %>
<% file =
if file.present?
file
Expand Down Expand Up @@ -92,6 +94,7 @@

<div class="relative w-28">
<%= f.file_field field_name,
accept: accept_attr,
data: {
file_preview_target: "input",
action: "change->file-preview#update"
Expand All @@ -118,9 +121,20 @@
<% end %>

</div>

<% if accepted_label.present? %>
<p class="text-gray-400 text-xs mt-1">Accepts: <%= accepted_label %></p>
<% end %>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

show accepted file types on form

<% end %>

</div>
</div>

<%= f.error :file, wrap_with: { tag: :p, class: "text-red-500 text-sm mt-1" } %>
<% if asset.errors[:file].any? %>
<div class="mt-2 mx-auto w-fit rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center">
<p class="text-red-600 text-sm">
<i class="fa-solid fa-circle-exclamation mr-1"></i>
File type not accepted
</p>
</div>
<% end %>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

show pretty error msg if wrong filetype

2 changes: 1 addition & 1 deletion app/views/stories/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
<div id="upload-progress-bar" class="h-full w-0 bg-blue-500 transition-[width] duration-200 ease-out"></div>
</div>
<div id="hero-image">
<%= render "assets/display_assets", resource: @story %>
<%= render "assets/display_assets", resource: @story, link: true %>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we want show page images to open in new window

</div>

<!-- Story Body Text -->
Expand Down
2 changes: 1 addition & 1 deletion app/views/story_ideas/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
</p>
</div>

<%= render "assets/display_assets", resource: @story_idea %>
<%= render "assets/display_assets", resource: @story_idea, link: true %>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we want show page images to open in new window


<!-- Story Body -->
<div class="mb-10">
Expand Down
5 changes: 3 additions & 2 deletions app/views/story_shares/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
<div class="aspect-[16/6] bg-gray-200">
<%= render "assets/display_image",
resource: @story,
variant: :hero %>
variant: :hero,
link: true %>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we want show page images to open in new window

</div>

<!-- Blue Sector Box on Top Left -->
Expand Down Expand Up @@ -81,7 +82,7 @@
<!-- Additional Story Assets (if any beyond hero) -->
<% if @story.assets.count > 1 %>
<div class="mb-10">
<%= render "assets/display_assets", resource: @story %>
<%= render "assets/display_assets", resource: @story, link: true %>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we want show page images to open in new window

</div>
<% end %>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/views/tutorials/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
</div>
<% end %>

<%= render "assets/display_assets", resource: @tutorial %>
<%= render "assets/display_assets", resource: @tutorial, link: true %>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we want show page images to open in new window



<!-- Main Text -->
Expand Down
2 changes: 1 addition & 1 deletion app/views/workshop_ideas/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,6 @@
</div>
</div>
<div class="media">
<%= render "assets/display_assets", resource: @workshop_idea %>
<%= render "assets/display_assets", resource: @workshop_idea, link: true %>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we want show page images to open in new window

</div>
</div>
Loading