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
2 changes: 1 addition & 1 deletion app/controllers/projects_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def show
@next_fields = @next_instance.project_instance_fields.includes(:project_template_field).order(project_template_field_id: :asc)
end

@comments = @current_instance.comments.order(created_at: :asc)
@comments = @project.comments.order(created_at: :asc)
@new_comment = Comment.new(user: current_user, location: @current_instance)

return unless @course.use_progress_updates
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/topics_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def show
.order(project_template_field_id: :asc)
end

@comments = @current_instance.comments.order(created_at: :asc)
@comments = @topic.comments.order(created_at: :asc)
@new_comment = Comment.new
@fields = @current_fields
end
Expand Down
44 changes: 44 additions & 0 deletions app/javascript/controllers/scroll_to_bottom_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
static values = {
enabled: Boolean,
targetVersion: String,
};

connect() {
// Wait for the browser to finish painting the DOM before calculating heights
requestAnimationFrame(() => {
setTimeout(() => {
if (this.enabledValue) {
this.scrollToBottom();
} else if (this.hasTargetVersionValue) {
this.scrollToVersion();
}
}, 50);
});
}

scrollToBottom() {
this.element.scrollTop = this.element.scrollHeight;
}

scrollToVersion() {
const targetId = `version-${this.targetVersionValue}-comments`;
const targetElement = document.getElementById(targetId);

if (targetElement) {
const containerRect = this.element.getBoundingClientRect();

const targetRect = targetElement.getBoundingClientRect();

const scrollPosition =
this.element.scrollTop + (targetRect.top - containerRect.top) - 20;

this.element.scrollTo({
top: scrollPosition,
behavior: "smooth",
});
}
}
}
1 change: 1 addition & 0 deletions app/models/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Project < ApplicationRecord

has_many :project_instances, dependent: :destroy
has_many :progress_updates, dependent: :destroy
has_many :comments, through: :project_instances

# DO NOT WRITE TO STATUS IN PROJECTS, IT'S ONLY MEANT TO KEEP TRACK OF THE STATUS OF THE LATEST PROJECT INSTANCE
# write to the latest project instance instead
Expand Down
2 changes: 2 additions & 0 deletions app/models/topic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class Topic < ApplicationRecord

has_many :topic_instances, dependent: :destroy, foreign_key: 'project_id'

has_many :comments, through: :topic_instances

has_many :proposed_project_instances, class_name: 'ProjectInstance', foreign_key: 'source_topic_id'

# DO NOT WRITE TO STATUS IN PROJECTS, IT'S ONLY MEANT TO KEEP TRACK OF THE STATUS OF THE LATEST PROJECT INSTANCE
Expand Down
23 changes: 17 additions & 6 deletions app/views/courses/_project_status_bar.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,22 @@
<ol class="relative border-s border-gray-200">
<% statuses.each do |status| %>
<% is_active = @current_status.to_s == status %>
<li class="mb-8 ms-7 step" data-status="<%= status %>">
<span class="absolute flex items-center justify-center w-8 h-8 rounded-full -start-4 ring-4 ring-white
<%= is_active ? 'bg-gray-300' : 'bg-gray-100' %>">
<%= image_tag "#{status}.svg", class: "w-4 h-4 #{is_active ? 'opacity-100' : 'opacity-30'}" %>
<li class="mb-8 ms-7 step flex items-center" data-status="<%= status %>">
<span
class="
absolute flex items-center justify-center w-8 h-8 rounded-full -start-4 ring-4
ring-white <%= is_active ? 'bg-gray-300' : 'bg-gray-100' %>
"
>
<%= image_tag "#{status}.svg",
class: "w-4 h-4 #{is_active ? "opacity-100" : "opacity-30"}" %>
</span>
<h3 class="font-medium leading-tight <%= is_active ? 'text-black' : 'text-gray-400' %>">
<h3
class="
font-medium leading-tight items-self-center
<%= is_active ? 'text-black' : 'text-gray-400' %>
"
>
<%= status.humanize %>
</h3>
</li>
Expand All @@ -36,7 +46,8 @@
<p class="text-sm text-gray-500 mb-4">No proposal submitted yet.</p>
<%= link_to "Submit Proposal",
new_course_project_path(@course),
class: "inline-flex items-center justify-center px-6 py-3 text-base font-semibold text-white transition-all duration-200 bg-blue-600 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 hover:shadow-md" %>
class:
"inline-flex items-center justify-center px-6 py-3 text-base font-semibold text-white transition-all duration-200 bg-blue-600 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 hover:shadow-md" %>
</div>
<% end %>
</div>
Expand Down
120 changes: 68 additions & 52 deletions app/views/projects/_project_comments.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -26,72 +26,88 @@
class="
flex-1 min-h-0 overflow-y-auto bg-gray-50/30 p-5 custom-scrollbar relative
"
data-controller="scroll-to-bottom"
data-scroll-to-bottom-enabled-value="<%= current_version == latest_version %>"
data-scroll-to-bottom-target-version-value="<%= current_version %>"
>

