From 7f17d106a33c3f6cf4fb46f953a4fc376eb8fe5e Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Tue, 13 Dec 2022 16:00:23 +0000 Subject: [PATCH 01/19] - --- website-v3/src/components/Markdown.tsx | 2 ++ website-v3/src/components/icons.tsx | 16 ++++++++++++ website-v3/src/components/mdx/CodeBlock.tsx | 10 +++++++ website-v3/src/context.ts | 2 ++ website-v3/src/layouts/Footer.astro | 26 +++++++++++++++++++ website-v3/src/layouts/Header.astro | 11 +++++++- website-v3/src/layouts/Root.astro | 4 ++- .../[owner]/[repository]/[...path].astro | 6 ++++- 8 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 website-v3/src/components/mdx/CodeBlock.tsx create mode 100644 website-v3/src/layouts/Footer.astro diff --git a/website-v3/src/components/Markdown.tsx b/website-v3/src/components/Markdown.tsx index 50210284..a6a2e877 100644 --- a/website-v3/src/components/Markdown.tsx +++ b/website-v3/src/components/Markdown.tsx @@ -2,6 +2,7 @@ import { getMDXComponent } from 'mdx-bundler/client/index.js'; import context from 'src/context'; import Link from './Link'; +import CodeBlock, { CodeBlockProps } from './mdx/CodeBlock'; import Image from './mdx/Image'; import YouTube from './mdx/YouTube'; import Vimeo from './mdx/Vimeo'; @@ -19,6 +20,7 @@ const Markdown: React.FC = () => { a: props => , img: props => , table: props => , + pre: props => , code: props => , h1: props => , h2: props => , diff --git a/website-v3/src/components/icons.tsx b/website-v3/src/components/icons.tsx index 73c5ef3a..4534997f 100644 --- a/website-v3/src/components/icons.tsx +++ b/website-v3/src/components/icons.tsx @@ -45,3 +45,19 @@ export const MoonIcon = () => ( /> ); + +export const PencilIcon = () => ( + + + +); diff --git a/website-v3/src/components/mdx/CodeBlock.tsx b/website-v3/src/components/mdx/CodeBlock.tsx new file mode 100644 index 00000000..bca880b4 --- /dev/null +++ b/website-v3/src/components/mdx/CodeBlock.tsx @@ -0,0 +1,10 @@ +export interface CodeBlockProps extends React.HTMLProps { + raw: string; + html: string; +} + +const CodeBlock: React.FC = props => { + return
; +}; + +export default CodeBlock; diff --git a/website-v3/src/context.ts b/website-v3/src/context.ts index b76b6ea7..fe219cb7 100644 --- a/website-v3/src/context.ts +++ b/website-v3/src/context.ts @@ -24,6 +24,8 @@ export type Context = { baseBranch: GetBundleResponseSuccess['baseBranch']; // The source of the page contents source: GetBundleResponseSuccess['source']; + // The theme of the site + theme: 'light' | 'dark' | undefined; }; const store = map(); diff --git a/website-v3/src/layouts/Footer.astro b/website-v3/src/layouts/Footer.astro new file mode 100644 index 00000000..3763956b --- /dev/null +++ b/website-v3/src/layouts/Footer.astro @@ -0,0 +1,26 @@ +--- +import { PencilIcon } from '@components/icons'; +--- + + diff --git a/website-v3/src/layouts/Header.astro b/website-v3/src/layouts/Header.astro index 2802fb3e..90c37ac2 100644 --- a/website-v3/src/layouts/Header.astro +++ b/website-v3/src/layouts/Header.astro @@ -23,10 +23,19 @@ const logoDark = getImagePath(config.logoDark); Logo + ) + } + { + !!logoDark && ( + ) } - {!!logoDark && } { !logoLight && !logoDark && ( {config.name || `${owner}/${repository}`} diff --git a/website-v3/src/layouts/Root.astro b/website-v3/src/layouts/Root.astro index 28399528..ddcdaf19 100644 --- a/website-v3/src/layouts/Root.astro +++ b/website-v3/src/layouts/Root.astro @@ -1,4 +1,6 @@ --- +import context from 'src/context'; + export interface Props { // Sets the tag of the page title?: string; @@ -9,7 +11,7 @@ export interface Props { } const { title = '', description = '', image = '', author = '' } = Astro.props; -const theme = Astro.cookies.get('theme').value; +const { theme } = context.get(); --- <html diff --git a/website-v3/src/pages/[owner]/[repository]/[...path].astro b/website-v3/src/pages/[owner]/[repository]/[...path].astro index 1c68feda..e4852d16 100644 --- a/website-v3/src/pages/[owner]/[repository]/[...path].astro +++ b/website-v3/src/pages/[owner]/[repository]/[...path].astro @@ -1,6 +1,7 @@ --- import Root from '@layouts/Root.astro'; import Header from '@layouts/Header.astro'; +import Footer from '@layouts/Footer.astro'; import Sidebar from '@layouts/Sidebar.astro'; import Navigation from '@layouts/Navigation.astro'; @@ -63,6 +64,7 @@ if (redirect && isExternalLink(redirect)) { } // Set the theme color +const theme = Astro.cookies.get('theme').value; const config = bundle!.config; const domain = domains.find(([, repository]) => repository === `${owner}/${repository}`)?.at(0); @@ -79,6 +81,7 @@ context.set({ domain, baseBranch: bundle!.baseBranch, source: bundle!.source, + theme: theme ? (theme === 'dark' ? 'dark' : 'light') : undefined, }); // TODO: Handle status codes @@ -102,8 +105,9 @@ context.set({ class="relative flex-grow mx-auto xl:-ml-12 overflow-auto xl:pr-1 xl:pl-12 max-w-3xl xl:max-w-[47rem] text-slate-500 dark:text-slate-400" > <Markdown /> + <Footer /> </div> - <div class="hidden xl:flex flex-none z-10 pl-10 w-[19rem]"> + <div class="hidden xl:flex flex-none z-10 pl-10 w-[17rem]"> <nav class="fixed text-sm leading-6 w-[16.5rem] h-[calc(100%-8rem)] overflow-y-auto"> <Navigation /> </nav> From 1e7e1dec4d45653ed118b817e9de3db4c1d07055 Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Tue, 13 Dec 2022 16:05:32 +0000 Subject: [PATCH 02/19] edit url --- website-v3/src/context.ts | 2 ++ website-v3/src/layouts/Footer.astro | 5 ++++- website-v3/src/layouts/Sidebar.astro | 2 +- website-v3/src/pages/[owner]/[repository]/[...path].astro | 6 +++++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/website-v3/src/context.ts b/website-v3/src/context.ts index fe219cb7..a28c7f67 100644 --- a/website-v3/src/context.ts +++ b/website-v3/src/context.ts @@ -10,6 +10,8 @@ export type Context = { ref: string | undefined; // The relative path of the page (e.g. /getting-started) relativePath: string; + // The path to the page on github + githubPath: string; // The docs.json config file config: BundleConfig; // Frontmatter of the current page diff --git a/website-v3/src/layouts/Footer.astro b/website-v3/src/layouts/Footer.astro index 3763956b..ab42ce1e 100644 --- a/website-v3/src/layouts/Footer.astro +++ b/website-v3/src/layouts/Footer.astro @@ -1,5 +1,8 @@ --- +import context from 'src/context'; import { PencilIcon } from '@components/icons'; + +const { githubPath } = context.get(); --- <footer class="border-t dark:border-slate-800/80 py-12 mt-12 text-sm flex items-center"> @@ -16,7 +19,7 @@ import { PencilIcon } from '@components/icons'; <div class="flex-1 flex justify-end"> <a class="opacity-50 hover:opacity-75 transition inline-flex items-center gap-1" - href="/todo" + href={githubPath} target="_blank" > <span class="w-4 h-4"><PencilIcon /></span> diff --git a/website-v3/src/layouts/Sidebar.astro b/website-v3/src/layouts/Sidebar.astro index 06ca9897..1cd9bb7f 100644 --- a/website-v3/src/layouts/Sidebar.astro +++ b/website-v3/src/layouts/Sidebar.astro @@ -28,7 +28,7 @@ const sidebar = context.get().config.sidebar || []; return ( <li class="my-8"> <h5 class="mb-3 block font-semibold tracking-wide">{title}</h5> - <ul class="space-y-2 border-l border-slate-700"> + <ul class="space-y-2 border-l dark:border-slate-700"> {urlOrChildren.map(([title, url]) => ( <li> <Link diff --git a/website-v3/src/pages/[owner]/[repository]/[...path].astro b/website-v3/src/pages/[owner]/[repository]/[...path].astro index e4852d16..1ac90047 100644 --- a/website-v3/src/pages/[owner]/[repository]/[...path].astro +++ b/website-v3/src/pages/[owner]/[repository]/[...path].astro @@ -66,6 +66,7 @@ if (redirect && isExternalLink(redirect)) { // Set the theme color const theme = Astro.cookies.get('theme').value; const config = bundle!.config; +const source = bundle!.source; const domain = domains.find(([, repository]) => repository === `${owner}/${repository}`)?.at(0); // Set the context store. @@ -73,6 +74,9 @@ context.set({ owner, repository, relativePath: ensureLeadingSlash(path || '/'), + githubPath: `https://github.com/${owner}/${repository}/edit/${ + source.type === 'branch' && source.ref !== 'HEAD' ? source.ref : bundle!.baseBranch + }/docs/${path || 'index'}.mdx`, ref, config, frontmatter: bundle!.frontmatter, @@ -80,7 +84,7 @@ context.set({ headings: bundle!.headings, domain, baseBranch: bundle!.baseBranch, - source: bundle!.source, + source, theme: theme ? (theme === 'dark' ? 'dark' : 'light') : undefined, }); From 5bbc2dc9d27e44f6b2e51354ce1830a9247d75c3 Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Tue, 13 Dec 2022 16:08:13 +0000 Subject: [PATCH 03/19] fix theme --- website-v3/src/layouts/Root.astro | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/website-v3/src/layouts/Root.astro b/website-v3/src/layouts/Root.astro index ddcdaf19..2710f967 100644 --- a/website-v3/src/layouts/Root.astro +++ b/website-v3/src/layouts/Root.astro @@ -12,13 +12,10 @@ export interface Props { const { title = '', description = '', image = '', author = '' } = Astro.props; const { theme } = context.get(); +console.log('theme', theme); --- -<html - class:list={{ - dark: !!theme && theme === 'dark', - }} -> +<html data-theme={!!theme && theme === 'dark' ? 'dark' : ''}> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> From ae512249b34df818135d5ba4207c4c66eb43bb0a Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Tue, 13 Dec 2022 16:08:50 +0000 Subject: [PATCH 04/19] - --- website-v3/src/layouts/Root.astro | 1 - 1 file changed, 1 deletion(-) diff --git a/website-v3/src/layouts/Root.astro b/website-v3/src/layouts/Root.astro index 2710f967..1216a06f 100644 --- a/website-v3/src/layouts/Root.astro +++ b/website-v3/src/layouts/Root.astro @@ -12,7 +12,6 @@ export interface Props { const { title = '', description = '', image = '', author = '' } = Astro.props; const { theme } = context.get(); -console.log('theme', theme); --- <html data-theme={!!theme && theme === 'dark' ? 'dark' : ''}> From b9a4bc450edc10ccd90b0c5073d4e113ed8251c5 Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Tue, 13 Dec 2022 16:18:29 +0000 Subject: [PATCH 05/19] - --- website-v3/src/components/Markdown.tsx | 5 ++-- website-v3/src/components/mdx/Tabs.tsx | 33 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 website-v3/src/components/mdx/Tabs.tsx diff --git a/website-v3/src/components/Markdown.tsx b/website-v3/src/components/Markdown.tsx index a6a2e877..14e6d1f3 100644 --- a/website-v3/src/components/Markdown.tsx +++ b/website-v3/src/components/Markdown.tsx @@ -8,6 +8,7 @@ import YouTube from './mdx/YouTube'; import Vimeo from './mdx/Vimeo'; import Table from './mdx/Table'; import Heading from './mdx/Heading'; +import Tabs, { TabItem } from './mdx/Tabs'; const Markdown: React.FC = () => { const code = context.get().code; @@ -30,8 +31,8 @@ const Markdown: React.FC = () => { h6: props => <Heading {...props} type="h6" />, Heading, Tweet: props => <div>TODO</div>, - Tabs: props => <div>TODO</div>, - TabItem: props => <div>TODO</div>, + Tabs: props => <Tabs {...props} />, + TabItem: props => <TabItem {...props} />, Image, YouTube, Vimeo, diff --git a/website-v3/src/components/mdx/Tabs.tsx b/website-v3/src/components/mdx/Tabs.tsx new file mode 100644 index 00000000..d2c3cbef --- /dev/null +++ b/website-v3/src/components/mdx/Tabs.tsx @@ -0,0 +1,33 @@ +type TabsProps = { + groupId?: string; + defaultValue?: string; + values: TabValue[]; + children: React.ReactElement; + className?: string; +}; + +type TabValue = { + label: string; + value: string; +}; + +type TabItemElement = React.ReactElement<{ + children: React.ReactNode; + mdxType: string; + parentName: string; + value: string; +}>; + +const Tabs: React.FC<TabsProps> = props => { + if (props.values.length === 0) { + return <div />; + } + + return <div>TODO TABS</div>; +}; + +export const TabItem: React.FC<{ value: string }> = props => { + return <div>TODO TAB ITEM</div>; +}; + +export default Tabs; From 5254bc632dcac6474808844fc0decd9d8afaf928 Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Tue, 13 Dec 2022 16:35:48 +0000 Subject: [PATCH 06/19] - --- .../[owner]/[repository]/[...path].astro | 182 ++++++++++-------- 1 file changed, 97 insertions(+), 85 deletions(-) diff --git a/website-v3/src/pages/[owner]/[repository]/[...path].astro b/website-v3/src/pages/[owner]/[repository]/[...path].astro index 1ac90047..7bcad9a7 100644 --- a/website-v3/src/pages/[owner]/[repository]/[...path].astro +++ b/website-v3/src/pages/[owner]/[repository]/[...path].astro @@ -17,106 +17,118 @@ import { isExternalLink, replaceMoustacheVariables, ensureLeadingSlash } from 's import Theme from '@components/Theme.astro'; import domains from '../../../../../domains.json'; +let status: GetBundleResponse['status'] = 404; + let { owner, repository, path } = Astro.params; let ref: string | undefined; -// TODO keep URL but show 404 page -if (!owner || !repository) { - return Astro.redirect('/404'); -} +if (owner && repository) { + // Check if the repo includes a ref (invertase/foo~bar) + if (repository.includes('~')) { + [repository, ref] = repository.split('~'); + } -// Check if the repo includes a ref (invertase/foo~bar) -if (repository.includes('~')) { - [repository, ref] = repository.split('~'); -} + let bundle: GetBundleResponse['bundle']; -let status: GetBundleResponse['status']; -let bundle: GetBundleResponse['bundle']; + try { + const response = await getBundle({ + owner, + repository, + ref, + path, + }); -try { - const response = await getBundle({ - owner, - repository, - ref, - path, - }); + status = response.status; + bundle = response.bundle; + } catch (e) { + console.error(e); + } - status = response.status; - bundle = response.bundle; -} catch (e) { - console.error(e); -} + // Set the status code for the request + Astro.response.status = status!; -// Set the status code for the request -Astro.response.status = status!; + if ('statusCode' in bundle!) { + Astro.response.status = bundle!.statusCode; + } else { + // Handle a frontmatter redirect + const redirect = bundle!.frontmatter.redirect; + if (redirect && isExternalLink(redirect)) { + return Astro.redirect(redirect); + } else if (redirect) { + return Astro.redirect(`/${owner}/${repository}${redirect}`); + } -if ('statusCode' in bundle!) { - // TODO handle 404 - return Astro.redirect('/404'); -} + // Set the theme color + const theme = Astro.cookies.get('theme').value; + const config = bundle!.config; + const source = bundle!.source; + const domain = domains.find(([, repository]) => repository === `${owner}/${repository}`)?.at(0); -// Handle a frontmatter redirect -const redirect = bundle!.frontmatter.redirect; -if (redirect && isExternalLink(redirect)) { - return Astro.redirect(redirect); -} else if (redirect) { - return Astro.redirect(`/${owner}/${repository}${redirect}`); + // Set the context store. + context.set({ + owner, + repository, + relativePath: ensureLeadingSlash(path || '/'), + githubPath: `https://github.com/${owner}/${repository}/edit/${ + source.type === 'branch' && source.ref !== 'HEAD' ? source.ref : bundle!.baseBranch + }/docs/${path || 'index'}.mdx`, + ref, + config, + frontmatter: bundle!.frontmatter, + code: replaceMoustacheVariables(config.variables ?? {}, bundle!.code), + headings: bundle!.headings, + domain, + baseBranch: bundle!.baseBranch, + source, + theme: theme ? (theme === 'dark' ? 'dark' : 'light') : undefined, + }); + } } -// Set the theme color -const theme = Astro.cookies.get('theme').value; -const config = bundle!.config; -const source = bundle!.source; -const domain = domains.find(([, repository]) => repository === `${owner}/${repository}`)?.at(0); - -// Set the context store. -context.set({ - owner, - repository, - relativePath: ensureLeadingSlash(path || '/'), - githubPath: `https://github.com/${owner}/${repository}/edit/${ - source.type === 'branch' && source.ref !== 'HEAD' ? source.ref : bundle!.baseBranch - }/docs/${path || 'index'}.mdx`, - ref, - config, - frontmatter: bundle!.frontmatter, - code: replaceMoustacheVariables(config.variables ?? {}, bundle!.code), - headings: bundle!.headings, - domain, - baseBranch: bundle!.baseBranch, - source, - theme: theme ? (theme === 'dark' ? 'dark' : 'light') : undefined, -}); +let state: 'success' | 'error' | 'not-found'; +switch (status) { + case 200: + state = 'success'; + break; + case 404: + state = 'not-found'; + break; + default: + state = 'error'; + break; +} // TODO: Handle status codes --- -<Root> - <Meta slot="head" /> - <Theme slot="head" fallback="#00bcd4" /> - <Scripts slot="head" /> - <Links slot="head" /> - <Header /> - <section class="max-w-8xl mx-auto px-4 sm:px-6 md:px-8"> - <div - class="hidden lg:block fixed z-20 top-16 bottom-0 left-[max(0px,calc(50%-46rem))] right-auto w-[19.5rem] pb-10 px-8 overflow-y-auto" - > - <Sidebar /> - </div> - <div class="lg:pl-[20rem]"> - <div class="flex flex-row pt-9 gap-12 items-stretch"> - <div - class="relative flex-grow mx-auto xl:-ml-12 overflow-auto xl:pr-1 xl:pl-12 max-w-3xl xl:max-w-[47rem] text-slate-500 dark:text-slate-400" - > - <Markdown /> - <Footer /> +{state === 'not-found' && <div>404</div>} +{state === 'error' && <div>500</div>} +{ + state === 'success' && ( + <Root> + <Meta slot="head" /> + <Theme slot="head" fallback="#00bcd4" /> + <Scripts slot="head" /> + <Links slot="head" /> + <Header /> + <section class="max-w-8xl mx-auto px-4 sm:px-6 md:px-8"> + <div class="fixed top-16 bottom-0 left-[max(0px,calc(50%-46rem))] right-auto z-20 hidden w-[19.5rem] overflow-y-auto px-8 pb-10 lg:block"> + <Sidebar /> </div> - <div class="hidden xl:flex flex-none z-10 pl-10 w-[17rem]"> - <nav class="fixed text-sm leading-6 w-[16.5rem] h-[calc(100%-8rem)] overflow-y-auto"> - <Navigation /> - </nav> + <div class="lg:pl-[20rem]"> + <div class="flex flex-row items-stretch gap-12 pt-9"> + <div class="relative mx-auto max-w-3xl flex-grow overflow-auto text-slate-500 dark:text-slate-400 xl:-ml-12 xl:max-w-[47rem] xl:pr-1 xl:pl-12"> + <Markdown /> + <Footer /> + </div> + <div class="z-10 hidden w-[17rem] flex-none pl-10 xl:flex"> + <nav class="fixed h-[calc(100%-8rem)] w-[16.5rem] overflow-y-auto text-sm leading-6"> + <Navigation /> + </nav> + </div> + </div> </div> - </div> - </div> - </section> -</Root> + </section> + </Root> + ) +} From a7999ce1eb2ab5a9e2b47706e102bbc2c97afdeb Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Wed, 14 Dec 2022 14:29:50 +0000 Subject: [PATCH 07/19] tab sync support --- website-v3/src/components/Scripts.astro | 64 +++++++++++++- website-v3/src/components/mdx/Tabs.tsx | 87 +++++++++++++++---- website-v3/src/context.ts | 2 + .../[owner]/[repository]/[...path].astro | 10 +++ website-v3/src/pages/api/tabs-sync.ts | 51 +++++++++++ 5 files changed, 194 insertions(+), 20 deletions(-) create mode 100644 website-v3/src/pages/api/tabs-sync.ts diff --git a/website-v3/src/components/Scripts.astro b/website-v3/src/components/Scripts.astro index c601bc21..19e869e9 100644 --- a/website-v3/src/components/Scripts.astro +++ b/website-v3/src/components/Scripts.astro @@ -1,9 +1,69 @@ --- import context from 'src/context'; -const { domain, config } = context.get(); +const ctx = context.get(); +const { config, domain } = ctx; --- +<script is:inline define:vars={{ ctx }}> + window.docs_page = { ...ctx }; +</script> + +<script> + import type { Context } from 'src/context'; + const { owner, repository, domain } = (window as any).docs_page as Context; + + const tabs = document.querySelectorAll('div[data-tab-group]'); + + const getButtons = (el: Element) => el.querySelectorAll('button[data-tab-group-button]'); + const getPanes = (el: Element) => el.querySelectorAll('div[data-tab-group-pane]'); + + const setButtonState = (button: Element, id: string) => { + const active = (button.getAttribute('data-tab-group-button-id') === id).toString(); + button.setAttribute('aria-selected', active); + }; + + const setPaneState = (pane: Element, id: string) => { + if (pane.getAttribute('data-tab-group-pane-id') === id) { + pane.classList.remove('hidden'); + } else { + pane.classList.add('hidden'); + } + }; + + for (const tab of tabs) { + const groupId = tab.getAttribute('data-tab-group-id'); + const groups = groupId ? document.querySelectorAll(`div[data-tab-group-id="${groupId}"]`) : []; + + const buttons = getButtons(tab); + const panes = getPanes(tab); + + for (const button of buttons) { + const buttonId = button.getAttribute('data-tab-group-button-id')!; + + button.addEventListener('click', () => { + if (groupId) { + fetch('/api/tabs-sync', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ owner, repository, domain, groupId, buttonId }), + }); + } + + for (const button of buttons) setButtonState(button, buttonId); + for (const pane of panes) setPaneState(pane, buttonId); + + for (const group of groups) { + const buttons = getButtons(group); + const panes = getPanes(group); + + for (const button of buttons) setButtonState(button, buttonId); + for (const pane of panes) setPaneState(pane, buttonId); + } + }); + } + } +</script> { config.googleAnalytics && ( <script async src={`https://www.googletagmanager.com/gtag/js?id=${config.googleAnalytics}`} /> @@ -42,4 +102,4 @@ const { domain, config } = context.get(); config.plausibleAnalytics && domain && ( <script slot="head" defer data-domain={domain} src="https://plausible.io/js/plausible.js" /> ) -} \ No newline at end of file +} diff --git a/website-v3/src/components/mdx/Tabs.tsx b/website-v3/src/components/mdx/Tabs.tsx index d2c3cbef..3e085c26 100644 --- a/website-v3/src/components/mdx/Tabs.tsx +++ b/website-v3/src/components/mdx/Tabs.tsx @@ -1,33 +1,84 @@ +import React, { isValidElement, PropsWithChildren } from 'react'; +import cx from 'classnames'; +import context from 'src/context'; + type TabsProps = { groupId?: string; defaultValue?: string; - values: TabValue[]; + values: { + label: string; + value: string; + }[]; children: React.ReactElement; - className?: string; -}; - -type TabValue = { - label: string; - value: string; }; -type TabItemElement = React.ReactElement<{ - children: React.ReactNode; - mdxType: string; - parentName: string; - value: string; -}>; - const Tabs: React.FC<TabsProps> = props => { - if (props.values.length === 0) { + const ctx = context.get(); + const { children, values, groupId, defaultValue } = props; + + if (values.length === 0) { return <div />; } - return <div>TODO TABS</div>; + const tabs = React.Children.map(children, child => { + if (isValidElement(child)) { + const el = child as React.ReactElement; + if (values.find(({ value }) => value === el.props.value)) { + return child; + } + } + + return null; + }).filter(Boolean); + + // Get the server rendered active state. + let active = ctx.tabs[groupId ?? ''] || defaultValue || values[0].value; + + // Sanity check the active state (e.g. incase it's old or invalid) + if (!values.find(({ value }) => value === active)) { + active = values[0].value; + } + + return ( + <div data-tab-group data-tab-group-id={groupId}> + <div className="flex items-center gap-6 border-b-2 dark:border-slate-600/80"> + {props.values.map(value => ( + <button + key={value.value} + data-tab-group-button + data-tab-group-button-id={value.value} + aria-selected={value.value === active} + className={ + 'aria-selected:border-docs-theme relative top-[2px] whitespace-nowrap border-b-2 border-transparent py-2 font-semibold tracking-wide opacity-75 transition hover:opacity-100 aria-selected:opacity-100' + } + > + {value.label} + </button> + ))} + </div> + <div className="my-3"> + {tabs.map((tab: React.ReactElement, index) => { + return ( + <div + data-tab-group-pane + data-tab-group-pane-id={values[index].value} + key={tab.props.value} + data-tab={tab.props.value} + className={cx({ + hidden: tab.props.value !== active, + })} + > + {tab} + </div> + ); + })} + </div> + </div> + ); }; -export const TabItem: React.FC<{ value: string }> = props => { - return <div>TODO TAB ITEM</div>; +export const TabItem: React.FC<PropsWithChildren> = props => { + return <>{props.children}</>; }; export default Tabs; diff --git a/website-v3/src/context.ts b/website-v3/src/context.ts index a28c7f67..7c43790f 100644 --- a/website-v3/src/context.ts +++ b/website-v3/src/context.ts @@ -28,6 +28,8 @@ export type Context = { source: GetBundleResponseSuccess['source']; // The theme of the site theme: 'light' | 'dark' | undefined; + // Any syncronized tabs (via groupId). + tabs: Record<string, string>; }; const store = map<Context>(); diff --git a/website-v3/src/pages/[owner]/[repository]/[...path].astro b/website-v3/src/pages/[owner]/[repository]/[...path].astro index 7bcad9a7..23b9596a 100644 --- a/website-v3/src/pages/[owner]/[repository]/[...path].astro +++ b/website-v3/src/pages/[owner]/[repository]/[...path].astro @@ -60,6 +60,15 @@ if (owner && repository) { // Set the theme color const theme = Astro.cookies.get('theme').value; + + // Get any syncronized tabs + let tabs = {}; + try { + tabs = Astro.cookies.get('tabs').json(); + } catch { + // noop + } + const config = bundle!.config; const source = bundle!.source; const domain = domains.find(([, repository]) => repository === `${owner}/${repository}`)?.at(0); @@ -81,6 +90,7 @@ if (owner && repository) { baseBranch: bundle!.baseBranch, source, theme: theme ? (theme === 'dark' ? 'dark' : 'light') : undefined, + tabs, }); } } diff --git a/website-v3/src/pages/api/tabs-sync.ts b/website-v3/src/pages/api/tabs-sync.ts new file mode 100644 index 00000000..53c11739 --- /dev/null +++ b/website-v3/src/pages/api/tabs-sync.ts @@ -0,0 +1,51 @@ +import type { APIRoute } from 'astro'; +import { z } from 'zod'; +import cookie from 'cookie'; + +const $Request = z.object({ + owner: z.string(), + repository: z.string(), + domain: z.string().optional(), + groupId: z.string(), + buttonId: z.string(), +}); + +function safeJsonParse(value: string) { + try { + return JSON.parse(value); + } catch { + return {}; + } +} + +export const post: APIRoute = async function ({ request }) { + try { + const { owner, repository, domain, groupId, buttonId } = $Request.parse(await request.json()); + const { tabs } = cookie.parse(request.headers.get('cookie') ?? ''); + + // Set an expiry for 10 years + const expires = new Date(); + expires.setFullYear(expires.getFullYear() + 10); + + const existing = safeJsonParse(tabs ?? `{}`); + existing[groupId] = buttonId; + + return new Response(null, { + status: 200, + headers: new Headers({ + 'Set-Cookie': cookie.serialize('tabs', JSON.stringify(existing), { + expires: expires, + // If its a domain, set the cookie to the root path, otherwise set it to the owner/repository + path: domain ? '/' : `/${owner}/${repository}`, + // The cookie needs to be sent to the server to merge. + httpOnly: false, + }), + }), + }); + } catch (e) { + console.error(e); + return new Response(null, { + status: 400, + }); + } +}; From 3d8f2135ffb402dc1edbad3b3669c91883a05b61 Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Wed, 14 Dec 2022 15:00:50 +0000 Subject: [PATCH 08/19] inline theme script --- website-v3/src/components/ThemeToggle.astro | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/website-v3/src/components/ThemeToggle.astro b/website-v3/src/components/ThemeToggle.astro index fb3c8a41..db89c1d7 100644 --- a/website-v3/src/components/ThemeToggle.astro +++ b/website-v3/src/components/ThemeToggle.astro @@ -1,21 +1,20 @@ --- -import context from 'src/context'; import { MoonIcon, SunIcon } from './icons'; - -const { owner, repository, domain } = context.get(); --- <button data-theme-toggle class="relative w-6 h-6"> <span data-theme-type="dark" class="hidden"><MoonIcon /></span> <span data-theme-type="light" class="hidden"><SunIcon /></span> </button> -<script is:inline define:vars={{ owner, repository, domain }}> +<script> + import type { Context } from 'src/context'; + const { owner, repository, domain } = (window as any).docs_page as Context; const html = document.documentElement; const isDark = () => html.getAttribute('data-theme') === 'dark'; - const toggle = document.querySelector('button[data-theme-toggle]'); - const light = toggle.querySelector('span[data-theme-type="light"]'); - const dark = toggle.querySelector('span[data-theme-type="dark"]'); + const toggle = document.querySelector('button[data-theme-toggle]')!; + const light = toggle.querySelector('span[data-theme-type="light"]') as HTMLSpanElement; + const dark = toggle.querySelector('span[data-theme-type="dark"]') as HTMLSpanElement; function toggleElements() { if (isDark()) { From 0ee70cc4dfad271da12b156077e2e0a0651e006c Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Wed, 14 Dec 2022 15:42:11 +0000 Subject: [PATCH 09/19] copy code button --- website-v3/src/components/Scripts.astro | 15 ++++++ website-v3/src/components/Search.astro | 51 +++++++++------------ website-v3/src/components/mdx/CodeBlock.tsx | 13 +++++- website-v3/src/layouts/Header.astro | 2 +- 4 files changed, 49 insertions(+), 32 deletions(-) diff --git a/website-v3/src/components/Scripts.astro b/website-v3/src/components/Scripts.astro index 19e869e9..66b90116 100644 --- a/website-v3/src/components/Scripts.astro +++ b/website-v3/src/components/Scripts.astro @@ -9,6 +9,21 @@ const { config, domain } = ctx; window.docs_page = { ...ctx }; </script> +<script> + const copy = document.querySelectorAll('[data-copy]'); + + for (const button of copy) { + button.addEventListener('click', () => { + button.innerHTML = 'Copied'; + navigator.clipboard.writeText(button.getAttribute('data-copy-value') || ''); + + setTimeout(() => { + button.innerHTML = 'Copy'; + }, 1500); + }); + } +</script> + <script> import type { Context } from 'src/context'; const { owner, repository, domain } = (window as any).docs_page as Context; diff --git a/website-v3/src/components/Search.astro b/website-v3/src/components/Search.astro index d688986f..195ada83 100644 --- a/website-v3/src/components/Search.astro +++ b/website-v3/src/components/Search.astro @@ -1,18 +1,8 @@ --- import { MagnifyingGlassIcon } from './icons'; - -type Props = { - appId: string; - apiKey: string; - indexName: string; -}; - -const { appId, apiKey, indexName } = Astro.props; - -// TODO: improve styling --- -<div data-docsearch data-appId={appId} data-apiKey={apiKey} data-indexName={indexName}></div> +<div data-docsearch></div> <button data-docsearch-override class="w-full bg-gradient-to-b from-gray-100 to-gray-100 dark:from-zinc-800/80 dark:to-zinc-800/40 flex items-center rounded px-4 py-2 border dark:border-zinc-800 hover:border-zinc-700/30 dark:hover:border-zinc-700/50 gap-2 text-sm text-gray-500 dark:text-white/70 dark:hover:text-white/90 transition" @@ -24,23 +14,24 @@ const { appId, apiKey, indexName } = Astro.props; <style is:global></style> <script> import docsearch from '@docsearch/js'; - - const element = document.querySelector('div[data-docsearch]') as HTMLDivElement; - const override = document.querySelector('button[data-docsearch-override]') as HTMLButtonElement; - - docsearch({ - container: element, - appId: element.dataset.appid!, - indexName: element.dataset.indexname!, - apiKey: element.dataset.apikey!, - }); - - // Once triggered, get the native docsearch button - const button = element.querySelector('button.DocSearch') as HTMLButtonElement; - - // Hide it - button.style.display = 'none'; - - // Then attach our own button to the native one - override.addEventListener('click', () => button.click()); + import type { Context } from 'src/context'; + + const { config } = (window as any).docs_page as Context; + + if (config.docsearch) { + const element = document.querySelector('div[data-docsearch]') as HTMLDivElement; + const override = document.querySelector('button[data-docsearch-override]') as HTMLButtonElement; + + docsearch({ + container: element, + ...config.docsearch, + }); + + // Once triggered, get the native docsearch button and hide it. + const button = element.querySelector('button.DocSearch') as HTMLButtonElement; + button.style.display = 'none'; + + // Then attach our own button to the native one + override.addEventListener('click', () => button.click()); + } </script> diff --git a/website-v3/src/components/mdx/CodeBlock.tsx b/website-v3/src/components/mdx/CodeBlock.tsx index bca880b4..aa7c586b 100644 --- a/website-v3/src/components/mdx/CodeBlock.tsx +++ b/website-v3/src/components/mdx/CodeBlock.tsx @@ -4,7 +4,18 @@ export interface CodeBlockProps extends React.HTMLProps<HTMLPreElement> { } const CodeBlock: React.FC<CodeBlockProps> = props => { - return <div dangerouslySetInnerHTML={{ __html: props.html }}></div>; + return ( + <div className="group relative"> + <button + data-copy + data-copy-value={props.raw} + className="absolute top-2 right-2 rounded bg-black px-3 py-1 font-mono text-xs opacity-0 transition hover:bg-black/50 group-hover:opacity-100 data-[copied=true]:[&>span]:hidden" + > + Copy + </button> + <div dangerouslySetInnerHTML={{ __html: props.html }}></div> + </div> + ); }; export default CodeBlock; diff --git a/website-v3/src/layouts/Header.astro b/website-v3/src/layouts/Header.astro index 90c37ac2..f695917b 100644 --- a/website-v3/src/layouts/Header.astro +++ b/website-v3/src/layouts/Header.astro @@ -48,7 +48,7 @@ const logoDark = getImagePath(config.logoDark); 'lg:w-64 xl:w-80': !!config.docsearch, }} > - {!!config.docsearch && <Search {...config.docsearch} />} + {!!config.docsearch && <Search />} </div> <div class="flex-1 flex items-center justify-end"> <ThemeToggle /> From 293093d795391ae9db9068b02043853e3ea57510 Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Wed, 14 Dec 2022 16:36:42 +0000 Subject: [PATCH 10/19] tweet support --- website-v3/src/components/Markdown.tsx | 3 +- website-v3/src/components/Scripts.astro | 38 +++++++++++++++++++++++++ website-v3/src/components/mdx/Tweet.tsx | 27 ++++++++++++++++++ website-v3/src/layouts/Footer.astro | 4 +-- 4 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 website-v3/src/components/mdx/Tweet.tsx diff --git a/website-v3/src/components/Markdown.tsx b/website-v3/src/components/Markdown.tsx index 14e6d1f3..8e2ae599 100644 --- a/website-v3/src/components/Markdown.tsx +++ b/website-v3/src/components/Markdown.tsx @@ -8,6 +8,7 @@ import YouTube from './mdx/YouTube'; import Vimeo from './mdx/Vimeo'; import Table from './mdx/Table'; import Heading from './mdx/Heading'; +import Tweet from './mdx/Tweet'; import Tabs, { TabItem } from './mdx/Tabs'; const Markdown: React.FC = () => { @@ -30,7 +31,7 @@ const Markdown: React.FC = () => { h5: props => <Heading {...props} type="h5" />, h6: props => <Heading {...props} type="h6" />, Heading, - Tweet: props => <div>TODO</div>, + Tweet: props => <Tweet id={props.id} />, Tabs: props => <Tabs {...props} />, TabItem: props => <TabItem {...props} />, Image, diff --git a/website-v3/src/components/Scripts.astro b/website-v3/src/components/Scripts.astro index 66b90116..0cd4af9b 100644 --- a/website-v3/src/components/Scripts.astro +++ b/website-v3/src/components/Scripts.astro @@ -24,6 +24,44 @@ const { config, domain } = ctx; } </script> +<script> + declare global { + interface Window { + twttr: { + widgets: { + createTweet: (tweetId: string, el: HTMLElement, options?: any) => Promise<void>; + }; + }; + } + } + + import type { Context } from 'src/context'; + const { theme } = (window as any).docs_page as Context; + + const tweets = document.querySelectorAll('div[data-tweet]'); + + if (tweets.length) { + const script = document.createElement('script'); + script.src = 'https://platform.twitter.com/widgets.js'; + + script.onload = () => { + for (const tweet of tweets) { + const id = tweet.getAttribute('data-tweet-id')!; + window.twttr.widgets.createTweet(id, tweet as HTMLElement, { + dnt: true, + cards: tweet.getAttribute('data-tweet-cards') === 'true' ? '' : 'hidden', + conversation: tweet.getAttribute('data-tweet-conversation') === 'true' ? '' : 'hidden', + theme: + theme || + (document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'), + }); + } + }; + + document.head.appendChild(script); + } +</script> + <script> import type { Context } from 'src/context'; const { owner, repository, domain } = (window as any).docs_page as Context; diff --git a/website-v3/src/components/mdx/Tweet.tsx b/website-v3/src/components/mdx/Tweet.tsx new file mode 100644 index 00000000..28ea1edb --- /dev/null +++ b/website-v3/src/components/mdx/Tweet.tsx @@ -0,0 +1,27 @@ +type TweetProps = { + id: string; + // String is legacy, boolean is new. + cards: string | boolean; + conversation: string | boolean; +}; + +const Tweet: React.FC<TweetProps> = props => { + if (!props.id) { + return <div />; + } + + const cards = props.cards ? props.cards : false; + const conversation = props.conversation ? props.conversation : true; + + return ( + <div + className="my-6 mx-auto [&>div]:mx-auto [&>div]:overflow-hidden [&>div]:rounded-[14px] [&>div]:border-[1px] [&>div]:border-solid [&>div]:border-gray-200 [&>div]:shadow [&>div]:hover:shadow-lg [&>div]:dark:border-zinc-800" + data-tweet + data-tweet-id={props.id} + data-tweet-cards={cards === 'hidden' ? 'false' : `${cards}`} + data-tweet-conversation={conversation === 'none' ? 'false' : `${conversation}`} + /> + ); +}; + +export default Tweet; diff --git a/website-v3/src/layouts/Footer.astro b/website-v3/src/layouts/Footer.astro index ab42ce1e..e4225a90 100644 --- a/website-v3/src/layouts/Footer.astro +++ b/website-v3/src/layouts/Footer.astro @@ -8,7 +8,7 @@ const { githubPath } = context.get(); <footer class="border-t dark:border-slate-800/80 py-12 mt-12 text-sm flex items-center"> <div> <a - class="opacity-50 hover:opacity-75 transition" + class="opacity-75 hover:opacity-100 transition" href="https://docs.page" target="_blank" rel="noopener" @@ -18,7 +18,7 @@ const { githubPath } = context.get(); </div> <div class="flex-1 flex justify-end"> <a - class="opacity-50 hover:opacity-75 transition inline-flex items-center gap-1" + class="opacity-75 hover:opacity-100 transition inline-flex items-center gap-1" href={githubPath} target="_blank" > From 9844cac60d5aff02cb2b33872964e4b42e8f141e Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Wed, 14 Dec 2022 16:37:31 +0000 Subject: [PATCH 11/19] - --- website-v3/src/components/Markdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website-v3/src/components/Markdown.tsx b/website-v3/src/components/Markdown.tsx index 8e2ae599..23be7a42 100644 --- a/website-v3/src/components/Markdown.tsx +++ b/website-v3/src/components/Markdown.tsx @@ -31,7 +31,7 @@ const Markdown: React.FC = () => { h5: props => <Heading {...props} type="h5" />, h6: props => <Heading {...props} type="h6" />, Heading, - Tweet: props => <Tweet id={props.id} />, + Tweet: props => <Tweet {...props} />, Tabs: props => <Tabs {...props} />, TabItem: props => <TabItem {...props} />, Image, From b81fdb9fbea5956a45d2038e1b5aad89a7e115fe Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Wed, 14 Dec 2022 16:39:13 +0000 Subject: [PATCH 12/19] - --- website-v3/src/layouts/Header.astro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website-v3/src/layouts/Header.astro b/website-v3/src/layouts/Header.astro index f695917b..976d9f46 100644 --- a/website-v3/src/layouts/Header.astro +++ b/website-v3/src/layouts/Header.astro @@ -38,7 +38,7 @@ const logoDark = getImagePath(config.logoDark); } { !logoLight && !logoDark && ( - <span class="text-2xl font-bold">{config.name || `${owner}/${repository}`}</span> + <span class="text-xl font-bold">{config.name || `${owner}/${repository}`}</span> ) } </Link> From d572f898af6c0d4abfd0cf253ebfe690d0f344b0 Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Wed, 14 Dec 2022 17:07:52 +0000 Subject: [PATCH 13/19] image zoom --- package-lock.json | 266 +++++++++++------- website-v3/package.json | 3 +- website-v3/src/components/Scripts.astro | 17 ++ website-v3/src/components/Styles.astro | 14 + website-v3/src/components/mdx/Image.tsx | 8 +- .../[owner]/[repository]/[...path].astro | 2 + 6 files changed, 207 insertions(+), 103 deletions(-) create mode 100644 website-v3/src/components/Styles.astro diff --git a/package-lock.json b/package-lock.json index 69bbe249..9a72e47d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -561,9 +561,9 @@ } }, "node_modules/@astrojs/compiler": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-0.30.1.tgz", - "integrity": "sha512-sH3hvGA9JR5eE2bHG3sr5QjaJl5ER9EmmZzSwBHkMPoBj0XU7XzQjSGoyCbIML6sSaelJEPnfsr9NsypvKOecg==" + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-0.31.0.tgz", + "integrity": "sha512-V8/Re/wXgXTZzpfWs4KZBLU5dRhnO6kSd4e3vObGuj+HFGHjaD11wws1zvaC9cXLQyQsM5CSrGagFGYlRZKvVQ==" }, "node_modules/@astrojs/language-server": { "version": "0.28.3", @@ -635,15 +635,24 @@ } }, "node_modules/@astrojs/node": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-3.1.0.tgz", - "integrity": "sha512-teKbDU3u1BIhh3pIVtLsBF/Hxr6NioInfnVFLlPp9afFFlVhakO3N+nR8fPkXrpNXGP3AxQmk+ROI4YHvhIIFw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-3.1.1.tgz", + "integrity": "sha512-EWfsvCtOGvN1ugpxhiW4sr0xD8xUGltFOcU/ysOHdVpP6WXWQUTZbYbqDo+fgYhYHksLIcBLxg8vLi+gBQMA1Q==", + "dev": true, "dependencies": { "@astrojs/webapi": "^1.1.1", "send": "^0.18.0" }, "peerDependencies": { - "astro": "^1.6.10" + "astro": "^1.6.15" + } + }, + "node_modules/@astrojs/prefetch": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@astrojs/prefetch/-/prefetch-0.1.1.tgz", + "integrity": "sha512-+XZcjivPHTo9thQshDWKnnORHJ1dE6odIzhmKHac2ptwla092eohkiJw7Y31+PYmuJkc3Jp+3AawuCvfqCnQ2Q==", + "dependencies": { + "throttles": "^1.0.1" } }, "node_modules/@astrojs/prism": { @@ -708,9 +717,9 @@ } }, "node_modules/@astrojs/vercel": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@astrojs/vercel/-/vercel-2.3.5.tgz", - "integrity": "sha512-kjbIKsbaHX41llEV2Qzj9/R8SfYjy/gNG6CdVATuQM1GGBayEvJYn7pU75YalrvLTrqfMKywS+oFPW9fwJl8yA==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@astrojs/vercel/-/vercel-2.3.6.tgz", + "integrity": "sha512-OAfu1d6pgzhKIi48rAKemfhNO6s8ZF/38zSRqGZwl9HnY5qfacccqWmoVeUJ9lSqB/rljxA5Xzh3s+jMqyd9mA==", "dev": true, "dependencies": { "@astrojs/webapi": "^1.1.1", @@ -6587,11 +6596,11 @@ } }, "node_modules/astro": { - "version": "1.6.14", - "resolved": "https://registry.npmjs.org/astro/-/astro-1.6.14.tgz", - "integrity": "sha512-8ljrWPKEU5Hrrbupan48ba4gLWPY14Aqiv4u0U92wSl3wtsqPJUWWlHGGkXcgS/9dx8aiLkUm4RzCPFVnP7V2w==", + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/astro/-/astro-1.6.15.tgz", + "integrity": "sha512-FT60PalffV0bCNxqEHadRzb+Rd3g3lYxV3fR9+bZ2CRlbSfC3UdoaLDh5wC6gJiz91caH1R0XiYaV0WT2Zg91g==", "dependencies": { - "@astrojs/compiler": "^0.30.0", + "@astrojs/compiler": "^0.31.0", "@astrojs/language-server": "^0.28.3", "@astrojs/markdown-remark": "^1.1.3", "@astrojs/telemetry": "^1.0.1", @@ -6623,7 +6632,7 @@ "html-escaper": "^3.0.3", "import-meta-resolve": "^2.1.0", "kleur": "^4.1.4", - "magic-string": "^0.25.9", + "magic-string": "^0.27.0", "mime": "^3.0.0", "ora": "^6.1.0", "path-browserify": "^1.0.1", @@ -6660,10 +6669,21 @@ "npm": ">=6.14.0" } }, + "node_modules/astro/node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/astro/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -11182,27 +11202,27 @@ } }, "node_modules/hast-to-hyperscript/node_modules/comma-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", - "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/hast-to-hyperscript/node_modules/property-information": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", - "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/hast-to-hyperscript/node_modules/space-separated-tokens": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", - "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -11317,9 +11337,9 @@ } }, "node_modules/hast-util-raw/node_modules/zwitch": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz", - "integrity": "sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -11376,9 +11396,9 @@ } }, "node_modules/hast-util-to-html/node_modules/comma-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", - "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -11394,18 +11414,18 @@ } }, "node_modules/hast-util-to-html/node_modules/property-information": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", - "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/hast-util-to-html/node_modules/space-separated-tokens": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", - "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -11429,18 +11449,18 @@ } }, "node_modules/hast-util-to-parse5/node_modules/property-information": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", - "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/hast-util-to-parse5/node_modules/zwitch": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz", - "integrity": "sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -13840,6 +13860,11 @@ "node": ">= 0.6" } }, + "node_modules/medium-zoom": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/medium-zoom/-/medium-zoom-1.0.8.tgz", + "integrity": "sha512-CjFVuFq/IfrdqesAXfg+hzlDKu6A2n80ZIq0Kl9kWjoHh9j1N9Uvk5X0/MmN0hOfm5F9YBswlClhcwnmtwz7gA==" + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -19086,6 +19111,14 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/throttles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttles/-/throttles-1.0.1.tgz", + "integrity": "sha512-fab7Xg+zELr9KOv4fkaBoe/b3L0GMGLd0IBSCn16GoE/Qx6/OfCr1eGNyEcDU2pUA79qQfZ8kPQWlRuok4YwTw==", + "engines": { + "node": ">=6" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -20965,9 +20998,9 @@ "integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==" }, "node_modules/vscode-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.6.tgz", - "integrity": "sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ==" + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", + "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==" }, "node_modules/wcwidth": { "version": "1.0.1", @@ -22393,25 +22426,27 @@ "name": "@example/minimal", "version": "0.0.1", "dependencies": { - "@astrojs/node": "^3.1.0", + "@astrojs/prefetch": "^0.1.1", "@astrojs/tailwind": "^2.1.3", "@docsearch/js": "^3.3.0", "@docsearch/react": "^3.3.0", "@vercel/og": "^0.0.21", - "astro": "^1.6.14", + "astro": "^1.6.15", "classnames": "^2.3.2", "color": "^4.2.3", "cookie": "^0.5.0", "lodash.get": "^4.4.2", "mdx-bundler": "^9.0.1", + "medium-zoom": "^1.0.8", "nanostores": "^0.7.1", "querystring": "^0.2.1", "tailwindcss": "^3.2.4", "zod": "^3.19.1" }, "devDependencies": { + "@astrojs/node": "^3.1.1", "@astrojs/react": "^1.2.2", - "@astrojs/vercel": "^2.3.5", + "@astrojs/vercel": "^2.3.6", "@tailwindcss/typography": "^0.5.8", "@types/classnames": "^2.3.1", "@types/color": "^3.0.3", @@ -22594,9 +22629,9 @@ } }, "@astrojs/compiler": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-0.30.1.tgz", - "integrity": "sha512-sH3hvGA9JR5eE2bHG3sr5QjaJl5ER9EmmZzSwBHkMPoBj0XU7XzQjSGoyCbIML6sSaelJEPnfsr9NsypvKOecg==" + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-0.31.0.tgz", + "integrity": "sha512-V8/Re/wXgXTZzpfWs4KZBLU5dRhnO6kSd4e3vObGuj+HFGHjaD11wws1zvaC9cXLQyQsM5CSrGagFGYlRZKvVQ==" }, "@astrojs/language-server": { "version": "0.28.3", @@ -22665,14 +22700,23 @@ } }, "@astrojs/node": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-3.1.0.tgz", - "integrity": "sha512-teKbDU3u1BIhh3pIVtLsBF/Hxr6NioInfnVFLlPp9afFFlVhakO3N+nR8fPkXrpNXGP3AxQmk+ROI4YHvhIIFw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-3.1.1.tgz", + "integrity": "sha512-EWfsvCtOGvN1ugpxhiW4sr0xD8xUGltFOcU/ysOHdVpP6WXWQUTZbYbqDo+fgYhYHksLIcBLxg8vLi+gBQMA1Q==", + "dev": true, "requires": { "@astrojs/webapi": "^1.1.1", "send": "^0.18.0" } }, + "@astrojs/prefetch": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@astrojs/prefetch/-/prefetch-0.1.1.tgz", + "integrity": "sha512-+XZcjivPHTo9thQshDWKnnORHJ1dE6odIzhmKHac2ptwla092eohkiJw7Y31+PYmuJkc3Jp+3AawuCvfqCnQ2Q==", + "requires": { + "throttles": "^1.0.1" + } + }, "@astrojs/prism": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-1.0.2.tgz", @@ -22717,9 +22761,9 @@ } }, "@astrojs/vercel": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@astrojs/vercel/-/vercel-2.3.5.tgz", - "integrity": "sha512-kjbIKsbaHX41llEV2Qzj9/R8SfYjy/gNG6CdVATuQM1GGBayEvJYn7pU75YalrvLTrqfMKywS+oFPW9fwJl8yA==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@astrojs/vercel/-/vercel-2.3.6.tgz", + "integrity": "sha512-OAfu1d6pgzhKIi48rAKemfhNO6s8ZF/38zSRqGZwl9HnY5qfacccqWmoVeUJ9lSqB/rljxA5Xzh3s+jMqyd9mA==", "dev": true, "requires": { "@astrojs/webapi": "^1.1.1", @@ -24574,22 +24618,24 @@ "@example/minimal": { "version": "file:website-v3", "requires": { - "@astrojs/node": "^3.1.0", + "@astrojs/node": "^3.1.1", + "@astrojs/prefetch": "^0.1.1", "@astrojs/react": "^1.2.2", "@astrojs/tailwind": "^2.1.3", - "@astrojs/vercel": "*", + "@astrojs/vercel": "^2.3.6", "@docsearch/js": "^3.3.0", "@docsearch/react": "^3.3.0", "@tailwindcss/typography": "^0.5.8", "@types/classnames": "^2.3.1", "@types/color": "^3.0.3", "@vercel/og": "^0.0.21", - "astro": "^1.6.14", + "astro": "^1.6.15", "classnames": "^2.3.2", "color": "^4.2.3", "cookie": "^0.5.0", "lodash.get": "^4.4.2", "mdx-bundler": "^9.0.1", + "medium-zoom": "*", "nanostores": "^0.7.1", "querystring": "^0.2.1", "react": "^18.2.0", @@ -27416,11 +27462,11 @@ "integrity": "sha512-sRpyiNrx2dEYIMmUXprS8nlpRg2Drs8m9ElX9vVEXaCB4XEAJhKfs7IcX0IwShjuOAjLR6wzIrgoptz1n19i1A==" }, "astro": { - "version": "1.6.14", - "resolved": "https://registry.npmjs.org/astro/-/astro-1.6.14.tgz", - "integrity": "sha512-8ljrWPKEU5Hrrbupan48ba4gLWPY14Aqiv4u0U92wSl3wtsqPJUWWlHGGkXcgS/9dx8aiLkUm4RzCPFVnP7V2w==", + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/astro/-/astro-1.6.15.tgz", + "integrity": "sha512-FT60PalffV0bCNxqEHadRzb+Rd3g3lYxV3fR9+bZ2CRlbSfC3UdoaLDh5wC6gJiz91caH1R0XiYaV0WT2Zg91g==", "requires": { - "@astrojs/compiler": "^0.30.0", + "@astrojs/compiler": "^0.31.0", "@astrojs/language-server": "^0.28.3", "@astrojs/markdown-remark": "^1.1.3", "@astrojs/telemetry": "^1.0.1", @@ -27452,7 +27498,7 @@ "html-escaper": "^3.0.3", "import-meta-resolve": "^2.1.0", "kleur": "^4.1.4", - "magic-string": "^0.25.9", + "magic-string": "^0.27.0", "mime": "^3.0.0", "ora": "^6.1.0", "path-browserify": "^1.0.1", @@ -27482,10 +27528,18 @@ "zod": "^3.17.3" }, "dependencies": { + "magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.13" + } + }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "requires": { "lru-cache": "^6.0.0" } @@ -31005,19 +31059,19 @@ }, "dependencies": { "comma-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", - "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" }, "property-information": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", - "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==" + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==" }, "space-separated-tokens": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", - "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" } } }, @@ -31100,9 +31154,9 @@ } }, "zwitch": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz", - "integrity": "sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==" } } }, @@ -31147,9 +31201,9 @@ }, "dependencies": { "comma-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", - "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" }, "hast-util-whitespace": { "version": "2.0.0", @@ -31157,14 +31211,14 @@ "integrity": "sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==" }, "property-information": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", - "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==" + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==" }, "space-separated-tokens": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", - "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" } } }, @@ -31182,14 +31236,14 @@ }, "dependencies": { "property-information": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", - "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==" + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==" }, "zwitch": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz", - "integrity": "sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==" } } }, @@ -32936,6 +32990,11 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, + "medium-zoom": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/medium-zoom/-/medium-zoom-1.0.8.tgz", + "integrity": "sha512-CjFVuFq/IfrdqesAXfg+hzlDKu6A2n80ZIq0Kl9kWjoHh9j1N9Uvk5X0/MmN0hOfm5F9YBswlClhcwnmtwz7gA==" + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -36779,6 +36838,11 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "throttles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttles/-/throttles-1.0.1.tgz", + "integrity": "sha512-fab7Xg+zELr9KOv4fkaBoe/b3L0GMGLd0IBSCn16GoE/Qx6/OfCr1eGNyEcDU2pUA79qQfZ8kPQWlRuok4YwTw==" + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -37951,9 +38015,9 @@ "integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==" }, "vscode-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.6.tgz", - "integrity": "sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ==" + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", + "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==" }, "wcwidth": { "version": "1.0.1", diff --git a/website-v3/package.json b/website-v3/package.json index 0ddcb734..aadf3028 100644 --- a/website-v3/package.json +++ b/website-v3/package.json @@ -11,8 +11,8 @@ "astro": "astro" }, "dependencies": { - "@astrojs/tailwind": "^2.1.3", "@astrojs/prefetch": "^0.1.1", + "@astrojs/tailwind": "^2.1.3", "@docsearch/js": "^3.3.0", "@docsearch/react": "^3.3.0", "@vercel/og": "^0.0.21", @@ -22,6 +22,7 @@ "cookie": "^0.5.0", "lodash.get": "^4.4.2", "mdx-bundler": "^9.0.1", + "medium-zoom": "^1.0.8", "nanostores": "^0.7.1", "querystring": "^0.2.1", "tailwindcss": "^3.2.4", diff --git a/website-v3/src/components/Scripts.astro b/website-v3/src/components/Scripts.astro index 0cd4af9b..8edad880 100644 --- a/website-v3/src/components/Scripts.astro +++ b/website-v3/src/components/Scripts.astro @@ -62,6 +62,23 @@ const { config, domain } = ctx; } </script> +<script> + const elements = document.querySelectorAll('main img[data-zoom="true"]'); + + // Only download the library if we need it. + if (elements.length) { + const zoom = (await import('medium-zoom')).default; + zoom('main img[data-zoom="true"]', { + container: document.body, + // Theme is applied via CSS in Styles.astro + background: 'transparent', + }); + } + + console.log(elements); + import zoom from 'medium-zoom'; +</script> + <script> import type { Context } from 'src/context'; const { owner, repository, domain } = (window as any).docs_page as Context; diff --git a/website-v3/src/components/Styles.astro b/website-v3/src/components/Styles.astro new file mode 100644 index 00000000..44efd502 --- /dev/null +++ b/website-v3/src/components/Styles.astro @@ -0,0 +1,14 @@ +<style is:global> + body.medium-zoom--opened .medium-zoom-overlay { + z-index: 999; + background-color: rgba(255, 255, 255, 0.95) !important; + } + + html[data-theme='dark'] body.medium-zoom--opened .medium-zoom-overlay { + background-color: rgba(24, 24, 28, 0.95) !important; + } + + body.medium-zoom--opened .medium-zoom-image--opened { + z-index: 9999; + } +</style> diff --git a/website-v3/src/components/mdx/Image.tsx b/website-v3/src/components/mdx/Image.tsx index 4c3d08ae..2acea8e8 100644 --- a/website-v3/src/components/mdx/Image.tsx +++ b/website-v3/src/components/mdx/Image.tsx @@ -1,18 +1,23 @@ +import context from 'src/context'; import { getImagePath } from 'src/utils'; interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> { - // TODO: zoom zoom?: boolean; + caption?: string; } const Image: React.FC<ImageProps> = props => { + const { config } = context.get(); const src = getImagePath(props.src ?? ''); const { width, height, ...other } = props; + const zoom = Boolean(props.zoom) || config.zoomImages; + return ( <figure> <img {...other} + data-zoom={`${zoom}`} src={src} loading="lazy" className="mx-auto" @@ -21,6 +26,7 @@ const Image: React.FC<ImageProps> = props => { height: height ? parseInt(height.toString()) : 'inherit', }} /> + {!!props.caption && <figcaption className="text-center">{props.caption}</figcaption>} </figure> ); }; diff --git a/website-v3/src/pages/[owner]/[repository]/[...path].astro b/website-v3/src/pages/[owner]/[repository]/[...path].astro index 23b9596a..b7070c85 100644 --- a/website-v3/src/pages/[owner]/[repository]/[...path].astro +++ b/website-v3/src/pages/[owner]/[repository]/[...path].astro @@ -7,6 +7,7 @@ import Navigation from '@layouts/Navigation.astro'; import Meta from '@components/Meta.astro'; import Scripts from '@components/Scripts.astro'; +import Styles from '@components/Styles.astro'; import Links from '@components/Links.astro'; import Markdown from '@components/Markdown'; @@ -119,6 +120,7 @@ switch (status) { <Meta slot="head" /> <Theme slot="head" fallback="#00bcd4" /> <Scripts slot="head" /> + <Styles slot="head" /> <Links slot="head" /> <Header /> <section class="max-w-8xl mx-auto px-4 sm:px-6 md:px-8"> From 9b5732e2453aa469df49f3421b8d55a55e928183 Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Thu, 15 Dec 2022 09:33:53 +0000 Subject: [PATCH 14/19] - --- website-v3/src/components/Scripts.astro | 27 ++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/website-v3/src/components/Scripts.astro b/website-v3/src/components/Scripts.astro index 8edad880..4e96a8fc 100644 --- a/website-v3/src/components/Scripts.astro +++ b/website-v3/src/components/Scripts.astro @@ -6,10 +6,17 @@ const { config, domain } = ctx; --- <script is:inline define:vars={{ ctx }}> + // Load the context into the window, so we can access it from other scripts + // without losing access to Astros script hoisting. window.docs_page = { ...ctx }; </script> <script> + /** + * This script looks for any buttons with the `data-copy` attribute, and adds + * an event listener to copy the value of the `data-copy-value` attribute to. + */ + const copy = document.querySelectorAll('[data-copy]'); for (const button of copy) { @@ -25,6 +32,10 @@ const { config, domain } = ctx; </script> <script> + /** + * This script is used to load the Twitter library, and apply it to any tweets + * with the `data-tweet="true"` attribute. + */ declare global { interface Window { twttr: { @@ -63,10 +74,13 @@ const { config, domain } = ctx; </script> <script> - const elements = document.querySelectorAll('main img[data-zoom="true"]'); + /** + * This script is used to load the Medium Zoom library, and apply it to any images + * with the `data-zoom="true"` attribute. + */ // Only download the library if we need it. - if (elements.length) { + if (document.querySelectorAll('main img[data-zoom="true"]').length) { const zoom = (await import('medium-zoom')).default; zoom('main img[data-zoom="true"]', { container: document.body, @@ -74,12 +88,15 @@ const { config, domain } = ctx; background: 'transparent', }); } - - console.log(elements); - import zoom from 'medium-zoom'; </script> <script> + /** + * This script is used to identify any tab groups on the page, and add event + * when a tab button is clicked. It also syncs the state of the tabs with a + * group ID across the page, and onto a cookie via the server. + */ + import type { Context } from 'src/context'; const { owner, repository, domain } = (window as any).docs_page as Context; From 70c41a5b8b14697184d172b0bfebdd54df28b31a Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Thu, 15 Dec 2022 12:13:05 +0000 Subject: [PATCH 15/19] zapp component --- website-v3/src/components/Markdown.tsx | 2 ++ website-v3/src/components/mdx/Zapp.tsx | 30 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 website-v3/src/components/mdx/Zapp.tsx diff --git a/website-v3/src/components/Markdown.tsx b/website-v3/src/components/Markdown.tsx index 23be7a42..a62b7f45 100644 --- a/website-v3/src/components/Markdown.tsx +++ b/website-v3/src/components/Markdown.tsx @@ -9,6 +9,7 @@ import Vimeo from './mdx/Vimeo'; import Table from './mdx/Table'; import Heading from './mdx/Heading'; import Tweet from './mdx/Tweet'; +import Zapp from './mdx/Zapp'; import Tabs, { TabItem } from './mdx/Tabs'; const Markdown: React.FC = () => { @@ -37,6 +38,7 @@ const Markdown: React.FC = () => { Image, YouTube, Vimeo, + Zapp, }} /> </main> diff --git a/website-v3/src/components/mdx/Zapp.tsx b/website-v3/src/components/mdx/Zapp.tsx new file mode 100644 index 00000000..e318541d --- /dev/null +++ b/website-v3/src/components/mdx/Zapp.tsx @@ -0,0 +1,30 @@ +type ZappProps = { + id: string; + lazy?: boolean; + theme?: 'light' | 'dark'; +}; + +const Zapp: React.FC<ZappProps> = props => { + if (!props.id) { + return <div />; + } + + const lazy = props.lazy === undefined ? false : props.lazy; + const theme = props.theme || 'dark'; + + return ( + <div className="my-6"> + <iframe + src={`http://zapp.run/embed/${props.id}?theme=${theme}&lazy=${lazy}`} + style={{ + width: '100%', + height: '100%', + border: 0, + overflow: 'hidden', + }} + /> + </div> + ); +}; + +export default Zapp; From 451037023a291cd2a8f6f6b81174aca3ac458e73 Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Thu, 15 Dec 2022 12:52:07 +0000 Subject: [PATCH 16/19] add callouts --- website-v3/src/components/Markdown.tsx | 5 ++ website-v3/src/components/icons.tsx | 30 ++++++++ website-v3/src/components/mdx/callouts.tsx | 87 ++++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 website-v3/src/components/mdx/callouts.tsx diff --git a/website-v3/src/components/Markdown.tsx b/website-v3/src/components/Markdown.tsx index a62b7f45..fc433001 100644 --- a/website-v3/src/components/Markdown.tsx +++ b/website-v3/src/components/Markdown.tsx @@ -10,6 +10,7 @@ import Table from './mdx/Table'; import Heading from './mdx/Heading'; import Tweet from './mdx/Tweet'; import Zapp from './mdx/Zapp'; +import * as callouts from './mdx/callouts'; import Tabs, { TabItem } from './mdx/Tabs'; const Markdown: React.FC = () => { @@ -31,6 +32,10 @@ const Markdown: React.FC = () => { h4: props => <Heading {...props} type="h4" />, h5: props => <Heading {...props} type="h5" />, h6: props => <Heading {...props} type="h6" />, + Info: props => <callouts.Info>{props.children}</callouts.Info>, + Warning: props => <callouts.Warning>{props.children}</callouts.Warning>, + Error: props => <callouts.Error>{props.children}</callouts.Error>, + Success: props => <callouts.Success>{props.children}</callouts.Success>, Heading, Tweet: props => <Tweet {...props} />, Tabs: props => <Tabs {...props} />, diff --git a/website-v3/src/components/icons.tsx b/website-v3/src/components/icons.tsx index 4534997f..6cd9271a 100644 --- a/website-v3/src/components/icons.tsx +++ b/website-v3/src/components/icons.tsx @@ -61,3 +61,33 @@ export const PencilIcon = () => ( /> </svg> ); + +export const InformationalCircleIcon = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"> + <path + fillRule="evenodd" + d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 01.67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 11-.671-1.34l.041-.022zM12 9a.75.75 0 100-1.5.75.75 0 000 1.5z" + clipRule="evenodd" + /> + </svg> +); + +export const ExclaimationTriangleIcon = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"> + <path + fillRule="evenodd" + d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" + clipRule="evenodd" + /> + </svg> +); + +export const CheckCircleIcon = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"> + <path + fillRule="evenodd" + d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm13.36-1.814a.75.75 0 10-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 00-1.06 1.06l2.25 2.25a.75.75 0 001.14-.094l3.75-5.25z" + clipRule="evenodd" + /> + </svg> +); diff --git a/website-v3/src/components/mdx/callouts.tsx b/website-v3/src/components/mdx/callouts.tsx new file mode 100644 index 00000000..afa527d5 --- /dev/null +++ b/website-v3/src/components/mdx/callouts.tsx @@ -0,0 +1,87 @@ +import type { PropsWithChildren, ReactElement, ReactNode } from 'react'; +import cx from 'classnames'; +import { + CheckCircleIcon, + ExclaimationTriangleIcon, + InformationalCircleIcon, +} from '@components/icons'; + +type Props = { + icon: ReactElement; + children: ReactNode | undefined; + className: string; +}; + +const Callout: React.FC<Props> = props => { + return ( + <div + className={cx( + 'bg:bg-slate-600 mb-3 flex items-center gap-2 overflow-hidden px-5 py-4', + props.className, + )} + > + <span className="h-6 w-6">{props.icon}</span> + <span>{props.children}</span> + </div> + ); +}; + +export const Info: React.FC<PropsWithChildren> = props => { + return ( + <Callout + icon={ + <span> + <InformationalCircleIcon /> + </span> + } + className="" + > + {props.children} + </Callout> + ); +}; + +export const Warning: React.FC<PropsWithChildren> = props => { + return ( + <Callout + icon={ + <span> + <ExclaimationTriangleIcon /> + </span> + } + className="" + > + {props.children} + </Callout> + ); +}; + +export const Error: React.FC<PropsWithChildren> = props => { + return ( + <Callout + icon={ + <span> + <ExclaimationTriangleIcon /> + </span> + } + className="" + > + {props.children} + </Callout> + ); +}; + +export const Success: React.FC<PropsWithChildren> = props => { + return ( + <Callout + icon={ + <span> + <CheckCircleIcon /> + </span> + } + className="" + > + {props.children} + </Callout> + ); +}; From 0db9ce9d3febdd36de5026f70b2ca81cb7a9b704 Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Thu, 15 Dec 2022 13:36:52 +0000 Subject: [PATCH 17/19] track page requests via plausible --- package-lock.json | 34 ++++++++++++++++++- website-v3/package.json | 2 ++ .../[owner]/[repository]/[...path].astro | 5 ++- website-v3/src/pages/index.astro | 9 ++--- website-v3/src/plausible.ts | 22 ++++++++++++ 5 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 website-v3/src/plausible.ts diff --git a/package-lock.json b/package-lock.json index 9a72e47d..06a524e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5225,6 +5225,15 @@ "@types/react": "*" } }, + "node_modules/@types/request-ip": { + "version": "0.0.37", + "resolved": "https://registry.npmjs.org/@types/request-ip/-/request-ip-0.0.37.tgz", + "integrity": "sha512-uw6/i3rQnpznxD7LtLaeuZytLhKZK6bRoTS6XVJlwxIOoOpEBU7bgKoVXDNtOg4Xl6riUKHa9bjMVrL6ESqYlQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -17370,6 +17379,11 @@ "node": ">=0.10" } }, + "node_modules/request-ip": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/request-ip/-/request-ip-3.3.0.tgz", + "integrity": "sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -22440,6 +22454,7 @@ "medium-zoom": "^1.0.8", "nanostores": "^0.7.1", "querystring": "^0.2.1", + "request-ip": "^3.3.0", "tailwindcss": "^3.2.4", "zod": "^3.19.1" }, @@ -22450,6 +22465,7 @@ "@tailwindcss/typography": "^0.5.8", "@types/classnames": "^2.3.1", "@types/color": "^3.0.3", + "@types/request-ip": "^0.0.37", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^4.9.4" @@ -24628,6 +24644,7 @@ "@tailwindcss/typography": "^0.5.8", "@types/classnames": "^2.3.1", "@types/color": "^3.0.3", + "@types/request-ip": "*", "@vercel/og": "^0.0.21", "astro": "^1.6.15", "classnames": "^2.3.2", @@ -24635,11 +24652,12 @@ "cookie": "^0.5.0", "lodash.get": "^4.4.2", "mdx-bundler": "^9.0.1", - "medium-zoom": "*", + "medium-zoom": "^1.0.8", "nanostores": "^0.7.1", "querystring": "^0.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "request-ip": "^3.3.0", "tailwindcss": "^3.2.4", "typescript": "^4.9.4", "zod": "^3.19.1" @@ -26542,6 +26560,15 @@ "@types/react": "*" } }, + "@types/request-ip": { + "version": "0.0.37", + "resolved": "https://registry.npmjs.org/@types/request-ip/-/request-ip-0.0.37.tgz", + "integrity": "sha512-uw6/i3rQnpznxD7LtLaeuZytLhKZK6bRoTS6XVJlwxIOoOpEBU7bgKoVXDNtOg4Xl6riUKHa9bjMVrL6ESqYlQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -35449,6 +35476,11 @@ "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", "dev": true }, + "request-ip": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/request-ip/-/request-ip-3.3.0.tgz", + "integrity": "sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==" + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/website-v3/package.json b/website-v3/package.json index aadf3028..9cb02ec7 100644 --- a/website-v3/package.json +++ b/website-v3/package.json @@ -25,6 +25,7 @@ "medium-zoom": "^1.0.8", "nanostores": "^0.7.1", "querystring": "^0.2.1", + "request-ip": "^3.3.0", "tailwindcss": "^3.2.4", "zod": "^3.19.1" }, @@ -35,6 +36,7 @@ "@tailwindcss/typography": "^0.5.8", "@types/classnames": "^2.3.1", "@types/color": "^3.0.3", + "@types/request-ip": "^0.0.37", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^4.9.4" diff --git a/website-v3/src/pages/[owner]/[repository]/[...path].astro b/website-v3/src/pages/[owner]/[repository]/[...path].astro index b7070c85..a2ee5c75 100644 --- a/website-v3/src/pages/[owner]/[repository]/[...path].astro +++ b/website-v3/src/pages/[owner]/[repository]/[...path].astro @@ -17,9 +17,9 @@ import type { GetBundleResponse } from 'src/bundle'; import { isExternalLink, replaceMoustacheVariables, ensureLeadingSlash } from 'src/utils'; import Theme from '@components/Theme.astro'; import domains from '../../../../../domains.json'; +import { trackPageRequest } from 'src/plausible'; let status: GetBundleResponse['status'] = 404; - let { owner, repository, path } = Astro.params; let ref: string | undefined; @@ -59,6 +59,9 @@ if (owner && repository) { return Astro.redirect(`/${owner}/${repository}${redirect}`); } + // Track the page request + await trackPageRequest(Astro.request); + // Set the theme color const theme = Astro.cookies.get('theme').value; diff --git a/website-v3/src/pages/index.astro b/website-v3/src/pages/index.astro index d394ffbc..cd37aef1 100644 --- a/website-v3/src/pages/index.astro +++ b/website-v3/src/pages/index.astro @@ -1,8 +1,9 @@ --- -import Root from "@layouts/Root.astro"; -import Homepage from "@layouts/homepage/index.astro"; - +import Root from '@layouts/Root.astro'; +import Homepage from '@layouts/homepage/index.astro'; --- + <Root> - <Homepage /> + <script slot="head" defer data-domain="docs.page" src="https://plausible.io/js/script.js"></script> + <Homepage /> </Root> diff --git a/website-v3/src/plausible.ts b/website-v3/src/plausible.ts new file mode 100644 index 00000000..418d8416 --- /dev/null +++ b/website-v3/src/plausible.ts @@ -0,0 +1,22 @@ +import { getClientIp } from 'request-ip'; + +export async function trackPageRequest(request: Request): Promise<void> { + try { + await fetch(`https://plausible.io/api/event`, { + method: 'POST', + headers: new Headers({ + 'Content-Type': 'application/json', + 'User-Agent': request.headers.get('User-Agent') || '', + // @ts-expect-error - request-ip types use none-generic Request + 'X-Forwarded-For': getClientIp(request) ?? '', + }), + body: JSON.stringify({ + name: 'pageview', + url: request.url, + domain: 'docs.page', + }), + }); + } catch (e) { + console.error('Failed to track page request', e); + } +} From 2d9bc3a88449b7fa103f879344f30c3323629112 Mon Sep 17 00:00:00 2001 From: Elliot Hesp <elliot.hesp@gmail.com> Date: Fri, 16 Dec 2022 09:40:57 +0000 Subject: [PATCH 18/19] add cache control --- website-v3/src/pages/[owner]/[repository]/[...path].astro | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website-v3/src/pages/[owner]/[repository]/[...path].astro b/website-v3/src/pages/[owner]/[repository]/[...path].astro index a2ee5c75..7468a278 100644 --- a/website-v3/src/pages/[owner]/[repository]/[...path].astro +++ b/website-v3/src/pages/[owner]/[repository]/[...path].astro @@ -62,6 +62,9 @@ if (owner && repository) { // Track the page request await trackPageRequest(Astro.request); + // Set the cache headers - see https://vercel.com/docs/concepts/edge-network/caching + Astro.response.headers.set('Cache-Control', 's-maxage=1, stale-while-revalidate=59'); + // Set the theme color const theme = Astro.cookies.get('theme').value; From f06f88819df1f3b08bf68e6b65c6e8e65b8c94e6 Mon Sep 17 00:00:00 2001 From: Jacob Cable <jacobcable94@gmail.com> Date: Fri, 16 Dec 2022 11:56:54 +0000 Subject: [PATCH 19/19] feat(wip): support mermaid via api --- api/package.json | 2 + api/src/bundler/bundler.ts | 1 + api/src/bundler/plugins/rehype-code-blocks.ts | 70 ++++++++++++++----- 3 files changed, 55 insertions(+), 18 deletions(-) diff --git a/api/package.json b/api/package.json index 82c3d252..c50f53cf 100644 --- a/api/package.json +++ b/api/package.json @@ -22,7 +22,9 @@ "esbuild": "^0.14.47", "express": "^4.18.1", "express-basic-auth": "^1.2.1", + "hast-util-from-html": "^1.0.0", "hast-util-parse-selector": "^3.1.0", + "headless-mermaid": "^1.3.0", "http-status": "^1.5.2", "is-badge": "^2.1.0", "js-yaml": "^4.1.0", diff --git a/api/src/bundler/bundler.ts b/api/src/bundler/bundler.ts index 7aae9b4e..39ccbdef 100644 --- a/api/src/bundler/bundler.ts +++ b/api/src/bundler/bundler.ts @@ -70,6 +70,7 @@ export async function bundle( return { code, frontmatter, + //@ts-ignore errors, headings: output.headings, }; diff --git a/api/src/bundler/plugins/rehype-code-blocks.ts b/api/src/bundler/plugins/rehype-code-blocks.ts index 69120452..2f1e88a3 100644 --- a/api/src/bundler/plugins/rehype-code-blocks.ts +++ b/api/src/bundler/plugins/rehype-code-blocks.ts @@ -4,39 +4,73 @@ import { visit } from 'unist-util-visit'; import { Node } from 'hast-util-heading-rank'; import { toString } from 'mdast-util-to-string'; import * as shiki from 'shiki'; - let highlighter: shiki.Highlighter; - +//@ts-ignore +import mermaid from 'headless-mermaid'; +//@ts-ignore +import { fromHtml } from 'hast-util-from-html'; /** * Matches any `pre code` elements and extracts the raw code and titles from the code block and assigns to the parent. * @returns */ export default function rehypeCodeBlocks(): (ast: Node) => void { - function visitor(node: any, _i: number, parent: any) { - if (!parent || parent.tagName !== 'pre' || node.tagName !== 'code') { - return; - } - const language = getLanguage(node); - const raw = toString(node); + return async (ast: Node): Promise<null> => { - // Raw value of the `code` block - used for copy/paste - parent.properties['raw'] = raw; - parent.properties['html'] = highlighter.codeToHtml(raw, language); + const promises: any[] = []; - // Get any metadata from the code block - const meta = (node.data?.meta as string) ?? ''; + async function visitor(node: any, i: number, parent: any) { + if (!parent || parent.tagName !== 'pre' || node.tagName !== 'code') { + return; + } - const title = extractTitle(meta); - if (title) parent.properties['title'] = title; - } + const language = getLanguage(node); + + const raw = toString(node); + + if (language === 'mermaid') { + + const value = node.children[0].value; + + + promises.push(mermaid.execute(value).then((svgResult: string) => { + //@ts-ignore + console.log(fromHtml(svgResult).children[0].children[1].children) + //@ts-ignore + const svgNode = fromHtml(svgResult).children[0].children[1].children[0]; + + Object.assign(parent, svgNode); + })) + + return; + } + + // If the user provides the `console` language, we add the 'shell' language and remove $ from the raw code, for copying purposes. + if (language === 'console') { + const removedDollarSymbol = raw.replace(/^(^ *)\$/g, ''); + + parent.properties['raw'] = removedDollarSymbol; + parent.properties['html'] = highlighter.codeToHtml(raw, { lang: 'shell' }); + } else { + parent.properties['raw'] = raw; + parent.properties['html'] = highlighter.codeToHtml(raw, { lang: language }); + } + + // Get any metadata from the code block + const meta = (node.data?.meta as string) ?? ''; + + const title = extractTitle(meta); + if (title) parent.properties['title'] = title; + } - return async (ast: Node): Promise<void> => { highlighter = await shiki.getHighlighter({ theme: 'github-dark', }); // @ts-ignore visit(ast, 'element', visitor); + + await Promise.all(promises); + return null; }; } @@ -76,4 +110,4 @@ function getLanguage(node: any): string | undefined { return value.slice(9); } } -} +} \ No newline at end of file