From a49a476b3989b3be8c31c45810746de5e61da897 Mon Sep 17 00:00:00 2001 From: rajiabdulrahmon2020-ui Date: Tue, 6 Oct 2026 21:39:40 +0100 Subject: [PATCH] fix: address Parallax issue #55 --- scripts/smoke-test.mjs | 9 +++++++++ src/pages/issue.tsx | 41 ++++++++++++++++++++++++++++++++++++++--- src/styles/pages.css | 4 ++++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/scripts/smoke-test.mjs b/scripts/smoke-test.mjs index a9df592..8dd7c4d 100644 --- a/scripts/smoke-test.mjs +++ b/scripts/smoke-test.mjs @@ -88,6 +88,15 @@ try { // ------------------------------------------------------- contributor apply await go('/issue/512'); await page.getByRole('heading', { name: 'Add a test helper for asserting emitted events' }).waitFor(); + await page.evaluate(() => Object.defineProperty(navigator, 'clipboard', { + configurable: true, + value: undefined, + })); + await page.getByRole('button', { name: 'Copy link' }).click(); + const issueLink = page.getByRole('textbox', { name: 'Issue link; select and copy' }); + assert.equal(await issueLink.inputValue(), `${base}/issue/512`); + assert.ok(await issueLink.evaluate(input => input.selectionStart === 0 && input.selectionEnd === input.value.length), + 'the fallback selects the full issue URL for copying'); await page.getByRole('button', { name: 'Apply to this issue' }).click(); await page.waitForURL('**/login?**'); await page.getByLabel('Display name').fill('Casey Contributor'); diff --git a/src/pages/issue.tsx b/src/pages/issue.tsx index 9a387ff..62a860a 100644 --- a/src/pages/issue.tsx +++ b/src/pages/issue.tsx @@ -1,6 +1,6 @@ -import { useState } from 'react'; +import { useRef, useState } from 'react'; import { Link, Navigate, useNavigate, useParams } from 'react-router-dom'; -import { ArrowUpRight, Check, GitPullRequest, Lock } from 'lucide-react'; +import { ArrowUpRight, Check, Copy, GitPullRequest, Lock } from 'lucide-react'; import { applicantName, formatMoney, isOwnApplication, repoName, statusTone } from '../lib/model'; import { PLATFORM } from '../lib/platform'; import { useApp } from '../lib/store'; @@ -13,6 +13,8 @@ export function IssuePage() { const [open, setOpen] = useState(false); const [message, setMessage] = useState(''); const [err, setErr] = useState(''); + const [copyFallback, setCopyFallback] = useState(false); + const copyInput = useRef(null); const issue = state.issues.find(i => i.id === issueId); if (!issue) return ; @@ -25,6 +27,22 @@ export function IssuePage() { const paid = taken?.status === 'Paid'; const canApply = !mine && !taken; + const copyLink = async () => { + try { + if (!navigator.clipboard?.writeText) throw new Error('Clipboard API unavailable'); + await navigator.clipboard.writeText(window.location.href); + setCopyFallback(false); + notify('Issue link copied.'); + } catch { + setCopyFallback(true); + notify('Select the issue link below and copy it.'); + window.setTimeout(() => { + copyInput.current?.focus(); + copyInput.current?.select(); + }, 0); + } + }; + const submit = () => { if (message.trim().length < 30) { setErr('Add at least 30 characters so the maintainer can judge your plan.'); return; } setState(s => ({ @@ -60,7 +78,24 @@ export function IssuePage() { {issue.complexity} {paid ? 'Paid out' : 'Escrowed'} -

{issue.title}

+
+

{issue.title}

+ +
+ {copyFallback && ( + + )}
{repoName(repo)} diff --git a/src/styles/pages.css b/src/styles/pages.css index bd4552d..8dd164d 100644 --- a/src/styles/pages.css +++ b/src/styles/pages.css @@ -128,6 +128,10 @@ a.row-title:hover { text-decoration: underline; text-underline-offset: 2px; } .detail-grid { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: var(--s7); align-items: start; } .detail-head { padding-bottom: var(--s4); border-bottom: 1px solid var(--line); margin-bottom: var(--s5); } .detail-head h1 { margin: var(--s3) 0 var(--s3); font-size: var(--t7); line-height: 1.25; } +.detail-title-row { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); flex-wrap: wrap; } +.detail-title-row h1 { min-width: 0; } +.copy-link-fallback { display: block; margin: 0 0 var(--s3); } +.copy-link-fallback .input { width: min(100%, 34rem); } .prose h2 { font-size: var(--t5); margin-bottom: var(--s2); } .prose h2 + p { font-size: var(--t4); color: var(--ink-2); } .prose > * + h2 { margin-top: var(--s5); }