/** * Copyright (c) 2017-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const CWD = process.cwd(); const React = require('react'); const fs = require('fs'); const classNames = require('classnames'); const loadConfig = require('../../server/config'); const siteConfig = loadConfig(`${CWD}/siteConfig.js`); const translation = require('../../server/translation.js'); const env = require('../../server/env.js'); const translate = require('../../server/translate.js').translate; const setLanguage = require('../../server/translate.js').setLanguage; const readMetadata = require('../../server/readMetadata.js'); readMetadata.generateMetadataDocs(); const Metadata = require('../metadata.js'); const {idx, getPath} = require('../utils.js'); const extension = siteConfig.cleanUrl ? '' : '.html'; // language dropdown nav item for when translations are enabled class LanguageDropDown extends React.Component { render() { setLanguage(this.props.language || 'en'); const helpTranslateString = translate( 'Help Translate|recruit community translators for your project', ); const docsPart = `${siteConfig.docsUrl ? `${siteConfig.docsUrl}/` : ''}`; // add all enabled languages to dropdown const enabledLanguages = env.translation .enabledLanguages() .filter(lang => lang.tag !== this.props.language) .map(lang => { // build the href so that we try to stay in current url but change the language. let href = siteConfig.baseUrl + lang.tag; if ( this.props.current && this.props.current.permalink && this.props.language ) { href = siteConfig.baseUrl + this.props.current.permalink.replace( new RegExp(`^${docsPart}${this.props.language}/`), `${docsPart}${lang.tag}/`, ); } else if (this.props.current.id && this.props.current.id !== 'index') { href = `${siteConfig.baseUrl + lang.tag}/${this.props.current.id}`; } return ( <li key={lang.tag}> <a href={getPath(href, this.props.cleanUrl)}>{lang.name}</a> </li> ); }); // if no languages are enabled besides English, return null if (enabledLanguages.length < 1) { return null; } // Get the current language full name for display in the header nav const currentLanguage = env.translation .enabledLanguages() .filter(lang => lang.tag === this.props.language) .map(lang => lang.name); // add Crowdin project recruiting link if (siteConfig.translationRecruitingLink) { enabledLanguages.push( <li key="recruiting"> <a href={siteConfig.translationRecruitingLink} target="_blank" rel="noreferrer noopener"> {helpTranslateString} </a> </li>, ); } return ( <span> <li key="languages"> <a id="languages-menu" href="#"> <img className="languages-icon" src={`${this.props.baseUrl}img/language.svg`} alt="Languages icon" /> {currentLanguage} </a> <div id="languages-dropdown" className="hide"> <ul id="languages-dropdown-items">{enabledLanguages}</ul> </div> </li> <script dangerouslySetInnerHTML={{ __html: ` const languagesMenuItem = document.getElementById("languages-menu"); const languagesDropDown = document.getElementById("languages-dropdown"); languagesMenuItem.addEventListener("click", function(event) { event.preventDefault(); if (languagesDropDown.className == "hide") { languagesDropDown.className = "visible"; } else { languagesDropDown.className = "hide"; } }); `, }} /> </span> ); } } // header navbar used by all pages generated with docusaurus class HeaderNav extends React.Component { // function to generate each header link, used with each object in siteConfig.headerLinks makeLinks(link) { let href; let docItemActive = false; let docGroupActive = false; if (link.search && this.props.config.algolia) { // return algolia search bar const placeholder = this.props.config.algolia.placeholder || 'Search'; return ( <li className="navSearchWrapper reactNavSearchWrapper" key="search"> <input id="search_input_react" type="text" placeholder={placeholder} title={placeholder} /> </li> ); } if (link.languages) { if ( env.translation.enabled && env.translation.enabledLanguages().length > 1 ) { return ( <LanguageDropDown baseUrl={this.props.baseUrl} language={this.props.language} current={this.props.current} cleanUrl={this.props.config.cleanUrl} key="languagedropdown" /> ); } return null; } if (link.doc) { // set link to document with current page's language/version const langPart = env.translation.enabled ? `${this.props.language || 'en'}-` : ''; const versionPart = env.versioning.enabled && this.props.version !== 'next' ? `version-${this.props.version || env.versioning.defaultVersion}-` : ''; const id = langPart + versionPart + link.doc; if (!Metadata[id]) { let errorStr = `Processing the following \`doc\` field in \`headerLinks\` within \`siteConfig.js\`: '${ link.doc }'`; if (id === link.doc) { errorStr += ' It looks like there is no document with that id that exists in your docs directory. Please double check the spelling of your `doc` field and the `id` fields of your docs.'; } else { errorStr += `${'. Check the spelling of your `doc` field. If that seems sane, and a document in your docs folder exists with that `id` value, \nthen this is likely a bug in Docusaurus.' + ' Docusaurus thinks one or both of translations (currently set to: '}${ env.translation.enabled }) or versioning (currently set to: ${ env.versioning.enabled }) is enabled when maybe they should not be. \nThus my internal id for this doc is: '${id}'. Please file an issue for this possible bug on GitHub.`; } throw new Error(errorStr); } href = this.props.config.baseUrl + getPath(Metadata[id].permalink, this.props.config.cleanUrl); const {id: currentID, sidebar} = this.props.current; docItemActive = currentID && currentID === id; docGroupActive = sidebar && sidebar === Metadata[id].sidebar; } else if (link.page) { // set link to page with current page's language if appropriate const language = this.props.language || ''; if (fs.existsSync(`${CWD}/pages/en/${link.page}.js`)) { href = siteConfig.baseUrl + (env.translation.enabled ? `${language}/` : '') + link.page + extension; } else { href = siteConfig.baseUrl + link.page + extension; } } else if (link.href) { // set link to specified href href = link.href; } else if (link.blog) { // set link to blog url href = `${this.props.baseUrl}blog/`; } const itemClasses = classNames({ siteNavGroupActive: (link.doc && docGroupActive) || (link.blog && this.props.current.blog), siteNavItemActive: docItemActive || (link.blog && this.props.current.blogListing) || (link.page && link.page === this.props.current.id), }); const i18n = translation[this.props.language]; return ( <li key={`${link.label}page`} className={itemClasses}> <a href={href} target={link.external ? '_blank' : '_self'}> {idx(i18n, ['localized-strings', 'links', link.label]) || link.label} </a> </li> ); } renderResponsiveNav() { const headerLinks = this.props.config.headerLinks; // add language drop down to end if location not specified let languages = false; headerLinks.forEach(link => { if (link.languages) { languages = true; } }); if (!languages) { headerLinks.push({languages: true}); } let search = false; headerLinks.forEach(link => { if ( link.doc && !fs.existsSync(`${CWD}/../${readMetadata.getDocsPath()}/`) ) { throw new Error( `You have 'doc' in your headerLinks, but no '${readMetadata.getDocsPath()}' folder exists one level up from ` + `'website' folder. Did you run \`docusaurus-init\` or \`npm run examples\`? If so, ` + `make sure you rename 'docs-examples-from-docusaurus' to 'docs'.`, ); } if (link.blog && !fs.existsSync(`${CWD}/blog/`)) { throw new Error( "You have 'blog' in your headerLinks, but no 'blog' folder exists in your " + "'website' folder. Did you run `docusaurus-init` or `npm run examples`? If so, " + "make sure you rename 'blog-examples-from-docusaurus' to 'blog'.", ); } if (link.page && !fs.existsSync(`${CWD}/pages/`)) { throw new Error( "You have 'page' in your headerLinks, but no 'pages' folder exists in your " + "'website' folder.", ); } // We will add search bar to end if location not specified if (link.search) { search = true; } }); if (!search && this.props.config.algolia) { headerLinks.push({search: true}); } return ( <div className="navigationWrapper navigationSlider"> <nav className="slidingNav"> <ul className="nav-site nav-site-internal"> {headerLinks.map(this.makeLinks, this)} </ul> </nav> </div> ); } render() { const headerClass = siteConfig.headerIcon ? 'headerTitleWithLogo' : 'headerTitle'; const versionsLink = this.props.baseUrl + (env.translation.enabled ? `${this.props.language}/versions${extension}` : `versions${extension}`); return ( <div className="fixedHeaderContainer"> <div className="headerWrapper wrapper"> <header> <a href={ this.props.baseUrl + (env.translation.enabled ? this.props.language : '') }> {siteConfig.headerIcon && ( <img className="logo" src={this.props.baseUrl + siteConfig.headerIcon} alt={siteConfig.title} /> )} {!this.props.config.disableHeaderTitle && ( <h2 className={headerClass}>{this.props.title}</h2> )} </a> {env.versioning.enabled && ( <a href={versionsLink}> <h3>{this.props.version || env.versioning.defaultVersion}</h3> </a> )} {this.renderResponsiveNav()} </header> </div> </div> ); } } HeaderNav.defaultProps = { current: {}, }; module.exports = HeaderNav;