feat: notes to self via self-DM
Backend: - Removed self-DM restriction in conversation Create - Added duplicate check for self-DMs (returns existing one) Frontend: - ConversationList: [📝] button creates/opens notes to self - Self-DMs show as 'Notes' with 📝 icon in sidebar - DMChat header shows 'Notes' for self-DMs
This commit is contained in:
+19
-5
@@ -82,10 +82,7 @@ func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
|
|||||||
memberSet[uid] = struct{}{}
|
memberSet[uid] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(memberSet) == 1 {
|
// ponytail: allow self-DM (notes to self) — len(memberSet)==1 is fine
|
||||||
http.Error(w, `{"error":"cannot create conversation with yourself"}`, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
memberIDs := make([]string, 0, len(memberSet))
|
memberIDs := make([]string, 0, len(memberSet))
|
||||||
for uid := range memberSet {
|
for uid := range memberSet {
|
||||||
@@ -94,7 +91,24 @@ func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
|
|||||||
sort.Strings(memberIDs)
|
sort.Strings(memberIDs)
|
||||||
|
|
||||||
// For a 1:1 DM, check if it already exists.
|
// For a 1:1 DM, check if it already exists.
|
||||||
if len(memberIDs) == 2 {
|
if len(memberIDs) == 1 {
|
||||||
|
var existingID string
|
||||||
|
err := h.db.QueryRowContext(r.Context(), `
|
||||||
|
SELECT c.id FROM conversations c
|
||||||
|
WHERE c.type = 'dm'
|
||||||
|
AND (SELECT COUNT(*) FROM conversation_members cm WHERE cm.conversation_id = c.id) = 1
|
||||||
|
AND EXISTS (SELECT 1 FROM conversation_members cm2 WHERE cm2.conversation_id = c.id AND cm2.user_id = $1)
|
||||||
|
LIMIT 1
|
||||||
|
`, memberIDs[0]).Scan(&existingID)
|
||||||
|
if err == nil {
|
||||||
|
h.getByID(w, r, existingID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !errors.Is(err, sql.ErrNoRows) {
|
||||||
|
http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if len(memberIDs) == 2 {
|
||||||
var existingID string
|
var existingID string
|
||||||
err := h.db.QueryRowContext(r.Context(), `
|
err := h.db.QueryRowContext(r.Context(), `
|
||||||
SELECT c.id FROM conversations c
|
SELECT c.id FROM conversations c
|
||||||
|
|||||||
@@ -8,11 +8,16 @@ function otherMemberName(conv: { members: { id: string; username: string }[] },
|
|||||||
return other?.username || "unknown";
|
return other?.username || "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSelfDM(conv: { members: { id: string }[] }, currentUserId: string) {
|
||||||
|
return conv.members.length === 1 && conv.members[0].id === currentUserId;
|
||||||
|
}
|
||||||
|
|
||||||
export function ConversationList() {
|
export function ConversationList() {
|
||||||
const conversations = useConversationStore((s) => s.conversations);
|
const conversations = useConversationStore((s) => s.conversations);
|
||||||
const activeId = useConversationStore((s) => s.activeConversationId);
|
const activeId = useConversationStore((s) => s.activeConversationId);
|
||||||
const fetchConversations = useConversationStore((s) => s.fetchConversations);
|
const fetchConversations = useConversationStore((s) => s.fetchConversations);
|
||||||
const setActive = useConversationStore((s) => s.setActiveConversation);
|
const setActive = useConversationStore((s) => s.setActiveConversation);
|
||||||
|
const createConversation = useConversationStore((s) => s.createConversation);
|
||||||
const currentUser = useAuthStore((s) => s.user);
|
const currentUser = useAuthStore((s) => s.user);
|
||||||
const [showNew, setShowNew] = useState(false);
|
const [showNew, setShowNew] = useState(false);
|
||||||
|
|
||||||
@@ -20,25 +25,48 @@ export function ConversationList() {
|
|||||||
fetchConversations();
|
fetchConversations();
|
||||||
}, [fetchConversations]);
|
}, [fetchConversations]);
|
||||||
|
|
||||||
|
const openNotes = async () => {
|
||||||
|
// Find existing self-DM or create one
|
||||||
|
const existing = conversations.find((c) => isSelfDM(c, currentUser?.id || ""));
|
||||||
|
if (existing) {
|
||||||
|
setActive(existing.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await createConversation([]);
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-56 bg-gb-bg-s border-r border-gb-bg-t flex flex-col">
|
<div className="h-full w-56 bg-gb-bg-s border-r border-gb-bg-t flex flex-col">
|
||||||
<div className="terminal-border border-t-0 border-x-0 px-3 py-2 text-gb-fg truncate flex items-center justify-between">
|
<div className="terminal-border border-t-0 border-x-0 px-3 py-2 text-gb-fg truncate flex items-center justify-between">
|
||||||
<span>[DIRECT MESSAGES]</span>
|
<span>[DIRECT MESSAGES]</span>
|
||||||
<button
|
<div className="flex gap-1">
|
||||||
onClick={() => setShowNew(true)}
|
<button
|
||||||
className="terminal-button text-xs"
|
onClick={openNotes}
|
||||||
title="New conversation"
|
className="terminal-button text-xs"
|
||||||
>
|
title="Notes to self"
|
||||||
[+]
|
>
|
||||||
</button>
|
[📝]
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowNew(true)}
|
||||||
|
className="terminal-button text-xs"
|
||||||
|
title="New conversation"
|
||||||
|
>
|
||||||
|
[+]
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto p-2 font-mono text-sm">
|
<div className="flex-1 overflow-y-auto p-2 font-mono text-sm">
|
||||||
{conversations.length === 0 && (
|
{conversations.length === 0 && (
|
||||||
<p className="text-gb-fg-f">[no conversations]</p>
|
<p className="text-gb-fg-f">[no conversations]</p>
|
||||||
)}
|
)}
|
||||||
{conversations.map((conv) => {
|
{conversations.map((conv) => {
|
||||||
const name =
|
const self = isSelfDM(conv, currentUser?.id || "");
|
||||||
conv.type === "group_dm"
|
const name = self
|
||||||
|
? "Notes"
|
||||||
|
: conv.type === "group_dm"
|
||||||
? conv.name || conv.members.map((m) => m.username).join(", ")
|
? conv.name || conv.members.map((m) => m.username).join(", ")
|
||||||
: otherMemberName(conv, currentUser?.id || "");
|
: otherMemberName(conv, currentUser?.id || "");
|
||||||
return (
|
return (
|
||||||
@@ -51,7 +79,7 @@ export function ConversationList() {
|
|||||||
: "hover:bg-gb-bg-t text-gb-fg-s"
|
: "hover:bg-gb-bg-t text-gb-fg-s"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span className="text-gb-fg-f">@</span>
|
<span className="text-gb-fg-f">{self ? "📝" : "@"}</span>
|
||||||
<span className="truncate">{name}</span>
|
<span className="truncate">{name}</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -75,13 +75,16 @@ export function DMChat() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const title = conversation
|
const selfDM = conversation && conversation.members.length === 1 && conversation.members[0].id === currentUser?.id;
|
||||||
? conversation.type === "group_dm"
|
const title = selfDM
|
||||||
? conversation.name || conversation.members.map((m) => m.username).join(", ")
|
? "Notes"
|
||||||
: conversation.members.find((m) => m.id !== currentUser?.id)?.username || "DM"
|
: conversation
|
||||||
: id
|
? conversation.type === "group_dm"
|
||||||
? "[LOADING...]"
|
? conversation.name || conversation.members.map((m) => m.username).join(", ")
|
||||||
: "[NO CONVERSATION]";
|
: conversation.members.find((m) => m.id !== currentUser?.id)?.username || "DM"
|
||||||
|
: id
|
||||||
|
? "[LOADING...]"
|
||||||
|
: "[NO CONVERSATION]";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full bg-gb-bg">
|
<div className="flex flex-col h-full bg-gb-bg">
|
||||||
|
|||||||
Reference in New Issue
Block a user