From 1a8d49ca2a5f16b36a1dc9c5741006d5bf7d1830 Mon Sep 17 00:00:00 2001 From: Sreesanth R Date: Tue, 3 Nov 2026 16:39:46 +0530 Subject: [PATCH 1/3] fix(theme-classic): respect announcement bar id in inline dismissal script When an announcement bar id changes, the inline script previously only checked docusaurus.announcement.dismiss, keeping data-announcement-bar-initially-dismissed='true' even though a new announcement bar id was configured. Now getAnnouncementBarInlineScript checks configuredId against the stored viewedId, ensuring the announcement bar is visible immediately on first load after an id change. Fixes #12646 --- .../src/__tests__/inlineScripts.test.ts | 19 ++++++++++++++++++- .../docusaurus-theme-classic/src/index.ts | 2 +- .../src/inlineScripts.ts | 9 +++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/docusaurus-theme-classic/src/__tests__/inlineScripts.test.ts b/packages/docusaurus-theme-classic/src/__tests__/inlineScripts.test.ts index 2690e0b09c89..c84ce0a2b06f 100644 --- a/packages/docusaurus-theme-classic/src/__tests__/inlineScripts.test.ts +++ b/packages/docusaurus-theme-classic/src/__tests__/inlineScripts.test.ts @@ -6,7 +6,10 @@ */ import {describe, expect, it} from 'vitest'; -import {getThemeInlineScript} from '../inlineScripts'; +import { + getThemeInlineScript, + getAnnouncementBarInlineScript, +} from '../inlineScripts'; describe('inlineScripts', () => { it('inline javascript for default options', () => { @@ -40,4 +43,18 @@ describe('inlineScripts', () => { }), ).toMatchSnapshot(); }); + + it('inline javascript for announcement bar with id', () => { + expect( + getAnnouncementBarInlineScript({ + siteStorage: { + type: 'localStorage', + namespace: '', + }, + announcementBar: { + id: 'announcement-2', + }, + }), + ).toContain('var configuredId = "announcement-2";'); + }); }); diff --git a/packages/docusaurus-theme-classic/src/index.ts b/packages/docusaurus-theme-classic/src/index.ts index 8c5a236835a3..b57e3bf69eb1 100644 --- a/packages/docusaurus-theme-classic/src/index.ts +++ b/packages/docusaurus-theme-classic/src/index.ts @@ -134,7 +134,7 @@ export default function themeClassic( innerHTML: ` ${getThemeInlineScript({colorMode, siteStorage})} ${DataAttributeQueryStringInlineJavaScript} -${announcementBar ? getAnnouncementBarInlineScript({siteStorage}) : ''} +${announcementBar ? getAnnouncementBarInlineScript({siteStorage, announcementBar}) : ''} `, }, ], diff --git a/packages/docusaurus-theme-classic/src/inlineScripts.ts b/packages/docusaurus-theme-classic/src/inlineScripts.ts index fe49094a52c4..a0c660de3cf6 100644 --- a/packages/docusaurus-theme-classic/src/inlineScripts.ts +++ b/packages/docusaurus-theme-classic/src/inlineScripts.ts @@ -76,21 +76,30 @@ export const DataAttributeQueryStringInlineJavaScript = ` // the announcement bar asap (before React hydration) export function getAnnouncementBarInlineScript({ siteStorage, + announcementBar, }: { siteStorage: SiteStorage; + announcementBar?: {id?: string}; }): string { // Duplicated constant. Unfortunately we can't import it from theme-common, as // we need to support older nodejs versions without ESM support // TODO: import from theme-common once we support Node.js with ESM support // + move all those announcementBar stuff there too const AnnouncementBarDismissStorageKey = `docusaurus.announcement.dismiss${siteStorage.namespace}`; + const AnnouncementBarIdStorageKey = `docusaurus.announcement.id${siteStorage.namespace}`; const AnnouncementBarDismissDataAttribute = 'data-announcement-bar-initially-dismissed'; + const configuredId = announcementBar?.id; /* language=js */ return `(function() { function isDismissed() { try { + var configuredId = ${JSON.stringify(configuredId)}; + var viewedId = localStorage.getItem('${AnnouncementBarIdStorageKey}'); + if (configuredId && viewedId && viewedId !== configuredId) { + return false; + } return localStorage.getItem('${AnnouncementBarDismissStorageKey}') === 'true'; } catch (err) {} return false; From f882e531a7a7a9eb46f2add2c2f84481bb2d972f Mon Sep 17 00:00:00 2001 From: Sreesanth R Date: Tue, 3 Nov 2026 17:13:27 +0530 Subject: [PATCH 2/3] fix(theme-classic): treat missing viewedId as unviewed when configuredId is set Signed-off-by: Sreesanth R --- packages/docusaurus-theme-classic/src/inlineScripts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docusaurus-theme-classic/src/inlineScripts.ts b/packages/docusaurus-theme-classic/src/inlineScripts.ts index a0c660de3cf6..a4ab79bc7f1f 100644 --- a/packages/docusaurus-theme-classic/src/inlineScripts.ts +++ b/packages/docusaurus-theme-classic/src/inlineScripts.ts @@ -97,7 +97,7 @@ export function getAnnouncementBarInlineScript({ try { var configuredId = ${JSON.stringify(configuredId)}; var viewedId = localStorage.getItem('${AnnouncementBarIdStorageKey}'); - if (configuredId && viewedId && viewedId !== configuredId) { + if (configuredId && (!viewedId || viewedId !== configuredId)) { return false; } return localStorage.getItem('${AnnouncementBarDismissStorageKey}') === 'true'; From f2c72facd433196c623eb64ffe1543219b7150f6 Mon Sep 17 00:00:00 2001 From: Sreesanth R Date: Tue, 3 Nov 2026 17:24:52 +0530 Subject: [PATCH 3/3] test(theme-classic): add runtime evaluation tests for announcement bar inline script with IDs Signed-off-by: Sreesanth R --- .../src/__tests__/inlineScripts.test.ts | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/packages/docusaurus-theme-classic/src/__tests__/inlineScripts.test.ts b/packages/docusaurus-theme-classic/src/__tests__/inlineScripts.test.ts index c84ce0a2b06f..78000b3cd891 100644 --- a/packages/docusaurus-theme-classic/src/__tests__/inlineScripts.test.ts +++ b/packages/docusaurus-theme-classic/src/__tests__/inlineScripts.test.ts @@ -57,4 +57,68 @@ describe('inlineScripts', () => { }), ).toContain('var configuredId = "announcement-2";'); }); + + it('evaluates announcement bar dismissal correctly with configuredId and viewedId', () => { + function evaluateScript(script: string, storage: Record) { + let resultAttr = ''; + const fakeLocalStorage = { + getItem: (k: string) => storage[k] ?? null, + }; + const fakeDocElement = { + setAttribute: (_attr: string, val: string | boolean) => { + resultAttr = String(val); + }, + }; + // Execute the IIFE with stubbed globals + const fn = new Function('localStorage', 'document', script); + fn(fakeLocalStorage, {documentElement: fakeDocElement}); + return resultAttr; + } + + const scriptWithId = getAnnouncementBarInlineScript({ + siteStorage: { + type: 'localStorage', + namespace: '', + }, + announcementBar: { + id: 'announcement-2', + }, + }); + + // Case 1: No stored viewedId (or empty) despite previously dismissed announcement -> should NOT be dismissed + expect( + evaluateScript(scriptWithId, { + 'docusaurus.announcement.dismiss': 'true', + }), + ).toBe('false'); + + // Case 2: Stored viewedId differs from configuredId -> should NOT be dismissed + expect( + evaluateScript(scriptWithId, { + 'docusaurus.announcement.dismiss': 'true', + 'docusaurus.announcement.id': 'announcement-1', + }), + ).toBe('false'); + + // Case 3: Stored viewedId matches configuredId and dismissed -> should be dismissed + expect( + evaluateScript(scriptWithId, { + 'docusaurus.announcement.dismiss': 'true', + 'docusaurus.announcement.id': 'announcement-2', + }), + ).toBe('true'); + + // Case 4: No configuredId but dismissed -> should be dismissed + const scriptWithoutId = getAnnouncementBarInlineScript({ + siteStorage: { + type: 'localStorage', + namespace: '', + }, + }); + expect( + evaluateScript(scriptWithoutId, { + 'docusaurus.announcement.dismiss': 'true', + }), + ).toBe('true'); + }); });