Rename Markdown util getHeaders() to getHeadings() (#4031)

* Renamed getHeaders() to getHeadings(), according to RFC #208.

* chore: update changeset

* fix: expose MarkdownHeading type from `astro`

Co-authored-by: Félix Sanz <me@felixsanz.com>
Co-authored-by: Nate Moore <nate@astro.build>
This commit is contained in:
Nate Moore 2022-07-23 17:23:15 -05:00 committed by GitHub
parent 1215e731b8
commit 6e27a5fdc2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 64 additions and 42 deletions

View file

@ -0,0 +1,6 @@
---
'astro': patch
'@astrojs/markdown-remark': minor
---
**BREAKING** Renamed Markdown utility function `getHeaders()` to `getHeadings()`.

View file

@ -4,14 +4,14 @@ import TableOfContents from "../RightSidebar/TableOfContents.tsx";
const { content, githubEditUrl } = Astro.props; const { content, githubEditUrl } = Astro.props;
const title = content.title; const title = content.title;
const headers = content.astro.headers; const headings = content.astro.headings;
--- ---
<article id="article" class="content"> <article id="article" class="content">
<section class="main-section"> <section class="main-section">
<h1 class="content-title" id="overview">{title}</h1> <h1 class="content-title" id="overview">{title}</h1>
<nav class="block sm:hidden"> <nav class="block sm:hidden">
<TableOfContents client:media="(max-width: 50em)" {headers} /> <TableOfContents client:media="(max-width: 50em)" {headings} />
</nav> </nav>
<slot /> <slot />
</section> </section>

View file

@ -8,7 +8,7 @@ const showMoreSection = CONFIG.COMMUNITY_INVITE_URL || editHref;
{showMoreSection && <h2 class="heading">More</h2>} {showMoreSection && <h2 class="heading">More</h2>}
<ul> <ul>
{editHref && ( {editHref && (
<li class={`header-link depth-2`}> <li class={`heading-link depth-2`}>
<a class="edit-on-github" href={editHref} target="_blank"> <a class="edit-on-github" href={editHref} target="_blank">
<svg <svg
aria-hidden="true" aria-hidden="true"
@ -32,7 +32,7 @@ const showMoreSection = CONFIG.COMMUNITY_INVITE_URL || editHref;
</li> </li>
)} )}
{CONFIG.COMMUNITY_INVITE_URL && ( {CONFIG.COMMUNITY_INVITE_URL && (
<li class={`header-link depth-2`}> <li class={`heading-link depth-2`}>
<a href={CONFIG.COMMUNITY_INVITE_URL} target="_blank"> <a href={CONFIG.COMMUNITY_INVITE_URL} target="_blank">
<svg <svg
aria-hidden="true" aria-hidden="true"

View file

@ -2,12 +2,12 @@
import TableOfContents from "./TableOfContents.tsx"; import TableOfContents from "./TableOfContents.tsx";
import MoreMenu from "./MoreMenu.astro"; import MoreMenu from "./MoreMenu.astro";
const { content, githubEditUrl } = Astro.props; const { content, githubEditUrl } = Astro.props;
const headers = content.astro.headers; const headings = content.astro.headings;
--- ---
<nav class="sidebar-nav" aria-labelledby="grid-right"> <nav class="sidebar-nav" aria-labelledby="grid-right">
<div class="sidebar-nav-inner"> <div class="sidebar-nav-inner">
<TableOfContents client:media="(min-width: 50em)" {headers} /> <TableOfContents client:media="(min-width: 50em)" {headings} />
<MoreMenu editHref={githubEditUrl} /> <MoreMenu editHref={githubEditUrl} />
</div> </div>
</nav> </nav>

View file

@ -1,11 +1,11 @@
import type { FunctionalComponent } from 'preact'; import type { FunctionalComponent } from 'preact';
import { h, Fragment } from 'preact'; import { h, Fragment } from 'preact';
import { useState, useEffect, useRef } from 'preact/hooks'; import { useState, useEffect, useRef } from 'preact/hooks';
import { MarkdownHeading } from 'astro';
const TableOfContents: FunctionalComponent<{ headers: any[] }> = ({ headers = [] }) => { const TableOfContents: FunctionalComponent<{ headings: MarkdownHeading[] }> = ({ headings = [] }) => {
const itemOffsets = useRef([]); const itemOffsets = useRef([]);
const [activeId, setActiveId] = useState<string>(undefined); const [activeId, setActiveId] = useState<string>(undefined);
useEffect(() => { useEffect(() => {
const getItemOffsets = () => { const getItemOffsets = () => {
const titles = document.querySelectorAll('article :is(h1, h2, h3, h4)'); const titles = document.querySelectorAll('article :is(h1, h2, h3, h4)');
@ -27,18 +27,18 @@ const TableOfContents: FunctionalComponent<{ headers: any[] }> = ({ headers = []
<> <>
<h2 class="heading">On this page</h2> <h2 class="heading">On this page</h2>
<ul> <ul>
<li class={`header-link depth-2 ${activeId === 'overview' ? 'active' : ''}`.trim()}> <li class={`heading-link depth-2 ${activeId === 'overview' ? 'active' : ''}`.trim()}>
<a href="#overview">Overview</a> <a href="#overview">Overview</a>
</li> </li>
{headers {headings
.filter(({ depth }) => depth > 1 && depth < 4) .filter(({ depth }) => depth > 1 && depth < 4)
.map((header) => ( .map((heading) => (
<li <li
class={`header-link depth-${header.depth} ${ class={`heading-link depth-${heading.depth} ${
activeId === header.slug ? 'active' : '' activeId === heading.slug ? 'active' : ''
}`.trim()} }`.trim()}
> >
<a href={`#${header.slug}`}>{header.text}</a> <a href={`#${heading.slug}`}>{heading.text}</a>
</li> </li>
))} ))}
</ul> </ul>

View file

@ -311,42 +311,42 @@ h2.heading {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.header-link { .heading-link {
font-size: 1rem; font-size: 1rem;
padding: 0.1rem 0 0.1rem 1rem; padding: 0.1rem 0 0.1rem 1rem;
border-left: 4px solid var(--theme-divider); border-left: 4px solid var(--theme-divider);
} }
.header-link:hover, .heading-link:hover,
.header-link:focus { .heading-link:focus {
border-left-color: var(--theme-accent); border-left-color: var(--theme-accent);
color: var(--theme-accent); color: var(--theme-accent);
} }
.header-link:focus-within { .heading-link:focus-within {
color: var(--theme-text-light); color: var(--theme-text-light);
border-left-color: hsla(var(--color-gray-40), 1); border-left-color: hsla(var(--color-gray-40), 1);
} }
.header-link svg { .heading-link svg {
opacity: 0.6; opacity: 0.6;
} }
.header-link:hover svg { .heading-link:hover svg {
opacity: 0.8; opacity: 0.8;
} }
.header-link a { .heading-link a {
display: inline-flex; display: inline-flex;
gap: 0.5em; gap: 0.5em;
width: 100%; width: 100%;
padding: 0.15em 0 0.15em 0; padding: 0.15em 0 0.15em 0;
} }
.header-link.depth-3 { .heading-link.depth-3 {
padding-left: 2rem; padding-left: 2rem;
} }
.header-link.depth-4 { .heading-link.depth-4 {
padding-left: 3rem; padding-left: 3rem;
} }
.header-link a { .heading-link a {
font: inherit; font: inherit;
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;

View file

@ -18,7 +18,9 @@ declare module '*.md' {
export const frontmatter: MD['frontmatter']; export const frontmatter: MD['frontmatter'];
export const file: MD['file']; export const file: MD['file'];
export const url: MD['url']; export const url: MD['url'];
export const getHeaders: MD['getHeaders']; export const getHeadings: MD['getHeadings'];
/** @deprecated Renamed to `getHeadings()` */
export const getHeaders: () => void;
export const Content: MD['Content']; export const Content: MD['Content'];
export const rawContent: MD['rawContent']; export const rawContent: MD['rawContent'];
export const compiledContent: MD['compiledContent']; export const compiledContent: MD['compiledContent'];

View file

@ -1,5 +1,5 @@
import type { import type {
MarkdownHeader, MarkdownHeading,
MarkdownMetadata, MarkdownMetadata,
MarkdownRenderingResult, MarkdownRenderingResult,
RehypePlugins, RehypePlugins,
@ -16,6 +16,14 @@ import type { AstroConfigSchema } from '../core/config';
import type { ViteConfigWithSSR } from '../core/create-vite'; import type { ViteConfigWithSSR } from '../core/create-vite';
import type { AstroComponentFactory, Metadata } from '../runtime/server'; import type { AstroComponentFactory, Metadata } from '../runtime/server';
export type { SSRManifest } from '../core/app/types'; export type { SSRManifest } from '../core/app/types';
export type {
MarkdownHeading,
MarkdownMetadata,
MarkdownRenderingResult,
RehypePlugins,
RemarkPlugins,
ShikiConfig,
} from '@astrojs/markdown-remark';
export interface AstroBuiltinProps { export interface AstroBuiltinProps {
'client:load'?: boolean; 'client:load'?: boolean;
@ -783,7 +791,9 @@ export interface MarkdownInstance<T extends Record<string, any>> {
rawContent(): string; rawContent(): string;
/** Markdown file compiled to valid Astro syntax. Queryable with most HTML parsing libraries */ /** Markdown file compiled to valid Astro syntax. Queryable with most HTML parsing libraries */
compiledContent(): Promise<string>; compiledContent(): Promise<string>;
getHeaders(): Promise<MarkdownHeader[]>; getHeadings(): Promise<MarkdownHeading[]>;
/** @deprecated Renamed to `getHeadings()` */
getHeaders(): void;
default: () => Promise<{ default: () => Promise<{
metadata: MarkdownMetadata; metadata: MarkdownMetadata;
frontmatter: MarkdownContent<T>; frontmatter: MarkdownContent<T>;

View file

@ -123,8 +123,12 @@ export default function markdown({ config }: AstroPluginOptions): Plugin {
return load().then((m) => m.default(...args)); return load().then((m) => m.default(...args));
} }
Content.isAstroComponentFactory = true; Content.isAstroComponentFactory = true;
export function getHeadings() {
return load().then((m) => m.metadata.headings);
}
export function getHeaders() { export function getHeaders() {
return load().then((m) => m.metadata.headers); console.warn('getHeaders() have been deprecated. Use getHeadings() function instead.');
return load().then((m) => m.metadata.headings);
};`, };`,
map: null, map: null,
}; };

View file

@ -1,7 +1,7 @@
import type { MarkdownRenderingOptions, MarkdownRenderingResult } from './types'; import type { MarkdownRenderingOptions, MarkdownRenderingResult } from './types';
import { loadPlugins } from './load-plugins.js'; import { loadPlugins } from './load-plugins.js';
import createCollectHeaders from './rehype-collect-headers.js'; import createCollectHeadings from './rehype-collect-headings.js';
import rehypeEscape from './rehype-escape.js'; import rehypeEscape from './rehype-escape.js';
import rehypeExpressions from './rehype-expressions.js'; import rehypeExpressions from './rehype-expressions.js';
import rehypeIslands from './rehype-islands.js'; import rehypeIslands from './rehype-islands.js';
@ -41,7 +41,7 @@ export async function renderMarkdown(
} = opts; } = opts;
const input = new VFile({ value: content, path: fileURL }); const input = new VFile({ value: content, path: fileURL });
const scopedClassName = opts.$?.scopedClassName; const scopedClassName = opts.$?.scopedClassName;
const { headers, rehypeCollectHeaders } = createCollectHeaders(); const { headings, rehypeCollectHeadings } = createCollectHeadings();
let parser = unified() let parser = unified()
.use(markdown) .use(markdown)
@ -94,8 +94,8 @@ export async function renderMarkdown(
parser parser
.use( .use(
isAstroFlavoredMd isAstroFlavoredMd
? [rehypeJsx, rehypeExpressions, rehypeEscape, rehypeIslands, rehypeCollectHeaders] ? [rehypeJsx, rehypeExpressions, rehypeEscape, rehypeIslands, rehypeCollectHeadings]
: [rehypeCollectHeaders, rehypeRaw] : [rehypeCollectHeadings, rehypeRaw]
) )
.use(rehypeStringify, { allowDangerousHtml: true }); .use(rehypeStringify, { allowDangerousHtml: true });
@ -113,7 +113,7 @@ export async function renderMarkdown(
} }
return { return {
metadata: { headers, source: content, html: result.toString() }, metadata: { headings, source: content, html: result.toString() },
code: result.toString(), code: result.toString(),
}; };
} }

View file

@ -2,13 +2,13 @@ import Slugger from 'github-slugger';
import { toHtml } from 'hast-util-to-html'; import { toHtml } from 'hast-util-to-html';
import { visit } from 'unist-util-visit'; import { visit } from 'unist-util-visit';
import type { MarkdownHeader, RehypePlugin } from './types.js'; import type { MarkdownHeading, RehypePlugin } from './types.js';
export default function createCollectHeaders() { export default function createCollectHeadings() {
const headers: MarkdownHeader[] = []; const headings: MarkdownHeading[] = [];
const slugger = new Slugger(); const slugger = new Slugger();
function rehypeCollectHeaders(): ReturnType<RehypePlugin> { function rehypeCollectHeadings(): ReturnType<RehypePlugin> {
return function (tree) { return function (tree) {
visit(tree, (node) => { visit(tree, (node) => {
if (node.type !== 'element') return; if (node.type !== 'element') return;
@ -61,13 +61,13 @@ export default function createCollectHeaders() {
} }
} }
headers.push({ depth, slug: node.properties.id, text }); headings.push({ depth, slug: node.properties.id, text });
}); });
}; };
} }
return { return {
headers, headings,
rehypeCollectHeaders, rehypeCollectHeadings,
}; };
} }

View file

@ -44,14 +44,14 @@ export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
isAstroFlavoredMd?: boolean; isAstroFlavoredMd?: boolean;
} }
export interface MarkdownHeader { export interface MarkdownHeading {
depth: number; depth: number;
slug: string; slug: string;
text: string; text: string;
} }
export interface MarkdownMetadata { export interface MarkdownMetadata {
headers: MarkdownHeader[]; headings: MarkdownHeading[];
source: string; source: string;
html: string; html: string;
} }