Skip to content

Commit ebf4bb7

Browse files
🐛 Added explanatory message when linking to hidden/deleted comments (#26390)
closes https://linear.app/ghost/issue/BER-3324/ - scroll the page to the comments area when a permalink targets ahidden/deleted comment - show a notice: "The linked comment is no longer available."
1 parent e046b1c commit ebf4bb7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+120
-18
lines changed

apps/comments-ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tryghost/comments-ui",
3-
"version": "1.3.4",
3+
"version": "1.3.6",
44
"license": "MIT",
55
"repository": "https://github.com/TryGhost/Ghost",
66
"author": "Ghost Foundation",

apps/comments-ui/src/app-context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export type EditableAppContext = {
8787
commentsIsLoading?: boolean,
8888
commentIdToHighlight: string | null,
8989
commentIdToScrollTo: string | null,
90+
showMissingCommentNotice: boolean,
9091
pageUrl: string,
9192
supportEmail: string | null,
9293
isMember: boolean,
@@ -129,4 +130,3 @@ export const useLabs = () => {
129130
return {};
130131
}
131132
};
132-

apps/comments-ui/src/app.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const App: React.FC<AppProps> = ({scriptTag, initialCommentId, pageUrl}) => {
4444
commentsIsLoading: false,
4545
commentIdToHighlight: null,
4646
commentIdToScrollTo: initialCommentId,
47+
showMissingCommentNotice: false,
4748
pageUrl,
4849
supportEmail: null,
4950
isMember: false,
@@ -332,6 +333,7 @@ const App: React.FC<AppProps> = ({scriptTag, initialCommentId, pageUrl}) => {
332333
commentsIsLoading: false,
333334
commentIdToHighlight: null,
334335
commentIdToScrollTo: scrollTargetFound ? initialCommentId : null,
336+
showMissingCommentNotice: !!initialCommentId && !scrollTargetFound,
335337
supportEmail,
336338
isMember,
337339
isPaidOnly,

apps/comments-ui/src/components/content/content.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import MainForm from './forms/main-form';
66
import Pagination from './pagination';
77
import {ROOT_DIV_ID} from '../../utils/constants';
88
import {SortingForm} from './forms/sorting-form';
9-
import {parseCommentIdFromHash} from '../../utils/helpers';
9+
import {parseCommentIdFromHash, scrollToElement} from '../../utils/helpers';
1010
import {useAppContext, useLabs} from '../../app-context';
1111
import {useCallback, useEffect, useRef} from 'react';
1212

@@ -73,7 +73,7 @@ function onIframeResize(
7373

7474
const Content = () => {
7575
const labs = useLabs();
76-
const {pagination, comments, commentCount, title, showCount, commentsIsLoading, t, dispatchAction, commentIdToScrollTo, isMember, isPaidOnly, hasRequiredTier, isCommentingDisabled} = useAppContext();
76+
const {pagination, comments, commentCount, title, showCount, commentsIsLoading, t, dispatchAction, commentIdToScrollTo, showMissingCommentNotice, isMember, isPaidOnly, hasRequiredTier, isCommentingDisabled} = useAppContext();
7777
const containerRef = useRef<HTMLDivElement>(null);
7878

7979
const scrollToComment = useCallback((element: HTMLElement, commentId: string) => {
@@ -137,6 +137,26 @@ const Content = () => {
137137
scrollToComment(element, commentIdToScrollTo);
138138
}, [commentIdToScrollTo, commentsIsLoading, comments, scrollToComment]);
139139

140+
useEffect(() => {
141+
if (!showMissingCommentNotice || commentsIsLoading) {
142+
return;
143+
}
144+
145+
const root = document.getElementById(ROOT_DIV_ID);
146+
if (!root) {
147+
return;
148+
}
149+
150+
const iframe = findContainingIframe(root.ownerDocument);
151+
if (iframe) {
152+
return onIframeResize(iframe, () => {
153+
scrollToElement(root);
154+
});
155+
}
156+
157+
scrollToElement(root);
158+
}, [showMissingCommentNotice, commentsIsLoading]);
159+
140160
const isFirst = pagination?.total === 0;
141161
const canComment = isMember && hasRequiredTier && !isCommentingDisabled;
142162

@@ -150,6 +170,11 @@ const Content = () => {
150170
return (
151171
<>
152172
<ContentTitle count={commentCount} showCount={showCount} title={title}/>
173+
{showMissingCommentNotice && (
174+
<div className="mb-6 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 font-sans text-sm text-amber-900 dark:border-amber-500/40 dark:bg-amber-500/15 dark:text-amber-100" data-testid="missing-comment-notice">
175+
{t('The linked comment is no longer available.')}
176+
</div>
177+
)}
153178
<div>
154179
{showMainForm && <MainForm commentsCount={comments.length} />}
155180
{showDisabledBox && (

apps/comments-ui/test/e2e/permalink.test.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ async function setupPermalinkTest(
4646
return page.frameLocator('iframe[title="comments-frame"]');
4747
}
4848

49+
const TALL_BODY_HTML = '<html><head><meta charset="UTF-8" /></head><body><div style="width: 100%; height: 1500px;"></div></body></html>';
50+
4951
test.describe('Comment Permalinks', async () => {
5052
test('timestamp is a link with correct permalink href', async ({page}) => {
5153
const mockedApi = new MockedApi({});
@@ -100,8 +102,7 @@ test.describe('Comment Permalinks', async () => {
100102
});
101103

102104
// Include a tall div to push comments below viewport (like lazy-loading test)
103-
const tallBodyHtml = '<html><head><meta charset="UTF-8" /></head><body><div style="width: 100%; height: 1500px;"></div></body></html>';
104-
const commentsFrame = await setupPermalinkTest(page, mockedApi, `#ghost-comments-${commentId}`, tallBodyHtml);
105+
const commentsFrame = await setupPermalinkTest(page, mockedApi, `#ghost-comments-${commentId}`, TALL_BODY_HTML);
105106

106107
await page.locator('iframe[title="comments-frame"]').waitFor({state: 'attached'});
107108

@@ -161,12 +162,16 @@ test.describe('Comment Permalinks', async () => {
161162
html: '<p>Regular comment</p>'
162163
});
163164

164-
const commentsFrame = await setupPermalinkTest(page, mockedApi, '#ghost-comments-nonexistent123');
165+
const commentsFrame = await setupPermalinkTest(page, mockedApi, '#ghost-comments-dead00000000000000000000', TALL_BODY_HTML);
165166

166167
// Comments should still load and display normally even with invalid ID
167168
await expect(commentsFrame.getByText('Regular comment')).toBeVisible();
168169

169-
// No errors should be thrown - page should function normally
170+
// A permalink to a non-existent comment should show a notice and scroll to comments
171+
await expect(commentsFrame.getByTestId('missing-comment-notice')).toContainText('The linked comment is no longer available.');
172+
await expect.poll(() => page.evaluate(() => window.scrollY)).toBeGreaterThan(0);
173+
174+
// Page should still function normally
170175
const comments = commentsFrame.getByTestId('comment-component');
171176
await expect(comments).toHaveCount(1);
172177
});
@@ -180,8 +185,7 @@ test.describe('Comment Permalinks', async () => {
180185
});
181186

182187
// Include a tall div to push comments below viewport
183-
const tallBodyHtml = '<html><head><meta charset="UTF-8" /></head><body><div style="width: 100%; height: 1500px;"></div></body></html>';
184-
const commentsFrame = await setupPermalinkTest(page, mockedApi, '#some-other-hash', tallBodyHtml);
188+
const commentsFrame = await setupPermalinkTest(page, mockedApi, '#some-other-hash', TALL_BODY_HTML);
185189

186190
await page.locator('iframe[title="comments-frame"]').waitFor({state: 'attached'});
187191

@@ -190,7 +194,7 @@ test.describe('Comment Permalinks', async () => {
190194
await expect(commentsFrame.getByTestId('loading')).toHaveCount(1);
191195
});
192196

193-
test('does not scroll to hidden comment', async ({page}) => {
197+
test('shows missing-comment notice for hidden permalink and scrolls to comments area', async ({page}) => {
194198
const mockedApi = new MockedApi({});
195199
mockedApi.setMember({});
196200

@@ -207,22 +211,26 @@ test.describe('Comment Permalinks', async () => {
207211
status: 'hidden'
208212
});
209213

210-
const commentsFrame = await setupPermalinkTest(page, mockedApi, `#ghost-comments-${hiddenCommentId}`);
214+
const commentsFrame = await setupPermalinkTest(page, mockedApi, `#ghost-comments-${hiddenCommentId}`, TALL_BODY_HTML);
211215

212216
// The visible comment should be displayed
213217
await expect(commentsFrame.getByText('Visible comment')).toBeVisible();
214218

215-
// The hidden comment element should not be scrolled to or highlighted
219+
// The hidden comment element should not be rendered
216220
// (hidden comments are not visible to regular members)
217221
const hiddenCommentElement = commentsFrame.locator(`[id="${hiddenCommentId}"]`);
218222
await expect(hiddenCommentElement).toHaveCount(0);
219223

220-
// Page should function normally with no errors
224+
// A permalink to a non-available comment should show a notice and scroll to comments
225+
await expect(commentsFrame.getByTestId('missing-comment-notice')).toContainText('The linked comment is no longer available.');
226+
await expect.poll(() => page.evaluate(() => window.scrollY)).toBeGreaterThan(0);
227+
228+
// Page should still function normally
221229
const comments = commentsFrame.getByTestId('comment-component');
222230
await expect(comments).toHaveCount(1);
223231
});
224232

225-
test('does not scroll to deleted comment', async ({page}) => {
233+
test('shows missing-comment notice for deleted permalink and scrolls to comments area', async ({page}) => {
226234
const mockedApi = new MockedApi({});
227235
mockedApi.setMember({});
228236

@@ -239,16 +247,20 @@ test.describe('Comment Permalinks', async () => {
239247
status: 'deleted'
240248
});
241249

242-
const commentsFrame = await setupPermalinkTest(page, mockedApi, `#ghost-comments-${deletedCommentId}`);
250+
const commentsFrame = await setupPermalinkTest(page, mockedApi, `#ghost-comments-${deletedCommentId}`, TALL_BODY_HTML);
243251

244252
// The visible comment should be displayed
245253
await expect(commentsFrame.getByText('Visible comment')).toBeVisible();
246254

247-
// The deleted comment element should not be scrolled to or highlighted
255+
// The deleted comment element should not be rendered
248256
const deletedCommentElement = commentsFrame.locator(`[id="${deletedCommentId}"]`);
249257
await expect(deletedCommentElement).toHaveCount(0);
250258

251-
// Page should function normally with no errors
259+
// A permalink to a non-available comment should show a notice and scroll to comments
260+
await expect(commentsFrame.getByTestId('missing-comment-notice')).toContainText('The linked comment is no longer available.');
261+
await expect.poll(() => page.evaluate(() => window.scrollY)).toBeGreaterThan(0);
262+
263+
// Page should still function normally
252264
const comments = commentsFrame.getByTestId('comment-component');
253265
await expect(comments).toHaveCount(1);
254266
});

ghost/i18n/locales/af/comments.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"Sign up now": "Sluit nou aan",
7070
"Sort by": "",
7171
"Start the conversation": "Begin die gesprek",
72+
"The linked comment is no longer available.": "",
7273
"This comment has been hidden.": "Hierdie kommentaar is weggesteek.",
7374
"This comment has been removed.": "Hierdie kommentaar is verwyder.",
7475
"Upgrade now": "Gradeer nou op",

ghost/i18n/locales/ar/comments.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"Sign up now": "قم بالتسجيل الآن",
7070
"Sort by": "ترتيب بحسب",
7171
"Start the conversation": "ابدأ النقاش",
72+
"The linked comment is no longer available.": "",
7273
"This comment has been hidden.": "تم اخفاء هذا التعليق",
7374
"This comment has been removed.": "تم حذف هذا التعليق",
7475
"Upgrade now": "قم بالترقية الآن",

ghost/i18n/locales/bg/comments.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"Sign up now": "Регистрация",
7070
"Sort by": "Сортиране по",
7171
"Start the conversation": "Започнете дискусия",
72+
"The linked comment is no longer available.": "",
7273
"This comment has been hidden.": "Този коментар е скрит.",
7374
"This comment has been removed.": "Този коментар е премахнат.",
7475
"Upgrade now": "Надградете сега",

ghost/i18n/locales/bn/comments.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"Sign up now": "এখনই সাইন আপ করুন",
7070
"Sort by": "",
7171
"Start the conversation": "আলোচনা শুরু করুন",
72+
"The linked comment is no longer available.": "",
7273
"This comment has been hidden.": "এই মন্তব্য লুকানো হয়েছে।",
7374
"This comment has been removed.": "এই মন্তব্য মুছে ফেলা হয়েছে।",
7475
"Upgrade now": "এখনই আপগ্রেড করুন",

ghost/i18n/locales/bs/comments.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"Sign up now": "Postani član",
7070
"Sort by": "",
7171
"Start the conversation": "Započni diskusiju",
72+
"The linked comment is no longer available.": "",
7273
"This comment has been hidden.": "Ovaj komentar je sakriven.",
7374
"This comment has been removed.": "Ovaj komentar je uklonjen.",
7475
"Upgrade now": "Nadogradi račun sada",

0 commit comments

Comments
 (0)