<% if comments.any? %>
<div class="space-y-6 relative pl-4">

<% comments.each do |comment| %>
<article class="relative flex gap-4 z-10 group">

<div class="flex-shrink-0 relative">
<div
class="
w-6 h-6 rounded-full bg-gray-400 text-white flex items-center justify-center
text-xs font-bold ring-4 ring-gray-50/30
"
>
<%= comment.user.name[0].upcase %>
<% comments.group_by { |c| c.location.version }.each do |version, version_comments| %>
<div class="pt-2 pb-1" id="version-<%= version %>-comments">
<h3 class="text-xs font-bold text-gray-500 uppercase tracking-wide">
Comments for v<%= version %>
</h3>
</div>
<% version_comments.each do |comment| %>
<% is_current_version = comment.location_id == current_instance_id %>

<article
class="
relative flex gap-4 z-10 group <%= is_current_version ? '' : 'opacity-60 hover:opacity-100 transition-opacity duration-300' %>
"
>

<div class="flex-shrink-0 relative">
<div
class="
w-6 h-6 rounded-full bg-gray-400 text-white flex items-center justify-center
text-xs font-bold ring-4 ring-gray-50/30
"
>
<%= comment.user.name[0].upcase %>
</div>
</div>
</div>

<div class="flex-1 min-w-0">

<%# --- FIXED HEADER LAYOUT --- %>
<header class="flex items-center justify-between gap-2 mb-1">
<div class="flex-1 min-w-0">

<div class="flex items-baseline gap-2 min-w-0 flex-1">
<% if Current.user == comment.user %>
<h4 class="text-sm font-bold text-green-600 truncate"><%= comment.user.name %></h4>
<% else %>
<h4 class="text-sm font-bold text-gray-800 truncate"><%= comment.user.name %></h4>
<% end %>
<%# --- FIXED HEADER LAYOUT --- %>
<header class="flex items-center justify-between gap-2 mb-1">

<span class="text-xs text-gray-400 whitespace-nowrap flex-shrink-0">
<% if comment.created_at > 3.days.ago %>
<%= time_ago_in_words(comment.created_at) %>
ago
<div class="flex items-baseline gap-2 min-w-0 flex-1">
<% if Current.user == comment.user %>
<h4 class="text-sm font-bold text-green-600 truncate"><%= comment.user.name %></h4>
<% else %>
<%= format_timestamp(comment.created_at) %>
<h4 class="text-sm font-bold text-gray-800 truncate"><%= comment.user.name %></h4>
<% end %>
</span>
</div>

<%# delete button %>
<% if Current.user == comment.user && !comment.deleted %>
<%= link_to soft_delete_comment_path(comment),
data: { turbo_method: :patch, confirm: "Delete this comment?" },
class: "flex-shrink-0 text-red-400 hover:text-red-600 transition-colors p-1 -mr-1",
title: "Delete comment" do %>
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/></svg>
<span class="text-xs text-gray-400 whitespace-nowrap flex-shrink-0">
<% if comment.created_at > 3.days.ago %>
<%= time_ago_in_words(comment.created_at) %>
ago
<% else %>
<%= format_timestamp(comment.created_at) %>
<% end %>
</span>
</div>

<%# delete button %>
<% if Current.user == comment.user && !comment.deleted %>
<%= link_to soft_delete_comment_path(comment),
data: { turbo_method: :patch, confirm: "Delete this comment?" },
class: "flex-shrink-0 text-red-400 hover:text-red-600 transition-colors p-1 -mr-1",
title: "Delete comment" do %>
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/></svg>
<% end %>
<% end %>
<% end %>
</header>

<div class="text-sm text-gray-600 leading-relaxed break-words">
<% if !comment.deleted %>
<%= simple_format(comment.text, class: "mb-3 last:mb-0") %>
<% else %>
<span class="text-gray-400 italic text-xs bg-gray-100 px-2 py-0.5 rounded">This comment was deleted</span>
<% end %>
</header>

<div class="text-sm text-gray-600 leading-relaxed break-words">
<% if !comment.deleted %>
<%= simple_format(comment.text, class: "mb-3 last:mb-0") %>
<% else %>
<span class="text-gray-400 italic text-xs bg-gray-100 px-2 py-0.5 rounded">This comment was deleted</span>
<% end %>
</div>
</div>
</div>
</article>
</article>
<% end %>
<% end %>
</div>

Expand Down
Loading
Loading