mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-10 06:42:31 +02:00
fix(theme): fix announcement bar layout shift due to missing storage key namespace (#10144)
This commit is contained in:
parent
87f0023eb3
commit
ef627f813b
3 changed files with 126 additions and 108 deletions
|
@ -10,7 +10,12 @@ import {createRequire} from 'module';
|
||||||
import rtlcss from 'rtlcss';
|
import rtlcss from 'rtlcss';
|
||||||
import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations';
|
import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations';
|
||||||
import {getTranslationFiles, translateThemeConfig} from './translations';
|
import {getTranslationFiles, translateThemeConfig} from './translations';
|
||||||
import type {LoadContext, Plugin, SiteStorage} from '@docusaurus/types';
|
import {
|
||||||
|
getThemeInlineScript,
|
||||||
|
getAnnouncementBarInlineScript,
|
||||||
|
DataAttributeQueryStringInlineJavaScript,
|
||||||
|
} from './inlineScripts';
|
||||||
|
import type {LoadContext, Plugin} from '@docusaurus/types';
|
||||||
import type {ThemeConfig} from '@docusaurus/theme-common';
|
import type {ThemeConfig} from '@docusaurus/theme-common';
|
||||||
import type {Plugin as PostCssPlugin} from 'postcss';
|
import type {Plugin as PostCssPlugin} from 'postcss';
|
||||||
import type {PluginOptions} from '@docusaurus/theme-classic';
|
import type {PluginOptions} from '@docusaurus/theme-classic';
|
||||||
|
@ -23,105 +28,6 @@ const ContextReplacementPlugin = requireFromDocusaurusCore(
|
||||||
'webpack/lib/ContextReplacementPlugin',
|
'webpack/lib/ContextReplacementPlugin',
|
||||||
) as typeof webpack.ContextReplacementPlugin;
|
) as typeof webpack.ContextReplacementPlugin;
|
||||||
|
|
||||||
// Support for ?docusaurus-theme=dark
|
|
||||||
const ThemeQueryStringKey = 'docusaurus-theme';
|
|
||||||
// Support for ?docusaurus-data-mode=embed&docusaurus-data-myAttr=42
|
|
||||||
const DataQueryStringPrefixKey = 'docusaurus-data-';
|
|
||||||
|
|
||||||
const noFlashColorMode = ({
|
|
||||||
colorMode: {defaultMode, respectPrefersColorScheme},
|
|
||||||
siteStorage,
|
|
||||||
}: {
|
|
||||||
colorMode: ThemeConfig['colorMode'];
|
|
||||||
siteStorage: SiteStorage;
|
|
||||||
}) => {
|
|
||||||
// Need to be inlined to prevent dark mode FOUC
|
|
||||||
// Make sure the key is the same as the one in the color mode React context
|
|
||||||
// Currently defined in: `docusaurus-theme-common/src/contexts/colorMode.tsx`
|
|
||||||
const themeStorageKey = `theme${siteStorage.namespace}`;
|
|
||||||
|
|
||||||
/* language=js */
|
|
||||||
return `(function() {
|
|
||||||
var defaultMode = '${defaultMode}';
|
|
||||||
var respectPrefersColorScheme = ${respectPrefersColorScheme};
|
|
||||||
|
|
||||||
function setDataThemeAttribute(theme) {
|
|
||||||
document.documentElement.setAttribute('data-theme', theme);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getQueryStringTheme() {
|
|
||||||
try {
|
|
||||||
return new URLSearchParams(window.location.search).get('${ThemeQueryStringKey}')
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStoredTheme() {
|
|
||||||
try {
|
|
||||||
return window['${siteStorage.type}'].getItem('${themeStorageKey}');
|
|
||||||
} catch (err) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var initialTheme = getQueryStringTheme() || getStoredTheme();
|
|
||||||
if (initialTheme !== null) {
|
|
||||||
setDataThemeAttribute(initialTheme);
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
respectPrefersColorScheme &&
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
||||||
) {
|
|
||||||
setDataThemeAttribute('dark');
|
|
||||||
} else if (
|
|
||||||
respectPrefersColorScheme &&
|
|
||||||
window.matchMedia('(prefers-color-scheme: light)').matches
|
|
||||||
) {
|
|
||||||
setDataThemeAttribute('light');
|
|
||||||
} else {
|
|
||||||
setDataThemeAttribute(defaultMode === 'dark' ? 'dark' : 'light');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();`;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* language=js */
|
|
||||||
const DataAttributeQueryStringInlineJavaScript = `
|
|
||||||
(function() {
|
|
||||||
try {
|
|
||||||
const entries = new URLSearchParams(window.location.search).entries();
|
|
||||||
for (var [searchKey, value] of entries) {
|
|
||||||
if (searchKey.startsWith('${DataQueryStringPrefixKey}')) {
|
|
||||||
var key = searchKey.replace('${DataQueryStringPrefixKey}',"data-")
|
|
||||||
document.documentElement.setAttribute(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(e) {}
|
|
||||||
})();
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 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 only support Node.js with ESM support
|
|
||||||
// + move all those announcementBar stuff there too
|
|
||||||
export const AnnouncementBarDismissStorageKey =
|
|
||||||
'docusaurus.announcement.dismiss';
|
|
||||||
const AnnouncementBarDismissDataAttribute =
|
|
||||||
'data-announcement-bar-initially-dismissed';
|
|
||||||
// We always render the announcement bar html on the server, to prevent layout
|
|
||||||
// shifts on React hydration. The theme can use CSS + the data attribute to hide
|
|
||||||
// the announcement bar asap (before React hydration)
|
|
||||||
/* language=js */
|
|
||||||
const AnnouncementBarInlineJavaScript = `
|
|
||||||
(function() {
|
|
||||||
function isDismissed() {
|
|
||||||
try {
|
|
||||||
return localStorage.getItem('${AnnouncementBarDismissStorageKey}') === 'true';
|
|
||||||
} catch (err) {}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
document.documentElement.setAttribute('${AnnouncementBarDismissDataAttribute}', isDismissed());
|
|
||||||
})();`;
|
|
||||||
|
|
||||||
function getInfimaCSSFile(direction: string) {
|
function getInfimaCSSFile(direction: string) {
|
||||||
return `infima/dist/css/default/default${
|
return `infima/dist/css/default/default${
|
||||||
direction === 'rtl' ? '-rtl' : ''
|
direction === 'rtl' ? '-rtl' : ''
|
||||||
|
@ -227,9 +133,9 @@ export default function themeClassic(
|
||||||
{
|
{
|
||||||
tagName: 'script',
|
tagName: 'script',
|
||||||
innerHTML: `
|
innerHTML: `
|
||||||
${noFlashColorMode({colorMode, siteStorage})}
|
${getThemeInlineScript({colorMode, siteStorage})}
|
||||||
${DataAttributeQueryStringInlineJavaScript}
|
${DataAttributeQueryStringInlineJavaScript}
|
||||||
${announcementBar ? AnnouncementBarInlineJavaScript : ''}
|
${announcementBar ? getAnnouncementBarInlineScript({siteStorage}) : ''}
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
114
packages/docusaurus-theme-classic/src/inlineScripts.ts
Normal file
114
packages/docusaurus-theme-classic/src/inlineScripts.ts
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {SiteStorage} from '@docusaurus/types';
|
||||||
|
import type {ThemeConfig} from '@docusaurus/theme-common';
|
||||||
|
|
||||||
|
// Support for ?docusaurus-theme=dark
|
||||||
|
const ThemeQueryStringKey = 'docusaurus-theme';
|
||||||
|
|
||||||
|
// Support for ?docusaurus-data-mode=embed&docusaurus-data-myAttr=42
|
||||||
|
const DataQueryStringPrefixKey = 'docusaurus-data-';
|
||||||
|
|
||||||
|
export function getThemeInlineScript({
|
||||||
|
colorMode: {defaultMode, respectPrefersColorScheme},
|
||||||
|
siteStorage,
|
||||||
|
}: {
|
||||||
|
colorMode: ThemeConfig['colorMode'];
|
||||||
|
siteStorage: SiteStorage;
|
||||||
|
}): string {
|
||||||
|
// Need to be inlined to prevent dark mode FOUC
|
||||||
|
// Make sure the key is the same as the one in the color mode React context
|
||||||
|
// Currently defined in: `docusaurus-theme-common/src/contexts/colorMode.tsx`
|
||||||
|
const themeStorageKey = `theme${siteStorage.namespace}`;
|
||||||
|
|
||||||
|
/* language=js */
|
||||||
|
return `(function() {
|
||||||
|
var defaultMode = '${defaultMode}';
|
||||||
|
var respectPrefersColorScheme = ${respectPrefersColorScheme};
|
||||||
|
|
||||||
|
function setDataThemeAttribute(theme) {
|
||||||
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getQueryStringTheme() {
|
||||||
|
try {
|
||||||
|
return new URLSearchParams(window.location.search).get('${ThemeQueryStringKey}')
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStoredTheme() {
|
||||||
|
try {
|
||||||
|
return window['${siteStorage.type}'].getItem('${themeStorageKey}');
|
||||||
|
} catch (err) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var initialTheme = getQueryStringTheme() || getStoredTheme();
|
||||||
|
if (initialTheme !== null) {
|
||||||
|
setDataThemeAttribute(initialTheme);
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
respectPrefersColorScheme &&
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
) {
|
||||||
|
setDataThemeAttribute('dark');
|
||||||
|
} else if (
|
||||||
|
respectPrefersColorScheme &&
|
||||||
|
window.matchMedia('(prefers-color-scheme: light)').matches
|
||||||
|
) {
|
||||||
|
setDataThemeAttribute('light');
|
||||||
|
} else {
|
||||||
|
setDataThemeAttribute(defaultMode === 'dark' ? 'dark' : 'light');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* language=js */
|
||||||
|
export const DataAttributeQueryStringInlineJavaScript = `
|
||||||
|
(function() {
|
||||||
|
try {
|
||||||
|
const entries = new URLSearchParams(window.location.search).entries();
|
||||||
|
for (var [searchKey, value] of entries) {
|
||||||
|
if (searchKey.startsWith('${DataQueryStringPrefixKey}')) {
|
||||||
|
var key = searchKey.replace('${DataQueryStringPrefixKey}',"data-")
|
||||||
|
document.documentElement.setAttribute(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
})();
|
||||||
|
`;
|
||||||
|
|
||||||
|
// We always render the announcement bar html on the server, to prevent layout
|
||||||
|
// shifts on React hydration. The theme can use CSS + the data attribute to hide
|
||||||
|
// the announcement bar asap (before React hydration)
|
||||||
|
export function getAnnouncementBarInlineScript({
|
||||||
|
siteStorage,
|
||||||
|
}: {
|
||||||
|
siteStorage: SiteStorage;
|
||||||
|
}): 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 AnnouncementBarDismissDataAttribute =
|
||||||
|
'data-announcement-bar-initially-dismissed';
|
||||||
|
|
||||||
|
/* language=js */
|
||||||
|
return `(function() {
|
||||||
|
function isDismissed() {
|
||||||
|
try {
|
||||||
|
return localStorage.getItem('${AnnouncementBarDismissStorageKey}') === 'true';
|
||||||
|
} catch (err) {}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
document.documentElement.setAttribute('${AnnouncementBarDismissDataAttribute}', isDismissed());
|
||||||
|
})();`;
|
||||||
|
}
|
|
@ -18,14 +18,12 @@ import {createStorageSlot} from '../utils/storageUtils';
|
||||||
import {ReactContextError} from '../utils/reactUtils';
|
import {ReactContextError} from '../utils/reactUtils';
|
||||||
import {useThemeConfig} from '../utils/useThemeConfig';
|
import {useThemeConfig} from '../utils/useThemeConfig';
|
||||||
|
|
||||||
export const AnnouncementBarDismissStorageKey =
|
// Keep these keys in sync with the inlined script
|
||||||
'docusaurus.announcement.dismiss';
|
// See packages/docusaurus-theme-classic/src/inlineScripts.ts
|
||||||
const AnnouncementBarIdStorageKey = 'docusaurus.announcement.id';
|
|
||||||
|
|
||||||
const AnnouncementBarDismissStorage = createStorageSlot(
|
const AnnouncementBarDismissStorage = createStorageSlot(
|
||||||
AnnouncementBarDismissStorageKey,
|
'docusaurus.announcement.dismiss',
|
||||||
);
|
);
|
||||||
const IdStorage = createStorageSlot(AnnouncementBarIdStorageKey);
|
const IdStorage = createStorageSlot('docusaurus.announcement.id');
|
||||||
|
|
||||||
const isDismissedInStorage = () =>
|
const isDismissedInStorage = () =>
|
||||||
AnnouncementBarDismissStorage.get() === 'true';
|
AnnouncementBarDismissStorage.get() === 'true';
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue