diff --git a/apps/admin-x-framework/src/api/comments.ts b/apps/admin-x-framework/src/api/comments.ts index 7808ff5222c..eef51ef64be 100644 --- a/apps/admin-x-framework/src/api/comments.ts +++ b/apps/admin-x-framework/src/api/comments.ts @@ -11,6 +11,7 @@ export type Comment = { id: string; html: string | null; status: 'published' | 'hidden' | 'deleted'; + pinned: boolean; created_at: string; updated_at: string; post_id: string; @@ -138,6 +139,34 @@ export const useDeleteComment = createMutation({ + method: 'PUT', + path: ({id}) => `/comments/${id}/`, + body: ({id}) => ({ + comments: [{ + id, + pinned: true + }] + }), + invalidateQueries: { + dataType + } +}); + +export const useUnpinComment = createMutation({ + method: 'PUT', + path: ({id}) => `/comments/${id}/`, + body: ({id}) => ({ + comments: [{ + id, + pinned: false + }] + }), + invalidateQueries: { + dataType + } +}); + export const useCommentReplies = createQueryWithId({ dataType, path: (id: string) => `/comments/${id}/replies/`, diff --git a/apps/admin/src/layout/app-sidebar/nav-content.tsx b/apps/admin/src/layout/app-sidebar/nav-content.tsx index ec9bed45d94..a6a561eaeb8 100644 --- a/apps/admin/src/layout/app-sidebar/nav-content.tsx +++ b/apps/admin/src/layout/app-sidebar/nav-content.tsx @@ -3,6 +3,7 @@ import React from "react" import {SidebarGroup, SidebarGroupContent, SidebarMenu, SidebarMenuBadge} from "@tryghost/shade/components" import {formatNumber, LucideIcon} from "@tryghost/shade/utils" import { useCurrentUser } from "@tryghost/admin-x-framework/api/current-user"; +import {getSettingValue, useBrowseSettings} from "@tryghost/admin-x-framework/api/settings"; import { canManageMembers, canManageTags } from "@tryghost/admin-x-framework/api/users"; import { NavMenuItem } from "./nav-menu-item"; import { useMemberCount } from "./hooks/use-member-count"; @@ -69,6 +70,7 @@ function MembersNavItemContent({ function NavContent({ ...props }: React.ComponentProps) { const { data: currentUser } = useCurrentUser(); + const {data: settingsData} = useBrowseSettings(); const [savedPostsExpanded, setPostsExpanded] = useNavigationExpanded('posts'); const [savedMembersExpanded, setMembersExpanded] = useNavigationExpanded('members'); const postCustomViews = useCustomSidebarViews('posts'); @@ -82,6 +84,8 @@ function NavContent({ ...props }: React.ComponentProps) { const showTags = currentUser && canManageTags(currentUser); const showMembers = currentUser && canManageMembers(currentUser); + const commentsEnabled = getSettingValue(settingsData?.settings, 'comments_enabled'); + const showComments = !!showMembers && commentModerationEnabled && commentsEnabled !== 'off'; const isDraftPostsRouteActive = routing.isRouteActive('posts', {type: 'draft'}); const isScheduledPostsRouteActive = routing.isRouteActive('posts', {type: 'scheduled'}); const isPublishedPostsRouteActive = routing.isRouteActive('posts', {type: 'published'}); @@ -202,7 +206,7 @@ function NavContent({ ...props }: React.ComponentProps) { )} - {showMembers && commentModerationEnabled && ( + {showComments && ( { @@ -501,6 +519,8 @@ export const Actions = { addComment, editComment, hideComment, + pinComment, + unpinComment, deleteComment, showComment, likeComment, diff --git a/apps/comments-ui/src/app-context.ts b/apps/comments-ui/src/app-context.ts index 49f178e0cd4..7733c220b77 100644 --- a/apps/comments-ui/src/app-context.ts +++ b/apps/comments-ui/src/app-context.ts @@ -21,6 +21,7 @@ export type Comment = { in_reply_to_snippet: string, replies: Comment[], status: string, + pinned: boolean, liked: boolean, count: { replies: number, diff --git a/apps/comments-ui/src/components/content/comment.tsx b/apps/comments-ui/src/components/content/comment.tsx index 9462cc81447..581deb638e9 100644 --- a/apps/comments-ui/src/components/content/comment.tsx +++ b/apps/comments-ui/src/components/content/comment.tsx @@ -8,6 +8,8 @@ import ReplyButton from './buttons/reply-button'; import ReplyForm from './forms/reply-form'; import {Avatar, BlankAvatar} from './avatar'; import {Comment, OpenCommentForm, useAppContext} from '../../app-context'; +import {ReactComponent as PinIcon} from '../../images/icons/pin.svg'; +import {ReactComponent as PinOffIcon} from '../../images/icons/pin-off.svg'; import {Transition} from '@headlessui/react'; import {buildCommentPermalink, findCommentById, formatExplicitTime, getCommentInReplyToSnippet, getMemberNameFromComment} from '../../utils/helpers'; import {useRelativeTime} from '../../utils/hooks'; @@ -128,7 +130,7 @@ const PublishedComment: React.FC = ({comment, parent, ope const avatar = (); return ( - +
{isInEditMode ? ( <> @@ -183,9 +185,10 @@ const UnpublishedComment: React.FC = ({comment, openEdi const showMoreButton = isAdmin && comment.status === 'hidden'; return ( - +
+

{notPublishedMessage}

@@ -228,6 +231,44 @@ const EditedInfo: React.FC<{comment: Comment}> = ({comment}) => { ); }; + +const PinnedLabel: React.FC<{comment: Comment}> = ({comment}) => { + const {dispatchAction, isAdmin, t} = useAppContext(); + + if (!comment.pinned) { + return null; + } + + const labelClassName = 'inline-flex items-center gap-1 rounded-full border border-amber-300/70 bg-amber-50 px-2 py-0.5 font-sans text-xs font-medium leading-none text-amber-800 dark:border-amber-400/30 dark:bg-amber-400/10 dark:text-amber-100'; + + if (isAdmin) { + const handleUnpinClick = (event: React.MouseEvent) => { + event.stopPropagation(); + dispatchAction('unpinComment', comment); + }; + + return ( + + ); + } + + return ( + + + ); +}; + const RepliesContainer: React.FC = ({comment, className = ''}) => { const hasReplies = comment.replies && comment.replies.length > 0; @@ -322,6 +363,11 @@ const CommentHeader: React.FC = ({comment, className = ''}) {timestampElement} + {comment.pinned && ( + + + + )}
@@ -433,10 +479,18 @@ type CommentLayoutProps = { hasReplies: boolean; className?: string; memberUuid?: string; + isPinned?: boolean; } -const CommentLayout: React.FC = ({children, avatar, hasReplies, className = '', memberUuid = ''}) => { + +const COMMENT_GAP_CLASS_NAME = 'mb-7'; +const PINNED_COMMENT_GAP_CLASS_NAME = 'mb-4'; +const PINNED_COMMENT_BOX_CLASS_NAME = 'bg-amber-50/70 px-3 py-3 dark:bg-amber-400/10'; + +const CommentLayout: React.FC = ({children, avatar, hasReplies, className = '', memberUuid = '', isPinned = false}) => { + const bottomMarginClassName = isPinned ? PINNED_COMMENT_GAP_CLASS_NAME : hasReplies ? 'mb-0' : COMMENT_GAP_CLASS_NAME; + return ( -
+
{avatar} diff --git a/apps/comments-ui/src/components/content/context-menus/admin-context-menu.tsx b/apps/comments-ui/src/components/content/context-menus/admin-context-menu.tsx index 3d0d55a5d15..f28f4ccc526 100644 --- a/apps/comments-ui/src/components/content/context-menus/admin-context-menu.tsx +++ b/apps/comments-ui/src/components/content/context-menus/admin-context-menu.tsx @@ -1,50 +1,101 @@ import {Comment, useAppContext, useLabs} from '../../../app-context'; +import {ReactComponent as ExternalLinkIcon} from '../../../images/icons/external-link.svg'; +import {ReactComponent as EyeIcon} from '../../../images/icons/eye.svg'; +import {ReactComponent as EyeOffIcon} from '../../../images/icons/eye-off.svg'; +import {ReactComponent as PencilIcon} from '../../../images/icons/pencil.svg'; +import {ReactComponent as PinIcon} from '../../../images/icons/pin.svg'; +import {ReactComponent as PinOffIcon} from '../../../images/icons/pin-off.svg'; +import {ReactComponent as TrashIcon} from '../../../images/icons/trash.svg'; type Props = { comment: Comment; close: () => void; + showAuthorActions?: boolean; + toggleEdit?: () => void; }; -const AdminContextMenu: React.FC = ({comment, close}) => { +const AdminContextMenu: React.FC = ({comment, close, showAuthorActions = false, toggleEdit}) => { const {dispatchAction, t, adminUrl} = useAppContext(); const labs = useLabs(); - const hideComment = () => { - dispatchAction('hideComment', comment); + const closeAfter = (action: () => void) => () => { + action(); close(); }; - const showComment = () => { - dispatchAction('showComment', comment); - close(); - }; + const editComment = toggleEdit && closeAfter(toggleEdit); + const deleteComment = closeAfter(() => { + dispatchAction('openPopup', { + type: 'deletePopup', + comment + }); + }); + const hideComment = closeAfter(() => dispatchAction('hideComment', comment)); + const showComment = closeAfter(() => dispatchAction('showComment', comment)); + const pinComment = closeAfter(() => dispatchAction('pinComment', comment)); + const unpinComment = closeAfter(() => dispatchAction('unpinComment', comment)); const isHidden = comment.status !== 'published'; + const canPin = !comment.parent_id && comment.status !== 'deleted'; const adminCommentUrl = adminUrl ? `${adminUrl}#/comments/?id=is:${comment.id}` : null; + const baseItemClassName = 'flex w-full items-center gap-3 rounded px-3 py-2 text-left text-[14px] leading-5 transition-colors hover:bg-neutral-100 dark:hover:bg-neutral-700'; + const itemClassName = `${baseItemClassName} text-neutral-900 dark:text-white`; + const destructiveItemClassName = `${baseItemClassName} text-red-600 dark:text-red-500`; + const iconClassName = 'size-4 shrink-0'; return (
- { - isHidden ? - - : - - } {labs?.commentModeration && adminCommentUrl && ( - {t('View in admin')} + )} + {canPin && ( + comment.pinned ? + + : + + )} + { + isHidden ? + + : + + } + {showAuthorActions && ( +
+ )} + {showAuthorActions && editComment && ( + + )} + {showAuthorActions && ( + + )}
); }; diff --git a/apps/comments-ui/src/components/content/context-menus/author-context-menu.tsx b/apps/comments-ui/src/components/content/context-menus/author-context-menu.tsx index f4c93c5d98b..739cb185174 100644 --- a/apps/comments-ui/src/components/content/context-menus/author-context-menu.tsx +++ b/apps/comments-ui/src/components/content/context-menus/author-context-menu.tsx @@ -1,5 +1,7 @@ import React from 'react'; import {Comment, useAppContext} from '../../../app-context'; +import {ReactComponent as PencilIcon} from '../../../images/icons/pencil.svg'; +import {ReactComponent as TrashIcon} from '../../../images/icons/trash.svg'; type Props = { comment: Comment; @@ -17,13 +19,20 @@ const AuthorContextMenu: React.FC = ({comment, close, toggleEdit}) => { close(); }; + const baseItemClassName = 'flex w-full items-center gap-3 rounded px-3 py-2 text-left text-[14px] leading-5 transition-colors hover:bg-neutral-100 dark:hover:bg-neutral-700'; + const itemClassName = `${baseItemClassName} text-neutral-900 dark:text-white`; + const destructiveItemClassName = `${baseItemClassName} text-red-600 dark:text-red-500`; + const iconClassName = 'size-4 shrink-0'; + return (
- -
); diff --git a/apps/comments-ui/src/components/content/context-menus/comment-context-menu.tsx b/apps/comments-ui/src/components/content/context-menus/comment-context-menu.tsx index 023f7e51f0c..a5d565c56b9 100644 --- a/apps/comments-ui/src/components/content/context-menus/comment-context-menu.tsx +++ b/apps/comments-ui/src/components/content/context-menus/comment-context-menu.tsx @@ -65,16 +65,16 @@ const CommentContextMenu: React.FC = ({comment, close, toggleEdit}) => { event.stopPropagation(); }; + const menuClassName = 'absolute z-10 w-52 rounded-lg bg-white p-1 font-sans text-sm shadow-lg outline-0 dark:bg-neutral-800 dark:text-white'; + let contextMenu = null; if (comment.status === 'published') { - if (isAuthor) { + if (isAdmin) { + contextMenu = ; + } else if (isAuthor) { contextMenu = ; } else { - if (isAdmin) { - contextMenu = ; - } else { - contextMenu = ; - } + contextMenu = ; } } else { if (isAdmin) { @@ -86,7 +86,7 @@ const CommentContextMenu: React.FC = ({comment, close, toggleEdit}) => { return (
-
+
{contextMenu}
diff --git a/apps/comments-ui/src/components/content/context-menus/not-author-context-menu.tsx b/apps/comments-ui/src/components/content/context-menus/not-author-context-menu.tsx index 28513b14bab..268424e840d 100644 --- a/apps/comments-ui/src/components/content/context-menus/not-author-context-menu.tsx +++ b/apps/comments-ui/src/components/content/context-menus/not-author-context-menu.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {Comment, useAppContext} from '../../../app-context'; +import {ReactComponent as FlagIcon} from '../../../images/icons/flag.svg'; type Props = { comment: Comment; @@ -16,9 +17,13 @@ const NotAuthorContextMenu: React.FC = ({comment, close}) => { close(); }; + const itemClassName = 'flex w-full items-center gap-3 rounded px-3 py-2 text-left text-[14px] leading-5 text-neutral-900 transition-colors hover:bg-neutral-100 dark:text-white dark:hover:bg-neutral-700'; + const iconClassName = 'size-4 shrink-0'; + return (
-
diff --git a/apps/comments-ui/src/images/icons/external-link.svg b/apps/comments-ui/src/images/icons/external-link.svg new file mode 100644 index 00000000000..56a29c28384 --- /dev/null +++ b/apps/comments-ui/src/images/icons/external-link.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/comments-ui/src/images/icons/eye-off.svg b/apps/comments-ui/src/images/icons/eye-off.svg new file mode 100644 index 00000000000..bf1b39d66ab --- /dev/null +++ b/apps/comments-ui/src/images/icons/eye-off.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/comments-ui/src/images/icons/eye.svg b/apps/comments-ui/src/images/icons/eye.svg new file mode 100644 index 00000000000..dd38ff78df3 --- /dev/null +++ b/apps/comments-ui/src/images/icons/eye.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/comments-ui/src/images/icons/flag.svg b/apps/comments-ui/src/images/icons/flag.svg new file mode 100644 index 00000000000..03b09730657 --- /dev/null +++ b/apps/comments-ui/src/images/icons/flag.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/comments-ui/src/images/icons/pencil.svg b/apps/comments-ui/src/images/icons/pencil.svg new file mode 100644 index 00000000000..23c128925fa --- /dev/null +++ b/apps/comments-ui/src/images/icons/pencil.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/comments-ui/src/images/icons/pin-off.svg b/apps/comments-ui/src/images/icons/pin-off.svg new file mode 100644 index 00000000000..d76537aa142 --- /dev/null +++ b/apps/comments-ui/src/images/icons/pin-off.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/comments-ui/src/images/icons/pin.svg b/apps/comments-ui/src/images/icons/pin.svg new file mode 100644 index 00000000000..56614ab939a --- /dev/null +++ b/apps/comments-ui/src/images/icons/pin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/comments-ui/src/images/icons/trash.svg b/apps/comments-ui/src/images/icons/trash.svg new file mode 100644 index 00000000000..d9244f5275a --- /dev/null +++ b/apps/comments-ui/src/images/icons/trash.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/apps/comments-ui/src/utils/admin-api.ts b/apps/comments-ui/src/utils/admin-api.ts index bdc841d196d..da592795fb9 100644 --- a/apps/comments-ui/src/utils/admin-api.ts +++ b/apps/comments-ui/src/utils/admin-api.ts @@ -62,6 +62,12 @@ export function setupAdminAPI({adminUrl}: {adminUrl: string}) { async showComment({id} : {id: string}) { return await callApi('showComment', {id}); }, + async pinComment({id}: {id: string}) { + return await callApi('pinComment', {id}); + }, + async unpinComment({id}: {id: string}) { + return await callApi('unpinComment', {id}); + }, async browse({page, postId, order, memberUuid}: {page: number, postId: string, order?: string, memberUuid?: string}) { let filter = null; diff --git a/apps/comments-ui/test/e2e/admin-moderation.test.ts b/apps/comments-ui/test/e2e/admin-moderation.test.ts index f40de7934b2..13cb63d7ce0 100644 --- a/apps/comments-ui/test/e2e/admin-moderation.test.ts +++ b/apps/comments-ui/test/e2e/admin-moderation.test.ts @@ -101,6 +101,32 @@ test.describe('Admin moderation', async () => { await expect(frame.getByTestId('hide-button')).toBeVisible(); }); + test('has pin option when signed in to Ghost admin and viewing own comment', async ({page}) => { + mockedApi.addComment({ + html: `

This is comment 1

`, + member: {id: '1', uuid: '12345'} + }); + const {frame} = await initializeTest(page); + + const moreButtons = frame.getByTestId('more-button'); + await expect(moreButtons).toHaveCount(1); + + await moreButtons.nth(0).click(); + await expect(frame.getByTestId('pin-button')).toBeVisible(); + await expect(frame.getByTestId('edit')).toBeVisible(); + }); + + test('has pin option when signed in to Ghost admin and viewing another member comment', async ({page}) => { + mockedApi.addComment({html: `

This is comment 1

`}); + const {frame} = await initializeTest(page); + + const moreButtons = frame.getByTestId('more-button'); + await expect(moreButtons).toHaveCount(1); + + await moreButtons.nth(0).click(); + await expect(frame.getByTestId('pin-button')).toBeVisible(); + }); + test('member uuid are passed to admin browse api params', async ({page}) => { mockedApi.addComment({html: '

This is comment 1

'}); const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments'); diff --git a/apps/comments-ui/test/unit/actions.test.js b/apps/comments-ui/test/unit/actions.test.js index 5b28b5fca0c..4825042d8ce 100644 --- a/apps/comments-ui/test/unit/actions.test.js +++ b/apps/comments-ui/test/unit/actions.test.js @@ -33,4 +33,38 @@ describe('Actions', function () { ]); }); }); + + describe('pinComment', function () { + it('pins via admin API and refetches the current order', async function () { + const state = { + adminApi: { + pinComment: vi.fn(() => Promise.resolve()) + }, + order: 'created_at desc' + }; + const dispatchAction = vi.fn(); + + await Actions.pinComment({state, data: {id: '1'}, dispatchAction}); + + expect(state.adminApi.pinComment).toHaveBeenCalledWith({id: '1'}); + expect(dispatchAction).toHaveBeenCalledWith('setOrder', {order: 'created_at desc'}); + }); + }); + + describe('unpinComment', function () { + it('unpins via admin API and refetches the current order', async function () { + const state = { + adminApi: { + unpinComment: vi.fn(() => Promise.resolve()) + }, + order: 'created_at asc' + }; + const dispatchAction = vi.fn(); + + await Actions.unpinComment({state, data: {id: '1'}, dispatchAction}); + + expect(state.adminApi.unpinComment).toHaveBeenCalledWith({id: '1'}); + expect(dispatchAction).toHaveBeenCalledWith('setOrder', {order: 'created_at asc'}); + }); + }); }); diff --git a/apps/comments-ui/test/unit/components/content/comment.test.jsx b/apps/comments-ui/test/unit/components/content/comment.test.jsx index e0795a529b0..c8d62c2b964 100644 --- a/apps/comments-ui/test/unit/components/content/comment.test.jsx +++ b/apps/comments-ui/test/unit/components/content/comment.test.jsx @@ -1,7 +1,7 @@ import {AppContext} from '../../../../src/app-context'; import {CommentComponent, RepliedToSnippet} from '../../../../src/components/content/comment'; import {buildComment} from '../../../utils/fixtures'; -import {render, screen} from '@testing-library/react'; +import {fireEvent, render, screen} from '@testing-library/react'; const contextualRender = (ui, {appContext, ...renderOptions}) => { const contextWithDefaults = { @@ -62,6 +62,55 @@ describe('', function () { const {container} = contextualRender(, {appContext}); expect(container.querySelector('[data-member-uuid="123"]')).not.toBeInTheDocument(); }); + + it('renders pinned badge inline after the timestamp', function () { + const comment = buildComment({ + pinned: true + }); + const appContext = {comments: [comment]}; + + contextualRender(, {appContext}); + + const label = screen.getByTestId('pinned-comment-label'); + expect(label.parentElement).toHaveClass('ml-2'); + expect(label.parentElement?.parentElement?.querySelector('a')).toHaveAttribute('href', `https://example.com/post#ghost-comments-${comment.id}`); + }); + + it('renders pinned badge as an unpin button for admins', function () { + const comment = buildComment({ + pinned: true + }); + const dispatchAction = vi.fn(); + const appContext = {comments: [comment], dispatchAction, isAdmin: true}; + + contextualRender(, {appContext}); + + const button = screen.getByRole('button', {name: 'Unpin comment'}); + + expect(button).toHaveTextContent('Pinned'); + expect(button).toHaveTextContent('Unpin'); + + fireEvent.click(button); + + expect(dispatchAction).toHaveBeenCalledWith('unpinComment', comment); + }); + + it('keeps a bottom gap after pinned comments with replies', function () { + const reply = buildComment({ + html: '

Reply

' + }); + const comment = buildComment({ + pinned: true, + replies: [reply] + }); + const appContext = {comments: [comment]}; + + const {container} = contextualRender(, {appContext}); + + const pinnedComment = container.querySelector('[data-pinned="true"]'); + expect(pinnedComment).toHaveClass('mb-4'); + expect(pinnedComment).toHaveClass('py-3'); + }); }); describe('', function () { diff --git a/apps/comments-ui/test/unit/components/content/context-menus/comment-context-menu.test.jsx b/apps/comments-ui/test/unit/components/content/context-menus/comment-context-menu.test.jsx index c1f5851326f..05db6f74304 100644 --- a/apps/comments-ui/test/unit/components/content/context-menus/comment-context-menu.test.jsx +++ b/apps/comments-ui/test/unit/components/content/context-menus/comment-context-menu.test.jsx @@ -3,7 +3,7 @@ import React from 'react'; import sinon from 'sinon'; import {AppContext} from '../../../../../src/app-context'; import {buildComment} from '../../../../utils/fixtures'; -import {render, screen} from '@testing-library/react'; +import {fireEvent, render, screen} from '@testing-library/react'; const contextualRender = (ui, {appContext, ...renderOptions}) => { const contextWithDefaults = { @@ -37,4 +37,55 @@ describe('', () => { contextualRender(, {appContext: {admin: true}}); expect(screen.getByTestId('comment-context-menu-inner')).toHaveClass('bottom-full', 'mb-6'); }); + + it('shows pin action for top-level comments when admin', () => { + const dispatchAction = sinon.spy(); + const comment = buildComment({pinned: false}); + + contextualRender( {}} comment={comment} />, {appContext: {dispatchAction, isAdmin: true}}); + + fireEvent.click(screen.getByTestId('pin-button')); + + expect(dispatchAction.calledWith('pinComment', comment)).toBe(true); + }); + + it('shows pin action for own comments when admin', () => { + const dispatchAction = sinon.spy(); + const member = {uuid: 'member-uuid'}; + const comment = buildComment({ + member: { + uuid: member.uuid + }, + pinned: false + }); + + contextualRender( {}} comment={comment} toggleEdit={() => {}} />, {appContext: {dispatchAction, isAdmin: true, member}}); + + expect(screen.getByTestId('edit')).toBeInTheDocument(); + expect(screen.getByTestId('delete')).toBeInTheDocument(); + + fireEvent.click(screen.getByTestId('pin-button')); + + expect(dispatchAction.calledWith('pinComment', comment)).toBe(true); + }); + + it('shows unpin action for pinned top-level comments when admin', () => { + const dispatchAction = sinon.spy(); + const comment = buildComment({pinned: true}); + + contextualRender( {}} comment={comment} />, {appContext: {dispatchAction, isAdmin: true}}); + + fireEvent.click(screen.getByTestId('unpin-button')); + + expect(dispatchAction.calledWith('unpinComment', comment)).toBe(true); + }); + + it('does not show pin action for replies', () => { + const comment = buildComment({parent_id: buildComment().id}); + + contextualRender( {}} comment={comment} />, {appContext: {isAdmin: true}}); + + expect(screen.queryByTestId('pin-button')).not.toBeInTheDocument(); + expect(screen.queryByTestId('unpin-button')).not.toBeInTheDocument(); + }); }); diff --git a/apps/comments-ui/test/utils/fixtures.ts b/apps/comments-ui/test/utils/fixtures.ts index 6d2e2b9eb8d..03e1ba07195 100644 --- a/apps/comments-ui/test/utils/fixtures.ts +++ b/apps/comments-ui/test/utils/fixtures.ts @@ -1,4 +1,4 @@ -const ObjectId = require('bson-objectid').default; +import ObjectId from 'bson-objectid'; let memberCounter = 0; export function buildMember(override: any = {}) { @@ -42,6 +42,7 @@ export function buildComment(override: any = {}) { edited_at: null, member: buildMember(), status: 'published', + pinned: false, ...override, count: { replies: 0, @@ -67,7 +68,7 @@ export function buildReply(override: any = {}) { }; } -export function buildCommentsReply(override: any = {}) { +export function buildCommentsReply() { return { comments: [], meta: { diff --git a/apps/posts/src/views/comments/components/comment-header.tsx b/apps/posts/src/views/comments/components/comment-header.tsx index 99eff0cd99d..eb1a67f3d16 100644 --- a/apps/posts/src/views/comments/components/comment-header.tsx +++ b/apps/posts/src/views/comments/components/comment-header.tsx @@ -1,5 +1,6 @@ import {Badge, Button, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from '@tryghost/shade/components'; import {LucideIcon, cn, formatTimestamp} from '@tryghost/shade/utils'; +import type {MouseEvent} from 'react'; function formatDate(dateString: string): string { const date = new Date(dateString); @@ -19,23 +20,38 @@ interface CommentHeaderProps { createdAt?: string; isHidden?: boolean; canComment?: boolean | null; + isPinned?: boolean; onAuthorClick?: () => void; postTitle?: string | null; onPostClick?: () => void; + onUnpinClick?: () => void; className?: string; } +const pinnedBadgeClassName = 'inline-flex items-center gap-1 rounded-full border border-amber-300/70 bg-amber-50 px-2 py-0.5 font-sans text-xs font-medium leading-none text-amber-800 dark:border-amber-400/30 dark:bg-amber-400/10 dark:text-amber-100'; +const pinnedButtonClassName = cn( + pinnedBadgeClassName, + 'hover:border-amber-400 hover:bg-amber-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-500 dark:hover:border-amber-400/50 dark:hover:bg-amber-400/20' +); + export function CommentHeader({ memberName, memberId, createdAt, isHidden, canComment, + isPinned, onAuthorClick, postTitle, onPostClick, + onUnpinClick, className }: CommentHeaderProps) { + const handleUnpinClick = (event: MouseEvent) => { + event.stopPropagation(); + onUnpinClick?.(); + }; + return (
Hidden )} + {isPinned && ( + onUnpinClick ? ( + + ) : ( + + + Pinned + + ) + )}
); } diff --git a/apps/posts/src/views/comments/components/comment-menu.tsx b/apps/posts/src/views/comments/components/comment-menu.tsx index 4785b43cf59..d982997e0d7 100644 --- a/apps/posts/src/views/comments/components/comment-menu.tsx +++ b/apps/posts/src/views/comments/components/comment-menu.tsx @@ -1,5 +1,5 @@ import {Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger} from '@tryghost/shade/components'; -import {Comment} from '@tryghost/admin-x-framework/api/comments'; +import {Comment, usePinComment, useUnpinComment} from '@tryghost/admin-x-framework/api/comments'; import {DisableCommentingDialog} from './disable-commenting-dialog'; import {LucideIcon} from '@tryghost/shade/utils'; import {useDisableMemberCommenting, useEnableMemberCommenting} from '@tryghost/admin-x-framework/api/members'; @@ -14,12 +14,15 @@ export function CommentMenu({ }: CommentMenuProps) { const {mutate: disableCommenting} = useDisableMemberCommenting(); const {mutate: enableCommenting} = useEnableMemberCommenting(); + const {mutate: pinComment} = usePinComment(); + const {mutate: unpinComment} = useUnpinComment(); const [disableDialogOpen, setDisableDialogOpen] = useState(false); const {id: commentId, post, member} = comment; const postUrl = post?.url; const memberId = member?.id; const canComment = member?.can_comment; + const canPin = !comment.parent_id && comment.status !== 'deleted'; const handleDisableCommenting = (hideComments: boolean) => { if (memberId) { @@ -68,6 +71,19 @@ export function CommentMenu({ )} + {canPin && ( + comment.pinned ? ( + unpinComment({id: commentId})}> + + Unpin comment + + ) : ( + pinComment({id: commentId})}> + + Pin comment + + ) + )} {memberId && ( canComment !== false ? ( setDisableDialogOpen(true)}> diff --git a/apps/posts/src/views/comments/components/comment-thread-list.tsx b/apps/posts/src/views/comments/components/comment-thread-list.tsx index d4f01c0d76c..19d1192f185 100644 --- a/apps/posts/src/views/comments/components/comment-thread-list.tsx +++ b/apps/posts/src/views/comments/components/comment-thread-list.tsx @@ -1,7 +1,7 @@ import CommentContent from './comment-content'; import React from 'react'; import {Button, LoadingIndicator} from '@tryghost/shade/components'; -import {Comment, useHideComment, useShowComment} from '@tryghost/admin-x-framework/api/comments'; +import {Comment, useHideComment, useShowComment, useUnpinComment} from '@tryghost/admin-x-framework/api/comments'; import {CommentAvatar} from './comment-avatar'; import {CommentHeader} from './comment-header'; import {CommentMenu} from './comment-menu'; @@ -33,6 +33,7 @@ function CommentRow({comment, isReply = false, isSelectedComment = false, select const [searchParams] = useSearchParams(); const {mutate: hideComment} = useHideComment(); const {mutate: showComment} = useShowComment(); + const {mutate: unpinComment} = useUnpinComment(); // Check replies array for loaded objects, or count.direct_replies for unloaded // TODO: remove count.replies fallback once backend is fully rolled out @@ -60,8 +61,10 @@ function CommentRow({comment, isReply = false, isSelectedComment = false, select canComment={comment.member?.can_comment} createdAt={comment.created_at} isHidden={comment.status === 'hidden'} + isPinned={comment.pinned} memberId={comment.member?.id} memberName={comment.member?.name} + onUnpinClick={() => unpinComment({id: comment.id})} /> {comment.in_reply_to_snippet && isSelectedComment && ( diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index 36cdd7f33aa..642f764b824 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -2,7 +2,7 @@ import CommentContent from './comment-content'; import CommentThreadSidebar from './comment-thread-sidebar'; import LoadMoreButton from '@components/virtual-table/load-more-button'; import {Button} from '@tryghost/shade/components'; -import {Comment, useHideComment, useShowComment} from '@tryghost/admin-x-framework/api/comments'; +import {Comment, useHideComment, useShowComment, useUnpinComment} from '@tryghost/admin-x-framework/api/comments'; import {CommentAvatar} from './comment-avatar'; import {CommentHeader} from './comment-header'; import {CommentMenu} from './comment-menu'; @@ -66,6 +66,7 @@ function CommentsList({ const {mutate: hideComment} = useHideComment(); const {mutate: showComment} = useShowComment(); + const {mutate: unpinComment} = useUnpinComment(); const handleCloseSidebar = (open: boolean) => { setThreadSidebarOpen(open); @@ -131,7 +132,7 @@ function CommentsList({
{ // Close sidebar when clicking on a comment in the main list @@ -152,11 +153,13 @@ function CommentsList({ canComment={item.member?.can_comment} createdAt={item.created_at} isHidden={item.status === 'hidden'} + isPinned={item.pinned} memberId={item.member?.id} memberName={item.member?.name} postTitle={item.post?.title} onAuthorClick={item.member?.id ? () => onAddFilter('author', item.member!.id) : undefined} onPostClick={item.post?.id ? () => onAddFilter('post', item.post!.id) : undefined} + onUnpinClick={() => unpinComment({id: item.id})} /> {item.in_reply_to_snippet && ( diff --git a/apps/posts/test/unit/views/comments/components/comment-header.test.tsx b/apps/posts/test/unit/views/comments/components/comment-header.test.tsx new file mode 100644 index 00000000000..1770fd12d21 --- /dev/null +++ b/apps/posts/test/unit/views/comments/components/comment-header.test.tsx @@ -0,0 +1,39 @@ +import {CommentHeader} from '../../../../../src/views/comments/components/comment-header'; +import {fireEvent, render, screen} from '@testing-library/react'; + +describe('CommentHeader', () => { + it('renders pinned badge as an unpin button', () => { + const onUnpinClick = vi.fn(); + + render( + + ); + + const unpinButton = screen.getByRole('button', {name: 'Unpin comment'}); + + expect(unpinButton).toHaveTextContent('Pinned'); + expect(unpinButton).toHaveTextContent('Unpin'); + + fireEvent.click(unpinButton); + + expect(onUnpinClick).toHaveBeenCalledOnce(); + }); + + it('renders pinned badge as static text when no unpin action is available', () => { + render( + + ); + + expect(screen.queryByRole('button', {name: 'Unpin comment'})).not.toBeInTheDocument(); + expect(screen.getByText('Pinned')).toBeInTheDocument(); + }); +}); diff --git a/e2e/tests/portal/member-actions.test.ts b/e2e/tests/portal/member-actions.test.ts index d4b59055784..d27ba09db78 100644 --- a/e2e/tests/portal/member-actions.test.ts +++ b/e2e/tests/portal/member-actions.test.ts @@ -2,6 +2,7 @@ import {APIRequestContext, Page} from '@playwright/test'; import {HomePage, MemberDetailsPage, MembersPage} from '@/helpers/pages'; import {MemberFactory, createMemberFactory} from '@/data-factory'; import {PortalAccountHomePage, PortalNewsletterManagementPage} from '@/portal-pages'; +import {SettingsService} from '@/helpers/services/settings/settings-service'; import {expect, test} from '@/helpers/playwright'; import {usePerTestIsolation} from '@/helpers/playwright/isolation'; @@ -78,6 +79,9 @@ test.describe('Portal - Member Actions', () => { }); test('can unsubscribe from newsletter', async ({page}) => { + const settingsService = new SettingsService(page.request); + await settingsService.setCommentsEnabled('off'); + const member = await createSubscribedMember(page.request, memberFactory); await impersonateMember(page, member.name!); @@ -120,7 +124,9 @@ test.describe('Portal - Member Actions', () => { await expect(newsletterManagement.newsletterToggleCheckbox(0)).not.toBeChecked(); await expect(newsletterManagement.newsletterToggleCheckbox(1)).not.toBeChecked(); - const memberNewsletters = await getMemberNewsletters(page.request, member.id); - expect(memberNewsletters).toHaveLength(0); + await expect(async () => { + const memberNewsletters = await getMemberNewsletters(page.request, member.id); + expect(memberNewsletters).toHaveLength(0); + }).toPass(); }); }); diff --git a/e2e/tests/public/stripe-webhook-subscription-lifecycle.test.ts b/e2e/tests/public/stripe-webhook-subscription-lifecycle.test.ts index 3e69125d5a9..3ba6faf96fb 100644 --- a/e2e/tests/public/stripe-webhook-subscription-lifecycle.test.ts +++ b/e2e/tests/public/stripe-webhook-subscription-lifecycle.test.ts @@ -1,6 +1,7 @@ import {APIRequestContext, Page} from '@playwright/test'; import {HomePage, MemberDetailsPage, MembersPage, PortalAccountPage} from '@/helpers/pages'; import {MembersService} from '@/helpers/services/members'; +import {SettingsService} from '@/helpers/services/settings/settings-service'; import {expect, test} from '@/helpers/playwright'; async function waitForMemberStatus(request: APIRequestContext, email: string, status: string) { @@ -84,6 +85,9 @@ test.describe('Portal - Stripe Subscription Lifecycle via Webhooks', () => { }); test('subscription-deleted webhook - shows free membership in portal', async ({page, stripe}) => { + const settingsService = new SettingsService(page.request); + await settingsService.setCommentsEnabled('off'); + const email = `portal-free-${Date.now()}@example.com`; const {subscription} = await stripe!.createPaidMemberViaWebhooks({email, name: 'Portal Free Member'}); diff --git a/ghost/admin/package.json b/ghost/admin/package.json index 307ac3fd96b..7d115058ec0 100644 --- a/ghost/admin/package.json +++ b/ghost/admin/package.json @@ -1,6 +1,6 @@ { "name": "ghost-admin", - "version": "6.36.1-rc.0", + "version": "6.37.0-rc.0", "description": "Ember.js admin client for Ghost", "author": "Ghost Foundation", "homepage": "http://ghost.org", diff --git a/ghost/core/core/frontend/public/admin-auth/admin-auth.min.js b/ghost/core/core/frontend/public/admin-auth/admin-auth.min.js index 60efe274b4c..7a291eca907 100644 --- a/ghost/core/core/frontend/public/admin-auth/admin-auth.min.js +++ b/ghost/core/core/frontend/public/admin-auth/admin-auth.min.js @@ -1 +1 @@ -"use strict";const adminUrl=window.location.href.replace("auth-frame/","")+"api/admin",siteOrigin="{{SITE_ORIGIN}}";function id(e){if(typeof e!="string"||!/^[a-f0-9]{24}$/i.test(e))throw new Error("Invalid identifier");return e}function qs(e){const t=new URLSearchParams(e).toString();return t?"?"+t:""}function setCommentStatus(e,t){const s=id(e);return fetch(`${adminUrl}/comments/${s}/`,{method:"PUT",body:JSON.stringify({comments:[{id:s,status:t}]}),headers:{"Content-Type":"application/json"}})}const actions={browseComments:e=>fetch(`${adminUrl}/comments/post/${id(e.postId)}/${qs(e.params)}`),getReplies:e=>fetch(`${adminUrl}/comments/${id(e.commentId)}/replies/${qs(e.params)}`),readComment:e=>fetch(`${adminUrl}/comments/${id(e.commentId)}/${qs(e.params)}`),getUser:()=>fetch(`${adminUrl}/users/me/?include=roles`),hideComment:e=>setCommentStatus(e.id,"hidden"),showComment:e=>setCommentStatus(e.id,"published")};window.addEventListener("message",async function(e){if(e.origin!==siteOrigin){console.warn("Ignored message to admin auth iframe because of mismatch in origin","expected",siteOrigin,"got",e.origin,"with data",e.data);return}let t;try{t=JSON.parse(e.data)}catch(n){console.error("Admin auth iframe failed to parse message from site origin:",e.data,n);return}function s(n,o){e.source.postMessage(JSON.stringify({uid:t.uid,error:n?n.message:null,result:o}),siteOrigin)}const i=actions[t.action];if(i)try{const o=await(await i(t)).json();s(null,o)}catch(n){s(n,null)}}); +"use strict";const adminUrl=window.location.href.replace("auth-frame/","")+"api/admin",siteOrigin="{{SITE_ORIGIN}}";function id(e){if(typeof e!="string"||!/^[a-f0-9]{24}$/i.test(e))throw new Error("Invalid identifier");return e}function qs(e){const t=new URLSearchParams(e).toString();return t?"?"+t:""}function setCommentStatus(e,t){const n=id(e);return fetch(`${adminUrl}/comments/${n}/`,{method:"PUT",body:JSON.stringify({comments:[{id:n,status:t}]}),headers:{"Content-Type":"application/json"}})}function setCommentPinned(e,t){const n=id(e);return fetch(`${adminUrl}/comments/${n}/`,{method:"PUT",body:JSON.stringify({comments:[{id:n,pinned:t}]}),headers:{"Content-Type":"application/json"}})}const actions={browseComments:e=>fetch(`${adminUrl}/comments/post/${id(e.postId)}/${qs(e.params)}`),getReplies:e=>fetch(`${adminUrl}/comments/${id(e.commentId)}/replies/${qs(e.params)}`),readComment:e=>fetch(`${adminUrl}/comments/${id(e.commentId)}/${qs(e.params)}`),getUser:()=>fetch(`${adminUrl}/users/me/?include=roles`),hideComment:e=>setCommentStatus(e.id,"hidden"),showComment:e=>setCommentStatus(e.id,"published"),pinComment:e=>setCommentPinned(e.id,!0),unpinComment:e=>setCommentPinned(e.id,!1)};window.addEventListener("message",async function(e){if(e.origin!==siteOrigin){console.warn("Ignored message to admin auth iframe because of mismatch in origin","expected",siteOrigin,"got",e.origin,"with data",e.data);return}let t;try{t=JSON.parse(e.data)}catch(o){console.error("Admin auth iframe failed to parse message from site origin:",e.data,o);return}function n(o,s){e.source.postMessage(JSON.stringify({uid:t.uid,error:o?o.message:null,result:s}),siteOrigin)}const i=actions[t.action];if(i)try{const s=await(await i(t)).json();n(null,s)}catch(o){n(o,null)}}); diff --git a/ghost/core/core/frontend/src/admin-auth/message-handler.js b/ghost/core/core/frontend/src/admin-auth/message-handler.js index 76d1baeb63e..6a20b588f20 100644 --- a/ghost/core/core/frontend/src/admin-auth/message-handler.js +++ b/ghost/core/core/frontend/src/admin-auth/message-handler.js @@ -28,13 +28,28 @@ function setCommentStatus(commentId, status) { }); } +function setCommentPinned(commentId, pinned) { + const safeId = id(commentId); + return fetch(`${adminUrl}/comments/${safeId}/`, { + method: 'PUT', + body: JSON.stringify({ + comments: [{id: safeId, pinned}] + }), + headers: { + 'Content-Type': 'application/json' + } + }); +} + const actions = { browseComments: d => fetch(`${adminUrl}/comments/post/${id(d.postId)}/${qs(d.params)}`), getReplies: d => fetch(`${adminUrl}/comments/${id(d.commentId)}/replies/${qs(d.params)}`), readComment: d => fetch(`${adminUrl}/comments/${id(d.commentId)}/${qs(d.params)}`), getUser: () => fetch(`${adminUrl}/users/me/?include=roles`), hideComment: d => setCommentStatus(d.id, 'hidden'), - showComment: d => setCommentStatus(d.id, 'published') + showComment: d => setCommentStatus(d.id, 'published'), + pinComment: d => setCommentPinned(d.id, true), + unpinComment: d => setCommentPinned(d.id, false) }; window.addEventListener('message', async function (event) { diff --git a/ghost/core/core/server/api/endpoints/comment-replies.js b/ghost/core/core/server/api/endpoints/comment-replies.js index 5b1f7fffc6d..55a5d39e447 100644 --- a/ghost/core/core/server/api/endpoints/comment-replies.js +++ b/ghost/core/core/server/api/endpoints/comment-replies.js @@ -39,6 +39,7 @@ const controller = { }, options: [ 'include', + 'fields', 'impersonate_member_uuid' ], data: [ diff --git a/ghost/core/core/server/api/endpoints/comments-members.js b/ghost/core/core/server/api/endpoints/comments-members.js index d7501987f31..6292ac4a32f 100644 --- a/ghost/core/core/server/api/endpoints/comments-members.js +++ b/ghost/core/core/server/api/endpoints/comments-members.js @@ -60,7 +60,8 @@ const controller = { cacheInvalidate: false }, options: [ - 'include' + 'include', + 'fields' ], data: [ 'id', diff --git a/ghost/core/core/server/api/endpoints/comments.js b/ghost/core/core/server/api/endpoints/comments.js index a999140de52..53b02551707 100644 --- a/ghost/core/core/server/api/endpoints/comments.js +++ b/ghost/core/core/server/api/endpoints/comments.js @@ -1,4 +1,3 @@ -const models = require('../../models'); const commentsService = require('../../services/comments'); const errors = require('@tryghost/errors'); function handleCacheHeaders(model, frame) { @@ -60,14 +59,7 @@ const controller = { }, permissions: true, async query(frame) { - const result = await models.Comment.edit({ - id: frame.data.comments[0].id, - status: frame.data.comments[0].status - }, frame.options); - - handleCacheHeaders(result, frame); - - return result; + return await commentsService.controller.adminEdit(frame); } }, browse: { diff --git a/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js b/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js index c0e2f6db577..e2729bb674f 100644 --- a/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +++ b/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js @@ -54,6 +54,16 @@ const countFieldsAdmin = [ 'reports' ]; +function getRequestedFields(frame) { + const fields = frame?.original?.query?.fields; + + if (!fields || typeof fields !== 'string') { + return null; + } + + return new Set(fields.split(',').map(field => field.trim()).filter(Boolean)); +} + const commentMapper = (model, frame) => { const jsonModel = model.toJSON ? model.toJSON(frame.options) : model; @@ -78,7 +88,20 @@ const commentMapper = (model, frame) => { jsonModel.in_reply_to_id = null; } - const response = _.pick(jsonModel, commentFields); + const fields = getRequestedFields(frame); + const includesHtml = !fields || fields.has('html'); + const response = _.pick(jsonModel, fields ? commentFields.filter(field => fields.has(field)) : commentFields); + + if (!fields || fields.has('pinned')) { + if (Object.prototype.hasOwnProperty.call(jsonModel, 'pinned')) { + response.pinned = Boolean(jsonModel.pinned); + } else { + const canShowPinned = !jsonModel.parent_id && Boolean(jsonModel.pinned_at); + response.pinned = isPublicRequest + ? canShowPinned && jsonModel.status === 'published' + : canShowPinned; + } + } if (jsonModel.member) { response.member = _.pick(jsonModel.member, isPublicRequest ? memberFields : memberFieldsAdmin); @@ -115,14 +138,12 @@ const commentMapper = (model, frame) => { response.count = _.pick(jsonModel.count, isPublicRequest ? countFields : countFieldsAdmin); } - if (isPublicRequest) { - if (jsonModel.status !== 'published') { - response.html = null; - } + if (includesHtml && isPublicRequest && jsonModel.status !== 'published') { + response.html = null; } // Deleted comments should never expose their content - if (jsonModel.status === 'deleted') { + if (includesHtml && jsonModel.status === 'deleted') { response.html = null; } diff --git a/ghost/core/core/server/data/migrations/versions/6.37/2026-05-03-00-46-46-add-pinned-at-to-comments.js b/ghost/core/core/server/data/migrations/versions/6.37/2026-05-03-00-46-46-add-pinned-at-to-comments.js new file mode 100644 index 00000000000..5ce635f435d --- /dev/null +++ b/ghost/core/core/server/data/migrations/versions/6.37/2026-05-03-00-46-46-add-pinned-at-to-comments.js @@ -0,0 +1,35 @@ +const logging = require('@tryghost/logging'); +const {createNonTransactionalMigration} = require('../../utils'); +const {addColumn, addIndex, dropColumn, dropIndex} = require('../../../schema/commands'); + +const columnDefinition = { + type: 'dateTime', + nullable: true +}; + +module.exports = createNonTransactionalMigration( + async function up(knex) { + const hasPinnedAtColumn = await knex.schema.hasColumn('comments', 'pinned_at'); + if (hasPinnedAtColumn) { + logging.warn('Adding comments.pinned_at column - skipping as column already exists'); + } else { + logging.info('Adding comments.pinned_at column'); + await addColumn('comments', 'pinned_at', knex, columnDefinition); + } + + await addIndex('comments', ['post_id', 'parent_id', 'pinned_at'], knex); + }, + async function down(knex) { + await addIndex('comments', ['post_id'], knex); + await dropIndex('comments', ['post_id', 'parent_id', 'pinned_at'], knex); + + const hasPinnedAtColumn = await knex.schema.hasColumn('comments', 'pinned_at'); + if (!hasPinnedAtColumn) { + logging.warn('Removing comments.pinned_at column - skipping as column does not exist'); + return; + } + + logging.info('Removing comments.pinned_at column'); + await dropColumn('comments', 'pinned_at', knex, columnDefinition); + } +); diff --git a/ghost/core/core/server/data/schema/default-settings/default-settings.json b/ghost/core/core/server/data/schema/default-settings/default-settings.json index 8b101f32da7..6f1206ba03a 100644 --- a/ghost/core/core/server/data/schema/default-settings/default-settings.json +++ b/ghost/core/core/server/data/schema/default-settings/default-settings.json @@ -599,7 +599,7 @@ "comments": { "comments_enabled": { "type": "string", - "defaultValue": "off", + "defaultValue": "all", "validations": { "isEmpty": false, "isIn": [[ diff --git a/ghost/core/core/server/data/schema/schema.js b/ghost/core/core/server/data/schema/schema.js index d0c6dece6b5..a43abe27fca 100644 --- a/ghost/core/core/server/data/schema/schema.js +++ b/ghost/core/core/server/data/schema/schema.js @@ -982,8 +982,12 @@ module.exports = { status: {type: 'string', maxlength: 50, nullable: false, defaultTo: 'published', validations: {isIn: [['published', 'hidden', 'deleted']]}}, html: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true}, edited_at: {type: 'dateTime', nullable: true}, + pinned_at: {type: 'dateTime', nullable: true}, created_at: {type: 'dateTime', nullable: false}, - updated_at: {type: 'dateTime', nullable: false} + updated_at: {type: 'dateTime', nullable: false}, + '@@INDEXES@@': [ + ['post_id', 'parent_id', 'pinned_at'] + ] }, comment_likes: { id: {type: 'string', maxlength: 24, nullable: false, primary: true}, diff --git a/ghost/core/core/server/models/comment.js b/ghost/core/core/server/models/comment.js index ddfa0105d39..6f933649e19 100644 --- a/ghost/core/core/server/models/comment.js +++ b/ghost/core/core/server/models/comment.js @@ -81,6 +81,14 @@ const Comment = ghostBookshelf.Model.extend({ const subquery = '(SELECT COUNT(*) FROM comment_reports WHERE comment_reports.comment_id = comments.id)'; qb.whereRaw(`${subquery} ${options.reportCount.op} ?`, [options.reportCount.value]); } + + if (options.pinnedFirst) { + if (options.isAdmin) { + qb.orderBy('comments.pinned_at', 'DESC'); + } else { + qb.orderByRaw('CASE WHEN comments.status = ? THEN comments.pinned_at END DESC', ['published']); + } + } }); }, @@ -263,7 +271,7 @@ const Comment = ghostBookshelf.Model.extend({ // instead of the previous N+1 per-model model.load() loop if (result.data.length > 0 && relationsToLoadInBatch.length > 0) { const collection = ghostBookshelf.Collection.forge(result.data, {model: this}); - await collection.load(relationsToLoadInBatch, _.omit(options, 'withRelated')); + await collection.load(relationsToLoadInBatch, _.omit(options, 'withRelated', 'columns', 'selectRaw')); } return result; @@ -345,6 +353,8 @@ const Comment = ghostBookshelf.Model.extend({ options.push('isAdmin'); options.push('browseAll'); options.push('reportCount'); + options.push('pinnedFirst'); + options.push('selectRaw'); return options; } diff --git a/ghost/core/core/server/services/comments/comments-controller.js b/ghost/core/core/server/services/comments/comments-controller.js index bf9a2135402..1d701d87614 100644 --- a/ghost/core/core/server/services/comments/comments-controller.js +++ b/ghost/core/core/server/services/comments/comments-controller.js @@ -169,6 +169,35 @@ module.exports = class CommentsController { }); } + async adminEdit(frame) { + const data = frame.data.comments[0]; + const updates = {}; + if (Object.prototype.hasOwnProperty.call(data, 'status')) { + updates.status = data.status; + } + if (Object.prototype.hasOwnProperty.call(data, 'pinned')) { + updates.pinned = data.pinned; + } + + const result = await this.service.moderateComment( + frame.options.id, + updates, + frame.options + ); + + if (result) { + const postId = result.get('post_id'); + const parentId = result.get('parent_id'); + const pathsToInvalidate = [ + postId ? `/api/members/comments/post/${postId}/` : null, + parentId ? `/api/members/comments/${parentId}/replies/` : null + ].filter(path => path !== null); + frame.setHeader('X-Cache-Invalidate', pathsToInvalidate.join(', ')); + } + + return result; + } + /** * @param {Frame} frame */ diff --git a/ghost/core/core/server/services/comments/comments-service.js b/ghost/core/core/server/services/comments/comments-service.js index fd46cee704c..b55a8d0a18a 100644 --- a/ghost/core/core/server/services/comments/comments-service.js +++ b/ghost/core/core/server/services/comments/comments-service.js @@ -12,9 +12,26 @@ const messages = { replyToReply: 'Can not reply to a reply', commentsNotEnabled: 'Comments are not enabled for this site.', cannotCommentOnPost: 'You do not have permission to comment on this post.', - cannotEditComment: 'You do not have permission to edit comments' + cannotEditComment: 'You do not have permission to edit comments', + cannotPinReply: 'Replies cannot be pinned', + cannotPinDeletedComment: 'Deleted comments cannot be pinned', + invalidPinnedValue: 'Pinned must be a boolean value' }; +function withPinnedSelect(options = {}) { + if (!options.columns?.includes('pinned')) { + return options; + } + + const statusClause = options.isAdmin ? '' : ' AND comments.status = \'published\''; + const pinnedSelect = `CASE WHEN comments.parent_id IS NULL AND comments.pinned_at IS NOT NULL${statusClause} THEN 1 ELSE 0 END AS pinned`; + + return { + ...options, + selectRaw: [options.selectRaw, pinnedSelect].filter(Boolean).join(', ') + }; +} + class CommentsService { constructor({config, logging, models, mailer, settingsCache, settingsHelpers, urlService, urlUtils, contentGating, labs}) { /** @private */ @@ -173,7 +190,7 @@ class CommentsService { */ async getComments(options) { this.checkEnabled(); - const page = await this.models.Comment.findPage({...options, parentId: null}); + const page = await this.models.Comment.findPage(withPinnedSelect({...options, parentId: null, pinnedFirst: true})); return page; } @@ -216,18 +233,63 @@ class CommentsService { async getAdminComments(options) { this.checkEnabled(); - const page = await this.models.Comment.findPage({...options, parentId: null, isAdmin: true}); + const page = await this.models.Comment.findPage(withPinnedSelect({...options, parentId: null, isAdmin: true, pinnedFirst: true})); return page; } + async moderateComment(id, data, options) { + const editData = {}; + + if (Object.prototype.hasOwnProperty.call(data, 'status')) { + editData.status = data.status; + + if (data.status === 'deleted') { + editData.pinned_at = null; + } + } + + if (Object.prototype.hasOwnProperty.call(data, 'pinned')) { + if (typeof data.pinned !== 'boolean') { + throw new errors.BadRequestError({ + message: tpl(messages.invalidPinnedValue) + }); + } + + let existingComment; + if (data.pinned) { + existingComment = await this.models.Comment.findOne({id}, {require: true}); + + if (existingComment.get('parent_id') !== null) { + throw new errors.BadRequestError({ + message: tpl(messages.cannotPinReply) + }); + } + + if (existingComment.get('status') === 'deleted' || data.status === 'deleted') { + throw new errors.BadRequestError({ + message: tpl(messages.cannotPinDeletedComment) + }); + } + } + + editData.pinned_at = data.pinned ? existingComment.get('pinned_at') || new Date() : null; + } + + return await this.models.Comment.edit(editData, { + id, + require: true, + ...options + }); + } + /** * @param {string} id - The ID of the Comment to get replies from * @param {any} options */ async getReplies(id, options) { this.checkEnabled(); - const page = await this.models.Comment.findPage({...options, parentId: id}); + const page = await this.models.Comment.findPage(withPinnedSelect({...options, parentId: id})); return page; } @@ -288,7 +350,7 @@ class CommentsService { */ async getCommentByID(id, options) { this.checkEnabled(); - const model = await this.models.Comment.findOne({id}, options); + const model = await this.models.Comment.findOne({id}, withPinnedSelect(options)); if (!model) { throw new errors.NotFoundError({ @@ -461,7 +523,8 @@ class CommentsService { } const model = await this.models.Comment.edit({ - status: 'deleted' + status: 'deleted', + pinned_at: null }, { id, require: true, diff --git a/ghost/core/package.json b/ghost/core/package.json index bff1a0e0b13..026a5e021f5 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -1,6 +1,6 @@ { "name": "ghost", - "version": "6.36.1-rc.0", + "version": "6.37.0-rc.0", "description": "The professional publishing platform", "author": "Ghost Foundation", "homepage": "https://ghost.org", diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/activity-feed.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/activity-feed.test.js.snap index c2e140b430e..03056f0c5cd 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/activity-feed.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/activity-feed.test.js.snap @@ -22699,7 +22699,7 @@ exports[`Activity Feed API Can filter events by post id 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "18676", + "content-length": "18721", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -22871,7 +22871,7 @@ exports[`Activity Feed API Filter splitting Can use NQL OR for type only 2: [hea Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "5734", + "content-length": "5779", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -23987,7 +23987,7 @@ exports[`Activity Feed API Returns comments in activity feed 2: [headers] 1`] = Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "1840", + "content-length": "1885", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/comments.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/comments.test.js.snap index e66d668d1e7..dade1a61d89 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/comments.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/comments.test.js.snap @@ -67,6 +67,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -124,6 +125,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "This is my custom excerpt!", "feature_image": "https://example.com/super_photo.jpg", @@ -162,6 +164,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -219,6 +222,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -276,6 +280,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -333,6 +338,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -373,6 +379,7 @@ Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac tu "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -430,6 +437,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -495,9 +503,11 @@ Object { "in_reply_to_snippet": null, "member": null, "parent_id": Nullable, + "pinned": false, "status": "deleted", }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -555,6 +565,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -612,6 +623,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -652,6 +664,7 @@ Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac tu "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -717,9 +730,11 @@ Object { "in_reply_to_snippet": null, "member": null, "parent_id": Nullable, + "pinned": false, "status": "published", }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -760,6 +775,7 @@ Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac tu "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -817,6 +833,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -874,6 +891,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -914,6 +932,7 @@ Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac tu "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "post": Object { "excerpt": "HTML Ipsum Presents @@ -1202,6 +1221,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -1214,7 +1234,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "446", + "content-length": "461", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1246,6 +1266,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "hidden", }, @@ -1258,7 +1279,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "421", + "content-length": "436", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1290,6 +1311,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -1302,7 +1324,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "462", + "content-length": "477", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1334,6 +1356,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "hidden", }, @@ -1346,7 +1369,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "439", + "content-length": "454", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1422,6 +1445,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [ Object { "count": Object { @@ -1451,6 +1475,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "pinned": Any, "status": Any, }, Object { @@ -1481,6 +1506,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "pinned": Any, "status": Any, }, Object { @@ -1511,6 +1537,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "pinned": Any, "status": Any, }, ], @@ -1568,6 +1595,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [ Object { "count": Object { @@ -1597,6 +1625,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "pinned": Any, "status": "hidden", }, Object { @@ -1627,6 +1656,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "pinned": Any, "status": "published", }, ], @@ -1694,6 +1724,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [ Object { "count": Object { @@ -1723,6 +1754,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "pinned": Any, "status": "hidden", }, Object { @@ -1753,6 +1785,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "pinned": Any, "status": "published", }, ], @@ -1804,6 +1837,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [ Object { "count": Object { @@ -1833,6 +1867,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "pinned": Any, "status": "published", }, ], @@ -1884,6 +1919,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap index c97cdbf9d78..c05ab4e916c 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap @@ -770,6 +770,20 @@ table.body h2 span { + + + + + + +
+ + \\"Comment\\" +

Comment

+
+
+ + @@ -960,6 +974,26 @@ Another email card with a similar replacement, Jamie + + +Comment + + +[http://127.0.0.1:2369/p/d52c42ae-2755-455c-80ec-70b2ec55c904/#ghost-comments-root] + + + + + + + + + + + + + + Ghost © 2025 – Unsubscribe [http://127.0.0.1:2369/unsubscribe/?uuid=example-uuid&key=803e513e2d8b88e759d8f433779659af335d7308b4cbac809600d563f6b49a76&newsletter=requested-newsletter-uuid] @@ -988,7 +1022,7 @@ exports[`Email Preview API Read can read post email preview with email card and Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "35639", + "content-length": "38065", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -1677,6 +1711,20 @@ table.body h2 span { + + + + + + +
+ + \\"Comment\\" +

Comment

+
+
+ + @@ -1884,6 +1932,26 @@ Header Level 3 + + +Comment + + +[http://127.0.0.1:2369/html-ipsum/#ghost-comments-root] + + + + + + + + + + + + + + Ghost © 2025 – Unsubscribe [http://127.0.0.1:2369/unsubscribe/?uuid=example-uuid&key=803e513e2d8b88e759d8f433779659af335d7308b4cbac809600d563f6b49a76&newsletter=requested-newsletter-uuid] @@ -1912,7 +1980,7 @@ exports[`Email Preview API Read can read post email preview with fields 4: [head Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "40073", + "content-length": "42443", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -2627,6 +2695,20 @@ table.body h2 span { + + + + + + +
+ + \\"Comment\\" +

Comment

+
+
+ + @@ -2811,6 +2893,26 @@ Testing links [https://ghost.org/] in email excerpt and apostrophes ' + + +Comment + + +[http://127.0.0.1:2369/p/d52c42ae-2755-455c-80ec-70b2ec55c904/#ghost-comments-root] + + + + + + + + + + + + + + Ghost © 2025 – Unsubscribe [http://127.0.0.1:2369/unsubscribe/?uuid=example-uuid&key=803e513e2d8b88e759d8f433779659af335d7308b4cbac809600d563f6b49a76&newsletter=requested-newsletter-uuid] @@ -2852,7 +2954,7 @@ exports[`Email Preview API Read has custom content transformations for email com Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "35351", + "content-length": "37777", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -3903,6 +4005,20 @@ table.body h2 span { + + + + + + +
+ + \\"Comment\\" +

Comment

+
+
+ + @@ -4094,6 +4210,26 @@ Testing links [https://ghost.org/] in email excerpt and apostrophes ' + + +Comment + + +[http://127.0.0.1:2369/p/d52c42ae-2755-455c-80ec-70b2ec55c904/#ghost-comments-root] + + + + + + + + + + + + + + Ghost © 2025 – Unsubscribe [http://127.0.0.1:2369/unsubscribe/?uuid=example-uuid&key=803e513e2d8b88e759d8f433779659af335d7308b4cbac809600d563f6b49a76&newsletter=requested-newsletter-uuid] @@ -4135,7 +4271,7 @@ exports[`Email Preview API Read uses the newsletter provided through ?newsletter Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "36228", + "content-length": "38654", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -5212,6 +5348,20 @@ table.body h2 span { + + + + + + +
+ + \\"Comment\\" +

Comment

+
+
+ + @@ -5403,6 +5553,26 @@ Testing links [https://ghost.org/] in email excerpt and apostrophes ' + + +Comment + + +[http://127.0.0.1:2369/p/d52c42ae-2755-455c-80ec-70b2ec55c904/#ghost-comments-root] + + + + + + + + + + + + + + Ghost © 2025 – Unsubscribe [http://127.0.0.1:2369/unsubscribe/?uuid=example-uuid&key=803e513e2d8b88e759d8f433779659af335d7308b4cbac809600d563f6b49a76&newsletter=requested-newsletter-uuid] @@ -5444,7 +5614,7 @@ exports[`Email Preview API Read uses the posts newsletter by default 4: [headers Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "36228", + "content-length": "38654", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snap index 869224ec2f8..8d59eac565b 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snap @@ -314,7 +314,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", @@ -1205,7 +1205,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", @@ -1651,7 +1651,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", @@ -2142,7 +2142,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", @@ -2587,7 +2587,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", @@ -3515,7 +3515,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", @@ -3961,7 +3961,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", @@ -4407,7 +4407,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", @@ -4857,7 +4857,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", @@ -5302,7 +5302,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", @@ -5752,7 +5752,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", @@ -6586,7 +6586,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", @@ -7032,7 +7032,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", @@ -7479,7 +7479,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", @@ -7926,7 +7926,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", @@ -8435,7 +8435,7 @@ Object { }, Object { "key": "comments_enabled", - "value": "off", + "value": "all", }, Object { "key": "outbound_link_tagging", diff --git a/ghost/core/test/e2e-api/admin/comments.test.js b/ghost/core/test/e2e-api/admin/comments.test.js index 5e7f0cc505b..855c1be5a04 100644 --- a/ghost/core/test/e2e-api/admin/comments.test.js +++ b/ghost/core/test/e2e-api/admin/comments.test.js @@ -23,7 +23,8 @@ const membersCommentMatcher = { count: { likes: anyNumber }, - liked: anyBoolean + liked: anyBoolean, + pinned: anyBoolean }; let postId; @@ -40,6 +41,7 @@ const dbFns = { * @property {string} [html='This is a comment'] * @property {string} [status='published'] * @property {Date} [created_at] + * @property {Date} [pinned_at] */ /** * @typedef {Object} AddCommentReplyData @@ -65,6 +67,7 @@ const dbFns = { parent_id: data.parent_id, html: data.html || '

This is a comment

', created_at: data.created_at, + pinned_at: data.pinned_at, status: data.status || 'published' }); }, @@ -182,6 +185,134 @@ describe(`Admin Comments API`, function () { }); }); + describe('Pin', function () { + it('Can pin and unpin top-level comments', async function () { + const commentToPin = await dbFns.addComment({member_id: fixtureManager.get('members', 0).id}); + + const pinRes = await adminApi.put(`comments/${commentToPin.id}/`).body({ + comments: [{ + id: commentToPin.id, + pinned: true + }] + }); + + assert.equal(pinRes.headers['x-cache-invalidate'], `/api/members/comments/post/${postId}/`); + assert.equal(pinRes.body.comments[0].pinned, true); + + const pinnedComment = await models.Comment.findOne({id: commentToPin.id}); + assert.ok(pinnedComment.get('pinned_at')); + const firstPinnedAt = pinnedComment.get('pinned_at'); + + await adminApi.put(`comments/${commentToPin.id}/`).body({ + comments: [{ + id: commentToPin.id, + pinned: true + }] + }); + + const repinnedComment = await models.Comment.findOne({id: commentToPin.id}); + assert.equal(repinnedComment.get('pinned_at').getTime(), firstPinnedAt.getTime()); + + const unpinRes = await adminApi.put(`comments/${commentToPin.id}/`).body({ + comments: [{ + id: commentToPin.id, + pinned: false + }] + }); + + assert.equal(unpinRes.headers['x-cache-invalidate'], `/api/members/comments/post/${postId}/`); + assert.equal(unpinRes.body.comments[0].pinned, false); + + const unpinnedComment = await models.Comment.findOne({id: commentToPin.id}); + assert.equal(unpinnedComment.get('pinned_at'), null); + }); + + it('Cannot pin replies', async function () { + const {replies: [reply]} = await dbFns.addCommentWithReplies({ + member_id: fixtureManager.get('members', 0).id, + replies: [{ + member_id: fixtureManager.get('members', 1).id + }] + }); + + await adminApi.put(`comments/${reply.id}/`).body({ + comments: [{ + id: reply.id, + pinned: true + }] + }).expectStatus(400); + }); + + it('Can moderate replies when pinned is false', async function () { + const {replies: [reply]} = await dbFns.addCommentWithReplies({ + member_id: fixtureManager.get('members', 0).id, + replies: [{ + member_id: fixtureManager.get('members', 1).id + }] + }); + + await adminApi.put(`comments/${reply.id}/`).body({ + comments: [{ + id: reply.id, + status: 'hidden', + pinned: false + }] + }).expectStatus(200); + + const hiddenReply = await models.Comment.findOne({id: reply.id}); + assert.equal(hiddenReply.get('status'), 'hidden'); + assert.equal(hiddenReply.get('pinned_at'), null); + }); + + it('Cannot pin deleted comments', async function () { + const deletedComment = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + status: 'deleted' + }); + + await adminApi.put(`comments/${deletedComment.id}/`).body({ + comments: [{ + id: deletedComment.id, + pinned: true + }] + }).expectStatus(400); + }); + + it('Cannot pin or unpin with non-boolean values', async function () { + const commentToPin = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id + }); + + await adminApi.put(`comments/${commentToPin.id}/`).body({ + comments: [{ + id: commentToPin.id, + pinned: 'false' + }] + }).expectStatus(400); + + const unchangedComment = await models.Comment.findOne({id: commentToPin.id}); + assert.equal(unchangedComment.get('pinned_at'), null); + }); + + it('Clears pinned state when deleting a pinned comment', async function () { + const pinnedComment = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + pinned_at: new Date('2025-01-01T00:00:00.000Z') + }); + + await adminApi.put(`comments/${pinnedComment.id}/`).body({ + comments: [{ + id: pinnedComment.id, + status: 'deleted' + }] + }).expectStatus(200); + + const deletedComment = await models.Comment.findOne({id: pinnedComment.id}); + assert.equal(deletedComment.get('status'), 'deleted'); + assert.equal(deletedComment.get('pinned_at'), null); + }); + }); + describe('browse by post', function () { it('returns comments', async function () { await dbFns.addComment({ @@ -199,6 +330,57 @@ describe(`Admin Comments API`, function () { assert.equal(res.body.comments.length, 2); }); + it('orders pinned comments first for front-end staff browsing', async function () { + const unpinnedOlder = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: 'Older unpinned', + created_at: new Date('2023-01-01T00:00:00.000Z') + }); + const pinnedOlder = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: 'Older pinned', + created_at: new Date('2023-02-01T00:00:00.000Z'), + pinned_at: new Date('2025-01-01T00:00:00.000Z') + }); + const pinnedNewer = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: 'Newer pinned', + created_at: new Date('2023-03-01T00:00:00.000Z'), + pinned_at: new Date('2025-02-01T00:00:00.000Z') + }); + const unpinnedNewer = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: 'Newer unpinned', + created_at: new Date('2023-04-01T00:00:00.000Z') + }); + + const res = await adminApi.get('/comments/post/' + postId + '/?order=' + encodeURIComponent('created_at asc')).expectStatus(200); + + assert.deepEqual(res.body.comments.map(comment => comment.id), [ + pinnedNewer.id, + pinnedOlder.id, + unpinnedOlder.id, + unpinnedNewer.id + ]); + }); + + it('preserves pinned state when fields are requested', async function () { + const pinnedComment = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: 'Pinned with fields', + pinned_at: new Date('2025-01-01T00:00:00.000Z') + }); + + const res = await adminApi.get('/comments/post/' + postId + '/?fields=id,pinned').expectStatus(200); + const [comment] = res.body.comments; + + assert.equal(comment.id, pinnedComment.id); + assert.equal(comment.pinned, true); + assert.equal(Object.hasOwn(comment, 'parent_id'), false); + assert.equal(Object.hasOwn(comment, 'status'), false); + assert.equal(Object.hasOwn(comment, 'pinned_at'), false); + }); + it('returns hidden comments (with html)', async function () { await dbFns.addComment({ member_id: fixtureManager.get('members', 0).id, @@ -391,7 +573,8 @@ describe(`Admin Comments API`, function () { }, count: { likes: anyNumber - } + }, + pinned: anyBoolean }; await adminApi.get(`/comments/${parent.get('id')}/`) @@ -507,7 +690,8 @@ describe(`Admin Comments API`, function () { member: { id: anyObjectId, uuid: anyUuid - } + }, + pinned: anyBoolean }; await adminApi.get('/comments/post/' + postId + '/') @@ -547,7 +731,8 @@ describe(`Admin Comments API`, function () { member: { id: anyObjectId, uuid: anyUuid - } + }, + pinned: anyBoolean }; // Admin sees hidden + published, but not deleted @@ -588,7 +773,8 @@ describe(`Admin Comments API`, function () { member: { id: anyObjectId, uuid: anyUuid - } + }, + pinned: anyBoolean }; // Admin sees hidden + published (2), but not deleted @@ -1315,7 +1501,8 @@ describe(`Admin Comments API`, function () { replies: anyNumber, direct_replies: anyNumber, reports: anyNumber - } + }, + pinned: anyBoolean }; // Matcher for child comments (no count.replies — alias is root-only) const childCommentMatcher = { @@ -1502,6 +1689,46 @@ describe(`Admin Comments API`, function () { }); }); + it('Keeps requested ordering for pinned comments', async function () { + const unpinnedOlder = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: '

Older unpinned

', + created_at: new Date('2023-01-01T00:00:00.000Z') + }); + const pinnedOlder = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: '

Older pinned

', + created_at: new Date('2023-02-01T00:00:00.000Z'), + pinned_at: new Date('2025-01-01T00:00:00.000Z') + }); + const pinnedNewer = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: '

Newer pinned

', + created_at: new Date('2023-03-01T00:00:00.000Z'), + pinned_at: new Date('2025-02-01T00:00:00.000Z') + }); + const unpinnedNewer = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: '

Newer unpinned

', + created_at: new Date('2023-04-01T00:00:00.000Z') + }); + + const res = await adminApi.get('/comments/?order=' + encodeURIComponent('created_at asc')).expectStatus(200); + + assert.deepEqual(res.body.comments.map(comment => comment.id), [ + unpinnedOlder.id, + pinnedOlder.id, + pinnedNewer.id, + unpinnedNewer.id + ]); + assert.deepEqual(res.body.comments.map(comment => comment.pinned), [ + false, + true, + true, + false + ]); + }); + it('Can order by created_at asc', async function () { await dbFns.addComment({ member_id: fixtureManager.get('members', 0).id, diff --git a/ghost/core/test/e2e-api/content/__snapshots__/pages.test.js.snap b/ghost/core/test/e2e-api/content/__snapshots__/pages.test.js.snap index b5f8d0eb6be..bf20541369e 100644 --- a/ghost/core/test/e2e-api/content/__snapshots__/pages.test.js.snap +++ b/ghost/core/test/e2e-api/content/__snapshots__/pages.test.js.snap @@ -9,7 +9,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e9", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -49,7 +49,7 @@ exports[`Pages Content API Can request page 2: [headers] 1`] = ` Object { "access-control-allow-origin": "*", "cache-control": "public, max-age=0", - "content-length": "1119", + "content-length": "1118", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -77,7 +77,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a78", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -117,7 +117,7 @@ Tip: If you're reading any post or page on your site and you notice something yo "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a79", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -162,7 +162,7 @@ If you prefer to use a contact form, almost all of the great embedded form servi "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a7b", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -203,7 +203,7 @@ Ghost is a non-profit organization, and we give away all our intellectual proper "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a7a", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -241,7 +241,7 @@ You can integrate any products, services, ads or integrations with Ghost yoursel "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e9", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -281,7 +281,7 @@ exports[`Pages Content API Can request pages 2: [headers] 1`] = ` Object { "access-control-allow-origin": "*", "cache-control": "public, max-age=0", - "content-length": "9413", + "content-length": "9408", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -356,7 +356,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a78", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -395,7 +395,7 @@ Tip: If you're reading any post or page on your site and you notice something yo "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a79", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -439,7 +439,7 @@ If you prefer to use a contact form, almost all of the great embedded form servi "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a7b", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -479,7 +479,7 @@ Ghost is a non-profit organization, and we give away all our intellectual proper "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a7a", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -516,7 +516,7 @@ You can integrate any products, services, ads or integrations with Ghost yoursel "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e9", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, diff --git a/ghost/core/test/e2e-api/content/__snapshots__/posts.test.js.snap b/ghost/core/test/e2e-api/content/__snapshots__/posts.test.js.snap index a25dfb0fc70..0611b07fd0d 100644 --- a/ghost/core/test/e2e-api/content/__snapshots__/posts.test.js.snap +++ b/ghost/core/test/e2e-api/content/__snapshots__/posts.test.js.snap @@ -582,7 +582,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a77", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "We've crammed the most important information to help you get started with Ghost into this one post. It's your cheat-sheet to get started, and your shortcut to advanced features.", "custom_template": null, @@ -620,7 +620,7 @@ exports[`Posts Content API Can filter by published date 2: [headers] 1`] = ` Object { "access-control-allow-origin": "*", "cache-control": "public, max-age=0", - "content-length": "4619", + "content-length": "4618", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -648,7 +648,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a76", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "How to tweak a few settings in Ghost to transform your site from a generic template to a custom brand with style and personality.", "custom_template": null, @@ -698,7 +698,7 @@ exports[`Posts Content API Can filter by published date 4: [headers] 1`] = ` Object { "access-control-allow-origin": "*", "cache-control": "public, max-age=0", - "content-length": "5909", + "content-length": "5908", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -726,7 +726,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a77", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "We've crammed the most important information to help you get started with Ghost into this one post. It's your cheat-sheet to get started, and your shortcut to advanced features.", "custom_template": null, @@ -764,7 +764,7 @@ exports[`Posts Content API Can filter by published date 6: [headers] 1`] = ` Object { "access-control-allow-origin": "*", "cache-control": "public, max-age=0", - "content-length": "4620", + "content-length": "4619", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -816,7 +816,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a77", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "We've crammed the most important information to help you get started with Ghost into this one post. It's your cheat-sheet to get started, and your shortcut to advanced features.", "custom_template": null, @@ -898,7 +898,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a76", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "How to tweak a few settings in Ghost to transform your site from a generic template to a custom brand with style and personality.", "custom_template": null, @@ -992,7 +992,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a75", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "A full overview of all the features built into the Ghost editor, including powerful workflow automations to speed up your creative process.", "custom_template": null, @@ -1074,7 +1074,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a74", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "How Ghost allows you to turn anonymous readers into an audience of active subscribers, so you know what's working and what isn't.", "custom_template": null, @@ -1255,7 +1255,7 @@ Most successful subscription businesses publish a mix of free and paid posts to "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a72", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "A guide to collaborating with other staff users to publish, and some resources to help you with the next steps of growing your business", "custom_template": null, @@ -1337,7 +1337,7 @@ Most successful subscription businesses publish a mix of free and paid posts to "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a71", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "Work with all your favorite apps and tools or create your own custom integrations using the Ghost API.", "custom_template": null, @@ -1420,7 +1420,7 @@ Most successful subscription businesses publish a mix of free and paid posts to "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6f1", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -1656,7 +1656,7 @@ Snippet Docume", "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a700", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -1838,7 +1838,7 @@ Snippet Doc", "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e7", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -1931,7 +1931,7 @@ Definition listConsectetur adipisicing elit, sed do eiusmod tempor incididunt ut "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e3", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -2029,7 +2029,7 @@ mctesters "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e1", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -2113,7 +2113,7 @@ Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac tu "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6df", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "This is my custom excerpt!", "custom_template": null, @@ -2173,7 +2173,7 @@ exports[`Posts Content API Can filter posts by authors 2: [headers] 1`] = ` Object { "access-control-allow-origin": "*", "cache-control": "public, max-age=0", - "content-length": "100276", + "content-length": "100264", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -2201,7 +2201,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e7", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -2250,7 +2250,7 @@ Definition listConsectetur adipisicing elit, sed do eiusmod tempor incididunt ut "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e3", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -2346,7 +2346,7 @@ mctesters "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e1", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -2449,7 +2449,7 @@ Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac tu "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6df", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "This is my custom excerpt!", "custom_template": null, @@ -2552,7 +2552,7 @@ exports[`Posts Content API Can filter posts by tag 2: [headers] 1`] = ` Object { "access-control-allow-origin": "*", "cache-control": "public, max-age=0", - "content-length": "13983", + "content-length": "13979", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -2581,7 +2581,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a77", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "We've crammed the most important information to help you get started with Ghost into this one post. It's your cheat-sheet to get started, and your shortcut to advanced features.", "custom_template": null, @@ -2662,7 +2662,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a76", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "How to tweak a few settings in Ghost to transform your site from a generic template to a custom brand with style and personality.", "custom_template": null, @@ -2755,7 +2755,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a75", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "A full overview of all the features built into the Ghost editor, including powerful workflow automations to speed up your creative process.", "custom_template": null, @@ -2836,7 +2836,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a74", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "How Ghost allows you to turn anonymous readers into an audience of active subscribers, so you know what's working and what isn't.", "custom_template": null, @@ -3015,7 +3015,7 @@ Most successful subscription businesses publish a mix of free and paid posts to "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a72", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "A guide to collaborating with other staff users to publish, and some resources to help you with the next steps of growing your business", "custom_template": null, @@ -3096,7 +3096,7 @@ Most successful subscription businesses publish a mix of free and paid posts to "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a71", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "Work with all your favorite apps and tools or create your own custom integrations using the Ghost API.", "custom_template": null, @@ -3178,7 +3178,7 @@ Most successful subscription businesses publish a mix of free and paid posts to "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6f1", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -3413,7 +3413,7 @@ Snippet Docume", "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a700", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -3572,7 +3572,7 @@ Snippet Doc", "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e7", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -3644,7 +3644,7 @@ Definition listConsectetur adipisicing elit, sed do eiusmod tempor incididunt ut "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e3", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -3741,7 +3741,7 @@ mctesters "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e1", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -3824,7 +3824,7 @@ Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac tu "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6df", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "This is my custom excerpt!", "custom_template": null, @@ -3906,7 +3906,7 @@ exports[`Posts Content API Can include relations 2: [headers] 1`] = ` Object { "access-control-allow-origin": "*", "cache-control": "public, max-age=0", - "content-length": "113211", + "content-length": "113199", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -3924,7 +3924,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6df", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "This is my custom excerpt!", "custom_template": null, @@ -3962,7 +3962,7 @@ exports[`Posts Content API Can request a single post 2: [headers] 1`] = ` Object { "access-control-allow-origin": "*", "cache-control": "public, max-age=0", - "content-length": "2358", + "content-length": "2357", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -4059,7 +4059,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a77", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "We've crammed the most important information to help you get started with Ghost into this one post. It's your cheat-sheet to get started, and your shortcut to advanced features.", "custom_template": null, @@ -4095,7 +4095,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a76", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "How to tweak a few settings in Ghost to transform your site from a generic template to a custom brand with style and personality.", "custom_template": null, @@ -4143,7 +4143,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a75", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "A full overview of all the features built into the Ghost editor, including powerful workflow automations to speed up your creative process.", "custom_template": null, @@ -4179,7 +4179,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a74", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "How Ghost allows you to turn anonymous readers into an audience of active subscribers, so you know what's working and what isn't.", "custom_template": null, @@ -4268,7 +4268,7 @@ Most successful subscription businesses publish a mix of free and paid posts to "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a72", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "A guide to collaborating with other staff users to publish, and some resources to help you with the next steps of growing your business", "custom_template": null, @@ -4304,7 +4304,7 @@ Most successful subscription businesses publish a mix of free and paid posts to "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a71", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "Work with all your favorite apps and tools or create your own custom integrations using the Ghost API.", "custom_template": null, @@ -4341,7 +4341,7 @@ Most successful subscription businesses publish a mix of free and paid posts to "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6f1", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -4531,7 +4531,7 @@ Snippet Docume", "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a700", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -4645,7 +4645,7 @@ Snippet Doc", "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e7", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -4692,7 +4692,7 @@ Definition listConsectetur adipisicing elit, sed do eiusmod tempor incididunt ut "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e3", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -4744,7 +4744,7 @@ mctesters "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e1", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -4782,7 +4782,7 @@ Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac tu "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6df", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "This is my custom excerpt!", "custom_template": null, @@ -4820,7 +4820,7 @@ exports[`Posts Content API Can request posts 2: [headers] 1`] = ` Object { "access-control-allow-origin": "*", "cache-control": "public, max-age=0", - "content-length": "87294", + "content-length": "87282", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -4848,7 +4848,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a77", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "We've crammed the most important information to help you get started with Ghost into this one post. It's your cheat-sheet to get started, and your shortcut to advanced features.", "custom_template": null, @@ -4884,7 +4884,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a76", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "How to tweak a few settings in Ghost to transform your site from a generic template to a custom brand with style and personality.", "custom_template": null, @@ -4932,7 +4932,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a75", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "A full overview of all the features built into the Ghost editor, including powerful workflow automations to speed up your creative process.", "custom_template": null, @@ -4968,7 +4968,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a74", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "How Ghost allows you to turn anonymous readers into an audience of active subscribers, so you know what's working and what isn't.", "custom_template": null, @@ -5057,7 +5057,7 @@ Most successful subscription businesses publish a mix of free and paid posts to "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a72", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "A guide to collaborating with other staff users to publish, and some resources to help you with the next steps of growing your business", "custom_template": null, @@ -5093,7 +5093,7 @@ Most successful subscription businesses publish a mix of free and paid posts to "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a71", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "Work with all your favorite apps and tools or create your own custom integrations using the Ghost API.", "custom_template": null, @@ -5130,7 +5130,7 @@ Most successful subscription businesses publish a mix of free and paid posts to "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6f1", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -5320,7 +5320,7 @@ Snippet Docume", "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a700", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -5434,7 +5434,7 @@ Snippet Doc", "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e7", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -5481,7 +5481,7 @@ Definition listConsectetur adipisicing elit, sed do eiusmod tempor incididunt ut "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e3", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -5533,7 +5533,7 @@ mctesters "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e1", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -5571,7 +5571,7 @@ Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac tu "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6df", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "This is my custom excerpt!", "custom_template": null, @@ -5609,7 +5609,7 @@ exports[`Posts Content API Can request posts from different origin 2: [headers] Object { "access-control-allow-origin": "*", "cache-control": "public, max-age=0", - "content-length": "87294", + "content-length": "87282", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -6425,7 +6425,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a77", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "We've crammed the most important information to help you get started with Ghost into this one post. It's your cheat-sheet to get started, and your shortcut to advanced features.", "custom_template": null, @@ -6460,7 +6460,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a76", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "How to tweak a few settings in Ghost to transform your site from a generic template to a custom brand with style and personality.", "custom_template": null, @@ -6495,7 +6495,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a75", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "A full overview of all the features built into the Ghost editor, including powerful workflow automations to speed up your creative process.", "custom_template": null, @@ -6530,7 +6530,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a74", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "How Ghost allows you to turn anonymous readers into an audience of active subscribers, so you know what's working and what isn't.", "custom_template": null, @@ -6600,7 +6600,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a72", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "A guide to collaborating with other staff users to publish, and some resources to help you with the next steps of growing your business", "custom_template": null, @@ -6635,7 +6635,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "6194d3ce51e2700162531a71", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "Work with all your favorite apps and tools or create your own custom integrations using the Ghost API.", "custom_template": null, @@ -6670,7 +6670,7 @@ Object { "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6f1", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -6771,7 +6771,7 @@ Snippet Docume", "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a700", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -6852,7 +6852,7 @@ Snippet Doc", "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e7", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -6898,7 +6898,7 @@ Definition listConsectetur adipisicing elit, sed do eiusmod tempor incididunt ut "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e3", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -6942,7 +6942,7 @@ mctesters "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6e1", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": null, "custom_template": null, @@ -6979,7 +6979,7 @@ Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac tu "codeinjection_foot": null, "codeinjection_head": null, "comment_id": "618ba1ffbe2896088840a6df", - "comments": false, + "comments": true, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000\\\\\\+\\\\d\\{2\\}:\\\\d\\{2\\}/, "custom_excerpt": "This is my custom excerpt!", "custom_template": null, diff --git a/ghost/core/test/e2e-api/content/__snapshots__/settings.test.js.snap b/ghost/core/test/e2e-api/content/__snapshots__/settings.test.js.snap index 86f8c6254ca..2c4dc054a76 100644 --- a/ghost/core/test/e2e-api/content/__snapshots__/settings.test.js.snap +++ b/ghost/core/test/e2e-api/content/__snapshots__/settings.test.js.snap @@ -9,7 +9,7 @@ Object { "bluesky": null, "codeinjection_foot": null, "codeinjection_head": null, - "comments_enabled": "off", + "comments_enabled": "all", "cover_image": "https://static.ghost.org/v5.0.0/images/publication-cover.jpg", "default_email_address": "noreply@127.0.0.1", "description": "Thoughts, stories and ideas", diff --git a/ghost/core/test/e2e-api/members-comments/__snapshots__/comments.test.js.snap b/ghost/core/test/e2e-api/members-comments/__snapshots__/comments.test.js.snap index d72b5ea263e..1d137e57352 100644 --- a/ghost/core/test/e2e-api/members-comments/__snapshots__/comments.test.js.snap +++ b/ghost/core/test/e2e-api/members-comments/__snapshots__/comments.test.js.snap @@ -24,6 +24,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -36,7 +37,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "464", + "content-length": "479", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -70,6 +71,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -82,7 +84,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "455", + "content-length": "470", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -178,6 +180,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -202,6 +205,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [ Object { "count": Object { @@ -223,6 +227,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, ], @@ -247,7 +252,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "1395", + "content-length": "1440", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -279,6 +284,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [ Object { "count": Object { @@ -300,6 +306,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, ], @@ -326,6 +333,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -348,7 +356,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "1395", + "content-length": "1440", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -380,6 +388,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [ Object { "count": Object { @@ -401,6 +410,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, ], @@ -427,6 +437,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -449,7 +460,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "1395", + "content-length": "1440", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -481,6 +492,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -505,6 +517,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [ Object { "count": Object { @@ -526,6 +539,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, ], @@ -550,7 +564,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "1395", + "content-length": "1440", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -582,6 +596,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -594,7 +609,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "464", + "content-length": "479", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -628,6 +643,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -640,7 +656,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "455", + "content-length": "470", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -706,6 +722,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -718,7 +735,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "452", + "content-length": "467", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -762,6 +779,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -774,7 +792,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "463", + "content-length": "478", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -837,6 +855,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -849,7 +868,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "462", + "content-length": "477", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -955,6 +974,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -967,7 +987,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "442", + "content-length": "457", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -999,6 +1019,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -1011,7 +1032,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "455", + "content-length": "470", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -1045,6 +1066,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -1057,7 +1079,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "455", + "content-length": "470", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -1091,6 +1113,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -1103,7 +1126,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "455", + "content-length": "470", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -1137,6 +1160,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -1149,7 +1173,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "455", + "content-length": "470", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -1193,6 +1217,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, ], @@ -1214,7 +1239,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "521", + "content-length": "536", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1245,6 +1270,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, Object { @@ -1267,6 +1293,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, Object { @@ -1289,6 +1316,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, Object { @@ -1311,6 +1339,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, Object { @@ -1333,6 +1362,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, Object { @@ -1355,6 +1385,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, Object { @@ -1377,6 +1408,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, ], @@ -1398,7 +1430,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "3062", + "content-length": "3167", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1503,6 +1535,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [ Object { "count": Object { @@ -1524,6 +1557,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, Object { @@ -1546,6 +1580,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, Object { @@ -1568,6 +1603,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, Object { @@ -1590,6 +1626,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, Object { @@ -1612,6 +1649,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, ], @@ -1626,7 +1664,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "2560", + "content-length": "2650", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1658,6 +1696,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [ Object { "count": Object { @@ -1679,6 +1718,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, ], @@ -1703,7 +1743,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "932", + "content-length": "962", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1793,6 +1833,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -1815,7 +1856,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "534", + "content-length": "549", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1905,6 +1946,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -1927,7 +1969,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "534", + "content-length": "549", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1959,6 +2001,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [ Object { "count": Object { @@ -1980,6 +2023,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, ], @@ -2004,7 +2048,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "932", + "content-length": "962", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -2036,6 +2080,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [ Object { "count": Object { @@ -2057,6 +2102,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, ], @@ -2081,7 +2127,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "931", + "content-length": "961", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -2113,6 +2159,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [ Object { "count": Object { @@ -2134,6 +2181,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, Object { @@ -2156,6 +2204,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, ], @@ -2180,7 +2229,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "1456", + "content-length": "1501", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -2212,6 +2261,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -2224,7 +2274,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "508", + "content-length": "523", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -2258,6 +2308,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -2270,7 +2321,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "473", + "content-length": "488", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -2304,6 +2355,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -2316,7 +2368,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "473", + "content-length": "488", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -2350,6 +2402,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -2362,7 +2415,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "493", + "content-length": "508", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -2394,6 +2447,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -2406,7 +2460,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "493", + "content-length": "508", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -2438,6 +2492,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -2450,7 +2505,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "473", + "content-length": "488", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -2484,6 +2539,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -2496,7 +2552,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "451", + "content-length": "466", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -2530,6 +2586,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -2542,7 +2599,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "520", + "content-length": "535", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -2576,6 +2633,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -2588,7 +2646,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "520", + "content-length": "535", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -2620,6 +2678,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [ Object { "count": Object { @@ -2641,6 +2700,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, ], @@ -2665,7 +2725,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "956", + "content-length": "986", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -2697,6 +2757,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [ Object { "count": Object { @@ -2718,6 +2779,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "status": "published", }, ], @@ -2742,7 +2804,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "956", + "content-length": "986", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -2929,6 +2991,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -2941,7 +3004,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "464", + "content-length": "479", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -2975,6 +3038,7 @@ Object { "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, }, "parent_id": Nullable, + "pinned": Any, "replies": Array [], "status": "published", }, @@ -2987,7 +3051,7 @@ Object { "access-control-allow-credentials": "true", "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "455", + "content-length": "470", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, diff --git a/ghost/core/test/e2e-api/members-comments/comments.test.js b/ghost/core/test/e2e-api/members-comments/comments.test.js index 946ef03599c..eda70609b54 100644 --- a/ghost/core/test/e2e-api/members-comments/comments.test.js +++ b/ghost/core/test/e2e-api/members-comments/comments.test.js @@ -24,6 +24,7 @@ const dbFns = { * @property {string} [html='This is a comment'] * @property {string} [status] * @property {Date} [created_at] + * @property {Date} [pinned_at] */ /** * @typedef {Object} AddCommentReplyData @@ -47,6 +48,7 @@ const dbFns = { parent_id: data.parent_id, html: data.html || '

This is a comment

', created_at: data.created_at, + pinned_at: data.pinned_at, in_reply_to_id: data.in_reply_to_id, status: data.status || 'published' }); @@ -112,7 +114,8 @@ const commentMatcher = { count: { likes: anyNumber }, - liked: anyBoolean + liked: anyBoolean, + pinned: anyBoolean }; const labsCommentMatcher = { @@ -847,6 +850,150 @@ describe('Comments API', function () { assert.equal(lastComment.id, oldestComment.id); }); + it('shows pinned top-level comments first regardless of requested sort order', async function () { + const unpinnedOlder = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: '

Older unpinned

', + created_at: new Date('2023-01-01T00:00:00.000Z') + }); + const pinnedOlder = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: '

Older pinned

', + created_at: new Date('2023-02-01T00:00:00.000Z'), + pinned_at: new Date('2025-01-01T00:00:00.000Z') + }); + const pinnedNewer = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: '

Newer pinned

', + created_at: new Date('2023-03-01T00:00:00.000Z'), + pinned_at: new Date('2025-02-01T00:00:00.000Z') + }); + const unpinnedNewer = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: '

Newer unpinned

', + created_at: new Date('2023-04-01T00:00:00.000Z') + }); + + const result = await membersAgent + .get(`/api/comments/post/${postId}/?order=${encodeURIComponent('created_at asc')}`) + .expectStatus(200); + + assert.deepEqual(result.body.comments.map(comment => comment.id), [ + pinnedNewer.id, + pinnedOlder.id, + unpinnedOlder.id, + unpinnedNewer.id + ]); + assert.deepEqual(result.body.comments.map(comment => comment.pinned), [ + true, + true, + false, + false + ]); + }); + + it('preserves pinned state when fields are requested', async function () { + const pinnedComment = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: '

Pinned with fields

', + pinned_at: new Date('2025-01-01T00:00:00.000Z') + }); + + const result = await membersAgent + .get(`/api/comments/post/${postId}/?fields=id,pinned`) + .expectStatus(200); + const [comment] = result.body.comments; + + assert.equal(comment.id, pinnedComment.id); + assert.equal(comment.pinned, true); + assert.equal(Object.hasOwn(comment, 'parent_id'), false); + assert.equal(Object.hasOwn(comment, 'status'), false); + assert.equal(Object.hasOwn(comment, 'pinned_at'), false); + }); + + it('preserves pinned state when fields are requested for a single comment', async function () { + const pinnedComment = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: '

Pinned single comment

', + pinned_at: new Date('2025-01-01T00:00:00.000Z') + }); + + const result = await membersAgent + .get(`/api/comments/${pinnedComment.id}/?fields=id,pinned`) + .expectStatus(200); + const [comment] = result.body.comments; + + assert.equal(comment.id, pinnedComment.id); + assert.equal(comment.pinned, true); + assert.equal(Object.hasOwn(comment, 'parent_id'), false); + assert.equal(Object.hasOwn(comment, 'status'), false); + assert.equal(Object.hasOwn(comment, 'pinned_at'), false); + }); + + it('does not add redacted html when fields do not request html', async function () { + const hiddenComment = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: '

Hidden pinned comment

', + status: 'hidden', + pinned_at: new Date('2025-01-01T00:00:00.000Z') + }); + const deletedComment = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: '

Deleted pinned comment

', + status: 'deleted', + pinned_at: new Date('2025-01-01T00:00:00.000Z') + }); + await dbFns.addComment({ + member_id: fixtureManager.get('members', 1).id, + parent_id: hiddenComment.id, + html: '

Visible reply

' + }); + await dbFns.addComment({ + member_id: fixtureManager.get('members', 1).id, + parent_id: deletedComment.id, + html: '

Visible reply

' + }); + + const result = await membersAgent + .get(`/api/comments/post/${postId}/?fields=id,pinned`) + .expectStatus(200); + const hiddenResult = result.body.comments.find(comment => comment.id === hiddenComment.id); + const deletedResult = result.body.comments.find(comment => comment.id === deletedComment.id); + + assert.equal(hiddenResult.pinned, false); + assert.equal(deletedResult.pinned, false); + assert.equal(Object.hasOwn(hiddenResult, 'html'), false); + assert.equal(Object.hasOwn(deletedResult, 'html'), false); + }); + + it('does not pin hidden placeholders in public ordering', async function () { + const visibleComment = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: '

Visible comment

', + created_at: new Date('2023-01-01T00:00:00.000Z') + }); + const hiddenPinnedComment = await dbFns.addComment({ + member_id: fixtureManager.get('members', 0).id, + html: '

Hidden pinned comment

', + status: 'hidden', + created_at: new Date('2023-02-01T00:00:00.000Z'), + pinned_at: new Date('2025-01-01T00:00:00.000Z') + }); + await dbFns.addComment({ + member_id: fixtureManager.get('members', 1).id, + parent_id: hiddenPinnedComment.id, + html: '

Visible reply

' + }); + + const result = await membersAgent + .get(`/api/comments/post/${postId}/?order=${encodeURIComponent('created_at asc')}`) + .expectStatus(200); + + assert.equal(result.body.comments[0].id, visibleComment.id); + assert.equal(result.body.comments[1].id, hiddenPinnedComment.id); + assert.equal(result.body.comments[1].pinned, false); + }); + it('Can reply to your own comment', async function () { // Should not update last_seen_at or last_commented_at when both are already set to a value on the same day const timezone = settingsCache.get('timezone'); @@ -1472,6 +1619,25 @@ describe('Comments API', function () { assert(!deletedComment.html); }); + it('Clears pinned state when a member deletes their pinned comment', async function () { + const commentToDeleteId = (await dbFns.addComment({ + member_id: loggedInMember.id, + html: 'Pinned comment to delete', + pinned_at: new Date('2025-01-01T00:00:00.000Z') + })).get('id'); + + await membersAgent + .put(`/api/comments/${commentToDeleteId}`) + .body({comments: [{ + status: 'deleted' + }]}) + .expectStatus(200); + + const deletedComment = await models.Comment.findOne({id: commentToDeleteId}); + assert.equal(deletedComment.get('status'), 'deleted'); + assert.equal(deletedComment.get('pinned_at'), null); + }); + describe('replies to replies', function () { it('can browse comments with replies to replies', async function () { const {replies: [reply]} = await dbFns.addCommentWithReplies({ diff --git a/ghost/core/test/integration/services/comments-service.test.js b/ghost/core/test/integration/services/comments-service.test.js new file mode 100644 index 00000000000..3919cc9c2a5 --- /dev/null +++ b/ghost/core/test/integration/services/comments-service.test.js @@ -0,0 +1,71 @@ +const assert = require('node:assert/strict'); +const models = require('../../../core/server/models'); +const commentsService = require('../../../core/server/services/comments'); +const testUtils = require('../../utils'); +const {mockSetting, restore: restoreMocks} = require('../../utils/e2e-framework-mock-manager'); + +describe('CommentsService', function () { + before(testUtils.teardownDb); + beforeEach(testUtils.setup('default')); + afterEach(testUtils.teardownDb); + + beforeEach(function () { + commentsService.init(); + mockSetting('comments_enabled', 'all'); + }); + + afterEach(function () { + restoreMocks(); + }); + + describe('getComments', function () { + it('computes pinned state when requested', async function () { + const post = await models.Post.findOne({}, testUtils.context.internal); + const member = await models.Member.add({ + email: 'comment-service-test@example.com', + email_disabled: false + }, testUtils.context.internal); + const comment = await models.Comment.add({ + html: '

First.

', + member_id: member.id, + post_id: post.id + }, testUtils.context.internal); + const hiddenComment = await models.Comment.add({ + html: '

Hidden.

', + member_id: member.id, + post_id: post.id + }, testUtils.context.internal); + const commentId = comment.id; + const hiddenCommentId = hiddenComment.id; + + await testUtils.knex('comments').where({id: commentId}).update({ + pinned_at: new Date('2026-01-01T00:00:00.000Z') + }); + await testUtils.knex('comments').where({id: hiddenCommentId}).update({ + pinned_at: new Date('2026-01-01T00:00:00.000Z'), + status: 'hidden' + }); + + const adminComments = await commentsService.api.getAdminComments({ + columns: ['id', 'pinned'], + filter: `id:'${hiddenCommentId}'` + }); + const publicComments = await commentsService.api.getComments({ + columns: ['id', 'pinned'], + filter: `id:'${commentId}'` + }); + const regularComments = await commentsService.api.getComments({ + columns: ['id'], + filter: `id:'${commentId}'` + }); + + const adminComment = adminComments.data[0]; + const publicComment = publicComments.data[0]; + const regularComment = regularComments.data[0]; + + assert.equal(Boolean(adminComment.get('pinned')), true); + assert.equal(Boolean(publicComment.get('pinned')), true); + assert.equal(regularComment.get('pinned'), undefined); + }); + }); +}); diff --git a/ghost/core/test/integration/services/email-service/__snapshots__/cards.test.js.snap b/ghost/core/test/integration/services/email-service/__snapshots__/cards.test.js.snap index 61f207f2b6b..a4c0feb344f 100644 --- a/ghost/core/test/integration/services/email-service/__snapshots__/cards.test.js.snap +++ b/ghost/core/test/integration/services/email-service/__snapshots__/cards.test.js.snap @@ -670,6 +670,20 @@ table.body h2 span { + + + + + + +
+ + \\"Comment\\" +

Comment

+
+
+ + @@ -843,6 +857,26 @@ This is a paragraph test. + + +Comment + + +[http://127.0.0.1:2369/r/xxxxxx?m=member-uuid] + + + + + + + + + + + + + + Ghost © 2025 – Unsubscribe [http://127.0.0.1:2369/unsubscribe/?uuid=member-uuid&key=xxxxxx&newsletter=requested-newsletter-uuid] @@ -1537,6 +1571,20 @@ table.body h2 span { + + + + + + +
+ + \\"Comment\\" +

Comment

+
+
+ + @@ -1710,6 +1758,26 @@ This is a paragraph + + +Comment + + +[http://127.0.0.1:2369/r/xxxxxx?m=member-uuid] + + + + + + + + + + + + + + Ghost © 2025 – Unsubscribe [http://127.0.0.1:2369/unsubscribe/?uuid=member-uuid&key=xxxxxx&newsletter=requested-newsletter-uuid] @@ -8521,6 +8589,20 @@ Ghost: Independent technology for modern publishingBeautiful, modern publishing + + + + + + +
+ + \\"Comment\\" +

Comment

+
+
+ + @@ -9039,6 +9121,26 @@ http://127.0.0.1:2369/r/xxxxxx?m=member-uuid Listen to your podcasts [http://127 + + +Comment + + +[http://127.0.0.1:2369/r/xxxxxx?m=member-uuid] + + + + + + + + + + + + + + Ghost © 2025 – Unsubscribe [http://127.0.0.1:2369/unsubscribe/?uuid=member-uuid&key=xxxxxx&newsletter=requested-newsletter-uuid] @@ -10068,6 +10170,20 @@ Beautiful, modern publishing with newsletters and premium subscriptions built-in + + + + + + +
+ + \\"Comment\\" +

Comment

+
+
+ + @@ -10592,6 +10708,26 @@ http://127.0.0.1:2369/r/xxxxxx?m=member-uuid Listen to your podcasts [http://127 + + +Comment + + +[http://127.0.0.1:2369/r/xxxxxx?m=member-uuid] + + + + + + + + + + + + + + Ghost © 2025 – Unsubscribe [http://127.0.0.1:2369/unsubscribe/?uuid=member-uuid&key=xxxxxx&newsletter=requested-newsletter-uuid] diff --git a/ghost/core/test/unit/api/canary/utils/serializers/output/mapper.test.js b/ghost/core/test/unit/api/canary/utils/serializers/output/mapper.test.js index a59c172351b..0a3cb0d2b90 100644 --- a/ghost/core/test/unit/api/canary/utils/serializers/output/mapper.test.js +++ b/ghost/core/test/unit/api/canary/utils/serializers/output/mapper.test.js @@ -421,6 +421,7 @@ describe('Unit: utils/serializers/output/mappers', function () { html: 'html1', created_at: 'created_at1', edited_at: 'edited_at1', + pinned: false, member: { id: 'id1', uuid: 'uuid1', @@ -680,10 +681,12 @@ describe('Unit: utils/serializers/output/mappers', function () { } }, in_reply_to_id: null, - in_reply_to_snippet: null + in_reply_to_snippet: null, + pinned: false }, in_reply_to_id: 'comment2', - in_reply_to_snippet: 'comment 2' + in_reply_to_snippet: 'comment 2', + pinned: false }); }); @@ -705,7 +708,8 @@ describe('Unit: utils/serializers/output/mappers', function () { assert.deepEqual(mapped, { in_reply_to_snippet: 'First paragraph with link, and new line. Second paragraph', - member: null + member: null, + pinned: false }); }); @@ -725,7 +729,8 @@ describe('Unit: utils/serializers/output/mappers', function () { html: '

comment 1

', member: null, in_reply_to_id: null, - in_reply_to_snippet: null + in_reply_to_snippet: null, + pinned: false }); }); }); diff --git a/ghost/core/test/unit/frontend/src/admin-auth-message-handler.test.js b/ghost/core/test/unit/frontend/src/admin-auth-message-handler.test.js index f1ac77bf167..8226563c2cb 100644 --- a/ghost/core/test/unit/frontend/src/admin-auth-message-handler.test.js +++ b/ghost/core/test/unit/frontend/src/admin-auth-message-handler.test.js @@ -74,7 +74,9 @@ const ACTION_CASES = [ {action: 'getReplies', idField: 'commentId', expectedPath: `/comments/${VALID_ID}/replies/`}, {action: 'readComment', idField: 'commentId', expectedPath: `/comments/${VALID_ID}/`}, {action: 'hideComment', idField: 'id', expectedPath: `/comments/${VALID_ID}/`, expectedMethod: 'PUT'}, - {action: 'showComment', idField: 'id', expectedPath: `/comments/${VALID_ID}/`, expectedMethod: 'PUT'} + {action: 'showComment', idField: 'id', expectedPath: `/comments/${VALID_ID}/`, expectedMethod: 'PUT'}, + {action: 'pinComment', idField: 'id', expectedPath: `/comments/${VALID_ID}/`, expectedMethod: 'PUT'}, + {action: 'unpinComment', idField: 'id', expectedPath: `/comments/${VALID_ID}/`, expectedMethod: 'PUT'} ]; describe('admin-auth message-handler', function () { diff --git a/ghost/core/test/unit/server/data/schema/integrity.test.js b/ghost/core/test/unit/server/data/schema/integrity.test.js index 7ffc629a390..9050adc78fc 100644 --- a/ghost/core/test/unit/server/data/schema/integrity.test.js +++ b/ghost/core/test/unit/server/data/schema/integrity.test.js @@ -35,9 +35,9 @@ const validateRouteSettings = require('../../../../../core/server/services/route */ describe('DB version integrity', function () { // Only these variables should need updating - const currentSchemaHash = '20b3032b2ec14edfdac13d1485391f19'; + const currentSchemaHash = 'adae152c216cb1350b85296fa29c9ec4'; const currentFixturesHash = 'b76d01321e02fb99b11e7a29f91859f7'; - const currentSettingsHash = '857b77a8e1c7072d9eb639152c78a49e'; + const currentSettingsHash = '8453fbccc17256a188cc5ffed8c31945'; const currentRoutesHash = '3d180d52c663d173a6be791ef411ed01'; // If this test is failing, then it is likely a change has been made that requires a DB version bump, diff --git a/ghost/core/test/unit/server/services/email-service/email-renderer.test.js b/ghost/core/test/unit/server/services/email-service/email-renderer.test.js index c1b2666a001..ccf84736503 100644 --- a/ghost/core/test/unit/server/services/email-service/email-renderer.test.js +++ b/ghost/core/test/unit/server/services/email-service/email-renderer.test.js @@ -1405,6 +1405,8 @@ describe('Email renderer', function () { }); it('Renders RTL attributes for Persian, Arabic, Hebrew, and Urdu', async function () { + this.timeout(10000); + for (const locale of ['fa', 'ar', 'he', 'ur']) { customSettings.locale = locale; const post = createModel(basePost); diff --git a/ghost/core/test/utils/fixtures/default-settings.json b/ghost/core/test/utils/fixtures/default-settings.json index 86a3aa08a8f..2457c3de5e6 100644 --- a/ghost/core/test/utils/fixtures/default-settings.json +++ b/ghost/core/test/utils/fixtures/default-settings.json @@ -558,7 +558,7 @@ "comments": { "comments_enabled": { "type": "string", - "defaultValue": "off", + "defaultValue": "all", "validations": { "isEmpty": false, "isIn": [[ diff --git a/ghost/i18n/locales/af/comments.json b/ghost/i18n/locales/af/comments.json index de0df8359a8..53feb9d24d5 100644 --- a/ghost/i18n/locales/af/comments.json +++ b/ghost/i18n/locales/af/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Voltydse ouer", "Head of Marketing at Acme, Inc": "Hoof van Bemarking by Acme, Inc", "Hidden for members": "", - "Hide": "Versteek", "Hide comment": "Versteek kommentaar", "Jamie Larson": "Jamie Larson", "Join the discussion": "Sluit aan by die bespreking", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "Een uur gelede", "One min ago": "Een minuut gelede", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "Antwoord", @@ -59,7 +60,6 @@ "Save": "Stoor", "Sending": "Besig om te stuur", "Sent": "Gestuur", - "Show": "Wys", "Show {amount} more replies": "Wys {amount} meer antwoorde", "Show 1 more reply": "Wys nog 1 antwoord", "Show comment": "Wys kommentaar", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Hierdie kommentaar is weggesteek.", "This comment has been removed.": "Hierdie kommentaar is verwyder.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Gradeer nou op", "View in admin": "", "Yesterday": "Gister", diff --git a/ghost/i18n/locales/ar/comments.json b/ghost/i18n/locales/ar/comments.json index 3a19cc7a623..198f0e9d466 100644 --- a/ghost/i18n/locales/ar/comments.json +++ b/ghost/i18n/locales/ar/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "أب بدوام كامل", "Head of Marketing at Acme, Inc": "رئيس وحدة التسويق لدى شركة أكمى", "Hidden for members": "", - "Hide": "اخفاء", "Hide comment": "اخف التعليق", "Jamie Larson": "فلان الفلانى", "Join the discussion": "انضم للمحادثة", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "منذ ساعة واحدة", "One min ago": " منذ دقيقة واحدة", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "رد", @@ -59,7 +60,6 @@ "Save": "حفظ", "Sending": "جارى الارسال", "Sent": "تم الارسال", - "Show": "اظهر", "Show {amount} more replies": "اظهر {amount} ردود", "Show 1 more reply": "اظهر تعليق وحيد", "Show comment": "اظهر التعليق", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "تم اخفاء هذا التعليق", "This comment has been removed.": "تم حذف هذا التعليق", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "قم بالترقية الآن", "View in admin": "", "Yesterday": "أمس", diff --git a/ghost/i18n/locales/bg/comments.json b/ghost/i18n/locales/bg/comments.json index c6c6b00d864..a28706686dd 100644 --- a/ghost/i18n/locales/bg/comments.json +++ b/ghost/i18n/locales/bg/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Родител на пълно работно време", "Head of Marketing at Acme, Inc": "Директор маркетинг в Компания ООД", "Hidden for members": "Скрит за абонати", - "Hide": "Скриване", "Hide comment": "Скриване на коментара", "Jamie Larson": "Иван Иванов", "Join the discussion": "Участвайте в дискусията", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Веднъж изтрит, коментарът не може да бъде възстановен.", "One hour ago": "Преди час", "One min ago": "Преди минута", + "Pin comment": "", + "Pinned": "", "removed": "премахнат", "Replied to": "Отговорено на", "Reply": "Отговор", @@ -59,7 +60,6 @@ "Save": "Запис", "Sending": "Изпращане", "Sent": "Изпратен", - "Show": "Показване", "Show {amount} more replies": "Покажи още {amount} отговора", "Show 1 more reply": "Покажи още един отговор", "Show comment": "Покажи коментар", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "Посоченият коментар вече не е наличен.", "This comment has been hidden.": "Този коментар е скрит.", "This comment has been removed.": "Този коментар е премахнат.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Надградете сега", "View in admin": "Преглед в админ панела", "Yesterday": "Вчера", diff --git a/ghost/i18n/locales/bn/comments.json b/ghost/i18n/locales/bn/comments.json index a00406484fb..8d7d65114fa 100644 --- a/ghost/i18n/locales/bn/comments.json +++ b/ghost/i18n/locales/bn/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "পূর্ণকালীন অভিভাবক", "Head of Marketing at Acme, Inc": "মার্কেটিং প্রধান @ বাংলাদেশ ট্রেড হাব", "Hidden for members": "", - "Hide": "লুকান", "Hide comment": "মন্তব্য লুকান", "Jamie Larson": "শাহ নেওয়াজ", "Join the discussion": "আলোচনায় যোগ দিন", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "এক ঘণ্টা পূর্বে", "One min ago": "", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "উত্তর", @@ -59,7 +60,6 @@ "Save": "সংরক্ষণ করুন", "Sending": "পাঠানো হচ্ছে", "Sent": "পাঠানো হয়েছে", - "Show": "দেখান", "Show {amount} more replies": " আরো {amount}টি উত্তর দেখান", "Show 1 more reply": "১টি আরো উত্তর দেখান", "Show comment": "মন্তব্য দেখান", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "এই মন্তব্য লুকানো হয়েছে।", "This comment has been removed.": "এই মন্তব্য মুছে ফেলা হয়েছে।", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "এখনই আপগ্রেড করুন", "View in admin": "", "Yesterday": "গতকাল", diff --git a/ghost/i18n/locales/bs/comments.json b/ghost/i18n/locales/bs/comments.json index 67c91355807..ea34570a423 100644 --- a/ghost/i18n/locales/bs/comments.json +++ b/ghost/i18n/locales/bs/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Full time roditelj", "Head of Marketing at Acme, Inc": "Šef marketinga u kompaniji Acme d.o.o", "Hidden for members": "", - "Hide": "Sakrij", "Hide comment": "Sakrij komentar", "Jamie Larson": "Vanja Larsić", "Join the discussion": "Pridruži se diskusiji", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "Prije jedan sat", "One min ago": "Prije jedan minut", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "Odgovori", @@ -59,7 +60,6 @@ "Save": "Sačuvaj", "Sending": "Šaljem", "Sent": "Poslano", - "Show": "Prikaži", "Show {amount} more replies": "Prikaži još {amount} odgovora", "Show 1 more reply": "Prikaži još jedan odgovor", "Show comment": "Prikaži komentar", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Ovaj komentar je sakriven.", "This comment has been removed.": "Ovaj komentar je uklonjen.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Nadogradi račun sada", "View in admin": "", "Yesterday": "Jučer", diff --git a/ghost/i18n/locales/ca/comments.json b/ghost/i18n/locales/ca/comments.json index 18d6bcaaf8d..5cb6470009a 100644 --- a/ghost/i18n/locales/ca/comments.json +++ b/ghost/i18n/locales/ca/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Pare a temps complet", "Head of Marketing at Acme, Inc": "Cap de màrqueting a Acme, Inc", "Hidden for members": "Ocult pels membres", - "Hide": "Oculta", "Hide comment": "Oculta el comentari", "Jamie Larson": "Jamie Larson", "Join the discussion": "Uneix-te al debat", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Un cop esborrat, aquest comentari no es pot recuperar", "One hour ago": "Fa una hora", "One min ago": "Fa un minut", + "Pin comment": "", + "Pinned": "", "removed": "Esborrat", "Replied to": "Respost a", "Reply": "Respon", @@ -59,7 +60,6 @@ "Save": "Desa", "Sending": "Enviant", "Sent": "Enviat", - "Show": "Mostra", "Show {amount} more replies": "Mostra {amount} respostes més", "Show 1 more reply": "Mostra 1 resposta més", "Show comment": "Mostra comentari", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Aquest comentari s'ha ocultat.", "This comment has been removed.": "Aquest comentari s'ha esborrat.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Actualitza ara", "View in admin": "", "Yesterday": "Ahir", diff --git a/ghost/i18n/locales/context.json b/ghost/i18n/locales/context.json index 2cb98bc543b..d47dac9b040 100644 --- a/ghost/i18n/locales/context.json +++ b/ghost/i18n/locales/context.json @@ -143,7 +143,6 @@ "Hey there!": "An introduction/opening to an email", "Hey there,": "An introduction/opening to an email", "Hidden for members": "Shown to moderators when a comment is hidden from members", - "Hide": "Action in the context menu for administrators, on smaller devices", "Hide comment": "Action in the context menu for administrators", "If a newsletter is flagged as spam, emails are automatically disabled for that address to make sure you no longer receive any unwanted messages.": "Paragraph in the email suppression FAQ", "If the spam complaint was accidental, or you would like to begin receiving emails again, you can resubscribe to emails by clicking the button on the previous screen.": "Paragraph in the email suppression FAQ", @@ -212,6 +211,8 @@ "Or, skip the code and sign in directly": "Text in the sign-in email offering a direct magic link as an alternative to entering the one-time code", "Permanent failure (bounce)": "A section title in the email suppression FAQ", "Phone number": "Label for phone number input", + "Pin comment": "Action in the comments UI context menu for staff users to pin a top-level comment", + "Pinned": "Label shown beside a pinned comment in the comments UI", "Plan": "Label for the default subscription plan", "Plan checkout was cancelled.": "Notification for when a plan checkout was cancelled", "Plan upgrade was cancelled.": "Notification for when a plan upgrade was cancelled", @@ -249,7 +250,6 @@ "Sent": "Button text when a comment was reported succesfully", "Sent to {email}": "Magic link email footer", "Share": "Title of Portal share modal for sharing a post, and label for the newsletter email footer Share button text passed as buttonText to the feedbackButton partial", - "Show": "Context menu action for a comment, for administrators - smaller devices", "Show 1 more reply": "Button text to load the last remaining reply for a comment", "Show all": "Show all recommendations", "Show comment": "Context menu action for a comment, for administrators", @@ -330,6 +330,8 @@ "Try free for {amount} days, then {originalPrice}.": "Portal - label for a free trial that lasts a specific number of days, followed by the original price", "Unable to initiate checkout session": "error message", "Unlock access to all newsletters by becoming a paid subscriber.": "A message to encourage members to upgrade to a paid subscription", + "Unpin": "Short hover label on the pinned comment badge button for staff users", + "Unpin comment": "Action in the comments UI context menu for staff users to unpin a pinned top-level comment", "Unsubscribe": "Link in the newsletter", "Unsubscribe from all emails": "A button on the unsubscribe page, offering a shortcut to unsubscribe from every newsletter at the same time", "Unsubscribe from comment reply notifications": "Link text in comments reply email to unsubscribe from notifications", diff --git a/ghost/i18n/locales/cs/comments.json b/ghost/i18n/locales/cs/comments.json index 4c8013006c9..8c8ac59e78a 100644 --- a/ghost/i18n/locales/cs/comments.json +++ b/ghost/i18n/locales/cs/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Rodič na plný úvazek", "Head of Marketing at Acme, Inc": "Vedoucí marketingu v Acme, Inc", "Hidden for members": "", - "Hide": "Skrýt", "Hide comment": "Skrýt komentář", "Jamie Larson": "Jamie Larson", "Join the discussion": "Připojte se k diskusi", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "Před jednou hodinou", "One min ago": "Před jednou minutou", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "Odpovědět", @@ -59,7 +60,6 @@ "Save": "Uložit", "Sending": "Odesílání", "Sent": "Odesláno", - "Show": "Zobrazit", "Show {amount} more replies": "Zobrazit {amount} dalších odpovědí", "Show 1 more reply": "Zobrazit 1 další odpověď", "Show comment": "Zobrazit komentář", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Tento komentář byl skryt.", "This comment has been removed.": "Tento komentář byl odstraněn.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Upgradovat", "View in admin": "", "Yesterday": "Včera", diff --git a/ghost/i18n/locales/da/comments.json b/ghost/i18n/locales/da/comments.json index 62ee188238a..32d094067fa 100644 --- a/ghost/i18n/locales/da/comments.json +++ b/ghost/i18n/locales/da/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Forældre på fuld tid", "Head of Marketing at Acme, Inc": "Chef for marking hos Acme, Inc", "Hidden for members": "Skjult for medlemmer", - "Hide": "Skjul", "Hide comment": "Skjul kommentar", "Jamie Larson": "Jamie Larson", "Join the discussion": "Bliv en del af diskussionen", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Når den først er slettet, kan denne kommentar ikke gendannes.", "One hour ago": "En time siden", "One min ago": "Et minut siden", + "Pin comment": "", + "Pinned": "", "removed": "fjernet", "Replied to": "Svarede", "Reply": "Svar", @@ -59,7 +60,6 @@ "Save": "Gem", "Sending": "Sender", "Sent": "Sendt", - "Show": "Vis", "Show {amount} more replies": "Vis {amount} flere svar", "Show 1 more reply": "Vis 1 mere svar", "Show comment": "Vis kommentar", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Denne kommentar er blevet skjult.", "This comment has been removed.": "Denne kommentar er blevet fjernet.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Opgrader nu", "View in admin": "", "Yesterday": "I går", diff --git a/ghost/i18n/locales/de-CH/comments.json b/ghost/i18n/locales/de-CH/comments.json index d81fd64871b..7ee70b6a9ef 100644 --- a/ghost/i18n/locales/de-CH/comments.json +++ b/ghost/i18n/locales/de-CH/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Vollzeit-Elternteil", "Head of Marketing at Acme, Inc": "Leiter Marketing bei Acme, Inc", "Hidden for members": "Verborgen für Mitglieder", - "Hide": "Verbergen", "Hide comment": "Kommentar verbergen", "Jamie Larson": "Jamie Larson", "Join the discussion": "An der Diskussion teilnehmen", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Der Kommentar kann nicht wiederhergestellt werden, wenn er gelöscht wurde.", "One hour ago": "Vor einer Stunde", "One min ago": "Vor einer Minute", + "Pin comment": "", + "Pinned": "", "removed": "entfernt", "Replied to": "Antwortet auf", "Reply": "Antworten", @@ -59,7 +60,6 @@ "Save": "Speichern", "Sending": "Senden", "Sent": "Gesendet", - "Show": "Anzeigen", "Show {amount} more replies": "{amount} weitere Antworten anzeigen", "Show 1 more reply": "1 weitere Antwort anzeigen", "Show comment": "Kommentar anzeigen", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "Der verlinkte Kommentar ist nicht mehr verfügbar.", "This comment has been hidden.": "Dieser Kommentar wurde ausgeblendet.", "This comment has been removed.": "Dieser Kommentar wurde entfernt.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Jetzt upgraden", "View in admin": "Im Admin anzeigen", "Yesterday": "Gestern", diff --git a/ghost/i18n/locales/de/comments.json b/ghost/i18n/locales/de/comments.json index 3ec343c0116..4329ebb6c1b 100644 --- a/ghost/i18n/locales/de/comments.json +++ b/ghost/i18n/locales/de/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Vollzeit-Elternteil", "Head of Marketing at Acme, Inc": "Leiter Marketing bei Acme, Inc", "Hidden for members": "Für Mitglieder verborgen", - "Hide": "Verbergen", "Hide comment": "Kommentar verbergen", "Jamie Larson": "Jamie Larson", "Join the discussion": "Nimm an der Diskussion teil", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Der Kommentar kann nicht wiederhergestellt werden, sobald er gelöscht wurde.", "One hour ago": "Vor einer Stunde", "One min ago": "Vor einer Minute", + "Pin comment": "", + "Pinned": "", "removed": "entfernt", "Replied to": "Antwort auf", "Reply": "Antworten", @@ -59,7 +60,6 @@ "Save": "Speichern", "Sending": "Senden", "Sent": "Gesendet", - "Show": "Anzeigen", "Show {amount} more replies": "{amount} weitere Antworten anzeigen", "Show 1 more reply": "1 weitere Antwort anzeigen", "Show comment": "Kommentar anzeigen", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Dieser Kommentar wurde ausgeblendet.", "This comment has been removed.": "Dieser Kommentar wurde entfernt.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Jetzt upgraden", "View in admin": "", "Yesterday": "Gestern", diff --git a/ghost/i18n/locales/el/comments.json b/ghost/i18n/locales/el/comments.json index 69e1d4500e0..39de6c86801 100644 --- a/ghost/i18n/locales/el/comments.json +++ b/ghost/i18n/locales/el/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Γονέας πλήρους απασχόλησης", "Head of Marketing at Acme, Inc": "Επικεφαλής Μάρκετινγκ στην Acme, Inc", "Hidden for members": "", - "Hide": "Απόκρυψη", "Hide comment": "Απόκρυψη σχολίου", "Jamie Larson": "Τζέιμι Λάρσον", "Join the discussion": "Συμμετοχή στη συζήτηση", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "Πριν από μία ώρα", "One min ago": "Πριν από ένα λεπτό", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "Απάντηση", @@ -59,7 +60,6 @@ "Save": "Αποθήκευση", "Sending": "Αποστολή", "Sent": "Απεστάλη", - "Show": "Εμφάνιση", "Show {amount} more replies": "Εμφάνιση {amount} περισσότερων απαντήσεων", "Show 1 more reply": "Εμφάνιση 1 ακόμα απάντησης", "Show comment": "Εμφάνιση σχολίου", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Αυτό το σχόλιο έχει κρυφτεί.", "This comment has been removed.": "Αυτό το σχόλιο έχει αφαιρεθεί.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Αναβάθμιση τώρα", "View in admin": "", "Yesterday": "Χθες", diff --git a/ghost/i18n/locales/en/comments.json b/ghost/i18n/locales/en/comments.json index f7f32a7c4ac..3144452a843 100644 --- a/ghost/i18n/locales/en/comments.json +++ b/ghost/i18n/locales/en/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "", "Head of Marketing at Acme, Inc": "", "Hidden for members": "", - "Hide": "", "Hide comment": "", "Jamie Larson": "", "Join the discussion": "", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "", "One min ago": "", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "", @@ -59,7 +60,6 @@ "Save": "", "Sending": "", "Sent": "", - "Show": "", "Show {amount} more replies": "", "Show 1 more reply": "", "Show comment": "", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "", "This comment has been removed.": "", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "", "View in admin": "", "Yesterday": "", diff --git a/ghost/i18n/locales/eo/comments.json b/ghost/i18n/locales/eo/comments.json index f7f32a7c4ac..3144452a843 100644 --- a/ghost/i18n/locales/eo/comments.json +++ b/ghost/i18n/locales/eo/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "", "Head of Marketing at Acme, Inc": "", "Hidden for members": "", - "Hide": "", "Hide comment": "", "Jamie Larson": "", "Join the discussion": "", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "", "One min ago": "", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "", @@ -59,7 +60,6 @@ "Save": "", "Sending": "", "Sent": "", - "Show": "", "Show {amount} more replies": "", "Show 1 more reply": "", "Show comment": "", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "", "This comment has been removed.": "", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "", "View in admin": "", "Yesterday": "", diff --git a/ghost/i18n/locales/es/comments.json b/ghost/i18n/locales/es/comments.json index be311e4645e..6f89385f420 100644 --- a/ghost/i18n/locales/es/comments.json +++ b/ghost/i18n/locales/es/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Padre a tiempo completo", "Head of Marketing at Acme, Inc": "Jefe de Marketing en Acme, Inc", "Hidden for members": "Oculto para miembros", - "Hide": "Ocultar", "Hide comment": "Ocultar comentario", "Jamie Larson": "Carlos Rodriguez", "Join the discussion": "Únete a la discusión", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Este comentario no se puede recuperar una vez eliminado.", "One hour ago": "Hace una hora", "One min ago": "Hace un minuto", + "Pin comment": "", + "Pinned": "", "removed": "eliminado", "Replied to": "En respuesta a", "Reply": "Responder", @@ -59,7 +60,6 @@ "Save": "Guardar", "Sending": "Enviando", "Sent": "Enviado", - "Show": "Mostrar", "Show {amount} more replies": "Mostrar {amount} respuestas más", "Show 1 more reply": "Mostrar 1 respuesta más", "Show comment": "Mostrar comentario", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Este comentario ha sido ocultado.", "This comment has been removed.": "Este comentario ha sido borrado.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Actualiza tu cuenta ahora", "View in admin": "", "Yesterday": "Ayer", diff --git a/ghost/i18n/locales/et/comments.json b/ghost/i18n/locales/et/comments.json index 5b7e604a817..07728589761 100644 --- a/ghost/i18n/locales/et/comments.json +++ b/ghost/i18n/locales/et/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Täiskohaga lapsevanem", "Head of Marketing at Acme, Inc": "Turundusjuht Acme, Inc-s", "Hidden for members": "", - "Hide": "Peida", "Hide comment": "Peida kommentaar", "Jamie Larson": "Jamie Larson", "Join the discussion": "Liitu aruteluga", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "Üks tund tagasi", "One min ago": "Üks minut tagasi", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "Vasta", @@ -59,7 +60,6 @@ "Save": "Salvesta", "Sending": "Saatmine", "Sent": "Saadetud", - "Show": "Näita", "Show {amount} more replies": "Näita {amount} vastust veel", "Show 1 more reply": "Näita 1 vastus veel", "Show comment": "Näita kommentaari", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "See kommentaar on peidetud.", "This comment has been removed.": "See kommentaar on eemaldatud.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Uuenda nüüd", "View in admin": "", "Yesterday": "Eile", diff --git a/ghost/i18n/locales/eu/comments.json b/ghost/i18n/locales/eu/comments.json index d6e2b2c88a0..5a775aeaf31 100644 --- a/ghost/i18n/locales/eu/comments.json +++ b/ghost/i18n/locales/eu/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Lanaldi osoko gurasoa", "Head of Marketing at Acme, Inc": "FAGOR kooperatibako marketin-zuzendaria", "Hidden for members": "Ezkutatuta kideentzat", - "Hide": "Ezkutatu", "Hide comment": "Ezkutatu iruzkina", "Jamie Larson": "Lur Axpe", "Join the discussion": "Batu eztabaidara", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Ezabatuz gero, ezingo da berreskuratu.", "One hour ago": "Duela ordubete", "One min ago": "Duela minutu bat", + "Pin comment": "", + "Pinned": "", "removed": "kenduta", "Replied to": "Honi erantzun dio", "Reply": "Erantzun", @@ -59,7 +60,6 @@ "Save": "Gorde", "Sending": "Bidaltzen", "Sent": "Bidalita", - "Show": "Erakutsi", "Show {amount} more replies": "Erakutsi {amount} erantzun gehiago", "Show 1 more reply": "Erakutsi erantzun bat gehiago", "Show comment": "Erakutsi iruzkina", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Iruzkin hau ezkutatu da.", "This comment has been removed.": "Iruzkin hau kendu da.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Ordaindu orain", "View in admin": "", "Yesterday": "Atzo", diff --git a/ghost/i18n/locales/fa/comments.json b/ghost/i18n/locales/fa/comments.json index c20e35cb144..d5017a24555 100644 --- a/ghost/i18n/locales/fa/comments.json +++ b/ghost/i18n/locales/fa/comments.json @@ -33,7 +33,6 @@ "Full-time parent": " خانه\u200cدار تمام وقت", "Head of Marketing at Acme, Inc": "سرپرست بخش بازاریابی یک شرکت خیالی", "Hidden for members": "", - "Hide": "مخفی کردن", "Hide comment": "مخفی کردن دیدگاه", "Jamie Larson": "جیمی لارسن", "Join the discussion": "به گفتمان بپیوندید", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "یک ساعت پیش", "One min ago": "یک دقیقه پیش", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "پاسخ دادن", @@ -59,7 +60,6 @@ "Save": "ذخیره کردن", "Sending": "در حال ارسال", "Sent": "ارسال شد", - "Show": "نمایش", "Show {amount} more replies": "نمایش {amount} پاسخ بیشتر", "Show 1 more reply": "نمایش یک دیدگاه بیشتر", "Show comment": "نمایش دیدگاه", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "این دیدگاه مخفی شده است", "This comment has been removed.": "این دیدگاه پاک شده است", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "ارتقاء دهید", "View in admin": "", "Yesterday": "دیروز", diff --git a/ghost/i18n/locales/fi/comments.json b/ghost/i18n/locales/fi/comments.json index adac22b102d..c04e29e57ca 100644 --- a/ghost/i18n/locales/fi/comments.json +++ b/ghost/i18n/locales/fi/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Kokoaikainen vanhempi", "Head of Marketing at Acme, Inc": "Markkinointijohtaja", "Hidden for members": "Piilotettu jäseniltä", - "Hide": "Piilota", "Hide comment": "Piilota kommentti", "Jamie Larson": "Jamie Larson", "Join the discussion": "Liity keskusteluun", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Jos poistat kommentin, sitä ei voi enää palauttaa.", "One hour ago": "Tunti sitten", "One min ago": "Minuutti sitten", + "Pin comment": "", + "Pinned": "", "removed": "poistettu", "Replied to": "Vastattu:", "Reply": "Vastaa", @@ -59,7 +60,6 @@ "Save": "Tallenna", "Sending": "Lähetetään", "Sent": "Lähetetty", - "Show": "Näytä", "Show {amount} more replies": "Näytä {amount} vastausta lisää ", "Show 1 more reply": "Näytä 1 vastaus lisää", "Show comment": "Näytä kommentti", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Tämä kommentti on piilotettu.", "This comment has been removed.": "Tämä kommentti on poistettu.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Korota nyt", "View in admin": "", "Yesterday": "Eilen", diff --git a/ghost/i18n/locales/fr/comments.json b/ghost/i18n/locales/fr/comments.json index a34af6c0a53..8e3225f24c7 100644 --- a/ghost/i18n/locales/fr/comments.json +++ b/ghost/i18n/locales/fr/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Parent à plein temps", "Head of Marketing at Acme, Inc": "Responsable du marketing chez Acme, Inc", "Hidden for members": "Masqué pour les abonnés", - "Hide": "Masquer", "Hide comment": "Masquer le commentaire", "Jamie Larson": "Jean Martin", "Join the discussion": "Rejoindre la discussion", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Une fois supprimé, ce commentaire ne pourra pas être restauré.", "One hour ago": "Il y a une heure", "One min ago": "Il y a une minute", + "Pin comment": "", + "Pinned": "", "removed": "supprimé", "Replied to": "Répondu à", "Reply": "Répondre", @@ -59,7 +60,6 @@ "Save": "Enregistrer", "Sending": "Envoi en cours", "Sent": "Envoyé", - "Show": "Afficher", "Show {amount} more replies": "Afficher {amount} réponses supplémentaires", "Show 1 more reply": "Afficher 1 réponse supplémentaire", "Show comment": "Afficher le commentaire", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Ce commentaire a été masqué.", "This comment has been removed.": "Ce commentaire a été supprimé.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Mettre à jour maintenant", "View in admin": "", "Yesterday": "Hier", diff --git a/ghost/i18n/locales/gd/comments.json b/ghost/i18n/locales/gd/comments.json index b8a2cd5270e..04fec4ef955 100644 --- a/ghost/i18n/locales/gd/comments.json +++ b/ghost/i18n/locales/gd/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Pàrant làn-ùine", "Head of Marketing at Acme, Inc": "Àrd-cheann Margaidheachd aig Acme, Inc", "Hidden for members": "Air am falach bho bhuill", - "Hide": "Cuir am falach", "Hide comment": "Cuir am beachd am falach", "Jamie Larson": "Seamaidh Larson", "Join the discussion": "Gabh pàirt san deasbad", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Cha ghabh a aiseag an dèidh sguabadh às", "One hour ago": "O chionn uair a thìde", "One min ago": "O chionn mionaid", + "Pin comment": "", + "Pinned": "", "removed": "air a sguabadh às", "Replied to": "Air freagairt fhaighinn", "Reply": "Cuir freagairt", @@ -59,7 +60,6 @@ "Save": "Sabhail", "Sending": "’Ga chur", "Sent": "Air a chur", - "Show": "Seall", "Show {amount} more replies": "An àireamh de bheachdan a bharrachd a chìthear: {amount}", "Show 1 more reply": "Seall beachd a bharrachd", "Show comment": "Seall am beachd", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Chaidh am beachd seo a chur am falach.", "This comment has been removed.": "Chaidh am beachd seo a sguabadh às.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Àrdaich a-nis", "View in admin": "", "Yesterday": "An-dè", diff --git a/ghost/i18n/locales/he/comments.json b/ghost/i18n/locales/he/comments.json index 91e42b54b3d..bf399fa97bf 100644 --- a/ghost/i18n/locales/he/comments.json +++ b/ghost/i18n/locales/he/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "הורה במשרה מלאה", "Head of Marketing at Acme, Inc": "ראש אגף שיווק ב- Acme, Inc", "Hidden for members": "", - "Hide": "הסתר", "Hide comment": "הסתר תגובה", "Jamie Larson": "ג׳יימי לרסון", "Join the discussion": "הצטרפו לדיון", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "לפני שעה", "One min ago": "לפני דקה", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "תגובה", @@ -59,7 +60,6 @@ "Save": "שמירה", "Sending": "שליחה", "Sent": "שלחנו", - "Show": "להציג", "Show {amount} more replies": "להציג עוד {amount} תגובות", "Show 1 more reply": "להציג תגובה נוספת", "Show comment": "להציג תגובה", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "התגובה הזו הוסתרה.", "This comment has been removed.": "התגובה הזו הוסרה.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "שדרגו עכשיו", "View in admin": "", "Yesterday": "אתמול", diff --git a/ghost/i18n/locales/hi/comments.json b/ghost/i18n/locales/hi/comments.json index 3e9f72a26c2..4645b4575b5 100644 --- a/ghost/i18n/locales/hi/comments.json +++ b/ghost/i18n/locales/hi/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "पूर्णकालिक माता-पिता", "Head of Marketing at Acme, Inc": "Acme, Inc में विपणन प्रमुख", "Hidden for members": "सदस्यों के लिए छिपाया गया", - "Hide": "छिपाएं", "Hide comment": "टिप्पणी छिपाएं", "Jamie Larson": "राहुल शर्मा", "Join the discussion": "चर्चा में शामिल हों", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "एक बार हटाए जाने के बाद, इस टिप्पणी को पुनः प्राप्त नहीं किया जा सकता।", "One hour ago": "एक घंटा पहले", "One min ago": "एक मिनट पहले", + "Pin comment": "", + "Pinned": "", "removed": "हटा दिया गया", "Replied to": "को जवाब दिया", "Reply": "जवाब दें", @@ -59,7 +60,6 @@ "Save": "सहेजें", "Sending": "भेजा जा रहा है", "Sent": "भेजा गया", - "Show": "दिखाएं", "Show {amount} more replies": "{amount} और जवाब दिखाएं", "Show 1 more reply": "1 और जवाब दिखाएं", "Show comment": "टिप्पणी दिखाएं", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "इस टिप्पणी को छिपा दिया गया है।", "This comment has been removed.": "इस टिप्पणी को हटा दिया गया है।", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "अब उन्नत करें", "View in admin": "", "Yesterday": "कल", diff --git a/ghost/i18n/locales/hr/comments.json b/ghost/i18n/locales/hr/comments.json index 6ef1022283b..e36e1622b55 100644 --- a/ghost/i18n/locales/hr/comments.json +++ b/ghost/i18n/locales/hr/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Full-time Roditelj", "Head of Marketing at Acme, Inc": "Voditelj marketinga Acme, Inc", "Hidden for members": "", - "Hide": "Sakrij", "Hide comment": "Sakrij komentar", "Jamie Larson": "Jamie Larson", "Join the discussion": "Pridruži se raspravi", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "Prije jednog sata", "One min ago": "Prije jedne minute", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "Odgovori", @@ -59,7 +60,6 @@ "Save": "Spremi", "Sending": "Slanje", "Sent": "Poslano", - "Show": "Prikaži", "Show {amount} more replies": "Prikaži još {amount} odgovora", "Show 1 more reply": "Prikaži još jedan odgovor", "Show comment": "Prikaži komentar", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Ovaj komentar je skriven", "This comment has been removed.": "Ovaj komentar je maknut", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Nadogradite sada", "View in admin": "", "Yesterday": "Jučer", diff --git a/ghost/i18n/locales/hu/comments.json b/ghost/i18n/locales/hu/comments.json index 604d155f5b0..3397ecac939 100644 --- a/ghost/i18n/locales/hu/comments.json +++ b/ghost/i18n/locales/hu/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Főállású szülő", "Head of Marketing at Acme, Inc": "Marketingvezető —\u00a0Sirály Kft.", "Hidden for members": "A tagok számára elrejtve", - "Hide": "Elrejtés", "Hide comment": "Hozzászólás elrejtése", "Jamie Larson": "Kiss Sára", "Join the discussion": "Csatlakozz a beszélgetéshez", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "A törlés után ez a hozzászólás nem állítható vissza.", "One hour ago": "Egy órája", "One min ago": "Egy perce", + "Pin comment": "", + "Pinned": "", "removed": "eltávolítva", "Replied to": "Válaszolt erre", "Reply": "Válasz", @@ -59,7 +60,6 @@ "Save": "Mentés", "Sending": "Küldés", "Sent": "Elküldve", - "Show": "Mutat", "Show {amount} more replies": "Mutass még {amount} választ", "Show 1 more reply": "Mutass még egy választ", "Show comment": "Hozzászólás mutatása", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Ezt a hozzászólást elrejtettük.", "This comment has been removed.": "Ezt a hozzászólást töröltük.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Előfizetéssel te is hozzászólhatsz", "View in admin": "", "Yesterday": "Tegnap", diff --git a/ghost/i18n/locales/id/comments.json b/ghost/i18n/locales/id/comments.json index 403015b0e16..3a53bb88440 100644 --- a/ghost/i18n/locales/id/comments.json +++ b/ghost/i18n/locales/id/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Orang tua penuh waktu", "Head of Marketing at Acme, Inc": "Direktur Pemasaran di Acme, Inc", "Hidden for members": "Tersembunyi bagi anggota", - "Hide": "Sembunyikan", "Hide comment": "Sembunyikan komentar", "Jamie Larson": "Sutan T. Alisjahbana", "Join the discussion": "Bergabung dalam diskusi", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Setelah dihapus, komentar ini tidak dapat dipulihkan.", "One hour ago": "Satu jam yang lalu", "One min ago": "Satu menit yang lalu", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "Balas", @@ -59,7 +60,6 @@ "Save": "Simpan", "Sending": "Mengirim", "Sent": "Terkirim", - "Show": "Tampilkan", "Show {amount} more replies": "Tampilkan {amount} balasan lainnya", "Show 1 more reply": "Tampilkan 1 balasan lainnya", "Show comment": "Tampilkan komentar", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Komentar ini telah disembunyikan.", "This comment has been removed.": "Komentar ini telah dihapus.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Daftar sekarang", "View in admin": "", "Yesterday": "Kemarin", diff --git a/ghost/i18n/locales/is/comments.json b/ghost/i18n/locales/is/comments.json index f7f32a7c4ac..3144452a843 100644 --- a/ghost/i18n/locales/is/comments.json +++ b/ghost/i18n/locales/is/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "", "Head of Marketing at Acme, Inc": "", "Hidden for members": "", - "Hide": "", "Hide comment": "", "Jamie Larson": "", "Join the discussion": "", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "", "One min ago": "", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "", @@ -59,7 +60,6 @@ "Save": "", "Sending": "", "Sent": "", - "Show": "", "Show {amount} more replies": "", "Show 1 more reply": "", "Show comment": "", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "", "This comment has been removed.": "", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "", "View in admin": "", "Yesterday": "", diff --git a/ghost/i18n/locales/it/comments.json b/ghost/i18n/locales/it/comments.json index 151ad15160a..26500a012b8 100644 --- a/ghost/i18n/locales/it/comments.json +++ b/ghost/i18n/locales/it/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Genitore a tempo pieno", "Head of Marketing at Acme, Inc": "Ragioniere presso Megaditta", "Hidden for members": "Nascosto per gli abbonati", - "Hide": "Nascondi", "Hide comment": "Nascondi commento", "Jamie Larson": "Andrea Rossi", "Join the discussion": "Partecipa alla discussione", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Una volta cancellato, non potrai più recuperare questo commento", "One hour ago": "Un'ora fa", "One min ago": "Un minuto fa", + "Pin comment": "", + "Pinned": "", "removed": "rimosso", "Replied to": "Ha risposto a", "Reply": "Rispondi", @@ -59,7 +60,6 @@ "Save": "Salva", "Sending": "Invio", "Sent": "Inviato", - "Show": "Mostra", "Show {amount} more replies": "Mostra altre {amount} risposte", "Show 1 more reply": "Mostra un'altra risposta", "Show comment": "Mostra commento", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "Il commento collegato non è più disponibile.", "This comment has been hidden.": "Questo commento è stato nascosto.", "This comment has been removed.": "Questo commento è stato rimosso.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Abbonati ora", "View in admin": "Visualizza nell'area admin", "Yesterday": "Ieri", diff --git a/ghost/i18n/locales/ja/comments.json b/ghost/i18n/locales/ja/comments.json index 41981c39b86..1b450398f36 100644 --- a/ghost/i18n/locales/ja/comments.json +++ b/ghost/i18n/locales/ja/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "専業主婦・主夫", "Head of Marketing at Acme, Inc": "マーケティング責任者 @ Acme, Inc", "Hidden for members": "", - "Hide": "非表示にする", "Hide comment": "コメントを非表示にする", "Jamie Larson": "ジェイミー・ラーソン", "Join the discussion": "議論に参加する", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "1時間前", "One min ago": "1分前", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "返信する", @@ -59,7 +60,6 @@ "Save": "保存", "Sending": "送信中", "Sent": "送信完了", - "Show": "表示する", "Show {amount} more replies": "{amount}個の返信を表示する", "Show 1 more reply": "返信をもう1つ表示する", "Show comment": "コメントを表示する", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "このコメントは非表示にされました。", "This comment has been removed.": "このコメントは削除されました。", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "今すぐアップデートする", "View in admin": "", "Yesterday": "昨日", diff --git a/ghost/i18n/locales/ko/comments.json b/ghost/i18n/locales/ko/comments.json index a5f32d2a1be..6f13ed228c1 100644 --- a/ghost/i18n/locales/ko/comments.json +++ b/ghost/i18n/locales/ko/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "전업 부모", "Head of Marketing at Acme, Inc": "Acme Inc 마케팅 책임자", "Hidden for members": "", - "Hide": "숨기기", "Hide comment": "댓글 숨기기", "Jamie Larson": "제이미 라슨", "Join the discussion": "댓글에 참여해 주세요", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "1시간 전", "One min ago": "1분 전", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "답변", @@ -59,7 +60,6 @@ "Save": "저장", "Sending": "전송중", "Sent": "전송됨", - "Show": "보기", "Show {amount} more replies": "{amount}개의 댓글 더 보기", "Show 1 more reply": "1개의 댓글 더 보기", "Show comment": "댓글 보기", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "숨김처리 된 댓글이에요.", "This comment has been removed.": "삭제 된 댓글이에요.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "업그레이드", "View in admin": "", "Yesterday": "어제", diff --git a/ghost/i18n/locales/kz/comments.json b/ghost/i18n/locales/kz/comments.json index 0200ba83ec0..dbaec69653f 100644 --- a/ghost/i18n/locales/kz/comments.json +++ b/ghost/i18n/locales/kz/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Толық күн жұмыс істейтін ата-ана", "Head of Marketing at Acme, Inc": "@ Acme маркетинг жетекшісі ", "Hidden for members": "", - "Hide": "Жасыру", "Hide comment": "Пікірді жасыру", "Jamie Larson": "Пәленше Түгеншиев", "Join the discussion": "Талқыға қосылу", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "Бір сағат бұрын", "One min ago": "Бір минут бұрын", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "Жауап беру", @@ -59,7 +60,6 @@ "Save": "Сақтау", "Sending": "Жіберілуде", "Sent": "Жіберілді", - "Show": "Көрсету", "Show {amount} more replies": "Тағы {amount} жауапты көрсету", "Show 1 more reply": "Тағы 1 жауап көрсету", "Show comment": "Пікірді көрсету", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Бұл пікір жасырылды.", "This comment has been removed.": "Бұл пікір өшірілді.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Жаңарту ", "View in admin": "", "Yesterday": "Кеше", diff --git a/ghost/i18n/locales/lt/comments.json b/ghost/i18n/locales/lt/comments.json index cde78bb9e10..cc15031847d 100644 --- a/ghost/i18n/locales/lt/comments.json +++ b/ghost/i18n/locales/lt/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Vaiką prižiūrintis tėvas", "Head of Marketing at Acme, Inc": "Tyrinėtojas", "Hidden for members": "", - "Hide": "Paslėpti", "Hide comment": "Paslėpti komentarą", "Jamie Larson": "Vardenis Pavardenis", "Join the discussion": "Prisijunkite prie diskusijos", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "Prieš valandą", "One min ago": "Prieš minutę", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "Atsakyti", @@ -59,7 +60,6 @@ "Save": "Išsaugoti", "Sending": "Siunčiama", "Sent": "Išsiųsta", - "Show": "Rodyti", "Show {amount} more replies": "Rodyti daugiau atsakymų ({amount})", "Show 1 more reply": "Rodyti dar 1 atsakymą", "Show comment": "Rodyti komentarą", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Šis komentaras buvo paslėptas.", "This comment has been removed.": "Šis komentaras buvo pašalintas.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Prenumeruokite", "View in admin": "", "Yesterday": "Vakar", diff --git a/ghost/i18n/locales/lv/comments.json b/ghost/i18n/locales/lv/comments.json index e8e13502c77..55e79783792 100644 --- a/ghost/i18n/locales/lv/comments.json +++ b/ghost/i18n/locales/lv/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Pilnas slodzes vecāks", "Head of Marketing at Acme, Inc": "Mārketinga vadītājs uzņēmumā Acme, Inc", "Hidden for members": "Paslēpts dalībniekiem", - "Hide": "Slēpt", "Hide comment": "Slēpt komentāru", "Jamie Larson": "Jānis Reikulis", "Join the discussion": "Pievienojieties diskusijai", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Pēc dzēšanas šo komentāru nevar atgūt.", "One hour ago": "Pirms stundas", "One min ago": "Pirms minūtes", + "Pin comment": "", + "Pinned": "", "removed": "noņemts", "Replied to": "Atbildēja uz", "Reply": "Atbildēt", @@ -59,7 +60,6 @@ "Save": "Saglabāt", "Sending": "Notiek nosūtīšana", "Sent": "Nosūtīts", - "Show": "Rādīt", "Show {amount} more replies": "Rādīt vēl {amount} atbildes", "Show 1 more reply": "Rādīt vēl 1 atbildi", "Show comment": "Rādīt komentāru", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Šis komentārs ir paslēpts.", "This comment has been removed.": "Šis komentārs ir noņemts.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Jauniniet tūlīt", "View in admin": "", "Yesterday": "Vakar", diff --git a/ghost/i18n/locales/mk/comments.json b/ghost/i18n/locales/mk/comments.json index 5ef19941c54..073aedc6d2b 100644 --- a/ghost/i18n/locales/mk/comments.json +++ b/ghost/i18n/locales/mk/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Родител", "Head of Marketing at Acme, Inc": "Раководител на Маркетинг во Примерна Компанија", "Hidden for members": "", - "Hide": "Скријте", "Hide comment": "Скријте го коментарот", "Jamie Larson": "Петар Петровски", "Join the discussion": "Приклучете се во разговорот", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "Пред еден час", "One min ago": "", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "Одговорете", @@ -59,7 +60,6 @@ "Save": "Зачувајте", "Sending": "Се испраќа", "Sent": "Испратено", - "Show": "Покажи", "Show {amount} more replies": "Покажи уште {amount} одговори", "Show 1 more reply": "Покажи уште 1 одговор", "Show comment": "Покажи коментар", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Овој коментар беше скриен.", "This comment has been removed.": "Овој коментар беше отстранет.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Надградете", "View in admin": "", "Yesterday": "Вчера", diff --git a/ghost/i18n/locales/mn/comments.json b/ghost/i18n/locales/mn/comments.json index bb5493795f2..5f061236306 100644 --- a/ghost/i18n/locales/mn/comments.json +++ b/ghost/i18n/locales/mn/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Бүтэн цагийн эцэг/эх", "Head of Marketing at Acme, Inc": "Acme, Inc-н маркетингийн удирдлага", "Hidden for members": "Гишүүдээс нуугдсан", - "Hide": "Нуух", "Hide comment": "Сэтгэгдлийг нуух", "Jamie Larson": "Доржийн Бат", "Join the discussion": "Хэлэлцүүлэгт нэгдэх", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Устгасны дараа энэ сэтгэгдлийг сэргээх боломжгүй.", "One hour ago": "Нэг цагийн өмнө", "One min ago": "Нэг минутын өмнө", + "Pin comment": "", + "Pinned": "", "removed": "хасагдсан", "Replied to": "Хариулсан", "Reply": "Хариулах", @@ -59,7 +60,6 @@ "Save": "Хадгалах", "Sending": "Илгээж байна", "Sent": "Илгээгдсэн", - "Show": "Харуулах", "Show {amount} more replies": "{amount} хариултыг харуулах", "Show 1 more reply": "1 хариултыг харуулах", "Show comment": "Сэтгэгдлийг харуулах", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Энэ сэтгэгдэл нуугдсан.", "This comment has been removed.": "Энэ сэтгэгдэл хасагдсан.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Одоо шинэчлэх", "View in admin": "", "Yesterday": "Өчигдөр", diff --git a/ghost/i18n/locales/ms/comments.json b/ghost/i18n/locales/ms/comments.json index f7f32a7c4ac..3144452a843 100644 --- a/ghost/i18n/locales/ms/comments.json +++ b/ghost/i18n/locales/ms/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "", "Head of Marketing at Acme, Inc": "", "Hidden for members": "", - "Hide": "", "Hide comment": "", "Jamie Larson": "", "Join the discussion": "", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "", "One min ago": "", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "", @@ -59,7 +60,6 @@ "Save": "", "Sending": "", "Sent": "", - "Show": "", "Show {amount} more replies": "", "Show 1 more reply": "", "Show comment": "", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "", "This comment has been removed.": "", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "", "View in admin": "", "Yesterday": "", diff --git a/ghost/i18n/locales/nb/comments.json b/ghost/i18n/locales/nb/comments.json index d0fb0d6b1bc..8f6b2faf7fe 100644 --- a/ghost/i18n/locales/nb/comments.json +++ b/ghost/i18n/locales/nb/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Forelder på heltid", "Head of Marketing at Acme, Inc": "Markedsføringsleder hos Acme Inc", "Hidden for members": "Skjult for medlemmer", - "Hide": "Skjul", "Hide comment": "Skjul kommentar", "Jamie Larson": "Jamie Larson", "Join the discussion": "Delta i diskusjonen", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "En time siden", "One min ago": "Ett minutt siden", + "Pin comment": "", + "Pinned": "", "removed": "fjernet", "Replied to": "Svarte", "Reply": "Svar", @@ -59,7 +60,6 @@ "Save": "Lagre", "Sending": "Sender", "Sent": "Sendt", - "Show": "Vis", "Show {amount} more replies": "Vis {amount} flere svar", "Show 1 more reply": "Vis ett svar til", "Show comment": "Vis kommentar", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Denne kommentaren er skjult.", "This comment has been removed.": "Denne kommentaren er fjernet.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Oppgrader nå", "View in admin": "", "Yesterday": "I går", diff --git a/ghost/i18n/locales/ne/comments.json b/ghost/i18n/locales/ne/comments.json index 4c89e3c8810..a14bdab471c 100644 --- a/ghost/i18n/locales/ne/comments.json +++ b/ghost/i18n/locales/ne/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "पूर्ण-कालीन अभिभावक", "Head of Marketing at Acme, Inc": "Acme, Inc मा मार्केटिङ प्रमुख", "Hidden for members": "सदस्यहरूका लागि लुकाइएको", - "Hide": "लुकाउनुहोस्", "Hide comment": "प्रतिक्रिया लुकाउनुहोस्", "Jamie Larson": "जेमी लार्सन", "Join the discussion": "छलफलमा सामेल हुनुहोस्", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "एक पटक मेटाइएपछि, यो प्रतिक्रिया पुन: प्राप्त गर्न सकिँदैन।", "One hour ago": "एक घण्टा अघि", "One min ago": "एक मिनेट अघि", + "Pin comment": "", + "Pinned": "", "removed": "हटाइयो", "Replied to": "प्रत्युत्तर दिइयो", "Reply": "प्रत्युत्तर दिनुहोस्", @@ -59,7 +60,6 @@ "Save": "बचत गर्नुहोस्", "Sending": "पठाइँदैछ", "Sent": "पठाइयो", - "Show": "देखाउनुहोस्", "Show {amount} more replies": "थप {amount} प्रत्युत्तरहरू देखाउनुहोस्", "Show 1 more reply": "थप १ प्रत्युत्तर देखाउनुहोस्", "Show comment": "प्रतिक्रिया देखाउनुहोस्", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "यो प्रतिक्रिया लुकाइएको छ।", "This comment has been removed.": "यो प्रतिक्रिया हटाइएको छ।", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "अहिले अपग्रेड गर्नुहोस्", "View in admin": "", "Yesterday": "हिजो", diff --git a/ghost/i18n/locales/nl/comments.json b/ghost/i18n/locales/nl/comments.json index 740d4bc0969..6aa7a2ab709 100644 --- a/ghost/i18n/locales/nl/comments.json +++ b/ghost/i18n/locales/nl/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Full-time ouder", "Head of Marketing at Acme, Inc": "Acme BV, Hoofd Marketing", "Hidden for members": "Verborgen voor leden", - "Hide": "Verbergen", "Hide comment": "Verberg reactie", "Jamie Larson": "Jan Jansen", "Join the discussion": "Doe mee aan het gesprek", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Eenmaal verwijderd, kan deze reactie niet meer worden hersteld.", "One hour ago": "Eén uur geleden", "One min ago": "Eén min geleden", + "Pin comment": "", + "Pinned": "", "removed": "verwijderd", "Replied to": "Geantwoord op", "Reply": "Beantwoorden", @@ -59,7 +60,6 @@ "Save": "Opslaan", "Sending": "Verzenden", "Sent": "Verzonden", - "Show": "Tonen", "Show {amount} more replies": "Toon {amount} extra antwoorden", "Show 1 more reply": "Toon nog 1 antwoord", "Show comment": "Toon reactie", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Deze reactie is verborgen.", "This comment has been removed.": "Deze reactie is verwijderd.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Upgrade nu", "View in admin": "", "Yesterday": "Gisteren", diff --git a/ghost/i18n/locales/nn/comments.json b/ghost/i18n/locales/nn/comments.json index f7f32a7c4ac..3144452a843 100644 --- a/ghost/i18n/locales/nn/comments.json +++ b/ghost/i18n/locales/nn/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "", "Head of Marketing at Acme, Inc": "", "Hidden for members": "", - "Hide": "", "Hide comment": "", "Jamie Larson": "", "Join the discussion": "", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "", "One min ago": "", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "", @@ -59,7 +60,6 @@ "Save": "", "Sending": "", "Sent": "", - "Show": "", "Show {amount} more replies": "", "Show 1 more reply": "", "Show comment": "", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "", "This comment has been removed.": "", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "", "View in admin": "", "Yesterday": "", diff --git a/ghost/i18n/locales/pa/comments.json b/ghost/i18n/locales/pa/comments.json index bdbc676a399..04473a99a38 100644 --- a/ghost/i18n/locales/pa/comments.json +++ b/ghost/i18n/locales/pa/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "ਘਰੇਲੂ ਮਾਪਾ", "Head of Marketing at Acme, Inc": "ਮਾਰਕੀਟਿੰਗ ਮੁਖੀ, ਫਲਾਣੀ ਕੰਪਨੀ ਵਿਖੇ", "Hidden for members": "ਮੈਂਬਰਾਂ ਲਈ ਛੁਪਾਇਆ ਗਿਆ", - "Hide": "ਛੁਪਾਓ", "Hide comment": "ਟਿੱਪਣੀ ਛੁਪਾਓ", "Jamie Larson": "ਫਲਾਣਾ ਫਲਾਣੀ", "Join the discussion": "ਵਿਚਾਰ-ਵਟਾਂਦਰੇ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਵੋ", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "ਇੱਕ ਵਾਰ ਮਿਟਾਉਣ ਤੋਂ ਬਾਅਦ, ਇਹ ਟਿੱਪਣੀ ਮੁੜ ਪ੍ਰਾਪਤ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।", "One hour ago": "ਇੱਕ ਘੰਟਾ ਪਹਿਲਾਂ", "One min ago": "ਇੱਕ ਮਿੰਟ ਪਹਿਲਾਂ", + "Pin comment": "", + "Pinned": "", "removed": "ਹਟਾਇਆ ਗਿਆ", "Replied to": "ਨੂੰ ਜਵਾਬ ਦਿੱਤਾ", "Reply": "ਜਵਾਬ", @@ -59,7 +60,6 @@ "Save": "ਸੁਰੱਖਿਅਤ ਕਰੋ", "Sending": "ਭੇਜਿਆ ਜਾ ਰਿਹਾ ਹੈ", "Sent": "ਭੇਜ ਦਿੱਤਾ ਗਿਆ", - "Show": "ਦਿਖਾਓ", "Show {amount} more replies": "{amount} ਹੋਰ ਜਵਾਬ ਦਿਖਾਓ", "Show 1 more reply": "1 ਹੋਰ ਜਵਾਬ ਦਿਖਾਓ", "Show comment": "ਟਿੱਪਣੀ ਦਿਖਾਓ", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "ਇਹ ਟਿੱਪਣੀ ਛੁਪਾ ਦਿੱਤੀ ਗਈ ਹੈ।", "This comment has been removed.": "ਇਹ ਟਿੱਪਣੀ ਹਟਾ ਦਿੱਤੀ ਗਈ ਹੈ।", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "ਹੁਣੇ ਅੱਪਗਰੇਡ ਕਰੋ", "View in admin": "", "Yesterday": "ਕੱਲ੍ਹ", diff --git a/ghost/i18n/locales/pl/comments.json b/ghost/i18n/locales/pl/comments.json index 483c3a3b7e7..fb2da6a44b9 100644 --- a/ghost/i18n/locales/pl/comments.json +++ b/ghost/i18n/locales/pl/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Pełnoetatowy rodzic", "Head of Marketing at Acme, Inc": "Head of Marketing at Acme, Inc", "Hidden for members": "Ukryte dla członków", - "Hide": "Ukryj", "Hide comment": "Ukryj komentarz", "Jamie Larson": "Jamie Larson", "Join the discussion": "Dołącz do dyskusji", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Po usunięciu, nie będzie możliwe przywrócenie tego komentarza", "One hour ago": "Godzinę temu", "One min ago": "Minutę temu", + "Pin comment": "", + "Pinned": "", "removed": "usunięte", "Replied to": "Odpowiedź na", "Reply": "Odpowiedz", @@ -59,7 +60,6 @@ "Save": "Zapisz", "Sending": "Wysyłanie", "Sent": "Wysłano", - "Show": "Pokaż", "Show {amount} more replies": "Pokaż {amount} kolejnych odpowiedzi", "Show 1 more reply": "Pokaż następną odpowiedź", "Show comment": "Pokaż komentarz", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Komentarz został ukryty.", "This comment has been removed.": "Komentarz został usunięty.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Ulepsz teraz", "View in admin": "", "Yesterday": "Wczoraj", diff --git a/ghost/i18n/locales/pt-BR/comments.json b/ghost/i18n/locales/pt-BR/comments.json index 9a6e3297887..0791dcc1349 100644 --- a/ghost/i18n/locales/pt-BR/comments.json +++ b/ghost/i18n/locales/pt-BR/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Pai/Mãe em tempo integral", "Head of Marketing at Acme, Inc": "Chefe de Marketing na Acme, Inc", "Hidden for members": "Oculto para membros", - "Hide": "Ocultar", "Hide comment": "Ocultar comentário", "Jamie Larson": "Cris Silva", "Join the discussion": "Participe da discussão", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Após ser excluído, esse comentário não poderá ser recuperado.", "One hour ago": "Uma hora atrás", "One min ago": "Um minuto atrás", + "Pin comment": "", + "Pinned": "", "removed": "removido", "Replied to": "Respondeu a", "Reply": "Responder", @@ -59,7 +60,6 @@ "Save": "Salvar", "Sending": "Enviando", "Sent": "Enviado", - "Show": "Exibir", "Show {amount} more replies": "Exibir mais {amount} respostas", "Show 1 more reply": "Exibir 1 resposta adicional", "Show comment": "Exibir comentário", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Este comentário foi ocultado.", "This comment has been removed.": "Este comentário foi removido.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Atualize agora", "View in admin": "", "Yesterday": "Ontem", diff --git a/ghost/i18n/locales/pt/comments.json b/ghost/i18n/locales/pt/comments.json index ba0b62781ab..507229b4fa7 100644 --- a/ghost/i18n/locales/pt/comments.json +++ b/ghost/i18n/locales/pt/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Pai a tempo inteiro", "Head of Marketing at Acme, Inc": "Responsável do Marketing @ Amce, Inc", "Hidden for members": "Oculto para membros", - "Hide": "Esconder", "Hide comment": "Esconder comentário", "Jamie Larson": "Jane Doe", "Join the discussion": "Junte-se à discussão", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "uma hora atrás", "One min ago": "um minuto atrás", + "Pin comment": "", + "Pinned": "", "removed": "removido", "Replied to": "Respondeu a", "Reply": "Responder", @@ -59,7 +60,6 @@ "Save": "Guardar", "Sending": "Enviar", "Sent": "Enviado", - "Show": "Mostrar", "Show {amount} more replies": "Mostrar mais {amount} respostas", "Show 1 more reply": "Mostrar mais uma resposta", "Show comment": "Mostrar comentário", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Este comentário foi ocultado", "This comment has been removed.": "Este comentário foi removido", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Atualize agora", "View in admin": "", "Yesterday": "Ontem", diff --git a/ghost/i18n/locales/ro/comments.json b/ghost/i18n/locales/ro/comments.json index ca0d7638716..26f74e0da7f 100644 --- a/ghost/i18n/locales/ro/comments.json +++ b/ghost/i18n/locales/ro/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Părinte cu normă întreagă", "Head of Marketing at Acme, Inc": "Șef de Marketing la Acme, Inc", "Hidden for members": "", - "Hide": "Ascunde", "Hide comment": "Ascunde comentariul", "Jamie Larson": "Jamie Larson", "Join the discussion": "Alătură-te discuției", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "Acum o oră", "One min ago": "Acum un minut", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "Răspunde", @@ -59,7 +60,6 @@ "Save": "Salvează", "Sending": "Se trimite", "Sent": "Trimis", - "Show": "Arată", "Show {amount} more replies": "Arată încă {amount} răspunsuri", "Show 1 more reply": "Arată încă un răspuns", "Show comment": "Arată comentariul", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Acest comentariu a fost ascuns.", "This comment has been removed.": "Acest comentariu a fost eliminat.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Actualizează acum", "View in admin": "", "Yesterday": "Ieri", diff --git a/ghost/i18n/locales/ru/comments.json b/ghost/i18n/locales/ru/comments.json index c0df839304f..1b71aa6772c 100644 --- a/ghost/i18n/locales/ru/comments.json +++ b/ghost/i18n/locales/ru/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Родитель, работающий полный рабочий день", "Head of Marketing at Acme, Inc": "Руководитель отдела маркетинга в Корпорации «Акме»", "Hidden for members": "Скрыто для участников", - "Hide": "Скрыть", "Hide comment": "Скрыть комментарий", "Jamie Larson": "Павел Бид", "Join the discussion": "Присоединиться к обсуждению", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "После удаления этот комментарий не может быть восстановлен.", "One hour ago": "Час назад", "One min ago": "Минуту назад", + "Pin comment": "", + "Pinned": "", "removed": "удалён", "Replied to": "Ответ на", "Reply": "Ответить", @@ -59,7 +60,6 @@ "Save": "Сохранить", "Sending": "Отправка", "Sent": "Отправлено", - "Show": "Показать", "Show {amount} more replies": "Показать ещё {amount} ответа(ов)", "Show 1 more reply": "Показать ответ", "Show comment": "Показать комментарий", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Этот комментарий скрыт", "This comment has been removed.": "Этот комментарий был удалён", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Улучшить подписку", "View in admin": "", "Yesterday": "Вчера", diff --git a/ghost/i18n/locales/si/comments.json b/ghost/i18n/locales/si/comments.json index 2a4a64cd1eb..7a58a3be091 100644 --- a/ghost/i18n/locales/si/comments.json +++ b/ghost/i18n/locales/si/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "පූර්ණ කාලීන භාරකරුව\u200bන්", "Head of Marketing at Acme, Inc": "Acme, Inc හි අලෙවි ප්\u200dරධානී", "Hidden for members": "", - "Hide": "සඟවන්න", "Hide comment": "අදහස සඟවන්න", "Jamie Larson": "ජේමි ලාර්සන්", "Join the discussion": "සාකච්ඡාවට එක්වන්න", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "පැයකට පෙ\u200bර", "One min ago": "මිනිත්තුවකට පෙ\u200bර", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "ප්\u200dරතිචාර දක්වන්\u200bන", @@ -59,7 +60,6 @@ "Save": "සුරකින්\u200bන", "Sending": "යවමි\u200bන්", "Sent": "යැව්වා", - "Show": "පෙන්වන්\u200bන", "Show {amount} more replies": "තවත් පිළිතුරු {amount} පෙන්වන්න", "Show 1 more reply": "තවත් පිළිතුරු 1ක් පෙන්වන්න", "Show comment": "අදහස පෙන්වන්න", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "මෙම අදහස සඟවා ඇත.", "This comment has been removed.": "මෙම අදහස ඉවත් කර ඇත.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "දැන් upgrade කරන්න", "View in admin": "", "Yesterday": "ඊ\u200bයෙ", diff --git a/ghost/i18n/locales/sk/comments.json b/ghost/i18n/locales/sk/comments.json index 119b0b023c4..2284c279571 100644 --- a/ghost/i18n/locales/sk/comments.json +++ b/ghost/i18n/locales/sk/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Rodič na plný úväzok", "Head of Marketing at Acme, Inc": "Vedúci marketingu v spoločnosti Acme, Inc", "Hidden for members": "Skryté pre členov", - "Hide": "Skryť", "Hide comment": "Skryť komentár", "Jamie Larson": "Jamie Larson", "Join the discussion": "Zapojiť sa do diskusie", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Po odstránení nebude možné tento komentár obnoviť.", "One hour ago": "Pred hodinou", "One min ago": "Pred minútou", + "Pin comment": "", + "Pinned": "", "removed": "odstránené", "Replied to": "Odpoveď na", "Reply": "Odpovedať", @@ -59,7 +60,6 @@ "Save": "Uložiť", "Sending": "Odosielanie", "Sent": "Odoslané", - "Show": "Zobraziť", "Show {amount} more replies": "Zobraziť {amount} ďalších odpoveďí", "Show 1 more reply": "Zobraziť 1 ďalšiu odpoveď", "Show comment": "Zobraziť komentár", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Tento komentár bol skrytý.", "This comment has been removed.": "Tento komentár bol vymazaný.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Upraviť členstvo", "View in admin": "", "Yesterday": "Včera", diff --git a/ghost/i18n/locales/sl/comments.json b/ghost/i18n/locales/sl/comments.json index 8ad0a0bcc38..495f82400bd 100644 --- a/ghost/i18n/locales/sl/comments.json +++ b/ghost/i18n/locales/sl/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Starš s polnim delovnim časom", "Head of Marketing at Acme, Inc": "Vodja marketinga pri Acme, Inc", "Hidden for members": "Skrito za člane", - "Hide": "Skrij", "Hide comment": "Skrij komentar", "Jamie Larson": "Janez Novak", "Join the discussion": "Pridružite se razpravi", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Ko je komentar izbrisan, ga ni mogoče več obnoviti.", "One hour ago": "Pred eno uro", "One min ago": "Pred eno minuto", + "Pin comment": "", + "Pinned": "", "removed": "odstranjeno", "Replied to": "Odgovorjeno na", "Reply": "Odgovori", @@ -59,7 +60,6 @@ "Save": "Shrani", "Sending": "Pošiljanje", "Sent": "Poslano", - "Show": "Prikaži", "Show {amount} more replies": "Prikaži še {amount} odgovorov", "Show 1 more reply": "Prikaži še 1 odgovor", "Show comment": "Prikaži komentar", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Ta komentar je bil skrit.", "This comment has been removed.": "Ta komentar je bil odstranjen.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Nadgradite zdaj", "View in admin": "", "Yesterday": "Včeraj", diff --git a/ghost/i18n/locales/sq/comments.json b/ghost/i18n/locales/sq/comments.json index f7f32a7c4ac..3144452a843 100644 --- a/ghost/i18n/locales/sq/comments.json +++ b/ghost/i18n/locales/sq/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "", "Head of Marketing at Acme, Inc": "", "Hidden for members": "", - "Hide": "", "Hide comment": "", "Jamie Larson": "", "Join the discussion": "", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "", "One min ago": "", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "", @@ -59,7 +60,6 @@ "Save": "", "Sending": "", "Sent": "", - "Show": "", "Show {amount} more replies": "", "Show 1 more reply": "", "Show comment": "", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "", "This comment has been removed.": "", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "", "View in admin": "", "Yesterday": "", diff --git a/ghost/i18n/locales/sr-Cyrl/comments.json b/ghost/i18n/locales/sr-Cyrl/comments.json index 50159aa73cf..7d51512cb73 100644 --- a/ghost/i18n/locales/sr-Cyrl/comments.json +++ b/ghost/i18n/locales/sr-Cyrl/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Родитељ пуним радним временом", "Head of Marketing at Acme, Inc": "Шеф маркетинга у Acme, Inc", "Hidden for members": "Сакривено за чланове", - "Hide": "Сакријте", "Hide comment": "Сакријте коментар", "Jamie Larson": "Пера Перић", "Join the discussion": "Придружите се дискусији", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Када се обрише, овај коментар не може бити враћен.", "One hour ago": "Пре сат", "One min ago": "Пре минут", + "Pin comment": "", + "Pinned": "", "removed": "уклоњено", "Replied to": "Одговорено кориснику", "Reply": "Одговорите", @@ -59,7 +60,6 @@ "Save": "Сачувајте", "Sending": "Слање", "Sent": "Послато", - "Show": "Прикажите", "Show {amount} more replies": "Прикажите још {amount} одговора", "Show 1 more reply": "Прикажите још 1 одговор", "Show comment": "Прикажите коментар", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Овај коментар је сакривен.", "This comment has been removed.": "Овај коментар је уклоњен.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Надоградите сада", "View in admin": "Погледајте у админ", "Yesterday": "Јуче", diff --git a/ghost/i18n/locales/sr/comments.json b/ghost/i18n/locales/sr/comments.json index 05118c35ffa..700f01f69d5 100644 --- a/ghost/i18n/locales/sr/comments.json +++ b/ghost/i18n/locales/sr/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Roditelj punim radnim vremenom", "Head of Marketing at Acme, Inc": "Šef marketinga u Acme, Inc", "Hidden for members": "Sakriveno za članove", - "Hide": "Sakrijte", "Hide comment": "Sakrijte komentar", "Jamie Larson": "Pera Perić", "Join the discussion": "Pridružite se diskusiji", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Kada se obriše, ovaj komentar ne može biti vraćen.", "One hour ago": "Pre sat", "One min ago": "Pre minut", + "Pin comment": "", + "Pinned": "", "removed": "uklonjeno", "Replied to": "Odgovoreno korisniku", "Reply": "Odgovorite", @@ -59,7 +60,6 @@ "Save": "Sačuvajte", "Sending": "Slanje", "Sent": "Poslato", - "Show": "Prikažite", "Show {amount} more replies": "Prikažite još {amount} odgovora", "Show 1 more reply": "Prikažite još 1 odgovor", "Show comment": "Prikažite komentar", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Ovaj komentar je sakriven.", "This comment has been removed.": "Ovaj komentar je uklonjen.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Nadogradite sada", "View in admin": "Pogledajte u admin", "Yesterday": "Juče", diff --git a/ghost/i18n/locales/sv/comments.json b/ghost/i18n/locales/sv/comments.json index 352bf899732..5c361031e5b 100644 --- a/ghost/i18n/locales/sv/comments.json +++ b/ghost/i18n/locales/sv/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Hemmaförälder", "Head of Marketing at Acme, Inc": "Marknadsföringschef på Företaget AB", "Hidden for members": "Dolt för medlemmar", - "Hide": "Dölj", "Hide comment": "Dölj kommentar", "Jamie Larson": "Anders Andersson", "Join the discussion": "Var med i diskussionen", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Kommentaren kan ej återskapas efter borttagning", "One hour ago": "En timme sedan", "One min ago": "En minut sedan", + "Pin comment": "", + "Pinned": "", "removed": "borttagen", "Replied to": "Svarade", "Reply": "Svar", @@ -59,7 +60,6 @@ "Save": "Spara", "Sending": "Skickar", "Sent": "Skickad", - "Show": "Visa", "Show {amount} more replies": "Visa {amount} fler svar", "Show 1 more reply": "Visa 1 kommentar till", "Show comment": "Visa kommentar", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Denna kommentar har dolts", "This comment has been removed.": "Denna kommentar är borttagen", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Uppgradera nu", "View in admin": "", "Yesterday": "I går", diff --git a/ghost/i18n/locales/sw/comments.json b/ghost/i18n/locales/sw/comments.json index 0d222d56ab7..20bfe4eb4a1 100644 --- a/ghost/i18n/locales/sw/comments.json +++ b/ghost/i18n/locales/sw/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Mzazi wa muda wote", "Head of Marketing at Acme, Inc": "Mkuu wa Masoko katika Acme, Inc", "Hidden for members": "", - "Hide": "Ficha", "Hide comment": "Ficha maoni", "Jamie Larson": "Jamie Larson", "Join the discussion": "Jiunge na mjadala", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "Saa moja iliyopita", "One min ago": "", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "Jibu", @@ -59,7 +60,6 @@ "Save": "Hifadhi", "Sending": "Inatuma", "Sent": "Imetumwa", - "Show": "Onyesha", "Show {amount} more replies": "Onyesha majibu {amount} zaidi", "Show 1 more reply": "Onyesha jibu 1 zaidi", "Show comment": "Onyesha maoni", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Maoni haya yamefichwa.", "This comment has been removed.": "Maoni haya yameondolewa.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Boresha sasa", "View in admin": "", "Yesterday": "Jana", diff --git a/ghost/i18n/locales/ta/comments.json b/ghost/i18n/locales/ta/comments.json index b0c5d1821c3..600157bf3e8 100644 --- a/ghost/i18n/locales/ta/comments.json +++ b/ghost/i18n/locales/ta/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "முழுநேர பெற்றோர்", "Head of Marketing at Acme, Inc": "Acme Inc நிறுவனத்தின் சந்தைப்படுத்தல் தலைவர்", "Hidden for members": "", - "Hide": "மறைக்கவும்", "Hide comment": "கருத்தை மறை", "Jamie Larson": "ஜேமி லார்சன்", "Join the discussion": "கலந்துரையாடலில் சேரவும்", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "ஒரு மணி முன்பு", "One min ago": "ஒரு நிமிடம் முன்பு", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "பதிலளிக்கவும்", @@ -59,7 +60,6 @@ "Save": "சேமிக்க", "Sending": "அனுப்புகிறது", "Sent": "அனுப்பப்பட்டது", - "Show": "காட்டு", "Show {amount} more replies": "முந்தைய {amount} மேலும் பதில்கள்க் காட்டு", "Show 1 more reply": "மேலும் 1 பதிலைக் காட்டு", "Show comment": "கருத்தைக் காட்டு", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "இந்தக் கருத்து மறைக்கப்பட்டது.", "This comment has been removed.": "இந்தக் கருத்து நீக்கப்பட்டது.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "இப்போதே மேம்படுத்தவும்", "View in admin": "", "Yesterday": "நேற்று", diff --git a/ghost/i18n/locales/th/comments.json b/ghost/i18n/locales/th/comments.json index 9dfe7d34706..fb97b786898 100644 --- a/ghost/i18n/locales/th/comments.json +++ b/ghost/i18n/locales/th/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "ผู้ปกครองเต็มเวลา", "Head of Marketing at Acme, Inc": "หัวหน้าฝ่ายการตลาด ณ Acme, Inc", "Hidden for members": "", - "Hide": "ซ่อน", "Hide comment": "ซ่อนข้อความ", "Jamie Larson": "กนกพัฒน์ สัณห์ฤทัย", "Join the discussion": "เข้าร่วมการสนทนา", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "1 ชั่วโมงที่แล้ว", "One min ago": "", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "ตอบกลับ", @@ -59,7 +60,6 @@ "Save": "บันทึก", "Sending": "กำลังส่ง", "Sent": "ส่งแล้ว", - "Show": "แสดง", "Show {amount} more replies": "แสดง {amount} การตอบกลับเพิ่มเติม", "Show 1 more reply": "แสดงอีก 1 การตอบกลับ", "Show comment": "ดูความคิดเห็น", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "ความคิดเห็นนี้ถูกซ่อน", "This comment has been removed.": "ความคิดเห็นนี้ถูกลบ", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "อัพเกรดตอนนี้เลย", "View in admin": "", "Yesterday": "เมื่อวาน", diff --git a/ghost/i18n/locales/tr/comments.json b/ghost/i18n/locales/tr/comments.json index c1c2d8270f1..060121bf9e4 100644 --- a/ghost/i18n/locales/tr/comments.json +++ b/ghost/i18n/locales/tr/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Tam zamanlı ebeveyn", "Head of Marketing at Acme, Inc": "Firma, Ünvan'da Pazarlama Müdürü", "Hidden for members": "Üyeler için gizli", - "Hide": "Gizle", "Hide comment": "Yorumu gizle", "Jamie Larson": "Ahmet Yılmaz", "Join the discussion": "Tartışmaya katıl", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Bu yorum silindikten sonra kurtarılamaz.", "One hour ago": "Bir saat önce", "One min ago": "Bir dakika önce", + "Pin comment": "", + "Pinned": "", "removed": "kaldırıldı", "Replied to": "Cevaplandı", "Reply": "Cevapla", @@ -59,7 +60,6 @@ "Save": "Kaydet", "Sending": "Gönderiliyor", "Sent": "Gönderildi", - "Show": "Göster", "Show {amount} more replies": "{amount} daha fazla yanıt göster", "Show 1 more reply": "1 cevap daha göster", "Show comment": "Yorumu göster", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Bu yorum gizlendi.", "This comment has been removed.": "Bu yorum kaldırıldı.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Şimdi yükselt", "View in admin": "", "Yesterday": "Dün", diff --git a/ghost/i18n/locales/uk/comments.json b/ghost/i18n/locales/uk/comments.json index 3a767b25533..03e3718a73e 100644 --- a/ghost/i18n/locales/uk/comments.json +++ b/ghost/i18n/locales/uk/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Виховую дітей", "Head of Marketing at Acme, Inc": "Голова продажів в Acme, Inc", "Hidden for members": "", - "Hide": "Сховати", "Hide comment": "Сховати коментар", "Jamie Larson": "Ваше імʼя", "Join the discussion": "Долучитися до обговорення", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "Одну годину тому", "One min ago": "Одну хвилину тому", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "Відповісти", @@ -59,7 +60,6 @@ "Save": "Зберегти", "Sending": "Відправка", "Sent": "Відправлено", - "Show": "Показати", "Show {amount} more replies": "Показати ще {amount} відповідей", "Show 1 more reply": "Показати ще одну відповідь", "Show comment": "Показати коментар", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Цей коментар було приховано.", "This comment has been removed.": "Цей коментар було видалено.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Оновити зараз", "View in admin": "", "Yesterday": "Вчора", diff --git a/ghost/i18n/locales/ur/comments.json b/ghost/i18n/locales/ur/comments.json index d373dff00d2..740b5eb2f2a 100644 --- a/ghost/i18n/locales/ur/comments.json +++ b/ghost/i18n/locales/ur/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "پورے وقت کا والد یا والدہ", "Head of Marketing at Acme, Inc": "Acme، Inc کے مارکیٹنگ کا سربراہ", "Hidden for members": "", - "Hide": "چھپائیں", "Hide comment": "تبادلہ چھپائیں", "Jamie Larson": "جیمی لارسن", "Join the discussion": "تبادلے میں شامل ہوں", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "ایک گھنٹہ پہلے", "One min ago": "", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "جواب", @@ -59,7 +60,6 @@ "Save": "محفوظ کریں", "Sending": "بھیج رہا ہے", "Sent": "بھیجا گیا", - "Show": "دکھائیں", "Show {amount} more replies": "{amount} مزید جوابات دکھائیں", "Show 1 more reply": "1 مزید جواب دکھائیں", "Show comment": "تبادلہ دکھائیں", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "یہ تبادلہ چھپا گیا ہے۔", "This comment has been removed.": "یہ تبادلہ ہٹا دیا گیا ہے۔", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "اب اپ گریڈ کریں", "View in admin": "", "Yesterday": "کل", diff --git a/ghost/i18n/locales/uz/comments.json b/ghost/i18n/locales/uz/comments.json index f7f32a7c4ac..3144452a843 100644 --- a/ghost/i18n/locales/uz/comments.json +++ b/ghost/i18n/locales/uz/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "", "Head of Marketing at Acme, Inc": "", "Hidden for members": "", - "Hide": "", "Hide comment": "", "Jamie Larson": "", "Join the discussion": "", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "", "One hour ago": "", "One min ago": "", + "Pin comment": "", + "Pinned": "", "removed": "", "Replied to": "", "Reply": "", @@ -59,7 +60,6 @@ "Save": "", "Sending": "", "Sent": "", - "Show": "", "Show {amount} more replies": "", "Show 1 more reply": "", "Show comment": "", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "", "This comment has been removed.": "", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "", "View in admin": "", "Yesterday": "", diff --git a/ghost/i18n/locales/vi/comments.json b/ghost/i18n/locales/vi/comments.json index 058d7744db8..12d1fc8b36d 100644 --- a/ghost/i18n/locales/vi/comments.json +++ b/ghost/i18n/locales/vi/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "Phụ huynh toàn thời gian", "Head of Marketing at Acme, Inc": "Trưởng phòng Marketing tại Acme, Inc", "Hidden for members": "Ẩn với thành viên", - "Hide": "Ẩn", "Hide comment": "Ẩn bình luận", "Jamie Larson": "Nguyễn Minh Châu", "Join the discussion": "Tham gia thảo luận", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "Một khi đã xóa, bình luận này không thể khôi phục.", "One hour ago": "Một giờ trước", "One min ago": "Một phút trước", + "Pin comment": "", + "Pinned": "", "removed": "đã xóa", "Replied to": "Đã trả lời", "Reply": "Trả lời", @@ -59,7 +60,6 @@ "Save": "Lưu", "Sending": "Đang gửi", "Sent": "Đã gửi", - "Show": "Hiển thị", "Show {amount} more replies": "Xem thêm {amount} trả lời khác", "Show 1 more reply": "Xem thêm 1 trả lời", "Show comment": "Xem bình luận", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "Bình luận này đã bị ẩn.", "This comment has been removed.": "Bình luận này đã bị xóa.", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "Nâng cấp", "View in admin": "", "Yesterday": "Hôm qua", diff --git a/ghost/i18n/locales/zh-Hant/comments.json b/ghost/i18n/locales/zh-Hant/comments.json index 6ed7db40fae..86ea1e63025 100644 --- a/ghost/i18n/locales/zh-Hant/comments.json +++ b/ghost/i18n/locales/zh-Hant/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "全職父母", "Head of Marketing at Acme, Inc": "Acme, Inc市場部負責人", "Hidden for members": "對會員隱藏", - "Hide": "隱藏", "Hide comment": "隱藏留言", "Jamie Larson": "阿宇", "Join the discussion": "加入討論", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "此留言一經刪除後將無法復原。", "One hour ago": "一小時前", "One min ago": "一分鐘前", + "Pin comment": "", + "Pinned": "", "removed": "已移除", "Replied to": "已回覆", "Reply": "回覆", @@ -59,7 +60,6 @@ "Save": "儲存", "Sending": "正在發送", "Sent": "已發送", - "Show": "顯示", "Show {amount} more replies": "顯示後續 {amount} 則留言", "Show 1 more reply": "顯示下一則留言", "Show comment": "顯示留言", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "連結的留言已不存在。", "This comment has been hidden.": "該則留言已被隱藏。", "This comment has been removed.": "該則留言已被移除。", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "立即升級", "View in admin": "從管理後台檢視", "Yesterday": "昨天", diff --git a/ghost/i18n/locales/zh/comments.json b/ghost/i18n/locales/zh/comments.json index 1556faa55ab..cbcbdf477e6 100644 --- a/ghost/i18n/locales/zh/comments.json +++ b/ghost/i18n/locales/zh/comments.json @@ -33,7 +33,6 @@ "Full-time parent": "全职父母", "Head of Marketing at Acme, Inc": "Acme, Inc市场部负责人", "Hidden for members": "对会员隐藏", - "Hide": "隐藏", "Hide comment": "隐藏评论", "Jamie Larson": "阿宇", "Join the discussion": "加入讨论", @@ -48,6 +47,8 @@ "Once deleted, this comment can’t be recovered.": "此评论一经删除后将无法复原。", "One hour ago": "一小时前", "One min ago": "一分钟前", + "Pin comment": "", + "Pinned": "", "removed": "已移除", "Replied to": "已回复", "Reply": "回复", @@ -59,7 +60,6 @@ "Save": "保存", "Sending": "正在发送", "Sent": "已发送", - "Show": "显示", "Show {amount} more replies": "显示更多{amount}条评论", "Show 1 more reply": "显示下一条评论", "Show comment": "显示评论", @@ -70,6 +70,8 @@ "The linked comment is no longer available.": "", "This comment has been hidden.": "该条评论已被隐藏。", "This comment has been removed.": "该条评论已被移除。", + "Unpin": "", + "Unpin comment": "", "Upgrade now": "立刻升级", "View in admin": "", "Yesterday": "昨天",