Skip to content

Commit e50377c

Browse files
authored
Merge pull request #13524 from quarto-dev/website/analytics-with-plausible
2 parents c9c36f0 + 932e21c commit e50377c

File tree

23 files changed

+352
-34
lines changed

23 files changed

+352
-34
lines changed

news/changelog-1.9.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ All changes included in 1.9:
3333
### `website`
3434

3535
- 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.
36+
- Add support for Plausible Analytics via `plausible-analytics` configuration option. Users can either paste their Plausible script snippet directly in YAML or provide a path to a file containing the snippet using `plausible-analytics: { path: _plausible_snippet.html }`.
3637

3738
## `publish`
3839

src/project/types/website/website-analytics.ts

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
import { Document } from "../../../core/deno-dom.ts";
77
import { join } from "../../../deno_ral/path.ts";
8+
import { existsSync } from "../../../deno_ral/fs.ts";
89
import { kLang, kTitle } from "../../../config/constants.ts";
910
import { Format, Metadata } from "../../../config/types.ts";
1011
import { projectTypeResourcePath } from "../../../core/resources.ts";
@@ -26,6 +27,10 @@ const kStorage = "storage";
2627
const kAnonymizeIp = "anonymize-ip";
2728
const kVersion = "version";
2829

30+
// Plausible analytics
31+
export const kPlausibleAnalytics = "plausible-analytics";
32+
const kPlausiblePath = "path";
33+
2934
// Cookie consent properties
3035
export const kCookieConsent = "cookie-consent";
3136
const kCookieConsentType = "type";
@@ -77,21 +82,19 @@ ${contents}
7782
}
7883
}
7984

