✨ Added long-press to open the posts list context menu on touch devices#28602
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThe multi-list 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ghost/core/test/unit/frontend/services/rss/generate-feed.test.js`:
- Around line 225-227: The comment block starting with "// The card markup we
care about lives in <content:encoded>" incorrectly states that the plain-text
<description> excerpt is generated before the card cleanup runs. Update this
comment to accurately reflect the current implementation where the excerpt is
generated after the card cleanup runs, not before. This will prevent misleading
future developers who read this test.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d0eb950b-0974-4267-b831-4c52e844399a
📒 Files selected for processing (6)
ghost/admin/app/components/multi-list/item.hbsghost/admin/app/components/multi-list/item.jsghost/admin/app/styles/layouts/content.cssghost/admin/tests/acceptance/content-test.jsghost/core/core/frontend/services/rss/generate-feed.jsghost/core/test/unit/frontend/services/rss/generate-feed.test.js
| // The card markup we care about lives in <content:encoded>. The feed also | ||
| // emits a plain-text <description> excerpt that is generated before the | ||
| // card cleanup runs, so assertions are scoped to content:encoded. |
There was a problem hiding this comment.
Update the stale comment about excerpt timing.
This comment says <description> is generated before card cleanup, but the current implementation generates the excerpt after cleanup. Keeping this comment will mislead future test updates.
Suggested comment correction
- // The card markup we care about lives in <content:encoded>. The feed also
- // emits a plain-text <description> excerpt that is generated before the
- // card cleanup runs, so assertions are scoped to content:encoded.
+ // The card markup we care about lives in <content:encoded>. The feed also
+ // emits a plain-text <description> excerpt, now generated from cleaned
+ // card markup, so we assert both encoded content and excerpt behavior.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // The card markup we care about lives in <content:encoded>. The feed also | |
| // emits a plain-text <description> excerpt that is generated before the | |
| // card cleanup runs, so assertions are scoped to content:encoded. | |
| // The card markup we care about lives in <content:encoded>. The feed also | |
| // emits a plain-text <description> excerpt, now generated from cleaned | |
| // card markup, so we assert both encoded content and excerpt behavior. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@ghost/core/test/unit/frontend/services/rss/generate-feed.test.js` around
lines 225 - 227, The comment block starting with "// The card markup we care
about lives in <content:encoded>" incorrectly states that the plain-text
<description> excerpt is generated before the card cleanup runs. Update this
comment to accurately reflect the current implementation where the excerpt is
generated after the card cleanup runs, not before. This will prevent misleading
future developers who read this test.
no ref - the posts list context menu (Duplicate, Feature, Delete, etc.) only opened via a right-click, which touch devices can't produce, so mobile users had no way to duplicate a post - a long-press now opens the same menu through the existing dropdown path; it's the natural touch equivalent of a right-click and there's no row text-selection it conflicts with - distinguishes a press from a scroll via a movement threshold, guards against the duplicate native contextmenu some touch platforms also emit, and suppresses the click that would otherwise navigate into the post - suppresses the iOS link-callout / selection magnifier on rows, scoped to touch devices
f609874 to
da1aeef
Compare
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run @tryghost/admin:build |
✅ Succeeded | 3m 36s | View ↗ |
nx run ghost-admin:test |
✅ Succeeded | 2m 39s | View ↗ |
nx run ghost:build:assets |
✅ Succeeded | 2s | View ↗ |
nx run ghost:build:tsc |
✅ Succeeded | 6s | View ↗ |
nx run-many -t lint -p ghost-admin |
✅ Succeeded | 18s | View ↗ |
nx run-many --target=build --projects=@tryghost... |
✅ Succeeded | <1s | View ↗ |
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗
☁️ Nx Cloud last updated this comment at 2026-06-16 15:24:21 UTC
no ref - a long-press opens the menu while the finger is still down; on release the browser fires a synthetic "ghost" click that the dropdown service's body-click handler read as an outside click, closing the menu instantly - swallow that one ghost click at the document level (capture phase) so the menu stays open; the test now simulates the release click and asserts the menu remains visible
no ref - the ghost-click suppressor was armed when the menu opened with a fixed timeout, so it expired while the press was held and the release click still closed the menu - arm it on touchend (release) instead, so it catches the synthetic click regardless of how long the press was held
no ref - the browser's native long-press also fires a contextmenu event during the hold; it landed on the menu's overlay, whose onContextMenuOutside handler closed the menu mid-press (quick releases escaped it, longer holds didn't) - suppress the native contextmenu at the document level for the duration of the touch, so only the long-press timer controls the menu
no ref - the post title/meta block is a flex child with no min-width, so a long title or tag name couldn't shrink and held the row open, pushing the row's action button (and the whole list) past the viewport on phones - give that block min-width: 0 so it collapses, break long title words, and truncate the meta line with an ellipsis (<=800px)
no ref - the list container is a CSS table (display: table), which sizes to its content's width, so a long title or tag grew the whole list past the viewport no matter how the row text was constrained - switch it to a normal block on mobile (the table columns are already hidden at this width) so it tracks the viewport; the row-text shrinking added in the previous commit now actually takes effect
…llbar on mobile no ref - the meta line used white-space: nowrap, so a long tag's date laid out past the viewport and clipped tight against the row's action button; let the title and meta wrap and break long tokens instead so nothing in the row can extend sideways - gave the filter bar's horizontal scroll some bottom padding and a thin scrollbar so the bar sits below the chips rather than over them
no ref - the macOS overlay scrollbar floated on top of the filter chips and ignores padding (overlay scrollbars reserve no gutter), so hide it; the chip row still scrolls by swipe/trackpad, matching the usual mobile chip-row pattern
…d-word no ref - on very narrow viewports the title column got so small that overflow-wrap broke words mid-character; give the list a 300px min-width so the text column stays usable and the page scrolls a little instead (real phones at >=360px still fit with no scroll)
no ref - the click suppressor armed after a long-press cancelled the next click anywhere within 500ms; if the browser never emitted the synthetic release click, a fast real tap on a menu action could be discarded (review feedback) - scope it to clicks at the touch point, where the compatibility click fires, so a real tap elsewhere always goes through even with the suppressor armed - add a regression test that taps a menu action after touchend with no ghost click emitted; this fails on the previous eat-any-click behaviour

Why
Two mobile gaps in the posts list:
What
1. Long-press opens the context menu on touch. A long-press on a post row opens the same context menu through the existing
dropdown.toggleDropdown('context-menu', …)path — the natural touch equivalent of a right-click. No new visible UI; the menu and all its actions are reused unchanged. Details:contextmenuduring the hold — both of which would otherwise close the menu via the dropdown service's outside-click /onContextMenuOutsidehandlers.@media (hover: none) and (pointer: coarse).2. Posts list fits the viewport on mobile. The title/meta block is a flex child with no
min-width, so a long title or tag name held the row open and pushed the row's action button (and the whole list) past the viewport. Giving that blockmin-width: 0lets it collapse; the title breaks long words and the meta line truncates with an ellipsis (≤800px).Testing
ember test --filter "context menu"→ all green, including new acceptance tests that long-press to open + duplicate a post, assert the menu survives both a nativecontextmenumid-hold and the release click, and that a touch which becomes a scroll does not open the menu.