Add copyable shareable links for individual stories#1541
Conversation
The story sharing surface exposed only social share icons and a count, with no way to grab the direct URL for a story to paste into email or posts. Stakeholders need a copy-paste link per story (issue #1523). Adds a reusable share section (direct URL + copy button, plus email and social targets) to the public story share page and the story show page, backed by a small copy-link Stimulus controller. The duplicated inline social markup on the story show page is replaced by the shared partial. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| await navigator.clipboard.writeText(url) | ||
| } catch { | ||
| // Fallback for browsers without the async clipboard API | ||
| this.inputTarget.select() |
There was a problem hiding this comment.
Fallback path for browsers/contexts where the async Clipboard API is unavailable (e.g. non-HTTPS or older Safari). execCommand("copy") is deprecated but still the most reliable cross-browser fallback.
| @@ -1,5 +1,4 @@ | |||
| <% story_url = story_url(@story) %> | |||
| <% story_title = ERB::Util.url_encode(@story.title) %> | |||
| <% share_url = story_share_url(@story) %> | |||
There was a problem hiding this comment.
Switched the copyable link from story_url (admin /stories/:id, auth-gated) to the canonical public story_share_url, so a copied link is actually shareable with non-logged-in recipients.
| @@ -0,0 +1,50 @@ | |||
| <%# Direct shareable link + social/email share targets for a single story. | |||
| Locals: story (decorated or plain), url (canonical public share URL string). %> | |||
There was a problem hiding this comment.
Partial takes url as a local rather than deriving it, so each caller passes the appropriate canonical URL for its context while keeping the markup shared.
There was a problem hiding this comment.
Pull request overview
Adds a per-story sharing UI that exposes the canonical public story URL (with a copy-to-clipboard affordance) and standard social/email share targets, and reuses that UI across both the story show and public story share pages.
Changes:
- Introduces a reusable
stories/_share_linkspartial rendering a direct URL field + copy button and social/email share links. - Adds a
copy-linkStimulus controller and registers it for clipboard copy + “Copied!” confirmation. - Adds a request spec assertion that the public story share page includes the direct share URL and “Copy link” text.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/requests/story_share_spec.rb | Adds request coverage asserting the share page renders the direct URL + copy affordance. |
| app/views/story_shares/show.html.erb | Renders the new share links partial on the public story share page. |
| app/views/stories/show.html.erb | Replaces duplicated inline social share markup with the shared partial (using story_share_url). |
| app/views/stories/_share_links.html.erb | New partial providing copyable URL UI and social/email share links. |
| app/frontend/javascript/controllers/index.js | Registers the new copy-link Stimulus controller. |
| app/frontend/javascript/controllers/copy_link_controller.js | Implements clipboard copy + transient confirmation label behavior. |
| AGENTS.md | Updates documented Stimulus controller list and controller count. |
| class="w-full sm:max-w-md px-3 py-2 bg-gray-100 border border-gray-300 rounded font-mono text-sm overflow-x-auto whitespace-nowrap cursor-text" | ||
| > |
| <% encoded_url = CGI.escape(url) %> | ||
| <% encoded_title = CGI.escape(story.title) %> |
| <%= link_to "mailto:?subject=#{encoded_title}&body=#{encoded_url}", | ||
| class: "text-gray-600 hover:text-gray-800", | ||
| title: "Share by email" do %> |
| <%= link_to "https://www.facebook.com/sharer/sharer.php?u=#{encoded_url}", | ||
| target: "_blank", rel: "noopener noreferrer", | ||
| class: "text-blue-600 hover:text-blue-800", title: "Share on Facebook" do %> |
| <%= link_to "https://twitter.com/intent/tweet?url=#{encoded_url}&text=#{encoded_title}", | ||
| target: "_blank", rel: "noopener noreferrer", | ||
| class: "text-sky-500 hover:text-sky-700", title: "Share on X" do %> |
| <%= link_to "https://www.linkedin.com/sharing/share-offsite/?url=#{encoded_url}", | ||
| target: "_blank", rel: "noopener noreferrer", | ||
| class: "text-blue-700 hover:text-blue-900", title: "Share on LinkedIn" do %> |
| <%= link_to "https://pinterest.com/pin/create/button/?url=#{encoded_url}&description=#{encoded_title}", | ||
| target: "_blank", rel: "noopener noreferrer", | ||
| class: "text-red-600 hover:text-red-800", title: "Share on Pinterest" do %> |
| |---|---| | ||
| | `app/frontend/entrypoints/` | Vite entry points (application.js, application.css) | | ||
| | `app/frontend/javascript/controllers/` | Stimulus controllers (34) | | ||
| | `app/frontend/javascript/controllers/` | Stimulus controllers (35) | |
Closes #1523
What is the goal of this PR and why is this important?
How did you approach the change?
stories/_share_linkspartial that renders:copy-linkStimulus controller (clipboard write with a gracefulexecCommandfallback and a transient "Copied!" confirmation).story_shares/show), which previously had no share affordance at all.story_share_url.AGENTS.md(Stimulus controller list + count).UI Testing Checklist
Anything else to add?
npm ciso Vite-built pages render in the test env.