This commit is contained in:
widgeter
2023-08-14 12:23:39 +02:00
46 changed files with 691 additions and 170 deletions

View File

@ -12,7 +12,7 @@
- ✅ Integration with **Tailwind CSS** ([@astrojs/tailwind](https://docs.astro.build/en/guides/integrations-guide/tailwind/)) supporting **Dark mode** and ***RTL***.
-**Production-ready** scores in [Lighthouse](https://web.dev/measure/) and [PageSpeed Insights](https://pagespeed.web.dev/) reports.
-**Fast and SEO friendly blog** with automatic **RSS feed** ([@astrojs/rss](https://docs.astro.build/en/guides/rss/)), [**MDX** support](https://docs.astro.build/en/guides/integrations-guide/mdx/), **Categories & Tags**, **Social Share**, ...
-**Image optimization** ([@astrojs/images](https://docs.astro.build/en/guides/integrations-guide/image/)) and **Font optimization**.
-**Image Optimization** (using new [Astro Assets](https://astro.build/blog/images/) and [Unpic](https://unpic.pics/lib/) for Universal image CDN) and **Font optimization**.
- ✅ Generation of **project sitemap** based on your routes ([@astrojs/sitemap](https://docs.astro.build/en/guides/integrations-guide/sitemap/)).
-**Open Graph tags** for social media sharing.
-**Analytics** built-in Google Analytics, and Splitbee integration.

View File

@ -5,10 +5,10 @@ import { defineConfig } from 'astro/config';
import tailwind from '@astrojs/tailwind';
import sitemap from '@astrojs/sitemap';
import image from '@astrojs/image';
import mdx from '@astrojs/mdx';
import icon from 'astro-icon';
import partytown from '@astrojs/partytown';
import tasks from "./src/utils/tasks"
import { readingTimeRemarkPlugin } from './src/utils/frontmatter.mjs';
@ -36,9 +36,7 @@ export default defineConfig({
applyBaseStyles: false,
}),
sitemap(),
image({
serviceEntryPoint: '@astrojs/image/sharp',
}),
mdx(),
icon({
include: {
@ -63,12 +61,18 @@ export default defineConfig({
config: { forward: ['dataLayer.push'] },
})
),
tasks()
],
markdown: {
remarkPlugins: [readingTimeRemarkPlugin],
},
experimental:{
assets: true
},
vite: {
resolve: {
alias: {

View File

@ -13,7 +13,6 @@
"lint:eslint": "eslint . --ext .js,.ts,.astro"
},
"devDependencies": {
"@astrojs/image": "^0.17.3",
"@astrojs/mdx": "^0.19.7",
"@astrojs/partytown": "^1.2.3",
"@astrojs/rss": "^2.4.4",
@ -29,9 +28,9 @@
"@types/lodash.merge": "^4.6.7",
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
"astro": "^2.10.5",
"astro": "^2.10.7",
"astro-icon": "^1.0.0-next.2",
"eslint": "^8.46.0",
"eslint": "^8.47.0",
"eslint-plugin-astro": "^0.28.0",
"eslint-plugin-jsx-a11y": "^6.7.1",
"js-yaml": "^4.1.0",
@ -41,11 +40,11 @@
"prettier": "^3.0.1",
"prettier-plugin-astro": "^0.11.0",
"reading-time": "^1.5.0",
"sharp": "^0.32.4",
"svgo": "3.0.2",
"tailwind-merge": "^1.14.0",
"tailwindcss": "^3.3.3",
"typescript": "^5.1.6"
"typescript": "^5.1.6",
"unpic": "^3.10.0"
},
"engines": {
"node": ">=16.12.0"

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@ -29,7 +29,7 @@
}
.btn {
@apply inline-flex items-center justify-center rounded-full shadow-md border-gray-400 border bg-transparent font-medium text-center text-base text-page leading-snug transition py-3.5 px-6 md:px-8 ease-in duration-200 focus:ring-blue-500 focus:ring-offset-blue-200 focus:ring-2 focus:ring-offset-2 hover:bg-gray-100 hover:border-gray-600 dark:text-slate-300 dark:border-slate-500 dark:hover:bg-slate-800 dark:hover:border-slate-800;
@apply inline-flex items-center justify-center rounded-full border-gray-400 border bg-transparent font-medium text-center text-base text-page leading-snug transition py-3.5 px-6 md:px-8 ease-in duration-200 focus:ring-blue-500 focus:ring-offset-blue-200 focus:ring-2 focus:ring-offset-2 hover:bg-gray-100 hover:border-gray-600 dark:text-slate-300 dark:border-slate-500 dark:hover:bg-slate-800 dark:hover:border-slate-800;
}
.btn-ghost {

View File

@ -1,9 +1,10 @@
---
import favIcon from '~/assets/favicons/favicon.ico';
import favIconSvg from '~/assets/favicons/favicon.svg';
import appleTouchIcon from '~/assets/favicons/apple-touch-icon.png';
---
<link rel="shortcut icon" href={favIcon} />
<link rel="icon" type="image/svg+xml" href={favIconSvg.src} />
<link rel="mask-icon" href={favIconSvg.src} color="#8D46E7" />
<link rel="apple-touch-icon" sizes="180x180" href={appleTouchIcon.src} />

View File

@ -1,19 +1,19 @@
---
import { Picture } from '@astrojs/image/components';
import type { ImageMetadata } from 'astro';
import { APP_BLOG_CONFIG } from '~/utils/config';
import type { Post } from '~/types';
import Image from '~/components/common/Image.astro';
import { findImage } from '~/utils/images';
import { getPermalink } from '~/utils/permalinks';
export interface Props {
post: Post;
}
const { post } = Astro.props;
const image = (await findImage(post.image)) as ImageMetadata | undefined;
const image = (await findImage(post.image));
---
<article class="mb-6 transition">
@ -21,15 +21,15 @@ const image = (await findImage(post.image)) as ImageMetadata | undefined;
{
image && (
<a href={getPermalink(post.permalink, 'post')}>
<Picture
<Image
src={image}
class="md:object-cover w-full md:w-auto md:h-full rounded shadow-lg bg-gray-400 dark:bg-slate-700"
class="w-full md:h-full rounded shadow-lg bg-gray-400 dark:bg-slate-700"
widths={[400, 900]}
width={400}
height={224}
sizes="(max-width: 900px) 400px, 900px"
alt={post.title}
aspectRatio="16:9"
layout="cover"
loading="lazy"
decoding="async"
/>

View File

@ -1,7 +1,7 @@
---
import { Picture } from '@astrojs/image/components';
import type { ImageMetadata } from 'astro';
import { Icon } from 'astro-icon/components';
import Image from '~/components/common/Image.astro';
import PostTags from '~/components/blog/Tags.astro';
import { APP_BLOG_CONFIG } from '~/utils/config';
@ -27,10 +27,11 @@ const link = APP_BLOG_CONFIG?.post?.isEnabled ? getPermalink(post.permalink, 'po
<a class="relative block group" href={link ?? 'javascript:void(0)'}>
<div class="relative h-0 pb-[56.25%] md:pb-[75%] md:h-72 lg:pb-[56.25%] overflow-hidden bg-gray-400 dark:bg-slate-700 rounded shadow-lg">
{image && (
<Picture
<Image
src={image}
class="absolute inset-0 object-cover w-full h-full mb-6 rounded shadow-lg bg-gray-400 dark:bg-slate-700"
widths={[400, 900]}
width={900}
sizes="(max-width: 900px) 400px, 900px"
alt={post.title}
aspectRatio="16:9"

View File

@ -1,7 +1,7 @@
---
import { Icon } from 'astro-icon/components';
import { Picture } from '@astrojs/image/components';
import Image from '~/components/common/Image.astro';
import PostTags from '~/components/blog/Tags.astro';
import SocialShare from '~/components/common/SocialShare.astro';
@ -53,19 +53,17 @@ const Content = post?.Content || null;
{
post.image ? (
<Picture
<Image
src={post.image}
class="max-w-full lg:max-w-6xl mx-auto mb-6 sm:rounded-md bg-gray-400 dark:bg-slate-700"
class="max-w-full lg:max-w-[900px] mx-auto mb-6 sm:rounded-md bg-gray-400 dark:bg-slate-700"
widths={[400, 900]}
sizes="(max-width: 900px) 400px, 900px"
alt={post?.excerpt || ''}
loading="eager"
aspectRatio={16 / 9}
width={900}
height={506}
loading="eager"
decoding="async"
background={undefined}
/>
) : (
<div class="max-w-3xl mx-auto px-4 sm:px-6">

View File

@ -0,0 +1,52 @@
---
import { findImage } from '~/utils/images';
import {
getImagesOptimized,
astroAsseetsOptimizer,
unpicOptimizer,
type ImageProps,
type AttributesProps
} from '~/utils/images-optimization';
type Props = ImageProps;
type ImageType = {
src: string;
attributes: AttributesProps;
}
const props = Astro.props;
if (props.alt === undefined || props.alt === null) {
throw new Error();
}
if (typeof props.width === 'string') {
props.width = parseInt(props.width);
}
if (typeof props.height === 'string') {
props.height = parseInt(props.height);
}
if (!props.loading) {
props.loading = 'lazy';
}
if (!props.decoding) {
props.decoding = 'async';
}
const _image = await findImage(props.src);
let image: ImageType | undefined = undefined;
if (_image !== null && typeof _image === 'object') {
image = await getImagesOptimized(_image, props, astroAsseetsOptimizer);
} else if (typeof _image === 'string' && (_image.startsWith('http://') || _image.startsWith('https://'))) {
image = await getImagesOptimized(_image, props, unpicOptimizer);
} else if (_image) {
image = await getImagesOptimized(_image, props);
}
---
{!image ? <Fragment /> : <img src={image.src} {...image.attributes} />}

View File

@ -1,9 +1,9 @@
---
import { Icon } from 'astro-icon/components';
import { Picture } from '@astrojs/image/components';
import type { Content } from '~/types';
import Headline from '../ui/Headline.astro';
import WidgetWrapper from '../ui/WidgetWrapper.astro';
import Image from '~/components/common/Image.astro';
const {
title = await Astro.slots.render('title'),
@ -70,13 +70,13 @@ const {
{typeof image === 'string' ? (
<Fragment set:html={image} />
) : (
<Picture
<Image
class="mx-auto w-full rounded-lg bg-gray-500 shadow-lg"
width={500}
height={500}
widths={[400, 768]}
sizes="(max-width: 768px) 100vw, 432px"
aspectRatio="500:500"
layout="responsive"
{...(image as any)}
/>
)}

View File

@ -1,8 +1,8 @@
---
import { Picture } from '@astrojs/image/components';
import Headline from '~/components/ui/Headline.astro';
import ItemGrid from '~/components/ui/ItemGrid.astro';
import WidgetWrapper from '~/components/ui/WidgetWrapper.astro';
import Image from '~/components/common/Image.astro';
import type { Features } from '~/types';
const {
@ -39,12 +39,12 @@ const {
{typeof image === 'string' ? (
<Fragment set:html={image} />
) : (
<Picture
<Image
class="w-full h-80 object-cover rounded-xl mx-auto bg-gray-500 shadow-lg"
width={0}
width="auto"
height={320}
widths={[400, 768]}
aspectRatio="16:7"
layout="fullWidth"
{...(image as any)}
/>
)}

View File

@ -1,6 +1,6 @@
---
import { Icon } from 'astro-icon/components';
import { Picture } from '@astrojs/image/components';
import Image from '~/components/common/Image.astro';
import WidgetWrapper from '../ui/WidgetWrapper.astro';
const {
@ -88,11 +88,10 @@ const {
{typeof image === 'string' ? (
<Fragment set:html={image} />
) : (
<Picture
<Image
class="mx-auto rounded-md w-full"
widths={[400, 768, 1024, 2040]}
sizes="(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px"
aspectRatio={1024 / 576}
loading="eager"
width={1024}
height={576}

View File

@ -1,6 +1,6 @@
---
import { Icon } from 'astro-icon/components';
import { Picture } from '@astrojs/image/components';
import Image from '~/components/common/Image.astro';
import { CallToAction } from '~/types';
export interface Props {
@ -88,11 +88,10 @@ const {
{typeof image === 'string' ? (
<Fragment set:html={image} />
) : (
<Picture
<Image
class="mx-auto rounded-md w-full"
widths={[400, 768, 1024, 2040]}
sizes="(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px"
aspectRatio={600 / 600}
loading="eager"
width={600}
height={600}

View File

@ -35,9 +35,9 @@ const {
</div>
)}
{title && (
<h6 class="text-sm font-medium uppercase tracking-widest text-gray-800 dark:text-slate-400 lg:text-base">
<div class="text-sm font-medium uppercase tracking-widest text-gray-800 dark:text-slate-400 lg:text-base">
{title}
</h6>
</div>
)}
</div>
))

View File

@ -1,8 +1,8 @@
---
import { Picture } from '@astrojs/image/components';
import WidgetWrapper from "~/components/ui/WidgetWrapper.astro";
import Timeline from "~/components/ui/Timeline.astro";
import Headline from "~/components/ui/Headline.astro";
import Image from '~/components/common/Image.astro';
import type { Steps } from "~/types";
const {
@ -37,13 +37,13 @@ const {
(typeof image === 'string' ? (
<Fragment set:html={image} />
) : (
<Picture
<Image
class="inset-0 object-cover object-top w-full rounded-md shadow-lg md:absolute md:h-full bg-gray-400 dark:bg-slate-700"
widths={[400, 768]}
sizes="(max-width: 768px) 100vw, 432px"
aspectRatio="432:768"
width={432}
height={768}
layout="cover"
src={image?.src}
alt={image?.alt || ""}
/>

View File

@ -1,9 +1,10 @@
---
import Headline from '~/components/ui/Headline.astro';
import WidgetWrapper from '~/components/ui/WidgetWrapper.astro';
import CTA from '~/components/ui/CTA.astro';
import Image from '~/components/common/Image.astro';
import type { Testimonials } from '~/types';
import CTA from '../ui/CTA.astro';
import { Picture } from '@astrojs/image/components';
const {
title = '',
@ -43,12 +44,12 @@ const {
{typeof image === 'string' ? (
<Fragment set:html={image} />
) : (
<Picture
<Image
class="h-10 w-10 rounded-full border border-slate-200 dark:border-slate-600"
width={40}
height={40}
widths={[400, 768]}
aspectRatio="1:1"
layout="fixed"
{...(image as any)}
/>
)}

View File

@ -2,7 +2,7 @@
publishDate: 2023-07-17T00:00:00Z
title: AstroWind template in depth
excerpt: While easy to get started, Astrowind is quite complex internally. This page provides documentation on some of the more intricate parts.
image: ~/assets/images/stickers.jpg
image: https://images.unsplash.com/photo-1534307671554-9a6d81f4d629?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1651&q=80
category: Documentation
tags:
- astro

View File

@ -2,7 +2,7 @@
publishDate: 2023-01-12T00:00:00Z
title: Get started with AstroWind to create a website using Astro and Tailwind CSS
excerpt: Sint sit cillum pariatur eiusmod nulla pariatur ipsum. Sit laborum anim qui mollit tempor pariatur.
image: ~/assets/images/do-more.jpg
image: https://images.unsplash.com/photo-1516996087931-5ae405802f9f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80
category: Tutorials
tags:
- astro

View File

@ -2,7 +2,7 @@
publishDate: 2023-01-06T00:00:00Z
title: How to customize AstroWind template to suit your branding
excerpt: Sint sit cillum pariatur eiusmod nulla pariatur ipsum. Sit laborum anim qui mollit tempor pariatur.
image: ~/assets/images/colors.jpg
image: https://images.unsplash.com/photo-1546984575-757f4f7c13cf?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80
tags:
- astro
- tailwind css

View File

@ -2,7 +2,7 @@
publishDate: 2023-01-09T00:00:00Z
title: Useful tools and resources to create a professional website
excerpt: Sint sit cillum pariatur eiusmod nulla pariatur ipsum. Sit laborum anim qui mollit tempor pariatur.
image: ~/assets/images/tools.jpg
image: https://images.unsplash.com/photo-1637144113536-9c6e917be447?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1674&q=80
tags:
- front-end
- tools

2
src/env.d.ts vendored
View File

@ -1,3 +1,3 @@
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path="../.astro/types.d.ts" />
/// <reference types="@astrojs/image/client" />
/// <reference types="astro/client-image" />

View File

@ -7,7 +7,9 @@ import Pagination from '~/components/blog/Pagination.astro';
import { blogListRobots, getStaticPathsBlogList } from '~/utils/blog';
export const getStaticPaths = getStaticPathsBlogList();
export async function getStaticPaths ({ paginate }) {
return await getStaticPathsBlogList({ paginate });
}
const { page } = Astro.props;
const currentPage = page.currentPage ?? 1;

View File

@ -6,7 +6,9 @@ import BlogList from '~/components/blog/List.astro';
import Headline from '~/components/blog/Headline.astro';
import Pagination from '~/components/blog/Pagination.astro';
export const getStaticPaths = getStaticPathsBlogCategory();
export async function getStaticPaths ({ paginate }) {
return await getStaticPathsBlogCategory({ paginate });
}
const { page, category } = Astro.props;

View File

@ -6,7 +6,9 @@ import BlogList from '~/components/blog/List.astro';
import Headline from '~/components/blog/Headline.astro';
import Pagination from '~/components/blog/Pagination.astro';
export const getStaticPaths = getStaticPathsBlogTag();
export async function getStaticPaths ({ paginate }) {
return await getStaticPathsBlogTag({ paginate });
}
const { page, tag } = Astro.props;

View File

@ -9,7 +9,9 @@ import { getCanonical, getPermalink } from '~/utils/permalinks';
import { getStaticPathsBlogPost, blogPostRobots } from '~/utils/blog';
import { findImage } from '~/utils/images';
export const getStaticPaths = getStaticPathsBlogPost();
export async function getStaticPaths () {
return await getStaticPathsBlogPost();
}
const { post } = Astro.props;
@ -26,7 +28,7 @@ const metadata = merge(
},
openGraph: {
type: 'article',
...(image ? { images: [{ url: image?.src, width: image?.width, height: image?.height }] } : {}),
...(image ? { images: [{ url: image, width: image?.width, height: image?.height }] } : {}),
},
},
{ ...(post?.metadata ? { ...post.metadata, canonical: post.metadata?.canonical || url } : {}) }

View File

@ -14,7 +14,7 @@ const metadata = {
<Layout metadata={metadata}>
<!-- Hero Widget ******************* -->
<Hero image={{ src: import('~/assets/images/caos.jpg'), alt: 'Caos Image' }}>
<Hero image={{ src: 'https://images.unsplash.com/photo-1559136555-9303baea8ebd?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80', alt: 'Caos Image' }}>
<Fragment slot="title">
Elevate your online presence with our <br />
<span class="text-accent dark:text-white highlight"> Beautiful Website Templates</span>
@ -112,7 +112,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/colors.jpg'),
src: '~/assets/images/colors.jpg',
alt: 'Colorful Image',
}}
/>

View File

@ -24,7 +24,7 @@ const metadata = {
<Hero
callToAction={{ text: 'Get template', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
callToAction2={{ text: 'Learn more', href: '#features' }}
image={{ src: import('~/assets/images/hero.png'), alt: 'AstroWind Hero Image' }}
image={{ src: '~/assets/images/hero.png', alt: 'AstroWind Hero Image' }}
>
<Fragment slot="title">
Free template for <span class="hidden xl:inline">creating websites with</span>
@ -96,8 +96,9 @@ const metadata = {
<!-- Content Widget **************** -->
<Content
isReversed
tagline="Inside template"
title="And what's inside? ..."
title="AstroWind's Blueprint: Fun Meets Functionality!"
items={[
{
title: 'Per ei quaeque sensibus',
@ -116,7 +117,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/caos.jpg'),
src: 'https://images.unsplash.com/photo-1519389950473-47ba0277781c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Colorful Image',
}}
>
@ -133,6 +134,43 @@ const metadata = {
<!-- Content Widget **************** -->
<Content
isAfterContent
items={[
{
title: 'Per ei quaeque sensibus',
description:
'Ex usu illum iudico molestie. Pro ne agam facete mediocritatem, ridens labore facete mea ei. Pro id apeirian dignissim.',
},
{
title: 'Cu imperdiet posidonium sed',
description:
'Amet utinam aliquando ut mea, malis admodum ocurreret nec et, elit tibique cu nec. Nec ex maluisset inciderint, ex quis.',
},
{
title: 'Nulla omittam sadipscing mel ne',
description:
'At sed possim oporteat probatus, justo graece ne nec, minim commodo legimus ut vix. Ut eos iudico quando soleat, nam modus.',
},
]}
image={{
src: 'https://images.unsplash.com/photo-1600132806370-bf17e65e942f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2194&q=80',
alt: 'Blueprint Image',
}}
>
<Fragment slot="content">
<h3 class="text-2xl font-bold tracking-tight dark:text-white sm:text-3xl mb-2">Ad vix debet docendi</h3>
Ne dicta praesent ocurreret has, diam theophrastus at pro. Eos etiam regione ut, persius eripuit quo id. Sit te
euismod tacimates.
</Fragment>
<Fragment slot="bg">
<div class="absolute inset-0 bg-blue-50 dark:bg-transparent"></div>
</Fragment>
</Content>
<!-- Content Widget **************** -->
<Content
isReversed
isAfterContent
@ -157,8 +195,8 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/vintage.jpg'),
alt: 'Vintage Image',
src: 'https://images.unsplash.com/photo-1611462985358-60d3498e0364?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Astronauts Image',
}}
>
<Fragment slot="bg">
@ -174,19 +212,19 @@ const metadata = {
{
title: 'Step 1: <span class="font-medium">Download</span>',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sagittis, quam nec venenatis lobortis, mirisus tempus nulla, sed porttitor est nibh at nulla. Praesent placerat enim ut ex tincidunt vehicula.',
'Kickstart with GitHub! Either fork the AstroWind template or simply click \'Use this template\'. Your canvas awaits, ready for your digital masterpiece. In just a few clicks, you\'ve already set the foundation.',
icon: 'tabler:package',
},
{
title: 'Step 2: <span class="font-medium">Add content</em>',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sagittis, quam nec venenatis lobortis, mirisus tempus nulla, sed porttitor est nibh at nulla.',
'Pour your vision into it. Add images, text, and all that jazz to breathe life into your digital space. Remember, it\'s the content that tells your story, so make it captivating.',
icon: 'tabler:letter-case',
},
{
title: 'Step 3: <span class="font-medium">Customize styles</span>',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sagittis, quam nec venenatis lobortis, mirisus tempus nulla, sed porttitor est nibh at nulla. Praesent placerat enim ut ex tincidunt vehicula. Fusce sit amet dui tellus.',
'Give it your personal touch. Tailor colors, fonts, and layouts until it feels just right. Your unique flair, amplified by AstroWind! Precision in design ensures a seamless user experience.',
icon: 'tabler:paint',
},
{
@ -195,7 +233,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/creativity.jpg'),
src: 'https://images.unsplash.com/photo-1616198814651-e71f960c3180?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=987&q=80',
alt: 'Steps image',
}}
/>
@ -210,55 +248,55 @@ const metadata = {
{
title: 'Headers',
description:
'In general, Headers contain information that makes it easier for visitors to interact with the website.',
'Ever tried driving without GPS? Boom! That\'s why websites need headers for direction.',
icon: 'flat-color-icons:template',
},
{
title: 'Heros',
description:
'If you want your website to get more than its fair share of visitors, the Hero section needs to be stellar.',
'Picture a superhero landing epic, right? That\'s the job of a Hero section, making grand entrances!',
icon: 'flat-color-icons:gallery',
},
{
title: 'Features',
description:
'Display your product in action and how the Features actually create a solution for your target customer.',
'Where websites strut their stuff and show off superpowers. No holding back on the bragging rights here!',
icon: 'flat-color-icons:approval',
},
{
title: 'Content',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.',
'Dive into the meat and potatoes of a site; without it, you\'d just be window shopping. Content is king.',
icon: 'flat-color-icons:document',
},
{
title: 'Call-to-Action',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.',
'That enthusiastic friend who\'s always urging, "Do it! Do it!"? Yeah, that\'s this button nudging you towards adventure.',
icon: 'flat-color-icons:advertising',
},
{
title: 'Pricing',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.',
'Behold the dessert menu of the website world. Tempting choices await, can you resist?',
icon: 'flat-color-icons:currency-exchange',
},
{
title: 'Testimonial',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.',
'Step into the gossip corner! Here, other visitors spill the beans and share the juicy details.',
icon: 'flat-color-icons:voice-presentation',
},
{
title: 'Contact',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.',
'Like a digital mailbox, but faster! Drop a line, ask a question, or send a virtual high-five. Ding! Message in.',
icon: 'flat-color-icons:business-contact',
},
{
title: 'Footers',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.',
'The footer\'s like the credits of a movie but sprinkled with easter eggs. Time to hunt!',
icon: 'flat-color-icons:database',
},
]}
@ -285,6 +323,11 @@ const metadata = {
subtitle="Duis turpis dui, fringilla mattis sem nec, fringilla euismod neque. Morbi tincidunt lacus nec tortor scelerisque pulvinar."
tagline="FAQs"
items={[
{
title: "Why AstroWind?",
description:
"Michael Knight a young loner on a crusade to champion the cause of the innocent. The helpless. The powerless in a world of criminals who operate above the law. Here he comes Here comes Speed Racer. He's a demon on wheels.",
},
{
title: 'What do I need to start?',
description:
@ -300,11 +343,6 @@ const metadata = {
description:
"A flower in my garden, a mystery in my panties. Heart attack never stopped old Big Bear. I didn't even know we were calling him Big Bear.",
},
{
title: "What's an example of when you changed your mind?",
description:
"Michael Knight a young loner on a crusade to champion the cause of the innocent. The helpless. The powerless in a world of criminals who operate above the law. Here he comes Here comes Speed Racer. He's a demon on wheels.",
},
{
title: 'What is something that you would like to try again?',
description:

View File

@ -40,7 +40,7 @@ const metadata = {
<Hero2
callToAction={{ text: 'Download App', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
callToAction2={{ text: 'Learn more', href: '#features' }}
image={{ src: import('~/assets/images/hero.png'), alt: 'AstroWind Hero Image' }}
image={{ src: 'https://images.unsplash.com/photo-1535303311164-664fc9ec6532?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=987&q=80', alt: 'AstroWind Hero Image' }}
>
<Fragment slot="title">
Free template for <span class="hidden lg:inline">create your website <br />with</span>
@ -91,7 +91,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/colors.jpg'),
src: 'https://images.unsplash.com/photo-1521517407911-565264e7d82d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2069&q=80',
alt: 'Colorful Image',
}}
/>
@ -99,6 +99,7 @@ const metadata = {
<!-- Content Widget **************** -->
<Content
isReversed
items={[
{
title: 'High-Quality Designs',
@ -128,7 +129,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/caos.jpg'),
src: 'https://images.unsplash.com/photo-1576153192621-7a3be10b356e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1674&q=80',
alt: 'Colorful Image',
}}
>
@ -140,7 +141,6 @@ const metadata = {
<!-- Content Widget **************** -->
<Content
isReversed
isAfterContent
items={[
{
@ -171,7 +171,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/vintage.jpg'),
src: 'https://images.unsplash.com/photo-1453738773917-9c3eff1db985?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Vintage Image',
}}
>
@ -202,7 +202,7 @@ const metadata = {
name: 'Cary Kennedy',
job: 'Film director',
image: {
src: import('~/assets/images/colors.jpg'),
src: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Cary Kennedy Image',
},
},
@ -212,7 +212,7 @@ const metadata = {
name: 'Josh Wilkinson',
job: 'Product Manager',
image: {
src: import('~/assets/images/vintage.jpg'),
src: 'https://images.unsplash.com/flagged/photo-1570612861542-284f4c12e75f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Josh Wilkinson Image',
},
},
@ -222,7 +222,7 @@ const metadata = {
name: 'Sidney Hansen',
job: 'Decorator',
image: {
src: import('~/assets/images/caos.jpg'),
src: 'https://images.unsplash.com/photo-1512361436605-a484bdb34b5f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Sidney Hansen Image',
},
},

View File

@ -3,6 +3,7 @@ import Layout from '~/layouts/PageLayout.astro';
import Header from '~/components/widgets/Header.astro';
import Hero2 from '~/components/widgets/Hero2.astro';
import Features from '~/components/widgets/Features.astro';
import Steps2 from '~/components/widgets/Steps2.astro';
import Content from '~/components/widgets/Content.astro';
import CallToAction from '~/components/widgets/CallToAction.astro';
@ -39,7 +40,7 @@ const metadata = {
<Hero2
callToAction={{ text: 'Get template', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
callToAction2={{ text: 'Learn more', href: '#features' }}
image={{ src: import('~/assets/images/hero.png'), alt: 'AstroWind Hero Image' }}
image={{ src: 'https://images.unsplash.com/photo-1580481072645-022f9a6dbf27?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80', alt: 'AstroWind Hero Image' }}
>
<Fragment slot="title">
Free template for <br />
@ -56,6 +57,51 @@ const metadata = {
</Fragment>
</Hero2>
<!-- Features Widget *************** -->
<Features
id="features"
tagline="Features"
title="Main features of our templates"
subtitle="Possess several key characteristics to effectively cater to the needs of startups and entrepreneurs."
columns={3}
items={[
{
title: 'Modern and Professional Design',
description:
'Have a contemporary design that reflects current design trends and gives a professional impression.',
icon: 'tabler:artboard',
},
{
title: 'Responsive and Mobile-Friendly',
description: 'Adapt seamlessly to different screen sizes and devices to ensure a consistent experience.',
icon: 'tabler:picture-in-picture',
},
{
title: 'Customizability',
description:
'Easily customizable, allowing users to adapt the design, colors, typography, and content to match their brand identity.',
icon: 'tabler:adjustments-horizontal',
},
{
title: 'Fast Loading Times',
description: 'Optimized for speed to ensure a smooth user experience and favorable search engine rankings.',
icon: 'tabler:rocket',
},
{
title: 'Search Engine Optimization (SEO)',
description:
'Incorporate SEO best practices in template structure and code to improve visibility in search engine results.',
icon: 'tabler:arrows-right-left',
},
{
title: 'Compatibility',
description: 'The templates work seamlessly across various content management systems and website builders.',
icon: 'tabler:plug-connected',
},
]}
/>
<!-- Content Widget **************** -->
<Content
@ -78,7 +124,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/caos.jpg'),
src: 'https://images.unsplash.com/photo-1620558138198-cfb9b4f3c294?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1671&q=80',
alt: 'Colorful Image',
}}
>
@ -115,7 +161,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/caos.jpg'),
src: 'https://images.unsplash.com/photo-1531973486364-5fa64260d75b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1658&q=80',
alt: 'Colorful Image',
}}
>
@ -153,7 +199,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/caos.jpg'),
src: 'https://images.unsplash.com/photo-1635070041078-e363dbe005cb?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Colorful Image',
}}
>

View File

@ -43,14 +43,18 @@ const metadata = {
</Fragment>
<Fragment slot="image">
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/dsTXcSeAZq8"
title="YouTube video player"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
style="width:100%"></iframe>
<div class="relative h-0 pb-[56.25%]">
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/dsTXcSeAZq8"
title="YouTube video player"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture;"
allowfullscreen
class="absolute top-0 left-0 w-full h-full"
>
</iframe>
</div>
</Fragment>
</Hero>

View File

@ -178,7 +178,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/creativity.jpg'),
src: 'https://images.unsplash.com/photo-1536816579748-4ecb3f03d72a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=987&q=80',
alt: 'Steps image',
}}
/>

View File

@ -16,7 +16,7 @@ export const get = async () => {
return rss({
title: `${SITE_CONFIG.name}s Blog`,
description: METADATA_CONFIG?.description,
description: METADATA_CONFIG?.description || "",
site: import.meta.env.SITE,
items: posts.map((post) => ({

View File

@ -27,7 +27,7 @@ const metadata = {
<div
slot="bg"
class="absolute inset-0 bg-dark overflow-hidden brightness-[0.25] bg-cover bg-[url('~/assets/images/hero.png')]"
class="absolute inset-0 bg-dark overflow-hidden brightness-[0.25] bg-cover bg-[url('https://images.unsplash.com/photo-1611462985358-60d3498e0364?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80')]"
>
</div>
</Hero>
@ -104,7 +104,7 @@ const metadata = {
name: 'Emily Kennedy',
job: 'Front-end developer',
image: {
src: import('~/assets/images/colors.jpg'),
src: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Emily Kennedy Image',
},
},
@ -113,7 +113,7 @@ const metadata = {
name: 'Sarah Hansen',
job: 'Photographer',
image: {
src: import('~/assets/images/caos.jpg'),
src: 'https://images.unsplash.com/flagged/photo-1570612861542-284f4c12e75f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Sarah Hansen Image',
},
},
@ -122,7 +122,7 @@ const metadata = {
name: 'Mark Wilkinson',
job: 'Small business owner',
image: {
src: import('~/assets/images/vintage.jpg'),
src: 'https://images.unsplash.com/photo-1512361436605-a484bdb34b5f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Mark Wilkinson Image',
},
},

View File

@ -162,18 +162,16 @@ export const findLatestPosts = async ({ count }: { count?: number }): Promise<Ar
};
/** */
export const getStaticPathsBlogList =
() =>
async ({ paginate }) => {
if (!isBlogEnabled || !isBlogListRouteEnabled) return [];
return paginate(await fetchPosts(), {
params: { blog: BLOG_BASE || undefined },
pageSize: blogPostsPerPage,
});
};
export const getStaticPathsBlogList = async ({ paginate }) => {
if (!isBlogEnabled || !isBlogListRouteEnabled) return [];
return paginate(await fetchPosts(), {
params: { blog: BLOG_BASE || undefined },
pageSize: blogPostsPerPage,
});
};
/** */
export const getStaticPathsBlogPost = () => async () => {
export const getStaticPathsBlogPost = async () => {
if (!isBlogEnabled || !isBlogPostRouteEnabled) return [];
return (await fetchPosts()).map((post) => ({
params: {
@ -184,49 +182,45 @@ export const getStaticPathsBlogPost = () => async () => {
};
/** */
export const getStaticPathsBlogCategory =
() =>
async ({ paginate }) => {
if (!isBlogEnabled || !isBlogCategoryRouteEnabled) return [];
export const getStaticPathsBlogCategory = async ({ paginate }) => {
if (!isBlogEnabled || !isBlogCategoryRouteEnabled) return [];
const posts = await fetchPosts();
const categories = new Set();
posts.map((post) => {
typeof post.category === 'string' && categories.add(post.category.toLowerCase());
});
const posts = await fetchPosts();
const categories = new Set();
posts.map((post) => {
typeof post.category === 'string' && categories.add(post.category.toLowerCase());
});
return Array.from(categories).map((category: string) =>
paginate(
posts.filter((post) => typeof post.category === 'string' && category === post.category.toLowerCase()),
{
params: { category: category, blog: CATEGORY_BASE || undefined },
pageSize: blogPostsPerPage,
props: { category },
}
)
);
};
return Array.from(categories).map((category: string) =>
paginate(
posts.filter((post) => typeof post.category === 'string' && category === post.category.toLowerCase()),
{
params: { category: category, blog: CATEGORY_BASE || undefined },
pageSize: blogPostsPerPage,
props: { category },
}
)
);
};
/** */
export const getStaticPathsBlogTag =
() =>
async ({ paginate }) => {
if (!isBlogEnabled || !isBlogTagRouteEnabled) return [];
export const getStaticPathsBlogTag = async ({ paginate }) => {
if (!isBlogEnabled || !isBlogTagRouteEnabled) return [];
const posts = await fetchPosts();
const tags = new Set();
posts.map((post) => {
Array.isArray(post.tags) && post.tags.map((tag) => tags.add(tag.toLowerCase()));
});
const posts = await fetchPosts();
const tags = new Set();
posts.map((post) => {
Array.isArray(post.tags) && post.tags.map((tag) => tags.add(tag.toLowerCase()));
});
return Array.from(tags).map((tag: string) =>
paginate(
posts.filter((post) => Array.isArray(post.tags) && post.tags.find((elem) => elem.toLowerCase() === tag)),
{
params: { tag: tag, blog: TAG_BASE || undefined },
pageSize: blogPostsPerPage,
props: { tag },
}
)
);
};
return Array.from(tags).map((tag: string) =>
paginate(
posts.filter((post) => Array.isArray(post.tags) && post.tags.find((elem) => elem.toLowerCase() === tag)),
{
params: { tag: tag, blog: TAG_BASE || undefined },
pageSize: blogPostsPerPage,
props: { tag },
}
)
);
};

View File

@ -0,0 +1,321 @@
import { getImage } from 'astro:assets';
import { transformUrl, parseUrl } from 'unpic';
import type { ImageMetadata } from 'astro';
import type { HTMLAttributes } from 'astro/types';
type Layout = 'fixed' | 'constrained' | 'fullWidth' | 'cover' | 'responsive' | 'contained';
export interface AttributesProps extends HTMLAttributes<'img'> {}
export interface ImageProps extends Omit<HTMLAttributes<'img'>, 'src'> {
src?: string | ImageMetadata | null;
width?: string | number | null;
height?: string | number | null;
alt?: string | null;
loading?: 'eager' | 'lazy' | null;
decoding?: 'sync' | 'async' | 'auto' | null;
style?: string;
srcset?: string | null;
sizes?: string | null;
fetchpriority?: 'high' | 'low' | 'auto' | null;
layout?: Layout;
widths?: number[] | null;
aspectRatio?: string | number | null;
}
export type ImagesOptimizer = (
image: ImageMetadata | string,
breakpoints: number[],
width?: number,
height?: number
) => Promise<Array<{ src: string; width: number }>>;
/* ******* */
const config = {
// FIXME: Use this when image.width is minor than deviceSizes
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
deviceSizes: [
640, // older and lower-end phones
750, // iPhone 6-8
828, // iPhone XR/11
960, // older horizontal phones
1080, // iPhone 6-8 Plus
1280, // 720p
1668, // Various iPads
1920, // 1080p
2048, // QXGA
2560, // WQXGA
3200, // QHD+
3840, // 4K
4480, // 4.5K
5120, // 5K
6016, // 6K
],
formats: ['image/webp'],
};
const computeHeight = (width: number, aspectRatio: number) => {
return Math.floor(width / aspectRatio);
};
const parseAspectRatio = (aspectRatio: number | string | null | undefined): number | undefined => {
if (typeof aspectRatio === 'number') return aspectRatio;
if (typeof aspectRatio === 'string') {
const match = aspectRatio.match(/(\d+)\s*[/:]\s*(\d+)/);
if (match) {
const [, num, den] = match.map(Number);
if (den && !isNaN(num)) return num / den;
} else {
const numericValue = parseFloat(aspectRatio);
if (!isNaN(numericValue)) return numericValue;
}
}
return undefined;
};
/**
* Gets the `sizes` attribute for an image, based on the layout and width
*/
export const getSizes = (width?: number, layout?: Layout): string | undefined => {
if (!width || !layout) {
return undefined;
}
switch (layout) {
// If screen is wider than the max size, image width is the max size,
// otherwise it's the width of the screen
case `constrained`:
return `(min-width: ${width}px) ${width}px, 100vw`;
// Image is always the same width, whatever the size of the screen
case `fixed`:
return `${width}px`;
// Image is always the width of the screen
case `fullWidth`:
return `100vw`;
default:
return undefined;
}
};
const pixelate = (value?: number) => (value || value === 0 ? `${value}px` : undefined);
const getStyle = ({
width,
height,
aspectRatio,
layout,
objectFit = 'cover',
objectPosition = 'center',
background,
}: {
width?: number;
height?: number;
aspectRatio?: number;
objectFit?: string;
objectPosition?: string;
layout?: string;
background?: string;
}) => {
const styleEntries: Array<[prop: string, value: string | undefined]> = [
['object-fit', objectFit],
['object-position', objectPosition],
];
// If background is a URL, set it to cover the image and not repeat
if (background?.startsWith('https:') || background?.startsWith('http:') || background?.startsWith('data:')) {
styleEntries.push(['background-image', `url(${background})`]);
styleEntries.push(['background-size', 'cover']);
styleEntries.push(['background-repeat', 'no-repeat']);
} else {
styleEntries.push(['background', background]);
}
if (layout === 'fixed') {
styleEntries.push(['width', pixelate(width)]);
styleEntries.push(['height', pixelate(height)]);
styleEntries.push(['object-position', 'top left']);
}
if (layout === 'constrained') {
styleEntries.push(['max-width', pixelate(width)]);
styleEntries.push(['max-height', pixelate(height)]);
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
styleEntries.push(['width', '100%']);
}
if (layout === 'fullWidth') {
styleEntries.push(['width', '100%']);
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
styleEntries.push(['height', pixelate(height)]);
}
if (layout === 'responsive') {
styleEntries.push(['width', '100%']);
styleEntries.push(['height', 'auto']);
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
}
if (layout === 'contained') {
styleEntries.push(['max-width', '100%']);
styleEntries.push(['max-height', '100%']);
styleEntries.push(['object-fit', 'contain']);
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
}
if (layout === 'cover') {
styleEntries.push(['max-width', '100%']);
styleEntries.push(['max-height', '100%']);
}
const styles = Object.fromEntries(styleEntries.filter(([, value]) => value));
return Object.entries(styles)
.map(([key, value]) => `${key}: ${value};`)
.join(' ');
};
const getBreakpoints = ({
width,
breakpoints,
layout,
}: {
width?: number;
breakpoints?: number[];
layout: Layout;
}): number[] => {
if (layout === 'fullWidth' || layout === 'cover' || layout === 'responsive' || layout === 'contained') {
return breakpoints || config.deviceSizes;
}
if (!width) {
return [];
}
const doubleWidth = width * 2;
if (layout === 'fixed') {
return [width, doubleWidth];
}
if (layout === 'constrained') {
return [
// Always include the image at 1x and 2x the specified width
width,
doubleWidth,
// Filter out any resolutions that are larger than the double-res image
...(breakpoints || config.deviceSizes).filter((w) => w < doubleWidth),
];
}
return [];
};
/* ** */
export const astroAsseetsOptimizer: ImagesOptimizer = async (image, breakpoints) => {
if (!image || typeof image === 'string') {
return [];
}
return Promise.all(
breakpoints.map(async (w: number) => {
const url = (await getImage({ src: image, width: w })).src;
return {
src: url,
width: w,
};
})
);
};
/* ** */
export const unpicOptimizer: ImagesOptimizer = async (image, breakpoints, width, height) => {
if (!image || typeof image !== 'string') {
return [];
}
const urlParsed = parseUrl(image);
if (!urlParsed) {
return [];
}
return Promise.all(
breakpoints.map(async (w: number) => {
const url =
(await transformUrl({
url: image,
width: w,
height: width && height ? computeHeight(w, width / height) : height,
cdn: urlParsed.cdn,
})) || image;
return {
src: String(url),
width: w,
};
})
);
};
/* ** */
export async function getImagesOptimized(
image: ImageMetadata | string,
{ src: _, width, height, sizes, aspectRatio, widths, layout = 'constrained', style = '', ...rest }: ImageProps,
transform: ImagesOptimizer = () => Promise.resolve([])
): Promise<{ src: string; attributes: AttributesProps }> {
if (typeof image !== 'string') {
width ||= Number(image.width) || undefined;
height ||= typeof width === 'number' ? computeHeight(width, image.width / image.height) : undefined;
}
width = (width && Number(width)) || undefined;
height = (height && Number(height)) || undefined;
widths ||= config.deviceSizes;
sizes ||= getSizes(Number(width) || undefined, layout);
aspectRatio = parseAspectRatio(aspectRatio);
// Calculate dimensions from aspect ratio
if (aspectRatio) {
if (width) {
if (height) {
/* empty */
} else {
height = width / aspectRatio;
}
} else if (height) {
width = Number(height * aspectRatio);
} else if (layout !== 'fullWidth') {
// Fullwidth images have 100% width, so aspectRatio is applicable
console.error('When aspectRatio is set, either width or height must also be set');
console.error('Image', image);
}
} else if (width && height) {
aspectRatio = width / height;
} else if (layout !== 'fullWidth') {
// Fullwidth images don't need dimensions
console.error('Either aspectRatio or both width and height must be set');
console.error('Image', image);
}
let breakpoints = getBreakpoints({ width: width, breakpoints: widths, layout: layout });
breakpoints = [...new Set(breakpoints)].sort((a, b) => a - b);
const srcset = (await transform(image, breakpoints, Number(width) || undefined, Number(height) || undefined))
.map(({ src, width }) => `${src} ${width}w`)
.join(', ');
return {
src: typeof image === 'string' ? image : image.src,
attributes: {
width: width,
height: height,
srcset: srcset || undefined,
sizes: sizes,
style: `${getStyle({
width: width,
height: height,
aspectRatio: aspectRatio,
layout: layout,
})}${style ?? ''}`,
...rest,
},
};
}

View File

@ -1,11 +1,11 @@
import { getImage } from '@astrojs/image';
import type { OpenGraph } from '@astrolib/seo/src/types';
import { getImage } from 'astro:assets';
import type { ImageMetadata } from 'astro';
import type { OpenGraph } from '@astrolib/seo/src/types';
const load = async function () {
let images: Record<string, () => Promise<unknown>> | undefined = undefined;
try {
images = import.meta.glob('~/assets/images/**');
images = import.meta.glob('~/assets/images/**/*.{jpeg,jpg,png,tiff,webp,gif,svg,JPEG,JPG,PNG,TIFF,WEBP,GIF,SVG}');
} catch (e) {
// continue regardless of error
}
@ -21,24 +21,27 @@ export const fetchLocalImages = async () => {
};
/** */
export const findImage = async (imagePath?: string) => {
export const findImage = async (imagePath?: string | ImageMetadata | null): Promise<string | ImageMetadata | undefined | null> => {
// Not string
if (typeof imagePath !== 'string') {
return null;
return imagePath;
}
// Absolute paths
if (imagePath.startsWith('http://') || imagePath.startsWith('https://') || imagePath.startsWith('/')) {
return imagePath;
}
if (!imagePath.startsWith('~/assets')) {
return null;
} // For now only consume images using ~/assets alias (or absolute)
// Relative paths or not "~/assets/"
if (!imagePath.startsWith('~/assets/images')) {
return imagePath;
}
const images = await fetchLocalImages();
const key = imagePath.replace('~/', '/src/');
return images && typeof images[key] === 'function'
? ((await images[key]()) as { default: unknown })['default']
? ((await images[key]()) as { default: ImageMetadata })['default']
: null;
};

53
src/utils/tasks.mjs Normal file
View File

@ -0,0 +1,53 @@
import fs from 'node:fs';
import os from 'node:os';
const tasksIntegration = () => {
let config;
return {
name: 'AstroWind:tasks',
hooks: {
'astro:config:done': async ({ config: cfg }) => {
config = cfg;
},
'astro:build:done': async () => {
try {
const outDir = config.outDir;
const publicDir = config.publicDir;
const sitemapName = 'sitemap-index.xml';
const sitemapFile = new URL(sitemapName, outDir);
const robotsTxtFile = new URL('robots.txt', publicDir);
const robotsTxtFileInOut = new URL('robots.txt', outDir);
const hasIntegration =
Array.isArray(config?.integrations) &&
config.integrations?.find((e) => e?.name === '@astrojs/sitemap') !== undefined;
const sitemapExists = fs.existsSync(sitemapFile);
if (hasIntegration && sitemapExists) {
const robotsTxt = fs.readFileSync(robotsTxtFile, { encoding: 'utf8', flags: 'a+' });
const sitemapUrl = new URL(sitemapName, String(new URL(config.base, config.site)));
const pattern = /^Sitemap:(.*)$/m;
if (!pattern.test(robotsTxt)) {
fs.appendFileSync(robotsTxtFileInOut, `${os.EOL}${os.EOL}Sitemap: ${sitemapUrl}`, {
encoding: 'utf8',
flags: 'w',
});
} else {
fs.writeFileSync(robotsTxtFileInOut, robotsTxt.replace(pattern, `Sitemap: ${sitemapUrl}`), {
encoding: 'utf8',
flags: 'w',
});
}
}
} catch (err) {
/* empty */
}
},
},
};
};
export default tasksIntegration;