From 8be8081147ea0498f695002d03be9b95afd6fdce Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 9 Oct 2025 17:56:58 +0200 Subject: [PATCH 1/4] Add correct cookie consent handling to search options and Algolia initialization when `website.cookie-consent: false` we need to make sure that Algolia Insigth does not use cookie --- src/project/types/website/website-search.ts | 4 ++++ .../projects/website/search/quarto-search.js | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/project/types/website/website-search.ts b/src/project/types/website/website-search.ts index 89b21c61108..875c0fe8556 100644 --- a/src/project/types/website/website-search.ts +++ b/src/project/types/website/website-search.ts @@ -117,6 +117,7 @@ const kTitle = "title"; const kText = "text"; const kAnalyticsEvents = "analytics-events"; const kShowLogo = "show-logo"; +const kCookieConsentEnabled = "cookie-consent-enabled"; interface SearchOptionsAlgolia { [kSearchOnlyApiKey]?: string; @@ -131,6 +132,7 @@ interface SearchOptionsAlgolia { [kSearchParams]?: Record; [kAnalyticsEvents]?: boolean; [kShowLogo]?: boolean; + [kCookieConsentEnabled]?: boolean; } export type SearchInputLocation = "navbar" | "sidebar"; @@ -624,6 +626,8 @@ export async function websiteSearchIncludeInHeader( includes.push(kAlogioSearchApiScript); if (options[kAlgolia]?.[kAnalyticsEvents]) { const cookieConsent = cookieConsentEnabled(project); + // Pass cookie consent status to JavaScript for Algolia Insights configuration + options[kAlgolia][kCookieConsentEnabled] = cookieConsent; includes.push(algoliaSearchInsightsScript(cookieConsent)); includes.push(autocompleteInsightsPluginScript(cookieConsent)); } diff --git a/src/resources/projects/website/search/quarto-search.js b/src/resources/projects/website/search/quarto-search.js index d788a958127..68a7c8a2e7b 100644 --- a/src/resources/projects/website/search/quarto-search.js +++ b/src/resources/projects/website/search/quarto-search.js @@ -466,10 +466,19 @@ function configurePlugins(quartoSearchOptions) { window.aa && window["@algolia/autocomplete-plugin-algolia-insights"] ) { + // Check if cookie consent is enabled from search options + const cookieConsentEnabled = algoliaOptions["cookie-consent-enabled"] || false; + + // Generate random session token only when cookies are disabled + const userToken = cookieConsentEnabled ? undefined : Array.from(Array(20), () => + Math.floor(Math.random() * 36).toString(36) + ).join(""); + window.aa("init", { appId, apiKey, - useCookie: true, + useCookie: cookieConsentEnabled, + userToken: userToken, }); const { createAlgoliaInsightsPlugin } = From a6bc00ee18bd90583c6cd5b5f4c9bc8ea5021977 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 9 Oct 2025 18:33:42 +0200 Subject: [PATCH 2/4] Add tests and fix issue in passing options --- src/project/types/website/website-search.ts | 18 ++++++++----- .../search/algolia-no-consent/.gitignore | 2 ++ .../search/algolia-no-consent/_quarto.yml | 17 +++++++++++++ .../search/algolia-no-consent/index.qmd | 25 +++++++++++++++++++ .../search/algolia-with-consent/.gitignore | 2 ++ .../search/algolia-with-consent/_quarto.yml | 16 ++++++++++++ .../search/algolia-with-consent/index.qmd | 25 +++++++++++++++++++ 7 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 tests/docs/smoke-all/search/algolia-no-consent/.gitignore create mode 100644 tests/docs/smoke-all/search/algolia-no-consent/_quarto.yml create mode 100644 tests/docs/smoke-all/search/algolia-no-consent/index.qmd create mode 100644 tests/docs/smoke-all/search/algolia-with-consent/.gitignore create mode 100644 tests/docs/smoke-all/search/algolia-with-consent/_quarto.yml create mode 100644 tests/docs/smoke-all/search/algolia-with-consent/index.qmd diff --git a/src/project/types/website/website-search.ts b/src/project/types/website/website-search.ts index 875c0fe8556..c2073928042 100644 --- a/src/project/types/website/website-search.ts +++ b/src/project/types/website/website-search.ts @@ -617,22 +617,28 @@ export async function websiteSearchIncludeInHeader( } }); - const searchOptionsJson = JSON.stringify(options, null, 2); - const searchOptionsScript = - ``; - const includes = [searchOptionsScript]; + const includes: string[] = []; + // Process all Algolia configuration and scripts if (options[kAlgolia]) { includes.push(kAlogioSearchApiScript); - if (options[kAlgolia]?.[kAnalyticsEvents]) { + if (options[kAlgolia][kAnalyticsEvents]) { const cookieConsent = cookieConsentEnabled(project); - // Pass cookie consent status to JavaScript for Algolia Insights configuration + // Set cookie consent flag for JavaScript configuration options[kAlgolia][kCookieConsentEnabled] = cookieConsent; + // Add Algolia Insights scripts with proper consent handling includes.push(algoliaSearchInsightsScript(cookieConsent)); includes.push(autocompleteInsightsPluginScript(cookieConsent)); } } + // Serialize search options to JSON after all modifications + const searchOptionsJson = JSON.stringify(options, null, 2); + const searchOptionsScript = + ``; + // Prepend search options script to beginning of includes + includes.unshift(searchOptionsScript); + Deno.writeTextFileSync(websiteSearchScript, includes.join("\n")); return websiteSearchScript; } diff --git a/tests/docs/smoke-all/search/algolia-no-consent/.gitignore b/tests/docs/smoke-all/search/algolia-no-consent/.gitignore new file mode 100644 index 00000000000..ad293093b07 --- /dev/null +++ b/tests/docs/smoke-all/search/algolia-no-consent/.gitignore @@ -0,0 +1,2 @@ +/.quarto/ +**/*.quarto_ipynb diff --git a/tests/docs/smoke-all/search/algolia-no-consent/_quarto.yml b/tests/docs/smoke-all/search/algolia-no-consent/_quarto.yml new file mode 100644 index 00000000000..bc1ff035729 --- /dev/null +++ b/tests/docs/smoke-all/search/algolia-no-consent/_quarto.yml @@ -0,0 +1,17 @@ +project: + type: website + +website: + title: "Algolia Without Cookie Consent Test" + search: + algolia: + application-id: "TEST_APP_ID" + search-only-api-key: "TEST_API_KEY" + index-name: "test-index" + analytics-events: true + # This is the default and should act as such + # cookie-consent: false + +format: + html: + theme: cosmo diff --git a/tests/docs/smoke-all/search/algolia-no-consent/index.qmd b/tests/docs/smoke-all/search/algolia-no-consent/index.qmd new file mode 100644 index 00000000000..618d5466037 --- /dev/null +++ b/tests/docs/smoke-all/search/algolia-no-consent/index.qmd @@ -0,0 +1,25 @@ +--- +title: "Algolia Search Without Cookie Consent" +_quarto: + tests: + html: + ensureHtmlElements: + # Ensure search options script exists + - ['script#quarto-search-options'] + ensureFileRegexMatches: + - + # Cookie consent should be false + - '"cookie-consent-enabled":\s*false' + # Scripts should load immediately with type="text/javascript" + - ']*type="text/javascript">[\s\S]+search-insights' + - + # Verify scripts do NOT have cookie-consent attribute + - ']*cookie-consent[^>]+>[\s\S]*search-insights' +--- + +This test verifies that when `cookie-consent` is NOT configured in the website settings: + +1. The Algolia search options JSON contains `"cookie-consent-enabled": false` (or the key is absent) +2. Algolia Insights scripts are loaded immediately with `type="text/javascript"` +3. Scripts are not deferred behind cookie consent (no `cookie-consent="tracking"` attribute) + diff --git a/tests/docs/smoke-all/search/algolia-with-consent/.gitignore b/tests/docs/smoke-all/search/algolia-with-consent/.gitignore new file mode 100644 index 00000000000..ad293093b07 --- /dev/null +++ b/tests/docs/smoke-all/search/algolia-with-consent/.gitignore @@ -0,0 +1,2 @@ +/.quarto/ +**/*.quarto_ipynb diff --git a/tests/docs/smoke-all/search/algolia-with-consent/_quarto.yml b/tests/docs/smoke-all/search/algolia-with-consent/_quarto.yml new file mode 100644 index 00000000000..5a2effde7ea --- /dev/null +++ b/tests/docs/smoke-all/search/algolia-with-consent/_quarto.yml @@ -0,0 +1,16 @@ +project: + type: website + +website: + title: "Algolia With Cookie Consent Test" + search: + algolia: + application-id: "TEST_APP_ID" + search-only-api-key: "TEST_API_KEY" + index-name: "test-index" + analytics-events: true + cookie-consent: true + +format: + html: + theme: cosmo diff --git a/tests/docs/smoke-all/search/algolia-with-consent/index.qmd b/tests/docs/smoke-all/search/algolia-with-consent/index.qmd new file mode 100644 index 00000000000..9ac8cb5c8f1 --- /dev/null +++ b/tests/docs/smoke-all/search/algolia-with-consent/index.qmd @@ -0,0 +1,25 @@ +--- +title: "Algolia Search With Cookie Consent" +_quarto: + tests: + html: + ensureHtmlElements: + - + # Ensure search options script exists + - 'script#quarto-search-options' + # Cookie consent element are loaded + - script[src$='cookie-consent.js'] + ensureFileRegexMatches: + - + # Cookie consent should be enabled + - '"cookie-consent-enabled":\s*true' + # Scripts should be deferred with cookie-consent attribute + - 'type="text/plain"[^>]*cookie-consent="tracking"[^>]*>[\s\S]*search-insights' +--- + +This test verifies that when `cookie-consent` is configured in the website settings: + +1. The Algolia search options JSON contains `"cookie-consent-enabled": true` +2. Algolia Insights scripts are deferred with `type="text/plain" cookie-consent="tracking"` +3. Scripts only execute after user grants "tracking" consent +4. Cookie consent UI elements are present on the page \ No newline at end of file From 370043d1f15fe58cd8c59cee95a1b8ddce828d0b Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 9 Oct 2025 18:34:37 +0200 Subject: [PATCH 3/4] move to better test name folder --- .../{search => website-search}/algolia-no-consent/.gitignore | 0 .../{search => website-search}/algolia-no-consent/_quarto.yml | 0 .../{search => website-search}/algolia-no-consent/index.qmd | 0 .../{search => website-search}/algolia-with-consent/.gitignore | 0 .../{search => website-search}/algolia-with-consent/_quarto.yml | 0 .../{search => website-search}/algolia-with-consent/index.qmd | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename tests/docs/smoke-all/{search => website-search}/algolia-no-consent/.gitignore (100%) rename tests/docs/smoke-all/{search => website-search}/algolia-no-consent/_quarto.yml (100%) rename tests/docs/smoke-all/{search => website-search}/algolia-no-consent/index.qmd (100%) rename tests/docs/smoke-all/{search => website-search}/algolia-with-consent/.gitignore (100%) rename tests/docs/smoke-all/{search => website-search}/algolia-with-consent/_quarto.yml (100%) rename tests/docs/smoke-all/{search => website-search}/algolia-with-consent/index.qmd (100%) diff --git a/tests/docs/smoke-all/search/algolia-no-consent/.gitignore b/tests/docs/smoke-all/website-search/algolia-no-consent/.gitignore similarity index 100% rename from tests/docs/smoke-all/search/algolia-no-consent/.gitignore rename to tests/docs/smoke-all/website-search/algolia-no-consent/.gitignore diff --git a/tests/docs/smoke-all/search/algolia-no-consent/_quarto.yml b/tests/docs/smoke-all/website-search/algolia-no-consent/_quarto.yml similarity index 100% rename from tests/docs/smoke-all/search/algolia-no-consent/_quarto.yml rename to tests/docs/smoke-all/website-search/algolia-no-consent/_quarto.yml diff --git a/tests/docs/smoke-all/search/algolia-no-consent/index.qmd b/tests/docs/smoke-all/website-search/algolia-no-consent/index.qmd similarity index 100% rename from tests/docs/smoke-all/search/algolia-no-consent/index.qmd rename to tests/docs/smoke-all/website-search/algolia-no-consent/index.qmd diff --git a/tests/docs/smoke-all/search/algolia-with-consent/.gitignore b/tests/docs/smoke-all/website-search/algolia-with-consent/.gitignore similarity index 100% rename from tests/docs/smoke-all/search/algolia-with-consent/.gitignore rename to tests/docs/smoke-all/website-search/algolia-with-consent/.gitignore diff --git a/tests/docs/smoke-all/search/algolia-with-consent/_quarto.yml b/tests/docs/smoke-all/website-search/algolia-with-consent/_quarto.yml similarity index 100% rename from tests/docs/smoke-all/search/algolia-with-consent/_quarto.yml rename to tests/docs/smoke-all/website-search/algolia-with-consent/_quarto.yml diff --git a/tests/docs/smoke-all/search/algolia-with-consent/index.qmd b/tests/docs/smoke-all/website-search/algolia-with-consent/index.qmd similarity index 100% rename from tests/docs/smoke-all/search/algolia-with-consent/index.qmd rename to tests/docs/smoke-all/website-search/algolia-with-consent/index.qmd From d49b5c1099f72096be7eb26e33266ff7b78575ce Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 9 Oct 2025 18:43:34 +0200 Subject: [PATCH 4/4] Add to changelog --- news/changelog-1.9.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/news/changelog-1.9.md b/news/changelog-1.9.md index 7c00fa71624..901a034fc88 100644 --- a/news/changelog-1.9.md +++ b/news/changelog-1.9.md @@ -28,6 +28,12 @@ All changes included in 1.9: - ([#13452](https://github.com/quarto-dev/quarto-cli/issues/13452)): Wraps subfigure captions generated by `quarto_super()` in `block` function to avoid emitting `par` elements. (author: @christopherkenny) - ([#13474](https://github.com/quarto-dev/quarto-cli/issues/13474)): Heading font for title should default to `mainfont`. +## Projects + +### `website` + +- Algolia Insights now uses privacy-friendly defaults: `useCookie: false` with random session tokens when cookie consent is not configured. When `cookie-consent: true` is enabled, Algolia scripts are deferred and only use cookies after user grants "tracking" consent, ensuring GDPR compliance. + ## `publish` ### Confluence