From decd7fce29d179a3e8641ccee628525809d06e20 Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 28 Oct 2025 11:45:08 +0100 Subject: [PATCH 1/8] Add .env to gitignore --- docusaurus/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/docusaurus/.gitignore b/docusaurus/.gitignore index 13f5c49771..fc6e0ad721 100644 --- a/docusaurus/.gitignore +++ b/docusaurus/.gitignore @@ -12,6 +12,7 @@ # Misc .DS_Store +.env .env.local .env.development.local .env.test.local From 0c1e261a99e98f6544aedadb867b88ca0ef8cdda Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 28 Oct 2025 11:45:19 +0100 Subject: [PATCH 2/8] Disable Algolia config --- docusaurus/docusaurus.config.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docusaurus/docusaurus.config.js b/docusaurus/docusaurus.config.js index d262f1c3eb..e6fff3fbc0 100644 --- a/docusaurus/docusaurus.config.js +++ b/docusaurus/docusaurus.config.js @@ -196,15 +196,15 @@ const config = { hideable: true, }, }, - algolia: { - appId: '392RJ63O14', - apiKey: '3f4b8953a20a4c5af4614a607ecf9a93', - indexName: 'strapi_newCmsCrawler_march2025', - contextualSearch: false, - searchParameters: { - facetFilters: [], - }, - }, + // algolia: { + // appId: '392RJ63O14', + // apiKey: '3f4b8953a20a4c5af4614a607ecf9a93', + // indexName: 'strapi_newCmsCrawler_march2025', + // contextualSearch: false, + // searchParameters: { + // facetFilters: [], + // }, + // }, navbar: { hideOnScroll: false, logo: { From 7479a5d78f75d72d91699679ba4a0b0e8d697852 Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 28 Oct 2025 11:45:28 +0100 Subject: [PATCH 3/8] Disable announcement bar --- docusaurus/docusaurus.config.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docusaurus/docusaurus.config.js b/docusaurus/docusaurus.config.js index e6fff3fbc0..b1dedbb3b6 100644 --- a/docusaurus/docusaurus.config.js +++ b/docusaurus/docusaurus.config.js @@ -183,14 +183,14 @@ const config = { themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ - announcementBar: { - id: 'support_us', - content: - "šŸ§‘šŸ½ā€šŸ”¬ We're testing new AI and search tools on docs-next.strapi.io! Feel free to have a look and share your feedback", - backgroundColor: '#F3E5FA', - textColor: '#091E42', - isCloseable: true, - }, + // announcementBar: { + // id: 'support_us', + // content: + // "šŸ§‘šŸ½ā€šŸ”¬ We're testing new AI and search tools on docs-next.strapi.io! Feel free to have a look and share your feedback", + // backgroundColor: '#F3E5FA', + // textColor: '#091E42', + // isCloseable: true, + // }, docs: { sidebar: { hideable: true, From ef345a48c7bb3aebf0eb6046d6952854a8774467 Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 28 Oct 2025 11:45:39 +0100 Subject: [PATCH 4/8] Add MeiliSearch to Docusaurus config. --- docusaurus/docusaurus.config.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docusaurus/docusaurus.config.js b/docusaurus/docusaurus.config.js index b1dedbb3b6..67101ad653 100644 --- a/docusaurus/docusaurus.config.js +++ b/docusaurus/docusaurus.config.js @@ -131,6 +131,16 @@ const config = { rel: 'stylesheet', }, ], + customFields: { + meilisearch: { + host: 'https://ms-47f23e4f6fb9-30446.fra.meilisearch.io', + apiKey: '45326fd7e6278ec3fc83af7a5c20a2ab4261f8591bd186adf8bf8f962581622b', + indexUid: 'strapi-docs', + searchParams: { + attributesToHighlight: null + } + }, + }, presets: [ [ '@docusaurus/preset-classic', From 08efc4025449a16142eb2dde34c3f86240bd4e77 Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 28 Oct 2025 11:46:19 +0100 Subject: [PATCH 5/8] Add or update MeiliSearch-related front-end files --- docusaurus/src/scss/custom-search-bar.scss | 199 +++++++++++++++--- docusaurus/src/scss/search.scss | 82 ++++++++ .../src/theme/DocSidebar/CustomSearchBar.js | 10 +- docusaurus/src/theme/SearchBar/index.js | 152 +++++++++++++ 4 files changed, 413 insertions(+), 30 deletions(-) create mode 100644 docusaurus/src/theme/SearchBar/index.js diff --git a/docusaurus/src/scss/custom-search-bar.scss b/docusaurus/src/scss/custom-search-bar.scss index 56b7c7c049..92f849bb89 100644 --- a/docusaurus/src/scss/custom-search-bar.scss +++ b/docusaurus/src/scss/custom-search-bar.scss @@ -58,7 +58,6 @@ padding-left: 0; color: var(--strapi-neutral-400); font-weight: 500; - // left: -16px; position: relative; &::before { @@ -82,27 +81,75 @@ .DocSearch-Button-Keys { display: none; - // min-width: 30px; - // position: relative; - // top: 1px; - // font-size: 1rem; - - // .DocSearch-Button-Key { - // color: var(--strapi-neutral-500); - // box-shadow: none; - // background: none; - // border-radius: 0; - // padding: 0; - // margin-right: 0; - // width: 10px; - // } } + // MeiliSearch specific styles - match existing DocSearch design + .navbar__search { + width: 50%; + border-right: solid 1px var(--strapi-neutral-200); + + .docsearch-btn { + all: unset; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + background-color: transparent; + height: 34px !important; + width: 100%; + cursor: pointer; + padding-left: 12px; + padding-right: 8px; + + &:hover { + color: var(--strapi-primary-600); + + .docsearch-btn-placeholder { + color: var(--strapi-primary-600); + } + + .docsearch-btn-placeholder::before { + color: var(--strapi-neutral-500) !important; + } + } + } + + // Hide default MeiliSearch search icon + .docsearch-btn-icon-container { + display: none; + } + + // Style placeholder with custom Phosphor icon + .docsearch-btn-placeholder { + font-size: var(--strapi-font-size-sm); + line-height: var(--strapi-line-height-sm); + color: var(--strapi-neutral-400); + font-weight: 500; + flex: 1; + text-align: left; + + // Add custom Phosphor search icon + &::before { + content: '\E30C'; + font-family: "Phosphor"; + color: var(--strapi-neutral-500); + margin-right: 6px; + font-size: 16px; + position: relative; + top: 2px; + } + } + + // Hide keyboard shortcuts completely + .docsearch-btn-keys { + display: none; + } + } button.kapa-widget-button { border: none; padding: 0 0 0 12px; - width: 100%; + width: 50%; height: 36px; overflow: visible; cursor: pointer; @@ -111,22 +158,23 @@ background: transparent; text-align: left; - /* inherit font & color from ancestor */ + // Inherit font & color from ancestor color: var(--strapi-neutral-400); font: inherit; font-size: .95rem; font-weight: 500; - /* Normalize `line-height`. Cannot be changed from `normal` in Firefox 4+. */ + // Normalize line-height. Cannot be changed from normal in Firefox 4+ line-height: normal; - /* Corrects font smoothing for webkit */ + // Correct font smoothing for webkit -webkit-font-smoothing: inherit; -moz-osx-font-smoothing: inherit; - /* Corrects inability to style clickable `input` types in iOS */ + // Correct inability to style clickable input types in iOS -webkit-appearance: none; - /* Remove excess padding and border in Firefox 4+ */ + + // Remove excess padding and border in Firefox 4+ &::-moz-focus-inner { border: 0; padding: 0; @@ -182,23 +230,120 @@ } } +// Responsive behavior - mobile view @media (max-width: 768px) { .my-custom-search-bar { - .kapa-widget-button-text { - display: none; + // Reset positioning for mobile sidebar context + position: static; + top: auto; + left: 8px !important; + margin: 16px 16px 8px 16px; + max-width: none; + width: auto; + border-bottom: solid 1px var(--strapi-neutral-200) !important; + padding-bottom: 8px !important; + + // Two-button layout: search + Ask AI + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + gap: 0; + + .navbar__search { + border-right: solid 1px var(--strapi-neutral-200); + width: 50%; + height: 40px; + flex-shrink: 0; + display: flex; + align-items: center; + + .docsearch-btn { + border-radius: 4px 0 0 4px; + width: 100%; + height: 40px; + justify-content: flex-start; + align-items: center; + padding: 0 12px; + + .docsearch-btn-placeholder { + display: block; + + &::before { + content: '\E30C'; + font-family: "Phosphor"; + color: var(--strapi-neutral-500); + margin-right: 6px; + font-size: 16px; + position: relative; + top: 2px; + } + } + + .docsearch-btn-keys { + display: none; + } + + // Hide default MeiliSearch icon on mobile + .docsearch-btn-icon-container { + display: none; + } + } } + .kapa-widget-button { - border-radius: 50%; - padding: 0; - padding-right: 4px; + border-radius: 0 4px 4px 0; + width: 50%; + height: 40px; + padding: 0 12px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: flex-start; + + .kapa-widget-button-text { + display: flex; + align-items: center; + font-size: var(--strapi-font-size-sm); + + i { + margin-right: 6px; + } + } + + svg { + position: static; + width: 16px; + height: 16px; + fill: var(--strapi-neutral-500); + margin-right: 6px; + } } } } +// Dark mode support @include dark { .my-custom-search-bar { background: var(--strapi-neutral-0); border: var(--strapi-input-border); color: var(--strapi-dark-100); + + .navbar__search { + .docsearch-btn { + background: transparent; + color: var(--strapi-dark-100); + + .docsearch-btn-placeholder { + color: var(--strapi-dark-100); + } + + &:hover { + .docsearch-btn-placeholder { + color: var(--strapi-primary-600); + } + } + } + } } -} +} \ No newline at end of file diff --git a/docusaurus/src/scss/search.scss b/docusaurus/src/scss/search.scss index 94e711aecb..0d36d1d41a 100644 --- a/docusaurus/src/scss/search.scss +++ b/docusaurus/src/scss/search.scss @@ -27,3 +27,85 @@ body .DocSearch { padding-bottom: var(--custom-search-hit-pb); } } + +/** + * Styles for the DocSearch modal + */ +.docsearch-modal { + --docsearch-primary-color: var(--strapi-primary-600); + --docsearch-muted-color: rgb(150, 159, 175); + --docsearch-modal-container-background: rgba(101, 108, 133, 0.8); + --docsearch-searchbox-focus-background: #fff; + --docsearch-searchbox-shadow: inset 0 0 0 2px var(--docsearch-primary-color); + --docsearch-hit-color: rgb(68, 73, 80); + --docsearch-hit-active-color: #fff; + --docsearch-hit-background: #fff; + --docsearch-hit-shadow: 0 1px 3px 0 rgb(212, 217, 225); +} + +.docsearch-modal-search-hits-item { + border-radius: 8px; + &:hover, + &[aria-selected="true"] { + background-color: var(--strapi-primary-600); + + .docsearch-modal-search-hits-item-text-container { + p { + color: var(--strapi-neutral-100); + } + } + + mark { + background-color: var(--strapi-primary-600) !important; + color: white; + } + } + + mark { + background-color: transparent !important; + } +} + +.docsearch-modal-search-input { + color: var(--strapi-neutral-800) +} + +@include dark { + .docsearch-modal { + --docsearch-text-color: rgb(245, 246, 247); + --docsearch-modal-container-background: rgba(9, 10, 17, 0.8); + --docsearch-modal-background: rgb(21, 23, 42); + --docsearch-modal-shadow: inset 1px 1px 0 0 rgb(44, 46, 64), 0 3px 8px 0 rgb(0, 3, 9); + --docsearch-searchbox-background: rgb(9, 10, 17); + --docsearch-searchbox-focus-background: #000; + --docsearch-hit-color: rgb(190, 195, 201); + --docsearch-hit-shadow: none; + --docsearch-hit-background: rgb(9, 10, 17); + --docsearch-key-gradient: + linear-gradient( + -26.5deg, + rgb(86, 88, 114) 0%, + rgb(49, 53, 91) 100% ); + --docsearch-key-shadow: + inset 0 -2px 0 0 rgb(40, 45, 85), + inset 0 0 1px 1px rgb(81, 87, 125), + 0 2px 2px 0 rgba(3, 4, 9, 0.3); + --docsearch-footer-background: rgb(30, 33, 54); + --docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, 0.5), 0 -4px 8px 0 rgba(0, 0, 0, 0.2); + --docsearch-muted-color: rgb(127, 132, 151); + --docsearch-icon-color: rgb(255, 255, 255); + } + .docsearch-modal-search-hits-item mark { + background-color: var(--strapi-neutral-100) !important; + color: var(--strapi-neutral-700); + } + .docsearch-modal-search-hits-item:hover, + .docsearch-modal-search-hits-item[aria-selected="true"], + .docsearch-modal-search-hits-item--active .docsearch-modal-search-hits-item-text-container p { + color: white !important; + mark { + background-color: var(--strapi-primary-600) !important; + color: var(--strapi-neutral-700); + } + } +} \ No newline at end of file diff --git a/docusaurus/src/theme/DocSidebar/CustomSearchBar.js b/docusaurus/src/theme/DocSidebar/CustomSearchBar.js index 090e9fdcbe..4111d972da 100644 --- a/docusaurus/src/theme/DocSidebar/CustomSearchBar.js +++ b/docusaurus/src/theme/DocSidebar/CustomSearchBar.js @@ -1,12 +1,16 @@ import React from 'react'; -import SearchBar from '@theme-original/SearchBar'; +import SearchBar from '../SearchBar/index.js'; import Icon from '../../components/Icon.js' export default function CustomSearchBarWrapper(props) { return (
- +
); -} +} \ No newline at end of file diff --git a/docusaurus/src/theme/SearchBar/index.js b/docusaurus/src/theme/SearchBar/index.js new file mode 100644 index 0000000000..0344af92f5 --- /dev/null +++ b/docusaurus/src/theme/SearchBar/index.js @@ -0,0 +1,152 @@ +import React, { useEffect, useRef, useState } from 'react'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import { useColorMode } from '@docusaurus/theme-common'; +import BrowserOnly from '@docusaurus/BrowserOnly'; + +function SearchBarContent() { + const { siteConfig } = useDocusaurusContext(); + const { colorMode } = useColorMode(); + const searchButtonRef = useRef(null); + const dropdownRef = useRef(null); + const searchInstanceRef = useRef(null); + const [isLoaded, setIsLoaded] = useState(false); + + useEffect(() => { + if (!searchButtonRef.current) { + return; + } + + // Clean up previous instance and clear container + if (searchInstanceRef.current) { + searchInstanceRef.current.destroy?.(); + searchInstanceRef.current = null; + } + + // Clear the container content + searchButtonRef.current.innerHTML = ''; + + // Dynamic import to avoid SSR issues with solid-js + Promise.all([ + import('meilisearch-docsearch'), + import('meilisearch-docsearch/css') + ]).then(([{ docsearch }]) => { + const search = docsearch({ + container: searchButtonRef.current, + host: siteConfig.customFields.meilisearch.host, + apiKey: siteConfig.customFields.meilisearch.apiKey, + indexUid: siteConfig.customFields.meilisearch.indexUid, + + transformItems: (items) => { + return items.map((item) => { + let section = item.hierarchy_lvl0 || 'Documentation'; + + if (item.url && item.url.includes('/cms/')) { + section = 'CMS'; + } else if (item.url && item.url.includes('/cloud/')) { + section = 'Cloud'; + } + + return { + ...item, + url: item.url.replace('https://docs-next.strapi.io', ''), + hierarchy: { + lvl0: section, + lvl1: item.hierarchy_lvl0, + lvl2: item.hierarchy_lvl1, + lvl3: item.hierarchy_lvl2 + } + }; + }); + }, + + searchParams: { + attributesToHighlight: ['hierarchy', 'content'], + highlightPreTag: '', + highlightPostTag: '', + matchingStrategy: 'all', + attributesToSearchOn: ['hierarchy_lvl1', 'hierarchy_lvl2', 'content'] + }, + + placeholder: 'Search documentation...', + translations: { + button: { + buttonText: 'Search', + buttonAriaLabel: 'Search', + }, + modal: { + searchBox: { + resetButtonTitle: 'Clear the query', + resetButtonAriaLabel: 'Clear the query', + cancelButtonText: 'Cancel', + cancelButtonAriaLabel: 'Cancel', + }, + startScreen: { + recentSearchesTitle: 'Recent', + noRecentSearchesText: 'No recent searches', + saveRecentSearchButtonTitle: 'Save this search', + removeRecentSearchButtonTitle: 'Remove this search', + favoriteSearchesTitle: 'Favorite', + removeFavoriteSearchButtonTitle: 'Remove this search from favorites', + }, + errorScreen: { + titleText: 'Unable to fetch results', + helpText: 'You might want to check your network connection.', + }, + footer: { + selectText: 'to select', + selectKeyAriaLabel: 'Enter key', + navigateText: 'to navigate', + navigateUpKeyAriaLabel: 'Arrow up', + navigateDownKeyAriaLabel: 'Arrow down', + closeText: 'to close', + closeKeyAriaLabel: 'Escape key', + searchByText: 'Search by', + }, + noResultsScreen: { + noResultsText: 'No results for', + suggestedQueryText: 'Try searching for', + reportMissingResultsText: 'Believe this query should return results?', + reportMissingResultsLinkText: 'Let us know.', + }, + }, + }, + + getMissingResultsUrl: ({ query }) => { + return `https://github.com/strapi/documentation/issues/new?title=Missing+search+results+for+${query}`; + }, + }); + + searchInstanceRef.current = search; + setIsLoaded(true); + + if (colorMode === 'dark') { + dropdownRef.current?.classList.add('dark'); + } else { + dropdownRef.current?.classList.remove('dark'); + } + }).catch((error) => { + console.error('Failed to load MeiliSearch:', error); + }); + + return () => { + if (searchInstanceRef.current) { + searchInstanceRef.current.destroy?.(); + searchInstanceRef.current = null; + } + }; + }, [colorMode, siteConfig]); + + return ( +
+
+
+ ); +} + +export default function SearchBar() { + return ( +
}> + {() => } +
+ ); +} \ No newline at end of file From e7b5b5ce2c869844ccb51b31f06b999ecc3adb65 Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 28 Oct 2025 11:46:40 +0100 Subject: [PATCH 6/8] Add or update MeiliSearch-related back-end files --- .../workflows/meilisearch-scraper-config.json | 377 ++++++++++++++++++ .github/workflows/meilisearch-scraper.yml | 58 +++ .../scripts/meilisearch/add-category-order.js | 82 ++++ .../scripts/meilisearch/analyze-categories.js | 91 +++++ .../meilisearch/category-order-config.json | 24 ++ 5 files changed, 632 insertions(+) create mode 100644 .github/workflows/meilisearch-scraper-config.json create mode 100644 .github/workflows/meilisearch-scraper.yml create mode 100644 docusaurus/scripts/meilisearch/add-category-order.js create mode 100644 docusaurus/scripts/meilisearch/analyze-categories.js create mode 100644 docusaurus/scripts/meilisearch/category-order-config.json diff --git a/.github/workflows/meilisearch-scraper-config.json b/.github/workflows/meilisearch-scraper-config.json new file mode 100644 index 0000000000..fed41f6b0a --- /dev/null +++ b/.github/workflows/meilisearch-scraper-config.json @@ -0,0 +1,377 @@ +{ + "index_uid": "strapi-docs", + "start_urls": [ + { + "url": "https://docs-next.strapi.io/cms/getting-started", + "selectors_key": "getting-started" + }, + { + "url": "https://docs-next.strapi.io/cms/features", + "selectors_key": "features" + }, + { + "url": "https://docs-next.strapi.io/cms/backend-customization", + "selectors_key": "backend" + }, + { + "url": "https://docs-next.strapi.io/cms/configurations", + "selectors_key": "configurations" + }, + { + "url": "https://docs-next.strapi.io/cms/admin-panel-customization", + "selectors_key": "admin-panel" + }, + { + "url": "https://docs-next.strapi.io/cms/typescript", + "selectors_key": "typescript" + }, + { + "url": "https://docs-next.strapi.io/cms/plugins", + "selectors_key": "plugins" + }, + { + "url": "https://docs-next.strapi.io/cms/plugins-development", + "selectors_key": "plugins-development" + }, + { + "url": "https://docs-next.strapi.io/cms/cli", + "selectors_key": "cli" + }, + { + "url": "https://docs-next.strapi.io/cms/api", + "selectors_key": "api" + }, + { + "url": "https://docs-next.strapi.io/cms/data-management", + "selectors_key": "data-management" + }, + { + "url": "https://docs-next.strapi.io/cms/migration", + "selectors_key": "migration" + }, + { + "url": "https://docs-next.strapi.io/cloud/getting-started", + "selectors_key": "cloud-getting-started" + }, + { + "url": "https://docs-next.strapi.io/cloud/projects", + "selectors_key": "cloud-projects" + }, + { + "url": "https://docs-next.strapi.io/cloud/account", + "selectors_key": "cloud-account" + }, + { + "url": "https://docs-next.strapi.io/cloud/cli", + "selectors_key": "cloud-cli" + }, + { + "url": "https://docs-next.strapi.io/cloud/advanced", + "selectors_key": "cloud-advanced" + }, + { + "url": "https://docs-next.strapi.io/cms/intro", + "selectors_key": "getting-started" + }, + { + "url": "https://docs-next.strapi.io/cloud/intro", + "selectors_key": "cloud-getting-started" + }, + { + "url": "https://docs-next.strapi.io/cms/usage-information", + "selectors_key": "getting-started" + }, + { + "url": "https://docs-next.strapi.io/cms/faq", + "selectors_key": "faq" + }, + { + "url": "https://docs-next.strapi.io/cms/upgrades", + "selectors_key": "migration" + }, + { + "url": "https://docs-next.strapi.io/cms/setup-deployment", + "selectors_key": "getting-started" + }, + { + "url": "https://docs-next.strapi.io/cms/installation", + "selectors_key": "getting-started" + }, + { + "url": "https://docs-next.strapi.io/cms/community", + "selectors_key": "getting-started" + }, + { + "url": "https://docs-next.strapi.io/release-notes", + "selectors_key": "release-notes" + }, + { + "url": "https://docs-next.strapi.io/cms", + "selectors_key": "default" + }, + { + "url": "https://docs-next.strapi.io/cloud", + "selectors_key": "default" + } + ], + "sitemap_urls": [ + "https://docs-next.strapi.io/sitemap.xml" + ], + "selectors": { + "default": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "Documentation" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "getting-started": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "Getting Started" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "features": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "Features" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "backend": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "Development" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "configurations": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "Configurations" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "admin-panel": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "Development" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "typescript": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "TypeScript" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "plugins": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "Plugins" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "plugins-development": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "Plugins Development" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "cli": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "CLI" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "api": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "APIs" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "data-management": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "Data Management" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "migration": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "Migration" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "faq": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "FAQ" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "release-notes": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "Release Notes" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "cloud-getting-started": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "Getting Started" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "cloud-projects": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "Projects" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "cloud-account": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "Account" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "cloud-cli": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "CLI" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + }, + "cloud-advanced": { + "lvl0": { + "selector": ".breadcrumbs .breadcrumbs__item:nth-child(2) span", + "global": true, + "default_value": "Advanced" + }, + "lvl1": "h1", + "lvl2": "h2", + "lvl3": "h3", + "text": "article p, article li, main p, main li" + } + }, + "selectors_exclude": [ + ".navbar", + ".sidebar", + ".menu", + ".pagination-nav", + ".table-of-contents", + ".hash-link", + "aside" + ], + "custom_settings": { + "rankingRules": [ + "words", + "exactness", + "attribute", + "proximity", + "typo", + "sort" + ], + "searchableAttributes": [ + "hierarchy_lvl1", + "hierarchy_lvl0", + "hierarchy_lvl2", + "hierarchy_lvl3", + "content" + ], + "displayedAttributes": [ + "hierarchy_lvl0", + "hierarchy_lvl1", + "hierarchy_lvl2", + "hierarchy_lvl3", + "content", + "url", + "anchor" + ] + } +} \ No newline at end of file diff --git a/.github/workflows/meilisearch-scraper.yml b/.github/workflows/meilisearch-scraper.yml new file mode 100644 index 0000000000..4d365012b5 --- /dev/null +++ b/.github/workflows/meilisearch-scraper.yml @@ -0,0 +1,58 @@ +name: Meilisearch Documentation Scraper + +on: + push: + branches: + - main + pull_request: + types: [closed] + branches: + - main + workflow_dispatch: # Manual trigger option + schedule: + - cron: '0 2 * * *' # Daily at 2 AM UTC + +jobs: + scrape-docs: + runs-on: ubuntu-latest + # Only run on main branch or merged PRs + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Wait for deployment (if push event) + if: github.event_name == 'push' + run: sleep 60 # Wait for Vercel/Netlify deployment + + - name: Run Meilisearch Scraper + env: + MEILISEARCH_HOST_URL: ${{ secrets.MEILISEARCH_HOST_URL }} + MEILISEARCH_API_KEY: ${{ secrets.MEILISEARCH_MASTER_KEY }} + CONFIG_FILE: .github/meilisearch/scraper-config.json + run: | + run: | + docker run \ + -e MEILISEARCH_HOST_URL="$MEILISEARCH_HOST_URL" \ + -e MEILISEARCH_API_KEY="$MEILISEARCH_API_KEY" \ + -v ${{ github.workspace }}/.github:/config \ + getmeili/docs-scraper:v0.12.8 \ + pipenv run ./docs_scraper /config/meilisearch-scraper-config.json + + - name: Test search index + env: + MEILISEARCH_HOST_URL: ${{ secrets.MEILISEARCH_HOST_URL }} + MEILISEARCH_SEARCH_KEY: ${{ secrets.MEILISEARCH_SEARCH_KEY }} + run: | + echo "Testing search functionality..." + curl -s "$MEILISEARCH_HOST_URL/indexes/strapi-docs/search" \ + -H "Authorization: Bearer $MEILISEARCH_SEARCH_KEY" \ + -H 'Content-Type: application/json' \ + --data '{"q":"strapi","limit":5}' | \ + python3 -m json.tool + + echo "Checking index stats..." + curl -s "$MEILISEARCH_HOST_URL/indexes/strapi-docs/stats" \ + -H "Authorization: Bearer $MEILISEARCH_SEARCH_KEY" | \ + python3 -m json.tool \ No newline at end of file diff --git a/docusaurus/scripts/meilisearch/add-category-order.js b/docusaurus/scripts/meilisearch/add-category-order.js new file mode 100644 index 0000000000..b18962d094 --- /dev/null +++ b/docusaurus/scripts/meilisearch/add-category-order.js @@ -0,0 +1,82 @@ +const { MeiliSearch } = require('meilisearch'); +const fs = require('fs'); +const path = require('path'); + +const client = new MeiliSearch({ + host: process.env.MEILISEARCH_HOST, + apiKey: process.env.MEILISEARCH_MASTER_KEY +}); + +const configPath = path.join(__dirname, 'category-order-config.json'); +const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); + +function getCategoryOrder(category) { + const cmsOrder = config.cms[category]; + const cloudOrder = config.cloud[category]; + + if (cmsOrder !== undefined) return cmsOrder; + if (cloudOrder !== undefined) return cloudOrder; + + return config.default; +} + +async function addCategoryOrder() { + if (!process.env.MEILISEARCH_HOST || !process.env.MEILISEARCH_MASTER_KEY) { + console.error('Error: Missing environment variables'); + console.error('Make sure MEILISEARCH_HOST and MEILISEARCH_MASTER_KEY are defined'); + process.exit(1); + } + + const index = client.index('strapi-docs'); + + console.log('Fetching documents...'); + + let offset = 0; + const limit = 1000; + let hasMore = true; + let totalUpdated = 0; + + while (hasMore) { + const results = await index.getDocuments({ + offset, + limit + }); + + if (results.results.length === 0) { + hasMore = false; + break; + } + + const documentsToUpdate = results.results.map(doc => { + const category = doc.hierarchy_lvl0 || 'Documentation'; + const categoryOrder = getCategoryOrder(category); + + return { + ...doc, + category_order: categoryOrder + }; + }); + + await index.updateDocuments(documentsToUpdate); + + totalUpdated += documentsToUpdate.length; + console.log(`Updated: ${documentsToUpdate.length} documents (total: ${totalUpdated})`); + + offset += limit; + } + + console.log('\nUpdating ranking rules...'); + await index.updateRankingRules([ + 'words', + 'exactness', + 'attribute', + 'category_order:asc', + 'proximity', + 'typo', + 'sort' + ]); + + console.log('\nDone! You can now test your search.'); +} + +addCategoryOrder().catch(console.error); \ No newline at end of file diff --git a/docusaurus/scripts/meilisearch/analyze-categories.js b/docusaurus/scripts/meilisearch/analyze-categories.js new file mode 100644 index 0000000000..088389e509 --- /dev/null +++ b/docusaurus/scripts/meilisearch/analyze-categories.js @@ -0,0 +1,91 @@ +const MEILISEARCH_HOST = process.env.MEILISEARCH_HOST; +const MEILISEARCH_MASTER_KEY = process.env.MEILISEARCH_MASTER_KEY; +const INDEX_UID = process.env.MEILISEARCH_INDEX || 'strapi-docs'; + +require('dotenv').config(); + +async function analyzeCategories() { + if (!MEILISEARCH_HOST || !MEILISEARCH_MASTER_KEY) { + console.error('āŒ Error: Missing environment variables!'); + console.log('Please set MEILISEARCH_HOST and MEILISEARCH_MASTER_KEY'); + process.exit(1); + } + + console.log('Fetching all documents...'); + + let allDocuments = []; + let offset = 0; + const limit = 1000; + let hasMore = true; + + while (hasMore) { + const response = await fetch( + `${MEILISEARCH_HOST}/indexes/${INDEX_UID}/documents?offset=${offset}&limit=${limit}&fields=url,hierarchy_lvl0`, + { + method: 'GET', + headers: { + 'Authorization': `Bearer ${MEILISEARCH_MASTER_KEY}`, + }, + } + ); + + const data = await response.json(); + + if (data.message || data.code) { + console.error('āŒ API Error:', data.message); + console.error('Code:', data.code); + process.exit(1); + } + + const documents = data.results || []; + + if (Array.isArray(documents) && documents.length > 0) { + allDocuments = allDocuments.concat(documents); + offset += limit; + console.log(`Fetched ${allDocuments.length} documents so far...`); + + if (documents.length < limit) { + hasMore = false; + } + } else { + hasMore = false; + } + } + + console.log(`\nšŸ“Š Total documents found: ${allDocuments.length}\n`); + + const categoryCounts = {}; + const urlsByCategory = {}; + + allDocuments.forEach(hit => { + const category = hit.hierarchy_lvl0 || 'Unknown'; + categoryCounts[category] = (categoryCounts[category] || 0) + 1; + + if (!urlsByCategory[category]) { + urlsByCategory[category] = new Set(); + } + if (hit.url) { + urlsByCategory[category].add(hit.url); + } + }); + + console.log('Category Statistics:\n'); + Object.entries(categoryCounts) + .sort((a, b) => b[1] - a[1]) + .forEach(([category, count]) => { + console.log(`${category}: ${count} documents (${urlsByCategory[category].size} unique URLs)`); + }); + + console.log('\n\nā— Pages with "Documentation" category:\n'); + if (urlsByCategory['Documentation']) { + [...urlsByCategory['Documentation']].forEach(url => console.log(url)); + } else { + console.log('āœ… No pages with "Documentation" category found!'); + } +} + +analyzeCategories().catch(error => { + console.error('āŒ Error:', error.message); + console.error(error); + process.exit(1); +}); \ No newline at end of file diff --git a/docusaurus/scripts/meilisearch/category-order-config.json b/docusaurus/scripts/meilisearch/category-order-config.json new file mode 100644 index 0000000000..826904d87e --- /dev/null +++ b/docusaurus/scripts/meilisearch/category-order-config.json @@ -0,0 +1,24 @@ +{ + "cms": { + "Getting Started": 1, + "Features": 2, + "API": 3, + "APIs": 3, + "Configurations": 4, + "Development": 5, + "TypeScript": 6, + "CLI": 7, + "Plugins": 8, + "Plugins Development": 8, + "Migration": 9, + "Data Management": 10 + }, + "cloud": { + "Getting Started": 20, + "Projects": 21, + "Account": 22, + "CLI": 23, + "Advanced": 24 + }, + "default": 99 +} \ No newline at end of file From 22717ba08be76de36ca99d1702e8653087c0e1e0 Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 28 Oct 2025 11:47:06 +0100 Subject: [PATCH 7/8] Fix CMS and Cloud buttons centering in top nav since we added the Contrib Program button --- docusaurus/src/scss/navbar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/src/scss/navbar.scss b/docusaurus/src/scss/navbar.scss index 0e3ca3e8cd..cf8119b7da 100644 --- a/docusaurus/src/scss/navbar.scss +++ b/docusaurus/src/scss/navbar.scss @@ -45,7 +45,7 @@ $selector-color-mode-toggle-wrapper: 'div[class*="ColorModeToggle"]'; * Optical adjustments to have CMS and Cloud items centered * Docusaurus DOM is weird so we can't rely on classical centering techniques */ - margin-left: calc(194px + 20px); // 194px = logo size + margin-left: calc(194px + 226px); // 194px = logo size margin-right: 64px; /** * Use these if we remove the GitHub logo from navbar From 558ff36e44b633f5cb2fdb87fa97fd5779c537b4 Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 28 Oct 2025 11:47:46 +0100 Subject: [PATCH 8/8] Update packages and scripts --- docusaurus/package.json | 6 ++++-- docusaurus/yarn.lock | 42 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/docusaurus/package.json b/docusaurus/package.json index 57b81132b8..d7c4637feb 100644 --- a/docusaurus/package.json +++ b/docusaurus/package.json @@ -16,7 +16,8 @@ "redirections-analysis": "node ./scripts/redirection-analysis/redirect-analyzer.js", "generate-llms": "node scripts/generate-llms.js", "dev:with-llms": "yarn generate-llms && docusaurus start --port 8080 --no-open", - "build:with-llms": "yarn generate-llms && docusaurus build" + "build:with-llms": "yarn generate-llms && docusaurus build", + "meilisearch:update-order": "node -r dotenv/config scripts/meilisearch/add-category-order.js" }, "dependencies": { "@amplitude/analytics-browser": "^2.12.2", @@ -35,7 +36,7 @@ "docusaurus-plugin-image-zoom": "^0.1.1", "docusaurus-plugin-sass": "^0.2.6", "docusaurus2-dotenv": "^1.4.0", - "dotenv": "^16.5.0", + "dotenv": "^17.2.3", "embla-carousel-autoplay": "^7.1.0", "embla-carousel-react": "^7.1.0", "embla-carousel-wheel-gestures": "^3.0.0", @@ -43,6 +44,7 @@ "glob": "^11.0.3", "gray-matter": "^4.0.3", "js-yaml": "^4.1.0", + "meilisearch-docsearch": "^0.8.0", "prism-react-renderer": "^2.1.0", "qs": "^6.11.1", "react": "^18.2.0", diff --git a/docusaurus/yarn.lock b/docusaurus/yarn.lock index a1cf065162..fd36bdc7e5 100644 --- a/docusaurus/yarn.lock +++ b/docusaurus/yarn.lock @@ -3857,7 +3857,7 @@ csso@^5.0.5: dependencies: css-tree "~2.2.0" -csstype@^3.0.2: +csstype@^3.0.2, csstype@^3.1.0: version "3.1.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== @@ -4456,10 +4456,10 @@ dotenv-webpack@1.7.0: dependencies: dotenv-defaults "^1.0.2" -dotenv@^16.5.0: - version "16.5.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.5.0.tgz#092b49f25f808f020050051d1ff258e404c78692" - integrity sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg== +dotenv@^17.2.3: + version "17.2.3" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.3.tgz#ad995d6997f639b11065f419a22fabf567cdb9a2" + integrity sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w== dotenv@^6.2.0: version "6.2.0" @@ -6573,6 +6573,19 @@ medium-zoom@^1.0.6: resolved "https://registry.yarnpkg.com/medium-zoom/-/medium-zoom-1.1.0.tgz#6efb6bbda861a02064ee71a2617a8dc4381ecc71" integrity sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ== +meilisearch-docsearch@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/meilisearch-docsearch/-/meilisearch-docsearch-0.8.0.tgz#5ba4bcba995e7851bd89654fe0db1d4a10aadd83" + integrity sha512-I0sd0z7CRSyn83ACDKbDD4PQNFCe/oZxN4pWgJbqLgLwOEICQMZLMlFVmQPQzzSb9ZkvjjJnaNPxVpZ4yi7upw== + dependencies: + meilisearch "0.50.0" + solid-js "1.9.5" + +meilisearch@0.50.0: + version "0.50.0" + resolved "https://registry.yarnpkg.com/meilisearch/-/meilisearch-0.50.0.tgz#b2eb92984100f33fe2c5dc19600bdc643776170e" + integrity sha512-9IzIkobvnuS18Eg4dq/eJB9W+eXqeLZjNRgq/kKMswSmVYYSQsXqGgSuCA0JkF+o5RwJlwIsieQee6rh313VhA== + memfs@^3.1.2, memfs@^3.4.3: version "3.6.0" resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" @@ -8851,6 +8864,16 @@ serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: dependencies: randombytes "^2.1.0" +seroval-plugins@^1.1.0: + version "1.3.3" + resolved "https://registry.yarnpkg.com/seroval-plugins/-/seroval-plugins-1.3.3.tgz#51bcacf09e5384080d7ea4002b08fd9f6166daf5" + integrity sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w== + +seroval@^1.1.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/seroval/-/seroval-1.3.2.tgz#7e5be0dc1a3de020800ef013ceae3a313f20eca7" + integrity sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ== + serve-handler@^6.1.5: version "6.1.5" resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.5.tgz#a4a0964f5c55c7e37a02a633232b6f0d6f068375" @@ -9026,6 +9049,15 @@ sockjs@^0.3.24: uuid "^8.3.2" websocket-driver "^0.7.4" +solid-js@1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/solid-js/-/solid-js-1.9.5.tgz#168ae067c27d3d437c868484d21751335ec16063" + integrity sha512-ogI3DaFcyn6UhYhrgcyRAMbu/buBJitYQASZz5WzfQVPP10RD2AbCoRZ517psnezrasyCbWzIxZ6kVqet768xw== + dependencies: + csstype "^3.1.0" + seroval "^1.1.0" + seroval-plugins "^1.1.0" + sort-css-media-queries@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz#aa33cf4a08e0225059448b6c40eddbf9f1c8334c"