diff --git a/.jekyll-metadata b/.jekyll-metadata new file mode 100644 index 0000000000..bdda6cca6b Binary files /dev/null and b/.jekyll-metadata differ diff --git a/_data/shared_chrome.json b/_data/shared_chrome.json new file mode 100644 index 0000000000..f162f18dd6 --- /dev/null +++ b/_data/shared_chrome.json @@ -0,0 +1,183 @@ +{ + "$schema": "https://www.metabase.com/shared/chrome.schema.json", + "version": 1, + "generated_at": "2026-05-22T16:22:46+00:00", + "origin": "https://www.metabase.com", + "stylesheets": [ + { + "tag": "link", + "attributes": { + "href": "https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,400;0,700;0,900&display=swap", + "rel": "stylesheet" + } + }, + { + "tag": "link", + "attributes": { + "href": "https://fonts.googleapis.com/css2?family=Fira+Code&display=swap", + "rel": "stylesheet" + } + }, + { + "tag": "link", + "attributes": { + "href": "https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;0,900;1,400;1,700&family=Roboto+Mono&display=swap", + "rel": "stylesheet" + } + }, + { + "tag": "link", + "attributes": { + "href": "https://www.metabase.com/gdpr-cookie-notice/dist/style.css", + "rel": "stylesheet" + } + }, + { + "tag": "link", + "attributes": { + "href": "https://www.metabase.com/css/docs.css", + "rel": "stylesheet" + } + }, + { + "tag": "link", + "attributes": { + "href": "https://www.metabase.com/css/styles.css", + "rel": "stylesheet" + } + }, + { + "tag": "link", + "attributes": { + "href": "https://www.metabase.com/css/main.css", + "rel": "stylesheet" + } + }, + { + "tag": "link", + "attributes": { + "href": "https://www.metabase.com/css/gdpr.css", + "rel": "stylesheet" + } + }, + { + "tag": "link", + "attributes": { + "href": "https://vjs.zencdn.net/7.20.3/video-js.css", + "rel": "stylesheet" + } + }, + { + "tag": "link", + "attributes": { + "href": "https://cdnjs.cloudflare.com/ajax/libs/flexboxgrid/6.3.1/flexboxgrid.min.css", + "rel": "stylesheet", + "type": "text/css" + } + }, + { + "tag": "link", + "attributes": { + "href": "https://vjs.zencdn.net/7.20.3/video-js.css", + "rel": "stylesheet" + } + }, + { + "tag": "link", + "attributes": { + "rel": "stylesheet", + "href": "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/base16/atelier-sulphurpool-light.min.css", + "integrity": "sha512-qrwjMzcPG1EHhhg9z2nH7VW7hj525RVx0o7GlxQc4ZhWkghp9slpnTjOUu5thnPaRfRqfAUlX7anvAqD9EonfQ==", + "crossorigin": "anonymous", + "referrerpolicy": "no-referrer" + } + } + ], + "scripts": [ + { + "tag": "script", + "attributes": { + "type": "text/javascript" + }, + "children": "\n window.environment = \"production\";\nwindow.HELP_IMPROVE_VIDEOJS = false;\n\nwindow.redirectTo = function redirectTo(theLocation) {\n if (typeof theLocation === \"string\") {\n theLocation = new URL(theLocation);\n }\n\n var redirectionSuffixes = [\"/index.html\", \".html\", \"/index\"];\n for (var i = 0; i < redirectionSuffixes.length; i = i + 1) {\n var redirectionSuffix = redirectionSuffixes[i];\n if (\n theLocation.pathname &&\n theLocation.pathname.length > redirectionSuffix.length &&\n theLocation.pathname.indexOf(redirectionSuffix) ===\n theLocation.pathname.length - redirectionSuffix.length\n ) {\n var pathname = theLocation.pathname.replace(redirectionSuffix, \"\");\n if (pathname[pathname.length - 1] === \"/\") {\n pathname = pathname.slice(0, -1);\n }\n\n return `${pathname}${theLocation.search}${theLocation.hash}`;\n }\n }\n\n return null;\n};\n\n " + }, + { + "tag": "script", + "attributes": { + "type": "application/ld+json" + }, + "children": "\n {\n \"@context\": \"https://schema.org\",\n \"@type\": \"Organization\",\n \"url\": \"https://www.metabase.com/\",\n \"sameAs\": [\n \"https://github.com/metabase\",\n \"https://www.linkedin.com/company/metabase/\",\n \"https://x.com/metabase\",\n \"https://www.youtube.com/@metabasedata\"\n ],\n \"logo\": \"https://github.com/metabase/metabase.github.io/blob/master/images/metabase-logo.png?raw=\",\n \"name\": \"Metabase\",\n \"description\": \"Metabase is an open source Business Intelligence and Embedded Analytics tool. Connect to your data stack in 5 minutes to make queries, visualizations, and dashboards easy for everyone—no SQL required\",\n \"email\": \"hello@metabase.com\",\n \"foundingDate\": \"2015-02-01T00:00:00.000Z\",\n \"address\": {\n \"@type\": \"PostalAddress\",\n \"streetAddress\": \"9740 Campo Rd\",\n \"addressLocality\": \"Spring Valley\",\n \"addressCountry\": \"USA\",\n \"addressRegion\": \"CA\",\n \"postalCode\": \"91977\"\n },\n \"knowsAbout\": [\n { \"@type\": \"Thing\", \"name\": \"Business Intelligence\" },\n { \"@type\": \"Thing\", \"name\": \"Embedded Analytics\" },\n { \"@type\": \"Thing\", \"name\": \"Self-service analytics\" },\n { \"@type\": \"Thing\", \"name\": \"Open source Business Intelligence\" }\n ]\n }\n" + }, + { + "tag": "script", + "attributes": { + "src": "https://www.metabase.com/js/navigation-header.js" + } + }, + { + "tag": "script", + "attributes": { + "src": "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" + } + }, + { + "tag": "script", + "attributes": { + "src": "https://www.metabase.com/js/promo-banner.js" + } + }, + { + "tag": "script", + "attributes": { + "type": "text/javascript" + }, + "children": "\n window.SENTRY_REPLAY_ENABLED = false;\n\n function sentryReplayInit () {\n if (window.SENTRY_REPLAY_ENABLED) {\n setTimeout(() => {\n if (!window.Sentry || !window.Sentry.lazyLoadIntegration) {\n return;\n }\n Sentry.lazyLoadIntegration(\"replayIntegration\").then(function (replayIntegration) {\n console.log({ loaded: !!replayIntegration, replayIntegration })\n window.Sentry.addIntegration(replayIntegration({ maskAllText: false }));\n });\n }, 500);\n }\n }\n\n function sentryInit() {\n Sentry.init({\n attachStacktrace: true,\n dsn:\n \"https://4b0be007af0a45f3bf6f59e5381ffe74@o408345.ingest.us.sentry.io/6722711\",\n environment: \"production\",\n tracesSampleRate: 0.1, // performance monitoring\n replaysSessionSampleRate: 0.05,\n enableLogs: true,\n ignoreErrors: [\n \"ResizeObserver loop limit exceeded\",\n \"ResizeObserver loop completed with undelivered notifications.\",\n ],\n sendDefaultPii: false,\n });\n sentryReplayInit();\n }\n\n if (window.SENTRY_REPLAY_ENABLED) {\n document.addEventListener(\"DOMContentLoaded\", sentryInit);\n } else {\n window.sentryOnLoad = sentryInit;\n }\n" + }, + { + "tag": "script", + "attributes": { + "src": "https://js.sentry-cdn.com/4b0be007af0a45f3bf6f59e5381ffe74.min.js", + "crossorigin": "anonymous", + "data-cfasync": "false" + } + }, + { + "tag": "script", + "attributes": { + "type": "text/javascript", + "src": "https://www.metabase.com/gdpr-cookie-notice/dist/script.js" + } + }, + { + "tag": "script", + "attributes": { + "type": "text/javascript", + "src": "https://www.metabase.com/js/cookie-consent.js" + } + }, + { + "tag": "script", + "attributes": { + "type": "text/javascript", + "src": "https://www.metabase.com/js/add-params-to-anchors.js" + } + }, + { + "tag": "script", + "attributes": { + "type": "text/javascript", + "src": "https://www.metabase.com/js/pass-referral-params-to-fillout-forms.js" + } + }, + { + "tag": "script", + "attributes": { + "type": "text/javascript", + "src": "https://www.metabase.com/js/ai-newsletter-subscribe-form.js", + "defer": "" + } + } + ], + "header_html": "\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n \n
\n
\n New\n Metabase 61: AI governance - access controls, token limits, Metabot customization, build dashboards with MCP, and more.\n \n Read the announcement\"Chevron\n \n
\n \n
\n\n\n \n\n
\n
\n \n\n \n
\n\n \n\n
\n \n\n \n\n \n\n \n\n
\n Pricing\n
\n
\n \n\n
\n \n Log in\n \n\n Get started\n
\n \n \n \n \n \n
\n
\n
\n
\n \n\n
\n\n \n\n\n\n\n\n \n\n\n\n\n\n \n\n\n\n\n\n \n\n\n\n\n \n Pricing\n \n\n \n Log in\n \n
\n\n
\n
\n\n\n\n", + "footer_html": "\n
\n \n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" +} diff --git a/_includes/head.html b/_includes/head.html index d4e342b55a..3ab2d88fe8 100644 --- a/_includes/head.html +++ b/_includes/head.html @@ -48,20 +48,7 @@ - - - - - - - {% unless page.layout == "docs-api" %} - - {% endunless %} - - + {% include shared-chrome/stylesheets.html %} {% if page.url contains "/data-stack-report-2023/" %} @@ -72,10 +59,6 @@ {% endif %} - - - {% if page.markdown_alternate %} @@ -86,23 +69,11 @@ {% include marketing-vwo.html %} - - - - - - - {% if jekyll.environment == "production" %} {% include sentry.html %} {% endif %} - - - + {% include shared-chrome/scripts.html %} {% if page.url contains "/pricing" or page.url contains "/product" or page.url @@ -121,14 +92,9 @@ {% endif %} - - - {% if page.url contains "/learn" or page.url contains "/docs" or page.url contains "/search" or page.url contains "/changelog" %} {% endif %} - - diff --git a/_includes/shared-chrome/footer.html b/_includes/shared-chrome/footer.html new file mode 100644 index 0000000000..6683461ed5 --- /dev/null +++ b/_includes/shared-chrome/footer.html @@ -0,0 +1 @@ +{{ site.data.shared_chrome.footer_html }} diff --git a/_includes/shared-chrome/header.html b/_includes/shared-chrome/header.html new file mode 100644 index 0000000000..86e3a06275 --- /dev/null +++ b/_includes/shared-chrome/header.html @@ -0,0 +1 @@ +{{ site.data.shared_chrome.header_html }} diff --git a/_includes/shared-chrome/scripts.html b/_includes/shared-chrome/scripts.html new file mode 100644 index 0000000000..03cb974f4d --- /dev/null +++ b/_includes/shared-chrome/scripts.html @@ -0,0 +1,11 @@ +{% for script in site.data.shared_chrome.scripts %} + {% assign script_src = script.attributes.src %} + + {% if script_src and script_src contains "sentry-cdn" %} + {% continue %} + {% endif %} + + {% if script_src %} + <{{ script.tag }}{% for attribute in script.attributes %} {{ attribute[0] }}="{{ attribute[1] }}"{% endfor %}> + {% endif %} +{% endfor %} diff --git a/_includes/shared-chrome/stylesheets.html b/_includes/shared-chrome/stylesheets.html new file mode 100644 index 0000000000..e1fee631a8 --- /dev/null +++ b/_includes/shared-chrome/stylesheets.html @@ -0,0 +1,3 @@ +{% for stylesheet in site.data.shared_chrome.stylesheets %} + <{{ stylesheet.tag }}{% for attribute in stylesheet.attributes %} {{ attribute[0] }}="{{ attribute[1] }}"{% endfor %} /> +{% endfor %} diff --git a/_layouts/default_new.html b/_layouts/default_new.html index 3505b4da64..bb8f7b9c03 100644 --- a/_layouts/default_new.html +++ b/_layouts/default_new.html @@ -4,13 +4,13 @@ {% include head.html %} - {% include navigation-header.html %} + {% include shared-chrome/header.html %}
{{ content }}
- {% include footer.html %} + {% include shared-chrome/footer.html %} diff --git a/package.json b/package.json index 898c0d0ae5..d5c504bb5e 100644 --- a/package.json +++ b/package.json @@ -39,10 +39,11 @@ "opengraph-glossary": "node script/_data/bin/opengraph.js --folder _glossary --properties title", "opengraph-learn": "node script/_data/bin/opengraph.js --folder _learn --properties title,image,categories", "all-opengraphs": "bun run opengraph-community && bun run opengraph-case-studies && bun run opengraph-dashboards && bun run opengraph-data-sources && bun run opengraph-docs && bun run opengraph-glossary && bun run opengraph-learn", + "shared-chrome": "node script/_data/bin/shared-chrome.js", "jekyll:clean": "rm -f -r _site && rm -f -r .sass-cache && rm -f .jekyll-metadata", - "jekyll:start": "bundle exec jekyll serve --config _config.dev.yml --livereload --incremental --port=4321", + "jekyll:start": "bun run shared-chrome && bundle exec jekyll serve --config _config.dev.yml --livereload --incremental --port=4321", "jekyll:clean-start": "bun run jekyll:clean && bun run jekyll:start", - "jekyll:build": "bundle exec jekyll build --config _config.yml", + "jekyll:build": "bun run shared-chrome && bundle exec jekyll build --config _config.yml", "frontend:dev": "webpack --watch --mode=development --stats-error-details", "frontend:build": "webpack --progress --mode=production", "cloudflare:remove-heavy-files": "find ./_site -type f -size +25M | xargs rm" diff --git a/script/_data/bin/shared-chrome.js b/script/_data/bin/shared-chrome.js new file mode 100644 index 0000000000..62b4ca73de --- /dev/null +++ b/script/_data/bin/shared-chrome.js @@ -0,0 +1,71 @@ +const { promises: fs } = require("fs"); +const https = require("https"); +const path = require("path"); + +const chromeUrl = + process.env.SHARED_CHROME_URL || + "https://www.metabase.com/shared/chrome.json"; +const outputPath = path.resolve(__dirname, "../../../_data/shared_chrome.json"); +const requiredKeys = ["stylesheets", "scripts", "header_html", "footer_html"]; + +function get(url) { + return new Promise((resolve, reject) => { + const request = https.get(url, (response) => { + const chunks = []; + + response.on("data", (chunk) => chunks.push(chunk)); + response.on("error", reject); + response.on("end", () => { + const { statusCode, statusMessage } = response; + const body = Buffer.concat(chunks).toString("utf8"); + + if (statusCode < 200 || statusCode >= 300) { + reject( + new Error( + `Failed to fetch shared chrome from ${url}: ${statusCode} ${statusMessage}`, + ), + ); + return; + } + + resolve(body); + }); + }); + + request.on("error", reject); + }); +} + +async function fetchJson(url) { + const body = await get(url); + return JSON.parse(body); +} + +function requireSharedChromeKeys(chrome) { + const missingKeys = requiredKeys.filter((key) => chrome[key] == null); + + if (missingKeys.length > 0) { + throw new Error( + `Shared chrome payload is missing required keys: ${missingKeys.join( + ", ", + )}`, + ); + } +} + +async function writeJson(filePath, data) { + await fs.writeFile(filePath, `${JSON.stringify(data, null, 2)}\n`); +} + +async function main() { + const chrome = await fetchJson(chromeUrl); + requireSharedChromeKeys(chrome); + + await writeJson(outputPath, chrome); + console.log(`Wrote ${outputPath}`); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/script/build b/script/build index e8d99cbc3e..3774852c83 100755 --- a/script/build +++ b/script/build @@ -3,4 +3,5 @@ # # Builds the site +node script/_data/bin/shared-chrome.js bundle exec jekyll build