Skip to content

Conversation

amyanger
Copy link

@amyanger amyanger commented Aug 4, 2025

Why are you making it?

Fixes #21999

When tags contain accented characters (e.g., "sécurité incendie") or spaces, Ghost's pagination links in {{ghost_head}} metadata contain
unencoded special characters. This can confuse search engine crawlers who may attempt to crawl invalid URL variations, potentially affecting
SEO indexing and site discoverability.

What does it do?

  • Modified getPaginatedUrl function in core/frontend/meta/paginated-url.js to apply encodeURIComponent() to each URL segment
  • Ensures proper URL encoding for special characters in pagination links:
    • "sécurité incendie""s%C3%A9curit%C3%A9%20incendie"
    • "hello world""hello%20world"
    • "café & résumé!""caf%C3%A9%20%26%20r%C3%A9sum%C3%A9!"
  • Added comprehensive test coverage for various special character scenarios

Why is this something Ghost users or developers need?

  • SEO Impact: Search engines can now properly crawl pagination links for tags with special characters, improving indexing
  • International Support: Better support for non-English tag names and international characters
  • Standards Compliance: Ensures URLs in metadata follow proper URL encoding standards (RFC 3986)
  • User Experience: Eliminates broken pagination links when users create tags with accented characters or spaces

amyanger and others added 2 commits August 2, 2025 21:31
Fixes TryGhost#24588

When importing HTML content with CTA cards wrapped in gated blocks via the Admin API
using `{source: 'html'}`, the visibility settings were being lost during HTML-to-Lexical
conversion. This caused all CTA visibility toggles to show as "on" (visible to everyone)
instead of preserving the intended restrictions like "free members only".

Root cause: The `@tryghost/kg-html-to-lexical` converter strips HTML comments where
gated block visibility settings are stored (e.g., `<\!--kg-gated-block:begin
nonMember:false memberSegment:"status:free"-->`).

Solution:
- Created `gated-block-visibility.js` utility to extract visibility settings from
  gated block comments before HTML-to-Lexical conversion
- Modified posts and pages serializers to preserve visibility during conversion
- Added comprehensive tests covering various scenarios

The fix ensures that CTA cards (and other supported cards) maintain their visibility
restrictions when imported via Admin API, resolving the issue where users would see
incorrect "visible to everyone" settings instead of their intended member segment
targeting.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
refs TryGhost#21999

When tags contained accented characters (e.g., "sécurité incendie") or spaces,
Ghost's pagination links in {{ghost_head}} metadata contained unencoded special
characters. This could confuse search engine crawlers and lead to invalid URL
variations being attempted, affecting SEO indexing.

The getPaginatedUrl function was using the raw data.relativeUrl from the request
path without properly encoding special characters when constructing pagination URLs.
Copy link
Contributor

coderabbitai bot commented Aug 4, 2025

Walkthrough

The changes introduce enhancements and new utilities for handling gated block visibility during HTML to Lexical document conversion, and improve URL encoding for paginated URLs containing special or accented characters. Specifically, the getPaginatedUrl function now encodes each segment of the base URL to handle accents and spaces. A new utility, preserveGatedBlockVisibility, is added to maintain visibility settings from HTML gated blocks when importing content. Corresponding integration and unit tests are introduced to verify these behaviors, covering scenarios for both CTA card visibility and correct URL encoding.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~18 minutes

Assessment against linked issues

