-
Notifications
You must be signed in to change notification settings - Fork 101
Include programatically generated API docs - initial first step #3376
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
18a24e8
Checkpoint
borland 1e18108
API badges in the API because they look fancy
borland e0c7fca
Fix bug in apiNav generator revealed by astro update
borland e6b9ebf
More things
borland eeb55d8
Lint, Spell check and Linkinator need to ignore pages in the api folder
borland dc678bc
api-examples plugin converted to satteri
borland 8e99612
Address review concerns
borland 06761a6
explicit :endpoint directive
borland 0172139
Remove index and readme, they shouldn't be here
borland a1fb952
Fix dead reference to Related component
borland File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "ignores": ["src/pages/docs/api/**"] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| --- | ||
| import { accelerator } from '@lib/accelerator'; | ||
| import { Translations, Lang } from '@util/Languages'; | ||
| import { apiMenu } from '@lib/apiNavigation'; | ||
|
|
||
| const stats = new accelerator.statistics('components/ApiNavigation.astro'); | ||
| stats.start(); | ||
|
|
||
| type Props = { | ||
| lang: string; | ||
| headings: { depth: number; slug: string; text: string }[]; | ||
| // One entry per endpoint, in heading order, left on the frontmatter by | ||
| // plugins/satteri-api-examples.js from the `:endpoint` directive under each | ||
| // heading. Sections without one — and pages that never reach that plugin — | ||
| // simply have none. | ||
| apiMethods?: | ||
| | { text: string; method: string | null; deprecated?: boolean }[] | ||
| | null; | ||
| }; | ||
| const { lang, headings, apiMethods } = Astro.props satisfies Props; | ||
|
|
||
| // The four the API badges in api.css cover. Title case because the badge is an | ||
| // image to a screen reader, and reads as a word rather than as shouting. | ||
| const METHOD_LABELS: Record<string, string> = { | ||
| get: 'Get', | ||
| post: 'Post', | ||
| put: 'Put', | ||
| delete: 'Delete', | ||
| }; | ||
|
|
||
| // Astro escapes a brace in heading text as `${`, so a title that is a route — | ||
| // "GET /api/{spaceId}/environments" — survives being read as an expression. The | ||
| // methods are collected before that happens, so the two are compared unescaped. | ||
| function headingText(text: string): string { | ||
| return text.replace(/\$\{/g, '{').trim(); | ||
| } | ||
|
|
||
| const _ = Lang(lang); | ||
|
|
||
| // Only the page the reader is on lists its endpoints. Expanding every page | ||
| // would put thousands of anchors in the markup of all ~100 of them, and it is | ||
| // the rule the site nav follows too: the branch you are inside is the one that | ||
| // opens. | ||
| const currentPath = Astro.url.pathname.replace(/\/$/, ''); | ||
|
|
||
| // An endpoint per H2, which is the first level of content on a generated API | ||
| // page. | ||
| // | ||
| // The methods are in the same order, an endpoint each, so they pair off by | ||
| // position — two endpoints on a page can share a title, which is what rules out | ||
| // pairing them by it. The title is checked all the same: if the two lists have | ||
| // drifted apart, the nav goes without badges rather than hanging the wrong one | ||
| // on a row. | ||
| const endpoints = (headings ?? []) | ||
| .filter((heading) => heading.depth === 2) | ||
| .map((heading, index) => { | ||
| const endpoint = apiMethods?.[index]; | ||
| const matches = endpoint?.text === headingText(heading.text); | ||
| const method = matches ? (endpoint.method ?? '') : ''; | ||
|
|
||
| return { | ||
| ...heading, | ||
| method: method in METHOD_LABELS ? method : null, | ||
| deprecated: matches && endpoint.deprecated === true, | ||
| }; | ||
| }); | ||
|
|
||
| const pages = apiMenu().map((page) => ({ | ||
| ...page, | ||
| isCurrent: page.url.replace(/\/$/, '') === currentPath, | ||
| })); | ||
|
|
||
| stats.stop(); | ||
| --- | ||
|
|
||
| <nav | ||
| class="site-nav" | ||
| id="site-nav" | ||
| aria-label={_(Translations.aria.site_navigation)} | ||
| > | ||
| <h2 class="site-nav-title">{_(Translations.navigation.title)}</h2> | ||
| <ul class="site-nav__list"> | ||
| { | ||
| pages.map((page) => | ||
| page.isCurrent && endpoints.length > 0 ? ( | ||
| <li class="site-nav__list-item"> | ||
| <details class="site-nav__group" open> | ||
| {/* The summary is the page the reader is already on, so it | ||
| labels the group rather than linking back to itself. */} | ||
| <summary class="site-nav__link" aria-current="page"> | ||
| <span class="site-nav__label">{page.title}</span> | ||
| </summary> | ||
| <ul class="site-nav__list"> | ||
| {endpoints.map((heading) => ( | ||
| <li class="site-nav__list-item"> | ||
| <a | ||
| class:list={[ | ||
| 'site-nav__link', | ||
| 'site-nav__link--heading', | ||
| heading.deprecated && 'site-nav__link--deprecated', | ||
| ]} | ||
| href={`#${heading.slug}`} | ||
| > | ||
| {/* The badge is the method: an icon in the nav, where | ||
| there is no room for the word beside the title. */} | ||
| {heading.method && ( | ||
| <span | ||
| class={`api-${heading.method} api-icon-only`} | ||
| role="img" | ||
| aria-label={METHOD_LABELS[heading.method]} | ||
| /> | ||
| )} | ||
| <span class="site-nav__label">{heading.text}</span> | ||
| </a> | ||
| </li> | ||
| ))} | ||
| </ul> | ||
| </details> | ||
| </li> | ||
| ) : ( | ||
| <li class="site-nav__list-item"> | ||
| <a | ||
| class="site-nav__link" | ||
| href={accelerator.urlFormatter.formatAddress(page.url)} | ||
| aria-current={page.isCurrent ? 'page' : null} | ||
| > | ||
| <span class="site-nav__label">{page.title}</span> | ||
| </a> | ||
| </li> | ||
| ) | ||
| ) | ||
| } | ||
| </ul> | ||
| </nav> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| --- | ||
| import { accelerator } from '@lib/accelerator'; | ||
| import { PostFiltering } from 'astro-accelerator-utils'; | ||
| import type { Frontmatter as OriginalFrontmatter } from 'astro-accelerator-utils/types/Frontmatter'; | ||
| import { SITE } from '@config'; | ||
| import { buildApiCrumbs } from '@lib/apiNavigation'; | ||
| import type { Crumb } from '@util/breadcrumbs'; | ||
|
|
||
| // Theme components | ||
| import Head from '@components/HtmlHead.astro'; | ||
| import SkipLinks from '@components/SkipLinks.astro'; | ||
| import Breadcrumbs from '@components/Breadcrumbs.astro'; | ||
| import Authors from '@components/Authors.astro'; | ||
| import Taxonomy from '@components/Taxonomy.astro'; | ||
|
|
||
| // Custom components | ||
| import ApiNavigation from '../components/ApiNavigation.astro'; | ||
| import ArticleHeader from '../components/ArticleHeader.astro'; | ||
| import Feedback from '../components/Feedback.astro'; | ||
| import Header from '../components/Header.astro'; | ||
| import Plausible from 'src/components/Plausible.astro'; | ||
| import Footer from 'src/components/Footer.astro'; | ||
| import DocsSearch from '../components/DocsSearch.astro'; | ||
|
|
||
| type Props = { | ||
| // `apiMethods` is not written by hand: plugins/satteri-api-examples.js leaves | ||
| // it on the frontmatter for the left nav, an entry per endpoint. | ||
| frontmatter: OriginalFrontmatter & { | ||
| apiMethods?: { text: string; method: string | null }[]; | ||
| }; | ||
| headings: { depth: number; slug: string; text: string }[]; | ||
| breadcrumbs?: Crumb[] | null; | ||
| }; | ||
| const { frontmatter, headings, breadcrumbs } = Astro.props satisfies Props; | ||
|
|
||
| // buildApiCrumbs, not buildCrumbs: the section has no page of its own at | ||
| // /docs/api for the generic walk to find, so it splices the crumb in. | ||
| const crumbs = buildApiCrumbs(Astro.url, breadcrumbs); | ||
|
|
||
| const lang = frontmatter.lang ?? SITE.default.lang; | ||
| const textDirection = frontmatter.dir ?? SITE.default.dir; | ||
|
|
||
| // Logic | ||
| const title = await accelerator.markdown.getInlineHtmlFrom( | ||
| frontmatter.title ?? SITE.title | ||
| ); | ||
|
|
||
| const subtitle = frontmatter.subtitle | ||
| ? await accelerator.markdown.getInlineHtmlFrom(frontmatter.subtitle) | ||
| : null; | ||
|
|
||
| const site_url = SITE.url; | ||
| const site_features = SITE.featureFlags; | ||
| const search = | ||
| accelerator.posts.all().filter(PostFiltering.isSearch).shift() ?? null; | ||
| const searchUrl = search && accelerator.urlFormatter.formatAddress(search.url); | ||
| const isSearchPage = | ||
| accelerator.urlFormatter.formatAddress(Astro.url.pathname) === searchUrl; | ||
|
|
||
| const showSearch = !isSearchPage; | ||
|
|
||
| // The footer prints this; JSON-LD carries it as dateModified. | ||
| const lastUpdated = frontmatter.modDate ?? frontmatter.pubDate ?? null; | ||
| --- | ||
|
|
||
| <html dir={textDirection} lang={lang} class="initial" data-theme="light"> | ||
| <Head | ||
| frontmatter={frontmatter} | ||
| headings={headings} | ||
| lang={lang} | ||
| crumbs={crumbs} | ||
| /> | ||
| <body> | ||
| <SkipLinks frontmatter={frontmatter} headings={headings} lang={lang} /> | ||
| <Header | ||
| frontmatter={frontmatter} | ||
| headings={headings} | ||
| lang={lang} | ||
| showSearch={showSearch} | ||
| /> | ||
| <!-- The article spans the table of contents column as well as its own: the | ||
| left nav lists this page's endpoints, so there is no table of contents | ||
| to show, and the width is what the examples column is built out of. --> | ||
| <div class="content-group content-group--api"> | ||
| <main id="site-main"> | ||
| <Breadcrumbs lang={lang} crumbs={crumbs} /> | ||
| <article> | ||
| <ArticleHeader lang={lang} subtitle={subtitle} title={title} /> | ||
| <div class="page-actions"> | ||
| { | ||
| /* Copy as markdown temporarily disabled until we can get it working with the API docs. Deliberately no "Edit on GitHub" because these are generated and should not be hand edited */ | ||
| } | ||
|
borland marked this conversation as resolved.
|
||
| </div> | ||
| <div class="page-content anim-show-parent"> | ||
| <slot /> | ||
| <Authors frontmatter={frontmatter} lang={lang} /> | ||
| <Taxonomy frontmatter={frontmatter} lang={lang} /> | ||
| </div> | ||
| </article> | ||
| <Feedback frontmatter={frontmatter} lang={lang} /> | ||
| </main> | ||
| <ApiNavigation | ||
| headings={headings} | ||
| lang={lang} | ||
| apiMethods={frontmatter.apiMethods} | ||
| /> | ||
| <Footer lang={lang} lastUpdated={lastUpdated} /> | ||
| </div> | ||
| { | ||
| /* The overlay the header's search field opens. Same single instance as | ||
| Default.astro, and not behind `showSearch` for the same reason. */ | ||
| } | ||
| <DocsSearch /> | ||
| <script define:vars={{ site_url, site_features }}> | ||
| window.site_url = site_url; | ||
| window.site_features = site_features; | ||
| </script> | ||
| <script> | ||
| import '../scripts/main.js'; | ||
| </script> | ||
| <Plausible /> | ||
| </body> | ||
| </html> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.