diff --git a/news/changelog-1.6.md b/news/changelog-1.6.md index da91c282f1..75efe0bb01 100644 --- a/news/changelog-1.6.md +++ b/news/changelog-1.6.md @@ -79,6 +79,7 @@ All changes included in 1.6: - ([#10268](https://github.com/quarto-dev/quarto-cli/issues/10268)): `quarto create` supports opening project in Positron, in addition to VS Code and RStudio IDE. - ([#10285](https://github.com/quarto-dev/quarto-cli/issues/10285)): Include text from before the first chapter sections in search indices. In addition, include text of every element with `.quarto-include-in-search-index` class in search indices. +- ([#11189](https://github.com/quarto-dev/quarto-cli/issues/11189)): Exclude text of every element with the `.quarto-exclude-from-search-index` class from search indices. - ([#10566](https://github.com/quarto-dev/quarto-cli/issues/10566)): Ensure that `quarto run` outputs `stdout` and `stderr` to the correct streams. ### Websites diff --git a/src/project/types/website/website-search.ts b/src/project/types/website/website-search.ts index af16f5ba3c..227fa5ff59 100644 --- a/src/project/types/website/website-search.ts +++ b/src/project/types/website/website-search.ts @@ -291,6 +291,12 @@ export async function updateSearchIndex( } }); + // Remove all page elements that should be excluded from the search index + const exclusions = doc.querySelectorAll(".quarto-exclude-from-search-index"); + for (const exclusion of exclusions) { + exclusion._remove(); + } + // We always take the first child of the main region (whether that is a p or section) // and create an index entry for the page itself (with no hash). If there is other // 'unsectioned' content on the page, we include that as well. diff --git a/tests/docs/search/issue-11189/.gitignore b/tests/docs/search/issue-11189/.gitignore new file mode 100644 index 0000000000..dc7ba9ac52 --- /dev/null +++ b/tests/docs/search/issue-11189/.gitignore @@ -0,0 +1,6 @@ +/.quarto/ +*.html +*.pdf +*_files/ +/_site_with_filter/ +/_site_without_filter/ diff --git a/tests/docs/search/issue-11189/_extensions/remove-class/_extension.yml b/tests/docs/search/issue-11189/_extensions/remove-class/_extension.yml new file mode 100644 index 0000000000..821c0ff6c1 --- /dev/null +++ b/tests/docs/search/issue-11189/_extensions/remove-class/_extension.yml @@ -0,0 +1,8 @@ +title: Remove-class +author: Nick Vigilante +version: 1.0.0 +quarto-required: ">=99.9.0" +contributes: + filters: + - remove-class.lua + diff --git a/tests/docs/search/issue-11189/_extensions/remove-class/remove-class.lua b/tests/docs/search/issue-11189/_extensions/remove-class/remove-class.lua new file mode 100644 index 0000000000..968288bb68 --- /dev/null +++ b/tests/docs/search/issue-11189/_extensions/remove-class/remove-class.lua @@ -0,0 +1,8 @@ + +-- Reformat all heading text +function RemoveClass(el) + class = "quarto-exclude-from-search-index" + if el.classes:includes(class) then + el.classes:remove(class) + end +end diff --git a/tests/docs/search/issue-11189/_quarto-with-filter.yml b/tests/docs/search/issue-11189/_quarto-with-filter.yml new file mode 100644 index 0000000000..0dabcfd940 --- /dev/null +++ b/tests/docs/search/issue-11189/_quarto-with-filter.yml @@ -0,0 +1,4 @@ +project: + output-dir: _site_with_filter + post-render: + - check-index-with-filter.ts diff --git a/tests/docs/search/issue-11189/_quarto-without-filter.yml b/tests/docs/search/issue-11189/_quarto-without-filter.yml new file mode 100644 index 0000000000..a8335bd3c9 --- /dev/null +++ b/tests/docs/search/issue-11189/_quarto-without-filter.yml @@ -0,0 +1,4 @@ +project: + output-dir: _site_without_filter + post-render: + - check-index-without-filter.ts diff --git a/tests/docs/search/issue-11189/_quarto.yml b/tests/docs/search/issue-11189/_quarto.yml new file mode 100644 index 0000000000..cb0a9d56fa --- /dev/null +++ b/tests/docs/search/issue-11189/_quarto.yml @@ -0,0 +1,19 @@ +project: + type: website + +profile: + default: without-filter + +website: + title: "issue-11189" + navbar: + left: + - href: index.qmd + text: Home + - about.qmd + +format: + html: + theme: cosmo + css: styles.css + toc: true diff --git a/tests/docs/search/issue-11189/about.qmd b/tests/docs/search/issue-11189/about.qmd new file mode 100644 index 0000000000..70cdf334eb --- /dev/null +++ b/tests/docs/search/issue-11189/about.qmd @@ -0,0 +1,11 @@ +--- +title: "About" +--- + +About this site + +::: {.quarto-exclude-from-search-index} + +Please don't find me. + +::: diff --git a/tests/docs/search/issue-11189/check-index-with-filter.ts b/tests/docs/search/issue-11189/check-index-with-filter.ts new file mode 100644 index 0000000000..fdd3b166aa --- /dev/null +++ b/tests/docs/search/issue-11189/check-index-with-filter.ts @@ -0,0 +1,9 @@ +const json = JSON.parse(Deno.readTextFileSync("_site_with_filter/search.json")); + +const obj = Object.fromEntries(json.map((x: any) => [x.objectID, x])); + +const file = "index.html"; + +if (obj[file].text.match("Please find me.") === null) { + throw new Error("could not find text that should be shown in " + file); +}; diff --git a/tests/docs/search/issue-11189/check-index-without-filter.ts b/tests/docs/search/issue-11189/check-index-without-filter.ts new file mode 100644 index 0000000000..be41084fb2 --- /dev/null +++ b/tests/docs/search/issue-11189/check-index-without-filter.ts @@ -0,0 +1,9 @@ +const json = JSON.parse(Deno.readTextFileSync("_site_without_filter/search.json")); + +const obj = Object.fromEntries(json.map((x: any) => [x.objectID, x])); + +const file = "about.html"; + +if (obj[file].text.match("Please don't find me.") !== null) { + throw new Error("found text that should be hidden in " + file); +}; diff --git a/tests/docs/search/issue-11189/index.qmd b/tests/docs/search/issue-11189/index.qmd new file mode 100644 index 0000000000..81dc995ed3 --- /dev/null +++ b/tests/docs/search/issue-11189/index.qmd @@ -0,0 +1,15 @@ +--- +title: "issue-11189" +filters: + - _extensions/remove-class/remove-class.lua +--- + +This is a Quarto website. + +To learn more about Quarto websites visit . + +::: {.quarto-exclude-from-search-index} + +Please find me. + +::: diff --git a/tests/docs/search/issue-11189/styles.css b/tests/docs/search/issue-11189/styles.css new file mode 100644 index 0000000000..2ddf50c7b4 --- /dev/null +++ b/tests/docs/search/issue-11189/styles.css @@ -0,0 +1 @@ +/* css styles */ diff --git a/tests/smoke/search/validate-search-index-exclusion.test.ts b/tests/smoke/search/validate-search-index-exclusion.test.ts new file mode 100644 index 0000000000..93a68bbc4a --- /dev/null +++ b/tests/smoke/search/validate-search-index-exclusion.test.ts @@ -0,0 +1,40 @@ +import { testQuartoCmd } from "../../test.ts"; +import { fileExists, noErrorsOrWarnings } from "../../verify.ts"; + +import { existsSync } from "../../../src/deno_ral/fs.ts"; +import { join } from "../../../src/deno_ral/path.ts"; +import { docs } from "../../utils.ts"; + +// Test a simple site +const input = docs("search/issue-11189"); +const withFilterProfile = "--profile with-filter" + +testQuartoCmd( + "render", + [input], + [noErrorsOrWarnings], + { + name: "Test search exclusions without filter", + teardown: async () => { + const siteDir = join(input, "_site_without_filter"); + if (existsSync(siteDir)) { + await Deno.remove(siteDir, { recursive: true }); + } + }, + }, +); + +testQuartoCmd( + "render", + [withFilterProfile, input], + [noErrorsOrWarnings], + { + name: "Test search exclusions with filter", + teardown: async () => { + const siteDir = join(input, "_site_with_filter"); + if (existsSync(siteDir)) { + await Deno.remove(siteDir, { recursive: true }); + } + }, + }, +);