fix(newsfeed): tighten card spacing and reposition pinned icon (#818)
* fix(newsfeed): tighten card spacing and reposition pinned icon - Move pinned icon to top-left corner with a tilted rotation - Remove the extra pin row that added excess top padding - Reduce spacing between post content, reactions, and comments - Remove card box shadow Co-authored-by: Rotimi Best <[email protected]> * fix(newsfeed): place pinned icon on card corner instead of avatar Co-authored-by: Rotimi Best <[email protected]> * fix(newsfeed): stop dropdown menus locking body scroll bits-ui defaults DropdownMenu.Content's preventScroll to true, so every open ran a body scroll lock: a forced full-document style recalc and reflow, a horizontal shift by the scrollbar width, and pointer-events: none on the body. On the newsfeed page — a long list of cards each with a scrollable comment thread — that made the comment ellipsis menu feel sluggish to open. Menus don't need scroll locking; only modal layers do. Also tighten card spacing, use the primary color for the pinned icon, give the card menu trigger a pointer cursor, and switch the comment save and submit buttons to the loading and secondary variants. * refactor(newsfeed): store reply targets as data, not markup Replies used to prepend a presentational <blockquote> — Tailwind classes, hardcoded English and a copied snippet of the parent — onto the comment body, and that whole string was persisted to course_newsfeed_comment.content. Editing a reply therefore showed raw markup in the textarea, the quoted snippet froze at write time, the label could never be localized, and two of its ui: utilities never compiled because they appeared only in dashboard code. Add a replyToCommentId column, distinct from parentId (the thread root), so a reply can name the comment it answers. The service verifies the target belongs to the same feed and the same thread; the paginated query joins it to return replyToAuthorFullname, so the label is rendered from live data by the new CommentTree.ReplyingTo component using the existing replying_to key. Rows written the old way still display their stored quote, and stripLegacyReplyQuote keeps that markup out of the edit textarea. * refactor(newsfeed): drop redundant comments on reply target plumbing * fix(newsfeed): address review feedback on pinned icon and quote stripping - The pin icon carried an unprefixed `text-muted-foreground` plus `ui:stroke-primary`, which is emitted nowhere because it appears only in dashboard code, so the outline fell back to the muted colour. Use `ui:text-primary` and let the stroke inherit currentColor. - Give the pinned state an accessible name: the icon is `aria-hidden`, and header.svelte no longer carries an indicator, so screen readers had nothing. - Filter feeds on `isPinned` alone. `is_pinned` is only the database column name; drizzle maps it to `isPinned` and the API never returns the snake_case form, so the `as any` fallback was dead and let the page and card disagree. - `ui:transition-color` is not a Tailwind utility and emitted nothing; the plural `ui:transition-colors` is what applies the transition. - Strip a legacy reply quote only when the blockquote carries the `reply-quote` class the old code always wrote, so a blockquote the author typed is kept. * docs(agents): forbid presentational markup in persisted content columns ---------
This commit is contained in:
@@ -2,6 +2,7 @@ import * as schema from '@db/schema';
|
||||
|
||||
import type { TCourseNewsfeed, TCourseNewsfeedComment, TNewCourseNewsfeed, TNewCourseNewsfeedComment } from '@db/types';
|
||||
import { and, db, desc, eq, isNull, lt, ne, sql } from '@db/drizzle';
|
||||
import { alias } from 'drizzle-orm/pg-core';
|
||||
import { ROLE } from '@cio/utils/constants';
|
||||
|
||||
/**
|
||||
@@ -251,16 +252,21 @@ export async function getNewsfeedCommentsByFeedIdPaginated(
|
||||
.where(and(...baseWhereConditions));
|
||||
const totalCount = Number(totalCountResult[0]?.count || 0);
|
||||
|
||||
const replyTarget = alias(schema.courseNewsfeedComment, 'reply_target');
|
||||
const replyTargetMember = alias(schema.groupmember, 'reply_target_member');
|
||||
const replyTargetProfile = alias(schema.profile, 'reply_target_profile');
|
||||
|
||||
// Fetch comments with author profile & reply count
|
||||
const comments = await db
|
||||
.select({
|
||||
comment: schema.courseNewsfeedComment,
|
||||
groupmember: schema.groupmember,
|
||||
profile: schema.profile,
|
||||
replyToAuthorFullname: replyTargetProfile.fullname,
|
||||
replyCount: sql<number>`
|
||||
COALESCE(
|
||||
(SELECT COUNT(*)::int
|
||||
FROM ${schema.courseNewsfeedComment} c2
|
||||
(SELECT COUNT(*)::int
|
||||
FROM ${schema.courseNewsfeedComment} c2
|
||||
WHERE c2.parent_id = ${schema.courseNewsfeedComment.id}),
|
||||
0
|
||||
)
|
||||
@@ -269,6 +275,9 @@ export async function getNewsfeedCommentsByFeedIdPaginated(
|
||||
.from(schema.courseNewsfeedComment)
|
||||
.leftJoin(schema.groupmember, eq(schema.courseNewsfeedComment.authorId, schema.groupmember.id))
|
||||
.leftJoin(schema.profile, eq(schema.groupmember.profileId, schema.profile.id))
|
||||
.leftJoin(replyTarget, eq(schema.courseNewsfeedComment.replyToCommentId, replyTarget.id))
|
||||
.leftJoin(replyTargetMember, eq(replyTarget.authorId, replyTargetMember.id))
|
||||
.leftJoin(replyTargetProfile, eq(replyTargetMember.profileId, replyTargetProfile.id))
|
||||
.where(and(...whereConditions))
|
||||
.orderBy(desc(schema.courseNewsfeedComment.createdAt))
|
||||
.limit(limit + 1);
|
||||
@@ -285,6 +294,7 @@ export async function getNewsfeedCommentsByFeedIdPaginated(
|
||||
authorFullname: row.profile?.fullname || null,
|
||||
authorUsername: row.profile?.username || null,
|
||||
authorAvatarUrl: row.profile?.avatarUrl || null,
|
||||
replyToAuthorFullname: row.replyToAuthorFullname || null,
|
||||
replyCount: Number(row.replyCount || 0)
|
||||
})),
|
||||
totalCount,
|
||||
|
||||
@@ -1693,7 +1693,8 @@ export const courseNewsfeedComment = pgTable(
|
||||
cache: 1
|
||||
}),
|
||||
courseNewsfeedId: uuid('course_newsfeed_id'),
|
||||
parentId: bigint('parent_id', { mode: 'number' })
|
||||
parentId: bigint('parent_id', { mode: 'number' }),
|
||||
replyToCommentId: bigint('reply_to_comment_id', { mode: 'number' })
|
||||
},
|
||||
(table) => [
|
||||
foreignKey({
|
||||
@@ -1711,6 +1712,11 @@ export const courseNewsfeedComment = pgTable(
|
||||
foreignColumns: [table.id],
|
||||
name: 'course_newsfeed_comment_parent_id_fkey'
|
||||
}).onDelete('cascade'),
|
||||
foreignKey({
|
||||
columns: [table.replyToCommentId],
|
||||
foreignColumns: [table.id],
|
||||
name: 'course_newsfeed_comment_reply_to_comment_id_fkey'
|
||||
}).onDelete('set null'),
|
||||
index('course_newsfeed_comment_parent_id_idx').on(table.parentId)
|
||||
]
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user