0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2024-12-30 22:34:01 -05:00

Improved various aspects of comments app

ref https://linear.app/ghost/issue/PLG-300/

Full details available soon on https://ghost.org/changelog/

- removed `commentImprovements` labs flag conditionals
This commit is contained in:
Kevin Ansfield 2024-12-11 17:17:42 +00:00
parent 904a30082e
commit 3dd33968f5
87 changed files with 1205 additions and 1847 deletions

View file

@ -7,7 +7,7 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react';
import i18nLib from '@tryghost/i18n';
import setupGhostApi from './utils/api';
import {ActionHandler, SyncActionHandler, isSyncAction} from './actions';
import {AppContext, DispatchActionType, EditableAppContext, LabsContextType} from './AppContext';
import {AppContext, DispatchActionType, EditableAppContext} from './AppContext';
import {CommentsFrame} from './components/Frame';
import {setupAdminAPI} from './utils/adminApi';
import {useOptions} from './utils/options';
@ -44,8 +44,6 @@ const App: React.FC<AppProps> = ({scriptTag}) => {
});
}, [options]);
// const [adminApi, setAdminApi] = useState<AdminApi|null>(null);
const setState = useCallback((newState: Partial<EditableAppContext> | ((state: EditableAppContext) => Partial<EditableAppContext>)) => {
setFullState((state) => {
if (typeof newState === 'function') {
@ -114,7 +112,7 @@ const App: React.FC<AppProps> = ({scriptTag}) => {
let admin = null;
try {
admin = await adminApi.getUser();
if (admin && state.labs.commentImprovements) {
if (admin) {
// this is a bit of a hack, but we need to fetch the comments fully populated if the user is an admin
const adminComments = await adminApi.browse({page: 1, postId: options.postId, order: state.order, memberUuid: state.member?.uuid});
setState({
@ -141,14 +139,8 @@ const App: React.FC<AppProps> = ({scriptTag}) => {
};
/** Fetch first few comments */
const fetchComments = async (labs: LabsContextType) => {
let dataPromise;
if (labs?.commentImprovements) {
dataPromise = api.comments.browse({page: 1, postId: options.postId, order: state.order});
} else {
dataPromise = api.comments.browse({page: 1, postId: options.postId});
}
const fetchComments = async () => {
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]);
@ -165,15 +157,14 @@ const App: React.FC<AppProps> = ({scriptTag}) => {
try {
// Fetch data from API, links, preview, dev sources
const {member, labs} = await api.init();
const {comments, pagination, count} = await fetchComments(labs);
const order = labs.commentImprovements ? 'count__likes desc, created_at desc' : 'created_at desc';
const {comments, pagination, count} = await fetchComments();
const state = {
member,
initStatus: 'success',
comments,
pagination,
commentCount: count,
order,
order: 'count__likes desc, created_at desc',
labs: labs,
commentsIsLoading: false,
commentIdToHighlight: null

View file

@ -9,7 +9,7 @@ async function loadMoreComments({state, api, options, order}: {state: EditableAp
page = state.pagination.page + 1;
}
let data;
if (state.admin && state.adminApi && state.labs.commentImprovements) {
if (state.admin && state.adminApi) {
data = await state.adminApi.browse({page, postId: options.postId, order: order || state.order, memberUuid: state.member?.uuid});
} else {
data = await api.comments.browse({page, postId: options.postId, order: order || state.order});
@ -33,7 +33,7 @@ async function setOrder({state, data: {order}, options, api, dispatchAction}: {s
try {
let data;
if (state.admin && state.adminApi && state.labs.commentImprovements) {
if (state.admin && state.adminApi) {
data = await state.adminApi.browse({page: 1, postId: options.postId, order, memberUuid: state.member?.uuid});
} else {
data = await api.comments.browse({page: 1, postId: options.postId, order});
@ -54,7 +54,7 @@ async function setOrder({state, data: {order}, options, api, dispatchAction}: {s
async function loadMoreReplies({state, api, data: {comment, limit}, isReply}: {state: EditableAppContext, api: GhostApi, data: {comment: any, limit?: number | 'all'}, isReply: boolean}): Promise<Partial<EditableAppContext>> {
let data;
if (state.admin && state.adminApi && state.labs.commentImprovements && !isReply) { // we don't want the admin api to load reply data for replying to a reply, so we pass isReply: true
if (state.admin && state.adminApi && !isReply) { // we don't want the admin api to load reply data for replying to a reply, so we pass isReply: true
data = await state.adminApi.replies({commentId: comment.id, afterReplyId: comment.replies[comment.replies.length - 1]?.id, limit, memberUuid: state.member?.uuid});
} else {
data = await api.comments.replies({commentId: comment.id, afterReplyId: comment.replies[comment.replies.length - 1]?.id, limit});
@ -155,7 +155,7 @@ async function showComment({state, api, data: comment}: {state: EditableAppConte
// We need to refetch the comment, to make sure we have an up to date HTML content
// + all relations are loaded as the current member (not the admin)
let data;
if (state.admin && state.adminApi && state.labs.commentImprovements) {
if (state.admin && state.adminApi) {
data = await state.adminApi.read({commentId: comment.id, memberUuid: state.member?.uuid});
} else {
data = await api.comments.read(comment.id);
@ -279,60 +279,29 @@ async function deleteComment({state, api, data: comment}: {state: EditableAppCon
}
});
if (state.labs.commentImprovements) {
return {
comments: state.comments.map((c) => {
// If the comment has replies we want to keep it so the replies are
// still visible, but mark the comment as deleted. Otherwise remove it.
if (c.id === comment.id) {
if (c.replies.length > 0) {
return {
...c,
status: 'deleted'
};
} else {
return null; // Will be filtered out later
}
}
const updatedReplies = c.replies.filter(r => r.id !== comment.id);
return {
...c,
replies: updatedReplies
};
}).filter(Boolean),
commentCount: state.commentCount - 1
};
} else {
return {
comments: state.comments.map((c) => {
const replies = c.replies.map((r) => {
if (r.id === comment.id) {
return {
...r,
status: 'deleted'
};
}
return r;
});
if (c.id === comment.id) {
return {
comments: state.comments.map((c) => {
// If the comment has replies we want to keep it so the replies are
// still visible, but mark the comment as deleted. Otherwise remove it.
if (c.id === comment.id) {
if (c.replies.length > 0) {
return {
...c,
status: 'deleted',
replies
status: 'deleted'
};
} else {
return null; // Will be filtered out later
}
}
return {
...c,
replies
};
}),
commentCount: state.commentCount - 1
};
}
const updatedReplies = c.replies.filter(r => r.id !== comment.id);
return {
...c,
replies: updatedReplies
};
}).filter(Boolean),
commentCount: state.commentCount - 1
};
}
async function editComment({state, api, data: {comment, parent}}: {state: EditableAppContext, api: GhostApi, data: {comment: Partial<Comment> & {id: string}, parent?: Comment}}) {

View file

@ -32,7 +32,7 @@ describe('<CommentComponent>', function () {
const parent = buildComment({
replies: [reply1, reply2]
});
const appContext = {comments: [parent], labs: {commentImprovements: true}};
const appContext = {comments: [parent]};
contextualRender(<CommentComponent comment={reply2} parent={parent} />, {appContext});
@ -53,7 +53,7 @@ describe('<RepliedToSnippet>', function () {
const parent = buildComment({
replies: [reply1, reply2]
});
const appContext = {comments: [parent], labs: {commentImprovements: true}};
const appContext = {comments: [parent]};
contextualRender(<RepliedToSnippet comment={reply2} />, {appContext});
@ -74,7 +74,7 @@ describe('<RepliedToSnippet>', function () {
const parent = buildComment({
replies: [reply1, reply2]
});
const appContext = {comments: [parent], labs: {commentImprovements: true}};
const appContext = {comments: [parent]};
contextualRender(<RepliedToSnippet comment={reply2} />, {appContext});
@ -91,7 +91,7 @@ describe('<RepliedToSnippet>', function () {
const parent = buildComment({
replies: [reply2]
});
const appContext = {comments: [parent], labs: {commentImprovements: true}};
const appContext = {comments: [parent]};
contextualRender(<RepliedToSnippet comment={reply2} />, {appContext});

View file

@ -5,7 +5,7 @@ import Replies, {RepliesProps} from './Replies';
import ReplyButton from './buttons/ReplyButton';
import ReplyForm from './forms/ReplyForm';
import {Avatar, BlankAvatar} from './Avatar';
import {Comment, OpenCommentForm, useAppContext, useLabs} from '../../AppContext';
import {Comment, OpenCommentForm, useAppContext} from '../../AppContext';
import {Transition} from '@headlessui/react';
import {findCommentById, formatExplicitTime, getCommentInReplyToSnippet, getMemberNameFromComment} from '../../utils/helpers';
import {useCallback} from 'react';
@ -39,8 +39,7 @@ const AnimatedComment: React.FC<AnimatedCommentProps> = ({comment, parent}) => {
export const CommentComponent: React.FC<CommentProps> = ({comment, parent}) => {
const {dispatchAction, admin} = useAppContext();
const labs = useLabs();
const {showDeletedMessage, showHiddenMessage, showCommentContent} = useCommentVisibility(comment, admin, labs);
const {showDeletedMessage, showHiddenMessage, showCommentContent} = useCommentVisibility(comment, admin);
const openEditMode = useCallback(() => {
const newForm: OpenCommentForm = {
@ -53,39 +52,28 @@ export const CommentComponent: React.FC<CommentProps> = ({comment, parent}) => {
dispatchAction('openCommentForm', newForm);
}, [comment.id, dispatchAction]);
if (showDeletedMessage) {
if (showDeletedMessage || showHiddenMessage) {
return <UnpublishedComment comment={comment} openEditMode={openEditMode} />;
} else if (showCommentContent && !showHiddenMessage) {
return <PublishedComment comment={comment} openEditMode={openEditMode} parent={parent} />;
} else if (!labs.commentImprovements && comment.status !== 'published' || showHiddenMessage) {
return <UnpublishedComment comment={comment} openEditMode={openEditMode} />;
}
return null;
};
type CommentProps = AnimatedCommentProps;
const useCommentVisibility = (comment: Comment, admin: boolean, labs: {commentImprovements?: boolean}) => {
const useCommentVisibility = (comment: Comment, admin: boolean) => {
const hasReplies = comment.replies && comment.replies.length > 0;
const isDeleted = comment.status === 'deleted';
const isHidden = comment.status === 'hidden';
if (labs?.commentImprovements) {
return {
// Show deleted message only when comment has replies (regardless of admin status)
showDeletedMessage: isDeleted && hasReplies,
// Show hidden message for non-admins when comment has replies
showHiddenMessage: hasReplies && isHidden && !admin,
// Show comment content if not deleted AND (is published OR admin viewing hidden)
showCommentContent: !isDeleted && (admin || comment.status === 'published')
};
}
// Original behavior when labs is false
return {
showDeletedMessage: false,
showHiddenMessage: false,
showCommentContent: comment.status === 'published'
// Show deleted message only when comment has replies (regardless of admin status)
showDeletedMessage: isDeleted && hasReplies,
// Show hidden message for non-admins when comment has replies
showHiddenMessage: hasReplies && isHidden && !admin,
// Show comment content if not deleted AND (is published OR admin viewing hidden)
showCommentContent: !isDeleted && (admin || comment.status === 'published')
};
};
@ -94,10 +82,9 @@ type PublishedCommentProps = CommentProps & {
}
const PublishedComment: React.FC<PublishedCommentProps> = ({comment, parent, openEditMode}) => {
const {dispatchAction, openCommentForms, admin, commentIdToHighlight} = useAppContext();
const labs = useLabs();
// Determine if the comment should be displayed with reduced opacity
const isHidden = labs.commentImprovements && admin && comment.status === 'hidden';
const isHidden = admin && comment.status === 'hidden';
const hiddenClass = isHidden ? 'opacity-30' : '';
// Check if this comment is being edited
@ -171,11 +158,11 @@ type UnpublishedCommentProps = {
openEditMode: () => void;
}
const UnpublishedComment: React.FC<UnpublishedCommentProps> = ({comment, openEditMode}) => {
const {openCommentForms, t, labs, admin} = useAppContext();
const {admin, openCommentForms, t} = useAppContext();
const avatar = (labs.commentImprovements && admin && comment.status !== 'deleted') ?
<Avatar comment={comment} /> :
<BlankAvatar />;
const avatar = (admin && comment.status !== 'deleted')
? <Avatar comment={comment} />
: <BlankAvatar />;
const hasReplies = comment.replies && comment.replies.length > 0;
const notPublishedMessage = comment.status === 'hidden' ?
@ -322,10 +309,9 @@ type CommentHeaderProps = {
const CommentHeader: React.FC<CommentHeaderProps> = ({comment, className = ''}) => {
const {member, t} = useAppContext();
const labs = useLabs();
const createdAtRelative = useRelativeTime(comment.created_at);
const memberExpertise = member && comment.member && comment.member.uuid === member.uuid ? member.expertise : comment?.member?.expertise;
const isReplyToReply = labs.commentImprovements && comment.in_reply_to_id && comment.in_reply_to_snippet;
const isReplyToReply = comment.in_reply_to_id && comment.in_reply_to_snippet;
return (
<>
@ -395,39 +381,25 @@ type CommentMenuProps = {
parent?: Comment;
className?: string;
};
const CommentMenu: React.FC<CommentMenuProps> = ({comment, openReplyForm, highlightReplyButton, openEditMode, parent, className = ''}) => {
const {member, commentsEnabled, t, admin} = useAppContext();
const labs = useLabs();
const CommentMenu: React.FC<CommentMenuProps> = ({comment, openReplyForm, highlightReplyButton, openEditMode, className = ''}) => {
const {admin, t} = useAppContext();
const paidOnly = commentsEnabled === 'paid';
const isPaidMember = member && !!member.paid;
const canReply = member && (isPaidMember || !paidOnly) && (labs.commentImprovements ? true : !parent);
const isHiddenForAdmin = labs.commentImprovements && admin && comment.status === 'hidden';
if (isHiddenForAdmin) {
if (admin && comment.status === 'hidden') {
return (
<div className={`flex items-center gap-4 ${className}`}>
<span className="font-sans text-base leading-snug text-red-600 sm:text-sm">{t('Hidden for members')}</span>
{<MoreButton comment={comment} toggleEdit={openEditMode} />}
</div>
);
}
return (
labs.commentImprovements ? (
} else {
return (
<div className={`flex items-center gap-4 ${className}`}>
{<LikeButton comment={comment} />}
{<ReplyButton isReplying={highlightReplyButton} openReplyForm={openReplyForm} />}
{<MoreButton comment={comment} toggleEdit={openEditMode} />}
</div>
) : (
<div className={`flex items-center gap-4 ${className}`}>
{<LikeButton comment={comment} />}
{(canReply && <ReplyButton isReplying={highlightReplyButton} openReplyForm={openReplyForm} />)}
{<MoreButton comment={comment} toggleEdit={openEditMode} />}
</div>
)
);
);
}
};
//

View file

@ -44,10 +44,10 @@ describe('<Content>', function () {
expect(screen.queryByTestId('main-form')).toBeInTheDocument();
});
it('renders no CTA or form when a reply form is open', function () {
it('renders main form when a reply form is open', function () {
contextualRender(<Content />, {appContext: {member: {}, openFormCount: 1}});
expect(screen.queryByTestId('cta-box')).not.toBeInTheDocument();
expect(screen.queryByTestId('main-form')).not.toBeInTheDocument();
expect(screen.queryByTestId('main-form')).toBeInTheDocument();
});
});
});

View file

@ -10,16 +10,7 @@ import {useEffect} from 'react';
const Content = () => {
const labs = useLabs();
const {pagination, member, comments, commentCount, commentsEnabled, title, showCount, openFormCount, commentsIsLoading, t} = useAppContext();
let commentsElements;
const commentsDataset = comments;
if (labs && labs.commentImprovements) {
commentsElements = commentsDataset.slice().map(comment => <Comment key={comment.id} comment={comment} />);
} else {
commentsElements = commentsDataset.slice().reverse().map(comment => <Comment key={comment.id} comment={comment} />);
}
const {pagination, member, comments, commentCount, commentsEnabled, title, showCount, commentsIsLoading, t} = useAppContext();
useEffect(() => {
const elem = document.getElementById(ROOT_DIV_ID);
@ -42,60 +33,36 @@ const Content = () => {
const isPaidOnly = commentsEnabled === 'paid';
const isPaidMember = member && !!member.paid;
const isFirst = pagination?.total === 0;
const hasOpenReplyForms = openFormCount > 0;
const commentsComponents = comments.slice().map(comment => <Comment key={comment.id} comment={comment} />);
return (
labs.commentImprovements ? (
<>
<ContentTitle count={commentCount} showCount={showCount} title={title}/>
<div>
{(member && (isPaidMember || !isPaidOnly)) ? (
<MainForm commentsCount={comments.length} />
) : (
<section className="flex flex-col items-center py-6 sm:px-8 sm:py-10" data-testid="cta-box">
<CTABox isFirst={isFirst} isPaid={isPaidOnly} />
</section>
)}
</div>
{commentCount > 1 && (
<div className="z-20 mb-7 mt-3">
<span className="flex items-center gap-1.5 text-sm font-medium text-neutral-900 dark:text-neutral-100">
{t('Sort by')}: <SortingForm/>
</span>
</div>
<>
<ContentTitle count={commentCount} showCount={showCount} title={title}/>
<div>
{(member && (isPaidMember || !isPaidOnly)) ? (
<MainForm commentsCount={comments.length} />
) : (
<section className="flex flex-col items-center py-6 sm:px-8 sm:py-10" data-testid="cta-box">
<CTABox isFirst={isFirst} isPaid={isPaidOnly} />
</section>
)}
<div className={`z-10 transition-opacity duration-100 ${commentsIsLoading ? 'opacity-50' : ''}`} data-testid="comment-elements">
{commentsElements}
</div>
{commentCount > 1 && (
<div className="z-20 mb-7 mt-3">
<span className="flex items-center gap-1.5 text-sm font-medium text-neutral-900 dark:text-neutral-100">
{t('Sort by')}: <SortingForm/>
</span>
</div>
<Pagination />
{
labs?.testFlag ? <div data-testid="this-comes-from-a-flag" style={{display: 'none'}}></div> : null
}
</>
) : (
<>
<ContentTitle count={commentCount} showCount={showCount} title={title}/>
<Pagination />
<div className={!pagination ? 'mt-4' : ''} data-testid="comment-elements">
{commentsElements}
</div>
<div>
{!hasOpenReplyForms
? (member ? (isPaidMember || !isPaidOnly ? <MainForm commentsCount={commentCount} /> :
<section className={`flex flex-col items-center pt-[40px] ${member ? 'pb-[32px]' : 'pb-[48px]'} ${!isFirst && 'mt-4'} ${(!member || (member && isPaidOnly)) && commentCount ? 'border-t' : 'border-none'} border-[rgba(0,0,0,0.075)] sm:px-8 dark:border-[rgba(255,255,255,0.1)]`} data-testid="cta-box">
<CTABox isFirst={isFirst} isPaid={isPaidOnly} />
</section>) :
<section className={`flex flex-col items-center pt-[40px] ${member ? 'pb-[32px]' : 'pb-[48px]'} ${!isFirst && 'mt-4'} ${(!member || (member && isPaidOnly)) && commentCount ? 'border-t' : 'border-none'} border-[rgba(0,0,0,0.075)] sm:px-8 dark:border-[rgba(255,255,255,0.1)]`} data-testid="cta-box">
<CTABox isFirst={isFirst} isPaid={isPaidOnly} />
</section>)
: null
}
</div>
{
labs?.testFlag ? <div data-testid="this-comes-from-a-flag" style={{display: 'none'}}></div> : null // do not remove
}
</>
)
)}
<div className={`z-10 transition-opacity duration-100 ${commentsIsLoading ? 'opacity-50' : ''}`} data-testid="comment-elements">
{commentsComponents}
</div>
<Pagination />
{
labs?.testFlag ? <div data-testid="this-comes-from-a-flag" style={{display: 'none'}}></div> : null
}
</>
);
};

View file

@ -0,0 +1,32 @@
import Pagination from './Pagination';
import {AppContext} from '../../AppContext';
import {render, screen} from '@testing-library/react';
const contextualRender = (ui, {appContext, ...renderOptions}) => {
const contextWithDefaults = {
t: (str, replacements) => {
if (replacements) {
return str.replace(/{{([^{}]*)}}/g, (_, key) => replacements[key]);
}
return str;
},
...appContext
};
return render(
<AppContext.Provider value={contextWithDefaults}>{ui}</AppContext.Provider>,
renderOptions
);
};
describe('<Pagination>', function () {
it('has correct text for 1 more', function () {
contextualRender(<Pagination />, {appContext: {pagination: {total: 4, page: 1, limit: 3}}});
expect(screen.getByText('Load more (1)')).toBeInTheDocument();
});
it('has correct text for x more', function () {
contextualRender(<Pagination />, {appContext: {pagination: {total: 6, page: 1, limit: 3}}});
expect(screen.getByText('Load more (3)')).toBeInTheDocument();
});
});

View file

@ -1,9 +1,8 @@
import {formatNumber} from '../../utils/helpers';
import {useAppContext, useLabs} from '../../AppContext';
import {useAppContext} from '../../AppContext';
const Pagination = () => {
const {pagination, dispatchAction, t} = useAppContext();
const labs = useLabs();
const loadMore = () => {
dispatchAction('loadMoreComments', {});
@ -13,27 +12,18 @@ const Pagination = () => {
return null;
}
const left = pagination.total - pagination.page * pagination.limit;
const commentsLeft = pagination.total - pagination.page * pagination.limit;
if (left <= 0) {
if (commentsLeft <= 0) {
return null;
}
// TODO: add i18n support for these strings when removing labs flag
const text = labs.commentImprovements
? (left === 1 ? 'Load more (1)' : `Load more (${formatNumber(left)})`)
: (left === 1 ? t('Show 1 previous comment') : t('Show {{amount}} previous comments', {amount: formatNumber(left)}));
const text = t(`Load more ({{amount}})`, {amount: formatNumber(commentsLeft)});
return (
labs.commentImprovements ? (
<button className="text-md group mb-10 flex items-center px-0 pb-2 pt-0 text-left font-sans font-semibold text-neutral-700 dark:text-white" data-testid="pagination-component" type="button" onClick={loadMore}>
<span className="flex h-[40px] items-center justify-center whitespace-nowrap rounded-[6px] bg-black/5 px-4 py-2 text-center font-sans text-sm font-semibold text-neutral-700 outline-0 transition-all duration-150 hover:bg-black/10 dark:bg-white/15 dark:text-neutral-300 dark:hover:bg-white/20 dark:hover:text-neutral-100">{text}</span>
</button>
) : (
<button className="text-md group mb-10 flex w-full items-center px-0 pb-2 pt-0 text-left font-sans font-semibold text-neutral-700 dark:text-white" data-testid="pagination-component" type="button" onClick={loadMore}>
<span className="flex h-[40px] w-full items-center justify-center whitespace-nowrap rounded-[6px] bg-black/5 px-3 py-2 text-center font-sans text-sm font-semibold text-neutral-700 outline-0 transition-all duration-150 hover:bg-black/10 dark:bg-white/15 dark:text-neutral-300 dark:hover:bg-white/20 dark:hover:text-neutral-100"> {text}</span>
</button>
)
<button className="text-md group mb-10 flex items-center px-0 pb-2 pt-0 text-left font-sans font-semibold text-neutral-700 dark:text-white" data-testid="pagination-component" type="button" onClick={loadMore}>
<span className="flex h-[40px] items-center justify-center whitespace-nowrap rounded-[6px] bg-black/5 px-4 py-2 text-center font-sans text-sm font-semibold text-neutral-700 outline-0 transition-all duration-150 hover:bg-black/10 dark:bg-white/15 dark:text-neutral-300 dark:hover:bg-white/20 dark:hover:text-neutral-100">{text}</span>
</button>
);
};

View file

@ -1,4 +1,4 @@
import {Comment, useAppContext, useLabs} from '../../../AppContext';
import {Comment, useAppContext} from '../../../AppContext';
import {ReactComponent as LikeIcon} from '../../../images/icons/like.svg';
import {useState} from 'react';
@ -7,7 +7,6 @@ type Props = {
};
const LikeButton: React.FC<Props> = ({comment}) => {
const {dispatchAction, member, commentsEnabled} = useAppContext();
const labs = useLabs();
const [animationClass, setAnimation] = useState('');
const paidOnly = commentsEnabled === 'paid';
@ -15,13 +14,11 @@ const LikeButton: React.FC<Props> = ({comment}) => {
const canLike = member && (isPaidMember || !paidOnly);
const toggleLike = () => {
if (!canLike && labs && labs.commentImprovements) {
if (!canLike) {
dispatchAction('openPopup', {
type: 'ctaPopup'
});
return;
} else if (!canLike) {
return;
}
if (!comment.liked) {
@ -35,39 +32,22 @@ const LikeButton: React.FC<Props> = ({comment}) => {
}
};
// If can like: use <button> element, otherwise use a <span>
const CustomTag = canLike ? `button` : `span`;
let likeCursor = 'cursor-pointer';
if (!canLike) {
likeCursor = 'cursor-text';
}
if (labs && labs.commentImprovements) {
return (
<button
className={`duration-50 group flex cursor-pointer items-center font-sans text-base outline-0 transition-all ease-linear sm:text-sm ${
comment.liked ? 'text-black/90 dark:text-white/90' : 'text-black/50 hover:text-black/75 dark:text-white/60 dark:hover:text-white/75'
}`}
data-testid="like-button"
type="button"
onClick={toggleLike}
>
<LikeIcon
className={animationClass + ` mr-[6px] ${
comment.liked ? 'fill-black dark:fill-white stroke-black dark:stroke-white' : 'stroke-black/50 group-hover:stroke-black/75 dark:stroke-white/60 dark:group-hover:stroke-white/75'
} ${!comment.liked && canLike && 'group-hover:stroke-black/75 dark:group-hover:stroke-white/75'} transition duration-50 ease-linear`}
/>
{comment.count.likes}
</button>
);
}
return (
<CustomTag className={`duration-50 group flex items-center font-sans text-base outline-0 transition-all ease-linear sm:text-sm ${comment.liked ? 'text-black/90 dark:text-white/90' : 'text-black/50 hover:text-black/75 dark:text-white/60 dark:hover:text-white/75'} ${likeCursor}`} data-testid="like-button" type="button" onClick={toggleLike}>
<LikeIcon className={animationClass + ` mr-[6px] ${comment.liked ? 'fill-black dark:fill-white stroke-black dark:stroke-white' : 'stroke-black/50 group-hover:stroke-black/75 dark:stroke-white/60 dark:group-hover:stroke-white/75'} ${!comment.liked && canLike && 'group-hover:stroke-black/75 dark:group-hover:stroke-white/75'} transition duration-50 ease-linear`} />
<button
className={`duration-50 group flex cursor-pointer items-center font-sans text-base outline-0 transition-all ease-linear sm:text-sm ${
comment.liked ? 'text-black/90 dark:text-white/90' : 'text-black/50 hover:text-black/75 dark:text-white/60 dark:hover:text-white/75'
}`}
data-testid="like-button"
type="button"
onClick={toggleLike}
>
<LikeIcon
className={animationClass + ` mr-[6px] ${
comment.liked ? 'fill-black dark:fill-white stroke-black dark:stroke-white' : 'stroke-black/50 group-hover:stroke-black/75 dark:stroke-white/60 dark:group-hover:stroke-white/75'
} ${!comment.liked && canLike && 'group-hover:stroke-black/75 dark:group-hover:stroke-white/75'} transition duration-50 ease-linear`}
/>
{comment.count.likes}
</CustomTag>
</button>
);
};

View file

@ -1,5 +1,5 @@
import {ReactComponent as ReplyIcon} from '../../../images/icons/reply.svg';
import {useAppContext, useLabs} from '../../../AppContext';
import {useAppContext} from '../../../AppContext';
type Props = {
disabled?: boolean;
@ -9,14 +9,13 @@ type Props = {
const ReplyButton: React.FC<Props> = ({disabled, isReplying, openReplyForm}) => {
const {member, t, dispatchAction, commentsEnabled} = useAppContext();
const labs = useLabs();
const paidOnly = commentsEnabled === 'paid';
const isPaidMember = member && !!member.paid;
const canReply = member && (isPaidMember || !paidOnly);
const handleClick = () => {
if (!canReply && labs && labs.commentImprovements) {
if (!canReply) {
dispatchAction('openPopup', {
type: 'ctaPopup'
});
@ -25,10 +24,6 @@ const ReplyButton: React.FC<Props> = ({disabled, isReplying, openReplyForm}) =>
openReplyForm();
};
if (!member && !labs?.commentImprovements) {
return null;
}
return (
<button
className={`duration-50 group flex items-center font-sans text-base outline-0 transition-all ease-linear sm:text-sm ${isReplying ? 'text-black/90 dark:text-white/90' : 'text-black/50 hover:text-black/75 dark:text-white/60 dark:hover:text-white/75'}`}

View file

@ -1,5 +1,5 @@
import React from 'react';
import {Comment, useAppContext, useLabs} from '../../../AppContext';
import {Comment, useAppContext} from '../../../AppContext';
type Props = {
comment: Comment;
@ -8,17 +8,12 @@ type Props = {
};
const AuthorContextMenu: React.FC<Props> = ({comment, close, toggleEdit}) => {
const {dispatchAction, t} = useAppContext();
const labs = useLabs();
const deleteComment = () => {
if (labs.commentImprovements) {
dispatchAction('openPopup', {
type: 'deletePopup',
comment
});
} else {
dispatchAction('deleteComment', comment);
}
dispatchAction('openPopup', {
type: 'deletePopup',
comment
});
close();
};

View file

@ -26,7 +26,7 @@ describe('<CommentContextMenu>', () => {
it('has display-below classes when in viewport', () => {
const comment = buildComment();
contextualRender(<CommentContextMenu comment={comment} />, {appContext: {admin: true, labs: {commentImprovements: true}}});
contextualRender(<CommentContextMenu comment={comment} />, {appContext: {admin: true}});
expect(screen.getByTestId('comment-context-menu-inner')).toHaveClass('top-0');
});
@ -34,7 +34,7 @@ describe('<CommentContextMenu>', () => {
sinon.stub(HTMLElement.prototype, 'getBoundingClientRect').returns({bottom: 2000});
const comment = buildComment();
contextualRender(<CommentContextMenu comment={comment} />, {appContext: {admin: true, labs: {commentImprovements: true}}});
contextualRender(<CommentContextMenu comment={comment} />, {appContext: {admin: true}});
expect(screen.getByTestId('comment-context-menu-inner')).toHaveClass('bottom-full', 'mb-6');
});
});

View file

@ -1,7 +1,7 @@
import AdminContextMenu from './AdminContextMenu';
import AuthorContextMenu from './AuthorContextMenu';
import NotAuthorContextMenu from './NotAuthorContextMenu';
import {Comment, useAppContext, useLabs} from '../../../AppContext';
import {Comment, useAppContext} from '../../../AppContext';
import {useEffect, useRef} from 'react';
import {useOutOfViewportClasses} from '../../../utils/hooks';
@ -16,10 +16,8 @@ const CommentContextMenu: React.FC<Props> = ({comment, close, toggleEdit}) => {
const isAdmin = !!admin;
const element = useRef<HTMLDivElement>(null);
const innerElement = useRef<HTMLDivElement>(null);
const labs = useLabs();
// By default display dropdown below but move above if that renders off-screen
// NOTE: innerElement ref is only set when commentImprovements flag is enabled
useOutOfViewportClasses(innerElement, {
bottom: {
default: 'top-0',
@ -88,19 +86,11 @@ const CommentContextMenu: React.FC<Props> = ({comment, close, toggleEdit}) => {
}
return (
labs.commentImprovements ? (
<div ref={element} className="relative" data-testid="comment-context-menu" onClick={stopPropagation}>
<div ref={innerElement} className={`absolute z-10 min-w-min whitespace-nowrap rounded bg-white p-1 font-sans text-sm shadow-lg outline-0 sm:min-w-[80px] dark:bg-neutral-800 dark:text-white`} data-testid="comment-context-menu-inner">
{contextMenu}
</div>
<div ref={element} className="relative" data-testid="comment-context-menu" onClick={stopPropagation}>
<div ref={innerElement} className={`absolute z-10 min-w-min whitespace-nowrap rounded bg-white p-1 font-sans text-sm shadow-lg outline-0 sm:min-w-[80px] dark:bg-neutral-800 dark:text-white`} data-testid="comment-context-menu-inner">
{contextMenu}
</div>
) : (
<div ref={element} onClick={stopPropagation}>
<div className="absolute z-10 min-w-min whitespace-nowrap rounded bg-white p-1 font-sans text-sm shadow-lg outline-0 sm:min-w-[80px] dark:bg-neutral-800 dark:text-white">
{contextMenu}
</div>
</div>
)
</div>
);
};

View file

@ -1,6 +1,6 @@
import React from 'react';
import {Avatar} from '../Avatar';
import {Comment, OpenCommentForm, useAppContext, useLabs} from '../../../AppContext';
import {Comment, OpenCommentForm, useAppContext} from '../../../AppContext';
import {ReactComponent as EditIcon} from '../../../images/icons/edit.svg';
import {Editor, EditorContent} from '@tiptap/react';
import {ReactComponent as SpinnerIcon} from '../../../images/icons/spinner.svg';
@ -25,7 +25,6 @@ export type FormEditorProps = {
};
export const FormEditor: React.FC<FormEditorProps> = ({comment, submit, progress, setProgress, close, isOpen, editor, submitText, submitSize, openForm}) => {
const labs = useLabs();
const {dispatchAction, t} = useAppContext();
let buttonIcon = null;
@ -55,7 +54,7 @@ export const FormEditor: React.FC<FormEditorProps> = ({comment, submit, progress
}, [editor, comment, openForm, dispatchAction]);
if (progress === 'sending') {
buttonIcon = <SpinnerIcon className={`h-[24px] w-[24px] fill-white ${labs.commentImprovements ? '' : 'dark:fill-black'}`} data-testid="button-spinner" />;
buttonIcon = <SpinnerIcon className={`h-[24px] w-[24px] fill-white`} data-testid="button-spinner" />;
}
const stopIfFocused = useCallback((event) => {
@ -144,28 +143,16 @@ export const FormEditor: React.FC<FormEditorProps> = ({comment, submit, progress
{close &&
<button className="ml-2.5 font-sans text-sm font-medium text-neutral-900/50 outline-0 transition-all hover:text-neutral-900/70 dark:text-white/60 dark:hover:text-white/75" type="button" onClick={close}>{t('Cancel')}</button>
}
{labs.commentImprovements ? (
<button
className={`flex w-auto items-center justify-center ${submitSize === 'medium' && 'sm:min-w-[100px]'} ${submitSize === 'small' && 'sm:min-w-[64px]'} h-[40px] rounded-md bg-[var(--gh-accent-color)] px-3 py-2 text-center font-sans text-base font-medium text-white outline-0 transition-colors duration-200 hover:brightness-105 disabled:bg-black/5 disabled:text-neutral-900/30 sm:text-sm dark:disabled:bg-white/15 dark:disabled:text-white/35`}
data-testid="submit-form-button"
disabled={!editor || editor.isEmpty}
type="button"
onClick={submitForm}
>
{buttonIcon && <span className="mr-1">{buttonIcon}</span>}
{submitText && <span>{submitText}</span>}
</button>
) : (
<button
className={`flex w-auto items-center justify-center ${submitSize === 'medium' && 'sm:min-w-[100px]'} ${submitSize === 'small' && 'sm:min-w-[64px]'} h-[40px] rounded-[6px] bg-neutral-900 px-3 py-2 text-center font-sans text-base font-medium text-white/95 outline-0 transition-all duration-150 hover:bg-black hover:text-white sm:text-sm dark:bg-white/95 dark:text-neutral-800 dark:hover:bg-white dark:hover:text-neutral-900`}
data-testid="submit-form-button"
type="button"
onClick={submitForm}
>
<span>{buttonIcon}</span>
{submitText && <span>{submitText}</span>}
</button>
)}
<button
className={`flex w-auto items-center justify-center ${submitSize === 'medium' && 'sm:min-w-[100px]'} ${submitSize === 'small' && 'sm:min-w-[64px]'} h-[40px] rounded-md bg-[var(--gh-accent-color)] px-3 py-2 text-center font-sans text-base font-medium text-white outline-0 transition-colors duration-200 hover:brightness-105 disabled:bg-black/5 disabled:text-neutral-900/30 sm:text-sm dark:disabled:bg-white/15 dark:disabled:text-white/35`}
data-testid="submit-form-button"
disabled={!editor || editor.isEmpty}
type="button"
onClick={submitForm}
>
{buttonIcon && <span className="mr-1">{buttonIcon}</span>}
{submitText && <span>{submitText}</span>}
</button>
</div>
</>
);
@ -183,9 +170,8 @@ type FormHeaderProps = {
const FormHeader: React.FC<FormHeaderProps> = ({show, name, expertise, replyingToText, editName, editExpertise}) => {
const {t} = useAppContext();
const labs = useLabs();
const isReplyingToReply = labs.commentImprovements && replyingToText;
const isReplyingToReply = !!replyingToText;
return (
<Transition
@ -320,14 +306,13 @@ const FormWrapper: React.FC<FormWrapperProps> = ({
children
}) => {
const {member, dispatchAction} = useAppContext();
const labs = useLabs();
const memberName = member?.name ?? comment?.member?.name;
const memberExpertise = member?.expertise ?? comment?.member?.expertise;
let openStyles = '';
if (isOpen) {
const isReplyToReply = labs.commentImprovements && !!openForm?.in_reply_to_snippet;
const isReplyToReply = !!openForm?.in_reply_to_snippet;
openStyles = isReplyToReply ? 'pl-[1px] pt-[68px] sm:pl-[44px] sm:pt-[56px]' : 'pl-[1px] pt-[48px] sm:pl-[44px] sm:pt-[40px]';
}

View file

@ -10,9 +10,8 @@ test.describe('Actions', async () => {
mockedApi,
page,
publication: 'Publisher Weekly',
labs: {
commentImprovements: labs
}
// always return `labs` value for any labs.x property access
labs: new Proxy({}, {get: () => labs})
});
}
@ -26,6 +25,7 @@ test.describe('Actions', async () => {
});
test('Can like and unlike a comment', async ({page}) => {
// NOTE: comments are ordered by likes
mockedApi.addComment({
html: '<p>This is comment 1</p>'
});
@ -43,7 +43,7 @@ test.describe('Actions', async () => {
const {frame} = await initializeTest(page);
// Check like button is not filled yet
const comment = frame.getByTestId('comment-component').nth(0);
const comment = frame.getByTestId('comment-component').nth(1);
const likeButton = comment.getByTestId('like-button');
await expect(likeButton).toHaveCount(1);
@ -65,7 +65,7 @@ test.describe('Actions', async () => {
await expect(likeButton).toHaveText('0');
// Check state for already liked comment
const secondComment = frame.getByTestId('comment-component').nth(1);
const secondComment = frame.getByTestId('comment-component').nth(0);
const likeButton2 = secondComment.getByTestId('like-button');
await expect(likeButton2).toHaveCount(1);
const icon2 = likeButton2.locator('svg');
@ -120,7 +120,7 @@ test.describe('Actions', async () => {
// Click button
await replyButton.click();
const editor = frame.getByTestId('form-editor');
const editor = comment.getByTestId('form-editor');
await expect(editor).toBeVisible();
// Wait for focused
await waitEditorFocused(editor);
@ -145,28 +145,8 @@ test.describe('Actions', async () => {
await expect(frame.getByText('This is a reply 123')).toHaveCount(1);
});
test('Reply-to-reply action not shown without labs flag', async ({
page
}) => {
mockedApi.addComment({
html: '<p>This is comment 1</p>',
replies: [
mockedApi.buildReply({
html: '<p>This is a reply to 1</p>'
})
]
});
const {frame} = await initializeTest(page);
const parentComment = frame.getByTestId('comment-component').nth(0);
const replyComment = parentComment.getByTestId('comment-component').nth(0);
expect(replyComment.getByTestId('reply-button')).not.toBeVisible();
});
async function testReplyToReply(page) {
const {frame} = await initializeTest(page, {labs: true});
const {frame} = await initializeTest(page);
const parentComment = frame.getByTestId('comment-component').nth(0);
const replyComment = parentComment.getByTestId('comment-component').nth(0);
@ -174,7 +154,7 @@ test.describe('Actions', async () => {
const replyReplyButton = replyComment.getByTestId('reply-button');
await replyReplyButton.click();
const editor = frame.getByTestId('form-editor').nth(1);
const editor = parentComment.getByTestId('form-editor');
await expect(editor).toBeVisible();
await waitEditorFocused(editor);
@ -218,6 +198,20 @@ test.describe('Actions', async () => {
await testReplyToReply(page);
});
test('Can reply to a reply with a deleted parent comment', async function ({page}) {
mockedApi.addComment({
html: '<p>This is comment 1</p>',
status: 'deleted',
replies: [
mockedApi.buildReply({
html: '<p>This is a reply to 1</p>'
})
]
});
await testReplyToReply(page);
});
test('Can highlight reply when clicking on reply to: snippet', async ({page}) => {
mockedApi.addComment({
html: '<p>This is comment 1</p>',
@ -235,7 +229,7 @@ test.describe('Actions', async () => {
]
});
const {frame} = await initializeTest(page, {labs: true});
const {frame} = await initializeTest(page);
await frame.getByTestId('comment-in-reply-to').click();
@ -274,7 +268,7 @@ test.describe('Actions', async () => {
]
});
const {frame} = await initializeTest(page, {labs: true});
const {frame} = await initializeTest(page);
await frame.getByTestId('comment-in-reply-to').click();
@ -300,20 +294,6 @@ test.describe('Actions', async () => {
await expect(markElement).not.toBeVisible();
});
test('Can reply to a reply with a deleted parent comment', async function ({page}) {
mockedApi.addComment({
html: '<p>This is comment 1</p>',
status: 'deleted',
replies: [
mockedApi.buildReply({
html: '<p>This is a reply to 1</p>'
})
]
});
await testReplyToReply(page);
});
test('Can add expertise', async ({page}) => {
mockedApi.setMember({name: 'John Doe', expertise: null});
@ -370,7 +350,7 @@ test.describe('Actions', async () => {
member: loggedInMember
});
const {frame} = await initializeTest(page, {labs: true});
const {frame} = await initializeTest(page);
const comment = frame.getByTestId('comment-component').nth(0);
const moreButton = comment.getByTestId('more-button').first();
@ -399,7 +379,7 @@ test.describe('Actions', async () => {
]
});
const {frame} = await initializeTest(page, {labs: true});
const {frame} = await initializeTest(page);
const comment = frame.getByTestId('comment-component').nth(0);
const reply = comment.getByTestId('comment-component').nth(0);
@ -430,7 +410,7 @@ test.describe('Actions', async () => {
]
});
const {frame} = await initializeTest(page, {labs: true});
const {frame} = await initializeTest(page);
const comment = frame.getByTestId('comment-component').nth(0);
const moreButton = comment.getByTestId('more-button').first();
@ -471,7 +451,7 @@ test.describe('Actions', async () => {
html: '<p>This is comment 6</p>'
});
const {frame} = await initializeTest(page, {labs: true});
const {frame} = await initializeTest(page);
const sortingForm = frame.getByTestId('comments-sorting-form');
@ -501,7 +481,7 @@ test.describe('Actions', async () => {
created_at: new Date('2022-02-01T00:00:00Z')
});
const {frame} = await initializeTest(page, {labs: true});
const {frame} = await initializeTest(page);
const sortingForm = frame.getByTestId('comments-sorting-form');
@ -538,7 +518,7 @@ test.describe('Actions', async () => {
html: '<p>This is comment 6</p>'
});
const {frame} = await initializeTest(page, {labs: true});
const {frame} = await initializeTest(page);
const sortingForm = frame.getByTestId('comments-sorting-form');
@ -575,7 +555,7 @@ test.describe('Actions', async () => {
created_at: new Date('2024-04-03T00:00:00Z')
});
const {frame} = await initializeTest(page, {labs: true});
const {frame} = await initializeTest(page);
const sortingForm = await frame.getByTestId('comments-sorting-form');
@ -618,7 +598,7 @@ test.describe('Actions', async () => {
created_at: new Date('2024-04-03T00:00:00Z')
});
const {frame} = await initializeTest(page, {labs: true});
const {frame} = await initializeTest(page);
const sortingForm = await frame.getByTestId('comments-sorting-form');
@ -657,7 +637,7 @@ test.describe('Actions', async () => {
created_at: new Date('2024-04-03T00:00:00Z')
});
const {frame} = await initializeTest(page, {labs: true});
const {frame} = await initializeTest(page);
const sortingForm = await frame.getByTestId('comments-sorting-form');
@ -720,7 +700,7 @@ test.describe('Actions', async () => {
await frame.getByTestId('edit').click();
// Verify the edit form is visible
await expect(frame.getByTestId('form-editor')).toBeVisible();
await expect(parentComment.getByTestId('form-editor')).toBeVisible();
// Verify replies are still visible while editing
await expect(replies[0]).toBeVisible();

View file

@ -28,7 +28,7 @@ test.describe('Admin moderation', async () => {
mockedApi.setMember(options.member);
if (options.labs) {
mockedApi.setLabs({commentImprovements: true});
// enable specific labs flags here
}
return await initialize({
@ -39,7 +39,7 @@ test.describe('Admin moderation', async () => {
count: true,
admin,
labs: {
commentImprovements: options.labs
// enable specific labs flags here
}
});
}
@ -101,241 +101,216 @@ test.describe('Admin moderation', async () => {
await expect(frame.getByTestId('hide-button')).toBeVisible();
});
test('can hide and show comments', async ({page}) => {
test('member uuid are passed to admin browse api params', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>'});
mockedApi.addComment({html: '<p>This is comment 2</p>'});
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
const {frame} = await initializeTest(page);
const comments = await frame.getByTestId('comment-component');
await expect(comments).toHaveCount(1);
expect(adminBrowseSpy.called).toBe(true);
const lastCall = adminBrowseSpy.lastCall.args[0];
const url = new URL(lastCall.request().url());
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
});
test('member uuid gets set when loading more comments', async ({page}) => {
// create 25 comments
for (let i = 0; i < 25; i++) {
mockedApi.addComment({html: `<p>This is comment ${i}</p>`});
}
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
const {frame} = await initializeTest(page);
await frame.getByTestId('pagination-component').click();
const lastCall = adminBrowseSpy.lastCall.args[0];
const url = new URL(lastCall.request().url());
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
});
test('member uuid gets set when changing order', async ({page}) => {
mockedApi.addComment({
html: '<p>This is the oldest</p>',
created_at: new Date('2024-02-01T00:00:00Z')
});
mockedApi.addComment({
html: '<p>This is comment 2</p>',
created_at: new Date('2024-03-02T00:00:00Z')
});
mockedApi.addComment({
html: '<p>This is the newest comment</p>',
created_at: new Date('2024-04-03T00:00:00Z')
});
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
const {frame} = await initializeTest(page);
// Click the hide button for 2nd comment
const sortingForm = await frame.getByTestId('comments-sorting-form');
await sortingForm.click();
const sortingDropdown = await frame.getByTestId(
'comments-sorting-form-dropdown'
);
const optionSelect = await sortingDropdown.getByText('Newest');
mockedApi.setDelay(100);
await optionSelect.click();
const lastCall = adminBrowseSpy.lastCall.args[0];
const url = new URL(lastCall.request().url());
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
});
test('member uuid gets set when loading more replies', async ({page}) => {
mockedApi.addComment({
html: '<p>This is comment 1</p>',
replies: [
buildReply({html: '<p>This is reply 1</p>'}),
buildReply({html: '<p>This is reply 2</p>'}),
buildReply({html: '<p>This is reply 3</p>'}),
buildReply({html: '<p>This is reply 4</p>'}),
buildReply({html: '<p>This is reply 5</p>'}),
buildReply({html: '<p>This is reply 6</p>'})
]
});
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'getReplies');
const {frame} = await initializeTest(page);
const comments = await frame.getByTestId('comment-component');
const comment = comments.nth(0);
await comment.getByTestId('reply-pagination-button').click();
const lastCall = adminBrowseSpy.lastCall.args[0];
const url = new URL(lastCall.request().url());
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
});
test('member uuid gets set when reading a comment (after unhiding)', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>'});
mockedApi.addComment({html: '<p>This is comment 2</p>', status: 'hidden'});
const adminReadSpy = sinon.spy(mockedApi.adminRequestHandlers, 'getOrUpdateComment');
const {frame} = await initializeTest(page);
const comments = await frame.getByTestId('comment-component');
await expect(comments).toHaveCount(2);
await expect(comments.nth(1)).toContainText('Hidden for members');
const moreButtons = comments.nth(1).getByTestId('more-button');
await moreButtons.click();
await moreButtons.getByTestId('show-button').click();
await expect(comments.nth(1)).not.toContainText('Hidden for members');
const lastCall = adminReadSpy.lastCall.args[0];
const url = new URL(lastCall.request().url());
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
});
test('hidden comments are not displayed for non-admins', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>'});
mockedApi.addComment({html: '<p>This is comment 2</p>', status: 'hidden'});
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
const {frame} = await initializeTest(page, {isAdmin: false});
const comments = await frame.getByTestId('comment-component');
await expect(comments).toHaveCount(1);
expect(adminBrowseSpy.called).toBe(false);
});
test('hidden comments are displayed for admins', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>'});
mockedApi.addComment({html: '<p>This is comment 2</p>', status: 'hidden'});
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
const {frame} = await initializeTest(page);
const comments = await frame.getByTestId('comment-component');
await expect(comments).toHaveCount(2);
await expect(comments.nth(1)).toContainText('Hidden for members');
expect(adminBrowseSpy.called).toBe(true);
});
test('can hide and show comments', async ({page}) => {
[1,2].forEach(i => mockedApi.addComment({html: `<p>This is comment ${i}</p>`}));
const {frame} = await initializeTest(page);
const comments = await frame.getByTestId('comment-component');
// Hide the 2nd comment
const moreButtons = frame.getByTestId('more-button');
await moreButtons.nth(1).click();
await moreButtons.nth(1).getByTestId('hide-button').click();
await moreButtons.nth(1).getByText('Hide comment').click();
// comment becomes hidden
const comments = frame.getByTestId('comment-component');
const secondComment = comments.nth(1);
await expect(secondComment).toContainText('This comment has been hidden.');
await expect(secondComment).not.toContainText('This is comment 2');
await expect(secondComment).toContainText('Hidden for members');
// can show it again
// Check can show it again
await moreButtons.nth(1).click();
await moreButtons.nth(1).getByTestId('show-button').click();
await moreButtons.nth(1).getByText('Show comment').click();
await expect(secondComment).toContainText('This is comment 2');
});
test.describe('commentImprovements', function () {
test('memeber uuid are passed to admin browse api params', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>'});
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
const {frame} = await initializeTest(page, {labs: true});
const comments = await frame.getByTestId('comment-component');
await expect(comments).toHaveCount(1);
expect(adminBrowseSpy.called).toBe(true);
const lastCall = adminBrowseSpy.lastCall.args[0];
const url = new URL(lastCall.request().url());
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
test('can hide and show replies', async ({page}) => {
mockedApi.addComment({
id: '1',
html: '<p>This is comment 1</p>',
replies: [
buildReply({id: '2', html: '<p>This is reply 1</p>'}),
buildReply({id: '3', html: '<p>This is reply 2</p>'})
]
});
test('member uuid gets set when loading more comments', async ({page}) => {
// create 25 comments
for (let i = 0; i < 25; i++) {
mockedApi.addComment({html: `<p>This is comment ${i}</p>`});
}
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
const {frame} = await initializeTest(page, {labs: true});
await frame.getByTestId('pagination-component').click();
const lastCall = adminBrowseSpy.lastCall.args[0];
const url = new URL(lastCall.request().url());
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
const {frame} = await initializeTest(page);
const comments = await frame.getByTestId('comment-component');
const replyToHide = comments.nth(1);
// Hide the 1st reply
await replyToHide.getByTestId('more-button').click();
await replyToHide.getByTestId('hide-button').click();
await expect(replyToHide).toContainText('Hidden for members');
// Show it again
await replyToHide.getByTestId('more-button').click();
await replyToHide.getByTestId('show-button').click();
await expect(replyToHide).not.toContainText('Hidden for members');
});
test('updates in-reply-to snippets when hiding', async ({page}) => {
mockedApi.addComment({
id: '1',
html: '<p>This is comment 1</p>',
replies: [
buildReply({id: '2', html: '<p>This is reply 1</p>'}),
buildReply({id: '3', html: '<p>This is reply 2</p>', in_reply_to_id: '2', in_reply_to_snippet: 'This is reply 1'}),
buildReply({id: '4', html: '<p>This is reply 3</p>'})
]
});
test('member uuid gets set when changing order', async ({page}) => {
mockedApi.addComment({
html: '<p>This is the oldest</p>',
created_at: new Date('2024-02-01T00:00:00Z')
});
mockedApi.addComment({
html: '<p>This is comment 2</p>',
created_at: new Date('2024-03-02T00:00:00Z')
});
mockedApi.addComment({
html: '<p>This is the newest comment</p>',
created_at: new Date('2024-04-03T00:00:00Z')
});
const {frame} = await initializeTest(page);
const comments = await frame.getByTestId('comment-component');
const replyToHide = comments.nth(1);
const inReplyToComment = comments.nth(2);
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
const {frame} = await initializeTest(page, {labs: true});
// Hide the 1st reply
await replyToHide.getByTestId('more-button').click();
await replyToHide.getByTestId('hide-button').click();
const sortingForm = await frame.getByTestId('comments-sorting-form');
await expect(inReplyToComment).toContainText('[removed]');
await expect(inReplyToComment).not.toContainText('This is reply 1');
await sortingForm.click();
// Show it again
await replyToHide.getByTestId('more-button').click();
await replyToHide.getByTestId('show-button').click();
const sortingDropdown = await frame.getByTestId(
'comments-sorting-form-dropdown'
);
await expect(inReplyToComment).not.toContainText('[removed]');
await expect(inReplyToComment).toContainText('This is reply 1');
});
const optionSelect = await sortingDropdown.getByText('Newest');
mockedApi.setDelay(100);
await optionSelect.click();
const lastCall = adminBrowseSpy.lastCall.args[0];
const url = new URL(lastCall.request().url());
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
});
test('has correct comments count', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>', replies: [buildReply()]});
mockedApi.addComment({html: '<p>This is comment 2</p>'});
test('member uuid gets set when loading more replies', async ({page}) => {
mockedApi.addComment({
html: '<p>This is comment 1</p>',
replies: [
buildReply({html: '<p>This is reply 1</p>'}),
buildReply({html: '<p>This is reply 2</p>'}),
buildReply({html: '<p>This is reply 3</p>'}),
buildReply({html: '<p>This is reply 4</p>'}),
buildReply({html: '<p>This is reply 5</p>'}),
buildReply({html: '<p>This is reply 6</p>'})
]
});
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'getReplies');
const {frame} = await initializeTest(page, {labs: true});
const comments = await frame.getByTestId('comment-component');
const comment = comments.nth(0);
await comment.getByTestId('reply-pagination-button').click();
const lastCall = adminBrowseSpy.lastCall.args[0];
const url = new URL(lastCall.request().url());
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
});
test('member uuid gets set when reading a comment (after unhiding)', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>'});
mockedApi.addComment({html: '<p>This is comment 2</p>', status: 'hidden'});
const adminReadSpy = sinon.spy(mockedApi.adminRequestHandlers, 'getOrUpdateComment');
const {frame} = await initializeTest(page, {labs: true});
const comments = await frame.getByTestId('comment-component');
await expect(comments).toHaveCount(2);
await expect(comments.nth(1)).toContainText('Hidden for members');
const moreButtons = comments.nth(1).getByTestId('more-button');
await moreButtons.click();
await moreButtons.getByTestId('show-button').click();
await expect(comments.nth(1)).not.toContainText('Hidden for members');
const lastCall = adminReadSpy.lastCall.args[0];
const url = new URL(lastCall.request().url());
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
});
test('hidden comments are not displayed for non-admins', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>'});
mockedApi.addComment({html: '<p>This is comment 2</p>', status: 'hidden'});
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
const {frame} = await initializeTest(page, {isAdmin: false, labs: true});
const comments = await frame.getByTestId('comment-component');
await expect(comments).toHaveCount(1);
expect(adminBrowseSpy.called).toBe(false);
});
test('hidden comments are displayed for admins', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>'});
mockedApi.addComment({html: '<p>This is comment 2</p>', status: 'hidden'});
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
const {frame} = await initializeTest(page, {labs: true});
const comments = await frame.getByTestId('comment-component');
await expect(comments).toHaveCount(2);
await expect(comments.nth(1)).toContainText('Hidden for members');
expect(adminBrowseSpy.called).toBe(true);
});
test('can hide and show comments', async ({page}) => {
[1,2].forEach(i => mockedApi.addComment({html: `<p>This is comment ${i}</p>`}));
const {frame} = await initializeTest(page, {labs: true});
const comments = await frame.getByTestId('comment-component');
// Hide the 2nd comment
const moreButtons = frame.getByTestId('more-button');
await moreButtons.nth(1).click();
await moreButtons.nth(1).getByText('Hide comment').click();
const secondComment = comments.nth(1);
await expect(secondComment).toContainText('Hidden for members');
// Check can show it again
await moreButtons.nth(1).click();
await moreButtons.nth(1).getByText('Show comment').click();
await expect(secondComment).toContainText('This is comment 2');
});
test('can hide and show replies', async ({page}) => {
mockedApi.addComment({
id: '1',
html: '<p>This is comment 1</p>',
replies: [
buildReply({id: '2', html: '<p>This is reply 1</p>'}),
buildReply({id: '3', html: '<p>This is reply 2</p>'})
]
});
const {frame} = await initializeTest(page, {labs: true});
const comments = await frame.getByTestId('comment-component');
const replyToHide = comments.nth(1);
// Hide the 1st reply
await replyToHide.getByTestId('more-button').click();
await replyToHide.getByTestId('hide-button').click();
await expect(replyToHide).toContainText('Hidden for members');
// Show it again
await replyToHide.getByTestId('more-button').click();
await replyToHide.getByTestId('show-button').click();
await expect(replyToHide).not.toContainText('Hidden for members');
});
test('updates in-reply-to snippets when hiding', async ({page}) => {
mockedApi.addComment({
id: '1',
html: '<p>This is comment 1</p>',
replies: [
buildReply({id: '2', html: '<p>This is reply 1</p>'}),
buildReply({id: '3', html: '<p>This is reply 2</p>', in_reply_to_id: '2', in_reply_to_snippet: 'This is reply 1'}),
buildReply({id: '4', html: '<p>This is reply 3</p>'})
]
});
const {frame} = await initializeTest(page, {labs: true});
const comments = await frame.getByTestId('comment-component');
const replyToHide = comments.nth(1);
const inReplyToComment = comments.nth(2);
// Hide the 1st reply
await replyToHide.getByTestId('more-button').click();
await replyToHide.getByTestId('hide-button').click();
await expect(inReplyToComment).toContainText('[removed]');
await expect(inReplyToComment).not.toContainText('This is reply 1');
// Show it again
await replyToHide.getByTestId('more-button').click();
await replyToHide.getByTestId('show-button').click();
await expect(inReplyToComment).not.toContainText('[removed]');
await expect(inReplyToComment).toContainText('This is reply 1');
});
test('has correct comments count', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>', replies: [buildReply()]});
mockedApi.addComment({html: '<p>This is comment 2</p>'});
const {frame} = await initializeTest(page, {labs: true});
await expect(frame.getByTestId('count')).toContainText('3 comments');
});
const {frame} = await initializeTest(page);
await expect(frame.getByTestId('count')).toContainText('3 comments');
});
});

View file

@ -26,9 +26,7 @@ test.describe('Autoclose forms', async () => {
mockedApi,
page,
publication: 'Publisher weekly',
labs: {
commentImprovements: true
}
labs: {}
}));
});

View file

@ -28,9 +28,7 @@ test.describe('Deleted and Hidden Content', async () => {
mockedApi,
page,
publication: 'Publisher Weekly',
labs: {
commentImprovements: true
}
labs: {}
});
const iframeElement = await page.locator('iframe[data-frame="admin-auth"]');
@ -95,9 +93,7 @@ test.describe('Deleted and Hidden Content', async () => {
mockedApi,
page,
publication: 'Publisher Weekly',
labs: {
commentImprovements: true
}
labs: {}
});
await expect (frame.getByText('This is comment 2')).not.toBeVisible();
@ -131,9 +127,7 @@ test.describe('Deleted and Hidden Content', async () => {
mockedApi,
page,
publication: 'Publisher Weekly',
labs: {
commentImprovements: true
}
labs: {}
});
await expect (frame.getByText('This is reply 1')).toBeVisible();

View file

@ -19,9 +19,9 @@ test.describe('CTA', async () => {
await expect(ctaBox).toContainText('Become a member of Publisher Weekly to start commenting');
await expect(ctaBox).toContainText('Sign in');
// Does not show the reply buttons if not logged in
// Show the reply buttons if not logged in
const replyButton = frame.getByTestId('reply-button');
await expect(replyButton).toHaveCount(0);
await expect(replyButton).toHaveCount(2);
// Does not show the main form
const form = frame.getByTestId('form');
@ -66,9 +66,9 @@ test.describe('CTA', async () => {
// Don't show sign in button
await expect(ctaBox).not.toContainText('Sign in');
// No replies or comments possible
// Shows replies buttons
const replyButton = frame.getByTestId('reply-button');
await expect(replyButton).toHaveCount(0);
await expect(replyButton).toHaveCount(2);
const form = frame.getByTestId('form');
await expect(form).toHaveCount(0);

View file

@ -16,21 +16,22 @@ test.describe('Pagination', async () => {
await expect(frame.getByTestId('pagination-component')).toBeVisible();
// Check text in pagination button
await expect(frame.getByTestId('pagination-component')).toContainText('Show 1 previous comment');
await expect(frame.getByTestId('pagination-component')).toContainText('Load more (1)');
// Test total comments with test-id comment-component is 5
await expect(frame.getByTestId('comment-component')).toHaveCount(20);
// Check only the first latest 20 comments are visible
await expect(frame.getByText('This is comment 1.')).not.toBeVisible();
await expect(frame.getByText('This is comment 1.')).toBeVisible();
await expect(frame.getByText('This is comment 2.')).toBeVisible();
await expect(frame.getByText('This is comment 3.')).toBeVisible();
await expect(frame.getByText('This is comment 4.')).toBeVisible();
await expect(frame.getByText('This is comment 5.')).toBeVisible();
await expect(frame.getByText('This is comment 6.')).toBeVisible();
await expect(frame.getByText('This is comment 20.')).toBeVisible();
await expect(frame.getByText('This is comment 21.')).not.toBeVisible();
//
//
// Click the pagination button
await frame.getByTestId('pagination-component').click();
@ -39,7 +40,7 @@ test.describe('Pagination', async () => {
await expect(frame.getByTestId('comment-component')).toHaveCount(21);
// Check comments 6 is visible
await expect(frame.getByText('This is comment 1.')).toBeVisible();
await expect(frame.getByText('This is comment 21.')).toBeVisible();
// Check the pagination button is not visible
await expect(frame.getByTestId('pagination-component')).not.toBeVisible();

View file

@ -162,10 +162,10 @@ export class MockedApi {
let filteredComments = this.comments;
if (this.labs.commentImprovements && !admin) {
if (!admin) {
function filterPublishedComments(comments: any[] = []) {
return comments
.filter(comment => comment.status === 'published')
.filter(comment => (comment.status === 'published' || comment.replies?.some(r => r.status === 'published')))
.map(comment => ({...comment, replies: filterPublishedComments(comment.replies)}));
}

View file

@ -2,7 +2,6 @@ const _ = require('lodash');
const utils = require('../../..');
const url = require('../utils/url');
const htmlToPlaintext = require('@tryghost/html-to-plaintext');
const labs = require('../../../../../../../shared/labs');
const commentFields = [
'id',
@ -48,20 +47,16 @@ const commentMapper = (model, frame) => {
const isPublicRequest = utils.isMembersAPI(frame);
if (labs.isSet('commentImprovements')) {
if (jsonModel.inReplyTo && (jsonModel.inReplyTo.status === 'published' || (!isPublicRequest && jsonModel.inReplyTo.status === 'hidden'))) {
jsonModel.in_reply_to_snippet = htmlToPlaintext.commentSnippet(jsonModel.inReplyTo.html);
} else if (jsonModel.inReplyTo && jsonModel.inReplyTo.status !== 'published') {
jsonModel.in_reply_to_snippet = '[removed]';
} else {
jsonModel.in_reply_to_snippet = null;
}
if (!jsonModel.inReplyTo) {
jsonModel.in_reply_to_id = null;
}
if (jsonModel.inReplyTo && (jsonModel.inReplyTo.status === 'published' || (!isPublicRequest && jsonModel.inReplyTo.status === 'hidden'))) {
jsonModel.in_reply_to_snippet = htmlToPlaintext.commentSnippet(jsonModel.inReplyTo.html);
} else if (jsonModel.inReplyTo && jsonModel.inReplyTo.status !== 'published') {
jsonModel.in_reply_to_snippet = '[removed]';
} else {
delete jsonModel.in_reply_to_id;
jsonModel.in_reply_to_snippet = null;
}
if (!jsonModel.inReplyTo) {
jsonModel.in_reply_to_id = null;
}
const response = _.pick(jsonModel, commentFields);

View file

@ -3,7 +3,6 @@ const _ = require('lodash');
const errors = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');
const {ValidationError} = require('@tryghost/errors');
const labs = require('../../shared/labs');
const messages = {
emptyComment: 'The body of a comment cannot be empty',
@ -62,43 +61,21 @@ const Comment = ghostBookshelf.Model.extend({
.query('limit', 3);
},
customQuery(qb) {
qb.where(function () {
this.whereNotIn('comments.status', ['hidden', 'deleted'])
.orWhereExists(function () {
this.select(1)
.from('comments as replies')
.whereRaw('replies.parent_id = comments.id')
.whereNotIn('replies.status', ['hidden', 'deleted']);
});
});
},
adminCustomQuery(qb) {
qb.where(function () {
this.whereNotIn('comments.status', ['deleted'])
.orWhereExists(function () {
this.select(1)
.from('comments as replies')
.whereRaw('replies.parent_id = comments.id')
.whereNotIn('replies.status', ['deleted']);
});
});
},
// Called by our filtered-collection bookshelf plugin
applyCustomQuery(options) {
if (labs.isSet('commentImprovements')) {
if (!options.isAdmin) { // if it's an admin request, we don't need to apply the custom query
this.query((qb) => {
this.customQuery(qb, options);
});
}
if (options.isAdmin) {
this.query((qb) => {
this.adminCustomQuery(qb, options);
});
}
}
const excludedCommentStatuses = options.isAdmin ? ['deleted'] : ['hidden', 'deleted'];
this.query((qb) => {
qb.where(function () {
this.whereNotIn('comments.status', excludedCommentStatuses)
.orWhereExists(function () {
this.select(1)
.from('comments as replies')
.whereRaw('replies.parent_id = comments.id')
.whereNotIn('replies.status', excludedCommentStatuses);
});
});
});
},
emitChange: function emitChange(event, options) {
@ -228,7 +205,7 @@ const Comment = ghostBookshelf.Model.extend({
// - public requests never return hidden or deleted replies
// - admin requests never return deleted replies but do return hidden replies
const repliesOptionIndex = withRelated.indexOf('replies');
if (labs.isSet('commentImprovements') && repliesOptionIndex > -1) {
if (repliesOptionIndex > -1) {
withRelated[repliesOptionIndex] = {
replies: (qb) => {
if (isAdmin) {
@ -297,31 +274,15 @@ const Comment = ghostBookshelf.Model.extend({
countRelations() {
return {
replies(modelOrCollection, options) {
if (labs.isSet('commentImprovements') && !options.isAdmin) {
modelOrCollection.query('columns', 'comments.*', (qb) => {
qb.count('replies.id')
.from('comments AS replies')
.whereRaw('replies.parent_id = comments.id')
.whereNotIn('replies.status', ['hidden', 'deleted'])
.as('count__replies');
});
} else {
modelOrCollection.query('columns', 'comments.*', (qb) => {
qb.count('replies.id')
.from('comments AS replies')
.whereRaw('replies.parent_id = comments.id')
.as('count__replies');
});
}
if (options.isAdmin && labs.isSet('commentImprovements')) {
modelOrCollection.query('columns', 'comments.*', (qb) => {
qb.count('replies.id')
.from('comments AS replies')
.whereRaw('replies.parent_id = comments.id')
.whereNotIn('replies.status', ['deleted'])
.as('count__replies');
});
}
const excludedCommentStatuses = options.isAdmin ? ['deleted'] : ['hidden', 'deleted'];
modelOrCollection.query('columns', 'comments.*', (qb) => {
qb.count('replies.id')
.from('comments AS replies')
.whereRaw('replies.parent_id = comments.id')
.whereNotIn('replies.status', excludedCommentStatuses)
.as('count__replies');
});
},
likes(modelOrCollection) {
modelOrCollection.query('columns', 'comments.*', (qb) => {

View file

@ -1,172 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Admin Comments API - commentImprovements off Hide Can hide comments 1: [body] 1`] = `
Object {
"comments": Array [
Object {
"count": Object {
"likes": Any<Number>,
"replies": 0,
},
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
"expertise": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Mr Egg",
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
"replies": Array [],
"status": "published",
},
],
}
`;
exports[`Admin Comments API - commentImprovements off Hide Can hide comments 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "361",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`Admin Comments API - commentImprovements off Hide Can hide comments 3: [body] 1`] = `
Object {
"comments": Array [
Object {
"count": Object {
"likes": Any<Number>,
"replies": 0,
},
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"edited_at": null,
"html": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
"expertise": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Mr Egg",
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
"replies": Array [],
"status": "hidden",
},
],
}
`;
exports[`Admin Comments API - commentImprovements off Hide Can hide comments 4: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "336",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`Admin Comments API - commentImprovements off Hide Can hide replies 1: [body] 1`] = `
Object {
"comments": Array [
Object {
"count": Object {
"likes": Any<Number>,
"replies": 0,
},
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
"expertise": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": null,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
"replies": Array [],
"status": "published",
},
],
}
`;
exports[`Admin Comments API - commentImprovements off Hide Can hide replies 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "355",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`Admin Comments API - commentImprovements off Hide Can hide replies 3: [body] 1`] = `
Object {
"comments": Array [
Object {
"count": Object {
"likes": Any<Number>,
"replies": 0,
},
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"edited_at": null,
"html": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
"expertise": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": null,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
"replies": Array [],
"status": "hidden",
},
],
}
`;
exports[`Admin Comments API - commentImprovements off Hide Can hide replies 4: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "332",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`Admin Comments API - commentImprovements off likes Can like a comment 1: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
"x-powered-by": "Express",
}
`;
exports[`Admin Comments API - commentImprovements on Hide Can hide comments 1: [body] 1`] = `
exports[`Admin Comments API Hide Can hide comments 1: [body] 1`] = `
Object {
"comments": Array [
Object {
@ -195,7 +29,7 @@ Object {
}
`;
exports[`Admin Comments API - commentImprovements on Hide Can hide comments 2: [headers] 1`] = `
exports[`Admin Comments API Hide Can hide comments 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
@ -207,7 +41,7 @@ Object {
}
`;
exports[`Admin Comments API - commentImprovements on Hide Can hide comments 3: [body] 1`] = `
exports[`Admin Comments API Hide Can hide comments 3: [body] 1`] = `
Object {
"comments": Array [
Object {
@ -236,7 +70,7 @@ Object {
}
`;
exports[`Admin Comments API - commentImprovements on Hide Can hide comments 4: [headers] 1`] = `
exports[`Admin Comments API Hide Can hide comments 4: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
@ -248,7 +82,7 @@ Object {
}
`;
exports[`Admin Comments API - commentImprovements on Hide Can hide replies 1: [body] 1`] = `
exports[`Admin Comments API Hide Can hide replies 1: [body] 1`] = `
Object {
"comments": Array [
Object {
@ -277,7 +111,7 @@ Object {
}
`;
exports[`Admin Comments API - commentImprovements on Hide Can hide replies 2: [headers] 1`] = `
exports[`Admin Comments API Hide Can hide replies 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
@ -289,7 +123,7 @@ Object {
}
`;
exports[`Admin Comments API - commentImprovements on Hide Can hide replies 3: [body] 1`] = `
exports[`Admin Comments API Hide Can hide replies 3: [body] 1`] = `
Object {
"comments": Array [
Object {
@ -318,7 +152,7 @@ Object {
}
`;
exports[`Admin Comments API - commentImprovements on Hide Can hide replies 4: [headers] 1`] = `
exports[`Admin Comments API Hide Can hide replies 4: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
@ -330,7 +164,7 @@ Object {
}
`;
exports[`Admin Comments API - commentImprovements on Logged in member gets own likes via admin api can get comment liked status by impersonating member 1: [headers] 1`] = `
exports[`Admin Comments API Logged in member gets own likes via admin api can get comment liked status by impersonating member via admin browse route 1: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
@ -340,7 +174,7 @@ Object {
}
`;
exports[`Admin Comments API - commentImprovements on Logged in member gets own likes via admin api can get comment liked status by impersonating member via admin browse route 1: [headers] 1`] = `
exports[`Admin Comments API Logged in member gets own likes via admin api can get comment liked status by impersonating member via admin get by comment id read route 1: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
@ -350,77 +184,7 @@ Object {
}
`;
exports[`Admin Comments API - commentImprovements on Logged in member gets own likes via admin api can get comment liked status by impersonating member via admin get by comment id read route 1: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
"x-powered-by": "Express",
}
`;
exports[`Admin Comments API - commentImprovements on Logged in member gets own likes via admin api can get comment liked status by impersonating member via admin get by comment read route 1: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
"x-powered-by": "Express",
}
`;
exports[`Admin Comments API - commentImprovements on Logged in member gets own likes via admin api can get comment liked status by impersonating member via admin get by comment replies route 1: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
"x-powered-by": "Express",
}
`;
exports[`Admin Comments API - commentImprovements on Logged in member gets own likes via admin api can get comment liked status by impersonating member via admin get by comment replies route 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
"x-powered-by": "Express",
}
`;
exports[`Admin Comments API - commentImprovements on Logged in member gets own likes via admin api can get comment liked status by impersonating member via admin get by comment replies route 3: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/, /api/members/comments/67568ca99db975825f9772a5/replies/",
"x-powered-by": "Express",
}
`;
exports[`Admin Comments API - commentImprovements on Logged in member likes via admin api can get comment liked status by impersonating member 1: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
"x-powered-by": "Express",
}
`;
exports[`Admin Comments API - commentImprovements on can get logged in member likes via admin api Can like a comment 1: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
"x-powered-by": "Express",
}
`;
exports[`Admin Comments API - commentImprovements on likes Can like a comment 1: [headers] 1`] = `
exports[`Admin Comments API Logged in member gets own likes via admin api can get comment liked status by impersonating member via admin get by comment replies route 1: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,8 @@ Object {
"edited_at": null,
"html": "<p>This is a message</p><p></p><p>New line</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -31,7 +33,7 @@ exports[`Comments API Tier-only posts Members with access Can comment on a post
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "379",
"content-length": "428",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -53,6 +55,8 @@ Object {
"edited_at": null,
"html": "This is a reply",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -72,7 +76,7 @@ exports[`Comments API Tier-only posts Members with access Can reply to a comment
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "348",
"content-length": "397",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -154,6 +158,8 @@ Object {
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -174,6 +180,8 @@ Object {
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -191,6 +199,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -222,7 +232,7 @@ exports[`Comments API when commenting enabled for all when authenticated Browsin
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "1118",
"content-length": "1265",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -242,6 +252,8 @@ Object {
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -259,6 +271,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -281,6 +295,8 @@ Object {
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -310,7 +326,7 @@ exports[`Comments API when commenting enabled for all when authenticated Can bro
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "1118",
"content-length": "1265",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -330,6 +346,8 @@ Object {
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -347,6 +365,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -369,6 +389,8 @@ Object {
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -398,7 +420,7 @@ exports[`Comments API when commenting enabled for all when authenticated Can bro
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "1118",
"content-length": "1265",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -418,6 +440,8 @@ Object {
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -438,6 +462,8 @@ Object {
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -455,6 +481,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -486,7 +514,7 @@ exports[`Comments API when commenting enabled for all when authenticated Can bro
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "1118",
"content-length": "1265",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -506,6 +534,8 @@ Object {
"edited_at": null,
"html": "<p>This is a message</p><p></p><p>New line</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -525,7 +555,7 @@ exports[`Comments API when commenting enabled for all when authenticated Can com
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "379",
"content-length": "428",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -547,6 +577,8 @@ Object {
"edited_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"html": "Updated comment",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -566,7 +598,7 @@ exports[`Comments API when commenting enabled for all when authenticated Can edi
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "370",
"content-length": "419",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -617,6 +649,8 @@ Object {
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -636,7 +670,7 @@ exports[`Comments API when commenting enabled for all when authenticated Can lik
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "367",
"content-length": "416",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -666,6 +700,8 @@ Object {
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -685,7 +721,7 @@ exports[`Comments API when commenting enabled for all when authenticated Can lik
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "356",
"content-length": "405",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -705,6 +741,8 @@ Object {
"edited_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"html": "Illegal comment update",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -724,7 +762,7 @@ exports[`Comments API when commenting enabled for all when authenticated Can not
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "377",
"content-length": "426",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -814,6 +852,8 @@ Object {
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -833,7 +873,7 @@ exports[`Comments API when commenting enabled for all when authenticated Can rem
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "357",
"content-length": "406",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -853,6 +893,8 @@ Object {
"edited_at": null,
"html": "This is a reply",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -872,7 +914,7 @@ exports[`Comments API when commenting enabled for all when authenticated Can rep
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "348",
"content-length": "397",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -894,6 +936,8 @@ Object {
"edited_at": null,
"html": "This is a reply",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -913,7 +957,7 @@ exports[`Comments API when commenting enabled for all when authenticated Can rep
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "348",
"content-length": "397",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -935,6 +979,8 @@ Object {
"edited_at": null,
"html": "This is a reply",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -954,7 +1000,7 @@ exports[`Comments API when commenting enabled for all when authenticated Can rep
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "348",
"content-length": "397",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -976,6 +1022,8 @@ Object {
"edited_at": null,
"html": "This is a reply",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -995,7 +1043,7 @@ exports[`Comments API when commenting enabled for all when authenticated Can rep
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "348",
"content-length": "397",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -1025,6 +1073,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -1053,7 +1103,7 @@ exports[`Comments API when commenting enabled for all when authenticated Can req
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "414",
"content-length": "463",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -1072,6 +1122,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -1090,6 +1142,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -1108,6 +1162,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -1126,6 +1182,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -1144,6 +1202,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -1162,6 +1222,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -1180,6 +1242,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -1208,7 +1272,7 @@ exports[`Comments API when commenting enabled for all when authenticated Can ret
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "2313",
"content-length": "2656",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -1297,6 +1361,8 @@ Object {
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -1314,6 +1380,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -1332,6 +1400,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -1350,6 +1420,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -1371,7 +1443,7 @@ exports[`Comments API when commenting enabled for all when authenticated Limits
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "1308",
"content-length": "1504",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -1379,7 +1451,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled doesn't count deleted or hidden comments in replies count 1: [body] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post doesn't count deleted or hidden comments in replies count 1: [body] 1`] = `
Object {
"comments": Array [
Object {
@ -1439,7 +1511,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled doesn't count deleted or hidden comments in replies count 2: [headers] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post doesn't count deleted or hidden comments in replies count 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
@ -1451,7 +1523,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled excludes deleted comments 1: [body] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post excludes deleted comments 1: [body] 1`] = `
Object {
"comments": Array [],
"meta": Object {
@ -1467,7 +1539,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled excludes deleted comments 2: [headers] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post excludes deleted comments 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
@ -1479,7 +1551,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled excludes deleted comments if all replies are hidden or deleted 1: [body] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post excludes deleted comments if all replies are hidden or deleted 1: [body] 1`] = `
Object {
"comments": Array [],
"meta": Object {
@ -1495,7 +1567,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled excludes deleted comments if all replies are hidden or deleted 2: [headers] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post excludes deleted comments if all replies are hidden or deleted 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
@ -1507,7 +1579,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled excludes deleted replies 1: [body] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post excludes deleted replies 1: [body] 1`] = `
Object {
"comments": Array [
Object {
@ -1546,7 +1618,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled excludes deleted replies 2: [headers] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post excludes deleted replies 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
@ -1558,7 +1630,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled excludes hidden comments 1: [body] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post excludes hidden comments 1: [body] 1`] = `
Object {
"comments": Array [],
"meta": Object {
@ -1574,7 +1646,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled excludes hidden comments 2: [headers] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post excludes hidden comments 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
@ -1586,7 +1658,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled excludes hidden comments if all replies are hidden or deleted 1: [body] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post excludes hidden comments if all replies are hidden or deleted 1: [body] 1`] = `
Object {
"comments": Array [],
"meta": Object {
@ -1602,7 +1674,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled excludes hidden comments if all replies are hidden or deleted 2: [headers] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post excludes hidden comments if all replies are hidden or deleted 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
@ -1614,7 +1686,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled excludes hidden replies 1: [body] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post excludes hidden replies 1: [body] 1`] = `
Object {
"comments": Array [
Object {
@ -1653,7 +1725,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled excludes hidden replies 2: [headers] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post excludes hidden replies 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
@ -1665,7 +1737,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled includes deleted comments if they have published replies 1: [body] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post includes deleted comments if they have published replies 1: [body] 1`] = `
Object {
"comments": Array [
Object {
@ -1725,7 +1797,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled includes deleted comments if they have published replies 2: [headers] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post includes deleted comments if they have published replies 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
@ -1737,7 +1809,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled includes hidden comments if they have published replies 1: [body] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post includes hidden comments if they have published replies 1: [body] 1`] = `
Object {
"comments": Array [
Object {
@ -1797,7 +1869,7 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated browse by post when commentImprovements flag is enabled includes hidden comments if they have published replies 2: [headers] 1`] = `
exports[`Comments API when commenting enabled for all when authenticated browse by post includes hidden comments if they have published replies 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
@ -2030,88 +2102,6 @@ Object {
}
`;
exports[`Comments API when commenting enabled for all when authenticated replies to replies does not include in_reply_to_snippet for deleted comments 1: [body] 1`] = `
Object {
"comments": Array [
Object {
"count": Object {
"likes": Any<Number>,
"replies": 0,
},
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": Nullable<StringMatching>,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
"expertise": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": null,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
"replies": Array [],
"status": "published",
},
],
}
`;
exports[`Comments API when commenting enabled for all when authenticated replies to replies does not include in_reply_to_snippet for deleted comments 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "406",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`Comments API when commenting enabled for all when authenticated replies to replies does not include in_reply_to_snippet for hidden comments 1: [body] 1`] = `
Object {
"comments": Array [
Object {
"count": Object {
"likes": Any<Number>,
"replies": 0,
},
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": Nullable<StringMatching>,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
"expertise": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": null,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
"replies": Array [],
"status": "published",
},
],
}
`;
exports[`Comments API when commenting enabled for all when authenticated replies to replies does not include in_reply_to_snippet for hidden comments 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "406",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`Comments API when commenting enabled for all when authenticated replies to replies has redacted in_reply_to_snippet when referenced comment is deleted 1: [body] 1`] = `
Object {
"comments": Array [
@ -2376,6 +2366,8 @@ Object {
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -2393,6 +2385,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -2424,7 +2418,7 @@ exports[`Comments API when commenting enabled for all when not authenticated Can
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "764",
"content-length": "862",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -2444,6 +2438,8 @@ Object {
"edited_at": null,
"html": "<p>This is a comment</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -2461,6 +2457,8 @@ Object {
"edited_at": null,
"html": "<p>This is a reply</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -2492,7 +2490,7 @@ exports[`Comments API when commenting enabled for all when not authenticated Can
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "764",
"content-length": "862",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -2662,6 +2660,8 @@ Object {
"edited_at": null,
"html": "<p>This is a message</p><p></p><p>New line</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -2681,7 +2681,7 @@ exports[`Comments API when paid only commenting Members with access Can comment
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "379",
"content-length": "428",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -2703,6 +2703,8 @@ Object {
"edited_at": null,
"html": "This is a reply",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"in_reply_to_id": null,
"in_reply_to_snippet": null,
"liked": Any<Boolean>,
"member": Object {
"avatar_image": null,
@ -2722,7 +2724,7 @@ exports[`Comments API when paid only commenting Members with access Can reply to
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "348",
"content-length": "397",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,

View file

@ -7,7 +7,6 @@ const moment = require('moment-timezone');
const settingsCache = require('../../../core/shared/settings-cache');
const sinon = require('sinon');
const DomainEvents = require('@tryghost/domain-events');
const {mockLabsEnabled, mockLabsDisabled} = require('../../utils/e2e-framework-mock-manager');
let membersAgent, membersAgent2, postId, postAuthorEmail, postTitle;
@ -351,8 +350,6 @@ describe('Comments API', function () {
beforeEach(async function () {
mockManager.mockMail();
mockLabsDisabled('commentImprovements');
// ensure we don't have data dependencies across tests
await dbUtils.truncate('comments');
await dbUtils.truncate('comment_likes');
@ -403,52 +400,44 @@ describe('Comments API', function () {
]);
});
describe('when commentImprovements flag is enabled', function () {
beforeEach(function () {
mockLabsEnabled('commentImprovements');
it('excludes hidden comments', async function () {
const hiddenComment = await dbFns.addComment({
post_id: postId,
member_id: fixtureManager.get('members', 2).id,
html: 'This is a hidden comment',
status: 'hidden'
});
it('excludes hidden comments', async function () {
const hiddenComment = await dbFns.addComment({
post_id: postId,
member_id: fixtureManager.get('members', 2).id,
html: 'This is a hidden comment',
status: 'hidden'
});
const data2 = await membersAgent
.get(`/api/comments/post/${postId}/`)
.expectStatus(200);
const data2 = await membersAgent
.get(`/api/comments/post/${postId}/`)
.expectStatus(200);
// check that hiddenComment.id is not in the response
should(data2.body.comments.map(c => c.id)).not.containEql(hiddenComment.id);
should(data2.body.comments.length).eql(0);
});
// check that hiddenComment.id is not in the response
should(data2.body.comments.map(c => c.id)).not.containEql(hiddenComment.id);
should(data2.body.comments.length).eql(0);
it('excludes deleted comments', async function () {
await dbFns.addComment({
post_id: postId,
member_id: fixtureManager.get('members', 2).id,
html: 'This is a deleted comment',
status: 'deleted'
});
it('excludes deleted comments', async function () {
// await mockManager.mockLabsEnabled('commentImprovements');
await dbFns.addComment({
post_id: postId,
member_id: fixtureManager.get('members', 2).id,
html: 'This is a deleted comment',
status: 'deleted'
});
const data2 = await membersAgent
.get(`/api/comments/post/${postId}/`)
.expectStatus(200);
const data2 = await membersAgent
.get(`/api/comments/post/${postId}/`)
.expectStatus(200);
// go through all comments and check if the deleted comment is not there
data2.body.comments.forEach((comment) => {
should(comment.html).not.eql('This is a deleted comment');
});
data2.body.comments.length.should.eql(0);
// go through all comments and check if the deleted comment is not there
data2.body.comments.forEach((comment) => {
should(comment.html).not.eql('This is a deleted comment');
});
data2.body.comments.length.should.eql(0);
});
it('shows hidden and deleted comment where there is a reply', async function () {
await mockManager.mockLabsEnabled('commentImprovements');
await setupBrowseCommentsData();
const hiddenComment = await dbFns.addComment({
post_id: postId,
@ -505,7 +494,6 @@ describe('Comments API', function () {
});
it('Returns nothing if both parent and reply are hidden', async function () {
await mockManager.mockLabsEnabled('commentImprovements');
const hiddenComment = await dbFns.addComment({
post_id: postId,
member_id: fixtureManager.get('members', 0).id,
@ -652,11 +640,7 @@ describe('Comments API', function () {
should.not.exist(response.body.comments[0].unsubscribe_url);
});
describe('browse by post when commentImprovements flag is enabled', function () {
beforeEach(function () {
mockLabsEnabled('commentImprovements');
});
describe('browse by post', function () {
it('excludes deleted comments', async function () {
await dbFns.addComment({
member_id: fixtureManager.get('members', 2).id,
@ -913,7 +897,6 @@ describe('Comments API', function () {
});
it('hidden replies are not included in the count', async function () {
await mockManager.mockLabsEnabled('commentImprovements');
const {parent} = await dbFns.addCommentWithReplies({
member_id: fixtureManager.get('members', 0).id,
replies: new Array(5).fill({
@ -928,7 +911,6 @@ describe('Comments API', function () {
});
it('deleted replies are not included in the count', async function () {
await mockManager.mockLabsEnabled('commentImprovements');
const {parent} = await dbFns.addCommentWithReplies({
member_id: fixtureManager.get('members', 0).id,
replies: new Array(5).fill({
@ -1335,10 +1317,6 @@ describe('Comments API', function () {
});
describe('replies to replies', function () {
beforeEach(function () {
mockLabsEnabled('commentImprovements');
});
it('can browse comments with replies to replies', async function () {
const {replies: [reply]} = await dbFns.addCommentWithReplies({
member_id: fixtureManager.get('members', 1).id,

View file

@ -8,7 +8,6 @@ const extraAttrsUtils = require('../../../../../../../core/server/api/endpoints/
const mappers = require('../../../../../../../core/server/api/endpoints/utils/serializers/output/mappers');
const memberAttribution = require('../../../../../../../core/server/services/member-attribution');
const htmlToPlaintext = require('@tryghost/html-to-plaintext');
const labs = require('../../../../../../../core/shared/labs');
function createJsonModel(data) {
return Object.assign(data, {toJSON: sinon.stub().returns(data)});
@ -414,6 +413,8 @@ describe('Unit: utils/serializers/output/mappers', function () {
data: {
// same except the remove foo keys
id: 'id1',
in_reply_to_id: null,
in_reply_to_snippet: null,
status: 'status1',
html: 'html1',
created_at: 'created_at1',
@ -612,11 +613,7 @@ describe('Unit: utils/serializers/output/mappers', function () {
});
});
describe('Comment mapper (commentImprovements flag enabled)', function () {
beforeEach(function () {
sinon.stub(labs, 'isSet').returns(true);
});
describe('Comment mapper', function () {
it('includes in_reply_to_snippet for published replies-to-replies', function () {
const frame = {};

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Sluit aan by die bespreking",
"Just now": "Sopas",
"Load more ({{amount}})": "",
"Local resident": "Plaaslike inwoner",
"Member discussion": "Lidmaat bespreking",
"Name": "Naam",
@ -55,9 +56,7 @@
"Sent": "Gestuur",
"Show": "Wys",
"Show {{amount}} more replies": "Wys {{amount}} meer antwoorde",
"Show {{amount}} previous comments": "Wys {{amount}} vorige kommentaar",
"Show 1 more reply": "Wys nog 1 antwoord",
"Show 1 previous comment": "Wys 1 vorige kommentaar",
"Show comment": "Wys kommentaar",
"Sign in": "Teken in",
"Sign up now": "Sluit nou aan",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "فلان الفلانى",
"Join the discussion": "انضم للمحادثة",
"Just now": "الآن",
"Load more ({{amount}})": "",
"Local resident": "مقيم محلى",
"Member discussion": "مناقشة الاعضاء",
"Name": "الاسم",
@ -55,9 +56,7 @@
"Sent": "تم الارسال",
"Show": "اظهر",
"Show {{amount}} more replies": "اظهر {{amount}} ردود",
"Show {{amount}} previous comments": "اظهر {{amount}} تعليقات سابقة",
"Show 1 more reply": "اظهر تعليق وحيد",
"Show 1 previous comment": "اظهر التعليق السابق",
"Show comment": "اظهر التعليق",
"Sign in": "تسجيل الدخول",
"Sign up now": "قم بالتسجيل الآن",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Иван Иванов",
"Join the discussion": "Участвайте в дискусията",
"Just now": "Току-що",
"Load more ({{amount}})": "",
"Local resident": "Местен жител",
"Member discussion": "Дискусия за абонатите",
"Name": "Име",
@ -55,9 +56,7 @@
"Sent": "Изпратен",
"Show": "Показване",
"Show {{amount}} more replies": "Покажи още {{amount}} отговора",
"Show {{amount}} previous comments": "Покажи {{amount}} предишни коментара",
"Show 1 more reply": "Покажи още един отговор",
"Show 1 previous comment": "Покажи един предишен коментар",
"Show comment": "Покажи коментар",
"Sign in": "Вход",
"Sign up now": "Регистрация",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "শাহ নেওয়াজ",
"Join the discussion": "আলোচনায় যোগ দিন",
"Just now": "এখনই",
"Load more ({{amount}})": "",
"Local resident": "স্থানীয় বাসিন্দা",
"Member discussion": "সদস্য আলোচনা",
"Name": "নাম",
@ -55,9 +56,7 @@
"Sent": "পাঠানো হয়েছে",
"Show": "দেখান",
"Show {{amount}} more replies": " আরো {{amount}}টি উত্তর দেখান",
"Show {{amount}} previous comments": "পূর্বের {{amount}}টি মন্তব্য দেখান",
"Show 1 more reply": "১টি আরো উত্তর দেখান",
"Show 1 previous comment": "১টি পূর্বের মন্তব্য দেখান",
"Show comment": "মন্তব্য দেখান",
"Sign in": "সাইন ইন করুন",
"Sign up now": "এখনই সাইন আপ করুন",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Vanja Larsić",
"Join the discussion": "Pridruži se diskusiji",
"Just now": "Upravo sada",
"Load more ({{amount}})": "",
"Local resident": "Lokalni stanovnik",
"Member discussion": "Diskusija članova",
"Name": "Ime",
@ -55,9 +56,7 @@
"Sent": "Poslano",
"Show": "Prikaži",
"Show {{amount}} more replies": "Prikaži još {{amount}} odgovora",
"Show {{amount}} previous comments": "Prikaži prethodnih {{amount}} komentara",
"Show 1 more reply": "Prikaži još jedan odgovor",
"Show 1 previous comment": "Prikaži prethodni komentar",
"Show comment": "Prikaži komentar",
"Sign in": "Prijavi se",
"Sign up now": "Postani član",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Uneix-te al debat",
"Just now": "Just ara",
"Load more ({{amount}})": "",
"Local resident": "Resident local",
"Member discussion": "Debat de membres",
"Name": "Nom",
@ -55,9 +56,7 @@
"Sent": "Enviat",
"Show": "Mostra",
"Show {{amount}} more replies": "Mostra {{amount}} respostes més",
"Show {{amount}} previous comments": "Mostra {{amount}} comentaris previs",
"Show 1 more reply": "Mostra 1 resposta més",
"Show 1 previous comment": "Mostra 1 comentari previ",
"Show comment": "Mostra comentari",
"Sign in": "Inicia sessió",
"Sign up now": "Registra't ara",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Připojte se k diskusi",
"Just now": "Právě teď",
"Load more ({{amount}})": "",
"Local resident": "Místní obyvatel",
"Member discussion": "Diskuse členů",
"Name": "Jméno",
@ -55,9 +56,7 @@
"Sent": "Odesláno",
"Show": "Zobrazit",
"Show {{amount}} more replies": "Zobrazit {{amount}} dalších odpovědí",
"Show {{amount}} previous comments": "Zobrazit {{amount}} předchozích komentářů",
"Show 1 more reply": "Zobrazit 1 další odpověď",
"Show 1 previous comment": "Zobrazit 1 předchozí komentář",
"Show comment": "Zobrazit komentář",
"Sign in": "Přihlásit se",
"Sign up now": "Zaregistrujte se nyní",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Bliv en del af diskussionen",
"Just now": "Lige nu",
"Load more ({{amount}})": "",
"Local resident": "Lokal borger",
"Member discussion": "Medlemsdiskussion",
"Name": "Navn",
@ -55,9 +56,7 @@
"Sent": "Sendt",
"Show": "Vis",
"Show {{amount}} more replies": "Vis {{amount}} flere svar",
"Show {{amount}} previous comments": "Vis {{amount}} tidligere kommentarer",
"Show 1 more reply": "Vis 1 mere svar",
"Show 1 previous comment": "Vis 1 tidligere kommentar",
"Show comment": "Vis kommentar",
"Sign in": "Log ind",
"Sign up now": "Tilmed dig nu",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "",
"Join the discussion": "",
"Just now": "",
"Load more ({{amount}})": "",
"Local resident": "",
"Member discussion": "",
"Name": "",
@ -55,9 +56,7 @@
"Sent": "",
"Show": "",
"Show {{amount}} more replies": "",
"Show {{amount}} previous comments": "{{amount}} vorherige Kommentare anzeigen",
"Show 1 more reply": "",
"Show 1 previous comment": "Einen vorherigen Kommentar anzeigen",
"Show comment": "",
"Sign in": "",
"Sign up now": "",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Nimm an der Diskussion teil",
"Just now": "Gerade eben",
"Load more ({{amount}})": "",
"Local resident": "Ortsansässiger",
"Member discussion": "Mitgliederdiskussion",
"Name": "Name",
@ -55,9 +56,7 @@
"Sent": "Gesendet",
"Show": "Anzeigen",
"Show {{amount}} more replies": "{{amount}} weitere Antworten anzeigen",
"Show {{amount}} previous comments": "{{amount}} frühere Kommentare anzeigen",
"Show 1 more reply": "1 weitere Antwort anzeigen",
"Show 1 previous comment": "1 vorherigen Kommentar anzeigen",
"Show comment": "Kommentar anzeigen",
"Sign in": "Einloggen",
"Sign up now": "Jetzt registrieren",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Τζέιμι Λάρσον",
"Join the discussion": "Συμμετοχή στη συζήτηση",
"Just now": "Μόλις τώρα",
"Load more ({{amount}})": "",
"Local resident": "Τοπικός κάτοικος",
"Member discussion": "Συζήτηση μελών",
"Name": "Όνομα",
@ -55,9 +56,7 @@
"Sent": "Απεστάλη",
"Show": "Εμφάνιση",
"Show {{amount}} more replies": "Εμφάνιση {{amount}} περισσότερων απαντήσεων",
"Show {{amount}} previous comments": "Εμφάνιση {{amount}} προηγούμενων σχολίων",
"Show 1 more reply": "Εμφάνιση 1 ακόμα απάντησης",
"Show 1 previous comment": "Εμφάνιση 1 προηγούμενου σχολίου",
"Show comment": "Εμφάνιση σχολίου",
"Sign in": "Σύνδεση",
"Sign up now": "Εγγραφείτε τώρα",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "",
"Join the discussion": "",
"Just now": "",
"Load more ({{amount}})": "",
"Local resident": "",
"Member discussion": "",
"Name": "",
@ -55,9 +56,7 @@
"Sent": "",
"Show": "",
"Show {{amount}} more replies": "",
"Show {{amount}} previous comments": "",
"Show 1 more reply": "",
"Show 1 previous comment": "",
"Show comment": "",
"Sign in": "",
"Sign up now": "",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "",
"Join the discussion": "",
"Just now": "",
"Load more ({{amount}})": "",
"Local resident": "",
"Member discussion": "",
"Name": "",
@ -55,9 +56,7 @@
"Sent": "",
"Show": "",
"Show {{amount}} more replies": "",
"Show {{amount}} previous comments": "",
"Show 1 more reply": "",
"Show 1 previous comment": "",
"Show comment": "",
"Sign in": "",
"Sign up now": "",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Únete a la discusión",
"Just now": "Justo ahora",
"Load more ({{amount}})": "",
"Local resident": "Residente local",
"Member discussion": "Discusión de miembros",
"Name": "Nombre",
@ -55,9 +56,7 @@
"Sent": "Enviar",
"Show": "Mostrar",
"Show {{amount}} more replies": "Mostrar {{amount}} respuestas más",
"Show {{amount}} previous comments": "Mostrar {{amount}} comentarios previos",
"Show 1 more reply": "Mostrar 1 respuesta más",
"Show 1 previous comment": "Mostrar 1 comentario previo",
"Show comment": "Mostrar comentario",
"Sign in": "Inicia sesión",
"Sign up now": "Regístrate ahora",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Liitu aruteluga",
"Just now": "Just praegu",
"Load more ({{amount}})": "",
"Local resident": "Kohalik elanik",
"Member discussion": "Liikmete arutelu",
"Name": "Nimi",
@ -55,9 +56,7 @@
"Sent": "Saadetud",
"Show": "Näita",
"Show {{amount}} more replies": "Näita {{amount}} vastust veel",
"Show {{amount}} previous comments": "Näita {{amount}} eelmist kommentaari",
"Show 1 more reply": "Näita 1 vastus veel",
"Show 1 previous comment": "Näita eelmist kommentaari",
"Show comment": "Näita kommentaari",
"Sign in": "Logi sisse",
"Sign up now": "Registreeru nüüd",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "جیمی لارسن",
"Join the discussion": "به گفتمان بپیوندید",
"Just now": "پیوستن",
"Load more ({{amount}})": "",
"Local resident": "ساکن محلی",
"Member discussion": "گفتمان کاربران",
"Name": "نام",
@ -55,9 +56,7 @@
"Sent": "ارسال شد",
"Show": "نمایش",
"Show {{amount}} more replies": "نمایش {{amount}} پاسخ بیشتر",
"Show {{amount}} previous comments": "نمایش {{amount}} دیدگاه قبل\u200cتر",
"Show 1 more reply": "نمایش یک دیدگاه بیشتر",
"Show 1 previous comment": "نمایش یک دیدگاه قبل\u200cتر",
"Show comment": "نمایش دیدگاه",
"Sign in": "ورود به حساب",
"Sign up now": "ایجاد حساب",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Liity keskusteluun",
"Just now": "Juuri nyt",
"Load more ({{amount}})": "",
"Local resident": "Paikallinen",
"Member discussion": "Jäsenten keskustelu",
"Name": "Nimi",
@ -55,9 +56,7 @@
"Sent": "Lähetetty",
"Show": "Näytä",
"Show {{amount}} more replies": "Näytä {{amount}} vastausta lisää ",
"Show {{amount}} previous comments": "Näytä {{amount}} edellistä kommenttia",
"Show 1 more reply": "Näytä 1 vastaus lisää",
"Show 1 previous comment": "Näytä edellinen kommentti",
"Show comment": "Näytä kommentti",
"Sign in": "Kirjaudu sisään",
"Sign up now": "Rekisteröidy nyt",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jean Martin",
"Join the discussion": "Rejoindre la discussion",
"Just now": "À l'instant",
"Load more ({{amount}})": "",
"Local resident": "Résident local",
"Member discussion": "Discussion entre abonnés",
"Name": "Nom",
@ -55,9 +56,7 @@
"Sent": "Envoyé",
"Show": "Afficher",
"Show {{amount}} more replies": "Afficher {{amount}} réponses supplémentaires",
"Show {{amount}} previous comments": "Afficher {{amount}} commentaires précédents",
"Show 1 more reply": "Afficher 1 réponse supplémentaire",
"Show 1 previous comment": "Afficher 1 commentaire précédent",
"Show comment": "Afficher le commentaire",
"Sign in": "Se connecter",
"Sign up now": "S'inscrire maintenant",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Seamaidh Larson",
"Join the discussion": "Gabh pàirt san deasbad",
"Just now": "An-dràsta",
"Load more ({{amount}})": "",
"Local resident": "Neach-còmhnaidh ionadail",
"Member discussion": "Deasbad",
"Name": "Ainm",
@ -55,9 +56,7 @@
"Sent": "Air a chur",
"Show": "Seall",
"Show {{amount}} more replies": "An àireamh de bheachdan a bharrachd a chìthear: {{amount}}",
"Show {{amount}} previous comments": "An àireamh de bheachdan roimhe a chìthear: {{amount}}",
"Show 1 more reply": "Seall beachd a bharrachd",
"Show 1 previous comment": "Seall am beachd roimhe",
"Show comment": "Seall am beachd",
"Sign in": "Clàraich a-steach",
"Sign up now": "Clàraich a-nis",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "ג׳יימי לרסון",
"Join the discussion": "הצטרפו לדיון",
"Just now": "ממש עכשיו",
"Load more ({{amount}})": "",
"Local resident": "תושב מקומי",
"Member discussion": "דיון חברים רשומים",
"Name": "שם",
@ -55,9 +56,7 @@
"Sent": "שלחנו",
"Show": "להציג",
"Show {{amount}} more replies": "להציג עוד {{amount}} תגובות",
"Show {{amount}} previous comments": "להציג {{amount}} תגובות קודמות",
"Show 1 more reply": "להציג תגובה נוספת",
"Show 1 previous comment": "להציג תגובה קודמת",
"Show comment": "להציג תגובה",
"Sign in": "היכנסו",
"Sign up now": "הירשמו עכשיו",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "राहुल शर्मा",
"Join the discussion": "चर्चा में शामिल हों",
"Just now": "अभी",
"Load more ({{amount}})": "",
"Local resident": "स्थानीय निवासी",
"Member discussion": "सदस्य चर्चा",
"Name": "नाम",
@ -55,9 +56,7 @@
"Sent": "भेजा गया",
"Show": "दिखाएं",
"Show {{amount}} more replies": "{{amount}} और जवाब दिखाएं",
"Show {{amount}} previous comments": "{{amount}} पिछली टिप्पणियाँ दिखाएं",
"Show 1 more reply": "1 और जवाब दिखाएं",
"Show 1 previous comment": "1 पिछली टिप्पणी दिखाएं",
"Show comment": "टिप्पणी दिखाएं",
"Sign in": "साइन इन करें",
"Sign up now": "अभी साइन अप करें",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Pridruži se raspravi",
"Just now": "Upravo sada",
"Load more ({{amount}})": "",
"Local resident": "Lokalni stanovnik",
"Member discussion": "Rasprava članova",
"Name": "Ime",
@ -55,9 +56,7 @@
"Sent": "Poslano",
"Show": "Prikaži",
"Show {{amount}} more replies": "Prikaži još {{amount}} odgovora",
"Show {{amount}} previous comments": "Prikaži {{amount}} prethodnih komentara",
"Show 1 more reply": "Prikaži još jedan odgovor",
"Show 1 previous comment": "Prikaži prethodan komentar",
"Show comment": "Prikaži komentar",
"Sign in": "Prijava",
"Sign up now": "Registriraj se sada",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Kiss Sára",
"Join the discussion": "Csatlakozzon a beszélgetéshez",
"Just now": "Épp most",
"Load more ({{amount}})": "",
"Local resident": "Helyi lakos",
"Member discussion": "Hozzászólások",
"Name": "Név",
@ -55,9 +56,7 @@
"Sent": "Elküldve",
"Show": "Mutat",
"Show {{amount}} more replies": "Mutass még {{amount}} választ",
"Show {{amount}} previous comments": "Mutass {{amount}} előző hozzászólást",
"Show 1 more reply": "Mutass még egy választ",
"Show 1 previous comment": "Mutass még egy hozzászólást",
"Show comment": "Hozzászólás mutatása",
"Sign in": "Bejelentkezés",
"Sign up now": "Regisztráljon most",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Sutan T. Alisjahbana",
"Join the discussion": "Bergabung dalam diskusi",
"Just now": "Baru saja",
"Load more ({{amount}})": "",
"Local resident": "Penduduk lokal",
"Member discussion": "Diskusi anggota",
"Name": "Nama",
@ -55,9 +56,7 @@
"Sent": "Terkirim",
"Show": "Tampilkan",
"Show {{amount}} more replies": "Tampilkan {{amount}} balasan lainnya",
"Show {{amount}} previous comments": "Tampilkan {{amount}} komentar sebelumnya",
"Show 1 more reply": "Tampilkan 1 balasan lainnya",
"Show 1 previous comment": "Tampilkan 1 komentar sebelumnya",
"Show comment": "Tampilkan komentar",
"Sign in": "Masuk",
"Sign up now": "Daftar sekarang",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "",
"Join the discussion": "",
"Just now": "",
"Load more ({{amount}})": "",
"Local resident": "",
"Member discussion": "",
"Name": "",
@ -55,9 +56,7 @@
"Sent": "",
"Show": "",
"Show {{amount}} more replies": "",
"Show {{amount}} previous comments": "",
"Show 1 more reply": "",
"Show 1 previous comment": "",
"Show comment": "",
"Sign in": "",
"Sign up now": "",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Andrea Rossi",
"Join the discussion": "Partecipa alla discussione",
"Just now": "Adesso",
"Load more ({{amount}})": "",
"Local resident": "Paesano",
"Member discussion": "Commenti",
"Name": "Nome",
@ -55,9 +56,7 @@
"Sent": "Inviato",
"Show": "Mostra",
"Show {{amount}} more replies": "Mostra altre {{amount}} risposte",
"Show {{amount}} previous comments": "Mostra altri {{amount}} commenti",
"Show 1 more reply": "Mostra un'altra risposta",
"Show 1 previous comment": "Mostra un commento precedente",
"Show comment": "Mostra commento",
"Sign in": "Accedi",
"Sign up now": "Accedi ora",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "ジェイミー・ラーソン",
"Join the discussion": "議論に参加する",
"Just now": "今すぐ参加する",
"Load more ({{amount}})": "",
"Local resident": "地元住民",
"Member discussion": "メンバーによる議論",
"Name": "名前",
@ -55,9 +56,7 @@
"Sent": "送信完了",
"Show": "表示する",
"Show {{amount}} more replies": "{{amount}}個の返信を表示する",
"Show {{amount}} previous comments": "前のコメントを{{amount}}個表示する",
"Show 1 more reply": "返信をもう1つ表示する",
"Show 1 previous comment": "1つ前のコメントを表示する",
"Show comment": "コメントを表示する",
"Sign in": "ログイン",
"Sign up now": "今すぐ新規登録する",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "제이미 라슨",
"Join the discussion": "댓글에 참여해 주세요",
"Just now": "방금 전",
"Load more ({{amount}})": "",
"Local resident": "지역 주민",
"Member discussion": "회원들의 댓글",
"Name": "이름",
@ -55,9 +56,7 @@
"Sent": "전송됨",
"Show": "보기",
"Show {{amount}} more replies": "{{amount}}개의 댓글 더 보기",
"Show {{amount}} previous comments": "{{amount}}개의 이전 댓글 보기",
"Show 1 more reply": "1개의 댓글 더 보기",
"Show 1 previous comment": "1개의 이전 댓글 보기",
"Show comment": "댓글 보기",
"Sign in": "로그인",
"Sign up now": "회원가입",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Пәленше Түгеншиев",
"Join the discussion": "Талқыға қосылу",
"Just now": "Дәл қазір",
"Load more ({{amount}})": "",
"Local resident": "Жергілікті тұрғын",
"Member discussion": "Мүшелер талқысы",
"Name": "Есімі",
@ -55,9 +56,7 @@
"Sent": "Жіберілді",
"Show": "Көрсету",
"Show {{amount}} more replies": "Тағы {{amount}} жауапты көрсету",
"Show {{amount}} previous comments": "Алдыңғы {{amount}} пікірді көрсету",
"Show 1 more reply": "Тағы 1 жауап көрсету",
"Show 1 previous comment": "Алдыңғы 1 пікірді көрсету",
"Show comment": "Пікірді көрсету",
"Sign in": "Кіру",
"Sign up now": "Қазір тіркелу",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Vardenis Pavardenis",
"Join the discussion": "Prisijunkite prie diskusijos",
"Just now": "Ką tik",
"Load more ({{amount}})": "",
"Local resident": "Vietinis gyventojas",
"Member discussion": "Komentarai",
"Name": "Vardas",
@ -55,9 +56,7 @@
"Sent": "Išsiųsta",
"Show": "Rodyti",
"Show {{amount}} more replies": "Rodyti daugiau atsakymų ({{amount}})",
"Show {{amount}} previous comments": "Rodyti ankstesnius komentarus ({{amount}})",
"Show 1 more reply": "Rodyti dar 1 atsakymą",
"Show 1 previous comment": "Rodyti ankstesnį komentarą",
"Show comment": "Rodyti komentarą",
"Sign in": "Prisijungti",
"Sign up now": "Registruotis",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Петар Петровски",
"Join the discussion": "Приклучете се во разговорот",
"Just now": "Штотуку",
"Load more ({{amount}})": "",
"Local resident": "Локал жител",
"Member discussion": "Разговор на членовите",
"Name": "Име",
@ -55,9 +56,7 @@
"Sent": "Испратено",
"Show": "Покажи",
"Show {{amount}} more replies": "Покажи уште {{amount}} одговори",
"Show {{amount}} previous comments": "Покажи {{amount}} претходни коментари",
"Show 1 more reply": "Покажи уште 1 одговор",
"Show 1 previous comment": "Покажи 1 претходен коментар",
"Show comment": "Покажи коментар",
"Sign in": "Најавете се",
"Sign up now": "Регистрирајте се",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "",
"Join the discussion": "",
"Just now": "",
"Load more ({{amount}})": "",
"Local resident": "",
"Member discussion": "",
"Name": "",
@ -55,9 +56,7 @@
"Sent": "",
"Show": "",
"Show {{amount}} more replies": "",
"Show {{amount}} previous comments": "",
"Show 1 more reply": "",
"Show 1 previous comment": "",
"Show comment": "",
"Sign in": "",
"Sign up now": "",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "",
"Join the discussion": "",
"Just now": "",
"Load more ({{amount}})": "",
"Local resident": "",
"Member discussion": "",
"Name": "",
@ -55,9 +56,7 @@
"Sent": "",
"Show": "",
"Show {{amount}} more replies": "",
"Show {{amount}} previous comments": "",
"Show 1 more reply": "",
"Show 1 previous comment": "",
"Show comment": "",
"Sign in": "",
"Sign up now": "",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "",
"Join the discussion": "",
"Just now": "",
"Load more ({{amount}})": "",
"Local resident": "",
"Member discussion": "",
"Name": "",
@ -55,9 +56,7 @@
"Sent": "",
"Show": "",
"Show {{amount}} more replies": "",
"Show {{amount}} previous comments": "",
"Show 1 more reply": "",
"Show 1 previous comment": "",
"Show comment": "",
"Sign in": "",
"Sign up now": "",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jan Jansen",
"Join the discussion": "Doe mee aan het gesprek",
"Just now": "Zojuist",
"Load more ({{amount}})": "",
"Local resident": "Lokale bewoner",
"Member discussion": "Gesprek",
"Name": "Naam",
@ -55,9 +56,7 @@
"Sent": "Verzonden",
"Show": "Tonen",
"Show {{amount}} more replies": "Toon {{amount}} extra antwoorden",
"Show {{amount}} previous comments": "Toon {{amount}} vorige reacties",
"Show 1 more reply": "Toon nog 1 antwoord",
"Show 1 previous comment": "Toon 1 vorige reactie",
"Show comment": "Toon reactie",
"Sign in": "Inloggen",
"Sign up now": "Registreren",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "",
"Join the discussion": "",
"Just now": "",
"Load more ({{amount}})": "",
"Local resident": "",
"Member discussion": "",
"Name": "",
@ -55,9 +56,7 @@
"Sent": "",
"Show": "",
"Show {{amount}} more replies": "",
"Show {{amount}} previous comments": "",
"Show 1 more reply": "",
"Show 1 previous comment": "",
"Show comment": "",
"Sign in": "",
"Sign up now": "",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Delta i diskusjonen",
"Just now": "Akkurat nå",
"Load more ({{amount}})": "",
"Local resident": "Lokal innbygger",
"Member discussion": "Medlemsdiskusjon",
"Name": "Navn",
@ -55,9 +56,7 @@
"Sent": "Sendt",
"Show": "Vis",
"Show {{amount}} more replies": "Vis {{amount}} flere svar",
"Show {{amount}} previous comments": "Vis {{amount}} tidligere kommentarer",
"Show 1 more reply": "Vis ett svar til",
"Show 1 previous comment": "Vis én tidligere kommentar",
"Show comment": "Vis kommentar",
"Sign in": "Logg inn",
"Sign up now": "Registrer deg nå",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Dołącz do dyskusji",
"Just now": "Przed chwilą",
"Load more ({{amount}})": "",
"Local resident": "",
"Member discussion": "Członek dyskusji",
"Name": "Imię",
@ -55,9 +56,7 @@
"Sent": "Wysłano",
"Show": "Pokza",
"Show {{amount}} more replies": "Pokaż {{amount}} kolejnych odpowiedzi",
"Show {{amount}} previous comments": "Pokaż {{amount}} poprzednich komentarzy",
"Show 1 more reply": "Pokaż następną odpowiedź",
"Show 1 previous comment": "Pokaż poprzedni komentarz",
"Show comment": "Pokaż komentarz",
"Sign in": "Zaloguj się",
"Sign up now": "Zarejestruj się",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Participe da discussão",
"Just now": "Agora mesmo",
"Load more ({{amount}})": "",
"Local resident": "Residente local",
"Member discussion": "Discussão entre membros",
"Name": "Nome",
@ -55,9 +56,7 @@
"Sent": "Enviado",
"Show": "Mostrar",
"Show {{amount}} more replies": "Mostrar mais {{amount}} respostas",
"Show {{amount}} previous comments": "Mostrar {{amount}} comentários anteriores",
"Show 1 more reply": "Mostrar 1 resposta adicional",
"Show 1 previous comment": "Mostrar 1 comentário anterior",
"Show comment": "Mostrar comentário",
"Sign in": "Entrar",
"Sign up now": "Inscreva-se agora",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jane Doe",
"Join the discussion": "Junte-se à discussão",
"Just now": "Agora",
"Load more ({{amount}})": "",
"Local resident": "Residente local",
"Member discussion": "Discussão para membros",
"Name": "Nome",
@ -55,9 +56,7 @@
"Sent": "Enviado",
"Show": "Mostrar",
"Show {{amount}} more replies": "Mostrar mais {{amount}} respostas",
"Show {{amount}} previous comments": "Mostrar os {{amount}} comentários anteriores",
"Show 1 more reply": "Mostrar mais uma resposta",
"Show 1 previous comment": "Mostrar mais um comentário",
"Show comment": "Mostrar comentário",
"Sign in": "Registar",
"Sign up now": "Registar agora",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Alătură-te discuției",
"Just now": "Chiar acum",
"Load more ({{amount}})": "",
"Local resident": "Rezident local",
"Member discussion": "Discuție membrii",
"Name": "Nume",
@ -55,9 +56,7 @@
"Sent": "Trimis",
"Show": "Arată",
"Show {{amount}} more replies": "Arată încă {{amount}} răspunsuri",
"Show {{amount}} previous comments": "Arată {{amount}} comentarii anterioare",
"Show 1 more reply": "Arată încă un răspuns",
"Show 1 previous comment": "Arată un comentariu anterior",
"Show comment": "Arată comentariul",
"Sign in": "Autentificare",
"Sign up now": "Înregistrează-te acum",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Павел Бид",
"Join the discussion": "Присоединиться к обсуждению",
"Just now": "Сейчас",
"Load more ({{amount}})": "",
"Local resident": "Местный житель",
"Member discussion": "Обсуждение участников",
"Name": "Имя",
@ -55,9 +56,7 @@
"Sent": "Отправлено",
"Show": "Показать",
"Show {{amount}} more replies": "Показать ещё {{amount}} ответа(ов)",
"Show {{amount}} previous comments": "Показать ещё {{amount}} комментария(ев)",
"Show 1 more reply": "Показать ответ",
"Show 1 previous comment": "Показать предыдущий комментарий",
"Show comment": "Показать комментарий",
"Sign in": "Войти",
"Sign up now": "Зарегистрироваться",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "ජේමි ලාර්සන්",
"Join the discussion": "සාකච්ඡාවට එක්වන්න",
"Just now": "මේ දැ\u200bන්",
"Load more ({{amount}})": "",
"Local resident": "දේශීය පදිංචි",
"Member discussion": "සාමාජික සාකච්ඡාව",
"Name": "න\u200bම",
@ -55,9 +56,7 @@
"Sent": "යැව්වා",
"Show": "පෙන්වන්\u200bන",
"Show {{amount}} more replies": "තවත් පිළිතුරු {{amount}} පෙන්වන්න",
"Show {{amount}} previous comments": "පෙර අදහස් {{amount}} පෙන්වන්න",
"Show 1 more reply": "තවත් පිළිතුරු 1ක් පෙන්වන්න",
"Show 1 previous comment": "පෙර අදහස් 1ක් පෙන්වන්න",
"Show comment": "අදහස පෙන්වන්න",
"Sign in": "Sign in වෙන්\u200bන",
"Sign up now": "දැන්ම Sign up වෙන්\u200bන",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Zapojiť sa do diskusie",
"Just now": "Teraz",
"Load more ({{amount}})": "",
"Local resident": "Miestny obyvateľ",
"Member discussion": "",
"Name": "Meno",
@ -55,9 +56,7 @@
"Sent": "Odoslané",
"Show": "Zobraziť",
"Show {{amount}} more replies": "Zobraziť {{amount}} ďalších odpoveďí",
"Show {{amount}} previous comments": "Zobraziť {{amount}} predchádzajúcich komentárov",
"Show 1 more reply": "Zobraziť 1 ďalšiu odpoveď",
"Show 1 previous comment": "Zobraziť 1 predchádzajúci komentár",
"Show comment": "Zobraziť komentár",
"Sign in": "Prihlásiť",
"Sign up now": "Registrovať sa",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "",
"Join the discussion": "",
"Just now": "",
"Load more ({{amount}})": "",
"Local resident": "",
"Member discussion": "",
"Name": "",
@ -55,9 +56,7 @@
"Sent": "",
"Show": "",
"Show {{amount}} more replies": "",
"Show {{amount}} previous comments": "",
"Show 1 more reply": "",
"Show 1 previous comment": "",
"Show comment": "",
"Sign in": "",
"Sign up now": "",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "",
"Join the discussion": "",
"Just now": "",
"Load more ({{amount}})": "",
"Local resident": "",
"Member discussion": "",
"Name": "",
@ -55,9 +56,7 @@
"Sent": "",
"Show": "",
"Show {{amount}} more replies": "",
"Show {{amount}} previous comments": "",
"Show 1 more reply": "",
"Show 1 previous comment": "",
"Show comment": "",
"Sign in": "",
"Sign up now": "",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Џејми Ларсон",
"Join the discussion": "Придружите се дискусији",
"Just now": "Управо сада",
"Load more ({{amount}})": "",
"Local resident": "Мештанин",
"Member discussion": "Дискусија чланова",
"Name": "Име",
@ -55,9 +56,7 @@
"Sent": "Послато",
"Show": "Прикажи",
"Show {{amount}} more replies": "Прикажи још {{amount}} одговора",
"Show {{amount}} previous comments": "Прикажи претходна {{amount}} коментара",
"Show 1 more reply": "Прикажи још 1 одговор",
"Show 1 previous comment": "Прикажи 1 претходни коментар",
"Show comment": "Прикажи коментар",
"Sign in": "Пријавите се",
"Sign up now": "Пријавите се сада",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "",
"Join the discussion": "",
"Just now": "",
"Load more ({{amount}})": "",
"Local resident": "",
"Member discussion": "",
"Name": "",
@ -55,9 +56,7 @@
"Sent": "",
"Show": "",
"Show {{amount}} more replies": "",
"Show {{amount}} previous comments": "",
"Show 1 more reply": "",
"Show 1 previous comment": "",
"Show comment": "",
"Sign in": "",
"Sign up now": "",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Anders Andersson",
"Join the discussion": "Var med i diskussionen",
"Just now": "Nu precis",
"Load more ({{amount}})": "",
"Local resident": "Lokalboende",
"Member discussion": "Medlemsdiskussion",
"Name": "Namn",
@ -55,9 +56,7 @@
"Sent": "Skickad",
"Show": "Visa",
"Show {{amount}} more replies": "Visa {{amount}} fler svar",
"Show {{amount}} previous comments": "Visa {{amount}} tidigare kommentarer",
"Show 1 more reply": "Visa 1 kommentar till",
"Show 1 previous comment": "Visa 1 tidigare kommentar",
"Show comment": "Visa kommentar",
"Sign in": "Logga in",
"Sign up now": "Registrera dig nu",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Jiunge na mjadala",
"Just now": "Hivi sasa",
"Load more ({{amount}})": "",
"Local resident": "Mkazi wa eneo",
"Member discussion": "Mjadala wa wanachama",
"Name": "Jina",
@ -55,9 +56,7 @@
"Sent": "Imetumwa",
"Show": "Onyesha",
"Show {{amount}} more replies": "Onyesha majibu {{amount}} zaidi",
"Show {{amount}} previous comments": "Onyesha maoni {{amount}} ya awali",
"Show 1 more reply": "Onyesha jibu 1 zaidi",
"Show 1 previous comment": "Onyesha maoni 1 ya awali",
"Show comment": "Onyesha maoni",
"Sign in": "Ingia",
"Sign up now": "Jisajili sasa",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "ஜேமி லார்சன்",
"Join the discussion": "கலந்துரையாடலில் சேரவும்",
"Just now": "சற்றுமுன்னர்தான்",
"Load more ({{amount}})": "",
"Local resident": "உள்ளூர்வாசி",
"Member discussion": "உறுப்பினர் கருத்து",
"Name": "பெயர்",
@ -55,9 +56,7 @@
"Sent": "அனுப்பப்பட்டது",
"Show": "காட்டு",
"Show {{amount}} more replies": "முந்தைய {{amount}} மேலும் பதில்கள்க் காட்டு",
"Show {{amount}} previous comments": "முந்தைய {{amount}} கருத்துகளைக் காட்டு",
"Show 1 more reply": "மேலும் 1 பதிலைக் காட்டு",
"Show 1 previous comment": "முந்தைய 1 கருத்தைக் காட்டு",
"Show comment": "கருத்தைக் காட்டு",
"Sign in": "உள்நுழைக",
"Sign up now": "இப்பொழுதே பதிவு செய்யுங்கள்",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "กนกพัฒน์ สัณห์ฤทัย",
"Join the discussion": "เข้าร่วมการสนทนา",
"Just now": "ตอนนี้",
"Load more ({{amount}})": "",
"Local resident": "ผู้อยู่อาศัยในท้องถิ่น",
"Member discussion": "การสนทนาของสมาชิก",
"Name": "ชื่อ",
@ -55,9 +56,7 @@
"Sent": "ส่งแล้ว",
"Show": "แสดง",
"Show {{amount}} more replies": "แสดง {{amount}} การตอบกลับเพิ่มเติม",
"Show {{amount}} previous comments": "แสดง {{amount}} ความคิดเห็นก่อนหน้า",
"Show 1 more reply": "แสดงอีก 1 การตอบกลับ",
"Show 1 previous comment": "แสดง 1 ความคิดเห็น",
"Show comment": "ดูความคิดเห็น",
"Sign in": "ลงชื่อเข้าใช้",
"Sign up now": "สมัครใช้งานตอนนี้เลย",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Ad Soyad",
"Join the discussion": "Tartışmaya katıl",
"Just now": "Şu anda",
"Load more ({{amount}})": "",
"Local resident": "Yerel sakini",
"Member discussion": "Üye tartışması",
"Name": "Ad",
@ -55,9 +56,7 @@
"Sent": "Gönderildi",
"Show": "Göster",
"Show {{amount}} more replies": "{{amount}} daha fazla yanıt göster",
"Show {{amount}} previous comments": "{{amount}} önceki yorumu göster",
"Show 1 more reply": "1 cevap daha göster",
"Show 1 previous comment": "1 önceki yorumu göster",
"Show comment": "Yorumu göster",
"Sign in": "Giriş yap",
"Sign up now": "Şimdi kayıt ol",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Ваше імʼя",
"Join the discussion": "Долучитися до обговорення",
"Just now": "Прямо зараз",
"Load more ({{amount}})": "",
"Local resident": "Місцевий експерт",
"Member discussion": "Обговорення учасників",
"Name": "Імʼя",
@ -55,9 +56,7 @@
"Sent": "Відправлено",
"Show": "Показати",
"Show {{amount}} more replies": "Показати ще {{amount}} відповідей",
"Show {{amount}} previous comments": "Показати {{amount}} попередніх коментарів",
"Show 1 more reply": "Показати ще одну відповідь",
"Show 1 previous comment": "Показати один попередній коментар",
"Show comment": "Показати коментар",
"Sign in": "Увійти",
"Sign up now": "Зареєструватись зараз",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "جیمی لارسن",
"Join the discussion": "تبادلے میں شامل ہوں",
"Just now": "ابھی",
"Load more ({{amount}})": "",
"Local resident": "مقامی رہائشی",
"Member discussion": "رکن کا تبادلہ",
"Name": "نام",
@ -55,9 +56,7 @@
"Sent": "بھیجا گیا",
"Show": "دکھائیں",
"Show {{amount}} more replies": "{{amount}} مزید جوابات دکھائیں",
"Show {{amount}} previous comments": "{{amount}} پچھلے تبادلات دکھائیں",
"Show 1 more reply": "1 مزید جواب دکھائیں",
"Show 1 previous comment": "1 پچھلا تبادلہ دکھائیں",
"Show comment": "تبادلہ دکھائیں",
"Sign in": "سائن ان کریں",
"Sign up now": "ابھی رجسٹر ہوں",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "",
"Join the discussion": "",
"Just now": "",
"Load more ({{amount}})": "",
"Local resident": "",
"Member discussion": "",
"Name": "",
@ -55,9 +56,7 @@
"Sent": "",
"Show": "",
"Show {{amount}} more replies": "",
"Show {{amount}} previous comments": "",
"Show 1 more reply": "",
"Show 1 previous comment": "",
"Show comment": "",
"Sign in": "",
"Sign up now": "",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "Tham gia thảo luận",
"Just now": "Vừa xong",
"Load more ({{amount}})": "",
"Local resident": "Cư dân địa phương",
"Member discussion": "Thảo luận của thành viên",
"Name": "Tên",
@ -55,9 +56,7 @@
"Sent": "Đã gửi",
"Show": "Hiển thị",
"Show {{amount}} more replies": "Xem {{amount}} trả lời khác",
"Show {{amount}} previous comments": "Đọc {{amount}} bình luận trước",
"Show 1 more reply": "Xem 1 trả lời",
"Show 1 previous comment": "Đọc 1 bình luận trước",
"Show comment": "Đọc bình luận",
"Sign in": "Đăng nhập",
"Sign up now": "Đăng ký ngay",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "加入討論",
"Just now": "剛剛",
"Load more ({{amount}})": "",
"Local resident": "本地居民",
"Member discussion": "會員討論",
"Name": "稱呼",
@ -55,9 +56,7 @@
"Sent": "已發送",
"Show": "顯示",
"Show {{amount}} more replies": "顯示後續 {{amount}} 則留言",
"Show {{amount}} previous comments": "顯示先前 {{amount}} 則留言",
"Show 1 more reply": "顯示下一則留言",
"Show 1 previous comment": "顯示前一則留言",
"Show comment": "顯示留言",
"Sign in": "登錄",
"Sign up now": "立即註冊",

View file

@ -35,6 +35,7 @@
"Jamie Larson": "Jamie Larson",
"Join the discussion": "加入讨论",
"Just now": "刚刚",
"Load more ({{amount}})": "",
"Local resident": "本地居民",
"Member discussion": "会员讨论",
"Name": "称呼",
@ -55,9 +56,7 @@
"Sent": "已发送",
"Show": "显示",
"Show {{amount}} more replies": "显示后续{{amount}}条评论",
"Show {{amount}} previous comments": "显示先前{{amount}}条评论",
"Show 1 more reply": "显示下一条评论",
"Show 1 previous comment": "显示前一条评论",
"Show comment": "显示评论",
"Sign in": "登录",
"Sign up now": "立刻注册",