├── .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 |
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 |
24 | );
25 | };
26 |
27 | export default IconKotlin;
28 |
--------------------------------------------------------------------------------
/src/components/icons/LightningBolt.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconLightningBolt = ({ ...styles }) => (
4 |
7 | );
8 |
9 | export default IconLightningBolt;
10 |
--------------------------------------------------------------------------------
/src/components/icons/Shell.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconShell = ({ ...styles }) => (
4 |
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 |
18 | );
19 |
20 | export const OFFLINE_HEAD_SCRIPTS = [
21 | bindTabUI,
22 | updateSidenavHeight,
23 | bindTabsSelectorsUI,
24 | bindCollapsibleUI,
25 | bindMethodSelectorUI,
26 | bindIOCode,
27 | bindCodeCopy,
28 | ].map((fn, idx) => getScript({ key: `offline-docs-ui-${idx}`, fn }));
29 |
--------------------------------------------------------------------------------
/src/utils/head-scripts/offline-ui/sidenav.js:
--------------------------------------------------------------------------------
1 | /**
2 | * For offline HTML builds, sidenav should adjust to scroll position
3 | * so that it is sticky, but takes up full height when scrolled down
4 | * and leaves room for Header Navigation at initial scrollY 0
5 | */
6 |
7 | function updateSidenavHeight() {
8 | const updateScrollY = () => {
9 | // pulled from SIDE_NAV_CONTAINER_ID
10 | // 'docs-side-nav-container'
11 | const sidenavElm = document.querySelector('#docs-side-nav-container');
12 | sidenavElm.style = `--scroll-y: ${window.scrollY}px`;
13 | window.addEventListener('scroll', (e) => {
14 | sidenavElm.style = `--scroll-y: ${e.currentTarget.scrollY}px`;
15 | });
16 | };
17 |
18 | document.addEventListener('DOMContentLoaded', updateScrollY, false);
19 | }
20 |
21 | export default updateSidenavHeight;
22 |
--------------------------------------------------------------------------------
/src/utils/intersperse.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Return an array with the separator interspersed between each element of the input array.
3 | */
4 | export const intersperse = (arr, sep = ', ') => {
5 | if (arr.length === 0) {
6 | return [];
7 | }
8 |
9 | return arr.slice(1).reduce((xs, x) => xs.concat([sep, x]), [arr[0]]);
10 | };
11 |
--------------------------------------------------------------------------------
/src/utils/is-active-toc-node.js:
--------------------------------------------------------------------------------
1 | import { isCurrentPage } from './is-current-page';
2 | /*
3 | Provided a current page slug, a slug, and a list of node children, returns
4 | true if either the given slug matches the current page slug or one of its children
5 | has a slug matching the given page slug, and false otherwise.
6 | */
7 | export const isActiveTocNode = (currentUrl, slug, children) => {
8 | if (currentUrl === undefined) return false;
9 | if (isCurrentPage(currentUrl, slug)) return true;
10 | if (children) {
11 | return children.reduce((a, b) => a || isActiveTocNode(currentUrl, b.slug, b.children), false);
12 | }
13 | return false;
14 | };
15 |
--------------------------------------------------------------------------------
/src/utils/is-browser.js:
--------------------------------------------------------------------------------
1 | export const isBrowser = typeof window !== 'undefined';
2 |
--------------------------------------------------------------------------------
/src/utils/is-current-page.js:
--------------------------------------------------------------------------------
1 | export const isCurrentPage = (currentUrl, slug) => {
2 | const trimSlashes = (str) => str.replace(/^\/|\/$/g, '');
3 | if (!currentUrl || !slug) return false;
4 | return trimSlashes(currentUrl) === trimSlashes(slug);
5 | };
6 |
--------------------------------------------------------------------------------
/src/utils/is-offline-docs-build.js:
--------------------------------------------------------------------------------
1 | export const isOfflineDocsBuild = process.env['OFFLINE_DOCS'] === 'true';
2 |
--------------------------------------------------------------------------------
/src/utils/is-relative-url.js:
--------------------------------------------------------------------------------
1 | export const isRelativeUrl = (url) => {
2 | // Assume that external links begin with http:// or https://
3 | const absolute = /^http(s)?:\/\//.test(url) || url.startsWith('mailto:');
4 | return !absolute;
5 | };
6 |
--------------------------------------------------------------------------------
/src/utils/is-selected-toc-node.js:
--------------------------------------------------------------------------------
1 | import { isCurrentPage } from './is-current-page';
2 |
3 | /*
4 | Provided the current url and a node slug, returns true if the user is on this
5 | page for navigation and false otherwise (defined as checking the url ending)
6 | */
7 | export const isSelectedTocNode = (currentUrl, slug) => {
8 | if (currentUrl === undefined) return false;
9 | return isCurrentPage(currentUrl, slug);
10 | };
11 |
--------------------------------------------------------------------------------
/src/utils/join-class-names.js:
--------------------------------------------------------------------------------
1 | // Returns a string of joined class names. Only defined class names are included in the string
2 | // We return undefined if there are no classes to avoid returning an empty string
3 | export const joinClassNames = (...args) => args.filter((className) => !!className).join(' ') || undefined;
4 |
--------------------------------------------------------------------------------
/src/utils/normalize-path.js:
--------------------------------------------------------------------------------
1 | // Remove duplicate slashes in path string
2 | const normalizePath = (path) => path.replace(/\/+/g, `/`);
3 |
4 | module.exports.normalizePath = normalizePath;
5 |
--------------------------------------------------------------------------------
/src/utils/remove-leading-slash.js:
--------------------------------------------------------------------------------
1 | const removeLeadingSlash = (filePath) => filePath.replace(/^\/+/, '');
2 |
3 | module.exports.removeLeadingSlash = removeLeadingSlash;
4 |
--------------------------------------------------------------------------------
/src/utils/remove-nested-value.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Safely remove values from a deeply nested object.
3 | * Arguments:
4 | * - target: the target key to be deleted
5 | * - traversalKey: the target key to traverse
6 | * - arr: an array containing the objects to search for the target value
7 | */
8 | const removeNestedValue = (target, traversalKey, arr) => {
9 | if (!arr) return;
10 |
11 | arr.forEach((entry) => {
12 | if (entry.hasOwnProperty(target)) {
13 | delete entry[target];
14 | }
15 |
16 | if (entry.hasOwnProperty(traversalKey)) {
17 | const children = entry[traversalKey];
18 | if (Array.isArray(children) && children.length) {
19 | removeNestedValue(target, traversalKey, children);
20 | }
21 | }
22 | });
23 | };
24 |
25 | module.exports.removeNestedValue = removeNestedValue;
26 |
--------------------------------------------------------------------------------
/src/utils/report-analytics.js:
--------------------------------------------------------------------------------
1 | export const reportAnalytics = (eventName, data) => {
2 | if (!window || !window.segment) return;
3 | try {
4 | window.segment.track(eventName, data);
5 | } catch (err) {
6 | console.error(`Error reporting analytics: ${eventName}`, err);
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/src/utils/search-facet-constants.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Util file for keeping constants related to search server
3 | * Store query parameter names and response names here
4 | *
5 | */
6 |
7 | // query param keys
8 | export const TERM_PARAM = 'q';
9 | export const PAGE_PARAM = 'page';
10 | export const V1_SEARCH_FILTER_PARAM = 'searchProperty';
11 |
12 | // facet param keys
13 | // ie. facets.target_product>atlas>sub_product=atlas-cli
14 | // translates to (target_products = atlas, target_products.atlas.sub_product = atlas-cli)
15 | export const FACETS_KEY_PREFIX = 'facets.';
16 | export const FACETS_LEVEL_KEY = '>';
17 |
18 | export const VERSION_GROUP_ID = 'version';
19 | // single select fields will be dropdown in UI,
20 | // and remove its parent selection as child facet selection implies parent facet
21 | export const SINGLE_SELECT_FIELDS = [VERSION_GROUP_ID];
22 |
23 | export const requestHeaders = process.env['GATSBY_MARIAN_URL']?.includes('staging')
24 | ? {
25 | credentials: 'include',
26 | }
27 | : {};
28 |
--------------------------------------------------------------------------------
/src/utils/setup/construct-build-filter.js:
--------------------------------------------------------------------------------
1 | const { constructPageIdPrefix } = require('./construct-page-id-prefix');
2 |
3 | // Returns the query to be used by our Stitch/Realm function to fetch a site's documents
4 | const constructBuildFilter = ({ commitHash, patchId, ...rest }) => {
5 | const pageIdPrefix = constructPageIdPrefix(rest);
6 | return {
7 | page_id: { $regex: new RegExp(`^${pageIdPrefix}(/|$)`) },
8 | commit_hash: commitHash || { $exists: false },
9 | patch_id: patchId || { $exists: false },
10 | };
11 | };
12 |
13 | module.exports = { constructBuildFilter };
14 |
--------------------------------------------------------------------------------
/src/utils/setup/construct-page-id-prefix.js:
--------------------------------------------------------------------------------
1 | // Concatenates a site prefix to return a page id
2 | const constructPageIdPrefix = ({ project, parserUser, parserBranch }) => `${project}/${parserUser}/${parserBranch}`;
3 |
4 | module.exports = { constructPageIdPrefix };
5 |
--------------------------------------------------------------------------------
/src/utils/setup/fetch-manifest-metadata.js:
--------------------------------------------------------------------------------
1 | const AdmZip = require('adm-zip');
2 | const BSON = require('bson');
3 | const fs = require('fs');
4 |
5 | // Returns the metadata from the manifest file if provided
6 | const fetchManifestMetadata = () => {
7 | let metadata = {};
8 | if (!process.env.GATSBY_MANIFEST_PATH && process.env.GATSBY_BUILD_FROM_JSON === 'true') {
9 | // Read metadata from Gatsby Action download
10 | try {
11 | metadata = JSON.parse(fs.readFileSync('snooty-metadata.json'));
12 | return metadata;
13 | } catch (err) {
14 | console.error('No metadata was found.');
15 | return metadata;
16 | }
17 | } else if (process.env.GATSBY_MANIFEST_PATH) {
18 | const zip = new AdmZip(process.env.GATSBY_MANIFEST_PATH);
19 | const zipEntries = zip.getEntries();
20 | for (const entry of zipEntries) {
21 | if (entry.entryName === 'site.bson') {
22 | metadata = BSON.deserialize(entry.getData());
23 | }
24 | }
25 | }
26 | return metadata;
27 | };
28 |
29 | module.exports = { fetchManifestMetadata };
30 |
--------------------------------------------------------------------------------
/src/utils/setup/init-realm.js:
--------------------------------------------------------------------------------
1 | const Realm = require('realm-web');
2 | const { SNOOTY_REALM_APP_ID } = require('../../build-constants');
3 |
4 | const initRealm = async () => {
5 | // Returns an instance of an app.
6 | // If an app with the specified id hasn't been created,
7 | // a new app instance will be created.
8 | const realmClient = Realm.App.getApp(SNOOTY_REALM_APP_ID);
9 | const anonymous = await realmClient.logIn(Realm.Credentials.anonymous());
10 | return anonymous;
11 | };
12 |
13 | module.exports = { initRealm };
14 |
--------------------------------------------------------------------------------
/src/utils/setup/transform-breadcrumbs.js:
--------------------------------------------------------------------------------
1 | const transformBreadcrumbs = (breadcrumbs, slugToBreadcrumbLabel) => {
2 | Object.entries(breadcrumbs).forEach(([slug, breadcrumbList]) => {
3 | breadcrumbs[slug] = breadcrumbList.map((path) => {
4 | const title = slugToBreadcrumbLabel?.[path] || '';
5 | return {
6 | path,
7 | title,
8 | };
9 | });
10 | });
11 | };
12 |
13 | module.exports = { transformBreadcrumbs };
14 |
--------------------------------------------------------------------------------
/src/utils/sort-versioned-branches.ts:
--------------------------------------------------------------------------------
1 | import { VersionChoice } from '../components/DeprecatedVersionSelector';
2 |
3 | /**
4 | * Order versions in the correct order such that V1.10 and V1.11 are sorted before V1.9
5 | * Function to be passed into .Sort() on an array
6 | */
7 | export const sortVersions = (a: VersionChoice, b: VersionChoice) =>
8 | a.text
9 | .toString()
10 | .replace(/\d+/g, (n) => String(+n + 1000))
11 | .localeCompare(b.text.toString().replace(/\d+/g, (n) => String(+n + 1000)));
12 |
--------------------------------------------------------------------------------
/src/utils/throttle.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Limit the rate at which a function is called (for example, useful to limit invokation of a scroll event listener)
3 | */
4 | export const throttle = (func, wait) => {
5 | let args = null;
6 | let result = null;
7 | let timeout = null;
8 | let previous = 0;
9 |
10 | const later = () => {
11 | previous = Date.now();
12 | timeout = null;
13 | result = func.apply(...args);
14 | if (!timeout) {
15 | args = null;
16 | }
17 | };
18 |
19 | return (...newArgs) => {
20 | const now = Date.now();
21 | const remaining = wait - (now - previous);
22 | args = newArgs;
23 | if (remaining <= 0 || remaining > wait) {
24 | if (timeout) {
25 | window.clearTimeout(timeout);
26 | timeout = null;
27 | }
28 |
29 | previous = now;
30 | result = func(...args);
31 | if (!timeout) {
32 | args = null;
33 | }
34 | } else if (!timeout) {
35 | timeout = window.setTimeout(later, remaining);
36 | }
37 |
38 | return result;
39 | };
40 | };
41 |
--------------------------------------------------------------------------------
/src/utils/url-utils.ts:
--------------------------------------------------------------------------------
1 | import { assertTrailingSlash } from './assert-trailing-slash';
2 | import { joinUrlAndPath, DOTCOM_BASE_URL } from './base-url';
3 | import { generateVersionedPrefix } from './generate-versioned-prefix';
4 | import { localizePath } from './locale';
5 |
6 | export const getUrl = (branchUrlSlug: string, project: string, siteBasePrefix: string, slug: string) => {
7 | if (branchUrlSlug === 'legacy') {
8 | // Avoid trailing slash to ensure query param remains valid
9 | const legacyPath = localizePath(`/docs/legacy/?site=${project}`);
10 | return DOTCOM_BASE_URL + legacyPath;
11 | }
12 | const prefixWithVersion = generateVersionedPrefix(branchUrlSlug, siteBasePrefix);
13 | return assertTrailingSlash(localizePath(`${prefixWithVersion}/${slug}`));
14 | };
15 |
16 | export const getCompleteUrl = (path: string) => {
17 | return joinUrlAndPath(DOTCOM_BASE_URL, path);
18 | };
19 |
--------------------------------------------------------------------------------
/src/utils/use-changelog-data.js:
--------------------------------------------------------------------------------
1 | import { useStaticQuery, graphql } from 'gatsby';
2 |
3 | export default function useChangelogData() {
4 | const data = useStaticQuery(graphql`
5 | query ChangelogData {
6 | allChangelogData {
7 | nodes {
8 | changelogData
9 | }
10 | }
11 | }
12 | `);
13 |
14 | return data.allChangelogData.nodes[0].changelogData;
15 | }
16 |
--------------------------------------------------------------------------------
/src/utils/use-snooty-metadata.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const MetadataContext = React.createContext();
4 |
5 | export default function useSnootyMetadata() {
6 | return React.useContext(MetadataContext);
7 | }
8 |
9 | export function MetadataProvider({ children, metadata }) {
10 | return {children};
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/validate-email.js:
--------------------------------------------------------------------------------
1 | export default function validateEmail(input) {
2 | const isValidEmail = input === '' || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input);
3 | return isValidEmail;
4 | }
5 |
--------------------------------------------------------------------------------
/static/assets/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/static/assets/404.png
--------------------------------------------------------------------------------
/static/assets/cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/static/assets/cloud.png
--------------------------------------------------------------------------------
/static/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/static/assets/favicon.ico
--------------------------------------------------------------------------------
/static/assets/lightning-bolt-dark.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/static/assets/lightning-bolt.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/static/assets/meta_generic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/static/assets/meta_generic.png
--------------------------------------------------------------------------------
/static/assets/offline-asset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/static/assets/offline-asset.png
--------------------------------------------------------------------------------
/static/assets/screenshoticon-dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/assets/screenshoticon-light.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/media/icomoon.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/static/media/icomoon.eot
--------------------------------------------------------------------------------
/static/media/icomoon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/static/media/icomoon.ttf
--------------------------------------------------------------------------------
/static/media/icomoon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mongodb/snooty/12623e6f2166c882dbfbc317af3f74b21441303c/static/media/icomoon.woff
--------------------------------------------------------------------------------
/stubs/process.js:
--------------------------------------------------------------------------------
1 | const process = require('process');
2 |
3 | //monkey patch process
4 | global.process = process;
5 |
6 | module.exports =
7 | typeof global !== 'undefined' && global.process ? global.process : require('../node_modules/process/browser.js');
8 |
--------------------------------------------------------------------------------
/tests/unit/Admonition.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import Admonition from '../../src/components/Admonition';
4 |
5 | // data for this component
6 | import mockData from './data/Admonition.test.json';
7 |
8 | it('admonitions render correctly', () => {
9 | const tree = render();
10 | expect(tree.asFragment()).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/Banner.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import Banner from '../../src/components/Banner/Banner';
4 | import OfflineBanner from '../../src/components/Banner/OfflineBanner';
5 |
6 | // data for this component
7 | import mockData from './data/Banner.test.json';
8 |
9 | it('renders a Banner correctly', () => {
10 | const wrapper = render();
11 | expect(wrapper.asFragment()).toMatchSnapshot();
12 | });
13 |
14 | it('renders an offline banner correctly', () => {
15 | const wrapper = render();
16 | expect(wrapper.asFragment()).toMatchSnapshot();
17 | });
18 |
--------------------------------------------------------------------------------
/tests/unit/BlockQuote.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { mockLocation } from '../utils/mock-location';
4 | import BlockQuote from '../../src/components/BlockQuote';
5 |
6 | // data for this component
7 | import mockData from './data/BlockQuote.test.json';
8 |
9 | beforeAll(() => {
10 | mockLocation(null, `/`);
11 | });
12 |
13 | it('renders correctly', () => {
14 | const tree = render();
15 | expect(tree.asFragment()).toMatchSnapshot();
16 | });
17 |
--------------------------------------------------------------------------------
/tests/unit/Button.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { mockLocation } from '../utils/mock-location';
4 | import Button from '../../src/components/Button';
5 |
6 | // data for this component
7 | import Link from '../../src/components/Link';
8 | import mockData from './data/Button.test.json';
9 |
10 | jest.mock('../../src/components/Link');
11 |
12 | beforeAll(() => {
13 | mockLocation(null, `/`);
14 | });
15 |
16 | describe('button component', () => {
17 | beforeEach(() => {
18 | Link.mockImplementation(jest.requireActual('../../src/components/Link').default);
19 | });
20 |
21 | it('renders correctly', () => {
22 | const tree = render();
23 | expect(tree.asFragment()).toMatchSnapshot();
24 | });
25 |
26 | it('converts to a link when passed an uri', () => {
27 | Link.mockImplementation(() => this is a link
);
28 | const tree = render();
29 | expect(tree.getByText('this is a link')).toBeTruthy();
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/tests/unit/CardGroup.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { ThemeProvider } from '@emotion/react';
4 | import { mockLocation } from '../utils/mock-location';
5 | import CardGroup from '../../src/components/Card/CardGroup';
6 | import { theme } from '../../src/theme/docsTheme';
7 | // data for this component
8 | import mockData from './data/CardGroup.test.json';
9 |
10 | beforeAll(() => {
11 | mockLocation(null, `/`);
12 | });
13 |
14 | it('renders correctly', () => {
15 | const tree = render(
16 |
17 |
18 |
19 | );
20 | expect(tree.asFragment()).toMatchSnapshot();
21 | });
22 |
--------------------------------------------------------------------------------
/tests/unit/CardRef.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { ThemeProvider } from '@emotion/react';
4 | import { mockLocation } from '../utils/mock-location';
5 | import CardGroup from '../../src/components/Card/CardGroup';
6 | import { theme } from '../../src/theme/docsTheme';
7 |
8 | // data for this component
9 | import mockData from './data/CardRef.test.json';
10 |
11 | beforeAll(() => {
12 | mockLocation(null, `/`);
13 | });
14 | // Since there isn't a proper CardRef component, test that card-ref styling
15 | // is applied appropriately (i.e., only when a Card :url: is not specified)
16 | it('card correctly with and without url', () => {
17 | const tree = render(
18 |
19 |
20 |
21 | );
22 | expect(tree.asFragment()).toMatchSnapshot();
23 | });
24 |
--------------------------------------------------------------------------------
/tests/unit/Chapter.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { mockLocation } from '../utils/mock-location';
4 | import Chapter from '../../src/components/Chapters/Chapter';
5 | import mockData from './data/Chapters.test.json';
6 |
7 | beforeAll(() => {
8 | mockLocation(null, `/`);
9 | });
10 |
11 | it('renders correctly', () => {
12 | const chapterData = mockData.nodeData.children[0];
13 | const wrapper = render();
14 | expect(wrapper.asFragment()).toMatchSnapshot();
15 |
16 | // Make sure that the logic used to get the component's rendered data is correct
17 | expect(wrapper.getByText('Chapter 1')).toBeTruthy();
18 | expect(wrapper.getByText('Atlas')).toBeTruthy();
19 | expect(wrapper.container.querySelectorAll('li')).toHaveLength(3);
20 | });
21 |
--------------------------------------------------------------------------------
/tests/unit/CommunityPillLink.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import CommunityPillLink from '../../src/components/CommunityPillLink';
4 |
5 | // data for this component
6 | import mockData from './data/CommunityPillLink.test.json';
7 |
8 | it('community drivers directives render correctly', () => {
9 | const tree = render();
10 | expect(tree.asFragment()).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/DefinitionList.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import DefinitionList from '../../src/components/DefinitionList';
4 | import mockData from './data/DefinitionList.test.json';
5 |
6 | it('DefinitionList renders correctly', () => {
7 | const tree = render();
8 | expect(tree.asFragment()).toMatchSnapshot();
9 | });
10 |
--------------------------------------------------------------------------------
/tests/unit/Emphasis.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import Emphasis from '../../src/components/Emphasis';
4 |
5 | // data for this component
6 | import mockData from './data/Emphasis.test.json';
7 |
8 | it('renders correctly', () => {
9 | const tree = render();
10 | expect(tree.asFragment()).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/Field.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import Field from '../../src/components/FieldList/Field';
4 |
5 | // data for this component
6 | import mockData from './data/FieldList.test.json';
7 |
8 | it('renders correctly', () => {
9 | const tree = render();
10 | expect(tree.asFragment()).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/FieldList.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { ThemeProvider } from '@emotion/react';
4 | import FieldList from '../../src/components/FieldList';
5 | import { theme } from '../../src/theme/docsTheme';
6 | // data for this component
7 | import mockData from './data/FieldList.test.json';
8 |
9 | it('renders correctly', () => {
10 | const tree = render(
11 |
12 |
13 |
14 | );
15 | expect(tree.asFragment()).toMatchSnapshot();
16 | });
17 |
--------------------------------------------------------------------------------
/tests/unit/Figure.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import Figure from '../../src/components/Figure';
4 |
5 | // data for this component
6 | import mockData from './data/Figure.test.json';
7 | import borderData from './data/FigureBorder.test.json';
8 | import lightboxData from './data/FigureLightbox.test.json';
9 |
10 | it('renders correctly', () => {
11 | const tree = render();
12 | expect(tree.asFragment()).toMatchSnapshot();
13 | });
14 |
15 | it('renders border correctly when specified as an option', () => {
16 | const tree = render();
17 | expect(tree.asFragment()).toMatchSnapshot();
18 | });
19 |
20 | it('renders lightbox correctly when specified as an option', () => {
21 | const tree = render();
22 | expect(tree.asFragment()).toMatchSnapshot();
23 | });
24 |
--------------------------------------------------------------------------------
/tests/unit/Footnote.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import Footnote from '../../src/components/Footnote';
4 | import FootnoteContext from '../../src/components/Footnote/footnote-context';
5 |
6 | // data for this component
7 | import mockData from './data/Footnote.test.json';
8 |
9 | const mountFootnotes = (footnotes) =>
10 | render(
11 |
12 |
13 |
14 | );
15 |
16 | const mockFootnotes = { 1: { label: 1, references: ['id1'] } };
17 |
18 | it('renders correctly', () => {
19 | const footnotes = mountFootnotes(mockFootnotes);
20 | expect(footnotes.asFragment()).toMatchSnapshot();
21 | });
22 |
--------------------------------------------------------------------------------
/tests/unit/FootnoteReference.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import FootnoteReference from '../../src/components/Footnote/FootnoteReference';
4 |
5 | // data for this component
6 | import mockData from './data/FootnoteReference.test.json';
7 |
8 | it('renders correctly', () => {
9 | const tree = render();
10 | expect(tree.asFragment()).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/GuidesLandingTree.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { mockLocation } from '../utils/mock-location';
4 | import GuidesLandingTree from '../../src/components/Sidenav/GuidesLandingTree';
5 | import mockData from './data/Chapters.test.json';
6 |
7 | const getWrapper = () => {
8 | const { chapters } = mockData.metadata;
9 |
10 | return render();
11 | };
12 |
13 | beforeAll(() => {
14 | mockLocation(null, `/`);
15 | });
16 |
17 | it('renders correctly', () => {
18 | const wrapper = getWrapper();
19 | expect(wrapper.asFragment()).toMatchSnapshot();
20 | });
21 |
--------------------------------------------------------------------------------
/tests/unit/Heading.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import LeafyGreenProvider from '@leafygreen-ui/leafygreen-provider';
4 | import Heading from '../../src/components/Heading';
5 |
6 | // data for this component
7 | import mockData from './data/Heading.test.json';
8 |
9 | it('renders correctly', () => {
10 | const tree = render();
11 | expect(tree.asFragment()).toMatchSnapshot();
12 | });
13 |
14 | it('renders correctly in dark mode', () => {
15 | const tree = render(
16 |
17 |
18 |
19 | );
20 | expect(tree.asFragment()).toMatchSnapshot();
21 | });
22 |
--------------------------------------------------------------------------------
/tests/unit/Line.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import Line from '../../src/components/LineBlock/Line';
4 |
5 | // data for this component
6 | import mockData from './data/Line.test.json';
7 | import mockData2 from './data/Line-empty.test.json';
8 |
9 | it('renders correctly', () => {
10 | const tree = render();
11 | expect(tree.asFragment()).toMatchSnapshot();
12 | });
13 |
14 | it('renders an empty Line node correctly', () => {
15 | const tree = render();
16 | expect(tree.asFragment()).toMatchSnapshot();
17 | });
18 |
--------------------------------------------------------------------------------
/tests/unit/LineBlock.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import LineBlock from '../../src/components/LineBlock';
4 |
5 | // data for this component
6 | import mockData from './data/Literal.test.json';
7 |
8 | it('renders correctly', () => {
9 | const tree = render();
10 | expect(tree.asFragment()).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/List.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import List from '../../src/components/List';
4 |
5 | // data for this component
6 | import mockData from './data/List.test.json';
7 |
8 | it('List renders correctly', () => {
9 | const tree = render(
);
10 | expect(tree.asFragment()).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/Literal.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import Literal from '../../src/components/Literal';
4 |
5 | // data for this component
6 | import mockData from './data/Literal.test.json';
7 |
8 | it('renders correctly', () => {
9 | const tree = render();
10 | expect(tree.asFragment()).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/LiteralInclude.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import LiteralInclude from '../../src/components/LiteralInclude';
4 |
5 | // data for this component
6 | import mockData from './data/LiteralInclude.test.json';
7 |
8 | it('renders correctly', () => {
9 | const tree = render();
10 | expect(tree.asFragment()).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/Meta.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import { getMetaFromDirective } from '../../src/utils/get-meta-from-directive';
4 | import Meta from '../../src/components/Meta';
5 |
6 | // data for this component
7 | import { testPageNodes } from './data/MetaData';
8 |
9 | describe('Meta Tag', () => {
10 | it('does not renders a Meta correctly when the meta value is present', () => {
11 | const c = {
12 | options: {},
13 | };
14 | const wrapper = render();
15 | expect(wrapper.queryByTestId('directive-meta')).not.toBeInTheDocument();
16 | });
17 |
18 | it('renders meta tags correctly when meta data is presented', () => {
19 | const meta = getMetaFromDirective('section', testPageNodes, 'meta');
20 |
21 | meta.forEach((c) => {
22 | render();
23 | });
24 |
25 | expect(screen.getAllByTestId('directive-meta')).toHaveLength(2);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/tests/unit/Paragraph.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { mockLocation } from '../utils/mock-location';
4 | import Paragraph from '../../src/components/Paragraph';
5 |
6 | // data for this component
7 | import mockData from './data/Paragraph.test.json';
8 | import mockDataFormat from './data/Paragraph-Format.test.json';
9 |
10 | beforeAll(() => {
11 | mockLocation(null, `/`);
12 | });
13 |
14 | describe('Paragraph unit tests', () => {
15 | it('renders correctly', () => {
16 | const tree = render();
17 | expect(tree.asFragment()).toMatchSnapshot();
18 | });
19 |
20 | it('handles formatting dangling punctuation after Links and no extra multiplying on rerenders', () => {
21 | const tree = render();
22 | expect(tree.asFragment()).toMatchSnapshot();
23 | const treeRerender = render();
24 | expect(treeRerender.asFragment()).toMatchSnapshot();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/tests/unit/Procedure.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { mockLocation } from '../utils/mock-location';
4 | import Procedure from '../../src/components/Procedure';
5 |
6 | // data for this component
7 | import mockData from './data/Procedure.test.json';
8 |
9 | beforeAll(() => {
10 | mockLocation(null, `/`);
11 | });
12 |
13 | it('renders correctly', () => {
14 | const tree = render();
15 | expect(tree.asFragment()).toMatchSnapshot();
16 | });
17 |
18 | it('renders with "normal" or YAML steps styling', () => {
19 | // Add styling to mock data
20 | mockData.testSteps.options = {
21 | style: 'normal',
22 | };
23 | const tree = render();
24 | expect(tree.asFragment()).toMatchSnapshot();
25 | });
26 |
27 | it('renders steps nested in include nodes', () => {
28 | const tree = render();
29 | expect(tree.asFragment()).toMatchSnapshot();
30 | expect(tree.getAllByText(/Step/)).toHaveLength(7);
31 | });
32 |
--------------------------------------------------------------------------------
/tests/unit/Reference.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { mockLocation } from '../utils/mock-location';
4 | import Reference from '../../src/components/Reference';
5 |
6 | // data for this component
7 | import mockData from './data/Reference.test.json';
8 |
9 | beforeAll(() => {
10 | mockLocation(null, `/`);
11 | });
12 |
13 | it('renders correctly', () => {
14 | const tree = render();
15 | expect(tree.asFragment()).toMatchSnapshot();
16 | });
17 |
--------------------------------------------------------------------------------
/tests/unit/ReleaseSpecification.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import ReleaseSpecification from '../../src/components/ReleaseSpecification';
4 |
5 | // data for this component
6 | import mockData from './data/ReleaseSpecification.test.json';
7 |
8 | it('renders correctly', () => {
9 | const tree = render();
10 | expect(tree.asFragment()).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/Section.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import Section from '../../src/components/Section';
4 |
5 | // data for this component
6 | import mockData from './data/Section.test.json';
7 |
8 | it('renders correctly', () => {
9 | const tree = render();
10 | expect(tree.asFragment()).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/Step.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { mockLocation } from '../utils/mock-location';
4 | import Step from '../../src/components/Procedure/Step';
5 |
6 | // data for this component
7 | import mockData from './data/Step.test.json';
8 |
9 | beforeAll(() => {
10 | mockLocation(null, `/`);
11 | });
12 |
13 | it('renders with "connected" styling by default', () => {
14 | const tree = render();
15 | expect(tree.asFragment()).toMatchSnapshot();
16 | });
17 |
18 | it('renders with "connected" styling', () => {
19 | const tree = render();
20 | expect(tree.asFragment()).toMatchSnapshot();
21 | });
22 |
23 | it('renders with "normal" or YAML steps styling', () => {
24 | const tree = render();
25 | expect(tree.asFragment()).toMatchSnapshot();
26 | });
27 |
--------------------------------------------------------------------------------
/tests/unit/Strong.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import Strong from '../../src/components/Strong';
4 |
5 | // data for this component
6 | import mockData from './data/Strong.test.json';
7 |
8 | it('renders correctly', () => {
9 | const tree = render();
10 | expect(tree.asFragment()).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/Target.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { mockLocation } from '../utils/mock-location';
4 | import Target from '../../src/components/Target';
5 |
6 | // data for this component
7 | import mockData from './data/Target.test.json';
8 |
9 | beforeAll(() => {
10 | mockLocation(null, `/`);
11 | });
12 |
13 | it('renders correctly with a directive_argument node', () => {
14 | const tree = render();
15 | expect(tree.asFragment()).toMatchSnapshot();
16 | });
17 |
18 | it('renders correctly with no directive_argument nodes', () => {
19 | const tree = render();
20 | expect(tree.asFragment()).toMatchSnapshot();
21 | });
22 |
--------------------------------------------------------------------------------
/tests/unit/Text.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import Text from '../../src/components/Text';
4 |
5 | // data for this component
6 | import mockData from './data/Text.test.json';
7 |
8 | it('renders correctly', () => {
9 | const tree = render();
10 | expect(tree.asFragment()).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/Time.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import Time from '../../src/components/Time';
4 | import mockData from './data/Time.test.json';
5 |
6 | it('renders correctly', () => {
7 | const wrapper = render();
8 | expect(wrapper.getByText('Time required: 15 minutes')).toBeTruthy();
9 | });
10 |
--------------------------------------------------------------------------------
/tests/unit/TitleReference.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import TitleReference from '../../src/components/TitleReference';
4 |
5 | // data for this component
6 | import mockData from './data/TitleReference.test.json';
7 |
8 | it('renders correctly', () => {
9 | const tree = render();
10 | expect(tree.asFragment()).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/__mockStyles.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/tests/unit/__snapshots__/BreadcrumbSchema.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`BreadcrumbSchema returns correct structured data with parents and intermediate breadcrumbs 1`] = `
4 |
5 |
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 |
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 |
--------------------------------------------------------------------------------