diff --git a/server/utils/readme.ts b/server/utils/readme.ts index 67345a65cd..50a8d838d2 100644 --- a/server/utils/readme.ts +++ b/server/utils/readme.ts @@ -19,7 +19,8 @@ interface PlaygroundProvider { id: string // Provider identifier name: string domains: string[] // Associated domains - paths?: string[] + paths?: string[] // pathname must start with one of these + pathContains?: string[] // pathname must contain one of these icon?: string // Provider icon name } @@ -44,6 +45,8 @@ const PLAYGROUND_PROVIDERS: PlaygroundProvider[] = [ name: 'CodePen', domains: ['codepen.io'], icon: 'codepen', + // Only match actual pen URLs like /username/pen/id (not profile/collection pages) + pathContains: ['/pen/', '/full/', '/details/'], }, { id: 'jsfiddle', @@ -128,7 +131,9 @@ function matchPlaygroundProvider(url: string): PlaygroundProvider | null { for (const domain of provider.domains) { if ( (hostname === domain || hostname.endsWith(`.${domain}`)) && - (!provider.paths || provider.paths.some(path => parsed.pathname.startsWith(path))) + (!provider.paths || provider.paths.some(path => parsed.pathname.startsWith(path))) && + (!provider.pathContains || + provider.pathContains.some(seg => parsed.pathname.includes(seg))) ) { return provider } diff --git a/test/unit/server/utils/readme.spec.ts b/test/unit/server/utils/readme.spec.ts index 3269f01850..2520a2d93d 100644 --- a/test/unit/server/utils/readme.spec.ts +++ b/test/unit/server/utils/readme.spec.ts @@ -84,13 +84,42 @@ describe('Playground Link Extraction', () => { }) describe('Other Providers', () => { - it('extracts CodePen links', async () => { + it('extracts CodePen pen links', async () => { const markdown = `[Pen](https://codepen.io/user/pen/abc123)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.playgroundLinks[0]!.provider).toBe('codepen') }) + it('does not treat a CodePen profile page as a playground link', async () => { + // https://codepen.io/username is a profile page, not a pen + const markdown = `[Profile](https://codepen.io/username)` + const result = await renderReadmeHtml(markdown, 'test-pkg') + + expect(result.playgroundLinks).toHaveLength(0) + }) + + it('does not treat a CodePen collection page as a playground link', async () => { + const markdown = `[Collection](https://codepen.io/username/pens/public)` + const result = await renderReadmeHtml(markdown, 'test-pkg') + + expect(result.playgroundLinks).toHaveLength(0) + }) + + it('extracts CodePen full-page links', async () => { + const markdown = `[Full](https://codepen.io/user/full/abc123)` + const result = await renderReadmeHtml(markdown, 'test-pkg') + + expect(result.playgroundLinks[0]!.provider).toBe('codepen') + }) + + it('extracts CodePen details links', async () => { + const markdown = `[Details](https://codepen.io/user/details/abc123)` + const result = await renderReadmeHtml(markdown, 'test-pkg') + + expect(result.playgroundLinks[0]!.provider).toBe('codepen') + }) + it('extracts Replit links', async () => { const markdown = `[Repl](https://replit.com/@user/project)` const result = await renderReadmeHtml(markdown, 'test-pkg')