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)
- ([#12293](https://github.com/quarto-dev/quarto-cli/pull/12293)): Add support for `target` attribute on listing item links. Users can specify `target: "_blank"` in listing item YAML to open links in a new tab. Works with default, grid, and table listing types. (author: @mcanouil)

### `book`

Expand Down
2 changes: 1 addition & 1 deletion src/project/types/website/listing/website-listing-read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ function hydrateListing(
// If the items have come from metadata, we should just show
// all the columns in the table. Otherwise, we should use the
// document default columns
const undisplayable = ["path"];
const undisplayable = ["path", "target"];
return itemFields.filter((field) => {
return !undisplayable.includes(field);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,9 @@ export function reshapeListing(
const value = val || item[field] || " ";
const path = item.path;
if (path && value !== undefined && fieldLinks.includes(field)) {
return `<a href="${path}" class="${field}${
const targetAttr = item.target ? ` target="${item.target}"` : "";
const relAttr = item.target === "_blank" ? ` rel="noopener"` : "";
return `<a href="${path}"${targetAttr}${relAttr} class="${field}${
clz ? ` ${clz}` : ""
}">${value}</a>`;
} else {
Expand Down
12 changes: 6 additions & 6 deletions src/resources/projects/website/listing/item-default.ejs.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ return value;
let value = readField(item, field);
if (value !== undefined) {
print(`<div class="metadata-value listing-${field}">${listing.utilities.outputLink(item, field, value)}</div>`);
}
}
}
%>

Expand All @@ -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 %>" <%= item.target ? `target="${item.target}"` : "" %><%= item.target === "_blank" ? ` rel="noopener"` : "" %> class="no-external">
<% 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.target ? `target="${item.target}"` : "" %><%= item.target === "_blank" ? ` rel="noopener"` : "" %> class="no-external"><%= 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.target ? `target="${item.target}"` : "" %><%= item.target === "_blank" ? ` rel="noopener"` : "" %> class="no-external"><%= 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.target ? `target="${item.target}"` : "" %><%= item.target === "_blank" ? ` rel="noopener"` : "" %> class="no-external">
```

<%= 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 %>" <%= item.target ? `target="${item.target}"` : "" %><%= item.target === "_blank" ? ` rel="noopener"` : "" %> class="no-external">
```

<% if (fields.includes('date') && item.date) { %>
Expand Down
2 changes: 1 addition & 1 deletion src/resources/projects/website/listing/item-grid.ejs.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ return !["title", "image", "image-alt", "date", "author", "subtitle", "descripti
::: {.g-col-1 <%= listing.utilities.metadataAttrs(item) %> }

```{=html}
<a href="<%- item.path %>" class="quarto-grid-link">
<a href="<%- item.path %>" <%= item.target ? `target="${item.target}"` : "" %><%= item.target === "_blank" ? ` rel="noopener"` : "" %> class="quarto-grid-link">
<div class="quarto-grid-item card h-100 <%= `card-${align}` %><%= hideBorders ? ' borderless' : '' %>">
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ return false;
const hoverCls = listing['table-hover'] ? " table-hover" : "";
const onclick = (item) => {
if (listing['table-hover']) {
return ` onclick="href = this.querySelector('a').getAttribute('href');\n if (href) { window.location=href ; return false; }"`;
return ` onclick="var a = this.querySelector('a'); var href = a.getAttribute('href'); var target = a.getAttribute('target'); if (href) { if (target === '_blank') { window.open(href, '_blank'); } else { window.location = href; } return false; }"`;
} else {
return "";
}
Expand Down
4 changes: 4 additions & 0 deletions tests/docs/smoke-all/listings/target-attribute/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
_site/
.quarto/
/.quarto/
**/*.quarto_ipynb
10 changes: 10 additions & 0 deletions tests/docs/smoke-all/listings/target-attribute/_quarto.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
project:
type: website

website:
title: "Target Attribute Test"

format:
html:
theme: cosmo
toc: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
listing:
type: default
contents:
- title: "Post with target"
path: posts/post-with-target.qmd
target: "_blank"
- title: "Post without target"
path: posts/post-without-target.qmd
_quarto:
tests:
html:
noErrors: true
ensureHtmlElements:
-
- "a[href$='post-with-target.html'][target='_blank'][rel='noopener']"
- "a[href$='post-without-target.html']:not([target]):not([rel])"
ensureHtmlElementCount:
selectors: ['.listing-target']
counts: [0]
---

## Default listing with metadata specification test
15 changes: 15 additions & 0 deletions tests/docs/smoke-all/listings/target-attribute/index-default.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
listing:
type: default
contents: posts/*.qmd
_quarto:
tests:
html:
noErrors: true
ensureHtmlElements:
-
- "a[href$='post-with-target.html'][target='_blank'][rel='noopener']"
- "a[href$='post-without-target.html']:not([target]):not([rel])"
---

## Default listing with target attribute test
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
listing:
type: grid
contents:
- title: "Post with target"
path: posts/post-with-target.qmd
target: "_blank"
- title: "Post without target"
path: posts/post-without-target.qmd
_quarto:
tests:
html:
noErrors: true
ensureHtmlElements:
-
- "a[href$='post-with-target.html'][target='_blank'][rel='noopener']"
- "a[href$='post-without-target.html']:not([target]):not([rel])"
ensureHtmlElementCount:
selectors: ['.listing-target']
counts: [0]
---

## Grid listing with metadata specification test
15 changes: 15 additions & 0 deletions tests/docs/smoke-all/listings/target-attribute/index-grid.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
listing:
type: grid
contents: posts/*.qmd
_quarto:
tests:
html:
noErrors: true
ensureHtmlElements:
-
- "a[href$='post-with-target.html'][target='_blank'][rel='noopener']"
- "a[href$='post-without-target.html']:not([target]):not([rel])"
---

## Grid listing with target attribute test
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
listing:
type: table
contents:
- title: "Post with target"
path: posts/post-with-target.qmd
target: "_blank"
- title: "Post without target"
path: posts/post-without-target.qmd
table-hover: true
_quarto:
tests:
html:
noErrors: true
ensureHtmlElements:
-
- "a[href$='post-with-target.html'][target='_blank'][rel='noopener']"
- "a[href$='post-without-target.html']:not([target]):not([rel])"
- "tr[onclick*='window.open']"
ensureHtmlElementCount:
selectors: ['.listing-target']
counts: [0]
---

## Table listing with metadata specification test
17 changes: 17 additions & 0 deletions tests/docs/smoke-all/listings/target-attribute/index-table.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
listing:
type: table
contents: posts/*.qmd
table-hover: true
_quarto:
tests:
html:
noErrors: true
ensureHtmlElements:
-
- "a[href$='post-with-target.html'][target='_blank'][rel='noopener']"
- "a[href$='post-without-target.html']:not([target]):not([rel])"
- "tr[onclick*='window.open']"
---

## Table listing with target attribute test
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Post with target blank
target: "_blank"
---

This post should open in a new tab.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Post without target
---

This post should open in the same tab.
Loading