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:
@@ -152,14 +152,14 @@ export const newsfeedRouter = new Hono()
|
||||
const user = c.get('user')!;
|
||||
const courseId = c.req.param('courseId')!;
|
||||
const { feedId } = c.req.valid('param');
|
||||
const { content, parentId } = c.req.valid('json');
|
||||
const { content, parentId, replyToCommentId } = c.req.valid('json');
|
||||
|
||||
const groupMemberId = await getGroupMemberIdByCourseAndProfile(courseId, user.id);
|
||||
if (!groupMemberId) {
|
||||
return c.json({ success: false, error: 'User is not a member of this course' }, 403);
|
||||
}
|
||||
|
||||
const comment = await createNewsfeedCommentService(feedId, groupMemberId, content, parentId);
|
||||
const comment = await createNewsfeedCommentService(feedId, groupMemberId, content, parentId, replyToCommentId);
|
||||
|
||||
return c.json({ success: true, data: comment }, 201);
|
||||
} catch (error) {
|
||||
|
||||
@@ -265,13 +265,15 @@ export async function getNewsfeedCommentsService(
|
||||
* @param authorId Group member ID (author)
|
||||
* @param content Comment content
|
||||
* @param parentId Optional parent comment ID for replies
|
||||
* @param replyToCommentId Optional comment being answered, within the same thread
|
||||
* @returns Created comment
|
||||
*/
|
||||
export async function createNewsfeedCommentService(
|
||||
feedId: string,
|
||||
authorId: string,
|
||||
content: string,
|
||||
parentId?: number
|
||||
parentId?: number,
|
||||
replyToCommentId?: number
|
||||
) {
|
||||
try {
|
||||
// Validate parentId when provided
|
||||
@@ -295,13 +297,33 @@ export async function createNewsfeedCommentService(
|
||||
}
|
||||
}
|
||||
|
||||
if (replyToCommentId !== undefined) {
|
||||
const targetComment = await getNewsfeedCommentById(replyToCommentId);
|
||||
|
||||
if (!targetComment) {
|
||||
throw new AppError('Comment being replied to not found', ErrorCodes.COMMENT_NOT_FOUND, 404);
|
||||
}
|
||||
|
||||
if (targetComment.courseNewsfeedId !== feedId) {
|
||||
throw new AppError('Comment being replied to does not belong to this feed', ErrorCodes.VALIDATION_ERROR, 400);
|
||||
}
|
||||
|
||||
const isThreadRoot = targetComment.id === parentId;
|
||||
const isSiblingReply = targetComment.parentId === parentId;
|
||||
|
||||
if (!isThreadRoot && !isSiblingReply) {
|
||||
throw new AppError('Comment being replied to is not part of this thread', ErrorCodes.VALIDATION_ERROR, 400);
|
||||
}
|
||||
}
|
||||
|
||||
const sanitizedContent = sanitizeHtml(content);
|
||||
|
||||
const commentData: TNewCourseNewsfeedComment = {
|
||||
courseNewsfeedId: feedId,
|
||||
authorId,
|
||||
content: sanitizedContent,
|
||||
parentId: parentId ?? null
|
||||
parentId: parentId ?? null,
|
||||
replyToCommentId: replyToCommentId ?? null
|
||||
};
|
||||
|
||||
const comment = await createNewsfeedComment(commentData);
|
||||
|
||||
Reference in New Issue
Block a user