Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] Added comments ordering #21181

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
5 changes: 3 additions & 2 deletions apps/comments-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ const App: React.FC<AppProps> = ({scriptTag}) => {
commentCount: 0,
secundaryFormCount: 0,
popup: null,
labs: null
labs: null,
order: 'best'
});

const iframeRef = React.createRef<HTMLIFrameElement>();
Expand Down Expand Up @@ -120,7 +121,7 @@ const App: React.FC<AppProps> = ({scriptTag}) => {

/** Fetch first few comments */
const fetchComments = async () => {
const dataPromise = api.comments.browse({page: 1, postId: options.postId});
const dataPromise = api.comments.browse({page: 1, postId: options.postId, order: state.order});
const countPromise = api.comments.count({postId: options.postId});

const [data, count] = await Promise.all([dataPromise, countPromise]);
Expand Down
11 changes: 9 additions & 2 deletions apps/comments-ui/src/AppContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export type EditableAppContext = {
commentCount: number,
secundaryFormCount: number,
popup: Page | null,
labs: LabsContextType
labs: LabsContextType,
order: string
}

export type TranslationFunction = (key: string, replacements?: Record<string, string | number>) => string;
Expand All @@ -87,7 +88,13 @@ export const AppContextProvider = AppContext.Provider;

export const useAppContext = () => useContext(AppContext);

// create a hook that will only get labs data from the context
export const useOrderChange = () => {
const context = useAppContext();
const dispatchAction = context.dispatchAction;
return (order: string) => {
dispatchAction('setOrder', {order});
};
};

export const useLabs = () => {
try {
Expand Down
14 changes: 13 additions & 1 deletion apps/comments-ui/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,17 @@ async function editComment({state, api, data: {comment, parent}}: {state: Editab
};
}

async function setOrder({data: {order}, options, api}: {state: EditableAppContext, data: {order: string}, options: CommentsOptions, api: GhostApi}) {
const data = await api.comments.browse({page: 1, postId: options.postId, order: order});

// replace comments in state with new ones
return {
comments: data.comments,
pagination: data.meta.pagination,
order
};
}

async function updateMember({data, state, api}: {data: {name: string, expertise: string}, state: EditableAppContext, api: GhostApi}) {
const {name, expertise} = data;
const patchData: {name?: string, expertise?: string} = {};
Expand Down Expand Up @@ -372,7 +383,8 @@ export const Actions = {
addReply,
loadMoreComments,
loadMoreReplies,
updateMember
updateMember,
setOrder
};

export type ActionType = keyof typeof Actions;
Expand Down
2 changes: 1 addition & 1 deletion apps/comments-ui/src/components/content/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {useEffect} from 'react';

const Content = () => {
const {pagination, member, comments, commentCount, commentsEnabled, title, showCount, secundaryFormCount} = useAppContext();
const commentsElements = comments.slice().reverse().map(comment => <Comment key={comment.id} comment={comment} />);
const commentsElements = comments.slice().map(comment => <Comment key={comment.id} comment={comment} />);
const labs = useLabs();

const paidOnly = commentsEnabled === 'paid';
Expand Down
4 changes: 4 additions & 0 deletions apps/comments-ui/src/components/content/ContentTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {SortingForm} from './forms/SortingForm';
import {formatNumber} from '../../utils/helpers';
import {useAppContext} from '../../AppContext';

Expand Down Expand Up @@ -52,6 +53,9 @@ const ContentTitle: React.FC<ContentTitleProps> = ({title, showCount, count}) =>
<Title title={title}/>
</h2>
<Count count={count} showCount={showCount} />

<SortingForm/>

</div>
);
};
Expand Down
26 changes: 26 additions & 0 deletions apps/comments-ui/src/components/content/forms/SortingForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {useOrderChange} from '../../../AppContext';

export const SortingForm = () => {
const changeOrder = useOrderChange();

const options = [
{value: 'best', label: 'Best'},
{value: 'newest', label: 'Newest'},
{value: 'oldest', label: 'Oldest'}
];

const handleOrderChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const order = event.target.value;
changeOrder(order);
};

return (
<select onChange={handleOrderChange}>
{options.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
);
};
8 changes: 6 additions & 2 deletions apps/comments-ui/src/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}: {site

return json;
},
browse({page, postId}: {page: number, postId: string}) {
browse({page, postId, order}: {page: number, postId: string, order?: string}) {
let filter = null;
if (firstCommentCreatedAt) {
if (firstCommentCreatedAt && !order) {
filter = `created_at:<=${firstCommentCreatedAt}`;
}

Expand All @@ -134,6 +134,10 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}: {site
params.set('filter', filter);
}
params.set('page', page.toString());

if (order) {
params.set('order', order);
}
const url = endpointFor({type: 'members', resource: `comments/post/${postId}`, params: `?${params.toString()}`});
const response = makeRequest({
url,
Expand Down
28 changes: 28 additions & 0 deletions ghost/core/core/server/models/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,37 @@ const Comment = ghostBookshelf.Model.extend({
await model.load(relationsToLoadIndividually, _.omit(options, 'withRelated'));
}

// if options.order === 'best', we findMostLikedComment
// then we remove it from the result set and add it as the first element
if (options.order === 'best') {
const mostLikedComment = await this.findMostLikedComment(options);
if (mostLikedComment) {
result.data = result.data.filter(comment => comment.id !== mostLikedComment.id);
result.data.unshift(mostLikedComment);
}
}

return result;
},

async findMostLikedComment(options = {}) {
// Build the query to find the comment with the most likes
let query = ghostBookshelf.knex('comments')
.select('comments.*')
.count('comment_likes.id as count__likes') // Counting likes for sorting
.leftJoin('comment_likes', 'comments.id', 'comment_likes.comment_id')
.groupBy('comments.id') // Group by comment ID to aggregate likes count
.orderBy('count__likes', 'desc') // Order by likes in descending order (most likes first)
.limit(1); // Limit to just 1 result
// Execute the query and get the result
const result = await query.first(); // Fetch the single top comment

const id = result && result.id;
// Fetch the comment model by ID

return this.findOne({id}, options);
},

countRelations() {
return {
replies(modelOrCollection) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ module.exports = class CommentsController {
* @param {Frame} frame
*/
async browse(frame) {
// our order options are best, newest, oldest
if (frame.options.post_id) {
if (frame.options.filter) {
frame.options.mongoTransformer = function (query) {
Expand All @@ -51,7 +52,8 @@ module.exports = class CommentsController {
frame.options.filter = `post_id:${frame.options.post_id}`;
}
}
return this.service.getComments(frame.options);
const result = await this.service.getComments(frame.options);
return result;
}

/**
Expand Down
12 changes: 11 additions & 1 deletion ghost/core/core/server/services/comments/CommentsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,20 @@ class CommentsService {
/**
* @param {any} options
*/
/**
* @param {any} options
*/
async getComments(options) {
this.checkEnabled();
const page = await this.models.Comment.findPage({...options, parentId: null});

const order = options.order || 'newest'; // Default to 'newest'
if (order === 'newest') {
options.order = 'created_at desc'; // Newest first
} else if (order === 'oldest') {
options.order = 'created_at asc'; // Oldest first
}
// Ensure other necessary options (like parentId)
const page = await this.models.Comment.findPage({...options, parentId: null});
return page;
}

Expand Down
Loading
Loading