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
6 changes: 6 additions & 0 deletions news/changelog-1.9.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 15 additions & 5 deletions src/project/types/website/website-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -131,6 +132,7 @@ interface SearchOptionsAlgolia {
[kSearchParams]?: Record<string, unknown>;
[kAnalyticsEvents]?: boolean;
[kShowLogo]?: boolean;
[kCookieConsentEnabled]?: boolean;
}

export type SearchInputLocation = "navbar" | "sidebar";
Expand Down Expand Up @@ -615,20 +617,28 @@ export async function websiteSearchIncludeInHeader(
}
});

const searchOptionsJson = JSON.stringify(options, null, 2);
const searchOptionsScript =
`<script id="quarto-search-options" type="application/json">${searchOptionsJson}</script>`;
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);
// 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 =
`<script id="quarto-search-options" type="application/json">${searchOptionsJson}</script>`;
// Prepend search options script to beginning of includes
includes.unshift(searchOptionsScript);

Deno.writeTextFileSync(websiteSearchScript, includes.join("\n"));
return websiteSearchScript;
}
Expand Down
11 changes: 10 additions & 1 deletion src/resources/projects/website/search/quarto-search.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 } =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.quarto/
**/*.quarto_ipynb
17 changes: 17 additions & 0 deletions tests/docs/smoke-all/website-search/algolia-no-consent/_quarto.yml
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions tests/docs/smoke-all/website-search/algolia-no-consent/index.qmd
Original file line number Diff line number Diff line change
@@ -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"
- '<script[^>]*type="text/javascript">[\s\S]+search-insights'
-
# Verify scripts do NOT have cookie-consent attribute
- '<script[^>]*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)

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.quarto/
**/*.quarto_ipynb
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions tests/docs/smoke-all/website-search/algolia-with-consent/index.qmd
Original file line number Diff line number Diff line change
@@ -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
Loading