Skip to content

Commit fc51a55

Browse files
drusepthclaude
andcommitted
Add full-screen messenger UI for private messages
- Redesign private topics show page as modern messenger UI - Add conversation sidebar with active state highlighting - Add dark mode support throughout messenger - Create styled edit page for private topics - Add Follow/Message/Share buttons to user profile - Add floating share FAB on user profile - Hide breadcrumbs and footer for fullscreen messenger experience - Fix PrivateTopicView usage with .to_model for URLs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent e23a3d9 commit fc51a55

File tree

6 files changed

+402
-80
lines changed

6 files changed

+402
-80
lines changed

app/views/layouts/forum.html.erb

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,52 +29,56 @@
2929

3030
<%= render 'layouts/tailwind/sidebar' %>
3131

32-
<!-- forum navigation & breadcrumbs -->
33-
<nav class="thredded-nav bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between sticky top-14 z-40 <%= user_signed_in? ? 'lg:ml-56' : '' %> transition-all duration-300" <% if user_signed_in? %>:class="{ 'ml-56 sm:ml-64 lg:ml-56': showSidebar, 'ml-0': !showSidebar }"<% end %>>
34-
<ol role="list" class="flex-1 flex space-x-4 px-4 py-2">
35-
<%= render('thredded/redesigned/breadcrumbs') %>
36-
</ol>
37-
<ul class="flex items-center h-full divide-x divide-gray-200 dark:divide-gray-700">
38-
<li><!-- Included to always force a left-border on whatever action is left-most --></li>
39-
<%= render 'thredded/shared/nav/moderation' %>
40-
<%= render 'thredded/shared/nav/notification_preferences' %>
41-
</ul>
42-
</nav>
32+
<% unless content_for?(:messenger_fullscreen) %>
33+
<!-- forum navigation & breadcrumbs -->
34+
<nav class="thredded-nav bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between sticky top-14 z-40 <%= user_signed_in? ? 'lg:ml-56' : '' %> transition-all duration-300" <% if user_signed_in? %>:class="{ 'ml-56 sm:ml-64 lg:ml-56': showSidebar, 'ml-0': !showSidebar }"<% end %>>
35+
<ol role="list" class="flex-1 flex space-x-4 px-4 py-2">
36+
<%= render('thredded/redesigned/breadcrumbs') %>
37+
</ol>
38+
<ul class="flex items-center h-full divide-x divide-gray-200 dark:divide-gray-700">
39+
<li><!-- Included to always force a left-border on whatever action is left-most --></li>
40+
<%= render 'thredded/shared/nav/moderation' %>
41+
<%= render 'thredded/shared/nav/notification_preferences' %>
42+
</ul>
43+
</nav>
44+
<% end %>
4345

4446
<!-- main content -->
4547
<main class="<%= user_signed_in? ? 'lg:ml-56' : '' %> transition-all duration-300" <% if user_signed_in? %>:class="{ 'ml-56 sm:ml-64 lg:ml-56': showSidebar, 'ml-0': !showSidebar }"<% end %>>
4648
<%= yield %>
4749
</main>
4850

49-
<footer class="<%= user_signed_in? ? 'lg:ml-56' : '' %> transition-all duration-300" <% if user_signed_in? %>:class="{ 'ml-56 sm:ml-64 lg:ml-56': showSidebar, 'ml-0': !showSidebar }"<% end %>>
50-
<div class="max-w-7xl mx-auto pt-24 px-4 sm:px-6 md:flex md:items-center md:justify-between lg:px-8">
51-
<div class="flex justify-center space-x-6 md:order-2">
52-
<%= link_to 'https://www.facebook.com/notebookai/', class: 'text-gray-400 hover:text-gray-500 dark:hover:text-gray-300' do %>
53-
<span class="sr-only">Facebook</span>
54-
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
55-
<path fill-rule="evenodd" d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z" clip-rule="evenodd" />
56-
</svg>
57-
<% end %>
51+
<% unless content_for?(:messenger_fullscreen) %>
52+
<footer class="<%= user_signed_in? ? 'lg:ml-56' : '' %> transition-all duration-300" <% if user_signed_in? %>:class="{ 'ml-56 sm:ml-64 lg:ml-56': showSidebar, 'ml-0': !showSidebar }"<% end %>>
53+
<div class="max-w-7xl mx-auto pt-24 px-4 sm:px-6 md:flex md:items-center md:justify-between lg:px-8">
54+
<div class="flex justify-center space-x-6 md:order-2">
55+
<%= link_to 'https://www.facebook.com/notebookai/', class: 'text-gray-400 hover:text-gray-500 dark:hover:text-gray-300' do %>
56+
<span class="sr-only">Facebook</span>
57+
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
58+
<path fill-rule="evenodd" d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z" clip-rule="evenodd" />
59+
</svg>
60+
<% end %>
5861

