Refactor routing in App component to enhance navigation and improve error handling by integrating dynamic routes and updating the NotFound route.
This commit is contained in:
126
node_modules/astro/components/Code.astro
generated
vendored
Normal file
126
node_modules/astro/components/Code.astro
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
import type { ThemePresets } from '@astrojs/markdown-remark';
|
||||
import type {
|
||||
BuiltinLanguage,
|
||||
LanguageRegistration,
|
||||
ShikiTransformer,
|
||||
SpecialLanguage,
|
||||
ThemeRegistration,
|
||||
ThemeRegistrationRaw,
|
||||
} from 'shiki';
|
||||
import { bundledLanguages } from 'shiki/langs';
|
||||
import { getCachedHighlighter } from '../dist/core/shiki.js';
|
||||
import type { HTMLAttributes } from '../types';
|
||||
|
||||
interface Props extends Omit<HTMLAttributes<'pre'>, 'lang'> {
|
||||
/** The code to highlight. Required. */
|
||||
code: string;
|
||||
/**
|
||||
* The language of your code.
|
||||
* Supports all languages listed here: https://shiki.style/languages
|
||||
* Instructions for loading a custom language: https://shiki.style/guide/load-lang
|
||||
*
|
||||
* @default "plaintext"
|
||||
*/
|
||||
lang?: BuiltinLanguage | SpecialLanguage | LanguageRegistration;
|
||||
/**
|
||||
* A metastring to pass to the highlighter.
|
||||
* Allows passing information to transformers: https://shiki.style/guide/transformers#meta
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
meta?: string;
|
||||
/**
|
||||
* The styling theme.
|
||||
* Supports all themes listed here: https://shiki.style/themes
|
||||
* Instructions for loading a custom theme: https://shiki.style/guide/load-theme
|
||||
*
|
||||
* @default "github-dark"
|
||||
*/
|
||||
theme?: ThemePresets | ThemeRegistration | ThemeRegistrationRaw;
|
||||
/**
|
||||
* Multiple themes to style with -- alternative to "theme" option.
|
||||
* Supports all themes found above; see https://shiki.style/guide/dual-themes for more information.
|
||||
*/
|
||||
themes?: Record<string, ThemePresets | ThemeRegistration | ThemeRegistrationRaw>;
|
||||
/**
|
||||
* Chooses a theme from the "themes" option that you've defined as the default styling theme.
|
||||
* - <string>: one of the keys defined in the "themes" option. Will throw an error if the key is not defined.
|
||||
* - false: disabled. You'll have to apply the styling theme yourself. No effect if the "themes" option is not set.
|
||||
*
|
||||
* See https://shiki.style/guide/dual-themes#without-default-color for more information.
|
||||
*
|
||||
* @default "light"
|
||||
*/
|
||||
defaultColor?: 'light' | 'dark' | string | false;
|
||||
/**
|
||||
* Enable word wrapping.
|
||||
* - true: enabled.
|
||||
* - false: disabled.
|
||||
* - null: All overflow styling removed. Code will overflow the element by default.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
wrap?: boolean | null;
|
||||
/**
|
||||
* Generate inline code element only, without the pre element wrapper.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
inline?: boolean;
|
||||
/**
|
||||
* Shiki transformers to customize the generated HTML by manipulating the hast tree.
|
||||
* Supports all transformers listed here: https://shiki.style/packages/transformers#transformers
|
||||
* Instructions for custom transformers: https://shiki.style/guide/transformers
|
||||
*/
|
||||
transformers?: ShikiTransformer[];
|
||||
}
|
||||
|
||||
const {
|
||||
code,
|
||||
lang = 'plaintext',
|
||||
meta,
|
||||
theme = 'github-dark',
|
||||
themes = {},
|
||||
defaultColor = 'light',
|
||||
wrap = false,
|
||||
inline = false,
|
||||
transformers = [],
|
||||
...rest
|
||||
} = Astro.props;
|
||||
|
||||
// shiki 1.0 compat
|
||||
if (typeof lang === 'object') {
|
||||
// `id` renamed to `name` (always override)
|
||||
if ((lang as any).id) {
|
||||
lang.name = (lang as any).id;
|
||||
}
|
||||
// `grammar` flattened to lang itself
|
||||
if ((lang as any).grammar) {
|
||||
Object.assign(lang, (lang as any).grammar);
|
||||
}
|
||||
}
|
||||
|
||||
const highlighter = await getCachedHighlighter({
|
||||
langs: [
|
||||
typeof lang === 'string'
|
||||
? Object.keys(bundledLanguages).includes(lang)
|
||||
? lang
|
||||
: 'plaintext'
|
||||
: (lang as any),
|
||||
],
|
||||
theme,
|
||||
themes,
|
||||
defaultColor,
|
||||
wrap,
|
||||
transformers,
|
||||
});
|
||||
|
||||
const html = await highlighter.highlight(code, typeof lang === 'string' ? lang : lang.name, {
|
||||
inline,
|
||||
meta,
|
||||
attributes: rest as any,
|
||||
});
|
||||
---
|
||||
|
||||
<Fragment set:html={html} />
|
61
node_modules/astro/components/Debug.astro
generated
vendored
Normal file
61
node_modules/astro/components/Debug.astro
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
import Code from './Code.astro';
|
||||
|
||||
const key = Object.keys(Astro.props)[0];
|
||||
const value = Astro.props[key];
|
||||
---
|
||||
|
||||
<div class="astro-debug">
|
||||
<div class="astro-debug-header">
|
||||
<h2 class="astro-debug-title">
|
||||
<span class="astro-debug-label">Debug</span>
|
||||
<span class="astro-debug-name">"{key}"</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Code code={JSON.stringify(value, null, 2)} />
|
||||
</div>
|
||||
|
||||
<style is:inline>
|
||||
.astro-debug {
|
||||
font-size: 14px;
|
||||
padding: 1rem 1.5rem;
|
||||
background: white;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
.astro-debug-header,
|
||||
pre.astro-code {
|
||||
margin: -1rem -1.5rem 1rem;
|
||||
padding: 0.25rem 0.75rem;
|
||||
}
|
||||
|
||||
.astro-debug-header {
|
||||
background: #ff1639;
|
||||
border-radius: 4px;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.astro-debug-title {
|
||||
font-size: 1em;
|
||||
color: white;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.astro-debug-label {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
margin-right: 0.75em;
|
||||
}
|
||||
|
||||
pre.astro-code {
|
||||
border: 1px solid #eee;
|
||||
padding: 1rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
38
node_modules/astro/components/Image.astro
generated
vendored
Normal file
38
node_modules/astro/components/Image.astro
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
import { type LocalImageProps, type RemoteImageProps, getImage } from 'astro:assets';
|
||||
import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
|
||||
import type { HTMLAttributes } from '../types';
|
||||
|
||||
// The TypeScript diagnostic for JSX props uses the last member of the union to suggest props, so it would be better for
|
||||
// LocalImageProps to be last. Unfortunately, when we do this the error messages that remote images get are complete nonsense
|
||||
// Not 100% sure how to fix this, seems to be a TypeScript issue. Unfortunate.
|
||||
type Props = LocalImageProps | RemoteImageProps;
|
||||
|
||||
const props = Astro.props;
|
||||
|
||||
if (props.alt === undefined || props.alt === null) {
|
||||
throw new AstroError(AstroErrorData.ImageMissingAlt);
|
||||
}
|
||||
|
||||
// As a convenience, allow width and height to be string with a number in them, to match HTML's native `img`.
|
||||
if (typeof props.width === 'string') {
|
||||
props.width = parseInt(props.width);
|
||||
}
|
||||
|
||||
if (typeof props.height === 'string') {
|
||||
props.height = parseInt(props.height);
|
||||
}
|
||||
|
||||
const image = await getImage(props);
|
||||
|
||||
const additionalAttributes: HTMLAttributes<'img'> = {};
|
||||
if (image.srcSet.values.length > 0) {
|
||||
additionalAttributes.srcset = image.srcSet.attribute;
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
additionalAttributes['data-image-component'] = 'true';
|
||||
}
|
||||
---
|
||||
|
||||
<img src={image.src} {...additionalAttributes} {...image.attributes} />
|
110
node_modules/astro/components/Picture.astro
generated
vendored
Normal file
110
node_modules/astro/components/Picture.astro
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
import { type LocalImageProps, type RemoteImageProps, getImage } from 'astro:assets';
|
||||
import * as mime from 'mrmime';
|
||||
import type { GetImageResult, ImageOutputFormat } from '../dist/@types/astro';
|
||||
import { isESMImportedImage, resolveSrc } from '../dist/assets/utils/imageKind';
|
||||
import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
|
||||
import type { HTMLAttributes } from '../types';
|
||||
|
||||
type Props = (LocalImageProps | RemoteImageProps) & {
|
||||
formats?: ImageOutputFormat[];
|
||||
fallbackFormat?: ImageOutputFormat;
|
||||
pictureAttributes?: HTMLAttributes<'picture'>;
|
||||
};
|
||||
|
||||
const defaultFormats = ['webp'] as const;
|
||||
const defaultFallbackFormat = 'png' as const;
|
||||
|
||||
// Certain formats don't want PNG fallbacks:
|
||||
// - GIF will typically want to stay as a gif, either for animation or for the lower amount of colors
|
||||
// - SVGs can't be converted to raster formats in most cases
|
||||
// - JPEGs compress photographs and high-noise images better than PNG in most cases
|
||||
// For those, we'll use the original format as the fallback instead.
|
||||
const specialFormatsFallback = ['gif', 'svg', 'jpg', 'jpeg'] as const;
|
||||
|
||||
const { formats = defaultFormats, pictureAttributes = {}, fallbackFormat, ...props } = Astro.props;
|
||||
|
||||
if (props.alt === undefined || props.alt === null) {
|
||||
throw new AstroError(AstroErrorData.ImageMissingAlt);
|
||||
}
|
||||
|
||||
// Picture attribute inherit scoped styles from class and attributes
|
||||
const scopedStyleClass = props.class?.match(/\bastro-\w{8}\b/)?.[0];
|
||||
if (scopedStyleClass) {
|
||||
if (pictureAttributes.class) {
|
||||
pictureAttributes.class = `${pictureAttributes.class} ${scopedStyleClass}`;
|
||||
} else {
|
||||
pictureAttributes.class = scopedStyleClass;
|
||||
}
|
||||
}
|
||||
for (const key in props) {
|
||||
if (key.startsWith('data-astro-cid')) {
|
||||
pictureAttributes[key] = props[key];
|
||||
}
|
||||
}
|
||||
|
||||
const originalSrc = await resolveSrc(props.src);
|
||||
const optimizedImages: GetImageResult[] = await Promise.all(
|
||||
formats.map(
|
||||
async (format) =>
|
||||
await getImage({
|
||||
...props,
|
||||
src: originalSrc,
|
||||
format: format,
|
||||
widths: props.widths,
|
||||
densities: props.densities,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
let resultFallbackFormat = fallbackFormat ?? defaultFallbackFormat;
|
||||
if (
|
||||
!fallbackFormat &&
|
||||
isESMImportedImage(originalSrc) &&
|
||||
(specialFormatsFallback as ReadonlyArray<string>).includes(originalSrc.format)
|
||||
) {
|
||||
resultFallbackFormat = originalSrc.format;
|
||||
}
|
||||
|
||||
const fallbackImage = await getImage({
|
||||
...props,
|
||||
format: resultFallbackFormat,
|
||||
widths: props.widths,
|
||||
densities: props.densities,
|
||||
});
|
||||
|
||||
const imgAdditionalAttributes: HTMLAttributes<'img'> = {};
|
||||
const sourceAdditionalAttributes: HTMLAttributes<'source'> = {};
|
||||
|
||||
// Propagate the `sizes` attribute to the `source` elements
|
||||
if (props.sizes) {
|
||||
sourceAdditionalAttributes.sizes = props.sizes;
|
||||
}
|
||||
|
||||
if (fallbackImage.srcSet.values.length > 0) {
|
||||
imgAdditionalAttributes.srcset = fallbackImage.srcSet.attribute;
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
imgAdditionalAttributes['data-image-component'] = 'true';
|
||||
}
|
||||
---
|
||||
|
||||
<picture {...pictureAttributes}>
|
||||
{
|
||||
Object.entries(optimizedImages).map(([_, image]) => {
|
||||
const srcsetAttribute =
|
||||
props.densities || (!props.densities && !props.widths)
|
||||
? `${image.src}${image.srcSet.values.length > 0 ? ', ' + image.srcSet.attribute : ''}`
|
||||
: image.srcSet.attribute;
|
||||
return (
|
||||
<source
|
||||
srcset={srcsetAttribute}
|
||||
type={mime.lookup(image.options.format ?? image.src) ?? `image/${image.options.format}`}
|
||||
{...sourceAdditionalAttributes}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
<img src={fallbackImage.src} {...imgAdditionalAttributes} {...fallbackImage.attributes} />
|
||||
</picture>
|
149
node_modules/astro/components/ViewTransitions.astro
generated
vendored
Normal file
149
node_modules/astro/components/ViewTransitions.astro
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
---
|
||||
type Fallback = 'none' | 'animate' | 'swap';
|
||||
|
||||
export interface Props {
|
||||
fallback?: Fallback;
|
||||
/** @deprecated handleForms is enabled by default in Astro 4.0
|
||||
*
|
||||
* Set `data-astro-reload` on your form to opt-out of the default behavior.
|
||||
*/
|
||||
handleForms?: boolean;
|
||||
}
|
||||
|
||||
const { fallback = 'animate' } = Astro.props;
|
||||
---
|
||||
|
||||
<style is:global>
|
||||
/* Route announcer */
|
||||
.astro-route-announcer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
clip-path: inset(50%);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
</style>
|
||||
<meta name="astro-view-transitions-enabled" content="true" />
|
||||
<meta name="astro-view-transitions-fallback" content={fallback} />
|
||||
<script>
|
||||
import type { Options } from 'astro:transitions/client';
|
||||
import { supportsViewTransitions, navigate } from 'astro:transitions/client';
|
||||
// NOTE: import from `astro/virtual-modules/prefetch.js` as `astro:prefetch` requires the `prefetch` config to be enabled
|
||||
// @ts-ignore
|
||||
import { init } from 'astro/virtual-modules/prefetch.js';
|
||||
|
||||
type Fallback = 'none' | 'animate' | 'swap';
|
||||
|
||||
function getFallback(): Fallback {
|
||||
const el = document.querySelector('[name="astro-view-transitions-fallback"]');
|
||||
if (el) {
|
||||
return el.getAttribute('content') as Fallback;
|
||||
}
|
||||
return 'animate';
|
||||
}
|
||||
|
||||
function isReloadEl(el: HTMLElement | SVGAElement): boolean {
|
||||
return el.dataset.astroReload !== undefined;
|
||||
}
|
||||
|
||||
if (supportsViewTransitions || getFallback() !== 'none') {
|
||||
if (import.meta.env.DEV && window.matchMedia('(prefers-reduced-motion)').matches) {
|
||||
console.warn(
|
||||
`[transitions]: all view transition animations, including fallback animation, are disabled as this device has the prefer-reduced-motion setting enabled.`,
|
||||
);
|
||||
}
|
||||
document.addEventListener('click', (ev) => {
|
||||
let link = ev.target;
|
||||
if (ev.composed) {
|
||||
link = ev.composedPath()[0];
|
||||
}
|
||||
if (link instanceof Element) {
|
||||
link = link.closest('a, area');
|
||||
}
|
||||
if (
|
||||
!(link instanceof HTMLAnchorElement) &&
|
||||
!(link instanceof SVGAElement) &&
|
||||
!(link instanceof HTMLAreaElement)
|
||||
)
|
||||
return;
|
||||
// This check verifies that the click is happening on an anchor
|
||||
// that is going to another page within the same origin. Basically it determines
|
||||
// same-origin navigation, but omits special key combos for new tabs, etc.
|
||||
const linkTarget = link instanceof HTMLElement ? link.target : link.target.baseVal;
|
||||
const href = link instanceof HTMLElement ? link.href : link.href.baseVal;
|
||||
const origin = new URL(href, location.href).origin;
|
||||
if (
|
||||
isReloadEl(link) ||
|
||||
link.hasAttribute('download') ||
|
||||
!link.href ||
|
||||
(linkTarget && linkTarget !== '_self') ||
|
||||
origin !== location.origin ||
|
||||
ev.button !== 0 || // left clicks only
|
||||
ev.metaKey || // new tab (mac)
|
||||
ev.ctrlKey || // new tab (windows)
|
||||
ev.altKey || // download
|
||||
ev.shiftKey || // new window
|
||||
ev.defaultPrevented
|
||||
) {
|
||||
// No page transitions in these cases,
|
||||
// Let the browser standard action handle this
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
navigate(href, {
|
||||
history: link.dataset.astroHistory === 'replace' ? 'replace' : 'auto',
|
||||
sourceElement: link,
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('submit', (ev) => {
|
||||
let el = ev.target as HTMLElement;
|
||||
if (el.tagName !== 'FORM' || ev.defaultPrevented || isReloadEl(el)) {
|
||||
return;
|
||||
}
|
||||
const form = el as HTMLFormElement;
|
||||
const submitter = ev.submitter;
|
||||
const formData = new FormData(form, submitter);
|
||||
// form.action and form.method can point to an <input name="action"> or <input name="method">
|
||||
// in which case should fallback to the form attribute
|
||||
const formAction =
|
||||
typeof form.action === 'string' ? form.action : form.getAttribute('action');
|
||||
const formMethod =
|
||||
typeof form.method === 'string' ? form.method : form.getAttribute('method');
|
||||
// Use the form action, if defined, otherwise fallback to current path.
|
||||
let action = submitter?.getAttribute('formaction') ?? formAction ?? location.pathname;
|
||||
// Use the form method, if defined, otherwise fallback to "get"
|
||||
const method = submitter?.getAttribute('formmethod') ?? formMethod ?? 'get';
|
||||
|
||||
// the "dialog" method is a special keyword used within <dialog> elements
|
||||
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fs-method
|
||||
if (method === 'dialog' || location.origin !== new URL(action, location.href).origin) {
|
||||
// No page transitions in these cases,
|
||||
// Let the browser standard action handle this
|
||||
return;
|
||||
}
|
||||
|
||||
const options: Options = { sourceElement: submitter ?? form };
|
||||
if (method === 'get') {
|
||||
const params = new URLSearchParams(formData as any);
|
||||
const url = new URL(action);
|
||||
url.search = params.toString();
|
||||
action = url.toString();
|
||||
} else {
|
||||
options.formData = formData;
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
navigate(action, options);
|
||||
});
|
||||
|
||||
// @ts-expect-error injected by vite-plugin-transitions for treeshaking
|
||||
if (!__PREFETCH_DISABLED__) {
|
||||
init({ prefetchAll: true });
|
||||
}
|
||||
}
|
||||
</script>
|
6
node_modules/astro/components/index.ts
generated
vendored
Normal file
6
node_modules/astro/components/index.ts
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
// The `ts-ignore` comments here are necessary because we're importing this file inside the `astro:components`
|
||||
// virtual module's types, which means that `tsc` will try to resolve these imports. Don't mind the editor errors.
|
||||
// @ts-ignore
|
||||
export { default as Code } from './Code.astro';
|
||||
// @ts-ignore
|
||||
export { default as Debug } from './Debug.astro';
|
66
node_modules/astro/components/viewtransitions.css
generated
vendored
Normal file
66
node_modules/astro/components/viewtransitions.css
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
@keyframes astroFadeInOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes astroFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
mix-blend-mode: plus-lighter;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
mix-blend-mode: plus-lighter;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes astroFadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
mix-blend-mode: plus-lighter;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
mix-blend-mode: plus-lighter;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes astroSlideFromRight {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes astroSlideFromLeft {
|
||||
from {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes astroSlideToRight {
|
||||
to {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes astroSlideToLeft {
|
||||
to {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
::view-transition-group(*),
|
||||
::view-transition-old(*),
|
||||
::view-transition-new(*) {
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
[data-astro-transition-scope] {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user