Objective Addressed Explanation
Ensure pagination URLs in metadata (e.g., <link rel="prev" href and "next") are correctly URL-encoded for accents and spaces in tag slugs (#21999)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Addition of gated block visibility preservation logic and related tests (ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js, test files) These changes introduce new logic and tests for gated block visibility, which are unrelated to URL encoding for tag slugs as described in the linked issue.

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f880de5 and 2fbb58b.

📒 Files selected for processing (4)
  • ghost/core/core/frontend/helpers/get.js (1 hunks)
  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js (1 hunks)
  • ghost/core/core/server/web/shared/middleware/normalize-urls.js (1 hunks)
  • ghost/core/test/unit/frontend/helpers/get.test.js (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • ghost/core/core/frontend/helpers/get.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js
  • ghost/core/core/server/web/shared/middleware/normalize-urls.js
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: cathysarisky
PR: TryGhost/Ghost#21982
File: ghost/i18n/locales/hu v2/portal.json:70-70
Timestamp: 2025-01-29T15:23:58.658Z
Learning: Typos and spelling errors within localization files (ghost/i18n/locales/*.json) must be treated as important problems, not nitpicks, as they directly affect the user experience for speakers of that language. These issues should be flagged with high priority during code review.
Learnt from: kevinansfield
PR: TryGhost/Ghost#22101
File: ghost/core/core/server/api/endpoints/utils/serializers/output/utils/post-gating.js:110-117
Timestamp: 2025-02-03T17:14:37.367Z
Learning: The potential issues with gated content blocks (lack of migration path, race conditions during flag transitions, content visibility when disabled) are accepted trade-offs during the early testing phase of the contentVisibility labs feature.
Learnt from: kevinansfield
PR: TryGhost/Ghost#23770
File: apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModalLabs.tsx:435-450
Timestamp: 2025-06-10T11:07:10.800Z
Learning: In Ghost's newsletter customization features, when promoting a feature from alpha to beta status, the feature flag guards are updated to make the feature available under both the `emailCustomization` (beta) and `emailCustomizationAlpha` (alpha) flags. This is done by either removing conditional guards entirely when the component is already behind both flags, or by updating conditionals to check for either flag.
📚 Learning: the pending activity utilities in the ghost activitypub module are covered by tests in the file `app...
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in the Ghost ActivityPub module are covered by tests in the file `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`.

Applied to files:

  • ghost/core/test/unit/frontend/helpers/get.test.js
📚 Learning: the pending activity utilities in ghost's activitypub module are thoroughly tested in `apps/admin-x-...
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in Ghost's ActivityPub module are thoroughly tested in `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`, which covers `generatePendingActivityId`, `isPendingActivity`, and `generatePendingActivity` functions.

Applied to files:

  • ghost/core/test/unit/frontend/helpers/get.test.js
📚 Learning: in ghost project, app.js files under core/server/web are intentionally excluded from unit test cover...
Learnt from: ErisDS
PR: TryGhost/Ghost#23582
File: ghost/core/.c8rc.json:24-24
Timestamp: 2025-05-29T07:45:35.714Z
Learning: In Ghost project, app.js files under core/server/web are intentionally excluded from unit test coverage because they are not easily unit-testable due to being entry points with initialization code and side effects.

Applied to files:

  • ghost/core/test/unit/frontend/helpers/get.test.js
📚 Learning: in the ghost test framework, when testing csv exports via the admin api, the response `body` field i...
Learnt from: ErisDS
PR: TryGhost/Ghost#23588
File: ghost/core/test/e2e-api/admin/backup.test.js:136-148
Timestamp: 2025-05-29T10:37:26.369Z
Learning: In the Ghost test framework, when testing CSV exports via the admin API, the response `body` field is empty while the actual CSV data is provided through the `text` field. Tests should use `expectEmptyBody()` and then validate the CSV content via `.expect(({text}) => ...)` - this is not contradictory behavior.

Applied to files:

  • ghost/core/test/unit/frontend/helpers/get.test.js
🔇 Additional comments (2)
ghost/core/test/unit/frontend/helpers/get.test.js (2)

168-205: LGTM! Well-structured test for pages resource.

The test suite follows Ghost's testing conventions and comprehensively verifies that the show_title_and_feature_image property is preserved when using the get helper with the "pages" resource. The mock data structure is realistic and the assertions are thorough.


207-244: LGTM! Focused test for posts property preservation.

The test suite effectively verifies that the show_title_and_feature_image property is preserved when using the get helper with the "posts" resource. While there's already a posts test suite in the file (lines 81-108), this new test has a specific focus on property preservation which complements the existing context preparation tests.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
ghost/core/core/frontend/meta/paginated-url.js (1)

20-41: LGTM! Fix ESLint style issue.

The URL encoding implementation correctly addresses the pagination link issue by encoding each path segment individually. This approach properly handles accented characters and spaces while preserving URL structure.

However, there's a minor ESLint style issue to fix:

-        const encodedSegments = segments.map(segment => {
+        const encodedSegments = segments.map((segment) => {
ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js (2)

14-55: LGTM with minor performance consideration.

The function logic is well-structured with proper input validation and clear separation of concerns. The regex handling with lastIndex reset is correct.

Consider the performance implications of deep cloning large Lexical documents using JSON.parse(JSON.stringify()). For very large documents, this could be expensive. However, this is likely acceptable for the current use case.


94-113: Fix ESLint arrow-parens violation.

The recursive traversal logic is correct and properly structured.

Address the ESLint arrow-parens error on line 109:

-        node.children.forEach(child => {
+        node.children.forEach((child) => {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47ff081 and 1d3f2a3.

📒 Files selected for processing (7)
  • ghost/core/core/frontend/meta/paginated-url.js (1 hunks)
  • ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js (2 hunks)
  • ghost/core/core/server/api/endpoints/utils/serializers/input/posts.js (2 hunks)
  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js (1 hunks)
  • ghost/core/test/integration/api/cta-visibility-import.test.js (1 hunks)
  • ghost/core/test/unit/frontend/meta/paginated-url.test.js (1 hunks)
  • ghost/core/test/unit/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.test.js (1 hunks)
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: cathysarisky
PR: TryGhost/Ghost#21982
File: ghost/i18n/locales/hu v2/portal.json:70-70
Timestamp: 2025-01-29T15:23:58.658Z
Learning: Typos and spelling errors within localization files (ghost/i18n/locales/*.json) must be treated as important problems, not nitpicks, as they directly affect the user experience for speakers of that language. These issues should be flagged with high priority during code review.
Learnt from: kevinansfield
PR: TryGhost/Ghost#22101
File: ghost/core/core/server/api/endpoints/utils/serializers/output/utils/post-gating.js:110-117
Timestamp: 2025-02-03T17:14:37.367Z
Learning: The potential issues with gated content blocks (lack of migration path, race conditions during flag transitions, content visibility when disabled) are accepted trade-offs during the early testing phase of the contentVisibility labs feature.
Learnt from: kevinansfield
PR: TryGhost/Ghost#23770
File: apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModalLabs.tsx:435-450
Timestamp: 2025-06-10T11:07:10.800Z
Learning: In Ghost's newsletter customization features, when promoting a feature from alpha to beta status, the feature flag guards are updated to make the feature available under both the `emailCustomization` (beta) and `emailCustomizationAlpha` (alpha) flags. This is done by either removing conditional guards entirely when the component is already behind both flags, or by updating conditionals to check for either flag.
📚 Learning: the pending activity utilities in the ghost activitypub module are covered by tests in the file `app...
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in the Ghost ActivityPub module are covered by tests in the file `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`.

Applied to files:

  • ghost/core/test/unit/frontend/meta/paginated-url.test.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js
  • ghost/core/test/integration/api/cta-visibility-import.test.js
  • ghost/core/test/unit/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.test.js
📚 Learning: the pending activity utilities in ghost's activitypub module are thoroughly tested in `apps/admin-x-...
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in Ghost's ActivityPub module are thoroughly tested in `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`, which covers `generatePendingActivityId`, `isPendingActivity`, and `generatePendingActivity` functions.

Applied to files:

  • ghost/core/test/unit/frontend/meta/paginated-url.test.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js
  • ghost/core/test/integration/api/cta-visibility-import.test.js
  • ghost/core/test/unit/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.test.js
📚 Learning: the existing implementation in `ghost/core/core/server/web/admin/middleware/serve-auth-frame-file.js...
Learnt from: 9larsons
PR: TryGhost/Ghost#21866
File: ghost/core/core/server/web/admin/middleware/serve-auth-frame-file.js:10-19
Timestamp: 2025-04-23T15:44:52.549Z
Learning: The existing implementation in `ghost/core/core/server/web/admin/middleware/serve-auth-frame-file.js` using `path.parse(req.url).base` is secure against path traversal attacks as it properly extracts only the filename component without any directory parts.

Applied to files:

  • ghost/core/core/frontend/meta/paginated-url.js
📚 Learning: the potential issues with gated content blocks (lack of migration path, race conditions during flag ...
Learnt from: kevinansfield
PR: TryGhost/Ghost#22101
File: ghost/core/core/server/api/endpoints/utils/serializers/output/utils/post-gating.js:110-117
Timestamp: 2025-02-03T17:14:37.367Z
Learning: The potential issues with gated content blocks (lack of migration path, race conditions during flag transitions, content visibility when disabled) are accepted trade-offs during the early testing phase of the contentVisibility labs feature.

Applied to files:

  • ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js
  • ghost/core/test/integration/api/cta-visibility-import.test.js
  • ghost/core/test/unit/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.test.js
📚 Learning: the validatecommentdata function in ghost/core/core/server/api/endpoints/comments.js is not duplicat...
Learnt from: 55sketch
PR: TryGhost/Ghost#23894
File: ghost/core/core/server/api/endpoints/comments.js:16-22
Timestamp: 2025-06-18T10:56:19.906Z
Learning: The validateCommentData function in ghost/core/core/server/api/endpoints/comments.js is not duplicated elsewhere in the codebase - it's a unique validation function that ensures either post_id or parent_id is provided for comment creation.

Applied to files:

  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js
📚 Learning: in ghost's newsletter customization features, when promoting a feature from alpha to beta status, th...
Learnt from: kevinansfield
PR: TryGhost/Ghost#23770
File: apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModalLabs.tsx:435-450
Timestamp: 2025-06-10T11:07:10.800Z
Learning: In Ghost's newsletter customization features, when promoting a feature from alpha to beta status, the feature flag guards are updated to make the feature available under both the `emailCustomization` (beta) and `emailCustomizationAlpha` (alpha) flags. This is done by either removing conditional guards entirely when the component is already behind both flags, or by updating conditionals to check for either flag.

Applied to files:

  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js
📚 Learning: in the ghost test framework, when testing csv exports via the admin api, the response `body` field i...
Learnt from: ErisDS
PR: TryGhost/Ghost#23588
File: ghost/core/test/e2e-api/admin/backup.test.js:136-148
Timestamp: 2025-05-29T10:37:26.369Z
Learning: In the Ghost test framework, when testing CSV exports via the admin API, the response `body` field is empty while the actual CSV data is provided through the `text` field. Tests should use `expectEmptyBody()` and then validate the CSV content via `.expect(({text}) => ...)` - this is not contradictory behavior.

Applied to files:

  • ghost/core/test/integration/api/cta-visibility-import.test.js
📚 Learning: in ghost project, app.js files under core/server/web are intentionally excluded from unit test cover...
Learnt from: ErisDS
PR: TryGhost/Ghost#23582
File: ghost/core/.c8rc.json:24-24
Timestamp: 2025-05-29T07:45:35.714Z
Learning: In Ghost project, app.js files under core/server/web are intentionally excluded from unit test coverage because they are not easily unit-testable due to being entry points with initialization code and side effects.

Applied to files:

  • ghost/core/test/unit/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.test.js
🪛 ESLint
ghost/core/core/frontend/meta/paginated-url.js

[error] 29-29: Expected parentheses around arrow function argument having a body with curly braces.

(arrow-parens)

ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js

[error] 50-50: Expected parentheses around arrow function argument having a body with curly braces.

(arrow-parens)


[error] 109-109: Expected parentheses around arrow function argument having a body with curly braces.

(arrow-parens)

🔇 Additional comments (20)
ghost/core/test/unit/frontend/meta/paginated-url.test.js (1)

168-217: Excellent test coverage for URL encoding scenarios.

The new test suite comprehensively covers the URL encoding functionality with well-chosen test cases:

  1. Accented characters: Properly tests UTF-8 encoding of 'sécurité incendie'
  2. Spaces: Verifies space encoding as %20 in 'hello world'
  3. Mixed special characters: Tests complex scenarios with 'café & résumé!'

The expected encoded values are accurate and the test structure follows the existing pattern. This provides good confidence that the encoding implementation works correctly across different character sets and scenarios.

ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js (6)

1-4: LGTM!

The import and regex pattern are correctly structured. Reusing parseGatedBlockParams from the existing post-gating module is a good approach for consistency.


63-86: LGTM!

The content identifier extraction logic is correct for CTA cards. The regex patterns properly match the expected HTML structure and the text cleaning with HTML tag removal is appropriate.


121-140: LGTM!

The card type checking logic is clean and comprehensive. The list of supported card types covers the major Ghost card types appropriately.


150-168: LGTM!

The card matching logic is well-designed. The approach of treating missing identifiers as matches provides good fallback behavior, and the specific CTA matching logic using text content and URLs is appropriate.


176-198: LGTM!

The visibility object construction is correct with sensible defaults. The parameter application logic properly handles undefined values and maintains consistency between web and email visibility settings.


200-202: LGTM!

Clean module exports exposing only the necessary public interface.

ghost/core/core/server/api/endpoints/utils/serializers/input/posts.js (2)

14-14: LGTM!

The import is correctly placed and follows the established patterns in the file.


236-238: LGTM!

The integration of gated block visibility preservation is clean and follows the established pattern. The three-step process (convert, enhance, serialize) is logical and maintains existing error handling.

ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js (2)

14-14: LGTM!

The import addition is consistent with the posts.js implementation and correctly placed.


194-196: LGTM!

The integration matches the posts.js implementation with appropriate adaptation for the pages data structure.

ghost/core/test/unit/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.test.js (5)

1-3: LGTM!

Standard test setup with appropriate imports and assertion library.


6-62: LGTM!

This test case provides comprehensive coverage of the core functionality, with realistic HTML and Lexical structures and proper verification of the applied visibility settings.


64-134: LGTM!

Important test case that verifies the function correctly distinguishes between gated and non-gated content, ensuring visibility is only applied where appropriate.


136-185: LGTM!

Good coverage of multiple gated blocks with different visibility settings, ensuring each block is processed independently and correctly.


187-222: LGTM!

Good edge case coverage ensuring the function handles empty inputs gracefully and doesn't modify documents when no gated blocks are present.

ghost/core/test/integration/api/cta-visibility-import.test.js (4)

1-8: LGTM!

Proper integration test setup with Ghost environment initialization following established patterns.


11-97: LGTM!

Comprehensive integration test with realistic HTML and Lexical structures that closely match actual usage scenarios. Good verification of visibility preservation.


99-163: LGTM!

Good integration test coverage for multiple gated blocks with different member segments, providing realistic scenarios for free vs paid member content.


165-220: LGTM!

Good integration test coverage for mixed public and gated CTAs, ensuring proper boundary handling in a realistic integration context.

refs TryGhost#21999

Added comprehensive solution for accented character URLs:

1. Fixed ESLint arrow-parens violations in paginated-url.js and gated-block-visibility.js
2. Added normalize-urls middleware to redirect accented taxonomy URLs to normalized versions
3. Integrated middleware into pretty-urls chain for automatic 301 redirects
4. Added comprehensive test coverage for all redirect scenarios

This provides a two-layer solution: 301 redirects prevent crawlers from reaching
wrong URLs, while the existing pagination URL encoding ensures proper metadata
even if accented URLs are accessed directly.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js (2)

50-52: Fix ESLint arrow function parentheses.

The static analysis tool correctly identified missing parentheses around the arrow function parameter.

-    gatedBlocks.forEach(block => {
+    gatedBlocks.forEach((block) => {
         applyVisibilityToMatchingCards(modifiedDoc.root, block);
     });

67-85: Improve regex robustness and consider edge cases.

The regex patterns for extracting content identifiers may not handle all HTML variations and could miss content with different formatting or nested elements.

Consider using a more robust HTML parsing approach:

-    const ctaTextMatch = content.match(/<div[^>]*kg-cta-text[^>]*>(.*?)<\/div>/s);
+    const ctaTextMatch = content.match(/<div[^>]*\bkg-cta-text\b[^>]*>(.*?)<\/div>/s);
     if (ctaTextMatch) {
         identifiers.ctaText = ctaTextMatch[1].replace(/<[^>]*>/g, '').trim();
     }
     
-    const buttonTextMatch = content.match(/<a[^>]*kg-cta-button[^>]*>(.*?)<\/a>/s);
+    const buttonTextMatch = content.match(/<a[^>]*\bkg-cta-button\b[^>]*>(.*?)<\/a>/s);
     if (buttonTextMatch) {
         identifiers.buttonText = buttonTextMatch[1].replace(/<[^>]*>/g, '').trim();
     }
     
-    const buttonUrlMatch = content.match(/href="([^"]*)"[^>]*kg-cta-button/);
+    const buttonUrlMatch = content.match(/href="([^"]*)"[^>]*\bkg-cta-button\b/);
ghost/core/core/server/web/shared/middleware/normalize-urls.js (3)

29-29: Add missing curly braces for single-statement if blocks.

ESLint correctly flagged missing curly braces, which is required by the coding standards.

-    if (!segment) return false;
+    if (!segment) {
+        return false;
+    }
-    if (!segment) return segment;
+    if (!segment) {
+        return segment;
+    }

Also applies to: 46-46


67-67: Remove unused variables.

The variables taxonomyType and decodedURI are assigned but never used.

-    const [, basePath, taxonomyType, slug, remainder] = taxonomyMatch;
+    const [, basePath, , slug, remainder] = taxonomyMatch;
-    let decodedURI;
-    
     // Only process taxonomy routes (tags, authors)
     const taxonomyMatch = pathToTest.match(/^(.*\/(tag|author)\/([^\/]+))(\/.*)?$/);
     
     if (!taxonomyMatch) {
         return next();
     }

     const [, basePath, , slug, remainder] = taxonomyMatch;
     
     try {
-        decodedURI = decodeURIComponent(pathToTest);
+        decodeURIComponent(pathToTest);
     } catch (err) {

Also applies to: 70-70


61-61: Remove unnecessary escape characters in regex.

The forward slashes in the regex don't need to be escaped.

-    const taxonomyMatch = pathToTest.match(/^(.*\/(tag|author)\/([^\/]+))(\/.*)?$/);
+    const taxonomyMatch = pathToTest.match(/^(.*(tag|author)\/([^/]+))(\/.*)?$/);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d3f2a3 and f880de5.

📒 Files selected for processing (6)
  • ghost/core/core/frontend/meta/paginated-url.js (1 hunks)
  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js (1 hunks)
  • ghost/core/core/server/web/shared/middleware/index.js (1 hunks)
  • ghost/core/core/server/web/shared/middleware/normalize-urls.js (1 hunks)
  • ghost/core/core/server/web/shared/middleware/pretty-urls.js (1 hunks)
  • ghost/core/test/unit/server/web/shared/middleware/normalize-urls.test.js (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • ghost/core/core/server/web/shared/middleware/index.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • ghost/core/core/frontend/meta/paginated-url.js
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: cathysarisky
PR: TryGhost/Ghost#21982
File: ghost/i18n/locales/hu v2/portal.json:70-70
Timestamp: 2025-01-29T15:23:58.658Z
Learning: Typos and spelling errors within localization files (ghost/i18n/locales/*.json) must be treated as important problems, not nitpicks, as they directly affect the user experience for speakers of that language. These issues should be flagged with high priority during code review.
Learnt from: kevinansfield
PR: TryGhost/Ghost#22101
File: ghost/core/core/server/api/endpoints/utils/serializers/output/utils/post-gating.js:110-117
Timestamp: 2025-02-03T17:14:37.367Z
Learning: The potential issues with gated content blocks (lack of migration path, race conditions during flag transitions, content visibility when disabled) are accepted trade-offs during the early testing phase of the contentVisibility labs feature.
Learnt from: kevinansfield
PR: TryGhost/Ghost#23770
File: apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModalLabs.tsx:435-450
Timestamp: 2025-06-10T11:07:10.800Z
Learning: In Ghost's newsletter customization features, when promoting a feature from alpha to beta status, the feature flag guards are updated to make the feature available under both the `emailCustomization` (beta) and `emailCustomizationAlpha` (alpha) flags. This is done by either removing conditional guards entirely when the component is already behind both flags, or by updating conditionals to check for either flag.
📚 Learning: in the ghost codebase, middleware directories should be included in unit test coverage because they ...
Learnt from: ErisDS
PR: TryGhost/Ghost#23582
File: ghost/core/.c8rc.json:28-28
Timestamp: 2025-05-29T07:47:00.748Z
Learning: In the Ghost codebase, middleware directories should be included in unit test coverage because they contain actual business logic that should be tested, not just routing or setup code.

Applied to files:

  • ghost/core/core/server/web/shared/middleware/pretty-urls.js
  • ghost/core/test/unit/server/web/shared/middleware/normalize-urls.test.js
📚 Learning: the existing implementation in `ghost/core/core/server/web/admin/middleware/serve-auth-frame-file.js...
Learnt from: 9larsons
PR: TryGhost/Ghost#21866
File: ghost/core/core/server/web/admin/middleware/serve-auth-frame-file.js:10-19
Timestamp: 2025-04-23T15:44:52.549Z
Learning: The existing implementation in `ghost/core/core/server/web/admin/middleware/serve-auth-frame-file.js` using `path.parse(req.url).base` is secure against path traversal attacks as it properly extracts only the filename component without any directory parts.

Applied to files:

  • ghost/core/core/server/web/shared/middleware/pretty-urls.js
  • ghost/core/core/server/web/shared/middleware/normalize-urls.js
📚 Learning: the pending activity utilities in the ghost activitypub module are covered by tests in the file `app...
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in the Ghost ActivityPub module are covered by tests in the file `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`.

Applied to files:

  • ghost/core/test/unit/server/web/shared/middleware/normalize-urls.test.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js
📚 Learning: the pending activity utilities in ghost's activitypub module are thoroughly tested in `apps/admin-x-...
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in Ghost's ActivityPub module are thoroughly tested in `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`, which covers `generatePendingActivityId`, `isPendingActivity`, and `generatePendingActivity` functions.

Applied to files:

  • ghost/core/test/unit/server/web/shared/middleware/normalize-urls.test.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js
📚 Learning: in ghost admin-x-settings, webhook url validation uses {require_tld: false} to allow localhost urls ...
Learnt from: troyciesco
PR: TryGhost/Ghost#24291
File: apps/admin-x-settings/package.json:45-45
Timestamp: 2025-07-09T18:06:09.856Z
Learning: In Ghost admin-x-settings, webhook URL validation uses {require_tld: false} to allow localhost URLs for local integration testing, while social URL validation uses the default {require_tld: true} since social URLs should be public-facing.

Applied to files:

  • ghost/core/test/unit/server/web/shared/middleware/normalize-urls.test.js
  • ghost/core/core/server/web/shared/middleware/normalize-urls.js
📚 Learning: the potential issues with gated content blocks (lack of migration path, race conditions during flag ...
Learnt from: kevinansfield
PR: TryGhost/Ghost#22101
File: ghost/core/core/server/api/endpoints/utils/serializers/output/utils/post-gating.js:110-117
Timestamp: 2025-02-03T17:14:37.367Z
Learning: The potential issues with gated content blocks (lack of migration path, race conditions during flag transitions, content visibility when disabled) are accepted trade-offs during the early testing phase of the contentVisibility labs feature.

Applied to files:

  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js
📚 Learning: the validatecommentdata function in ghost/core/core/server/api/endpoints/comments.js is not duplicat...
Learnt from: 55sketch
PR: TryGhost/Ghost#23894
File: ghost/core/core/server/api/endpoints/comments.js:16-22
Timestamp: 2025-06-18T10:56:19.906Z
Learning: The validateCommentData function in ghost/core/core/server/api/endpoints/comments.js is not duplicated elsewhere in the codebase - it's a unique validation function that ensures either post_id or parent_id is provided for comment creation.

Applied to files:

  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js
🪛 ESLint
ghost/core/core/server/web/shared/middleware/normalize-urls.js

[error] 29-29: Expected { after 'if' condition.

(curly)


[error] 46-46: Expected { after 'if' condition.

(curly)


[error] 61-61: Unnecessary escape character: /.

(no-useless-escape)


[error] 67-67: 'taxonomyType' is assigned a value but never used.

(no-unused-vars)


[error] 70-70: 'decodedURI' is assigned a value but never used.

(no-unused-vars)

ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js

[error] 50-50: Expected parentheses around arrow function argument having a body with curly braces.

(arrow-parens)

🔇 Additional comments (6)
ghost/core/core/server/api/endpoints/utils/serializers/input/utils/gated-block-visibility.js (1)

1-202: Inconsistent with PR objectives.

This entire file appears to implement gated block visibility functionality, which is unrelated to the stated PR objective of fixing pagination URL encoding for accented characters. This suggests either the PR description is incomplete or this file was included by mistake.

Likely an incorrect or invalid review comment.

ghost/core/core/server/web/shared/middleware/pretty-urls.js (1)

20-22: LGTM! Proper middleware placement.

The normalize-urls middleware is correctly positioned after uncapitalise in the pipeline, ensuring URLs are processed in the right order for the pagination URL encoding fix.

ghost/core/test/unit/server/web/shared/middleware/normalize-urls.test.js (2)

1-133: Excellent test coverage!

The test suite comprehensively covers all the scenarios mentioned in the PR objectives, including accented characters, spaces, pagination, query parameters, and error handling. The test structure is well-organized and follows good practices.


99-108: Encoded URL normalization behavior is intentional and loop-safe

The middleware’s needsNormalization logic decodes the slug and compares it to its “safe” normalized form, redirecting only when they differ (e.g. already-encoded accents/spaces). Once the URL is normalized (e.g. /tag/securite-incendie/), needsNormalization returns false, so next() is called and no further redirects occur. No redirect loop will happen—no changes required.

ghost/core/core/server/web/shared/middleware/normalize-urls.js (2)

56-92: Core functionality looks solid for pagination URL encoding fix.

The middleware correctly identifies taxonomy URLs, normalizes slugs with accented characters and spaces, and issues proper 301 redirects. This addresses the main PR objective of fixing pagination links with special characters.


84-86: Good security practice using removeOpenRedirectFromUrl.

The use of localUtils.removeOpenRedirectFromUrl properly prevents open redirect vulnerabilities when constructing redirect URLs.

refs TryGhost#24603

- Fixed ESLint arrow-parens violations in gated-block-visibility.js
- Improved regex robustness with word boundaries in CTA matching
- Fixed false positive matching for non-CTA cards
- Added missing curly braces for single-statement if blocks in normalize-urls.js
- Removed unused variables and unnecessary regex escapes
- All syntax checks pass and functionality remains intact
… pages and posts

When using the #get helper to retrieve pages or posts, the show_title_and_feature_image property was being stripped by prepareContextResource, making it inaccessible in templates. This fix preserves the property so it can be accessed directly on the retrieved items.

- Modified get.js to preserve show_title_and_feature_image for pages and posts
- Added comprehensive tests for both pages and posts
- Maintains backward compatibility with existing behavior
- Allows themes to access the property when using {{#get "pages"}} or {{#get "posts"}}

Fixes TryGhost#23316
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

{{ghost_head}} and accents
1 participant