59-
<%= link_to 'https://twitter.com/indentlabs', class: 'text-gray-400 hover:text-gray-500 dark:hover:text-gray-300' do %>
60-
<span class="sr-only">Twitter</span>
61-
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
62-
<path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z" />
63-
</svg>
64-
<% end %>
62+
<%= link_to 'https://twitter.com/indentlabs', class: 'text-gray-400 hover:text-gray-500 dark:hover:text-gray-300' do %>
63+
<span class="sr-only">Twitter</span>
64+
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
65+
<path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z" />
66+
</svg>
67+
<% end %>
6568

66-
<%= link_to 'https://github.com/indentlabs/notebook', class: 'text-gray-400 hover:text-gray-500 dark:hover:text-gray-300' do %>
67-
<span class="sr-only">GitHub</span>
68-
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
69-
<path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd" />
70-
</svg>
71-
<% end %>
69+
<%= link_to 'https://github.com/indentlabs/notebook', class: 'text-gray-400 hover:text-gray-500 dark:hover:text-gray-300' do %>
70+
<span class="sr-only">GitHub</span>
71+
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
72+
<path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd" />
73+
</svg>
74+
<% end %>
75+
</div>
76+
<div class="mt-8 md:mt-0 md:order-1">
77+
<p class="text-center text-base text-gray-400 dark:text-gray-500">Notebook.ai &copy; 2016-2025 Indent Labs, LLC</p>
78+
</div>
7279
</div>
73-
<div class="mt-8 md:mt-0 md:order-1">
74-
<p class="text-center text-base text-gray-400 dark:text-gray-500">Notebook.ai &copy; 2016-2025 Indent Labs, LLC</p>
75-
</div>
76-
</div>
77-
</footer>
80+
</footer>
81+
<% end %>
7882

7983
<script type="text/javascript">
8084
<% if user_signed_in? %>
Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,43 @@
11
<% private_post, content = post_and_content if local_assigns.key?(:post_and_content) %>
2+
<% is_current_user = private_post.user == current_user %>
23

3-
<%
4-
blocked_post = user_signed_in? && private_post.user.present? && private_post.user.blocked_by?(current_user)
5-
%>
4+
<div id="<%= dom_id(private_post) %>" class="flex w-full <%= is_current_user ? 'justify-end' : 'justify-start' %> mb-4 group">
5+
<div class="flex max-w-[85%] md:max-w-[70%] <%= is_current_user ? 'flex-row-reverse' : 'flex-row' %> gap-2">
6+
7+
<!-- Avatar -->
8+
<div class="flex-shrink-0 self-end mb-1">
9+
<% if private_post.user.respond_to?(:avatar_url) && private_post.user.avatar_url.present? %>
10+
<%= image_tag private_post.user.avatar_url, class: "w-8 h-8 rounded-full bg-gray-200 dark:bg-gray-700" %>
11+
<% else %>
12+
<div class="w-8 h-8 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center text-xs font-bold text-gray-500">
13+
<%= private_post.user.thredded_display_name.first(2).upcase %>
14+
</div>
15+
<% end %>
16+
</div>
617

