Skip to content
Merged
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.10.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ All changes included in 1.10:
- ([#13565](https://github.com/quarto-dev/quarto-cli/issues/13565), [#14353](https://github.com/quarto-dev/quarto-cli/issues/14353)): Fix sidebar logo not appearing on secondary sidebars in multi-sidebar website layouts.
- ([#14562](https://github.com/quarto-dev/quarto-cli/issues/14562)): Fix a heading inside `content-hidden when-format="llms-txt"` (visible in HTML) losing its `<section>` wrapper and `id` in a website with `llms-txt` enabled, which broke its table-of-contents link, anchors, and cross-references.
- ([#14563](https://github.com/quarto-dev/quarto-cli/issues/14563)): Fix a fatal error when a shortcode is used inside conditional content (e.g. `content-visible when-format="llms-txt"`) in a website with `llms-txt` enabled.
- ([#14667](https://github.com/quarto-dev/quarto-cli/issues/14667)): Fix the clean-URL rewrite in `quarto-nav.js` breaking links to files whose names merely start with `index.html`, such as the `index.html.md` "Other Formats" links produced when `output-file: index.html` is paired with a markdown format (e.g. by nbdev for llms.txt workflows). The rewrite now only strips `index.html` at the end of the path.

## Commands

Expand Down
2 changes: 1 addition & 1 deletion src/project/types/website/website-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,5 +474,5 @@ export function containsHref(href: string, items: SidebarItem[]) {

export function itemHasNavTarget(item: SidebarItem, href: string) {
return item.href === href ||
item.href === href.replace(/\/index\.html/, "/");
item.href === href.replace(/\/index\.html(?=[?#]|$)/, "/");
}
5 changes: 4 additions & 1 deletion src/resources/projects/website/navigation/quarto-nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,10 @@ window.document.addEventListener("DOMContentLoaded", function () {
for (let i = 0; i < links.length; i++) {
if (links[i].href) {
links[i].dataset.originalHref = links[i].href;
links[i].href = links[i].href.replace(/\/index\.html/, "/");
// only strip index.html at the end of the path (optionally followed
// by a query or fragment) so that files like index.html.md
// (alternate-format twins) are left alone (#14667)
links[i].href = links[i].href.replace(/\/index\.html(?=[?#]|$)/, "/");
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/webui/quarto-preview/dist/quarto-preview.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/webui/quarto-preview/src/server/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ export function navigationHandler() {

return () => {
const normalizeTarget = (target: string) => {
return target.replace(/\/index\.html/, "/")
// only strip index.html at the end of the path so that files like
// index.html.md (alternate-format twins) are left alone (#14667)
return target.replace(/\/index\.html(?=[?#]|$)/, "/")
};

return (ev: MessageEvent<string>): boolean => {
Expand Down
2 changes: 2 additions & 0 deletions tests/docs/playwright/website/issue-14667/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.quarto/
/_site/
7 changes: 7 additions & 0 deletions tests/docs/playwright/website/issue-14667/_quarto.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
project:
type: website

website:
title: "issue-14667"

format: html
18 changes: 18 additions & 0 deletions tests/docs/playwright/website/issue-14667/index.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: Home
---

Links exercising the clean-URL rewrite in `quarto-nav.js` (issue #14667).
The rewrite must strip `index.html` only at the end of the path, leaving
names that merely start with `index.html` (like the `index.html.md`
markdown twins generated for llms.txt workflows) untouched.

- [markdown twin](index.html.md){#lnk-md} — must NOT be rewritten
- [clean url](sub/index.html){#lnk-clean} — must become `sub/`
- [fragment](index.html#section){#lnk-frag} — must become `#section` on `./`
- [query](index.html?a=1){#lnk-query} — must become `./?a=1`
- [path segment](index.html/foo){#lnk-segment} — must NOT be rewritten

## Section {#section}

Target for the fragment link.
5 changes: 5 additions & 0 deletions tests/docs/playwright/website/issue-14667/sub/index.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Sub page
---

Target page for the clean-URL link on the home page.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { expect, test } from "@playwright/test";

// quarto-nav.js rewrites every link on page load to clean up index.html
// URLs. The rewrite must only strip index.html at the end of the path
// (optionally followed by a query or fragment); hrefs that merely start
// with index.html — like the index.html.md markdown twins generated for
// llms.txt workflows — must be left alone (#14667).
test("index.html clean-URL rewrite only strips whole path segments", async ({ page }) => {
await page.goto("./website/issue-14667/_site/index.html");

// data-original-href is stamped by the rewrite loop, so waiting on it
// ensures quarto-nav.js has processed the links
const mdLink = page.locator("#lnk-md");
await expect(mdLink).toHaveAttribute("data-original-href", /\/index\.html\.md$/);

// a link to an index.html-prefixed file must not be rewritten
await expect(mdLink).toHaveAttribute("href", /\/index\.html\.md$/);

// ...while genuine index.html links still get cleaned up
await expect(page.locator("#lnk-clean")).toHaveAttribute("href", /\/sub\/$/);
await expect(page.locator("#lnk-frag")).toHaveAttribute("href", /\/_site\/#section$/);
await expect(page.locator("#lnk-query")).toHaveAttribute("href", /\/_site\/\?a=1$/);

// index.html as an intermediate path segment is not the index page of
// the link's directory, so it must be left alone too
await expect(page.locator("#lnk-segment")).toHaveAttribute("href", /\/index\.html\/foo$/);
});
Loading