80-
// Generate the script to inject into the head for Google Analytics
85+
// Generate the script to inject into the head for Google Analytics and/or Plausible
8186
export function websiteAnalyticsScriptFile(
8287
project: ProjectContext,
8388
temp: TempContext,
8489
) {
85-
// Find the ga tag
8690
const siteMeta = project.config?.[kWebsite] as Metadata;
87-
88-
// The google analytics metadata (either from the page or the site)
89-
// Deal with page and site options
90-
let gaConfig: GaConfiguration | undefined = undefined;
91+
const scripts: string[] = [];
9192

9293
if (siteMeta) {
94+
// Google Analytics
95+
let gaConfig: GaConfiguration | undefined = undefined;
9396
const siteGa = siteMeta[kGoogleAnalytics];
94-
if (typeof (siteGa) === "object") {
97+
if (typeof siteGa === "object") {
9598
const siteGaMeta = siteGa as Metadata;
9699
// Merge the site and page options and then layer over defaults
97100
const trackingId = siteGaMeta[kTrackingId] as string;
@@ -105,19 +108,45 @@ export function websiteAnalyticsScriptFile(
105108
anonymizedIp,
106109
version,
107110
);
108-
} else if (siteGa && typeof (siteGa) === "string") {
111+
} else if (siteGa && typeof siteGa === "string") {
109112
gaConfig = googleAnalyticsConfig(project, siteGa as string);
110113
}
111-
}
112114

113-
// Generate the actual GA dependencies
114-
if (gaConfig) {
115-
const script = analyticsScript(gaConfig);
116-
if (script) {
117-
return scriptFile(script, temp);
118-
} else {
119-
return undefined;
115+
if (gaConfig) {
116+
const gaScript = analyticsScript(gaConfig);
117+
if (gaScript) {
118+
scripts.push(gaScript);
119+
}
120120
}
121+
122+
// Plausible Analytics
123+
const plausibleSnippet = siteMeta[kPlausibleAnalytics];
124+
if (plausibleSnippet) {
125+
if (typeof plausibleSnippet === "string") {
126+
// Inline snippet provided directly in YAML
127+
scripts.push(plausibleSnippet);
128+
} else if (typeof plausibleSnippet === "object") {
129+
// Path to file containing snippet
130+
const plausibleMeta = plausibleSnippet as Metadata;
131+
const snippetPath = plausibleMeta[kPlausiblePath] as string;
132+
if (snippetPath) {
133+
const absolutePath = join(project.dir, snippetPath);
134+
if (existsSync(absolutePath)) {
135+
const snippetContent = Deno.readTextFileSync(absolutePath);
136+
scripts.push(snippetContent);
137+
} else {
138+
throw new Error(
139+
`Plausible Analytics snippet file not found: ${snippetPath}`,
140+
);
141+
}
142+
}
143+
}
144+
}
145+
}
146+
147+
// Return combined script file if we have any analytics
148+
if (scripts.length > 0) {
149+
return scriptFile(scripts.join("\n"), temp);
121150
} else {
122151
return undefined;
123152
}
@@ -138,7 +167,7 @@ export function cookieConsentDependencies(
138167
let configuration: CookieConsentConfiguration | undefined = undefined;
139168
let changePrefsText: string | undefined = undefined;
140169
const consent = siteMeta[kCookieConsent];
141-
if (typeof (consent) === "object") {
170+
if (typeof consent === "object") {
142171
const cookieMeta = consent as Metadata;
143172
configuration = cookieConsentConfiguration(
144173
title,
@@ -384,7 +413,7 @@ cookieconsent.run({
384413
}
385414

386415
function scriptFile(script: string, temp: TempContext) {
387-
const gaScriptFile = temp.createFile({ suffix: "-lytics.js" });
388-
Deno.writeTextFileSync(gaScriptFile, script);
389-
return gaScriptFile;
416+
const analyticsScriptFile = temp.createFile({ suffix: "-lytics.js" });
417+
Deno.writeTextFileSync(analyticsScriptFile, script);
418+
return analyticsScriptFile;
390419
}

src/resources/editor/tools/vs-code.mjs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9427,6 +9427,30 @@ var require_yaml_intelligence_resources = __commonJS({
94279427
],
94289428
description: "Enable Google Analytics for this website"
94299429
},
9430+
"plausible-analytics": {
9431+
anyOf: [
9432+
"string",
9433+
{
9434+
object: {
9435+
closed: true,
9436+
properties: {
9437+
path: {
9438+
path: {
9439+
description: "Path to a file containing the Plausible Analytics script snippet"
9440+
}
9441+
}
9442+
},
9443+
required: [
9444+
"path"
9445+
]
9446+
}
9447+
}
9448+
],
9449+
description: {
9450+
short: "Enable Plausible Analytics for this website by providing a script snippet or path to snippet file",
9451+
long: 'Enable Plausible Analytics for this website by pasting the script snippet from your Plausible dashboard,\nor by providing a path to a file containing the snippet.\n\nPlausible is a privacy-friendly, GDPR-compliant web analytics service that does not use cookies and does not require cookie consent.\n\n**Option 1: Inline snippet**\n\n```yaml\nwebsite:\n plausible-analytics: |\n <script async src="https://plausible.io/js/script.js"><\/script>\n```\n\n**Option 2: File path**\n\n```yaml\nwebsite:\n plausible-analytics:\n path: _plausible_snippet.html\n```\n\nTo get your script snippet:\n\n1. Log into your Plausible account at <https://plausible.io>\n2. Go to your site settings\n3. Copy the JavaScript snippet provided\n4. Either paste it directly in your configuration or save it to a file\n\nFor more information, see <https://plausible.io/docs/plausible-script>\n'
9452+
}
9453+
},
94309454
announcement: {
94319455
anyOf: [
94329456
"string",
@@ -21513,6 +21537,11 @@ var require_yaml_intelligence_resources = __commonJS({
2151321537
short: "The version number of Google Analytics to use.",
2151421538
long: "The version number of Google Analytics to use."
2151521539
},
21540+
{
21541+
short: "Enable Plausible Analytics for this website by providing a script\nsnippet or path to snippet file",
21542+
long: "Enable Plausible Analytics for this website by pasting the script\nsnippet from your Plausible dashboard, or by providing a path to a file\ncontaining the snippet.\nPlausible is a privacy-friendly, GDPR-compliant web analytics service\nthat does not use cookies and does not require cookie consent.\n<strong>Option 1: Inline snippet</strong>"
21543+
},
21544+
"Path to a file containing the Plausible Analytics script snippet",
2151621545
"Provides an announcement displayed at the top of the page.",
2151721546
"The content of the announcement",
2151821547
"Whether this announcement may be dismissed by the user.",
@@ -21674,6 +21703,11 @@ var require_yaml_intelligence_resources = __commonJS({
2167421703
short: "The version number of Google Analytics to use.",
2167521704
long: "The version number of Google Analytics to use."
2167621705
},
21706+
{
21707+
short: "Enable Plausible Analytics for this website by providing a script\nsnippet or path to snippet file",
21708+
long: "Enable Plausible Analytics for this website by pasting the script\nsnippet from your Plausible dashboard, or by providing a path to a file\ncontaining the snippet.\nPlausible is a privacy-friendly, GDPR-compliant web analytics service\nthat does not use cookies and does not require cookie consent.\n<strong>Option 1: Inline snippet</strong>"
21709+
},
21710+
"Path to a file containing the Plausible Analytics script snippet",
2167721711
"Provides an announcement displayed at the top of the page.",
2167821712
"The content of the announcement",
2167921713
"Whether this announcement may be dismissed by the user.",
@@ -24060,6 +24094,11 @@ var require_yaml_intelligence_resources = __commonJS({
2406024094
short: "The version number of Google Analytics to use.",
2406124095
long: "The version number of Google Analytics to use."
2406224096
},
24097+
{
24098+
short: "Enable Plausible Analytics for this website by providing a script\nsnippet or path to snippet file",
24099+
long: "Enable Plausible Analytics for this website by pasting the script\nsnippet from your Plausible dashboard, or by providing a path to a file\ncontaining the snippet.\nPlausible is a privacy-friendly, GDPR-compliant web analytics service\nthat does not use cookies and does not require cookie consent.\n<strong>Option 1: Inline snippet</strong>"
24100+
},
24101+
"Path to a file containing the Plausible Analytics script snippet",
2406324102
"Provides an announcement displayed at the top of the page.",
2406424103
"The content of the announcement",
2406524104
"Whether this announcement may be dismissed by the user.",
@@ -24410,6 +24449,11 @@ var require_yaml_intelligence_resources = __commonJS({
2441024449
short: "The version number of Google Analytics to use.",
2441124450
long: "The version number of Google Analytics to use."
2441224451
},
24452+
{
24453+
short: "Enable Plausible Analytics for this website by providing a script\nsnippet or path to snippet file",
24454+
long: "Enable Plausible Analytics for this website by pasting the script\nsnippet from your Plausible dashboard, or by providing a path to a file\ncontaining the snippet.\nPlausible is a privacy-friendly, GDPR-compliant web analytics service\nthat does not use cookies and does not require cookie consent.\n<strong>Option 1: Inline snippet</strong>"
24455+
},
24456+
"Path to a file containing the Plausible Analytics script snippet",
2441324457
"Provides an announcement displayed at the top of the page.",
2441424458
"The content of the announcement",
2441524459
"Whether this announcement may be dismissed by the user.",
@@ -24933,12 +24977,12 @@ var require_yaml_intelligence_resources = __commonJS({
2493324977
mermaid: "%%"
2493424978
},
2493524979
"handlers/mermaid/schema.yml": {
24936-
_internalId: 197474,
24980+
_internalId: 197491,
2493724981
type: "object",
2493824982
description: "be an object",
2493924983
properties: {
2494024984
"mermaid-format": {
24941-
_internalId: 197466,
24985+
_internalId: 197483,
2494224986
type: "enum",
2494324987
enum: [
2494424988
"png",
@@ -24954,7 +24998,7 @@ var require_yaml_intelligence_resources = __commonJS({
2495424998
exhaustiveCompletions: true
2495524999
},
2495625000
theme: {
24957-
_internalId: 197473,
25001+
_internalId: 197490,
2495825002
type: "anyOf",
2495925003
anyOf: [
2496025004
{

src/resources/editor/tools/yaml/all-schema-definitions.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/resources/editor/tools/yaml/web-worker.js

Lines changed: 47 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)