7-
<% if blocked_post %>
8-
<div class='grey-text text-lighten-2'>
9-
<em class="tooltipped" data-tooltip="This message is hidden because you've blocked the user it's from.">1 message hidden.</em>
18+
<!-- Message Bubble -->
19+
<div class="flex flex-col <%= is_current_user ? 'items-end' : 'items-start' %>">
20+
<!-- Sender Name (only for others) -->
21+
<% unless is_current_user %>
22+
<span class="text-xs text-gray-500 dark:text-gray-400 ml-1 mb-1">
23+
<%= private_post.user.thredded_display_name %>
24+
</span>
25+
<% end %>
26+
27+
<div class="px-4 py-2 rounded-2xl shadow-sm text-sm md:text-base break-words
28+
<%= is_current_user ?
29+
'bg-blue-600 text-white rounded-br-none' :
30+
'bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 border border-gray-200 dark:border-gray-700 rounded-bl-none' %>">
31+
<%= ForumReplacementService.replace_for(content, current_user) || render('thredded/private_posts/content', post: private_post) %>
32+
</div>
33+
34+
<!-- Timestamp & Status -->
35+
<div class="flex items-center gap-1 mt-1 text-[10px] text-gray-400 dark:text-gray-500 <%= is_current_user ? 'mr-1' : 'ml-1' %>">
36+
<%= time_ago private_post.created_at %>
37+
<% if is_current_user && private_post.read_state == 'read' %>
38+
<i class="material-icons text-[12px] text-blue-500" title="Read">done_all</i>
39+
<% end %>
40+
</div>
41+
</div>
1042
</div>
11-
<% else %>
12-
<%= render 'thredded/posts_common/before_first_unread_post', post: private_post if private_post.first_unread_in_page? %>
13-
<%= content_tag :article, id: dom_id(private_post), class: "thredded--post thredded--#{private_post.read_state}--post" do %>
14-
<%= render 'thredded/posts_common/actions', post: private_post, actions: local_assigns[:actions] %>
15-
<%= render 'thredded/posts_common/header', post: private_post %>
16-
<%= ForumReplacementService.replace_for(content, current_user) || render('thredded/private_posts/content', post: private_post) %>
17-
<%# content || render('thredded/private_posts/content', post: private_post) %>
18-
<% end %>
19-
<% end %>
43+
</div>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<% content_for :thredded_page_title, t('thredded.nav.edit_private_topic') %>
2+
<% content_for :thredded_page_id, 'thredded--edit-private-topic' %>
3+
4+
<%= thredded_page do %>
5+
<div class="max-w-2xl mx-auto py-8 px-4">
6+
<!-- Header -->
7+
<div class="mb-8">
8+
<%= link_to thredded.private_topic_path(@private_topic), class: "inline-flex items-center text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 mb-4" do %>
9+
<i class="material-icons text-sm mr-1">arrow_back</i>
10+
Back to conversation
11+
<% end %>
12+
13+
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Edit Conversation</h1>
14+
<p class="text-gray-500 dark:text-gray-400 mt-1">Update the title of this private conversation</p>
15+
</div>
16+
17+
<!-- Form -->
18+
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6">
19+
<%= form_for @private_topic,
20+
url: thredded.private_topic_path(@private_topic),
21+
html: { class: 'space-y-6' } do |form| %>
22+
23+
<!-- Title Field -->
24+
<div>
25+
<%= form.label :title, t('thredded.private_topics.form.title_label'), class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" %>
26+
<%= form.text_field :title,
27+
placeholder: t('thredded.private_topics.form.title_placeholder_new'),
28+
autofocus: true,
29+
required: true,
30+
class: "w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 transition-colors" %>
31+
<% if @private_topic.errors[:title].any? %>
32+
<p class="mt-2 text-sm text-red-600 dark:text-red-400">
33+
<%= @private_topic.errors[:title].join(', ') %>
34+
</p>
35+
<% end %>
36+
</div>
37+
38+
<!-- Participants (read-only info) -->
39+
<div>
40+
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Participants</label>
41+
<div class="flex flex-wrap gap-2">
42+
<% @private_topic.users.each do |user| %>
43+
<div class="inline-flex items-center px-3 py-1.5 bg-gray-100 dark:bg-gray-700 rounded-full">
44+
<% if user.respond_to?(:avatar_url) && user.avatar_url.present? %>
45+
<%= image_tag user.avatar_url, class: "w-5 h-5 rounded-full mr-2" %>
46+
<% else %>
47+
<div class="w-5 h-5 rounded-full bg-gray-300 dark:bg-gray-600 mr-2 flex items-center justify-center text-xs font-bold text-gray-600 dark:text-gray-300">
48+
<%= user.thredded_display_name.first(1).upcase %>
49+
</div>
50+
<% end %>
51+
<span class="text-sm text-gray-700 dark:text-gray-300"><%= user.thredded_display_name %></span>
52+
</div>
53+
<% end %>
54+
</div>
55+
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
56+
Participants cannot be changed after a conversation is created.
57+
</p>
58+
</div>
59+
60+
<!-- Actions -->
61+
<div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-gray-700">
62+
<%= link_to thredded.private_topic_path(@private_topic), class: "px-4 py-2 text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors" do %>
63+
Cancel
64+
<% end %>
65+
66+
<button type="submit"
67+
class="px-6 py-2.5 bg-notebook-blue hover:bg-blue-700 text-white font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-gray-800"
68+
data-disable-with="<%= t('thredded.private_topics.form.update_btn_submitting') %>">
69+
<%= t('thredded.private_topics.form.update_btn') %>
70+
</button>
71+
</div>
72+
<% end %>
73+
</div>
74+
</div>
75+
<% end %>

0 commit comments

Comments
 (0)