From 37a57aea2a76bf52ecbaf3e32974e535b5efdcb6 Mon Sep 17 00:00:00 2001 From: Hashim Khan <64767361+Hashim1999164@users.noreply.github.com> Date: Fri, 21 Aug 2026 19:27:18 +0500 Subject: [PATCH 1/6] fix(minifier): Keep attribute quotes in the SWC HTML minifier (#12369) --- packages/docusaurus-bundler/src/minifyHtml.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/docusaurus-bundler/src/minifyHtml.ts b/packages/docusaurus-bundler/src/minifyHtml.ts index 226fb08f2ba2..f76f4bb48848 100644 --- a/packages/docusaurus-bundler/src/minifyHtml.ts +++ b/packages/docusaurus-bundler/src/minifyHtml.ts @@ -88,6 +88,11 @@ async function getSwcMinifier(): Promise { // See https://github.com/swc-project/swc/issues/10994 tagOmission: 'keep-head-and-body', + // Keep attribute quotes. WhatsApp and some RDFa parsers ignore + // og:image when the minifier emits property=og:image without quotes. + // See https://github.com/facebook/docusaurus/issues/12368 + quotes: true, + // Sorting these attributes (class) can lead to React hydration errors sortSpaceSeparatedAttributeValues: false, sortAttributes: false, From d7d7498de413dc9c84776432e69aa4dcb5e1402a Mon Sep 17 00:00:00 2001 From: Orkhan Abbasli Date: Fri, 21 Aug 2026 18:27:57 +0400 Subject: [PATCH 2/6] feat(theme-translations): add Azerbaijani (az) default theme translations (#12370) --- .../locales/az/plugin-ideal-image.json | 7 ++ .../locales/az/plugin-pwa.json | 5 ++ .../locales/az/theme-common.json | 84 +++++++++++++++++++ .../locales/az/theme-live-codeblock.json | 5 ++ .../locales/az/theme-search-algolia.json | 60 +++++++++++++ 5 files changed, 161 insertions(+) create mode 100644 packages/docusaurus-theme-translations/locales/az/plugin-ideal-image.json create mode 100644 packages/docusaurus-theme-translations/locales/az/plugin-pwa.json create mode 100644 packages/docusaurus-theme-translations/locales/az/theme-common.json create mode 100644 packages/docusaurus-theme-translations/locales/az/theme-live-codeblock.json create mode 100644 packages/docusaurus-theme-translations/locales/az/theme-search-algolia.json diff --git a/packages/docusaurus-theme-translations/locales/az/plugin-ideal-image.json b/packages/docusaurus-theme-translations/locales/az/plugin-ideal-image.json new file mode 100644 index 000000000000..accd2e5b50e7 --- /dev/null +++ b/packages/docusaurus-theme-translations/locales/az/plugin-ideal-image.json @@ -0,0 +1,7 @@ +{ + "theme.IdealImageMessage.404error": "404. Təsvir tapılmadı", + "theme.IdealImageMessage.error": "Xəta. Yeniləmək üçün klik edin.", + "theme.IdealImageMessage.load": "Yükləmək üçün klik edin{sizeMessage}", + "theme.IdealImageMessage.loading": "Yüklənir...", + "theme.IdealImageMessage.offline": "Brauzer oflayndır. Təsvir yüklənmədi" +} diff --git a/packages/docusaurus-theme-translations/locales/az/plugin-pwa.json b/packages/docusaurus-theme-translations/locales/az/plugin-pwa.json new file mode 100644 index 000000000000..4a96350db073 --- /dev/null +++ b/packages/docusaurus-theme-translations/locales/az/plugin-pwa.json @@ -0,0 +1,5 @@ +{ + "theme.PwaReloadPopup.closeButtonAriaLabel": "Bağla", + "theme.PwaReloadPopup.info": "Yeni versiya mövcuddur", + "theme.PwaReloadPopup.refreshButtonText": "Yenilə" +} diff --git a/packages/docusaurus-theme-translations/locales/az/theme-common.json b/packages/docusaurus-theme-translations/locales/az/theme-common.json new file mode 100644 index 000000000000..1cc16d46e99d --- /dev/null +++ b/packages/docusaurus-theme-translations/locales/az/theme-common.json @@ -0,0 +1,84 @@ +{ + "theme.AnnouncementBar.closeButtonAriaLabel": "Bağla", + "theme.BackToTopButton.buttonAriaLabel": "Səhifənin başına qayıt", + "theme.CodeBlock.copied": "Kopyalandı", + "theme.CodeBlock.copy": "Kopyala", + "theme.CodeBlock.copyButtonAriaLabel": "Kodu kopyala", + "theme.CodeBlock.wordWrapToggle": "Söz sürüşdürməni aç/bağla", + "theme.DocSidebarItem.collapseCategoryAriaLabel": "'{label}' yan panel kateqoriyasını yığ", + "theme.DocSidebarItem.expandCategoryAriaLabel": "'{label}' yan panel kateqoriyasını aç", + "theme.ErrorPageContent.title": "Bu səhifə çökdü.", + "theme.ErrorPageContent.tryAgain": "Yenidən cəhd edin", + "theme.IconExternalLink.ariaLabel": "(yeni tab-da açılır)", + "theme.NavBar.navAriaLabel": "Əsas", + "theme.NotFound.p1": "Axtardığınızı tapa bilmədik.", + "theme.NotFound.p2": "Zəhmət olmasa, sizi bu ünvana yönləndirən saytın sahibi ilə əlaqə saxlayın və linkin işləmədiyini bildirin.", + "theme.NotFound.title": "Səhifə tapılmadı", + "theme.TOCCollapsible.toggleButtonLabel": "Bu səhifədə", + "theme.admonition.caution": "diqqət", + "theme.admonition.danger": "təhlükə", + "theme.admonition.info": "məlumat", + "theme.admonition.note": "qeyd", + "theme.admonition.tip": "məsləhət", + "theme.admonition.warning": "xəbərdarlıq", + "theme.blog.archive.description": "Arxiv", + "theme.blog.archive.title": "Arxiv", + "theme.blog.author.noPosts": "Bu müəllif hələ heç bir yazı yazmayıb.", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Müəlliflər", + "theme.blog.authorsList.viewAll": "Bütün müəlliflərə bax", + "theme.blog.paginator.navAriaLabel": "Bloq siyahısı səhifə naviqasiyası", + "theme.blog.paginator.newerEntries": "Daha yeni yazılar", + "theme.blog.paginator.olderEntries": "Daha köhnə yazılar", + "theme.blog.post.paginator.navAriaLabel": "Bloq yazısı səhifə naviqasiyası", + "theme.blog.post.paginator.newerPost": "Daha yeni yazı", + "theme.blog.post.paginator.olderPost": "Daha köhnə yazı", + "theme.blog.post.plurals": "Bir yazı|{count} yazı", + "theme.blog.post.readMore": "Ətraflı oxu", + "theme.blog.post.readMoreLabel": "{title} haqqında ətraflı oxu", + "theme.blog.post.readingTime.plurals": "Bir dəqiqəlik oxu|{readingTime} dəqiqəlik oxu", + "theme.blog.sidebar.navAriaLabel": "Bloqun son yazıları üzrə naviqasiya", + "theme.blog.tagTitle": "\"{tagName}\" etiketi ilə {nPosts}", + "theme.colorToggle.ariaLabel": "Tünd və işıqlı rejim arasında keçid et (hazırda {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "tünd rejim", + "theme.colorToggle.ariaLabel.mode.light": "işıqlı rejim", + "theme.colorToggle.ariaLabel.mode.system": "sistem rejimi", + "theme.common.editThisPage": "Səhifəni redaktə et", + "theme.common.headingLinkTitle": "{heading} başlığına birbaşa keçid", + "theme.common.skipToMainContent": "Əsas məzmuna keç", + "theme.contentVisibility.draftBanner.message": "Bu səhifə qaralamadır. O, yalnız dev rejimində görünəcək və istismar mühitinə daxil edilməyəcək.", + "theme.contentVisibility.draftBanner.title": "Qaralama səhifə", + "theme.contentVisibility.unlistedBanner.message": "Bu səhifə siyahıya alınmayıb. Axtarış sistemləri onu indeksləməyəcək və ona yalnız birbaşa linki olan istifadəçilər daxil ola biləcək.", + "theme.contentVisibility.unlistedBanner.title": "Siyahıya alınmamış səhifə", + "theme.docs.DocCard.categoryDescription.plurals": "1 element|{count} element", + "theme.docs.breadcrumbs.home": "Ana səhifə", + "theme.docs.breadcrumbs.navAriaLabel": "Naviqasiya yolu", + "theme.docs.paginator.navAriaLabel": "Sənəd səhifələri", + "theme.docs.paginator.next": "Növbəti", + "theme.docs.paginator.previous": "Əvvəlki", + "theme.docs.sidebar.closeSidebarButtonAriaLabel": "Naviqasiya panelini bağla", + "theme.docs.sidebar.collapseButtonAriaLabel": "Yan paneli yığ", + "theme.docs.sidebar.collapseButtonTitle": "Yan paneli yığ", + "theme.docs.sidebar.expandButtonAriaLabel": "Yan paneli aç", + "theme.docs.sidebar.expandButtonTitle": "Yan paneli aç", + "theme.docs.sidebar.navAriaLabel": "Sənədlər yan paneli", + "theme.docs.sidebar.toggleSidebarButtonAriaLabel": "Yan paneli aç/bağla", + "theme.docs.tagDocListPageTitle": "\"{tagName}\" etiketi ilə {nDocsTagged}", + "theme.docs.tagDocListPageTitle.nDocsTagged": "Bir sənəd etiketlənib|{count} sənəd etiketlənib", + "theme.docs.versionBadge.label": "Versiya: {versionLabel}", + "theme.docs.versions.latestVersionLinkLabel": "Ən son versiya", + "theme.docs.versions.latestVersionSuggestionLabel": "Ən son sənədlər üçün {latestVersionLink} ({versionLabel}) səhifəsinə baxın.", + "theme.docs.versions.unmaintainedVersionLabel": "Bu, {siteTitle} {versionLabel} üçün sənədlərdir və artıq aktiv şəkildə dəstəklənmir.", + "theme.docs.versions.unreleasedVersionLabel": "Bu, {siteTitle} {versionLabel} versiyası üçün hələ buraxılmamış sənədlərdir", + "theme.lastUpdated.atDate": " {date} tarixində", + "theme.lastUpdated.byUser": " {user} tərəfindən", + "theme.lastUpdated.lastUpdatedAtBy": "Son yenilənmə{atDate}{byUser}", + "theme.navbar.mobileDropdown.collapseButton.collapseAriaLabel": "Siyahını yığ", + "theme.navbar.mobileDropdown.collapseButton.expandAriaLabel": "Siyahını genişləndir", + "theme.navbar.mobileLanguageDropdown.label": "Dillər", + "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Əsas menyuya qayıt", + "theme.navbar.mobileVersionsDropdown.label": "Versiyalar", + "theme.tags.tagsListLabel": "Etiketlər:", + "theme.tags.tagsPageLink": "Bütün etiketlərə bax", + "theme.tags.tagsPageTitle": "Etiketlər" +} diff --git a/packages/docusaurus-theme-translations/locales/az/theme-live-codeblock.json b/packages/docusaurus-theme-translations/locales/az/theme-live-codeblock.json new file mode 100644 index 000000000000..17b268f8073f --- /dev/null +++ b/packages/docusaurus-theme-translations/locales/az/theme-live-codeblock.json @@ -0,0 +1,5 @@ +{ + "theme.Playground.buttons.reset": "Sıfırla", + "theme.Playground.liveEditor": "Canlı redaktor", + "theme.Playground.result": "Nəticə" +} diff --git a/packages/docusaurus-theme-translations/locales/az/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/az/theme-search-algolia.json new file mode 100644 index 000000000000..a862e9ac2092 --- /dev/null +++ b/packages/docusaurus-theme-translations/locales/az/theme-search-algolia.json @@ -0,0 +1,60 @@ +{ + "theme.SearchBar.label": "Axtar", + "theme.SearchBar.seeAll": "{count} nəticənin hamısına bax", + "theme.SearchModal.askAiScreen.afterToolCallText": "Axtarıldı:", + "theme.SearchModal.askAiScreen.copyButtonCopiedText": "Kopyalandı!", + "theme.SearchModal.askAiScreen.copyButtonText": "Kopyala", + "theme.SearchModal.askAiScreen.copyButtonTitle": "Kopyala", + "theme.SearchModal.askAiScreen.disclaimerText": "Cavablar süni intellekt tərəfindən yaradılır və səhv ola bilər. Cavabları yoxlayın.", + "theme.SearchModal.askAiScreen.dislikeButtonTitle": "Bəyənmədim", + "theme.SearchModal.askAiScreen.duringToolCallText": "Axtarılır: ", + "theme.SearchModal.askAiScreen.likeButtonTitle": "Bəyəndim", + "theme.SearchModal.askAiScreen.preToolCallText": "Axtarılır...", + "theme.SearchModal.askAiScreen.relatedSourcesText": "Əlaqəli mənbələr", + "theme.SearchModal.askAiScreen.thanksForFeedbackText": "Rəyiniz üçün təşəkkür edirik!", + "theme.SearchModal.askAiScreen.thinkingText": "Düşünür...", + "theme.SearchModal.errorScreen.helpText": "İnternet bağlantınızı yoxlamağınız faydalı ola bilər.", + "theme.SearchModal.errorScreen.titleText": "Nəticələri əldə etmək mümkün olmadı", + "theme.SearchModal.footer.backToSearchText": "Axtarışa qayıt", + "theme.SearchModal.footer.closeKeyAriaLabel": "Escape düyməsi", + "theme.SearchModal.footer.closeText": "Bağla", + "theme.SearchModal.footer.navigateDownKeyAriaLabel": "Aşağı ox", + "theme.SearchModal.footer.navigateText": "Naviqasiya", + "theme.SearchModal.footer.navigateUpKeyAriaLabel": "Yuxarı ox", + "theme.SearchModal.footer.searchByText": "Dəstəkləyir: ", + "theme.SearchModal.footer.selectKeyAriaLabel": "Enter düyməsi", + "theme.SearchModal.footer.selectText": "Seç", + "theme.SearchModal.footer.submitQuestionText": "Sualı göndər", + "theme.SearchModal.noResultsScreen.noResultsText": "Nəticə tapılmadı:", + "theme.SearchModal.noResultsScreen.reportMissingResultsLinkText": "Bizə bildirin.", + "theme.SearchModal.noResultsScreen.reportMissingResultsText": "Bu sorğunun nəticə qaytarmalı olduğunu düşünürsünüz?", + "theme.SearchModal.noResultsScreen.suggestedQueryText": "Bunu axtarmağa cəhd edin:", + "theme.SearchModal.placeholder": "Sənədlərdə axtar", + "theme.SearchModal.resultsScreen.askAiPlaceholder": "Süni intellektdən soruş: ", + "theme.SearchModal.searchBox.backToKeywordSearchButtonAriaLabel": "Açar söz axtarışına qayıt", + "theme.SearchModal.searchBox.backToKeywordSearchButtonText": "Açar söz axtarışına qayıt", + "theme.SearchModal.searchBox.cancelButtonText": "Ləğv et", + "theme.SearchModal.searchBox.enterKeyHint": "axtar", + "theme.SearchModal.searchBox.enterKeyHintAskAi": "enter", + "theme.SearchModal.searchBox.placeholderText": "Sənədlərdə axtar", + "theme.SearchModal.searchBox.placeholderTextAskAi": "Başqa sual verin...", + "theme.SearchModal.searchBox.placeholderTextAskAiStreaming": "Cavablandırılır...", + "theme.SearchModal.searchBox.resetButtonTitle": "Sorğunu təmizlə", + "theme.SearchModal.searchBox.searchInputLabel": "Axtar", + "theme.SearchModal.startScreen.favoriteSearchesTitle": "Seçilmiş", + "theme.SearchModal.startScreen.noRecentSearchesText": "Son axtarış yoxdur", + "theme.SearchModal.startScreen.recentConversationsTitle": "Son söhbətlər", + "theme.SearchModal.startScreen.recentSearchesTitle": "Son", + "theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle": "Bu axtarışı seçilmişlərdən sil", + "theme.SearchModal.startScreen.removeRecentConversationButtonTitle": "Bu söhbəti tarixçədən sil", + "theme.SearchModal.startScreen.removeRecentSearchButtonTitle": "Bu axtarışı tarixçədən sil", + "theme.SearchModal.startScreen.saveRecentSearchButtonTitle": "Axtarışı yadda saxla", + "theme.SearchPage.algoliaLabel": "Powered by Algolia", + "theme.SearchPage.documentsFound.plurals": "Bir sənəd tapıldı|{count} sənəd tapıldı", + "theme.SearchPage.emptyResultsTitle": "Sənədlərdə axtar", + "theme.SearchPage.existingResultsTitle": "\"{query}\" üçün axtarış nəticələri", + "theme.SearchPage.fetchingNewResults": "Yeni nəticələr yüklənir...", + "theme.SearchPage.inputLabel": "Axtar", + "theme.SearchPage.inputPlaceholder": "Axtarış məntini buraya yazın", + "theme.SearchPage.noResultsText": "Heç bir nəticə tapılmadı" +} From 3525a813f8edf49ea0d765b6d361a97043281170 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Fri, 21 Aug 2026 08:54:11 -0700 Subject: [PATCH 3/6] fix: memory leak in Link due to IntersectionObserver (#12326) --- .../docusaurus/src/client/exports/Link.tsx | 61 ++++++++++--------- .../client/exports/__tests__/Link.test.tsx | 52 +++++++++++++++- 2 files changed, 83 insertions(+), 30 deletions(-) diff --git a/packages/docusaurus/src/client/exports/Link.tsx b/packages/docusaurus/src/client/exports/Link.tsx index 1fab775967ee..7352508c14dc 100644 --- a/packages/docusaurus/src/client/exports/Link.tsx +++ b/packages/docusaurus/src/client/exports/Link.tsx @@ -6,6 +6,7 @@ */ import React, { + useCallback, useEffect, useImperativeHandle, useRef, @@ -99,30 +100,39 @@ function Link({ const ioRef = useRef(undefined); - const handleRef = (el: HTMLAnchorElement | null) => { - innerRef.current = el; - - if (IOSupported && el && isInternal) { - // If IO supported and element reference found, set up Observer. - ioRef.current = new window.IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (el === entry.target) { - // If element is in viewport, stop observing and run callback. - // https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API - if (entry.isIntersecting || entry.intersectionRatio > 0) { - ioRef.current!.unobserve(el); - ioRef.current!.disconnect(); - if (targetLink != null) { - window.docusaurus.prefetch(targetLink); + const handleRef = useCallback( + (el: HTMLAnchorElement | null) => { + innerRef.current = el; + ioRef.current?.disconnect(); + ioRef.current = undefined; + + if (IOSupported && el && isInternal) { + // If IO supported and element reference found, set up Observer. + const observer = new window.IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (el === entry.target) { + // If element is in viewport, stop observing and run callback. + // https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API + if (entry.isIntersecting || entry.intersectionRatio > 0) { + observer.unobserve(el); + observer.disconnect(); + if (ioRef.current === observer) { + ioRef.current = undefined; + } + if (targetLink != null) { + window.docusaurus.prefetch(targetLink); + } } } - } + }); }); - }); - // Add element to the observer. - ioRef.current.observe(el); - } - }; + // Add element to the observer. + ioRef.current = observer; + observer.observe(el); + } + }, + [IOSupported, isInternal, targetLink], + ); const onInteractionEnter = () => { if (!preloaded.current && targetLink != null) { @@ -138,14 +148,7 @@ function Link({ window.docusaurus.prefetch(targetLink); } } - - // When unmounting, stop intersection observer from watching. - return () => { - if (IOSupported && ioRef.current) { - ioRef.current.disconnect(); - } - }; - }, [ioRef, targetLink, IOSupported, isInternal]); + }, [targetLink, IOSupported, isInternal]); // It is simple local anchor link targeting current page? const isAnchorLink = targetLink?.startsWith('#') ?? false; diff --git a/packages/docusaurus/src/client/exports/__tests__/Link.test.tsx b/packages/docusaurus/src/client/exports/__tests__/Link.test.tsx index fce2c970b6a8..f236b144162c 100644 --- a/packages/docusaurus/src/client/exports/__tests__/Link.test.tsx +++ b/packages/docusaurus/src/client/exports/__tests__/Link.test.tsx @@ -11,8 +11,9 @@ import React, {type ReactNode} from 'react'; import {render as renderRTL} from '@testing-library/react'; import '@testing-library/jest-dom/vitest'; import {fromPartial} from '@total-typescript/shoehorn'; -import {StaticRouter} from 'react-router-dom'; +import {MemoryRouter, StaticRouter} from 'react-router-dom'; import Link from '../Link'; +import ExecutionEnvironment from '../ExecutionEnvironment'; import {Context} from '../../docusaurusContext'; import type {DocusaurusContext} from '@docusaurus/types'; @@ -67,6 +68,55 @@ function createLinkRenderer(defaultRendererOptions: Partial = {}) { } describe('', () => { + it('reuses and disposes its intersection observer across renders', () => { + const canUseIntersectionObserver = + ExecutionEnvironment.canUseIntersectionObserver; + const observers: IntersectionObserver[] = []; + const createIntersectionObserver = vi.fn(function IntersectionObserver() { + const observer = fromPartial({ + disconnect: vi.fn(), + observe: vi.fn(), + unobserve: vi.fn(), + }); + observers.push(observer); + return observer; + }); + vi.stubGlobal('IntersectionObserver', createIntersectionObserver); + ExecutionEnvironment.canUseIntersectionObserver = true; + + const context = createDocusaurusContext(defaultOptions); + function Wrapper({children}: {children: ReactNode}) { + return ( + + {children} + + ); + } + + try { + const {rerender, unmount} = renderRTL(, { + wrapper: Wrapper, + }); + expect(createIntersectionObserver).toHaveBeenCalledTimes(1); + + rerender(); + expect(createIntersectionObserver).toHaveBeenCalledTimes(1); + expect(observers[0]!.disconnect).not.toHaveBeenCalled(); + + rerender(); + expect(createIntersectionObserver).toHaveBeenCalledTimes(2); + expect(observers[0]!.disconnect).toHaveBeenCalledTimes(1); + expect(observers[1]!.disconnect).not.toHaveBeenCalled(); + + unmount(); + expect(observers[1]!.disconnect).toHaveBeenCalledTimes(1); + } finally { + ExecutionEnvironment.canUseIntersectionObserver = + canUseIntersectionObserver; + vi.unstubAllGlobals(); + } + }); + describe('using "browser" router', () => { const render = createLinkRenderer({router: 'browser'}); From abeec346e3a319093ea5a54ed4e6963861b62ddc Mon Sep 17 00:00:00 2001 From: jibin jose Date: Fri, 21 Aug 2026 21:50:11 +0530 Subject: [PATCH 4/6] fix(utils): allow MDX to detect title when a component is exported before h1 (#12335) Co-authored-by: sebastien --- .../src/__tests__/markdownUtils.test.ts | 71 +++++++++++++++---- .../docusaurus-utils/src/markdownUtils.ts | 14 ++-- 2 files changed, 65 insertions(+), 20 deletions(-) diff --git a/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts b/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts index a81e8cfff103..abe9df8c99d2 100644 --- a/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts @@ -572,6 +572,26 @@ Lorem Ipsum }); }); + it('parses markdown h1 title placed after export declarations', () => { + const markdown = dedent` + export function Author() { + return ( + Author + ); + } + + # Markdown Title + + Lorem Ipsum + + `; + + expect(parseMarkdownContentTitle(markdown)).toEqual({ + content: markdown, + contentTitle: 'Markdown Title', + }); + }); + it('parses markdown h1 title placed after multiple import declarations', () => { const markdown = dedent` import Component1 from '@site/src/components/Component1'; @@ -602,23 +622,21 @@ Lorem Ipsum }); }); - it('parses markdown h1 title placed after multiple import declarations and remove it', () => { + it('parses markdown h1 title placed after multiple import/export declarations and remove it', () => { const markdown = dedent` import Component1 from '@site/src/components/Component1'; import Component2 from '@site/src/components/Component2'; - import Component3 from '@site/src/components/Component3'; - import Component4 from '@site/src/components/Component4'; + export function Component3() { + return Component3 + } + + export function Component4() { + return Component4 + } import Component5 from '@site/src/components/Component5'; - import Component6 from '@site/src/components/Component6'; - import Component7 from '@site/src/components/Component7'; - import Component8 from '@site/src/components/Component8'; - import Component9 from '@site/src/components/Component9'; - import Component10 from '@site/src/components/Component10'; - import Component11 from '@site/src/components/Component11'; - import Component12 from '@site/src/components/Component12'; - import Component13 from '@site/src/components/Component13'; - import Component14 from '@site/src/components/Component14'; - import Component15 from '@site/src/components/Component15'; + export function Component6() { + return Component6 + } # Markdown Title @@ -633,6 +651,33 @@ Lorem Ipsum contentTitle: 'Markdown Title', }); }); + + it('parses markdown h1 title placed after multiple import/export declarations', () => { + const markdown = dedent` + import Component1 from '@site/src/components/Component1'; + import Component2 from '@site/src/components/Component2'; + export function Component3() { + return Component3 + } + + export function Component4() { + return Component4 + } + import Component5 from '@site/src/components/Component5'; + export function Component6() { + return Component6 + } + + # Markdown Title + + Lorem Ipsum + `; + + expect(parseMarkdownContentTitle(markdown)).toEqual({ + content: markdown, + contentTitle: 'Markdown Title', + }); + }); }); describe('parseFileContentFrontMatter', () => { diff --git a/packages/docusaurus-utils/src/markdownUtils.ts b/packages/docusaurus-utils/src/markdownUtils.ts index 216d0122e7ad..451ea7b22ff5 100644 --- a/packages/docusaurus-utils/src/markdownUtils.ts +++ b/packages/docusaurus-utils/src/markdownUtils.ts @@ -255,18 +255,18 @@ export function parseMarkdownContentTitle( const removeContentTitleOption = options?.removeContentTitle ?? false; const content = contentUntrimmed.trim(); - // We only need to detect import statements that will be parsed by MDX as - // `import` nodes, as broken syntax can't render anyways. That means any block - // that has `import` at the very beginning and surrounded by empty lines. - const contentWithoutImport = content - .replace(/^(?:import\s(?:.|\r?\n(?!\r?\n))*(?:\r?\n){2,})*/, '') + // We only need to detect import/export statements that will be parsed by MDX as + // `import` or `export` nodes, as broken syntax can't render anyways. That means any block + // that has `import` or `export` at the very beginning and surrounded by empty lines. + const contentWithoutImportExport = content + .replace(/^(?:(?:import|export)\s(?:.|\r?\n(?!\r?\n))*(?:\r?\n){2,})*/, '') .trim(); const regularTitleMatch = /^#[ \t]+(?[^ \t].*)(?:\r?\n|$)/.exec( - contentWithoutImport, + contentWithoutImportExport, ); const alternateTitleMatch = /^(?<title>.*)\r?\n=+(?:\r?\n|$)/.exec( - contentWithoutImport, + contentWithoutImportExport, ); const titleMatch = regularTitleMatch ?? alternateTitleMatch; From eeb5608e79184c747e8ed03e04cee37e272c4491 Mon Sep 17 00:00:00 2001 From: Dhinesh Ponnarasan <160256912+DhineshPonnarasan@users.noreply.github.com> Date: Fri, 21 Aug 2026 12:41:43 -0400 Subject: [PATCH 5/6] fix(utils): correct auto-generated description when H1 heading text contains # (e.g. C#) (#12308) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com> --- .../src/__tests__/markdownUtils.test.ts | 41 +++++++++++++++++++ .../docusaurus-utils/src/markdownUtils.ts | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts b/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts index abe9df8c99d2..21bb9d45b758 100644 --- a/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts @@ -47,6 +47,47 @@ describe('createExcerpt', () => { ); }); + it('creates excerpt for h1 heading containing hash in text (e.g. C#)', () => { + expect( + createExcerpt(dedent` + + # C# Programming Guide + + This paragraph should become the description. + `), + ).toBe('This paragraph should become the description.'); + }); + + it('creates excerpt for h1 heading containing hash in text (e.g. F#)', () => { + expect( + createExcerpt(dedent` + + # F# Programming Guide + + This paragraph should become the description. + `), + ).toBe('This paragraph should become the description.'); + }); + + it('creates excerpt for h1 heading containing hash with trailing ATX closing marker', () => { + expect( + createExcerpt(dedent` + + # C# Programming Guide # + + This paragraph should become the description. + `), + ).toBe('This paragraph should become the description.'); + }); + + it('creates excerpt for h2 heading containing hash in text (e.g. C#)', () => { + expect( + createExcerpt(dedent` + ## C# Setup Guide + `), + ).toBe('C# Setup Guide'); + }); + it('creates excerpt for regular content with alternate title', () => { expect( createExcerpt(dedent` diff --git a/packages/docusaurus-utils/src/markdownUtils.ts b/packages/docusaurus-utils/src/markdownUtils.ts index 451ea7b22ff5..f1ad5fdc009f 100644 --- a/packages/docusaurus-utils/src/markdownUtils.ts +++ b/packages/docusaurus-utils/src/markdownUtils.ts @@ -146,7 +146,7 @@ export function createExcerpt(fileString: string): string | undefined { // Remove HTML tags. .replace(/<[^>]*>/g, '') // Remove Title headers - .replace(/^#[^#]+#?/gm, '') + .replace(/^#(?!#).*/gm, '') // Remove Markdown + ATX-style headers .replace(/^#{1,6}\s*(?<text>[^#]*?)\s*#{0,6}/gm, '$1') // Remove emphasis. From 4421e60d3f33ac95d4c686cbc4c9e8bb549922d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= <slorber@users.noreply.github.com> Date: Fri, 21 Aug 2026 17:43:45 +0100 Subject: [PATCH 6/6] fix(ci): skip CI workflows that fail from forks (#12372) --- .github/workflows/lighthouse-report.yml | 1 + .github/workflows/lint-autofix.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/lighthouse-report.yml b/.github/workflows/lighthouse-report.yml index 7711bba545f7..be3610facbb9 100644 --- a/.github/workflows/lighthouse-report.yml +++ b/.github/workflows/lighthouse-report.yml @@ -68,6 +68,7 @@ jobs: - name: Add Lighthouse stats as comment id: comment_to_pr + if: github.event.pull_request.head.repo.full_name == github.repository uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # 3.0.5 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lint-autofix.yml b/.github/workflows/lint-autofix.yml index 3bada614c695..55635072b0a3 100644 --- a/.github/workflows/lint-autofix.yml +++ b/.github/workflows/lint-autofix.yml @@ -55,5 +55,6 @@ jobs: run: git diff - uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0 + if: github.event.pull_request.head.repo.full_name == github.repository with: commit_message: 'refactor: apply lint autofix'