├── .eslintrc.js ├── .github ├── pull_request_template.md └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .lintstagedrc.js ├── .npmrc ├── .nvmrc ├── .percy.yml ├── .prettierignore ├── .prettierrc ├── AWSCLIV2.pkg ├── CHANGELOG.md ├── Makefile ├── README.md ├── __mocks__ ├── gatsby.js └── redoc.js ├── build-retry.sh ├── build.sh ├── code-of-conduct.md ├── component-factory-transformer ├── .cargo │ └── config ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── package.json └── src │ ├── filter_components.rs │ ├── get_components.rs │ └── lib.rs ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-ssr.js ├── jest-preprocess.js ├── jest.config.js ├── netlify.toml ├── netlify └── functions │ └── fetch-url.js ├── package-lock.json ├── package.json ├── plugins ├── gatsby-source-snooty-prod │ ├── gatsby-browser.js │ ├── gatsby-node.js │ ├── gatsby-ssr.js │ ├── package.json │ └── src │ │ └── global-context-providers.js └── utils │ ├── breadcrumbs.js │ ├── docsets.js │ ├── openapi.js │ └── products.js ├── scripts └── ensure-main.js ├── src ├── build-constants.js ├── components │ ├── ActionBar │ │ ├── ActionBar.tsx │ │ ├── ChatbotModal.tsx │ │ ├── DarkModeDropdown.tsx │ │ ├── SearchInput.tsx │ │ ├── SparkIcon.tsx │ │ └── styles.ts │ ├── Admonition.tsx │ ├── AssociatedVersionSelector │ │ └── index.js │ ├── Banner │ │ ├── Banner.tsx │ │ ├── CTABanner.tsx │ │ ├── OfflineBanner.tsx │ │ ├── SiteBanner │ │ │ ├── BrandingShape.tsx │ │ │ ├── index.tsx │ │ │ └── types.ts │ │ └── styles │ │ │ └── bannerItemStyle.ts │ ├── BlockQuote.tsx │ ├── Breadcrumbs │ │ ├── BreadcrumbContainer.js │ │ ├── CollapsedBreadcrumbs.js │ │ ├── IndividualBreadcrumb.js │ │ └── index.js │ ├── Button.tsx │ ├── CTA.tsx │ ├── Card │ │ ├── CardGroup.js │ │ └── index.js │ ├── Chapters │ │ ├── Chapter.js │ │ ├── ChapterNumberLabel.js │ │ ├── RightColumn.js │ │ └── index.js │ ├── ChatbotUi.tsx │ ├── Code │ │ ├── Code.tsx │ │ ├── CodeIO.tsx │ │ ├── Input.tsx │ │ ├── Output.tsx │ │ ├── code-context.tsx │ │ └── styles │ │ │ └── codeStyle.ts │ ├── Collapsible │ │ ├── index.js │ │ ├── styles.css │ │ └── styles.js │ ├── CommunityPillLink.tsx │ ├── ComponentFactory.tsx │ ├── ComponentFactoryLazy.tsx │ ├── ComposableTutorial │ │ ├── Composable.tsx │ │ ├── ComposableTutorial.tsx │ │ ├── ConfigurableOption.tsx │ │ └── index.tsx │ ├── Cond.tsx │ ├── ConditionalWrapper.js │ ├── Container.js │ ├── ContentTransition.js │ ├── Contents │ │ ├── ContentsList.tsx │ │ ├── ContentsListItem.js │ │ ├── contents-context.tsx │ │ └── index.tsx │ ├── DefinitionList │ │ ├── DefinitionListItem.js │ │ └── index.js │ ├── DeprecatedVersionSelector.tsx │ ├── Describe.tsx │ ├── DismissibleSkillsCard.tsx │ ├── DocumentBody.js │ ├── Emphasis.tsx │ ├── Extract.tsx │ ├── FieldList │ │ ├── Field.js │ │ └── index.js │ ├── Figure │ │ ├── CaptionLegend.js │ │ ├── Lightbox.js │ │ └── index.js │ ├── Footer.js │ ├── Footnote │ │ ├── FootnoteReference.js │ │ ├── footnote-context.js │ │ └── index.js │ ├── Glossary.tsx │ ├── GuideNext │ │ ├── ChapterInfo.js │ │ ├── Content.js │ │ ├── GuidesList.js │ │ ├── GuidesListItem.js │ │ ├── index.js │ │ └── read-guides-context.js │ ├── Header │ │ ├── header-context.tsx │ │ └── index.js │ ├── Heading.js │ ├── HorizontalList.js │ ├── Image.js │ ├── Include.tsx │ ├── Instruqt │ │ ├── DrawerButtons.js │ │ ├── InstruqtFrame.js │ │ ├── LabDrawer.js │ │ ├── index.js │ │ └── instruqt-context.js │ ├── Internal │ │ └── Overline.js │ ├── InternalPageNav │ │ ├── InternalPageNav.js │ │ ├── NextPrevLink.js │ │ ├── index.js │ │ └── styles.js │ ├── Introduction.tsx │ ├── Kicker.tsx │ ├── LineBlock │ │ ├── Line.js │ │ └── index.js │ ├── Link.tsx │ ├── List │ │ ├── ListItem.js │ │ └── index.js │ ├── ListTable.js │ ├── Literal.tsx │ ├── LiteralBlock.tsx │ ├── LiteralInclude.tsx │ ├── MainColumn.tsx │ ├── Meta.tsx │ ├── MethodSelector │ │ ├── MethodDescription.js │ │ ├── MethodOptionContent.js │ │ ├── MethodSelector.js │ │ └── index.tsx │ ├── MultiPageTutorials │ │ ├── MPTNextLinkFull.js │ │ ├── MPTNextLinkMini.js │ │ ├── StepNumber.js │ │ ├── TimeRequired.js │ │ ├── constants.ts │ │ ├── hooks │ │ │ ├── use-active-mp-tutorial.js │ │ │ ├── use-mpt-page-options.js │ │ │ └── use-should-show-next.js │ │ ├── index.ts │ │ └── utils.ts │ ├── OfflineDownloadModal │ │ ├── DownloadButton.tsx │ │ ├── DownloadContext.tsx │ │ ├── DownloadModal.tsx │ │ ├── VersionSelector.tsx │ │ └── index.tsx │ ├── OfflineNotAvailable.tsx │ ├── Only.tsx │ ├── OpenAPI │ │ ├── index.js │ │ ├── styles.tsx │ │ └── whitelist.tsx │ ├── OpenAPIChangelog │ │ ├── OpenAPIChangelog.js │ │ ├── components │ │ │ ├── Change.js │ │ │ ├── ChangeList.js │ │ │ ├── FiltersPanel │ │ │ │ ├── components │ │ │ │ │ ├── DiffSelect.js │ │ │ │ │ └── FiltersPanel.js │ │ │ │ └── index.js │ │ │ ├── ReleaseDateBlock.js │ │ │ └── ResourceChangesBlock.js │ │ ├── index.js │ │ └── utils │ │ │ ├── constants.js │ │ │ ├── filterHiddenChanges.js │ │ │ ├── getDiffRequestFormat.js │ │ │ ├── getDiffResourcesList.js │ │ │ ├── getResourceLinkUrl.js │ │ │ └── useFetchDiff.js │ ├── Paragraph.tsx │ ├── Permalink.js │ ├── Procedure │ │ ├── Step.js │ │ └── index.js │ ├── Products │ │ ├── ProductItem.js │ │ └── index.tsx │ ├── RefRole.js │ ├── Reference.js │ ├── ReleaseSpecification.js │ ├── RightColumn.tsx │ ├── Roles │ │ ├── Abbr.js │ │ ├── Class.js │ │ ├── Command.js │ │ ├── File.js │ │ ├── GUILabel.js │ │ ├── Gold.js │ │ ├── Highlight.js │ │ ├── Icon.js │ │ ├── Kbd.js │ │ ├── LinkNewTab.js │ │ ├── Manual.js │ │ ├── Red.js │ │ └── Required.js │ ├── Root.tsx │ ├── RootProvider.js │ ├── Rubric.tsx │ ├── SEO.js │ ├── SVGs │ │ ├── DocsLogo.tsx │ │ ├── NoResults.tsx │ │ └── SkillsBadgeIcon.tsx │ ├── SearchResults │ │ ├── EmptyResults.js │ │ ├── Facets │ │ │ ├── FacetGroup.js │ │ │ ├── FacetTags.js │ │ │ ├── FacetValue.js │ │ │ ├── FacetVersionGroup.js │ │ │ ├── Facets.js │ │ │ ├── index.js │ │ │ ├── useFacets.js │ │ │ └── utils.js │ │ ├── MobileFilters.js │ │ ├── SearchContext.js │ │ ├── SearchFilters.js │ │ ├── SearchResult.js │ │ ├── SearchResults.js │ │ ├── SearchWrapper.js │ │ └── index.js │ ├── Section.tsx │ ├── SectionHeader.tsx │ ├── SeeAlso.tsx │ ├── Select.tsx │ ├── Sidenav │ │ ├── DarkModeToggle.js │ │ ├── DocsHomeButton.js │ │ ├── GuidesLandingTree.js │ │ ├── GuidesTOCTree.js │ │ ├── IA.js │ │ ├── IALinkedData.js │ │ ├── IATransition.js │ │ ├── ProductsList.tsx │ │ ├── Sidenav.js │ │ ├── SidenavDocsLogo.js │ │ ├── SidenavMobileTransition.js │ │ ├── TOCNode.js │ │ ├── Toctree.js │ │ ├── VersionSelector.js │ │ ├── index.js │ │ ├── sidenav-context.tsx │ │ └── styles │ │ │ └── sideNavItem.js │ ├── Spinner.tsx │ ├── StandaloneHeader.js │ ├── Strong.js │ ├── StructuredData │ │ ├── BreadcrumbSchema.tsx │ │ └── DocsLandingSD.tsx │ ├── Subscript.js │ ├── SubstitutionReference.js │ ├── Superscript.js │ ├── SuspenseHelper.js │ ├── Tabs │ │ ├── TabSelectors.js │ │ ├── index.js │ │ ├── make-choices.ts │ │ ├── tab-context.tsx │ │ └── tab-hash-context.tsx │ ├── Tag.js │ ├── Target.js │ ├── Text.tsx │ ├── Time.tsx │ ├── TitleReference.tsx │ ├── Transition.tsx │ ├── Twitter.tsx │ ├── VersionDropdown │ │ └── index.js │ ├── VersionModified.tsx │ ├── Video │ │ ├── VideoPlayButton.js │ │ └── index.js │ ├── Wayfinding │ │ ├── Wayfinding.js │ │ ├── WayfindingOption.js │ │ └── index.js │ ├── Widgets │ │ ├── FeedbackWidget │ │ │ ├── FeedbackCard.js │ │ │ ├── FeedbackContainer.js │ │ │ ├── FeedbackForm.js │ │ │ ├── components │ │ │ │ ├── CloseButton.tsx │ │ │ │ ├── LeafygreenTooltip.tsx │ │ │ │ ├── PageIndicators.js │ │ │ │ ├── ScreenshotButton.js │ │ │ │ ├── StarRating.js │ │ │ │ ├── ViewHeader.tsx │ │ │ │ └── view-components.ts │ │ │ ├── constants.ts │ │ │ ├── context.js │ │ │ ├── handleScreenshot.js │ │ │ ├── hooks │ │ │ │ └── useNoScroll.tsx │ │ │ ├── icons.tsx │ │ │ ├── index.tsx │ │ │ ├── realm.js │ │ │ ├── useFeedbackData.js │ │ │ └── views │ │ │ │ ├── CommentView.js │ │ │ │ ├── RatingView.js │ │ │ │ ├── SubmittedView.js │ │ │ │ └── index.js │ │ └── QuizWidget │ │ │ ├── QuizChoice.js │ │ │ ├── QuizWidget.js │ │ │ ├── RealmApp.js │ │ │ ├── RealmFuncs.js │ │ │ ├── hooks │ │ │ └── useCollection.js │ │ │ └── realm-constants.js │ └── icons │ │ ├── Book.js │ │ ├── C.js │ │ ├── Compass.js │ │ ├── Cpp.js │ │ ├── Csharp.js │ │ ├── DarkMode.tsx │ │ ├── Dart.js │ │ ├── DriverIconMap.ts │ │ ├── Go.js │ │ ├── Java.js │ │ ├── Javascript.js │ │ ├── Kotlin.js │ │ ├── LightningBolt.js │ │ ├── Node.js │ │ ├── ObjectiveC.js │ │ ├── Php.js │ │ ├── Python.js │ │ ├── Ruby.js │ │ ├── Rust.js │ │ ├── Scala.js │ │ ├── Shell.js │ │ ├── Swift.js │ │ └── Typescript.js ├── constants.ts ├── context │ ├── ancestor-components-context.tsx │ ├── dark-mode-context.tsx │ ├── heading-context.tsx │ ├── image-context.tsx │ ├── page-context.tsx │ ├── toc-context.js │ └── version-context.js ├── hooks │ ├── __mocks__ │ │ └── use-site-metadata.js │ ├── use-breadcrumbs.tsx │ ├── use-canonical-url.js │ ├── use-click-outside.tsx │ ├── use-current-url-slug.tsx │ ├── use-hash-anchor.tsx │ ├── use-marian-manifests.js │ ├── use-media.tsx │ ├── use-presentation-mode.tsx │ ├── use-remote-metadata.js │ ├── use-site-metadata.tsx │ ├── useActiveHeading.tsx │ ├── useAllDocsets.tsx │ ├── useAllProducts.tsx │ ├── useAssociatedProducts.tsx │ ├── useCopyClipboard.tsx │ ├── useScreenSize.tsx │ ├── useStickyTopValues.tsx │ ├── useViewport.tsx │ └── useVisibleOnScroll.tsx ├── html.js ├── images │ └── .gitignore ├── init │ └── DocumentDatabase.js ├── layouts │ └── index.js ├── styles │ ├── button.ts │ ├── fonts │ │ ├── EuclidCircularA-Semibold-WebXL.woff │ │ ├── MMSIcons-Regular.ttf │ │ ├── MMSIcons-Regular.woff │ │ ├── MMSIcons-Regular.woff2 │ │ ├── MMSOrgIcons-Regular.ttf │ │ ├── MMSOrgIcons-Regular.woff │ │ ├── MMSOrgIcons-Regular.woff2 │ │ ├── charts.ttf │ │ ├── charts.woff │ │ ├── charts.woff2 │ │ ├── fontawesome4-webfont.ttf │ │ ├── fontawesome4-webfont.woff │ │ └── fontawesome4-webfont.woff2 │ ├── global-dark-mode.css │ ├── icons.css │ ├── images │ │ ├── page-icon-active.png │ │ └── page-icon.png │ ├── landing.module.css │ ├── mongodb-docs.css │ ├── navigation.module.css │ └── sidebar.module.css ├── templates │ ├── FeatureNotAvailable.js │ ├── NotFound.js │ ├── blank.tsx │ ├── changelog.tsx │ ├── document.js │ ├── drivers-index.js │ ├── index.js │ ├── instruqt.tsx │ ├── landing.tsx │ ├── openapi.tsx │ └── product-landing.js ├── theme │ └── docsTheme.ts ├── types │ ├── ast-utils.ts │ ├── ast.ts │ ├── css.d.ts │ └── data.ts └── utils │ ├── append-trailing-punctuation.js │ ├── assert-leading-brand.ts │ ├── assert-leading-slash.js │ ├── assert-trailing-slash.js │ ├── base-url.js │ ├── browser-storage.js │ ├── compare-branches-with-version-numbers.ts │ ├── debounce.js │ ├── display-none.js │ ├── download-file.ts │ ├── dynamically-set-z-index.js │ ├── escape-reserved-html-characters.js │ ├── find-all-key-value-pairs.js │ ├── find-all-nested-attribute.js │ ├── find-key-value-pair.js │ ├── format-text.tsx │ ├── generate-path-prefix.js │ ├── generate-versioned-prefix.js │ ├── get-complete-breadcrumb-data.js │ ├── get-language.js │ ├── get-meta-from-directive.js │ ├── get-nested-value.js │ ├── get-page-slug.js │ ├── get-page-title.js │ ├── get-plaintext.ts │ ├── get-searchbar-results-from-json.js │ ├── get-site-title.js │ ├── get-suitable-icon.js │ ├── get-template.js │ ├── head-scripts │ ├── offline-ui │ │ ├── code.js │ │ ├── collapsible.js │ │ ├── index.js │ │ ├── method-selector.js │ │ ├── sidenav.js │ │ ├── tabs-selectors.js │ │ └── tabs.js │ └── redirect-based-on-lang.js │ ├── intersperse.js │ ├── is-active-toc-node.js │ ├── is-browser.js │ ├── is-current-page.js │ ├── is-offline-docs-build.js │ ├── is-relative-url.js │ ├── is-selected-toc-node.js │ ├── join-class-names.js │ ├── locale.ts │ ├── normalize-path.js │ ├── parse-marian-manifests.js │ ├── realm-user-management.js │ ├── realm.ts │ ├── remove-leading-slash.js │ ├── remove-nested-value.js │ ├── report-analytics.js │ ├── search-facet-constants.js │ ├── search-params-to-url.js │ ├── setup │ ├── construct-build-filter.js │ ├── construct-page-id-prefix.js │ ├── fetch-manifest-metadata.js │ ├── get-page-components.js │ ├── init-realm.js │ ├── save-asset-files.js │ ├── transform-breadcrumbs.js │ └── validate-env-variables.js │ ├── site-metadata.js │ ├── snooty-data-api.ts │ ├── sort-versioned-branches.ts │ ├── structured-data.js │ ├── throttle.js │ ├── url-utils.ts │ ├── use-changelog-data.js │ ├── use-snooty-metadata.js │ ├── validate-element-attributes.js │ └── validate-email.js ├── static ├── assets │ ├── 404.png │ ├── cloud.png │ ├── favicon.ico │ ├── feature-not-avail.svg │ ├── lightning-bolt-dark.svg │ ├── lightning-bolt.svg │ ├── link.svg │ ├── meta_generic.png │ ├── offline-asset.png │ ├── screenshotCTA.svg │ ├── screenshoticon-dark.svg │ └── screenshoticon-light.svg └── media │ ├── icomoon.eot │ ├── icomoon.svg │ ├── icomoon.ttf │ └── icomoon.woff ├── stubs └── process.js ├── tests ├── context │ ├── ancestor-components-context.test.js │ ├── dark-mode-context.test.js │ ├── heading-context.test.js │ ├── toc-context.test.js │ └── version-context.test.js ├── testSetup.js ├── unit │ ├── ActionBar.test.js │ ├── Admonition.test.js │ ├── Banner.test.js │ ├── BlockQuote.test.js │ ├── BreadcrumbContainer.test.js │ ├── BreadcrumbSchema.test.js │ ├── Breadcrumbs.test.js │ ├── Button.test.js │ ├── CTABanner.test.js │ ├── Card.test.js │ ├── CardGroup.test.js │ ├── CardRef.test.js │ ├── ChangeList.test.js │ ├── Chapter.test.js │ ├── Chapters.test.js │ ├── Code.test.js │ ├── CodeIO.test.js │ ├── Collapsible.test.js │ ├── CommunityPillLink.test.js │ ├── ComposableTutorial.test.tsx │ ├── ContentsList.test.js │ ├── ContentsListItem.test.js │ ├── DarkModeDropdown.test.js │ ├── DefinitionList.test.js │ ├── DeprecatedVersionSelector.test.js │ ├── Emphasis.test.js │ ├── FeedbackWidget.test.js │ ├── Field.test.js │ ├── FieldList.test.js │ ├── Figure.test.js │ ├── Footnote.test.js │ ├── FootnoteReference.test.js │ ├── GuideNext.test.js │ ├── GuidesLandingTree.test.js │ ├── GuidesTOCTree.test.js │ ├── Head.test.js │ ├── Heading.test.js │ ├── IA.test.js │ ├── Instruqt.test.js │ ├── InternalPageNav.test.js │ ├── Lightbox.test.js │ ├── Line.test.js │ ├── LineBlock.test.js │ ├── Link.test.js │ ├── List.test.js │ ├── ListTable.test.js │ ├── Literal.test.js │ ├── LiteralInclude.test.js │ ├── Meta.test.js │ ├── MethodSelector.test.js │ ├── MultiPageTutorials.test.js │ ├── OfflineDownloadModal.test.tsx │ ├── OpenAPI.test.js │ ├── OpenAPIChangelog.test.js │ ├── Paragraph.test.js │ ├── Presentation.test.js │ ├── Procedure.test.js │ ├── ProductsList.test.js │ ├── QuizWidget.test.js │ ├── Reference.test.js │ ├── ReleaseSpecification.test.js │ ├── Role.test.js │ ├── SearchResults.test.js │ ├── Section.test.js │ ├── Select.test.js │ ├── Sidenav.test.js │ ├── SiteBanner.test.tsx │ ├── Step.test.js │ ├── Strong.test.js │ ├── Tabs.test.js │ ├── Target.test.js │ ├── Text.test.js │ ├── Time.test.js │ ├── TitleReference.test.js │ ├── Toctree.test.js │ ├── VersionDropdown.test.js │ ├── VersionModified.test.js │ ├── Video.test.js │ ├── Wayfinding.test.js │ ├── __mockStyles.js │ ├── __snapshots__ │ │ ├── Admonition.test.js.snap │ │ ├── Banner.test.js.snap │ │ ├── BlockQuote.test.js.snap │ │ ├── BreadcrumbContainer.test.js.snap │ │ ├── BreadcrumbSchema.test.js.snap │ │ ├── Breadcrumbs.test.js.snap │ │ ├── Button.test.js.snap │ │ ├── CTABanner.test.js.snap │ │ ├── Card.test.js.snap │ │ ├── CardGroup.test.js.snap │ │ ├── CardRef.test.js.snap │ │ ├── ChangeList.test.js.snap │ │ ├── Chapter.test.js.snap │ │ ├── Code.test.js.snap │ │ ├── CodeIO.test.js.snap │ │ ├── Collapsible.test.js.snap │ │ ├── CommunityPillLink.test.js.snap │ │ ├── ComposableTutorial.test.tsx.snap │ │ ├── DarkModeDropdown.test.js.snap │ │ ├── DefinitionList.test.js.snap │ │ ├── Emphasis.test.js.snap │ │ ├── Field.test.js.snap │ │ ├── FieldList.test.js.snap │ │ ├── Figure.test.js.snap │ │ ├── Footnote.test.js.snap │ │ ├── FootnoteReference.test.js.snap │ │ ├── GuideNext.test.js.snap │ │ ├── GuidesLandingTree.test.js.snap │ │ ├── Head.test.js.snap │ │ ├── Heading.test.js.snap │ │ ├── IA.test.js.snap │ │ ├── InternalPageNav.test.js.snap │ │ ├── Lightbox.test.js.snap │ │ ├── Line.test.js.snap │ │ ├── LineBlock.test.js.snap │ │ ├── Link.test.js.snap │ │ ├── List.test.js.snap │ │ ├── ListTable.test.js.snap │ │ ├── Literal.test.js.snap │ │ ├── LiteralInclude.test.js.snap │ │ ├── OfflineDownloadModal.test.tsx.snap │ │ ├── OpenAPIChangelog.test.js.snap │ │ ├── Paragraph.test.js.snap │ │ ├── Presentation.test.js.snap │ │ ├── Procedure.test.js.snap │ │ ├── QuizWidget.test.js.snap │ │ ├── Reference.test.js.snap │ │ ├── ReleaseSpecification.test.js.snap │ │ ├── Role.test.js.snap │ │ ├── SearchResults.test.js.snap │ │ ├── Section.test.js.snap │ │ ├── Select.test.js.snap │ │ ├── SiteBanner.test.tsx.snap │ │ ├── Step.test.js.snap │ │ ├── Strong.test.js.snap │ │ ├── Target.test.js.snap │ │ ├── Text.test.js.snap │ │ ├── TitleReference.test.js.snap │ │ ├── VersionDropdown.test.js.snap │ │ ├── VersionModified.test.js.snap │ │ ├── Video.test.js.snap │ │ └── Wayfinding.test.js.snap │ ├── browser-storage.test.js │ ├── data │ │ ├── Admonition.test.json │ │ ├── Banner.test.json │ │ ├── BlockQuote.test.json │ │ ├── Breadcrumbs.test.json │ │ ├── Button.test.json │ │ ├── CTABanner.test.json │ │ ├── Card.test.json │ │ ├── CardGroup.test.json │ │ ├── CardRef.test.json │ │ ├── Chapters.test.json │ │ ├── Code.test.json │ │ ├── CodeIO.test.json │ │ ├── Collapsible.test.json │ │ ├── CollapsibleExpanded.test.json │ │ ├── CommunityPillLink.test.json │ │ ├── CompleteEOLPageContext.json │ │ ├── Composable.test.json │ │ ├── DefinitionList.test.json │ │ ├── EOLSnootyMetadata.json │ │ ├── Emphasis.test.json │ │ ├── FeedbackWidget.js │ │ ├── FieldList.test.json │ │ ├── Figure.test.json │ │ ├── FigureBorder.test.json │ │ ├── FigureLightbox.test.json │ │ ├── Footnote.test.json │ │ ├── FootnoteReference.test.json │ │ ├── GuideNext.test.json │ │ ├── HeadPageContext.test.json │ │ ├── Heading.test.json │ │ ├── IA.test.json │ │ ├── Include.test.json │ │ ├── Instruqt.test.json │ │ ├── LandingPageCards.test.json │ │ ├── Line-empty.test.json │ │ ├── Line.test.json │ │ ├── LineBlock.test.json │ │ ├── List.test.json │ │ ├── ListTable.test.json │ │ ├── ListTableFixedWidths.test.json │ │ ├── Literal.test.json │ │ ├── LiteralInclude.test.json │ │ ├── MetaData.js │ │ ├── MetadataWithoutToc.json │ │ ├── MethodSelector.test.json │ │ ├── MultiCard.test.json │ │ ├── OpenAPIChangelog.js │ │ ├── PageContext.test.json │ │ ├── Paragraph-Format.test.json │ │ ├── Paragraph.test.json │ │ ├── Procedure.test.json │ │ ├── QuizWidget.test.json │ │ ├── Reference.test.json │ │ ├── ReleaseSpecification.test.json │ │ ├── Role-abbr.test.json │ │ ├── Role-file.test.json │ │ ├── Role-guilabel.test.json │ │ ├── SearchResults.test.json │ │ ├── Section.test.json │ │ ├── SnootyMetadata.json │ │ ├── Step.test.json │ │ ├── Strong.test.json │ │ ├── Tabs-anonymous.test.json │ │ ├── Tabs-hidden.test.json │ │ ├── Tabs-languages.test.json │ │ ├── Tabs-platform.test.json │ │ ├── Target.test.json │ │ ├── Text.test.json │ │ ├── Time.test.json │ │ ├── TitleReference.test.json │ │ ├── Toctree.test.json │ │ ├── VersionDropdown.test.json │ │ ├── VersionModified.test.json │ │ ├── Video.test.json │ │ ├── Wayfinding.test.json │ │ ├── ecosystem │ │ │ └── slugToBreadcrumbLabel.json │ │ ├── guidesPageMetadata.json │ │ ├── guidesPageMetadataTwoCategories.json │ │ └── screenshot.test.json │ ├── debounce.test.js │ ├── filterHiddenChanges.test.js │ ├── useFeedbackData.test.js │ ├── useScreenSize.test.js │ ├── useStickyTopValues.test.js │ ├── useViewport.test.js │ ├── useVisibleOnScroll.test.js │ └── utils │ │ ├── __snapshots__ │ │ ├── locale.test.js.snap │ │ └── structured-data.test.js.snap │ │ ├── append-trailing-punctuation.test.js │ │ ├── assert-leading-brand.test.ts │ │ ├── assert-trailing-slash.test.js │ │ ├── base-url.test.js │ │ ├── find-all-nested-attribute.test.js │ │ ├── generate-path-prefix.test.js │ │ ├── generate-versioned-prefix.test.js │ │ ├── head-scripts │ │ └── redirect-based-on-lang.test.js │ │ ├── is-relative-url.test.js │ │ ├── join-class-names.test.js │ │ ├── locale.test.js │ │ ├── mock-marian-fetch.js │ │ ├── remove-leading-slash.test.js │ │ ├── setup │ │ └── construct-build-filter.test.js │ │ ├── structured-data.test.js │ │ └── validate-element-attributes.test.js └── utils │ ├── data │ ├── feedbackWidgetScreenshotFunctions.js │ ├── how-to-structured-data.json │ ├── marian-manifests.json │ └── parsed-marian-manifests.json │ ├── feedbackWidgetStitchFunctions.js │ ├── get-searchbar-results-from-json.test.js │ ├── index.js │ ├── mock-location.js │ ├── mock-with-prefix.js │ ├── mockStaticQuery.js │ └── parse-marian-manifests.test.js └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | globals: { 3 | __PATH_PREFIX__: true, 4 | browser: true, 5 | }, 6 | extends: ['react-app', 'plugin:import/errors'], 7 | ignorePatterns: ['node_modules/', 'public/'], 8 | plugins: ['jest', '@emotion', 'import', 'testing-library'], 9 | rules: { 10 | '@emotion/pkg-renaming': 'error', 11 | 'no-return-await': 1, 12 | 'import/order': [ 13 | 'error', 14 | { 15 | groups: ['external', 'builtin', 'internal', 'parent', 'sibling', 'index'], 16 | }, 17 | ], 18 | 'testing-library/no-wait-for-snapshot': 'error', 19 | }, 20 | settings: { 21 | 'import/resolver': { 22 | node: { 23 | moduleDirectory: ['node_modules', '.'], 24 | extensions: ['.js', '.jsx', '.ts', '.tsx'], 25 | }, 26 | }, 27 | }, 28 | overrides: [ 29 | { 30 | files: ['*.ts', '*.tsx'], 31 | parser: '@typescript-eslint/parser', 32 | }, 33 | ], 34 | }; 35 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Stories/Links: 2 | 3 | DOP-NNNN 4 | 5 | ### Current Behavior: 6 | 7 | _Put a link to current staging or production behavior, if applicable_ 8 | 9 | ### Staging Links: 10 | 11 | _Put a link to your staging environment(s), if applicable_ 12 | 13 | ### Notes: 14 | 15 | ### README updates 16 | 17 | - - [ ] This PR introduces changes that should be reflected in the README, and I have made those updates. 18 | - - [ ] This PR does not introduce changes that should be reflected in the README 19 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' 5 | 6 | name: Create Release 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | - name: Get environment 15 | id: environment 16 | run: | 17 | echo "::set-output name=date::$(date +%Y-%m-%d)" 18 | - name: Create Release 19 | id: create_release 20 | uses: actions/create-release@v1 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | with: 24 | tag_name: ${{ github.ref }} 25 | release_name: "Release: [${{ github.ref }}] - ${{ steps.environment.outputs.date }}" 26 | draft: true 27 | prerelease: true 28 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test: 13 | strategy: 14 | matrix: 15 | platform: [ubuntu-latest, macos-latest] 16 | runs-on: ${{ matrix.platform }} 17 | env: 18 | NPM_BASE_64_AUTH: ${{ secrets.NPM_BASE_64_AUTH }} 19 | NPM_EMAIL: ${{ secrets.NPM_EMAIL }} 20 | steps: 21 | - uses: actions/checkout@v1 22 | - uses: actions/setup-node@v1 23 | with: 24 | node-version: '18.15' 25 | - name: Install 26 | run: npm ci --legacy-peer-deps 27 | - name: Lint 28 | run: npm run lint && npm run format 29 | - name: Test 30 | run: npm test 31 | env: 32 | CI: true 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | public/ 3 | .cache/ 4 | .DS_Store 5 | static/* 6 | # Don't ignore the assets or media directory 7 | !static/assets 8 | !static/media 9 | docs-tools/ 10 | coverage/ 11 | .coverage 12 | .env.* 13 | .vscode/ 14 | .env 15 | .swc/ 16 | bundle.zip 17 | # Local Netlify folder 18 | .netlify 19 | # pyenv 20 | .python-version -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | const { CLIEngine } = require('eslint'); 2 | 3 | // lint-staged seems to disregard eslint ignore rules if we have the eslint 4 | // "--max-warnings 0" option set. This will potentially break our linting process 5 | // if we are staging files that we want ignored (such as .eslintrc.js). 6 | 7 | // This was the recommended fix according to lint-staged's README: 8 | // https://github.com/okonet/lint-staged/blob/fa15d686deb90b7ffddfbcf644d56ed05fcd8a38/README.md#how-can-i-ignore-files-from-eslintignore 9 | 10 | module.exports = { 11 | '*.{js,jsx,ts,tsx}': (files) => { 12 | const cli = new CLIEngine({}); 13 | const filesToLint = files.filter((file) => !cli.isPathIgnored(file)).join(' '); 14 | return ['npm run format:fix', `npm run lint:fix -- ${filesToLint}`]; 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://artifactory.corp.mongodb.com/artifactory/api/npm/npm/ 2 | @mdb:registry=https://artifactory.corp.mongodb.com/artifactory/api/npm/npm/ 3 | //artifactory.corp.mongodb.com/artifactory/api/npm/npm/:_auth=${NPM_BASE_64_AUTH} 4 | _email=${NPM_EMAIL} 5 | _always-auth=true 6 | # Ensures npm doesn't throw error about conflicting versions of React for LG components 7 | legacy-peer-deps=true 8 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18.15 2 | -------------------------------------------------------------------------------- /.percy.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | discovery: 3 | concurrency: 16 4 | launch-options: 5 | timeout: 90000 6 | snapshot: 7 | widths: 8 | - 375 9 | - 1280 10 | minHeight: 1024 11 | enable-javascript: false 12 | percy-css: | 13 | #intercom-container { 14 | display: none; 15 | } 16 | static: 17 | include: "**/*.html" 18 | exclude: ["/includes/**", "/reference/**"] 19 | options: 20 | - include: "**/*.html" 21 | waitForTimeout: 2000 22 | waitForSelector: body 23 | upload: 24 | files: "**/*.{png,jpg,jpeg}" 25 | ignore: "" 26 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .cache 2 | CHANGELOG.md 3 | package.json 4 | package-lock.json 5 | public 6 | static 7 | tests/unit/data 8 | component-factory-transformer 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "jsxBracketSameLine": false, 4 | "printWidth": 120, 5 | "singleQuote": true, 6 | "tabWidth": 2, 7 | } 8 | -------------------------------------------------------------------------------- /AWSCLIV2.pkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/AWSCLIV2.pkg -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GIT_BRANCH=$(shell git rev-parse --abbrev-ref HEAD) 2 | USER=$(shell whoami) 3 | STAGING_BUCKET=docs-mongodb-org-stg 4 | STAGING_URL="https://docs-mongodb-org-stg.s3.us-east-2.amazonaws.com" 5 | -include .env.production 6 | 7 | .PHONY: stage 8 | 9 | # To stage a specific build, include the commit hash as environment variable when staging 10 | # example: COMMIT_HASH=123456 make stage 11 | # Here, generate path prefix according to environment variables 12 | prefix: 13 | ifdef COMMIT_HASH 14 | ifdef PATCH_ID 15 | PREFIX = $(COMMIT_HASH)/$(PATCH_ID)/$(GATSBY_PARSER_BRANCH)/$(GATSBY_SITE) 16 | else 17 | PREFIX = $(COMMIT_HASH)/$(GATSBY_PARSER_BRANCH)/$(GATSBY_SITE) 18 | endif 19 | else 20 | PREFIX = $(GATSBY_PARSER_BRANCH)/$(GATSBY_SITE) 21 | endif 22 | 23 | stage: prefix 24 | @if [ -z "${GATSBY_SNOOTY_DEV}" ]; then \ 25 | echo "To stage changes to the Snooty frontend, ensure that GATSBY_SNOOTY_DEV=true in your production environment."; exit 1; \ 26 | else \ 27 | mut-publish public ${STAGING_BUCKET} --prefix=${PREFIX} --stage ${ARGS}; \ 28 | echo "Hosted at ${STAGING_URL}/${PREFIX}/${USER}/${GIT_BRANCH}/index.html"; \ 29 | fi 30 | -------------------------------------------------------------------------------- /__mocks__/gatsby.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const gatsby = jest.requireActual('gatsby'); 4 | 5 | module.exports = { 6 | ...gatsby, 7 | graphql: jest.fn(), 8 | navigate: jest.fn(), 9 | StaticQuery: jest.fn(), 10 | withPrefix: jest.fn().mockImplementation((path) => (path.startsWith(`/`) ? path : `/${path}`)), 11 | useStaticQuery: jest.fn(), 12 | // https://www.gatsbyjs.org/docs/unit-testing/ 13 | Link: jest.fn().mockImplementation( 14 | // these props are invalid for an `a` tag 15 | ({ activeClassName, activeStyle, getProps, innerRef, partiallyActive, ref, replace, to, ...rest }) => 16 | React.createElement('a', { 17 | ...rest, 18 | href: to, 19 | }) 20 | ), 21 | }; 22 | -------------------------------------------------------------------------------- /__mocks__/redoc.js: -------------------------------------------------------------------------------- 1 | // Mock redoc library by default to avoid nested import error in RedocStandalone component 2 | // import * as YAML from './dist/index.js' 3 | module.exports = { 4 | RedocStandalone: jest.fn(), 5 | }; 6 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | # variables that need to be changed based on the content repo you're working on ------------------------------------------- 2 | TESTING_REPO_NAME=$1 # name of content repo 3 | PARSER_VERSION=$2 # version of the parser to download 4 | # ------------------------------------------------------------------------------------------------------------------------- 5 | 6 | # Check that content repo has been successfully cloned 7 | if [ -d "${TESTING_REPO_NAME}" ]; then 8 | echo "Directory ${TESTING_REPO_NAME} exists" 9 | else 10 | echo "Content repository directory for ${TESTING_REPO_NAME} does not exist, parse and build will fail" 11 | fi 12 | 13 | 14 | echo Beginning build step 15 | -------------------------------------------------------------------------------- /component-factory-transformer/.cargo/config: -------------------------------------------------------------------------------- 1 | # These command aliases are not final, may change 2 | [alias] 3 | # Alias to build actual plugin binary for the specified target. 4 | build-wasi = "build --target wasm32-wasip1" 5 | build-wasm32 = "build --target wasm32-unknown-unknown" 6 | -------------------------------------------------------------------------------- /component-factory-transformer/.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /component-factory-transformer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "component-factory-filter" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [profile.release] 10 | lto = false 11 | 12 | [dependencies] 13 | serde = "1" 14 | serde_json = "1.0.114" 15 | swc_atoms = "0.6.5" 16 | swc_core = { version = "0.90.*", features = ["ecma_plugin_transform"] } 17 | swc_ecma_parser = "0.143.6" 18 | swc_ecma_transforms_base = "0.137.12" 19 | swc_ecma_transforms_testing = "0.140.11" 20 | testing = "0.35.19" 21 | 22 | # .cargo/config defines few alias to build plugin. 23 | # cargo build-wasi generates wasm-wasi32 binary 24 | # cargo build-wasm32 generates wasm32-unknown-unknown binary. 25 | -------------------------------------------------------------------------------- /component-factory-transformer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "component-factory-transformer", 3 | "version": "0.1.0", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "keywords": ["swc-plugin"], 8 | "main": "target/wasm32-wasip1/release/component-factory-transformer.wasm", 9 | "scripts": { 10 | "prepublishOnly": "cargo build-wasi --release" 11 | }, 12 | "files": [], 13 | "preferUnplugged": true 14 | } 15 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ThemeProvider } from '@emotion/react'; 3 | import { theme } from './src/theme/docsTheme'; 4 | import './src/styles/mongodb-docs.css'; 5 | import './src/styles/icons.css'; 6 | import './src/styles/global-dark-mode.css'; 7 | 8 | export const wrapRootElement = ({ element }) => {element}; 9 | 10 | export const shouldUpdateScroll = ({ routerProps: { location } }) => { 11 | const { hash, state } = location; 12 | if (hash) { 13 | return decodeURI(hash.slice(1)); 14 | } 15 | if (state?.preserveScroll) { 16 | return false; 17 | } 18 | return true; 19 | }; 20 | -------------------------------------------------------------------------------- /jest-preprocess.js: -------------------------------------------------------------------------------- 1 | const babelOptions = { 2 | presets: [ 3 | [ 4 | '@babel/preset-react', 5 | { 6 | runtime: 'automatic', 7 | }, 8 | ], 9 | 'babel-preset-gatsby', 10 | '@babel/preset-typescript', 11 | '@emotion/babel-preset-css-prop', 12 | ], 13 | plugins: ['@emotion'], 14 | }; 15 | 16 | module.exports = require('babel-jest').createTransformer(babelOptions); 17 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [[integrations]] 2 | name = "snooty-cache-plugin" 3 | 4 | [build] 5 | publish = "public" 6 | command = ". ./build.sh $REPO_NAME $PARSER_VERSION" 7 | 8 | [build.environment] 9 | ORG_NAME = "10gen" 10 | REPO_NAME = "cloud-docs" 11 | BRANCH_NAME = "master" 12 | PARSER_VERSION = "v0.20.5" 13 | -------------------------------------------------------------------------------- /plugins/gatsby-source-snooty-prod/gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { GlobalProviders } from './src/global-context-providers'; 3 | 4 | export function wrapRootElement({ element }) { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /plugins/gatsby-source-snooty-prod/gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { GlobalProviders } from './src/global-context-providers'; 3 | 4 | export function wrapRootElement({ element }) { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /plugins/gatsby-source-snooty-prod/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-source-snooty-prod" 3 | } 4 | -------------------------------------------------------------------------------- /plugins/gatsby-source-snooty-prod/src/global-context-providers.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useStaticQuery, graphql } from 'gatsby'; 3 | import { MetadataProvider } from '../../../src/utils/use-snooty-metadata'; 4 | 5 | export function GlobalProviders({ element }) { 6 | const { 7 | snootyMetadata: { metadata }, 8 | } = useStaticQuery(graphql` 9 | { 10 | snootyMetadata { 11 | metadata 12 | } 13 | } 14 | `); 15 | 16 | return {element}; 17 | } 18 | -------------------------------------------------------------------------------- /plugins/utils/breadcrumbs.js: -------------------------------------------------------------------------------- 1 | const { siteMetadata } = require('../../src/utils/site-metadata'); 2 | 3 | const breadcrumbType = `Breadcrumb`; 4 | 5 | const createBreadcrumbNodes = async ({ db, createNode, createNodeId, createContentDigest }) => { 6 | const { database, project } = siteMetadata; 7 | let breadcrumbData; 8 | try { 9 | breadcrumbData = await db.fetchBreadcrumbs(database, project); 10 | } catch (e) { 11 | console.error(`Error while fetching breadcrumb data from Atlas: ${e}`); 12 | } 13 | const [breadcrumbs, propertyUrl] = breadcrumbData 14 | ? [breadcrumbData.breadcrumbs, breadcrumbData.propertyUrl] 15 | : [null, '']; 16 | 17 | return createNode({ 18 | children: [], 19 | id: createNodeId(`Breadcrumbs-${project}`), 20 | internal: { 21 | contentDigest: createContentDigest(breadcrumbs), 22 | type: breadcrumbType, 23 | }, 24 | breadcrumbs: breadcrumbs, 25 | propertyUrl: propertyUrl, 26 | }); 27 | }; 28 | 29 | module.exports = { 30 | createBreadcrumbNodes, 31 | breadcrumbType, 32 | }; 33 | -------------------------------------------------------------------------------- /plugins/utils/docsets.js: -------------------------------------------------------------------------------- 1 | const createDocsetNodes = async ({ db, createNode, createNodeId, createContentDigest }) => { 2 | // Get all MongoDB products for the sidenav 3 | const docsets = await db.realmInterface.fetchDocsets(); 4 | docsets.forEach((docset) => { 5 | createNode({ 6 | children: [], 7 | id: createNodeId(`docsetInfo-${docset.repoName}`), 8 | internal: { 9 | contentDigest: createContentDigest(docset), 10 | type: 'Docset', 11 | }, 12 | displayName: docset.displayName, 13 | prefix: docset.prefix, 14 | project: docset.project, 15 | url: docset.url, 16 | branches: docset.branches, 17 | hasEolVersions: docset.hasEolVersions, 18 | }); 19 | }); 20 | }; 21 | 22 | module.exports = { 23 | createDocsetNodes, 24 | }; 25 | -------------------------------------------------------------------------------- /plugins/utils/products.js: -------------------------------------------------------------------------------- 1 | const createProductNodes = async ({ db, createNode, createNodeId, createContentDigest }) => { 2 | // Get all MongoDB products for the sidenav 3 | const products = await db.fetchAllProducts(); 4 | products.forEach((product) => { 5 | createNode({ 6 | children: [], 7 | id: createNodeId(`Product-${product.title}`), 8 | internal: { 9 | contentDigest: createContentDigest(product), 10 | type: 'Product', 11 | }, 12 | parent: null, 13 | title: product.title, 14 | url: product.baseUrl + product.slug, 15 | }); 16 | }); 17 | }; 18 | 19 | module.exports = { 20 | createProductNodes, 21 | }; 22 | -------------------------------------------------------------------------------- /scripts/ensure-main.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require('child_process'); 2 | 3 | const getGitBranch = () => { 4 | return execSync('git rev-parse --abbrev-ref HEAD') 5 | .toString('utf8') 6 | .replace(/[\n\r\s]+$/, ''); 7 | }; 8 | 9 | const ensureMaster = () => ['main', 'master'].includes(getGitBranch()); 10 | 11 | const main = () => { 12 | let warned = false; 13 | 14 | if (!ensureMaster()) { 15 | warned = true; 16 | console.error('ERROR: Can only release on master'); 17 | } 18 | 19 | if (warned) { 20 | process.exit(1); 21 | } 22 | }; 23 | 24 | main(); 25 | -------------------------------------------------------------------------------- /src/build-constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | DOCUMENTS_COLLECTION: 'documents', 3 | ASSETS_COLLECTION: 'assets', 4 | METADATA_COLLECTION: 'metadata', 5 | SNOOTY_REALM_APP_ID: 'snooty-koueq', 6 | }; 7 | -------------------------------------------------------------------------------- /src/components/Banner/SiteBanner/BrandingShape.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const BrandingShape = () => { 4 | const width = 109; 5 | const height = 40; 6 | const viewBox = `0 0 ${width} ${height}`; 7 | 8 | return ( 9 | 10 | 14 | 15 | ); 16 | }; 17 | 18 | export default BrandingShape; 19 | -------------------------------------------------------------------------------- /src/components/Banner/SiteBanner/types.ts: -------------------------------------------------------------------------------- 1 | export interface SiteBannerContent { 2 | isEnabled: boolean; 3 | altText: string; 4 | imgPath?: string; 5 | tabletImgPath?: string; 6 | mobileImgPath?: string; 7 | bgColor?: string; 8 | text?: string; 9 | pillText?: string; 10 | url: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/components/BlockQuote.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BlockQuoteNode } from '../types/ast'; 3 | import ComponentFactory from './ComponentFactory'; 4 | 5 | const BlockQuote = ({ nodeData: { children }, ...rest }: { nodeData: BlockQuoteNode }) => ( 6 |
7 | {children.map((element, index) => ( 8 | 9 | ))} 10 |
11 | ); 12 | 13 | export default BlockQuote; 14 | -------------------------------------------------------------------------------- /src/components/CTA.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { css, cx } from '@leafygreen-ui/emotion'; 3 | import { ParentNode } from '../types/ast'; 4 | import ComponentFactory from './ComponentFactory'; 5 | 6 | const CTA = ({ nodeData: { children }, ...rest }: { nodeData: ParentNode }) => ( 7 |
12 | {children.map((child, i) => ( 13 | 14 | ))} 15 |
16 | ); 17 | 18 | export default CTA; 19 | -------------------------------------------------------------------------------- /src/components/Chapters/ChapterNumberLabel.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from '@emotion/styled'; 4 | import { palette } from '@leafygreen-ui/palette'; 5 | import { theme } from '../../theme/docsTheme'; 6 | 7 | const Label = styled('div')` 8 | background-color: ${palette.green.light3}; 9 | border-radius: ${theme.size.tiny}; 10 | color: ${palette.black}; 11 | 12 | .dark-theme & { 13 | background-color: ${palette.blue.dark3}; 14 | color: ${palette.blue.light2}; 15 | } 16 | font-size: ${theme.fontSize.small}; 17 | font-weight: bold; 18 | line-height: ${theme.size.medium}; 19 | height: ${theme.size.medium}; 20 | text-align: center; 21 | width: 83px; 22 | `; 23 | 24 | const ChapterNumberLabel = ({ className, number }) => { 25 | return ; 26 | }; 27 | 28 | ChapterNumberLabel.propTypes = { 29 | className: PropTypes.string, 30 | number: PropTypes.number.isRequired, 31 | }; 32 | 33 | export default ChapterNumberLabel; 34 | -------------------------------------------------------------------------------- /src/components/Code/Input.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ComponentFactory from '../ComponentFactory'; 3 | import { ParentNode } from '../../types/ast'; 4 | 5 | const Input = ({ nodeData: { children }, ...rest }: { nodeData: ParentNode }) => { 6 | return children.map((child, i) => ); 7 | }; 8 | 9 | export default Input; 10 | -------------------------------------------------------------------------------- /src/components/Collapsible/styles.css: -------------------------------------------------------------------------------- 1 | /* minor css to change nth child properties in browsers without support for :nth-child(n of [selector]) */ 2 | 3 | .collapsible { 4 | border-top: 1px solid #e8edeb; 5 | margin-top: 32px; 6 | } 7 | 8 | .collapsible ~ .collapsible { 9 | border-top: none; 10 | margin-top: 0; 11 | } -------------------------------------------------------------------------------- /src/components/ComposableTutorial/Composable.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { css, cx } from '@leafygreen-ui/emotion'; 3 | import { ComposableNode } from '../../types/ast'; 4 | import ComponentFactory from '../ComponentFactory'; 5 | import { theme } from '../../theme/docsTheme'; 6 | 7 | interface ComposableProps { 8 | nodeData: ComposableNode; 9 | } 10 | 11 | const containerStyle = css` 12 | > *:first-child { 13 | margin-top: ${theme.size.medium}; 14 | } 15 | `; 16 | 17 | const Composable = ({ nodeData: { children }, ...rest }: ComposableProps) => { 18 | return ( 19 |
20 | {children.map((c, i) => ( 21 | 22 | ))} 23 |
24 | ); 25 | }; 26 | 27 | export default Composable; 28 | -------------------------------------------------------------------------------- /src/components/ComposableTutorial/index.tsx: -------------------------------------------------------------------------------- 1 | import ComposableTutorial from './ComposableTutorial'; 2 | 3 | export { ComposableTutorial }; 4 | -------------------------------------------------------------------------------- /src/components/Cond.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { getNestedValue } from '../utils/get-nested-value'; 3 | import { Directive } from '../types/ast'; 4 | import ComponentFactory from './ComponentFactory'; 5 | 6 | // For now, explicitly define the arguments that should be accepted for Gatsby to build the node 7 | const VALID_COND_ARGS = ['html', '(not man)', 'cloud']; 8 | 9 | const Cond = ({ nodeData, ...rest }: { nodeData: Directive }) => { 10 | const argument = getNestedValue(['argument', 0, 'value'], nodeData); 11 | if (VALID_COND_ARGS.includes(argument)) { 12 | return nodeData.children.map((child, index) => ); 13 | } 14 | return null; 15 | }; 16 | 17 | export default Cond; 18 | -------------------------------------------------------------------------------- /src/components/ConditionalWrapper.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | const ConditionalWrapper = ({ condition, wrapper, children }) => (condition ? wrapper(children) : children); 4 | 5 | ConditionalWrapper.propTypes = { 6 | children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired, 7 | condition: PropTypes.bool.isRequired, 8 | wrapper: PropTypes.func.isRequired, 9 | }; 10 | 11 | export default ConditionalWrapper; 12 | -------------------------------------------------------------------------------- /src/components/Container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ComponentFactory from './ComponentFactory'; 4 | 5 | const Container = ({ nodeData: { argument, children }, ...rest }) => { 6 | const customClass = argument.map((node) => node.value).join(' '); 7 | return ( 8 |
9 | {children.map((element, index) => ( 10 | 11 | ))} 12 |
13 | ); 14 | }; 15 | 16 | Container.propTypes = { 17 | nodeData: PropTypes.shape({ 18 | argument: PropTypes.arrayOf( 19 | PropTypes.shape({ 20 | value: PropTypes.string, 21 | }) 22 | ), 23 | children: PropTypes.arrayOf(PropTypes.object), 24 | }).isRequired, 25 | }; 26 | 27 | export default Container; 28 | -------------------------------------------------------------------------------- /src/components/DefinitionList/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ComponentFactory from '../ComponentFactory'; 4 | 5 | const DefinitionList = ({ nodeData, ...rest }) => { 6 | return ( 7 |
8 | {nodeData.children.map((definition, index) => ( 9 | 10 | ))} 11 |
12 | ); 13 | }; 14 | 15 | DefinitionList.propTypes = { 16 | nodeData: PropTypes.shape({ 17 | children: PropTypes.array.isRequired, 18 | }).isRequired, 19 | }; 20 | 21 | export default DefinitionList; 22 | -------------------------------------------------------------------------------- /src/components/Describe.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { css } from '@emotion/react'; 3 | import { Directive } from '../types/ast'; 4 | import ComponentFactory from './ComponentFactory'; 5 | 6 | const Describe = ({ nodeData: { argument, children }, ...rest }: { nodeData: Directive }) => ( 7 |
8 |
9 | 16 | {argument.map((arg, i) => ( 17 | 18 | ))} 19 | 20 |
21 |
22 | {children.map((child, i) => ( 23 | 24 | ))} 25 |
26 |
27 | ); 28 | 29 | export default Describe; 30 | -------------------------------------------------------------------------------- /src/components/Emphasis.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { getNestedValue } from '../utils/get-nested-value'; 3 | import { EmphasisNode } from '../types/ast'; 4 | 5 | const Emphasis = ({ nodeData }: { nodeData: EmphasisNode }) => ( 6 | {getNestedValue(['children', 0, 'value'], nodeData)} 7 | ); 8 | 9 | export default Emphasis; 10 | -------------------------------------------------------------------------------- /src/components/Extract.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ParentNode } from '../types/ast'; 3 | import ComponentFactory from './ComponentFactory'; 4 | 5 | const Extract = ({ nodeData: { children }, ...rest }: { nodeData: ParentNode }) => 6 | children.map((child, i) => ); 7 | 8 | export default Extract; 9 | -------------------------------------------------------------------------------- /src/components/FieldList/Field.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { css } from '@emotion/react'; 4 | import ComponentFactory from '../ComponentFactory'; 5 | import { theme } from '../../theme/docsTheme'; 6 | 7 | const Field = ({ nodeData: { children, label, name }, ...rest }) => ( 8 | th, 12 | > td { 13 | padding: 11px 5px 12px; 14 | text-align: left; 15 | } 16 | `} 17 | > 18 | {label || name}: 19 | 20 | {children.map((element, index) => ( 21 | 22 | ))} 23 | 24 | 25 | ); 26 | 27 | Field.propTypes = { 28 | nodeData: PropTypes.shape({ 29 | children: PropTypes.arrayOf(PropTypes.object).isRequired, 30 | label: PropTypes.string, 31 | name: PropTypes.string.isRequired, 32 | }).isRequired, 33 | }; 34 | 35 | export default Field; 36 | -------------------------------------------------------------------------------- /src/components/FieldList/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from '@emotion/styled'; 4 | import ComponentFactory from '../ComponentFactory'; 5 | 6 | const Table = styled('table')` 7 | border-spacing: 0; 8 | font-size: ${({ theme }) => `${theme.fontSize.small}`}; 9 | line-height: ${({ theme }) => `${theme.size.medium}`}; 10 | margin: ${({ theme }) => `${theme.size.medium} 0`}; 11 | max-width: 100%; 12 | 13 | tbody { 14 | vertical-align: top; 15 | } 16 | `; 17 | 18 | const FieldList = ({ nodeData: { children }, ...rest }) => ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | {children.map((element, index) => ( 26 | 27 | ))} 28 | 29 |
30 | ); 31 | 32 | FieldList.propTypes = { 33 | nodeData: PropTypes.shape({ 34 | children: PropTypes.arrayOf(PropTypes.object).isRequired, 35 | }).isRequired, 36 | }; 37 | 38 | export default FieldList; 39 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UnifiedFooter } from '@mdb/consistent-nav'; 3 | import { getAvailableLanguages, getCurrLocale, onSelectLocale } from '../utils/locale'; 4 | 5 | const Footer = () => { 6 | const enabledLocales = getAvailableLanguages().map((language) => language.localeCode); 7 | return ; 8 | }; 9 | 10 | export default Footer; 11 | -------------------------------------------------------------------------------- /src/components/Footnote/footnote-context.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | const FootnoteContext = createContext({ 4 | footnotes: {}, 5 | }); 6 | 7 | export default FootnoteContext; 8 | -------------------------------------------------------------------------------- /src/components/Glossary.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ParentNode } from '../types/ast'; 3 | import ComponentFactory from './ComponentFactory'; 4 | 5 | const Glossary = ({ nodeData: { children }, ...rest }: { nodeData: ParentNode }) => ( 6 | <> 7 | {children.map((node, index) => ( 8 | 9 | ))} 10 | 11 | ); 12 | 13 | export default Glossary; 14 | -------------------------------------------------------------------------------- /src/components/GuideNext/GuidesList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from '@emotion/styled'; 4 | import { getPlaintext } from '../../utils/get-plaintext'; 5 | import GuidesListItem from './GuidesListItem'; 6 | 7 | const List = styled('ul')` 8 | list-style: none; 9 | padding-left: 0; 10 | `; 11 | 12 | const GuidesList = ({ guidesMetadata, guideSlugs, targetSlug }) => { 13 | return ( 14 | 15 | {guideSlugs.map((slug) => { 16 | const title = getPlaintext(guidesMetadata[slug].title); 17 | return ( 18 | 19 | {title} 20 | 21 | ); 22 | })} 23 | 24 | ); 25 | }; 26 | 27 | GuidesList.propTypes = { 28 | guidesMetadata: PropTypes.object.isRequired, 29 | guideSlugs: PropTypes.array.isRequired, 30 | targetSlug: PropTypes.string, 31 | }; 32 | 33 | export default GuidesList; 34 | -------------------------------------------------------------------------------- /src/components/GuideNext/read-guides-context.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState, useEffect } from 'react'; 2 | import { getLocalValue, setLocalValue } from '../../utils/browser-storage'; 3 | 4 | const ReadGuidesContext = createContext({ 5 | readGuides: {}, 6 | }); 7 | 8 | const ReadGuidesContextProvider = ({ children, slug }) => { 9 | const localStorageKey = 'readGuides'; 10 | const [readGuides, setReadGuides] = useState({}); 11 | 12 | useEffect(() => { 13 | setReadGuides(getLocalValue(localStorageKey)); 14 | }, []); 15 | 16 | useEffect(() => { 17 | if (!readGuides[slug]) { 18 | setReadGuides((prevState) => ({ 19 | ...prevState, 20 | [slug]: true, 21 | })); 22 | } 23 | }, [readGuides, setReadGuides, slug]); 24 | 25 | useEffect(() => { 26 | setLocalValue(localStorageKey, readGuides); 27 | }, [localStorageKey, readGuides]); 28 | 29 | return {children}; 30 | }; 31 | 32 | export { ReadGuidesContext, ReadGuidesContextProvider }; 33 | -------------------------------------------------------------------------------- /src/components/Include.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ParentNode } from '../types/ast'; 3 | import ComponentFactory from './ComponentFactory'; 4 | 5 | const Include = ({ nodeData: { children }, ...rest }: { nodeData: ParentNode }) => 6 | children.map((child, i) => ); 7 | 8 | export default Include; 9 | -------------------------------------------------------------------------------- /src/components/Instruqt/index.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; 3 | import LabDrawer from './LabDrawer'; 4 | import InstruqtFrame from './InstruqtFrame'; 5 | import { InstruqtContext } from './instruqt-context'; 6 | 7 | const Instruqt = ({ nodeData }) => { 8 | const embedValue = nodeData?.argument[0]?.value; 9 | const title = nodeData?.options?.title; 10 | const isDrawer = nodeData?.options?.drawer; 11 | const { isOpen } = useContext(InstruqtContext); 12 | const { darkMode } = useDarkMode(); 13 | 14 | if (!embedValue) { 15 | return null; 16 | } 17 | 18 | return ( 19 | <> 20 | {isDrawer ? ( 21 | isOpen && ( 22 | <> 23 | 24 | 25 | ) 26 | ) : ( 27 | 28 | )} 29 | 30 | ); 31 | }; 32 | 33 | export default Instruqt; 34 | -------------------------------------------------------------------------------- /src/components/Instruqt/instruqt-context.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | const defaultContextValue = { 4 | hasDrawer: false, 5 | isOpen: false, 6 | setIsOpen: () => {}, 7 | }; 8 | 9 | const InstruqtContext = React.createContext(defaultContextValue); 10 | 11 | const InstruqtProvider = ({ children, hasLabDrawer }) => { 12 | const hasDrawer = hasLabDrawer; 13 | const [isOpen, setIsOpen] = useState(false); 14 | 15 | return {children}; 16 | }; 17 | 18 | InstruqtProvider.defaultProps = { 19 | hasLabDrawer: false, 20 | }; 21 | 22 | export { InstruqtContext, InstruqtProvider }; 23 | -------------------------------------------------------------------------------- /src/components/Internal/Overline.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { css, cx } from '@leafygreen-ui/emotion'; 4 | import { Overline as LGOverline } from '@leafygreen-ui/typography'; 5 | 6 | const overlineBaseStyling = css` 7 | margin-top: 48px; 8 | margin-bottom: 0px; 9 | color: var(--font-color-light); 10 | `; 11 | 12 | const Overline = ({ className, children }) => { 13 | return {children}; 14 | }; 15 | 16 | Overline.propTypes = { 17 | className: PropTypes.string, 18 | children: PropTypes.node, 19 | }; 20 | 21 | export default Overline; 22 | -------------------------------------------------------------------------------- /src/components/InternalPageNav/index.js: -------------------------------------------------------------------------------- 1 | export { default as InternalPageNav } from './InternalPageNav'; 2 | export { default as NextPrevLink } from './NextPrevLink'; 3 | export { navLinkButtonStyle } from './styles'; 4 | -------------------------------------------------------------------------------- /src/components/InternalPageNav/styles.js: -------------------------------------------------------------------------------- 1 | import { css } from '@leafygreen-ui/emotion'; 2 | import { palette } from '@leafygreen-ui/palette'; 3 | 4 | export const navLinkButtonStyle = css` 5 | background-color: ${palette.gray.light3}; 6 | color: ${palette.black}; 7 | 8 | .dark-theme & { 9 | background-color: ${palette.gray.dark2}; 10 | color: ${palette.white}; 11 | } 12 | `; 13 | -------------------------------------------------------------------------------- /src/components/Kicker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { css } from '@leafygreen-ui/emotion'; 3 | import { theme } from '../theme/docsTheme'; 4 | import { Directive } from '../types/ast'; 5 | import ComponentFactory from './ComponentFactory'; 6 | import Overline from './Internal/Overline'; 7 | 8 | const kickerBaseStyle = css` 9 | grid-column: 2; 10 | @media ${theme.screenSize.upToSmall} { 11 | padding-top: 56px; 12 | } 13 | @media ${theme.screenSize.upToXSmall} { 14 | padding-top: ${theme.size.large}; 15 | } 16 | `; 17 | 18 | const Kicker = ({ nodeData: { argument }, ...rest }: { nodeData: Directive }) => { 19 | return ( 20 | 21 | {argument.map((child, i) => ( 22 | 23 | ))} 24 | 25 | ); 26 | }; 27 | 28 | export default Kicker; 29 | -------------------------------------------------------------------------------- /src/components/LineBlock/Line.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ComponentFactory from '../ComponentFactory'; 4 | 5 | const Line = ({ nodeData: { children }, ...rest }) => { 6 | if (children.length !== 0) { 7 | return ( 8 |
9 | {children.map((child, index) => ( 10 | 11 | ))} 12 |
13 | ); 14 | } 15 | return ( 16 |
17 |
18 |
19 | ); 20 | }; 21 | 22 | Line.propTypes = { 23 | nodeData: PropTypes.shape({ 24 | children: PropTypes.arrayOf(PropTypes.object).isRequired, 25 | }).isRequired, 26 | }; 27 | export default Line; 28 | -------------------------------------------------------------------------------- /src/components/LineBlock/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ComponentFactory from '../ComponentFactory'; 4 | 5 | const LineBlock = ({ nodeData: { children }, ...rest }) => ( 6 |
7 | {children.map((child, index) => ( 8 | 9 | ))} 10 |
11 | ); 12 | 13 | LineBlock.propTypes = { 14 | nodeData: PropTypes.shape({ 15 | children: PropTypes.arrayOf(PropTypes.object).isRequired, 16 | }).isRequired, 17 | }; 18 | 19 | export default LineBlock; 20 | -------------------------------------------------------------------------------- /src/components/List/ListItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { cx, css } from '@leafygreen-ui/emotion'; 4 | import ComponentFactory from '../ComponentFactory'; 5 | 6 | const listParagraphStyles = css` 7 | ::marker { 8 | color: var(--font-color-primary); 9 | } 10 | & > p { 11 | margin-bottom: 8px; 12 | } 13 | `; 14 | const ListItem = ({ nodeData, ...rest }) => ( 15 |
  • 16 | {nodeData.children.map((child, index) => ( 17 | 18 | ))} 19 |
  • 20 | ); 21 | 22 | ListItem.propTypes = { 23 | nodeData: PropTypes.shape({ 24 | children: PropTypes.array.isRequired, 25 | }).isRequired, 26 | }; 27 | 28 | export default ListItem; 29 | -------------------------------------------------------------------------------- /src/components/LiteralBlock.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ParentNode } from '../types/ast'; 3 | import ComponentFactory from './ComponentFactory'; 4 | 5 | const LiteralBlock = ({ nodeData: { children }, ...rest }: { nodeData: ParentNode }) => ( 6 |
    7 |
    8 |
     9 |         {children.map((child, index) => (
    10 |           
    11 |         ))}
    12 |       
    13 |
    14 |
    15 | ); 16 | 17 | export default LiteralBlock; 18 | -------------------------------------------------------------------------------- /src/components/LiteralInclude.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ParentNode } from '../types/ast'; 3 | import ComponentFactory from './ComponentFactory'; 4 | 5 | const LiteralInclude = ({ nodeData: { children }, ...rest }: { nodeData: ParentNode }) => { 6 | return children.map((child, i) => ); 7 | }; 8 | 9 | export default LiteralInclude; 10 | -------------------------------------------------------------------------------- /src/components/MainColumn.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | import { cx, css } from '@leafygreen-ui/emotion'; 3 | import { theme } from '../theme/docsTheme'; 4 | 5 | export const MAIN_COLUMN_HORIZONTAL_MARGIN = theme.size.xlarge; 6 | 7 | const MainColumn = ({ children, className }: { children: ReactNode; className?: string }) => ( 8 |
    26 | {children} 27 |
    28 | ); 29 | 30 | export default MainColumn; 31 | -------------------------------------------------------------------------------- /src/components/Meta.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DirectiveOptions, MetaNode } from '../types/ast'; 3 | 4 | export type MetaProps = { 5 | nodeData: MetaNode; 6 | }; 7 | 8 | const getFilteredOptions = (options?: DirectiveOptions): [string, string][] => { 9 | if (!options) { 10 | return []; 11 | } 12 | 13 | const skipList = new Set(['canonical']); 14 | return Object.entries(options).filter(([key]) => !skipList.has(key)); 15 | }; 16 | 17 | const Meta = ({ nodeData: { options } }: MetaProps) => { 18 | const filteredOptions = getFilteredOptions(options); 19 | if (!filteredOptions.length) { 20 | return null; 21 | } 22 | 23 | return ( 24 | <> 25 | {filteredOptions.map(([key, value]) => ( 26 | 27 | ))} 28 | 29 | ); 30 | }; 31 | 32 | export default Meta; 33 | -------------------------------------------------------------------------------- /src/components/MethodSelector/MethodDescription.js: -------------------------------------------------------------------------------- 1 | import { css, cx } from '@leafygreen-ui/emotion'; 2 | import React from 'react'; 3 | import { theme } from '../../theme/docsTheme'; 4 | import ComponentFactory from '../ComponentFactory'; 5 | import TabSelectors from '../Tabs/TabSelectors'; 6 | 7 | const containerStyle = css` 8 | font-size: ${theme.fontSize.small}; 9 | margin-bottom: ${theme.size.large}; 10 | 11 | * { 12 | font-size: ${theme.fontSize.small} !important; 13 | } 14 | `; 15 | 16 | const tabSelectorStyle = css` 17 | margin-top: ${theme.size.large}; 18 | 19 | @media ${theme.screenSize.smallAndUp} { 20 | max-width: 400px; 21 | } 22 | `; 23 | 24 | const MethodDescription = ({ nodeData: { children } }) => { 25 | return ( 26 |
    27 | {children.map((child, index) => { 28 | if (child.name === 'tabs-selector') { 29 | return ; 30 | } 31 | 32 | return ; 33 | })} 34 |
    35 | ); 36 | }; 37 | 38 | export default MethodDescription; 39 | -------------------------------------------------------------------------------- /src/components/MethodSelector/index.tsx: -------------------------------------------------------------------------------- 1 | import MethodSelector from './MethodSelector'; 2 | 3 | export { MethodSelector }; 4 | -------------------------------------------------------------------------------- /src/components/MultiPageTutorials/StepNumber.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { css } from '@leafygreen-ui/emotion'; 4 | import { theme } from '../../theme/docsTheme'; 5 | import Overline from '../Internal/Overline'; 6 | import { MPTNextLinkFull } from './MPTNextLinkFull'; 7 | 8 | const overlineStyle = css` 9 | font-weight: 600; 10 | line-height: ${theme.size.default}; 11 | padding-top: 0 !important; 12 | `; 13 | 14 | export const StepNumber = ({ slug, activeTutorial }) => { 15 | const totalSteps = activeTutorial.total_steps; 16 | const currentStep = activeTutorial.slugs.indexOf(slug) + 1; 17 | 18 | return ( 19 | <> 20 | 21 | Step {currentStep} of {totalSteps} 22 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | StepNumber.propTypes = { 29 | slug: PropTypes.string.isRequired, 30 | activeTutorial: PropTypes.shape({ 31 | parent: PropTypes.string, 32 | total_steps: PropTypes.number, 33 | slugs: PropTypes.arrayOf(PropTypes.string), 34 | }), 35 | }; 36 | -------------------------------------------------------------------------------- /src/components/MultiPageTutorials/TimeRequired.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { css } from '@leafygreen-ui/emotion'; 3 | import Overline from '../Internal/Overline'; 4 | import { theme } from '../../theme/docsTheme'; 5 | import { MPTNextLinkMini } from './MPTNextLinkMini'; 6 | import { useMptPageOptions } from './hooks/use-mpt-page-options'; 7 | import { OPTION_KEY_TIME_REQUIRED } from './constants'; 8 | 9 | const timeBaseStyle = css` 10 | font-weight: 600; 11 | line-height: ${theme.size.default}; 12 | margin-top: 40px; 13 | margin-bottom: ${theme.size.default}; 14 | `; 15 | 16 | export const TimeRequired = () => { 17 | const options = useMptPageOptions(); 18 | const time = options?.[OPTION_KEY_TIME_REQUIRED]; 19 | 20 | if (!time) { 21 | return null; 22 | } 23 | 24 | return ( 25 | <> 26 | Read time {time} min 27 | {/* Keeping at the bottom so that it can be more easily aligned regardless of what's above the Time component*/} 28 | 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/components/MultiPageTutorials/constants.ts: -------------------------------------------------------------------------------- 1 | export const PAGE_OPTION_NAME = 'multi_page_tutorial_settings'; 2 | export const OPTION_KEY_SHOW_NEXT_TOP = 'show_next_top'; 3 | export const OPTION_KEY_TIME_REQUIRED = 'time_required'; 4 | export const LINK_TITLE = 'Next Step'; 5 | -------------------------------------------------------------------------------- /src/components/MultiPageTutorials/hooks/use-mpt-page-options.js: -------------------------------------------------------------------------------- 1 | import { usePageContext } from '../../../context/page-context'; 2 | import { PAGE_OPTION_NAME } from '../constants'; 3 | 4 | export const useMptPageOptions = () => { 5 | const { options } = usePageContext(); 6 | if (!options || Object.keys(options).length === 0) { 7 | return; 8 | } 9 | return options[PAGE_OPTION_NAME]; 10 | }; 11 | -------------------------------------------------------------------------------- /src/components/MultiPageTutorials/hooks/use-should-show-next.js: -------------------------------------------------------------------------------- 1 | import { OPTION_KEY_SHOW_NEXT_TOP } from '../constants'; 2 | import { useActiveMpTutorial } from './use-active-mp-tutorial'; 3 | import { useMptPageOptions } from './use-mpt-page-options'; 4 | 5 | export const useShouldShowNext = () => { 6 | const activeTutorial = useActiveMpTutorial(); 7 | const options = useMptPageOptions(); 8 | const hasNextTutorial = activeTutorial && !!activeTutorial.next; 9 | const hasPageOption = !!options?.[OPTION_KEY_SHOW_NEXT_TOP]; 10 | return hasPageOption && hasNextTutorial; 11 | }; 12 | -------------------------------------------------------------------------------- /src/components/MultiPageTutorials/index.ts: -------------------------------------------------------------------------------- 1 | export { StepNumber } from './StepNumber'; 2 | export { TimeRequired } from './TimeRequired'; 3 | export { useActiveMpTutorial } from './hooks/use-active-mp-tutorial'; 4 | -------------------------------------------------------------------------------- /src/components/MultiPageTutorials/utils.ts: -------------------------------------------------------------------------------- 1 | import { reportAnalytics } from '../../utils/report-analytics'; 2 | 3 | export const reportMPTAnalytics = (targetSlug: string, variant: string) => { 4 | reportAnalytics('MultiPageTutorialNextClicked', { 5 | targetSlug, 6 | variant, 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /src/components/OfflineDownloadModal/index.tsx: -------------------------------------------------------------------------------- 1 | import DownloadButton from './DownloadButton'; 2 | 3 | export { DownloadButton }; 4 | -------------------------------------------------------------------------------- /src/components/Only.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { getNestedValue } from '../utils/get-nested-value'; 3 | import { ParentNode } from '../types/ast'; 4 | import ComponentFactory from './ComponentFactory'; 5 | 6 | // For now, explicitly define the arguments that should be accepted for Gatsby to build the node 7 | const VALID_ONLY_ARGS = ['html', '(not man)']; 8 | 9 | const Only = ({ nodeData, ...rest }: { nodeData: ParentNode }) => { 10 | const argument = getNestedValue(['argument', 0, 'value'], nodeData); 11 | if (VALID_ONLY_ARGS.includes(argument)) { 12 | return nodeData.children.map((child, index) => ); 13 | } 14 | return null; 15 | }; 16 | 17 | export default Only; 18 | -------------------------------------------------------------------------------- /src/components/OpenAPI/whitelist.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Callout from '@leafygreen-ui/callout'; 3 | import type { CalloutProps } from '@leafygreen-ui/callout'; 4 | 5 | const HOST_WHITELIST = [ 6 | 'mongodb-mms-build-server.s3.amazonaws.com', 7 | 'raw.githubusercontent.com', 8 | 'mciuploads.s3.amazonaws.com', 9 | ]; 10 | export const isLinkInWhitelist = (link: string) => HOST_WHITELIST.includes(new URL(link).hostname); 11 | 12 | export const WhitelistErrorCallout = (props: CalloutProps) => { 13 | return ( 14 | 15 | The link you're trying to preview isn't currently permitted - if this is a mistake, please raise a pull request to 16 | add this source{' '} 17 | here for our 18 | consideration. 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/OpenAPIChangelog/components/ChangeList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { COMPARE_VERSIONS } from '../utils/constants'; 4 | 5 | import ReleaseDateBlock from './ReleaseDateBlock'; 6 | import ResourceChangesBlock from './ResourceChangesBlock'; 7 | 8 | const Wrapper = styled.div` 9 | width: 100%; 10 | margin-top: 32px; 11 | `; 12 | 13 | const ChangeList = ({ versionMode, changes }) => { 14 | const ChangeListComponent = versionMode === COMPARE_VERSIONS ? ResourceChangesBlock : ReleaseDateBlock; 15 | 16 | return ( 17 | 18 | {changes.map((data, i) => ( 19 | 20 | ))} 21 | 22 | ); 23 | }; 24 | 25 | export default ChangeList; 26 | -------------------------------------------------------------------------------- /src/components/OpenAPIChangelog/components/FiltersPanel/index.js: -------------------------------------------------------------------------------- 1 | import FiltersPanel from './components/FiltersPanel'; 2 | 3 | export default FiltersPanel; 4 | -------------------------------------------------------------------------------- /src/components/OpenAPIChangelog/components/ReleaseDateBlock.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { H3 } from '@leafygreen-ui/typography'; 4 | import { format } from 'date-fns'; 5 | import ResourceChangesBlock from './ResourceChangesBlock'; 6 | 7 | const Wrapper = styled.section` 8 | width: 100%; 9 | margin-top: 28px; 10 | `; 11 | 12 | const ReleaseDateBlock = ({ date: releaseDate, paths }) => { 13 | const formattedReleaseDate = format(new Date(releaseDate.replace(/-/g, '/')), 'dd MMMM y'); 14 | 15 | return ( 16 | 17 |

    {`${formattedReleaseDate} Release`}

    18 | {paths.map((path, i) => ( 19 | 20 | ))} 21 |
    22 | ); 23 | }; 24 | 25 | export default ReleaseDateBlock; 26 | -------------------------------------------------------------------------------- /src/components/OpenAPIChangelog/index.js: -------------------------------------------------------------------------------- 1 | import OpenAPIChangelog from './OpenAPIChangelog'; 2 | 3 | export default OpenAPIChangelog; 4 | -------------------------------------------------------------------------------- /src/components/OpenAPIChangelog/utils/constants.js: -------------------------------------------------------------------------------- 1 | import { Variant } from '@leafygreen-ui/badge'; 2 | 3 | export const ALL_VERSIONS = 'ALL_VERSIONS'; 4 | export const COMPARE_VERSIONS = 'COMPARE_VERSIONS'; 5 | 6 | export const getDownloadChangelogUrl = (snootyEnv) => { 7 | const branch = snootyEnv === 'staging' || snootyEnv === 'development' ? 'qa' : 'main'; 8 | return `https://raw.githubusercontent.com/mongodb/openapi/${branch}/changelog/changelog.json`; 9 | }; 10 | 11 | export const changeTypeBadges = { 12 | release: { 13 | variant: Variant.Green, 14 | label: 'Released', 15 | }, 16 | deprecate: { 17 | variant: Variant.Red, 18 | label: 'Deprecated', 19 | }, 20 | update: { 21 | variant: Variant.Green, 22 | label: 'Updated', 23 | }, 24 | removed: { 25 | variant: Variant.Red, 26 | label: 'Removed', 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/OpenAPIChangelog/utils/getDiffRequestFormat.js: -------------------------------------------------------------------------------- 1 | import { isAfter } from 'date-fns'; 2 | 3 | export const getDiffRequestFormat = (resourceVersionOne, resourceVersionTwo) => { 4 | const resourceVersionChoices = [resourceVersionOne, resourceVersionTwo].sort((a, b) => 5 | isAfter(new Date(b.slice(0, 10).split('-').join('/')), new Date(a.slice(0, 10).split('-').join('/'))) ? -1 : 1 6 | ); 7 | return resourceVersionChoices.join('_'); 8 | }; 9 | -------------------------------------------------------------------------------- /src/components/OpenAPIChangelog/utils/getDiffResourcesList.js: -------------------------------------------------------------------------------- 1 | const getDiffResourcesList = (diff) => { 2 | const resourcesList = new Set(); 3 | diff.forEach(({ httpMethod, path }) => resourcesList.add(`${httpMethod} ${path}`)); 4 | 5 | return Array.from(resourcesList); 6 | }; 7 | 8 | export default getDiffResourcesList; 9 | -------------------------------------------------------------------------------- /src/components/OpenAPIChangelog/utils/getResourceLinkUrl.js: -------------------------------------------------------------------------------- 1 | import { generatePathPrefix } from '../../../utils/generate-path-prefix'; 2 | import { normalizePath } from '../../../utils/normalize-path'; 3 | 4 | const getResourceLinkUrl = (metadata, tag, operationId, openapi_pages = {}) => { 5 | const pathPrefix = generatePathPrefix(metadata); 6 | const resourceTag = `#tag/${tag.split(' ').join('-')}/operation/${operationId}`; 7 | const oaSpecPageRoute = 8 | Object.keys(openapi_pages).find((page) => page.includes('v2')) || 9 | Object.keys(openapi_pages).find((page) => !page.includes('v1')) || 10 | 'reference/api-resources-spec/v2'; 11 | 12 | return `${pathPrefix}${normalizePath(`/${oaSpecPageRoute}/${resourceTag}`)}`; 13 | }; 14 | 15 | export default getResourceLinkUrl; 16 | -------------------------------------------------------------------------------- /src/components/Products/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import ComponentFactory from '../ComponentFactory'; 4 | import { ParentNode } from '../../types/ast'; 5 | 6 | const StyledSectionContainer = styled.section` 7 | display: grid; 8 | grid-template-columns: minmax(64px, 1fr) repeat(12, minmax(0, 120px)) minmax(64px, 1fr); 9 | margin-bottom: 56px; 10 | margin-top: 60px; 11 | `; 12 | 13 | const Products = ({ nodeData: { children } }: { nodeData: ParentNode }) => { 14 | return ( 15 | 16 | {children.map((child, i) => ( 17 | 18 | ))} 19 | 20 | ); 21 | }; 22 | 23 | export default Products; 24 | -------------------------------------------------------------------------------- /src/components/Reference.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ComponentFactory from './ComponentFactory'; 4 | import Link from './Link'; 5 | 6 | const Reference = ({ nodeData, ...rest }) => { 7 | return ( 8 | 9 | {nodeData.children.map((element, index) => ( 10 | 11 | ))} 12 | 13 | ); 14 | }; 15 | 16 | Reference.propTypes = { 17 | nodeData: PropTypes.shape({ 18 | children: PropTypes.array.isRequired, 19 | refuri: PropTypes.string.isRequired, 20 | }).isRequired, 21 | }; 22 | 23 | export default Reference; 24 | -------------------------------------------------------------------------------- /src/components/ReleaseSpecification.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ComponentFactory from './ComponentFactory'; 4 | 5 | const ReleaseSpecification = ({ nodeData: { children }, ...rest }) => { 6 | return children.map((child, i) => ); 7 | }; 8 | 9 | ReleaseSpecification.propTypes = { 10 | nodeData: PropTypes.shape({ 11 | children: PropTypes.arrayOf(PropTypes.object).isRequired, 12 | }).isRequired, 13 | }; 14 | 15 | export default ReleaseSpecification; 16 | -------------------------------------------------------------------------------- /src/components/Roles/Abbr.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import InlineDefinition from '@leafygreen-ui/inline-definition'; 4 | import { theme } from '../../theme/docsTheme'; 5 | 6 | const Abbr = ({ 7 | nodeData: { 8 | children: [{ value }], 9 | }, 10 | }) => { 11 | if (!value) { 12 | return null; 13 | } 14 | 15 | // Abbreviations are written as as "ABBR (Full Name Here)", so separate this into `abbr` and `expansion` 16 | let [abbr, expansion] = value.split('('); 17 | if (expansion) { 18 | expansion = expansion.split(')')[0]; 19 | abbr = abbr.trim(); 20 | } 21 | return ( 22 | 23 | {abbr} 24 | 25 | ); 26 | }; 27 | 28 | Abbr.propTypes = { 29 | nodeData: PropTypes.shape({ 30 | children: PropTypes.arrayOf( 31 | PropTypes.shape({ 32 | value: PropTypes.string.isRequired, 33 | }) 34 | ).isRequired, 35 | }).isRequired, 36 | }; 37 | 38 | export default Abbr; 39 | -------------------------------------------------------------------------------- /src/components/Roles/Class.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ComponentFactory from '../ComponentFactory'; 4 | 5 | const RoleClass = ({ nodeData: { children, target } }) => ( 6 | 7 | {children.map((node, i) => ( 8 | 9 | ))} 10 | 11 | ); 12 | 13 | RoleClass.propTypes = { 14 | nodeData: PropTypes.shape({ 15 | children: PropTypes.arrayOf(PropTypes.node).isRequired, 16 | }).isRequired, 17 | }; 18 | 19 | export default RoleClass; 20 | -------------------------------------------------------------------------------- /src/components/Roles/Command.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ComponentFactory from '../ComponentFactory'; 4 | 5 | const RoleCommand = ({ nodeData: { children }, ...rest }) => ( 6 | 7 | {children.map((child, i) => ( 8 | 9 | ))} 10 | 11 | ); 12 | 13 | RoleCommand.propTypes = { 14 | nodeData: PropTypes.shape({ 15 | children: PropTypes.arrayOf(PropTypes.object).isRequired, 16 | }).isRequired, 17 | }; 18 | 19 | export default RoleCommand; 20 | -------------------------------------------------------------------------------- /src/components/Roles/File.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ComponentFactory from '../ComponentFactory'; 4 | 5 | const RoleFile = ({ nodeData: { children } }) => ( 6 | 7 | 8 | {children.map((node, i) => ( 9 | 10 | ))} 11 | 12 | 13 | ); 14 | 15 | RoleFile.propTypes = { 16 | nodeData: PropTypes.shape({ 17 | children: PropTypes.arrayOf(PropTypes.node).isRequired, 18 | }).isRequired, 19 | }; 20 | 21 | export default RoleFile; 22 | -------------------------------------------------------------------------------- /src/components/Roles/GUILabel.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { css } from '@emotion/react'; 4 | import ComponentFactory from '../ComponentFactory'; 5 | 6 | const guiLabelStyle = css` 7 | font-style: normal; 8 | font-weight: 700; 9 | `; 10 | 11 | const RoleGUILabel = ({ nodeData: { children } }) => ( 12 | // Keep "guilabel" className for styling when this component is inside of a Heading. 13 | 14 | {children.map((node, i) => ( 15 | 16 | ))} 17 | 18 | ); 19 | 20 | RoleGUILabel.propTypes = { 21 | nodeData: PropTypes.shape({ 22 | children: PropTypes.array.isRequired, 23 | }).isRequired, 24 | }; 25 | 26 | export default RoleGUILabel; 27 | -------------------------------------------------------------------------------- /src/components/Roles/Gold.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { css } from '@emotion/react'; 4 | import { palette } from '@leafygreen-ui/palette'; 5 | import ComponentFactory from '../ComponentFactory'; 6 | 7 | const Gold = ({ nodeData: { children } }) => ( 8 | 17 | {children.map((node, i) => ( 18 | 19 | ))} 20 | 21 | ); 22 | 23 | Gold.propTypes = { 24 | nodeData: PropTypes.shape({ 25 | children: PropTypes.arrayOf(PropTypes.object).isRequired, 26 | }).isRequired, 27 | }; 28 | 29 | export default Gold; 30 | -------------------------------------------------------------------------------- /src/components/Roles/Kbd.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { css, cx } from '@leafygreen-ui/emotion'; 4 | import { InlineKeyCode } from '@leafygreen-ui/typography'; 5 | import ComponentFactory from '../ComponentFactory'; 6 | 7 | const darkModeOverwriteStyling = css` 8 | color: var(--font-color-primary); 9 | background-color: var(--background-color-primary); 10 | `; 11 | 12 | const Kbd = ({ nodeData: { children } }) => ( 13 | 14 | {children.map((node, i) => ( 15 | 16 | ))} 17 | 18 | ); 19 | 20 | Kbd.propTypes = { 21 | nodeData: PropTypes.shape({ 22 | children: PropTypes.arrayOf(PropTypes.object).isRequired, 23 | }).isRequired, 24 | }; 25 | 26 | export default Kbd; 27 | -------------------------------------------------------------------------------- /src/components/Roles/LinkNewTab.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ComponentFactory from '../ComponentFactory'; 4 | import Link from '../Link'; 5 | 6 | const LinkNewTab = ({ nodeData: { children, target } }) => ( 7 | 8 | {children.map((node, i) => ( 9 | 10 | ))} 11 | 12 | ); 13 | 14 | LinkNewTab.propTypes = { 15 | nodeData: PropTypes.shape({ 16 | children: PropTypes.arrayOf(PropTypes.object).isRequired, 17 | target: PropTypes.string.isRequired, 18 | }).isRequired, 19 | }; 20 | 21 | export default LinkNewTab; 22 | -------------------------------------------------------------------------------- /src/components/Roles/Manual.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ComponentFactory from '../ComponentFactory'; 4 | import { REF_TARGETS } from '../../constants'; 5 | 6 | const RoleManual = ({ nodeData: { children, target } }) => { 7 | return ( 8 | 9 | {children.map((node, i) => ( 10 | 11 | ))} 12 | 13 | ); 14 | }; 15 | 16 | RoleManual.propTypes = { 17 | nodeData: PropTypes.shape({ 18 | children: PropTypes.arrayOf(PropTypes.node).isRequired, 19 | }).isRequired, 20 | }; 21 | 22 | export default RoleManual; 23 | -------------------------------------------------------------------------------- /src/components/Roles/Red.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { css } from '@emotion/react'; 4 | import { palette } from '@leafygreen-ui/palette'; 5 | import ComponentFactory from '../ComponentFactory'; 6 | 7 | const Red = ({ nodeData: { children } }) => ( 8 | 17 | {children.map((node, i) => ( 18 | 19 | ))} 20 | 21 | ); 22 | 23 | Red.propTypes = { 24 | nodeData: PropTypes.shape({ 25 | children: PropTypes.arrayOf(PropTypes.object).isRequired, 26 | }).isRequired, 27 | }; 28 | 29 | export default Red; 30 | -------------------------------------------------------------------------------- /src/components/Roles/Required.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { palette } from '@leafygreen-ui/palette'; 4 | 5 | const Em = styled('em')` 6 | color: ${palette.red.base}; 7 | font-size: ${({ theme }) => `${theme.fontSize.default}`}; 8 | font-weight: normal !important; 9 | `; 10 | 11 | const RoleRequired = () => required; 12 | 13 | export default RoleRequired; 14 | -------------------------------------------------------------------------------- /src/components/Root.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Root as RootNode } from '../types/ast'; 3 | import ComponentFactory from './ComponentFactory'; 4 | 5 | const Root = ({ nodeData: { children }, ...rest }: { nodeData: RootNode }) => 6 | children.map((child, i) => ); 7 | 8 | export default Root; 9 | -------------------------------------------------------------------------------- /src/components/Rubric.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { css } from '@emotion/react'; 3 | import { Directive } from '../types/ast'; 4 | import ComponentFactory from './ComponentFactory'; 5 | 6 | const rubricStyle = css` 7 | font-weight: 700; 8 | `; 9 | 10 | const Rubric = ({ nodeData: { argument }, ...rest }: { nodeData: Directive }) => ( 11 | // @ts-ignore 12 |

    13 | {argument.map((node, i) => ( 14 | 15 | ))} 16 |

    17 | ); 18 | 19 | export default Rubric; 20 | -------------------------------------------------------------------------------- /src/components/SearchResults/Facets/Facets.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import FacetGroup from './FacetGroup'; 3 | 4 | const Facets = ({ facets }) => { 5 | return ( 6 |
    7 | {facets?.length > 0 && 8 | facets.map((facetOption) => { 9 | return ; 10 | })} 11 |
    12 | ); 13 | }; 14 | 15 | export default Facets; 16 | -------------------------------------------------------------------------------- /src/components/SearchResults/Facets/index.js: -------------------------------------------------------------------------------- 1 | import Facets from './Facets'; 2 | import FacetTags from './FacetTags'; 3 | 4 | export { Facets, FacetTags }; 5 | -------------------------------------------------------------------------------- /src/components/SearchResults/Facets/useFacets.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { assertTrailingSlash } from '../../../utils/assert-trailing-slash'; 3 | import { requestHeaders } from '../../../utils/search-facet-constants'; 4 | import { MARIAN_URL } from '../../../constants'; 5 | 6 | const useFacets = () => { 7 | const [facets, setFacets] = useState([]); 8 | 9 | // Fetch facets 10 | useEffect(() => { 11 | const fetchFacets = async () => { 12 | try { 13 | const result = await fetch(assertTrailingSlash(MARIAN_URL) + 'v2/status', requestHeaders); 14 | const jsonResult = await result.json(); 15 | setFacets(jsonResult); 16 | } catch (err) { 17 | console.error(`Failed to fetch facets: ${err}`); 18 | } 19 | }; 20 | fetchFacets(); 21 | }, []); 22 | 23 | return facets; 24 | }; 25 | 26 | export default useFacets; 27 | -------------------------------------------------------------------------------- /src/components/SearchResults/Facets/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {obj} facet - contains key and id properties as returned by /v2/status ie. 4 | * https://docs-search-transport.mongodb.com/v2/status 5 | * 6 | * @returns {str} blue | green 7 | */ 8 | export const getFacetTagVariant = (facet) => { 9 | if (facet.key === 'target_product') { 10 | return 'green'; 11 | } 12 | return 'blue'; 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/SearchResults/SearchWrapper.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SearchContextProvider } from './SearchContext'; 3 | import SearchResults from './SearchResults'; 4 | 5 | // Check for feature flag here to make it easier to pass down for testing purposes 6 | const SHOW_FACETS = process.env.GATSBY_FEATURE_FACETED_SEARCH === 'true'; 7 | 8 | // Wraps the main SearchResults component with a context provider to limit scope of data 9 | const SearchWrapper = () => { 10 | return ( 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default SearchWrapper; 18 | -------------------------------------------------------------------------------- /src/components/SearchResults/index.js: -------------------------------------------------------------------------------- 1 | import SearchWrapper from './SearchWrapper'; 2 | 3 | export default SearchWrapper; 4 | -------------------------------------------------------------------------------- /src/components/SectionHeader.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; 3 | import { palette } from '@leafygreen-ui/palette'; 4 | import { cx } from '@leafygreen-ui/emotion'; 5 | import { H3 } from '@leafygreen-ui/typography'; 6 | 7 | const SectionHeader = ({ children, customStyles }: { children: ReactNode; customStyles?: string }) => { 8 | const { darkMode } = useDarkMode(); 9 | return ( 10 |

    11 | {children} 12 |

    13 | ); 14 | }; 15 | 16 | export default SectionHeader; 17 | -------------------------------------------------------------------------------- /src/components/Sidenav/DarkModeToggle.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { css, cx } from '@leafygreen-ui/emotion'; 3 | import Icon from '@leafygreen-ui/icon'; 4 | import IconButton from '@leafygreen-ui/icon-button'; 5 | import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; 6 | import { DARK_THEME_CLASSNAME, DarkModeContext, LIGHT_THEME_CLASSNAME } from '../../context/dark-mode-context'; 7 | import { theme } from '../../theme/docsTheme'; 8 | 9 | const toggleStyle = css` 10 | margin: 0 ${theme.size.default}; 11 | `; 12 | 13 | const DarkModeToggle = () => { 14 | const { setDarkModePref } = useContext(DarkModeContext); 15 | const { darkMode } = useDarkMode(); 16 | 17 | const onToggle = () => { 18 | setDarkModePref(darkMode ? LIGHT_THEME_CLASSNAME : DARK_THEME_CLASSNAME); 19 | }; 20 | 21 | return ( 22 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | export default DarkModeToggle; 29 | -------------------------------------------------------------------------------- /src/components/Sidenav/SidenavDocsLogo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from '@emotion/styled'; 4 | import { baseUrl } from '../../utils/base-url'; 5 | import { theme } from '../../theme/docsTheme'; 6 | 7 | import DocsLogo from '../SVGs/DocsLogo'; 8 | import Link from '../Link'; 9 | 10 | const PaddedDocsLogo = styled(DocsLogo)` 11 | margin: 0px ${theme.size.medium}; 12 | `; 13 | 14 | const SidenavDocsLogo = ({ border, ...props }) => { 15 | return ( 16 | <> 17 | 18 | 19 | 20 | {border} 21 | 22 | ); 23 | }; 24 | 25 | SidenavDocsLogo.propTypes = { 26 | border: PropTypes.element, 27 | }; 28 | 29 | export default SidenavDocsLogo; 30 | -------------------------------------------------------------------------------- /src/components/Sidenav/Toctree.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import TOCNode from './TOCNode'; 4 | 5 | const Toctree = ({ handleClick, slug, toctree: { children } }) => { 6 | return ( 7 | <> 8 | {children.map((c, idx) => ( 9 | 10 | ))} 11 | 12 | ); 13 | }; 14 | 15 | Toctree.propTypes = { 16 | toctree: PropTypes.shape({ 17 | children: PropTypes.arrayOf( 18 | PropTypes.shape({ 19 | title: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.string]).isRequired, 20 | slug: PropTypes.string, 21 | url: PropTypes.string, 22 | children: PropTypes.array.isRequired, 23 | options: PropTypes.shape({ 24 | drawer: PropTypes.bool, 25 | styles: PropTypes.objectOf(PropTypes.string), 26 | }), 27 | }) 28 | ), 29 | }), 30 | }; 31 | 32 | export default Toctree; 33 | -------------------------------------------------------------------------------- /src/components/Sidenav/index.js: -------------------------------------------------------------------------------- 1 | import Sidenav from './Sidenav'; 2 | import { SidenavContext, SidenavContextProvider } from './sidenav-context'; 3 | 4 | export { Sidenav, SidenavContext, SidenavContextProvider }; 5 | -------------------------------------------------------------------------------- /src/components/Strong.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { getNestedValue } from '../utils/get-nested-value'; 4 | 5 | const Strong = ({ nodeData }) => {getNestedValue(['children', 0, 'value'], nodeData)}; 6 | 7 | Strong.propTypes = { 8 | nodeData: PropTypes.shape({ 9 | children: PropTypes.arrayOf( 10 | PropTypes.shape({ 11 | value: PropTypes.string.isRequired, 12 | }) 13 | ).isRequired, 14 | }).isRequired, 15 | }; 16 | 17 | export default Strong; 18 | -------------------------------------------------------------------------------- /src/components/Subscript.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ComponentFactory from './ComponentFactory'; 4 | 5 | const Subscript = ({ nodeData, ...rest }) => ( 6 | 7 | {nodeData.children.map((child, index) => ( 8 | 9 | ))} 10 | 11 | ); 12 | 13 | Subscript.propTypes = { 14 | nodeData: PropTypes.shape({ 15 | children: PropTypes.arrayOf(PropTypes.object).isRequired, 16 | }).isRequired, 17 | }; 18 | 19 | export default Subscript; 20 | -------------------------------------------------------------------------------- /src/components/SubstitutionReference.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ComponentFactory from './ComponentFactory'; 4 | 5 | const SubstitutionReference = ({ nodeData: { children, name }, ...rest }) => ( 6 | 7 | {children ? children.map((child, index) => ) : name} 8 | 9 | ); 10 | 11 | SubstitutionReference.propTypes = { 12 | nodeData: PropTypes.shape({ 13 | children: PropTypes.arrayOf(PropTypes.object).isRequired, 14 | name: PropTypes.string.isRequired, 15 | }).isRequired, 16 | }; 17 | 18 | export default SubstitutionReference; 19 | -------------------------------------------------------------------------------- /src/components/Superscript.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ComponentFactory from './ComponentFactory'; 4 | 5 | const Superscript = ({ nodeData, ...rest }) => ( 6 | 7 | {nodeData.children.map((child, index) => ( 8 | 9 | ))} 10 | 11 | ); 12 | 13 | Superscript.propTypes = { 14 | nodeData: PropTypes.shape({ 15 | children: PropTypes.arrayOf(PropTypes.object).isRequired, 16 | }).isRequired, 17 | }; 18 | 19 | export default Superscript; 20 | -------------------------------------------------------------------------------- /src/components/SuspenseHelper.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, Suspense } from 'react'; 2 | 3 | /* Helper to avoid React minified errors. Compiles fallback component into the static HTML pages. */ 4 | export const SuspenseHelper = ({ fallback, children }) => { 5 | const [isMounted, setMounted] = useState(false); 6 | 7 | useEffect(() => { 8 | if (!isMounted) { 9 | setMounted(true); 10 | } 11 | }, [isMounted]); 12 | 13 | return {!isMounted ? fallback : children}; 14 | }; 15 | -------------------------------------------------------------------------------- /src/components/Tabs/make-choices.ts: -------------------------------------------------------------------------------- 1 | import { getPlaintext } from '../../utils/get-plaintext'; 2 | import { Node } from '../../types/ast'; 3 | import { DriverMap } from '../icons/DriverIconMap'; 4 | 5 | export const makeChoices = ({ 6 | name, 7 | iconMapping, 8 | options, 9 | }: { 10 | name: string; 11 | options: string | { [k: string]: Node[] }; 12 | iconMapping?: DriverMap; 13 | }) => 14 | Object.entries(options).map(([tabId, title]) => ({ 15 | text: getPlaintext(title), 16 | value: tabId, 17 | ...(name === 'drivers' && iconMapping && { tabSelectorIcon: iconMapping[tabId] }), 18 | })); 19 | -------------------------------------------------------------------------------- /src/components/Text.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TextNode } from '../types/ast'; 3 | 4 | const Text = ({ nodeData: { value } }: { nodeData: TextNode }) => {value}; 5 | 6 | export default Text; 7 | -------------------------------------------------------------------------------- /src/components/Time.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { getPlaintext } from '../utils/get-plaintext'; 3 | import { Directive } from '../types/ast'; 4 | 5 | const Time = ({ nodeData: { argument } }: { nodeData: Directive }) => { 6 | const time = getPlaintext(argument); 7 | if (!time) { 8 | return null; 9 | } 10 | 11 | return ( 12 |

    13 | Time required: {time} minutes 14 |

    15 | ); 16 | }; 17 | 18 | export default Time; 19 | -------------------------------------------------------------------------------- /src/components/TitleReference.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TitleReferenceNode } from '../types/ast'; 3 | 4 | const TitleReference = ({ 5 | nodeData: { 6 | children: [{ value }], 7 | }, 8 | }: { 9 | nodeData: TitleReferenceNode; 10 | }) => {value}; 11 | 12 | export default TitleReference; 13 | -------------------------------------------------------------------------------- /src/components/Transition.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { palette } from '@leafygreen-ui/palette'; 4 | 5 | const HR = styled('hr')` 6 | border: 0.5px solid ${palette.gray.light2}; 7 | `; 8 | 9 | const Transition = () =>
    ; 10 | 11 | export default Transition; 12 | -------------------------------------------------------------------------------- /src/components/Wayfinding/index.js: -------------------------------------------------------------------------------- 1 | import Wayfinding, { MAX_INIT_OPTIONS } from './Wayfinding'; 2 | 3 | export { Wayfinding, MAX_INIT_OPTIONS }; 4 | -------------------------------------------------------------------------------- /src/components/Widgets/FeedbackWidget/FeedbackContainer.js: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import { cx } from '@leafygreen-ui/emotion'; 3 | import { useClickOutside } from '../../../hooks/use-click-outside'; 4 | import useScreenSize from '../../../hooks/useScreenSize'; 5 | import { useFeedbackContext } from './context'; 6 | 7 | const FeedbackContainer = ({ children, className }) => { 8 | const ref = useRef(null); 9 | const { abandon, isScreenshotButtonClicked } = useFeedbackContext(); 10 | const { isMobile } = useScreenSize(); 11 | 12 | useClickOutside(ref, () => { 13 | !isMobile && !isScreenshotButtonClicked && abandon(); 14 | }); 15 | 16 | return ( 17 |
    18 | {children} 19 |
    20 | ); 21 | }; 22 | 23 | export default FeedbackContainer; 24 | -------------------------------------------------------------------------------- /src/components/Widgets/FeedbackWidget/components/LeafygreenTooltip.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Tooltip from '@leafygreen-ui/tooltip'; 3 | import type { TooltipProps } from '@leafygreen-ui/tooltip'; 4 | 5 | const LeafyGreenTooltip = (props: TooltipProps) => ; 6 | export const fwTooltipId = 'feedback-tooltip'; 7 | 8 | export default LeafyGreenTooltip; 9 | -------------------------------------------------------------------------------- /src/components/Widgets/FeedbackWidget/components/PageIndicators.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { palette } from '@leafygreen-ui/palette'; 4 | import { useFeedbackContext } from '../context'; 5 | import { theme } from '../../../../theme/docsTheme'; 6 | 7 | //styling for individual dots in the progress bar 8 | const Dot = styled('span')` 9 | height: ${theme.size.tiny}; 10 | width: ${theme.size.tiny}; 11 | background-color: ${(props) => (props.isActive ? `${palette.green.base}` : `${palette.gray.light2}`)}; 12 | border-radius: 50%; 13 | display: inline-block; 14 | `; 15 | 16 | const DotSpan = styled('span')` 17 | display: flex; 18 | gap: ${theme.size.tiny}; 19 | `; 20 | 21 | const StyledBar = styled('span')` 22 | align-self: center; 23 | `; 24 | 25 | const ProgressBar = () => { 26 | const { progress } = useFeedbackContext(); 27 | return ( 28 | 29 | 30 | {progress.map((value, i) => ( 31 | 32 | ))} 33 | 34 | 35 | ); 36 | }; 37 | 38 | export default ProgressBar; 39 | -------------------------------------------------------------------------------- /src/components/Widgets/FeedbackWidget/components/ViewHeader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { Body } from '@leafygreen-ui/typography'; 4 | import { theme } from '../../../../theme/docsTheme'; 5 | import { RATING_QUESTION_TEXT } from '../constants'; 6 | 7 | const TextHeader = styled(Body)` 8 | font-weight: 600; 9 | text-align: center; 10 | margin-top: 20px; 11 | 12 | @media ${theme.screenSize.upToLarge} { 13 | margin-top: ${theme.size.large}; 14 | } 15 | `; 16 | 17 | // this should only render when in modal or mobile view 18 | const ViewHeader = () => { 19 | return {RATING_QUESTION_TEXT}; 20 | }; 21 | 22 | export default ViewHeader; 23 | -------------------------------------------------------------------------------- /src/components/Widgets/FeedbackWidget/components/view-components.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { theme } from '../../../../theme/docsTheme'; 3 | 4 | export const Layout = styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | `; 9 | 10 | export const Subheading = styled.div` 11 | margin-top: 0; 12 | margin-bottom: ${theme.size.default}; 13 | width: 100%; 14 | text-align: center; 15 | font-weight: regular; 16 | font-size: ${theme.fontSize.small}; 17 | `; 18 | -------------------------------------------------------------------------------- /src/components/Widgets/FeedbackWidget/hooks/useNoScroll.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import noScroll from 'no-scroll'; 3 | 4 | const useNoScroll = (condition: boolean) => { 5 | useEffect(() => { 6 | if (condition) { 7 | noScroll.on(); 8 | return () => noScroll.off(); 9 | } 10 | }, [condition]); 11 | }; 12 | 13 | export default useNoScroll; 14 | -------------------------------------------------------------------------------- /src/components/Widgets/FeedbackWidget/icons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type FontAwesomeIconProps = { 4 | icon: string; 5 | size: string; 6 | spin?: boolean; 7 | }; 8 | 9 | export const FontAwesomeIcon = ({ icon, size, spin, ...props }: FontAwesomeIconProps) => { 10 | const classes = [`fa`, `fa-${icon}`, `fa-${size}`]; 11 | if (spin) { 12 | classes.push(`fa-spin`); 13 | } 14 | return
    ; 15 | }; 16 | 17 | export const CameraIcon = (props: Omit) => ; 18 | 19 | export const SpinnerIcon = (props: Omit) => ( 20 | 21 | ); 22 | 23 | export const CheckIcon = (props: Omit) => ; 24 | 25 | export const StarIcon = (props: Omit) => ; 26 | -------------------------------------------------------------------------------- /src/components/Widgets/FeedbackWidget/useFeedbackData.js: -------------------------------------------------------------------------------- 1 | import useSnootyMetadata from '../../../utils/use-snooty-metadata'; 2 | 3 | export default function useFeedbackData({ slug, title, url }) { 4 | const { project } = useSnootyMetadata(); 5 | const feedback_data = { 6 | slug, 7 | url, 8 | title, 9 | docs_property: project, 10 | }; 11 | return feedback_data; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/Widgets/FeedbackWidget/views/RatingView.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { css, cx } from '@leafygreen-ui/emotion'; 3 | import { Label } from '@leafygreen-ui/typography'; 4 | import { useFeedbackContext } from '../context'; 5 | import StarRating from '../components/StarRating'; 6 | 7 | const labelStyling = css` 8 | font-size: 13px; 9 | font-weight: 500 !important; 10 | color: --label-color; 11 | `; 12 | 13 | const RatingView = () => { 14 | const { selectInitialRating } = useFeedbackContext(); 15 | 16 | return ( 17 | <> 18 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default RatingView; 25 | -------------------------------------------------------------------------------- /src/components/Widgets/FeedbackWidget/views/index.js: -------------------------------------------------------------------------------- 1 | import Loadable from '@loadable/component'; 2 | import SubmittedView from './SubmittedView'; 3 | import RatingView from './RatingView'; 4 | const CommentView = Loadable(() => import('../views/CommentView')); 5 | 6 | export { CommentView, RatingView, SubmittedView }; 7 | -------------------------------------------------------------------------------- /src/components/Widgets/QuizWidget/RealmFuncs.js: -------------------------------------------------------------------------------- 1 | import { useCollection } from './hooks/useCollection'; 2 | import { dataSourceName } from './realm-constants'; 3 | 4 | export function useRealmFuncs(dbName, collectionName) { 5 | const currentCollection = useCollection({ 6 | cluster: dataSourceName, 7 | db: dbName, 8 | collection: collectionName, 9 | }); 10 | 11 | const insertDocument = async (todo) => { 12 | await currentCollection.insertOne(todo); 13 | }; 14 | return { 15 | insertDocument, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/Widgets/QuizWidget/hooks/useCollection.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useRealmApp } from '../RealmApp'; 3 | 4 | /** 5 | * Returns a MongoDB Collection client object 6 | * @template DocType extends Realm.Services.MongoDB.Document 7 | * @param {Object} config - A description of the collection. 8 | * @param {string} [config.cluster] - The service name of the collection's linked cluster. 9 | * @param {string} config.db - The name of database that contains the collection. 10 | * @param {string} config.collection - The name of the collection. 11 | * @returns {Realm.Services.MongoDB.MongoDBCollection} config.collection - The name of the collection. 12 | */ 13 | export function useCollection({ cluster = 'mongodb-atlas', db, collection }) { 14 | const realmApp = useRealmApp(); 15 | return React.useMemo(() => { 16 | if (realmApp.currentUser) { 17 | const mdb = realmApp.currentUser.mongoClient(cluster); 18 | return mdb.db(db).collection(collection); 19 | } 20 | }, [realmApp.currentUser, cluster, db, collection]); 21 | } 22 | -------------------------------------------------------------------------------- /src/components/Widgets/QuizWidget/realm-constants.js: -------------------------------------------------------------------------------- 1 | export const quizAppId = 'quizwidget-ftaro'; 2 | export const baseUrl = 'https://realm.mongodb.com'; 3 | export const dataSourceName = 'mongodb-atlas'; 4 | -------------------------------------------------------------------------------- /src/components/icons/Kotlin.js: -------------------------------------------------------------------------------- 1 | import React, { useId } from 'react'; 2 | 3 | const IconKotlin = ({ ...styles }) => { 4 | const hash = useId(); 5 | 6 | return ( 7 | 8 | 9 | 10 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | export default IconKotlin; 28 | -------------------------------------------------------------------------------- /src/components/icons/LightningBolt.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const IconLightningBolt = ({ ...styles }) => ( 4 | 5 | 6 | 7 | ); 8 | 9 | export default IconLightningBolt; 10 | -------------------------------------------------------------------------------- /src/components/icons/Shell.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const IconShell = ({ ...styles }) => ( 4 | 5 | 6 | 10 | 14 | 18 | 19 | 20 | ); 21 | 22 | export default IconShell; 23 | -------------------------------------------------------------------------------- /src/context/page-context.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react'; 2 | import { Root } from '../types/ast'; 3 | 4 | export type PageTemplateType = 5 | | 'blank' 6 | | 'drivers-index' 7 | | 'errorpage' 8 | | 'feature-not-avail' 9 | | 'instruqt' 10 | | 'landing' 11 | | 'openapi' 12 | | 'changelog' 13 | | 'search' 14 | | 'guide' 15 | | 'product-landing'; 16 | 17 | interface PageContextType { 18 | page: Root | null; 19 | template: PageTemplateType | null; 20 | slug: string; 21 | tabsMainColumn: boolean | null; 22 | options: {} | null; 23 | } 24 | 25 | export const PageContext = createContext({ 26 | page: null, 27 | template: null, 28 | slug: '', 29 | tabsMainColumn: null, 30 | options: null, 31 | }); 32 | 33 | export const usePageContext = () => { 34 | return useContext(PageContext); 35 | }; 36 | -------------------------------------------------------------------------------- /src/hooks/__mocks__/use-site-metadata.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | useSiteMetadata: jest.fn().mockReturnValue({ 3 | branch: 'master', 4 | project: 'datalake', 5 | title: 'MongoDB Datalake', 6 | user: 'andrew', 7 | }), 8 | }; 9 | -------------------------------------------------------------------------------- /src/hooks/use-breadcrumbs.tsx: -------------------------------------------------------------------------------- 1 | import { useStaticQuery, graphql } from 'gatsby'; 2 | 3 | type AllBreadcrumbsQueryResult = { 4 | allBreadcrumb: { 5 | nodes: { 6 | breadcrumbs: { 7 | title: string; 8 | path: string; 9 | }[]; 10 | propertyUrl: string; 11 | }[]; 12 | }; 13 | }; 14 | 15 | // Return an array containing the project property's Url and any intermediate breadcrumbs 16 | export const useBreadcrumbs = () => { 17 | const { allBreadcrumb } = useStaticQuery( 18 | graphql` 19 | query AllBreadcrumbs { 20 | allBreadcrumb { 21 | nodes { 22 | breadcrumbs 23 | propertyUrl 24 | } 25 | } 26 | } 27 | ` 28 | ); 29 | 30 | return allBreadcrumb?.nodes[0] || []; 31 | }; 32 | -------------------------------------------------------------------------------- /src/hooks/use-current-url-slug.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { BranchData } from '../types/data'; 3 | 4 | export const getBranchSlug = (branch: BranchData) => { 5 | return branch['urlSlug'] || branch['gitBranchName']; 6 | }; 7 | 8 | export const useCurrentUrlSlug = (parserBranch: string, branches: BranchData[]): string | undefined => { 9 | const currentUrlSlug = useMemo(() => { 10 | if (!branches || !branches.length) { 11 | return; 12 | } 13 | for (let branch of branches) { 14 | if (branch.gitBranchName === parserBranch) { 15 | return getBranchSlug(branch); 16 | } 17 | } 18 | return parserBranch; 19 | }, [branches, parserBranch]); 20 | 21 | return currentUrlSlug; 22 | }; 23 | -------------------------------------------------------------------------------- /src/hooks/use-media.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { isBrowser } from '../utils/is-browser'; 3 | 4 | // from https://github.com/streamich/react-use/blob/master/src/useMedia.ts 5 | 6 | const useMedia = (query: string, defaultState = false) => { 7 | const [state, setState] = useState(defaultState); 8 | 9 | useEffect(() => { 10 | if (isBrowser) { 11 | let mounted = true; 12 | const mql = window.matchMedia(query); 13 | const onChange = () => { 14 | if (!mounted) { 15 | return; 16 | } 17 | setState(!!mql.matches); 18 | }; 19 | 20 | mql.addListener(onChange); 21 | setState(mql.matches); 22 | 23 | return () => { 24 | mounted = false; 25 | mql.removeListener(onChange); 26 | }; 27 | } 28 | }, [query]); 29 | 30 | return state; 31 | }; 32 | 33 | export default useMedia; 34 | -------------------------------------------------------------------------------- /src/hooks/use-presentation-mode.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import queryString from 'query-string'; 3 | import { useLocation } from '@gatsbyjs/reach-router'; 4 | 5 | export const usePresentationMode = () => { 6 | const { search } = useLocation(); 7 | const presentationResult = useMemo(() => { 8 | const { presentation } = queryString.parse(search); 9 | return presentation; 10 | }, [search]); 11 | 12 | return presentationResult; 13 | }; 14 | -------------------------------------------------------------------------------- /src/hooks/use-remote-metadata.js: -------------------------------------------------------------------------------- 1 | import { useStaticQuery, graphql } from 'gatsby'; 2 | 3 | // Return the remote metadata node 4 | export const useRemoteMetadata = () => { 5 | const data = useStaticQuery( 6 | graphql` 7 | query RemoteMetadata { 8 | allRemoteMetadata { 9 | nodes { 10 | remoteMetadata 11 | } 12 | } 13 | } 14 | ` 15 | ); 16 | return data.allRemoteMetadata.nodes[0]?.remoteMetadata ?? {}; 17 | }; 18 | -------------------------------------------------------------------------------- /src/hooks/use-site-metadata.tsx: -------------------------------------------------------------------------------- 1 | import { useStaticQuery, graphql } from 'gatsby'; 2 | import { SiteMetadata } from '../types/data'; 3 | 4 | type SiteMetadataQueryResult = { 5 | site: { 6 | siteMetadata: SiteMetadata; 7 | }; 8 | }; 9 | 10 | export const useSiteMetadata = () => { 11 | const { site } = useStaticQuery( 12 | graphql` 13 | query SiteMetaData { 14 | site { 15 | siteMetadata { 16 | commitHash 17 | database 18 | parserBranch 19 | parserUser 20 | patchId 21 | pathPrefix 22 | project 23 | reposDatabase 24 | siteUrl 25 | snootyBranch 26 | snootyEnv 27 | user 28 | } 29 | } 30 | } 31 | ` 32 | ); 33 | return site.siteMetadata; 34 | }; 35 | -------------------------------------------------------------------------------- /src/hooks/useAllProducts.tsx: -------------------------------------------------------------------------------- 1 | import { useStaticQuery, graphql } from 'gatsby'; 2 | 3 | type AllProductsQueryResult = { 4 | allProduct: { 5 | nodes: { 6 | title: string; 7 | url: string; 8 | }[]; 9 | }; 10 | }; 11 | 12 | // Return an array of MongoDB products 13 | export const useAllProducts = () => { 14 | const { allProduct } = useStaticQuery( 15 | graphql` 16 | query AllProducts { 17 | allProduct { 18 | nodes { 19 | title 20 | url 21 | } 22 | } 23 | } 24 | ` 25 | ); 26 | return allProduct.nodes; 27 | }; 28 | -------------------------------------------------------------------------------- /src/hooks/useAssociatedProducts.tsx: -------------------------------------------------------------------------------- 1 | import { useStaticQuery, graphql } from 'gatsby'; 2 | 3 | type AllAssociatedProductsQueryResult = { 4 | allAssociatedProduct: { 5 | nodes: { 6 | productName: string; 7 | }[]; 8 | }; 9 | }; 10 | 11 | // Return an array of MongoDB products 12 | export const useAllAssociatedProducts = () => { 13 | const { allAssociatedProduct } = useStaticQuery( 14 | graphql` 15 | query AllAssociatedProducts { 16 | allAssociatedProduct { 17 | nodes { 18 | productName 19 | } 20 | } 21 | } 22 | ` 23 | ); 24 | return allAssociatedProduct.nodes.map((ap) => ap.productName); 25 | }; 26 | -------------------------------------------------------------------------------- /src/hooks/useCopyClipboard.tsx: -------------------------------------------------------------------------------- 1 | import ClipboardJS from 'clipboard'; 2 | import React, { useEffect } from 'react'; 3 | 4 | const useCopyClipboard = ( 5 | copied: boolean, 6 | setCopied: React.Dispatch>, 7 | component: HTMLAnchorElement | null, 8 | contents: string 9 | ) => { 10 | useEffect(() => { 11 | // The component should be a ref 12 | if (!component) { 13 | return; 14 | } 15 | 16 | const clipboard = new ClipboardJS(component, { 17 | text: () => contents, 18 | }); 19 | 20 | if (copied) { 21 | const timeoutId = setTimeout(() => { 22 | setCopied(false); 23 | }, 1500); 24 | 25 | return () => clearTimeout(timeoutId); 26 | } 27 | 28 | return () => clipboard.destroy(); 29 | }, [component, contents, copied, setCopied]); 30 | }; 31 | 32 | export default useCopyClipboard; 33 | -------------------------------------------------------------------------------- /src/hooks/useScreenSize.tsx: -------------------------------------------------------------------------------- 1 | import { theme } from '../theme/docsTheme'; 2 | import useMedia from './use-media'; 3 | 4 | export default function useScreenSize() { 5 | const isMobile = useMedia(theme.screenSize.upToSmall); 6 | const isTabletOrMobile = useMedia(theme.screenSize.upToLarge); 7 | const isMedium = useMedia(theme.screenSize.upToMedium); 8 | const isTablet = useMedia(theme.screenSize.tablet); 9 | const isDesktop = useMedia(theme.screenSize.upTo2XLarge); 10 | const isLargeDesktop = useMedia(theme.screenSize.upTo3XLarge); 11 | 12 | const screen = { 13 | isMobile, 14 | isTabletOrMobile, 15 | isTablet, 16 | isDesktop, 17 | isLargeDesktop, 18 | isMedium, 19 | }; 20 | return screen; 21 | } 22 | -------------------------------------------------------------------------------- /src/images/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # except this file 4 | !.gitignore -------------------------------------------------------------------------------- /src/styles/fonts/EuclidCircularA-Semibold-WebXL.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/src/styles/fonts/EuclidCircularA-Semibold-WebXL.woff -------------------------------------------------------------------------------- /src/styles/fonts/MMSIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/src/styles/fonts/MMSIcons-Regular.ttf -------------------------------------------------------------------------------- /src/styles/fonts/MMSIcons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/src/styles/fonts/MMSIcons-Regular.woff -------------------------------------------------------------------------------- /src/styles/fonts/MMSIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/src/styles/fonts/MMSIcons-Regular.woff2 -------------------------------------------------------------------------------- /src/styles/fonts/MMSOrgIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/src/styles/fonts/MMSOrgIcons-Regular.ttf -------------------------------------------------------------------------------- /src/styles/fonts/MMSOrgIcons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/src/styles/fonts/MMSOrgIcons-Regular.woff -------------------------------------------------------------------------------- /src/styles/fonts/MMSOrgIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/src/styles/fonts/MMSOrgIcons-Regular.woff2 -------------------------------------------------------------------------------- /src/styles/fonts/charts.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/src/styles/fonts/charts.ttf -------------------------------------------------------------------------------- /src/styles/fonts/charts.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/src/styles/fonts/charts.woff -------------------------------------------------------------------------------- /src/styles/fonts/charts.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/src/styles/fonts/charts.woff2 -------------------------------------------------------------------------------- /src/styles/fonts/fontawesome4-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/src/styles/fonts/fontawesome4-webfont.ttf -------------------------------------------------------------------------------- /src/styles/fonts/fontawesome4-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/src/styles/fonts/fontawesome4-webfont.woff -------------------------------------------------------------------------------- /src/styles/fonts/fontawesome4-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/src/styles/fonts/fontawesome4-webfont.woff2 -------------------------------------------------------------------------------- /src/styles/images/page-icon-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/src/styles/images/page-icon-active.png -------------------------------------------------------------------------------- /src/styles/images/page-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/src/styles/images/page-icon.png -------------------------------------------------------------------------------- /src/styles/landing.module.css: -------------------------------------------------------------------------------- 1 | .fullWidth { 2 | margin: 40px auto !important; 3 | } 4 | 5 | .document { 6 | padding: 8px 15px 0 15px; 7 | } 8 | -------------------------------------------------------------------------------- /src/styles/navigation.module.css: -------------------------------------------------------------------------------- 1 | .leftColumn.postRender { 2 | display: flex !important; 3 | } 4 | 5 | .showNav.postRender { 6 | display: block !important; 7 | } 8 | -------------------------------------------------------------------------------- /src/styles/sidebar.module.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | display: block; 3 | } 4 | .sphinxsidebar { 5 | display: block !important; 6 | overflow-y: auto !important; 7 | height: calc(100vh - 45px); 8 | position: sticky !important; 9 | } 10 | -------------------------------------------------------------------------------- /src/templates/blank.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | import * as landingStyles from '../styles/landing.module.css'; 3 | import { NotFoundContainer, Wrapper } from './NotFound'; 4 | 5 | const Blank = ({ children }: { children: ReactNode }) => ( 6 | 7 | {children} 8 | 9 | ); 10 | 11 | export default Blank; 12 | -------------------------------------------------------------------------------- /src/templates/changelog.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { theme } from '../theme/docsTheme'; 4 | import { CONTENT_MAX_WIDTH } from './product-landing'; 5 | 6 | const Wrapper = styled('div')` 7 | grid-column: 2/ -2; 8 | overflow-x: auto; 9 | `; 10 | 11 | const DocumentContainer = styled('main')` 12 | display: grid; 13 | grid-template-columns: minmax(${theme.size.xlarge}, 1fr) repeat(2, minmax(0, ${CONTENT_MAX_WIDTH / 2}px)) minmax( 14 | ${theme.size.xlarge}, 15 | 1fr 16 | ); 17 | @media ${theme.screenSize.upToLarge} { 18 | grid-template-columns: 48px 1fr 48px; 19 | } 20 | 21 | @media ${theme.screenSize.upToMedium} { 22 | grid-template-columns: ${theme.size.medium} 1fr ${theme.size.medium}; 23 | } 24 | `; 25 | 26 | const Changelog = ({ children, offlineBanner }: { children: ReactNode; offlineBanner: ReactNode }) => ( 27 | 28 | 29 | {offlineBanner} 30 | {children} 31 | 32 | 33 | ); 34 | 35 | export default Changelog; 36 | -------------------------------------------------------------------------------- /src/templates/index.js: -------------------------------------------------------------------------------- 1 | import Blank from './blank'; 2 | import Document from './document'; 3 | import DriversIndex from './drivers-index'; 4 | import Instruqt from './instruqt'; 5 | import Landing from './landing'; 6 | import NotFound from './NotFound'; 7 | import FeatureNotAvailable from './FeatureNotAvailable'; 8 | import OpenAPITemplate from './openapi'; 9 | import ProductLanding from './product-landing'; 10 | import Changelog from './changelog'; 11 | 12 | export { 13 | Blank, 14 | Document, 15 | DriversIndex, 16 | Instruqt, 17 | Landing, 18 | NotFound, 19 | FeatureNotAvailable, 20 | OpenAPITemplate, 21 | ProductLanding, 22 | Changelog, 23 | }; 24 | -------------------------------------------------------------------------------- /src/templates/instruqt.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import MainColumn from '../components/MainColumn'; 4 | 5 | const Wrapper = styled(MainColumn)` 6 | max-width: unset; 7 | margin-right: 160px; 8 | `; 9 | 10 | const Instruqt = ({ children, offlineBanner }: { children: ReactNode; offlineBanner: ReactNode }) => ( 11 | 12 | {offlineBanner} 13 | {children} 14 | 15 | ); 16 | 17 | export default Instruqt; 18 | -------------------------------------------------------------------------------- /src/templates/openapi.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | const Wrapper = styled('div')` 5 | max-width: 100vw; 6 | min-height: 600px; 7 | `; 8 | 9 | const OpenAPITemplate = ({ children }: { children: ReactNode }) => {children}; 10 | 11 | export default OpenAPITemplate; 12 | -------------------------------------------------------------------------------- /src/types/ast-utils.ts: -------------------------------------------------------------------------------- 1 | import { isObject, isString } from 'lodash'; 2 | import { Directive, ParentNode, TextNode, RoleName, HeadingNode, roleNames } from './ast'; 3 | 4 | const isTextNode = (node: unknown): node is TextNode => { 5 | return isObject(node) && 'type' in node && node.type === 'text' && 'value' in node && isString(node.value); 6 | }; 7 | 8 | const isParentNode = (node: unknown): node is ParentNode => { 9 | return isObject(node) && 'children' in node && Array.isArray(node.children); 10 | }; 11 | 12 | const isDirectiveNode = (node: unknown): node is Directive => { 13 | return ( 14 | isParentNode(node) && 'name' in node && isString(node.name) && 'argument' in node && Array.isArray(node.argument) 15 | ); 16 | }; 17 | 18 | const isRoleName = (name: string): name is RoleName => { 19 | return roleNames.includes(name); 20 | }; 21 | 22 | const isHeadingNode = (node: unknown): node is HeadingNode => { 23 | return isParentNode(node) && 'type' in node && node.type === 'heading'; 24 | }; 25 | 26 | export { isTextNode, isParentNode, isDirectiveNode, isRoleName, isHeadingNode }; 27 | -------------------------------------------------------------------------------- /src/types/css.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.module.css' { 2 | const classes: { [key: string]: string }; 3 | export = classes; 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/assert-leading-brand.ts: -------------------------------------------------------------------------------- 1 | type options = { 2 | titleCase: boolean; 3 | }; 4 | 5 | const assertLeadingBrand = (title: string, options?: options): string => { 6 | const casingFn = options?.titleCase 7 | ? (e: string) => 8 | e 9 | // replaces starting characters with upper case unless there is a mix of casing 10 | .replace(/(\w\S*)/g, (text) => { 11 | return text.match(/[a-z]+[A-Z]+/) ? text : text.charAt(0).toUpperCase() + text.substring(1); 12 | }) 13 | // replaces characters after hypen with upper case 14 | .replace(/(-\w*)/g, (text) => text.substring(0, 1) + text.charAt(1).toUpperCase() + text.substring(2)) 15 | : (e: string) => e; 16 | const titleIncludesBrand = title.toLowerCase().startsWith('mongodb'); 17 | if (!titleIncludesBrand) { 18 | return `MongoDB ${casingFn(title.replace(/mongodb/i, ''))}`.trimEnd(); 19 | } 20 | return casingFn(title) 21 | .replace(/mongodb/i, 'MongoDB') 22 | .trimEnd(); 23 | }; 24 | 25 | export default assertLeadingBrand; 26 | -------------------------------------------------------------------------------- /src/utils/assert-leading-slash.js: -------------------------------------------------------------------------------- 1 | const assertLeadingSlash = (url) => { 2 | if (url && url.match(/^\//)) { 3 | return url; 4 | } 5 | return `/${url}`; 6 | }; 7 | 8 | module.exports.assertLeadingSlash = assertLeadingSlash; 9 | -------------------------------------------------------------------------------- /src/utils/assert-trailing-slash.js: -------------------------------------------------------------------------------- 1 | const assertTrailingSlash = (url) => { 2 | if (url && url.match(/\/$/)) { 3 | return url; 4 | } 5 | return `${url}/`; 6 | }; 7 | 8 | module.exports.assertTrailingSlash = assertTrailingSlash; 9 | -------------------------------------------------------------------------------- /src/utils/debounce.js: -------------------------------------------------------------------------------- 1 | export default function debounce(fn, delay) { 2 | let timer = null; 3 | return () => { 4 | clearTimeout(timer); 5 | timer = setTimeout(function () { 6 | fn.apply(this, arguments); 7 | }, delay); 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/display-none.js: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | import { theme } from '../theme/docsTheme'; 3 | 4 | const mediaQuery = (size) => css` 5 | @media ${size} { 6 | display: none; 7 | } 8 | `; 9 | 10 | // Add "display: none" to a component's css based on size for media query 11 | export const displayNone = { 12 | onMobile: mediaQuery(theme.screenSize.upToSmall), 13 | onLargerThanMobile: mediaQuery(theme.screenSize.smallAndUp), 14 | onMedium: mediaQuery(theme.screenSize.upToMedium), 15 | onLargerThanMedium: mediaQuery(theme.screenSize.mediumAndUp), 16 | onLargerThanTablet: mediaQuery(theme.screenSize.largeAndUp), 17 | onMobileAndTablet: mediaQuery(theme.screenSize.upToLarge), 18 | }; 19 | -------------------------------------------------------------------------------- /src/utils/download-file.ts: -------------------------------------------------------------------------------- 1 | const fetchAndSaveFile = async (url: string, filename: string) => { 2 | try { 3 | // Fetch the resource 4 | const response = await fetch(url); 5 | 6 | // Ensure the fetch was successful 7 | if (!response.ok) { 8 | throw new Error(`HTTP error! status: ${response.status}`); 9 | } 10 | 11 | // Convert the response to a Blob 12 | const blob = await response.blob(); 13 | 14 | // Create a temporary object URL 15 | const objectURL = URL.createObjectURL(blob); 16 | const a = Object.assign(document.createElement('a'), { 17 | href: objectURL, 18 | download: filename, 19 | }); 20 | 21 | // Trigger the download 22 | a.click(); 23 | 24 | // Cleanup: revoke the object URL 25 | URL.revokeObjectURL(objectURL); 26 | } catch (error) { 27 | console.error('Error fetching and saving the file:', error); 28 | throw error; 29 | } 30 | }; 31 | 32 | export default fetchAndSaveFile; 33 | -------------------------------------------------------------------------------- /src/utils/dynamically-set-z-index.js: -------------------------------------------------------------------------------- 1 | export const elementZIndex = { 2 | setZIndex: (selector, zIndex) => { 3 | const ele = document.querySelector(selector); 4 | 5 | if (!ele) { 6 | console.error('selector not found'); 7 | return; 8 | } 9 | 10 | if (typeof zIndex !== 'number') { 11 | console.error('z-index value has to be a number'); 12 | return; 13 | } 14 | 15 | document.querySelector(selector).style.zIndex = zIndex; 16 | }, 17 | resetZIndex: (selector) => { 18 | const ele = document.querySelector(selector); 19 | 20 | if (!ele) { 21 | console.error('selector not found'); 22 | return; 23 | } 24 | 25 | ele.style.zIndex = 0; 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /src/utils/escape-reserved-html-characters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Replaces HTML-reserved characters with safe equivalents when 3 | * dangerously setting inner HTML (e.g. for search previews). 4 | * See DOP-2863 for rationale. 5 | */ 6 | export const escapeHtml = (unsafe) => { 7 | if (!unsafe) return ''; 8 | 9 | return unsafe 10 | .replaceAll('&', '&') 11 | .replaceAll('<', '<') 12 | .replaceAll('>', '>') 13 | .replaceAll('"', '"') 14 | .replaceAll("'", '''); 15 | }; 16 | -------------------------------------------------------------------------------- /src/utils/find-all-key-value-pairs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Searches child nodes to find all instances of the specified key/value pair in the `nodes` object. 3 | */ 4 | const findAllKeyValuePairs = (nodes, key, value) => { 5 | const results = []; 6 | const searchNode = (node) => { 7 | if (node[key] === value) { 8 | results.push(node); 9 | } 10 | if (node.children) { 11 | return node.children.forEach(searchNode); 12 | } 13 | return null; 14 | }; 15 | nodes.forEach(searchNode); 16 | return results; 17 | }; 18 | 19 | // TODO: switch to ES6 export syntax if Gatsby implements support for ES6 module imports 20 | // https://github.com/gatsbyjs/gatsby/issues/7810 21 | module.exports.findAllKeyValuePairs = findAllKeyValuePairs; 22 | -------------------------------------------------------------------------------- /src/utils/find-all-nested-attribute.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Search all children nodes for attribute 3 | * @param {node[]} nodes 4 | * @param {string} attribute 5 | * @returns {string[]} 6 | */ 7 | export const findAllNestedAttribute = (nodes, attribute) => { 8 | const results = []; 9 | const searchNode = (node) => { 10 | if (Object.hasOwn(node, attribute)) { 11 | results.push(node[attribute]); 12 | } 13 | if (node.children) { 14 | node.children.forEach(searchNode); 15 | } 16 | }; 17 | nodes.forEach(searchNode); 18 | return results; 19 | }; 20 | -------------------------------------------------------------------------------- /src/utils/find-key-value-pair.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Recursively searches child nodes to find the specified key/value pair. 3 | * Prevents us from having to rely on a fixed depth for properties in the AST. 4 | */ 5 | const findKeyValuePair = (nodes, key, value) => { 6 | let result; 7 | const iter = (node) => { 8 | if (node[key] === value) { 9 | result = node; 10 | return true; 11 | } 12 | return Array.isArray(node.children) && node.children.some(iter); 13 | }; 14 | 15 | nodes.some(iter); 16 | return result; 17 | }; 18 | 19 | // TODO: switch to ES6 export syntax if Gatsby implements support for ES6 module imports 20 | // https://github.com/gatsbyjs/gatsby/issues/7810 21 | module.exports.findKeyValuePair = findKeyValuePair; 22 | -------------------------------------------------------------------------------- /src/utils/format-text.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ComponentFactory from '../components/ComponentFactory'; 3 | import { FormatTextOptions } from '../components/Literal'; 4 | import { Node } from '../types/ast'; 5 | import { isDirectiveNode } from '../types/ast-utils'; 6 | 7 | /* 8 | * Given either a string or an array of Snooty text nodes, return the appropriate text output. 9 | */ 10 | export const formatText = (text?: string | Node[], options?: FormatTextOptions) => { 11 | if (!text) return ''; 12 | return typeof text === 'string' 13 | ? text 14 | : text.map((e, index) => { 15 | if (isDirectiveNode(e) && e.name === 'icon') { 16 | return null; 17 | } 18 | return ; 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /src/utils/generate-versioned-prefix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates the prefix to be used for a version's URL. The prefix will typically consist of the docs repo's 3 | * set prefix, with the new version appended at the end. 4 | * 5 | * Use this when the target URL needs to point to a version 6 | * of the docs site that does not use the same exact path prefix (i.e. an aliased docs site needs its exact URL slug). 7 | * @param {string} version The version to include at the end of the prefix. 8 | * @param {string} siteBasePrefix The current docs site's base prefix to append the version to. 9 | */ 10 | export const generateVersionedPrefix = (version, siteBasePrefix) => { 11 | let versionedPrefix = `/${siteBasePrefix}`; 12 | if (version) versionedPrefix += `/${version}`; 13 | return versionedPrefix; 14 | }; 15 | -------------------------------------------------------------------------------- /src/utils/get-nested-value.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Safely return a deeply nested value from an object. If the property is not found, return undefined. 3 | * Arguments: 4 | * - p: an array containing the path to the desired return value 5 | * - o: the object to be searched 6 | */ 7 | const getNestedValue = (p, o) => { 8 | if (!o) return undefined; 9 | return p.reduce((xs, x) => (xs && xs[x] ? xs[x] : undefined), o); 10 | }; 11 | 12 | // TODO: switch to ES6 export syntax if Gatsby implements support for ES6 module imports 13 | // https://github.com/gatsbyjs/gatsby/issues/7810 14 | module.exports.getNestedValue = getNestedValue; 15 | -------------------------------------------------------------------------------- /src/utils/get-page-slug.js: -------------------------------------------------------------------------------- 1 | // Takes a look at the page name and returns the appropriate page url 2 | const getPageSlug = (page) => { 3 | return page === 'index' ? '/' : page; 4 | }; 5 | 6 | module.exports.getPageSlug = getPageSlug; 7 | -------------------------------------------------------------------------------- /src/utils/get-page-title.js: -------------------------------------------------------------------------------- 1 | import { formatText } from './format-text'; 2 | import { getNestedValue } from './get-nested-value'; 3 | 4 | /* 5 | * Given slug and a property's slug-title mapping, look up the title for a given page. 6 | * Returns array of text nodes with formatting or a plaintext string. 7 | */ 8 | export const getPageTitle = (slug, slugTitleMapping) => { 9 | const slugLookup = slug === '/' ? 'index' : slug; 10 | const title = getNestedValue([slugLookup], slugTitleMapping); 11 | return title ? formatText(title) : null; 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/get-plaintext.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Given an array of text nodes with formatting, retrieve the string. 3 | * Returns plaintext string indicating the nested title. 4 | */ 5 | 6 | import { Node } from '../types/ast'; 7 | import { isParentNode, isTextNode } from '../types/ast-utils'; 8 | 9 | export const getPlaintext = (nodeArray: Node[]) => { 10 | const extractText = (title: string, node: Node): string => { 11 | if (isTextNode(node)) { 12 | return title + node.value; 13 | } else if (isParentNode(node)) { 14 | return title + node.children.reduce(extractText, ''); 15 | } 16 | return title; 17 | }; 18 | 19 | return nodeArray && nodeArray.length > 0 ? nodeArray.reduce(extractText, '') : ''; 20 | }; 21 | -------------------------------------------------------------------------------- /src/utils/get-searchbar-results-from-json.js: -------------------------------------------------------------------------------- 1 | // Helper function which extracts a marian title based on the format 2 | // title — property. Also optionally only parse the first 9 results for the dropdown 3 | 4 | export const getSearchbarResultsFromJSON = (resultJSON, searchPropertyMapping = {}, limitResults) => { 5 | const resultsWithoutProperty = resultJSON.results 6 | .filter((r) => { 7 | const searchProperty = r.searchProperty?.[0]; 8 | const endsWithDash = searchProperty?.endsWith('-'); 9 | const isInMapping = !!searchPropertyMapping[searchProperty]; 10 | return !endsWithDash && isInMapping; 11 | }) 12 | .map((r) => ({ ...r, title: r.title.split(' —')[0] })); 13 | if (limitResults) { 14 | return resultsWithoutProperty.slice(0, limitResults); 15 | } 16 | return resultsWithoutProperty; 17 | }; 18 | -------------------------------------------------------------------------------- /src/utils/get-site-title.js: -------------------------------------------------------------------------------- 1 | import { getNestedValue } from './get-nested-value'; 2 | 3 | /** 4 | * Given property metadata, 5 | * return the site title as a string 6 | * 7 | * @param {object} metadata 8 | */ 9 | export const getSiteTitle = (metadata) => { 10 | let title = getNestedValue(['title'], metadata) || ''; 11 | if (!title) { 12 | return ''; 13 | } 14 | const version = getNestedValue(['branch'], metadata) || ''; 15 | title += typeof version === 'string' && version?.startsWith('v') ? ` ${version}` : ''; 16 | return title; 17 | }; 18 | -------------------------------------------------------------------------------- /src/utils/get-suitable-icon.js: -------------------------------------------------------------------------------- 1 | import { withPrefix } from 'gatsby'; 2 | 3 | /** 4 | * 5 | * @param {string} icon 6 | * @param {boolean} iconDark 7 | * @param {boolean} isDarkMode 8 | * @returns {string} 9 | */ 10 | export const getSuitableIcon = (icon, iconDark, isDarkMode) => { 11 | if (typeof icon == 'string') { 12 | const isPath = icon.startsWith('/'); 13 | const getIcon = `${icon}${isDarkMode ? '_inverse' : ''}`; 14 | const imageUrl = `https://webimages.mongodb.com/_com_assets/icons/${getIcon}.svg`; 15 | 16 | return isPath ? (isDarkMode && iconDark ? withPrefix(iconDark) : withPrefix(icon)) : imageUrl; 17 | } 18 | 19 | return ''; 20 | }; 21 | -------------------------------------------------------------------------------- /src/utils/head-scripts/offline-ui/collapsible.js: -------------------------------------------------------------------------------- 1 | function bindCollapsibleUI() { 2 | const onContentLoaded = () => { 3 | try { 4 | const collapsibleComponents = document.querySelectorAll('.offline-collapsible'); 5 | for (const collapsible of collapsibleComponents) { 6 | // bind event to button 7 | const button = collapsible.querySelector('button'); 8 | button?.addEventListener('click', () => { 9 | const newVal = button.getAttribute('aria-expanded') === 'false'; 10 | button.setAttribute('aria-expanded', newVal); 11 | collapsible.setAttribute('aria-expanded', newVal); 12 | }); 13 | } 14 | } catch (e) { 15 | console.error(e); 16 | } 17 | }; 18 | 19 | document.addEventListener('DOMContentLoaded', onContentLoaded, false); 20 | } 21 | 22 | export default bindCollapsibleUI; 23 | 24 | export const OFFLINE_COLLAPSIBLE_CLASSNAME = `offline-collapsible`; 25 | -------------------------------------------------------------------------------- /src/utils/head-scripts/offline-ui/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import bindTabUI from './tabs'; 3 | import bindCollapsibleUI from './collapsible'; 4 | import updateSidenavHeight from './sidenav'; 5 | import bindTabsSelectorsUI from './tabs-selectors'; 6 | import bindMethodSelectorUI from './method-selector'; 7 | import bindIOCode, { bindCodeCopy } from './code'; 8 | 9 | const OFFLINE_UI_CLASSNAME = 'snooty-offline-ui'; 10 | 11 | const getScript = ({ key, fn }) => ( 12 | 11 | 12 | `; 13 | -------------------------------------------------------------------------------- /tests/unit/__snapshots__/Emphasis.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders correctly 1`] = ` 4 | 5 | 6 | Step 3 7 | 8 | 9 | `; 10 | -------------------------------------------------------------------------------- /tests/unit/__snapshots__/FootnoteReference.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders correctly 1`] = ` 4 | 5 | 10 | [1] 11 | 12 | 13 | `; 14 | -------------------------------------------------------------------------------- /tests/unit/__snapshots__/LineBlock.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders correctly 1`] = ` 4 | 5 |
    8 | N. Virginia (us-east-1) 9 |
    10 |
    11 | `; 12 | -------------------------------------------------------------------------------- /tests/unit/__snapshots__/Strong.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders correctly 1`] = ` 4 | 5 | 6 | first 7 | 8 | 9 | `; 10 | -------------------------------------------------------------------------------- /tests/unit/__snapshots__/Text.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders correctly 1`] = ` 4 | 5 | MongoDB offers both a Community and an Enterprise version 6 | 7 | `; 8 | -------------------------------------------------------------------------------- /tests/unit/__snapshots__/TitleReference.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders correctly 1`] = ` 4 | 5 | 6 | admin 7 | 8 | 9 | `; 10 | -------------------------------------------------------------------------------- /tests/unit/browser-storage.test.js: -------------------------------------------------------------------------------- 1 | import { expect, jest, test } from '@jest/globals'; 2 | import { setLocalValue, getLocalValue } from '../../src/utils/browser-storage'; 3 | 4 | const errMsg = 'getItem error'; 5 | const mockLocalStorage = jest.spyOn(window, 'localStorage', 'get').mockImplementation(() => { 6 | return { 7 | getItem: (key) => { 8 | throw new Error(errMsg); 9 | }, 10 | }; 11 | }); 12 | 13 | describe('when rendering in the browser', () => { 14 | test('setLocalValue does not break if no storage', () => { 15 | expect(window.localStorage.getItem).toThrowError(errMsg); 16 | expect(setLocalValue).not.toThrow(); 17 | }); 18 | 19 | test('getLocalValue does not break if no storage', () => { 20 | expect(window.localStorage.getItem).toThrowError(errMsg); 21 | expect(getLocalValue).not.toThrow(); 22 | }); 23 | }); 24 | 25 | // // reset window.localStorage.getItem just so we don't mess up any global objects 26 | afterAll(() => { 27 | mockLocalStorage.mockRestore(); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/unit/data/Admonition.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "directive", 3 | "position": { 4 | "start": { 5 | "line": 0 6 | } 7 | }, 8 | "name": "note", 9 | "argument": [], 10 | "children": [ 11 | { 12 | "type": "text", 13 | "position": { 14 | "start": { 15 | "line": 6 16 | } 17 | }, 18 | "value": "These instructions are for installing MongoDB directly from" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /tests/unit/data/Banner.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "directive", 3 | "position": { 4 | "start": { 5 | "line": 0 6 | } 7 | }, 8 | "name": "banner", 9 | "variant": "info", 10 | "argument": [], 11 | "children": [ 12 | { 13 | "type": "text", 14 | "position": { 15 | "start": { 16 | "line": 6 17 | } 18 | }, 19 | "value": "These instructions are for installing MongoDB directly from" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /tests/unit/data/Breadcrumbs.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "example-projects": [], 3 | "sdk/cpp/app-services/call-a-function": [ 4 | {"path": "sdk/cpp", "title": [{"type": "text", "position": {"start": {"line": 9}}, "value": "Atlas Device SDK for C++"}], "plaintext": "Atlas Device SDK for C++"}, 5 | {"path": "sdk/cpp/application-services", "title": [ 6 | {"type": "text", "position": {"start": {"line": 4}}, "value": "Application Services - C++ SDK"}], "plaintext": "Application Services - - C++ SDK"} 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tests/unit/data/Button.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "directive", 3 | "position": { 4 | "start": { 5 | "line": 12 6 | } 7 | }, 8 | "children": [], 9 | "domain": "landing", 10 | "name": "button", 11 | "argument": [ 12 | { 13 | "type": "text", 14 | "position": { 15 | "start": { 16 | "line": 12 17 | } 18 | }, 19 | "value": "Download Compass" 20 | } 21 | ], 22 | "options": { 23 | "uri": "/install" 24 | } 25 | } -------------------------------------------------------------------------------- /tests/unit/data/Code.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "testCode": { 3 | "type": "code", 4 | "position": { 5 | "start": { 6 | "line": 0 7 | } 8 | }, 9 | "lang": "javascript", 10 | "copyable": true, 11 | "caption": "Test Caption", 12 | "value": "mongoimport --db test --collection inventory ^\n --authenticationDatabase admin --username --password ^\n --drop --file ~\\downloads\\inventory.crud.json" 13 | }, 14 | "testWithSelectors": { 15 | "type": "code", 16 | "position": { 17 | "start": { 18 | "line": 0 19 | } 20 | }, 21 | "lang": "javascript", 22 | "copyable": true, 23 | "value": "Testing code" 24 | }, 25 | "testNoneLanguage" : { 26 | "type": "code", 27 | "position": { 28 | "start": { 29 | "line": 0 30 | } 31 | }, 32 | "lang": "none", 33 | "copyable": true, 34 | "emphasize_lines":[], 35 | "value": "[{plot A trio of guys try and make up for missed opportunities in childhood by forming a three-player baseball team to compete against standard children baseball squads.}]" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/unit/data/CommunityPillLink.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "directive", 3 | "position": { 4 | "start": { 5 | "line": 6 6 | } 7 | }, 8 | "children": [], 9 | "domain": "", 10 | "name": "community-driver", 11 | "argument": [ 12 | { 13 | "type": "text", 14 | "position": { 15 | "start": { 16 | "line": 6 17 | } 18 | }, 19 | "value": "MongoEngine for Flask" 20 | } 21 | ], 22 | "options": { 23 | "url": "https://example.com" 24 | } 25 | } -------------------------------------------------------------------------------- /tests/unit/data/Emphasis.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "emphasis", 3 | "position": { 4 | "start": { 5 | "line": 2 6 | } 7 | }, 8 | "children": [ 9 | { 10 | "type": "text", 11 | "position": { 12 | "start": { 13 | "line": 2 14 | } 15 | }, 16 | "value": "Step 3" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /tests/unit/data/Figure.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "directive", 3 | "position": { 4 | "start": { 5 | "line": 13 6 | } 7 | }, 8 | "name": "figure", 9 | "argument": [ 10 | { 11 | "type": "text", 12 | "position": { 13 | "start": { 14 | "line": 13 15 | } 16 | }, 17 | "value": "/images/firstcluster.png" 18 | } 19 | ], 20 | "options": { 21 | "figwidth": "700px", 22 | "checksum": "968654cd30e0b15d8b835ff39a066d3e4555aeef2bc77707908d6f990a643706", 23 | "width": "500", 24 | "height": "300", 25 | "class": "hero-img", 26 | "scale": "1000", 27 | "align": "test-align", 28 | "alt": "test-alt" 29 | }, 30 | "children": [] 31 | } -------------------------------------------------------------------------------- /tests/unit/data/FigureBorder.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "directive", 3 | "position": { 4 | "start": { 5 | "line": 13 6 | } 7 | }, 8 | "name": "figure", 9 | "argument": [ 10 | { 11 | "type": "text", 12 | "position": { 13 | "start": { 14 | "line": 13 15 | } 16 | }, 17 | "value": "/images/firstcluster.png" 18 | } 19 | ], 20 | "options": { 21 | "figwidth": "700px", 22 | "border": true, 23 | "checksum": "968654cd30e0b15d8b835ff39a066d3e4555aeef2bc77707908d6f990a643706" 24 | }, 25 | "children": [] 26 | } -------------------------------------------------------------------------------- /tests/unit/data/FigureLightbox.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "directive", 3 | "position": { 4 | "start": { 5 | "line": 13 6 | } 7 | }, 8 | "name": "figure", 9 | "argument": [ 10 | { 11 | "type": "text", 12 | "position": { 13 | "start": { 14 | "line": 13 15 | } 16 | }, 17 | "value": "/images/firstcluster.png" 18 | } 19 | ], 20 | "options": { 21 | "figwidth": "700px", 22 | "checksum": "968654cd30e0b15d8b835ff39a066d3e4555aeef2bc77707908d6f990a643706", 23 | "lightbox": true 24 | }, 25 | "children": [] 26 | } 27 | -------------------------------------------------------------------------------- /tests/unit/data/Footnote.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "footnote", 3 | "position": { 4 | "start": { 5 | "line": 108 6 | } 7 | }, 8 | "children": [{ 9 | "type": "paragraph", 10 | "position": { 11 | "start": { 12 | "line": 108 13 | } 14 | }, 15 | "children": [{ 16 | "type": "text", 17 | "position": { 18 | "start": { 19 | "line": 108 20 | } 21 | }, 22 | "value": "Numerical footnote." 23 | }] 24 | }], 25 | "id": "id8", 26 | "name": "1" 27 | } -------------------------------------------------------------------------------- /tests/unit/data/FootnoteReference.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "footnote_reference", 3 | "position": { 4 | "start": { 5 | "line": 100 6 | } 7 | }, 8 | "children": [{ 9 | "type": "text", 10 | "position": { 11 | "start": { 12 | "line": 100 13 | } 14 | }, 15 | "value": "1" 16 | }], 17 | "id": "id1", 18 | "refname": "1" 19 | } -------------------------------------------------------------------------------- /tests/unit/data/Heading.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "heading", 3 | "position": { 4 | "start": { 5 | "line": 0 6 | } 7 | }, 8 | "children": [ 9 | { 10 | "type": "text", 11 | "position": { 12 | "start": { 13 | "line": 0 14 | } 15 | }, 16 | "value": "Create an administrative username and password" 17 | } 18 | ], 19 | "id": "create-an-administrative-username-and-password" 20 | } 21 | -------------------------------------------------------------------------------- /tests/unit/data/Include.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "directive", 3 | "position": { 4 | "start": { 5 | "line": 22 6 | } 7 | }, 8 | "name": "include", 9 | "argument": [ 10 | { 11 | "type": "text", 12 | "position": { 13 | "start": { 14 | "line": 22 15 | } 16 | }, 17 | "value": "/includes/steps/cloud_pr.rst" 18 | } 19 | ], 20 | "children": [ 21 | { 22 | "type": "FixedTextElement", 23 | "position": { 24 | "start": { 25 | "line": 22 26 | } 27 | }, 28 | "children": [] 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /tests/unit/data/Instruqt.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "noArgument": { 3 | "type": "directive", 4 | "children": [], 5 | "domain": "mongodb", 6 | "name": "instruqt", 7 | "argument": [] 8 | }, 9 | "example": { 10 | "type": "directive", 11 | "children": [], 12 | "domain": "mongodb", 13 | "name": "instruqt", 14 | "argument": [ 15 | { 16 | "type": "text", 17 | "value": "/mongodb-docs/tracks/getting-started-with-mongodb?token=em_j_d_wUT93QTFvgsZ" 18 | } 19 | ] 20 | }, 21 | "exampleDrawer": { 22 | "type": "directive", 23 | "children": [], 24 | "domain": "mongodb", 25 | "name": "instruqt", 26 | "argument": [ 27 | { 28 | "type": "text", 29 | "value": "/mongodb-docs/tracks/getting-started-with-mongodb?token=em_j_d_wUT93QTFvgsZ" 30 | } 31 | ], 32 | "options": { 33 | "drawer": true 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /tests/unit/data/Line-empty.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "line", 3 | "position": { 4 | "start": { 5 | "line": 0 6 | } 7 | }, 8 | "children": [] 9 | } -------------------------------------------------------------------------------- /tests/unit/data/Line.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "line", 3 | "position": { 4 | "start": { 5 | "line": 0 6 | } 7 | }, 8 | "children": [ 9 | { 10 | "type": "literal", 11 | "position": { 12 | "start": { 13 | "line": 0 14 | } 15 | }, 16 | "children": [ 17 | { 18 | "type": "text", 19 | "position": { 20 | "start": { 21 | "line": 0 22 | } 23 | }, 24 | "value": "N. Virginia (us-east-1)" 25 | } 26 | ] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /tests/unit/data/LineBlock.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "line_block", 3 | "position": { 4 | "start": { 5 | "line": 0 6 | } 7 | }, 8 | "children": [ 9 | { 10 | "type": "line", 11 | "position": { 12 | "start": { 13 | "line": 0 14 | } 15 | }, 16 | "children": [ 17 | { 18 | "type": "literal", 19 | "position": { 20 | "start": { 21 | "line": 0 22 | } 23 | }, 24 | "children": [ 25 | { 26 | "type": "text", 27 | "position": { 28 | "start": { 29 | "line": 0 30 | } 31 | }, 32 | "value": "N. Virginia (us-east-1)" 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /tests/unit/data/Literal.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "literal", 3 | "position": { 4 | "start": { 5 | "line": 3 6 | } 7 | }, 8 | "children": [ 9 | { 10 | "type": "text", 11 | "position": { 12 | "start": { 13 | "line": 3 14 | } 15 | }, 16 | "value": "N. Virginia (us-east-1)" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /tests/unit/data/LiteralInclude.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "directive", 3 | "position": { 4 | "start": { 5 | "line": 0 6 | } 7 | }, 8 | "name": "literalinclude", 9 | "argument": [ 10 | { 11 | "type": "text", 12 | "position": { 13 | "start": { 14 | "line": 0 15 | } 16 | }, 17 | "value": "/driver-examples/JavaConnectExample.java" 18 | } 19 | ], 20 | "options": { 21 | "language": "java", 22 | "dedent": "4", 23 | "start-after": "Start Connection", 24 | "end-before": "End Connection" 25 | }, 26 | "children": [ 27 | { 28 | "type": "code", 29 | "lang": "java", 30 | "copyable": true, 31 | "emphasize_lines": [0,1], 32 | "value": "sample code", 33 | "linenos": [0,1] 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /tests/unit/data/MetadataWithoutToc.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "atlas-cli", 3 | "branch": "v1.18", 4 | "title": "Atlas CLI", 5 | "eol": false, 6 | "canonical": null, 7 | "parentPaths": {}, 8 | "slugToTitle": { 9 | "atlas-cli-getting-started": [ 10 | { 11 | "type": "text", 12 | "position": { 13 | "start": { 14 | "line": 4 15 | } 16 | }, 17 | "value": "Get Started with " 18 | } 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/unit/data/PageContext.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "community-supported-drivers", 3 | "repoBranches": { 4 | "branches": [ 5 | { 6 | "name": "master", 7 | "publishOriginalBranchName": false, 8 | "active": true, 9 | "aliases": null, 10 | "gitBranchName": "master", 11 | "isStableBranch": true, 12 | "urlAliases": null, 13 | "urlSlug": null, 14 | "versionSelectorLabel": "master", 15 | "buildsWithSnooty": true, 16 | "id": "634991b4246409bf0cc51123" 17 | } 18 | ], 19 | "siteBasePrefix": "docs-qa/drivers" 20 | }, 21 | "associatedReposInfo": {}, 22 | "remoteMetadata": null, 23 | "isAssociatedProduct": false, 24 | "template": "blank", 25 | "page": { 26 | "type": "root", 27 | "position": { "start": { "line": 0 } }, 28 | "children": [{ "type": "section", "position": [], "children": [] }], 29 | "fileid": "community-supported-drivers.txt", 30 | "options": { "orphan": "", "template": "blank" } 31 | } 32 | } -------------------------------------------------------------------------------- /tests/unit/data/Paragraph-Format.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "paragraph", 3 | "position": { 4 | "start": { 5 | "line": 0 6 | } 7 | }, 8 | "children": [ 9 | { 10 | "type": "reference", 11 | "children": [{ 12 | "position": { 13 | "start": { 14 | "line": 0 15 | } 16 | }, 17 | "type": "text", 18 | "value": "Deploy a Free Tier Cluster" 19 | }], 20 | "position": { 21 | "start": { 22 | "line": 0 23 | } 24 | }, 25 | "refuri": "https://cloud.mongodb.com?tck=docs_realm" 26 | }, 27 | { 28 | "type": "text", 29 | "position": { 30 | "start": { 31 | "line": 0 32 | } 33 | }, 34 | "value": "." 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /tests/unit/data/Paragraph.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "paragraph", 3 | "position": { 4 | "start": { 5 | "line": 0 6 | } 7 | }, 8 | "children": [ 9 | { 10 | "type": "text", 11 | "position": { 12 | "start": { 13 | "line": 0 14 | } 15 | }, 16 | "value": "Verify that MongoDB has started successfully by\nchecking the process output for the following line:" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /tests/unit/data/Reference.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "reference", 3 | "position": { 4 | "start": { 5 | "line": 40 6 | } 7 | }, 8 | "refuri": "https://docs.mlab.com/subscriptions/#change-plans-using-rnr", 9 | "children": [ 10 | { 11 | "type": "text", 12 | "position": { 13 | "start": { 14 | "line": 40 15 | } 16 | }, 17 | "value": "mLab documentation" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /tests/unit/data/ReleaseSpecification.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "directive", 3 | "position": { 4 | "start": { 5 | "line": 0 6 | } 7 | }, 8 | "children": [ 9 | { 10 | "type": "code", 11 | "position": { 12 | "start": { 13 | "line": 0 14 | } 15 | }, 16 | "lang": "bat", 17 | "copyable": true, 18 | "value": "test code", 19 | "linenos": false 20 | } 21 | ], 22 | "name": "release_specification" 23 | } 24 | -------------------------------------------------------------------------------- /tests/unit/data/Role-abbr.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "role", 3 | "position": { 4 | "start": { 5 | "line": { 6 | "$numberInt": "8" 7 | } 8 | } 9 | }, 10 | "domain": "", 11 | "name": "abbr", 12 | "children": [ 13 | { 14 | "type": "text", 15 | "position": { 16 | "start": { 17 | "line": { 18 | "$numberInt": "8" 19 | } 20 | } 21 | }, 22 | "value": "ABBR (Full Name Here)" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /tests/unit/data/Role-file.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "role", 3 | "position": { 4 | "start": { 5 | "line": { 6 | "$numberInt": "101" 7 | } 8 | } 9 | }, 10 | "domain": "", 11 | "name": "file", 12 | "children": [ 13 | { 14 | "type": "text", 15 | "position": { 16 | "start": { 17 | "line": { 18 | "$numberInt": "101" 19 | } 20 | } 21 | }, 22 | "value": "examples/treasury_yield/src/main/resources/yield_historical_in.json" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /tests/unit/data/Role-guilabel.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "role", 3 | "position": { 4 | "start": { 5 | "line": { 6 | "$numberInt": "8" 7 | } 8 | } 9 | }, 10 | "domain": "", 11 | "name": "guilabel", 12 | "children": [ 13 | { 14 | "type": "text", 15 | "position": { 16 | "start": { 17 | "line": { 18 | "$numberInt": "8" 19 | } 20 | } 21 | }, 22 | "value": "Yes" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /tests/unit/data/Strong.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "strong", 3 | "position": { 4 | "start": { 5 | "line": 0 6 | } 7 | }, 8 | "children": [ 9 | { 10 | "type": "text", 11 | "position": { 12 | "start": { 13 | "line": 0 14 | } 15 | }, 16 | "value": "first" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /tests/unit/data/Text.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "text", 3 | "position": { 4 | "start": { 5 | "line": 0 6 | } 7 | }, 8 | "value": "MongoDB offers both a Community and an Enterprise version" 9 | } -------------------------------------------------------------------------------- /tests/unit/data/Time.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "directive", 3 | "position": { 4 | "start": { 5 | "line": 22 6 | } 7 | }, 8 | "children": [], 9 | "domain": "mongodb", 10 | "name": "time", 11 | "argument": [{ 12 | "type": "text", 13 | "position": { 14 | "start": { 15 | "line": 22 16 | } 17 | }, 18 | "value": "15" 19 | }] 20 | } -------------------------------------------------------------------------------- /tests/unit/data/TitleReference.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "title_reference", 3 | "position": { 4 | "start": { 5 | "line": 0 6 | } 7 | }, 8 | "children": [ 9 | { 10 | "type": "text", 11 | "position": { 12 | "start": { 13 | "line": 0 14 | } 15 | }, 16 | "value": "admin" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/utils/assert-trailing-slash.test.js: -------------------------------------------------------------------------------- 1 | import { assertTrailingSlash } from '../../../src/utils/assert-trailing-slash'; 2 | 3 | it('should add trailing slashes to links if they are missing', () => { 4 | const linkWithoutSlash = 'foo.bar'; 5 | const linkWithSlash = `${linkWithoutSlash}/`; 6 | expect(assertTrailingSlash(linkWithSlash)).toBe(linkWithSlash); 7 | 8 | // Should ignore anything without a slash 9 | expect(assertTrailingSlash(linkWithoutSlash)).toBe(linkWithSlash); 10 | 11 | // Should handle null/empty inputs 12 | expect(assertTrailingSlash('')).toBe('/'); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/unit/utils/find-all-nested-attribute.test.js: -------------------------------------------------------------------------------- 1 | import { findAllNestedAttribute } from '../../../src/utils/find-all-nested-attribute'; 2 | import figureData from '../data/Figure.test.json'; 3 | import headingData from '../data/Heading.test.json'; 4 | import footnoteData from '../data/Footnote.test.json'; 5 | 6 | describe('findAllNestedAttribute', () => { 7 | it('gets all attribute from one level of children', () => { 8 | const res = findAllNestedAttribute([figureData, headingData, footnoteData], 'id'); 9 | expect(res).toEqual(['create-an-administrative-username-and-password', 'id8']); 10 | }); 11 | 12 | it('gets all attributes from multiple children levels', () => { 13 | const headingWithChildren = { ...headingData }; 14 | headingWithChildren.children = [headingData, footnoteData]; 15 | const res = findAllNestedAttribute([headingWithChildren], 'type'); 16 | expect(res).toEqual(['heading', 'heading', 'text', 'footnote', 'paragraph', 'text']); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/unit/utils/generate-versioned-prefix.test.js: -------------------------------------------------------------------------------- 1 | import { generateVersionedPrefix } from '../../../src/utils/generate-versioned-prefix'; 2 | 3 | describe('generateVersionedPrefix', () => { 4 | it('returns a prefix for a versioned docs site', () => { 5 | const mockSiteBasePrefix = 'docs/bi-connector'; 6 | expect(generateVersionedPrefix('v2.15', mockSiteBasePrefix)).toBe('/docs/bi-connector/v2.15'); 7 | }); 8 | 9 | it("returns a prefix when the site's base prefix has more than 1 forward slash", () => { 10 | const mockSiteBasePrefix = 'docs/atlas/cli'; 11 | expect(generateVersionedPrefix('upcoming', mockSiteBasePrefix)).toBe('/docs/atlas/cli/upcoming'); 12 | }); 13 | 14 | it('returns a prefix when a urlSlug/version with multiple forward slashes exists', () => { 15 | const mockSiteBasePrefix = 'docs/realm'; 16 | expect(generateVersionedPrefix('sdk/android/v10.2', mockSiteBasePrefix)).toBe('/docs/realm/sdk/android/v10.2'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/unit/utils/is-relative-url.test.js: -------------------------------------------------------------------------------- 1 | import { isRelativeUrl } from '../../../src/utils/is-relative-url'; 2 | 3 | it('should return false for any absolute or external links', () => { 4 | expect(isRelativeUrl('http://foo.bar')).toBe(false); 5 | expect(isRelativeUrl('https://foo.bar')).toBe(false); 6 | expect(isRelativeUrl('mailto:test@test.com')).toBe(false); 7 | }); 8 | 9 | it('should return true for any relative links', () => { 10 | expect(isRelativeUrl('/foo')).toBe(true); 11 | expect(isRelativeUrl('/foo/')).toBe(true); 12 | expect(isRelativeUrl('foo')).toBe(true); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/unit/utils/join-class-names.test.js: -------------------------------------------------------------------------------- 1 | const { joinClassNames } = require('../../../src/utils/join-class-names'); 2 | 3 | describe('joinClassNames', () => { 4 | it('returns a string of all class names joined together', () => { 5 | const className = joinClassNames('class1', 'class2', 'class3'); 6 | expect(className).toEqual('class1 class2 class3'); 7 | }); 8 | 9 | it('returns a string even when a class is undefined', () => { 10 | const className = joinClassNames(undefined, undefined, 'class3'); 11 | expect(className).toEqual('class3'); 12 | }); 13 | 14 | it('returns undefined when no classes are defined', () => { 15 | const className = joinClassNames(undefined, null); 16 | expect(className).toEqual(undefined); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/unit/utils/remove-leading-slash.test.js: -------------------------------------------------------------------------------- 1 | import { removeLeadingSlash } from '../../../src/utils/remove-leading-slash'; 2 | 3 | it('should remove leading slashes to file pathnames if found', () => { 4 | const pathNameWithSlash = '/path/to/image.png'; 5 | 6 | const pathNameWithMultipleSlashes = '////path/to/image.png'; 7 | 8 | const pathNameWithoutSlash = 'path/to/image.png'; 9 | 10 | expect(removeLeadingSlash(pathNameWithSlash)).toBe(pathNameWithoutSlash); 11 | expect(removeLeadingSlash(pathNameWithMultipleSlashes)).toBe(pathNameWithoutSlash); 12 | expect(removeLeadingSlash(pathNameWithoutSlash)).toBe(pathNameWithoutSlash); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/utils/data/feedbackWidgetScreenshotFunctions.js: -------------------------------------------------------------------------------- 1 | import * as handleScreenshot from '../../../src/components/Widgets/FeedbackWidget/handleScreenshot'; 2 | 3 | export const screenshotFunctionMocks = {}; 4 | export function mockScreenshotFunctions() { 5 | screenshotFunctionMocks['addEventListener'] = jest 6 | .spyOn(document, 'addEventListener') 7 | .mockImplementation((mousemove, handleElementHighlight) => { 8 | return { mousemove, handleElementHighlight }; 9 | }); 10 | 11 | screenshotFunctionMocks['retrieveDataUri'] = jest 12 | .spyOn(handleScreenshot, 'retrieveDataUri') 13 | .mockImplementation(() => { 14 | return 'dataUri retrieved successfully'; 15 | }); 16 | } 17 | 18 | export const clearMockScreenshotFunctions = () => { 19 | Object.keys(screenshotFunctionMocks).forEach((mockedFunctionName) => { 20 | screenshotFunctionMocks[mockedFunctionName].mockClear(); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /tests/utils/data/parsed-marian-manifests.json: -------------------------------------------------------------------------------- 1 | { 2 | "Atlas": { "Latest": "atlas-master" }, 3 | "Realm": { "Latest": "realm-master" }, 4 | "BI Connector": { 5 | "2.14": "bi-connector-current", 6 | "2.12": "bi-connector-v2.12", 7 | "2.13": "bi-connector-v2.13" 8 | }, 9 | "MongoDB Server": { 10 | "5.3 (current)": "manual-v5.3" 11 | }, 12 | "Mongoid": { 13 | "Latest": "mongoid-master", 14 | "Version 7.4 (current)": "mongoid-7.4", 15 | "Version 7.3": "mongoid-7.3" 16 | }, 17 | "Cloud Manager": { "Latest": "mms-cloud-master" }, 18 | "Ops Manager": { 19 | "upcoming": "mms-onprem-master", 20 | "Version 5.0 (current)": "mms-onprem-current" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/utils/feedbackWidgetStitchFunctions.js: -------------------------------------------------------------------------------- 1 | import * as Realm from 'realm-web'; 2 | import * as realm from '../../src/components/Widgets/FeedbackWidget/realm'; 3 | 4 | export const stitchFunctionMocks = {}; 5 | export function mockStitchFunctions() { 6 | stitchFunctionMocks['upsertFeedback'] = jest 7 | .spyOn(realm, 'upsertFeedback') 8 | .mockImplementation(({ page, user, ...rest }) => { 9 | return { 10 | _id: rest.feedback_id ?? new Realm.BSON.ObjectId(), 11 | page, 12 | user, 13 | ...rest, 14 | }; 15 | }); 16 | 17 | stitchFunctionMocks['useRealmUser'] = jest.spyOn(realm, 'useRealmUser').mockImplementation(() => { 18 | return { 19 | user: { 20 | id: 'test-user-id', 21 | }, 22 | // Most of this logic is dependent on Realm app working 23 | reassignCurrentUser: () => ({ id: 'another-test-user-id' }), 24 | }; 25 | }); 26 | } 27 | export const clearMockStitchFunctions = () => { 28 | Object.keys(stitchFunctionMocks).forEach((mockedFunctionName) => { 29 | stitchFunctionMocks[mockedFunctionName].mockClear(); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /tests/utils/mock-location.js: -------------------------------------------------------------------------------- 1 | import { useLocation } from '@gatsbyjs/reach-router'; 2 | 3 | jest.mock('@gatsbyjs/reach-router', () => ({ 4 | useLocation: jest.fn(), 5 | })); 6 | 7 | export const mockLocation = (search, pathname, hash, href) => 8 | useLocation.mockImplementation(() => ({ search, pathname, hash, href })); 9 | -------------------------------------------------------------------------------- /tests/utils/mock-with-prefix.js: -------------------------------------------------------------------------------- 1 | import * as Gatsby from 'gatsby'; 2 | 3 | const withPrefix = jest.spyOn(Gatsby, 'withPrefix'); 4 | 5 | export const mockWithPrefix = (prefix) => { 6 | withPrefix.mockImplementation((path) => { 7 | let normalizedPrefix = prefix; 8 | let normalizedPath = path; 9 | 10 | if (!normalizedPrefix.startsWith('/')) { 11 | normalizedPrefix = `/${normalizedPrefix}`; 12 | } 13 | 14 | if (normalizedPrefix.endsWith('/')) { 15 | normalizedPrefix = normalizedPath.slice(0, -1); 16 | } 17 | 18 | if (normalizedPath.startsWith('/')) { 19 | normalizedPath = normalizedPath.slice(1); 20 | } 21 | 22 | return `${normalizedPrefix}/${normalizedPath}`; 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /tests/utils/mockStaticQuery.js: -------------------------------------------------------------------------------- 1 | import * as Gatsby from 'gatsby'; 2 | 3 | const useStaticQuery = jest.spyOn(Gatsby, 'useStaticQuery'); 4 | const mockStaticQuery = (mockSiteMetadata = {}) => { 5 | useStaticQuery.mockImplementation(() => ({ 6 | site: { 7 | siteMetadata: mockSiteMetadata, 8 | }, 9 | })); 10 | }; 11 | 12 | export default mockStaticQuery; 13 | -------------------------------------------------------------------------------- /tests/utils/parse-marian-manifests.test.js: -------------------------------------------------------------------------------- 1 | import { getSortedBranchesForProperty, parseMarianManifests } from '../../src/utils/parse-marian-manifests'; 2 | import mockInputData from './data/marian-manifests.json'; 3 | import mockResponseData from './data/parsed-marian-manifests.json'; 4 | 5 | it('should parse marian manifests', () => { 6 | expect(parseMarianManifests(mockInputData.manifests, mockInputData.searchPropertyMapping)).toStrictEqual( 7 | mockResponseData 8 | ); 9 | }); 10 | 11 | it('should properly sort branches for a property with version numbers', () => { 12 | const parsedSampleData = parseMarianManifests(mockInputData.manifests, mockInputData.searchPropertyMapping); 13 | expect(getSortedBranchesForProperty(parsedSampleData, 'Mongoid')).toStrictEqual([ 14 | 'Latest', 15 | 'Version 7.4 (current)', 16 | 'Version 7.3', 17 | ]); 18 | }); 19 | --------------------------------------------------------------------------------