Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/changelog-1.9.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ All changes included in 1.9:
- ([#13570](https://github.com/quarto-dev/quarto-cli/pull/13570)): Replace Twitter with Bluesky in default blog template and documentation examples. New blog projects now include Bluesky social links instead of Twitter.
- ([#13716](https://github.com/quarto-dev/quarto-cli/issues/13716)): Fix draft pages showing blank during preview when pre-render scripts are configured.
- ([#13847](https://github.com/quarto-dev/quarto-cli/pull/13847)): Open graph title with markdown is now processed correctly. (author: @mcanouil)
- ([#9468](https://github.com/quarto-dev/quarto-cli/issues/9468)): Respect `link-external-newwindow: true` in "default" type listing. (author: @mcanouil)

### `book`

Expand Down
4 changes: 4 additions & 0 deletions src/resources/formats/html/_quarto-rules-link-external.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ a.external:after:hover {
cursor: pointer;
}

.quarto-listing-default a.external:after {
content: none;
}

.quarto-ext-icon {
display: inline-block;
font-size: 0.75em;
Expand Down
10 changes: 5 additions & 5 deletions src/resources/projects/website/listing/item-default.ejs.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ print(`<div class="metadata-value listing-${field}">${listing.utilities.outputLi
<% if (fields.includes('image')) { %>

```{=html}
<div class="thumbnail"><a href="<%- item.path %>" class="no-external">
<div class="thumbnail"><a href="<%- item.path %>">
<% if (item.image) { %>
<%= listing.utilities.img(itemNumber, item.image, "thumbnail-image", item['image-alt'], item['image-lazy-loading'] ?? listing['image-lazy-loading']) %>
<% } else { %>
Expand All @@ -55,9 +55,9 @@ print(`<div class="metadata-value listing-${field}">${listing.utilities.outputLi
::: {.body}

<% if (fields.includes('title')) { %>
<h3 class="no-anchor listing-title"><a href="<%- item.path %>" class="no-external"><%= item.title %></a></h3>
<h3 class="no-anchor listing-title"><a href="<%- item.path %>"><%= item.title %></a></h3>
<% if (fields.includes('subtitle')) { %>
<div class="listing-subtitle"><a href="<%- item.path %>" class="no-external"><%= item.subtitle %></a></div>
<div class="listing-subtitle"><a href="<%- item.path %>"><%= item.subtitle %></a></div>
<% } %>
<% } %>

Expand All @@ -76,7 +76,7 @@ print(`<div class="metadata-value listing-${field}">${listing.utilities.outputLi
<% if (fields.includes('description')) { %>

```{=html}
<div class="delink listing-description"><a href="<%- item.path %>" class="no-external">
<div class="delink listing-description"><a href="<%- item.path %>">
```

<%= item.description %>
Expand All @@ -92,7 +92,7 @@ print(`<div class="metadata-value listing-${field}">${listing.utilities.outputLi
::: {.metadata}

```{=html}
<a href="<%- item.path %>" class="no-external">
<a href="<%- item.path %>">
```

<% if (fields.includes('date') && item.date) { %>
Expand Down
2 changes: 2 additions & 0 deletions tests/docs/playwright/listings/external-links/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.quarto/
**/*.quarto_ipynb
9 changes: 9 additions & 0 deletions tests/docs/playwright/listings/external-links/_quarto.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
project:
type: website

website:
title: "Testing external links in listings"

link-external-newwindow: true

format: html
16 changes: 16 additions & 0 deletions tests/docs/playwright/listings/external-links/grid.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
title: "External Links Test - Grid"
listing:
id: grid-listing
contents:
- path: https://example.com/external-article
title: "External Article"
description: "An external article that should open in new window."
- path: local-post.qmd
title: "Local Post"
description: "A local post that should NOT open in new window."
type: grid
---

::: {#grid-listing}
:::
16 changes: 16 additions & 0 deletions tests/docs/playwright/listings/external-links/index.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
title: "External Links Test - Default"
listing:
id: external-listing
contents:
- path: https://example.com/external-article
title: "External Article"
description: "An external article that should open in new window."
- path: local-post.qmd
title: "Local Post"
description: "A local post that should NOT open in new window."
type: default
---

::: {#external-listing}
:::
5 changes: 5 additions & 0 deletions tests/docs/playwright/listings/external-links/local-post.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: "Local Post"
---

Local content.
17 changes: 17 additions & 0 deletions tests/docs/playwright/listings/external-links/table.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
title: "External Links Test - Table"
listing:
id: table-listing
contents:
- path: https://example.com/external-article
title: "External Article"
description: "An external article that should open in new window."
- path: local-post.qmd
title: "Local Post"
description: "A local post that should NOT open in new window."
type: table
fields: [title, description]
---

::: {#table-listing}
:::
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { test, expect } from "@playwright/test";

const listingTypes = [
{
name: "default",
page: "index.html",
containerSelector: ".quarto-listing-default",
externalLinkSelector:
'.quarto-listing-default a[href="https://example.com/external-article"]',
},
{
name: "grid",
page: "grid.html",
containerSelector: ".quarto-listing-container-grid",
externalLinkSelector:
'.quarto-listing-container-grid a[href="https://example.com/external-article"]',
},
{
name: "table",
page: "table.html",
containerSelector: ".quarto-listing-table",
externalLinkSelector:
'.quarto-listing-table a[href="https://example.com/external-article"]',
},
];

listingTypes.forEach(({ name, page, containerSelector, externalLinkSelector }) => {
test.describe(`External links in ${name} listing`, () => {
test("External listing item has target=_blank attribute", async ({
page: browserPage,
}) => {
await browserPage.goto(`./listings/external-links/_site/${page}`);

// Wait for the listing to be visible
await browserPage.waitForSelector(containerSelector);

// Find the link to the external article
const externalLink = browserPage.locator(externalLinkSelector);

await expect(externalLink.first()).toHaveAttribute("target", "_blank");
});

test("External listing item has rel=noopener attribute", async ({
page: browserPage,
}) => {
await browserPage.goto(`./listings/external-links/_site/${page}`);

// Wait for the listing to be visible
await browserPage.waitForSelector(containerSelector);

// Find the link to the external article
const externalLink = browserPage.locator(externalLinkSelector);

await expect(externalLink.first()).toHaveAttribute("rel", "noopener");
});

test("Internal listing item does NOT have target=_blank attribute", async ({
page: browserPage,
}) => {
await browserPage.goto(`./listings/external-links/_site/${page}`);

// Wait for the listing to be visible
await browserPage.waitForSelector(containerSelector);

// Find the link to the local post by its text content
const internalLink = browserPage
.getByRole("link", { name: "Local Post" })
.first();

// Verify the link exists
await expect(internalLink).toBeVisible();

// Internal links should not have target="_blank"
await expect(internalLink).not.toHaveAttribute("target", "_blank");
});
});
});
Loading