Merge branch 'main' of https://github.com/widgeter/astrowind
@ -12,7 +12,7 @@
|
|||||||
- ✅ Integration with **Tailwind CSS** ([@astrojs/tailwind](https://docs.astro.build/en/guides/integrations-guide/tailwind/)) supporting **Dark mode** and ***RTL***.
|
- ✅ 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.
|
- ✅ **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**, ...
|
- ✅ **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/)).
|
- ✅ 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.
|
- ✅ **Open Graph tags** for social media sharing.
|
||||||
- ✅ **Analytics** built-in Google Analytics, and Splitbee integration.
|
- ✅ **Analytics** built-in Google Analytics, and Splitbee integration.
|
||||||
|
@ -5,10 +5,10 @@ import { defineConfig } from 'astro/config';
|
|||||||
|
|
||||||
import tailwind from '@astrojs/tailwind';
|
import tailwind from '@astrojs/tailwind';
|
||||||
import sitemap from '@astrojs/sitemap';
|
import sitemap from '@astrojs/sitemap';
|
||||||
import image from '@astrojs/image';
|
|
||||||
import mdx from '@astrojs/mdx';
|
import mdx from '@astrojs/mdx';
|
||||||
import icon from 'astro-icon';
|
import icon from 'astro-icon';
|
||||||
import partytown from '@astrojs/partytown';
|
import partytown from '@astrojs/partytown';
|
||||||
|
import tasks from "./src/utils/tasks"
|
||||||
|
|
||||||
import { readingTimeRemarkPlugin } from './src/utils/frontmatter.mjs';
|
import { readingTimeRemarkPlugin } from './src/utils/frontmatter.mjs';
|
||||||
|
|
||||||
@ -36,9 +36,7 @@ export default defineConfig({
|
|||||||
applyBaseStyles: false,
|
applyBaseStyles: false,
|
||||||
}),
|
}),
|
||||||
sitemap(),
|
sitemap(),
|
||||||
image({
|
|
||||||
serviceEntryPoint: '@astrojs/image/sharp',
|
|
||||||
}),
|
|
||||||
mdx(),
|
mdx(),
|
||||||
icon({
|
icon({
|
||||||
include: {
|
include: {
|
||||||
@ -63,12 +61,18 @@ export default defineConfig({
|
|||||||
config: { forward: ['dataLayer.push'] },
|
config: { forward: ['dataLayer.push'] },
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
|
||||||
|
tasks()
|
||||||
],
|
],
|
||||||
|
|
||||||
markdown: {
|
markdown: {
|
||||||
remarkPlugins: [readingTimeRemarkPlugin],
|
remarkPlugins: [readingTimeRemarkPlugin],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
experimental:{
|
||||||
|
assets: true
|
||||||
|
},
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
"lint:eslint": "eslint . --ext .js,.ts,.astro"
|
"lint:eslint": "eslint . --ext .js,.ts,.astro"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/image": "^0.17.3",
|
|
||||||
"@astrojs/mdx": "^0.19.7",
|
"@astrojs/mdx": "^0.19.7",
|
||||||
"@astrojs/partytown": "^1.2.3",
|
"@astrojs/partytown": "^1.2.3",
|
||||||
"@astrojs/rss": "^2.4.4",
|
"@astrojs/rss": "^2.4.4",
|
||||||
@ -29,9 +28,9 @@
|
|||||||
"@types/lodash.merge": "^4.6.7",
|
"@types/lodash.merge": "^4.6.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
||||||
"@typescript-eslint/parser": "^6.3.0",
|
"@typescript-eslint/parser": "^6.3.0",
|
||||||
"astro": "^2.10.5",
|
"astro": "^2.10.7",
|
||||||
"astro-icon": "^1.0.0-next.2",
|
"astro-icon": "^1.0.0-next.2",
|
||||||
"eslint": "^8.46.0",
|
"eslint": "^8.47.0",
|
||||||
"eslint-plugin-astro": "^0.28.0",
|
"eslint-plugin-astro": "^0.28.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
@ -41,11 +40,11 @@
|
|||||||
"prettier": "^3.0.1",
|
"prettier": "^3.0.1",
|
||||||
"prettier-plugin-astro": "^0.11.0",
|
"prettier-plugin-astro": "^0.11.0",
|
||||||
"reading-time": "^1.5.0",
|
"reading-time": "^1.5.0",
|
||||||
"sharp": "^0.32.4",
|
|
||||||
"svgo": "3.0.2",
|
"svgo": "3.0.2",
|
||||||
"tailwind-merge": "^1.14.0",
|
"tailwind-merge": "^1.14.0",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"typescript": "^5.1.6"
|
"typescript": "^5.1.6",
|
||||||
|
"unpic": "^3.10.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.12.0"
|
"node": ">=16.12.0"
|
||||||
|
BIN
src/assets/favicons/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 276 KiB |
Before Width: | Height: | Size: 136 KiB |
Before Width: | Height: | Size: 108 KiB |
Before Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 49 KiB |
@ -29,7 +29,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.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 {
|
.btn-ghost {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
import favIcon from '~/assets/favicons/favicon.ico';
|
import favIcon from '~/assets/favicons/favicon.ico';
|
||||||
import favIconSvg from '~/assets/favicons/favicon.svg';
|
import favIconSvg from '~/assets/favicons/favicon.svg';
|
||||||
|
import appleTouchIcon from '~/assets/favicons/apple-touch-icon.png';
|
||||||
---
|
---
|
||||||
|
|
||||||
<link rel="shortcut icon" href={favIcon} />
|
<link rel="shortcut icon" href={favIcon} />
|
||||||
<link rel="icon" type="image/svg+xml" href={favIconSvg.src} />
|
<link rel="icon" type="image/svg+xml" href={favIconSvg.src} />
|
||||||
<link rel="mask-icon" href={favIconSvg.src} color="#8D46E7" />
|
<link rel="mask-icon" href={favIconSvg.src} color="#8D46E7" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href={appleTouchIcon.src} />
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
---
|
---
|
||||||
import { Picture } from '@astrojs/image/components';
|
|
||||||
import type { ImageMetadata } from 'astro';
|
|
||||||
|
|
||||||
import { APP_BLOG_CONFIG } from '~/utils/config';
|
import { APP_BLOG_CONFIG } from '~/utils/config';
|
||||||
import type { Post } from '~/types';
|
import type { Post } from '~/types';
|
||||||
|
|
||||||
|
import Image from '~/components/common/Image.astro';
|
||||||
|
|
||||||
import { findImage } from '~/utils/images';
|
import { findImage } from '~/utils/images';
|
||||||
import { getPermalink } from '~/utils/permalinks';
|
import { getPermalink } from '~/utils/permalinks';
|
||||||
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
post: Post;
|
post: Post;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { post } = Astro.props;
|
const { post } = Astro.props;
|
||||||
const image = (await findImage(post.image)) as ImageMetadata | undefined;
|
const image = (await findImage(post.image));
|
||||||
---
|
---
|
||||||
|
|
||||||
<article class="mb-6 transition">
|
<article class="mb-6 transition">
|
||||||
@ -21,15 +21,15 @@ const image = (await findImage(post.image)) as ImageMetadata | undefined;
|
|||||||
{
|
{
|
||||||
image && (
|
image && (
|
||||||
<a href={getPermalink(post.permalink, 'post')}>
|
<a href={getPermalink(post.permalink, 'post')}>
|
||||||
<Picture
|
<Image
|
||||||
src={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]}
|
widths={[400, 900]}
|
||||||
width={400}
|
width={400}
|
||||||
height={224}
|
|
||||||
sizes="(max-width: 900px) 400px, 900px"
|
sizes="(max-width: 900px) 400px, 900px"
|
||||||
alt={post.title}
|
alt={post.title}
|
||||||
aspectRatio="16:9"
|
aspectRatio="16:9"
|
||||||
|
layout="cover"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { Picture } from '@astrojs/image/components';
|
|
||||||
import type { ImageMetadata } from 'astro';
|
import type { ImageMetadata } from 'astro';
|
||||||
import { Icon } from 'astro-icon/components';
|
import { Icon } from 'astro-icon/components';
|
||||||
|
import Image from '~/components/common/Image.astro';
|
||||||
import PostTags from '~/components/blog/Tags.astro';
|
import PostTags from '~/components/blog/Tags.astro';
|
||||||
|
|
||||||
import { APP_BLOG_CONFIG } from '~/utils/config';
|
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)'}>
|
<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">
|
<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 && (
|
{image && (
|
||||||
<Picture
|
<Image
|
||||||
src={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"
|
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]}
|
widths={[400, 900]}
|
||||||
|
width={900}
|
||||||
sizes="(max-width: 900px) 400px, 900px"
|
sizes="(max-width: 900px) 400px, 900px"
|
||||||
alt={post.title}
|
alt={post.title}
|
||||||
aspectRatio="16:9"
|
aspectRatio="16:9"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from 'astro-icon/components';
|
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 PostTags from '~/components/blog/Tags.astro';
|
||||||
import SocialShare from '~/components/common/SocialShare.astro';
|
import SocialShare from '~/components/common/SocialShare.astro';
|
||||||
|
|
||||||
@ -53,19 +53,17 @@ const Content = post?.Content || null;
|
|||||||
|
|
||||||
{
|
{
|
||||||
post.image ? (
|
post.image ? (
|
||||||
<Picture
|
<Image
|
||||||
src={post.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]}
|
widths={[400, 900]}
|
||||||
sizes="(max-width: 900px) 400px, 900px"
|
sizes="(max-width: 900px) 400px, 900px"
|
||||||
alt={post?.excerpt || ''}
|
alt={post?.excerpt || ''}
|
||||||
loading="eager"
|
loading="eager"
|
||||||
aspectRatio={16 / 9}
|
|
||||||
width={900}
|
width={900}
|
||||||
height={506}
|
height={506}
|
||||||
loading="eager"
|
loading="eager"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
background={undefined}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div class="max-w-3xl mx-auto px-4 sm:px-6">
|
<div class="max-w-3xl mx-auto px-4 sm:px-6">
|
||||||
|
52
src/components/common/Image.astro
Normal 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} />}
|
@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from 'astro-icon/components';
|
import { Icon } from 'astro-icon/components';
|
||||||
import { Picture } from '@astrojs/image/components';
|
|
||||||
import type { Content } from '~/types';
|
import type { Content } from '~/types';
|
||||||
import Headline from '../ui/Headline.astro';
|
import Headline from '../ui/Headline.astro';
|
||||||
import WidgetWrapper from '../ui/WidgetWrapper.astro';
|
import WidgetWrapper from '../ui/WidgetWrapper.astro';
|
||||||
|
import Image from '~/components/common/Image.astro';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title = await Astro.slots.render('title'),
|
title = await Astro.slots.render('title'),
|
||||||
@ -70,13 +70,13 @@ const {
|
|||||||
{typeof image === 'string' ? (
|
{typeof image === 'string' ? (
|
||||||
<Fragment set:html={image} />
|
<Fragment set:html={image} />
|
||||||
) : (
|
) : (
|
||||||
<Picture
|
<Image
|
||||||
class="mx-auto w-full rounded-lg bg-gray-500 shadow-lg"
|
class="mx-auto w-full rounded-lg bg-gray-500 shadow-lg"
|
||||||
width={500}
|
width={500}
|
||||||
height={500}
|
height={500}
|
||||||
widths={[400, 768]}
|
widths={[400, 768]}
|
||||||
sizes="(max-width: 768px) 100vw, 432px"
|
sizes="(max-width: 768px) 100vw, 432px"
|
||||||
aspectRatio="500:500"
|
layout="responsive"
|
||||||
{...(image as any)}
|
{...(image as any)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import { Picture } from '@astrojs/image/components';
|
|
||||||
import Headline from '~/components/ui/Headline.astro';
|
import Headline from '~/components/ui/Headline.astro';
|
||||||
import ItemGrid from '~/components/ui/ItemGrid.astro';
|
import ItemGrid from '~/components/ui/ItemGrid.astro';
|
||||||
import WidgetWrapper from '~/components/ui/WidgetWrapper.astro';
|
import WidgetWrapper from '~/components/ui/WidgetWrapper.astro';
|
||||||
|
import Image from '~/components/common/Image.astro';
|
||||||
import type { Features } from '~/types';
|
import type { Features } from '~/types';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -39,12 +39,12 @@ const {
|
|||||||
{typeof image === 'string' ? (
|
{typeof image === 'string' ? (
|
||||||
<Fragment set:html={image} />
|
<Fragment set:html={image} />
|
||||||
) : (
|
) : (
|
||||||
<Picture
|
<Image
|
||||||
class="w-full h-80 object-cover rounded-xl mx-auto bg-gray-500 shadow-lg"
|
class="w-full h-80 object-cover rounded-xl mx-auto bg-gray-500 shadow-lg"
|
||||||
width={0}
|
width="auto"
|
||||||
height={320}
|
height={320}
|
||||||
widths={[400, 768]}
|
widths={[400, 768]}
|
||||||
aspectRatio="16:7"
|
layout="fullWidth"
|
||||||
{...(image as any)}
|
{...(image as any)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from 'astro-icon/components';
|
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';
|
import WidgetWrapper from '../ui/WidgetWrapper.astro';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -88,11 +88,10 @@ const {
|
|||||||
{typeof image === 'string' ? (
|
{typeof image === 'string' ? (
|
||||||
<Fragment set:html={image} />
|
<Fragment set:html={image} />
|
||||||
) : (
|
) : (
|
||||||
<Picture
|
<Image
|
||||||
class="mx-auto rounded-md w-full"
|
class="mx-auto rounded-md w-full"
|
||||||
widths={[400, 768, 1024, 2040]}
|
widths={[400, 768, 1024, 2040]}
|
||||||
sizes="(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px"
|
sizes="(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px"
|
||||||
aspectRatio={1024 / 576}
|
|
||||||
loading="eager"
|
loading="eager"
|
||||||
width={1024}
|
width={1024}
|
||||||
height={576}
|
height={576}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from 'astro-icon/components';
|
import { Icon } from 'astro-icon/components';
|
||||||
import { Picture } from '@astrojs/image/components';
|
import Image from '~/components/common/Image.astro';
|
||||||
import { CallToAction } from '~/types';
|
import { CallToAction } from '~/types';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -88,11 +88,10 @@ const {
|
|||||||
{typeof image === 'string' ? (
|
{typeof image === 'string' ? (
|
||||||
<Fragment set:html={image} />
|
<Fragment set:html={image} />
|
||||||
) : (
|
) : (
|
||||||
<Picture
|
<Image
|
||||||
class="mx-auto rounded-md w-full"
|
class="mx-auto rounded-md w-full"
|
||||||
widths={[400, 768, 1024, 2040]}
|
widths={[400, 768, 1024, 2040]}
|
||||||
sizes="(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px"
|
sizes="(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px"
|
||||||
aspectRatio={600 / 600}
|
|
||||||
loading="eager"
|
loading="eager"
|
||||||
width={600}
|
width={600}
|
||||||
height={600}
|
height={600}
|
||||||
|
@ -35,9 +35,9 @@ const {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{title && (
|
{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}
|
{title}
|
||||||
</h6>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import { Picture } from '@astrojs/image/components';
|
|
||||||
import WidgetWrapper from "~/components/ui/WidgetWrapper.astro";
|
import WidgetWrapper from "~/components/ui/WidgetWrapper.astro";
|
||||||
import Timeline from "~/components/ui/Timeline.astro";
|
import Timeline from "~/components/ui/Timeline.astro";
|
||||||
import Headline from "~/components/ui/Headline.astro";
|
import Headline from "~/components/ui/Headline.astro";
|
||||||
|
import Image from '~/components/common/Image.astro';
|
||||||
import type { Steps } from "~/types";
|
import type { Steps } from "~/types";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -37,13 +37,13 @@ const {
|
|||||||
(typeof image === 'string' ? (
|
(typeof image === 'string' ? (
|
||||||
<Fragment set:html={image} />
|
<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"
|
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]}
|
widths={[400, 768]}
|
||||||
sizes="(max-width: 768px) 100vw, 432px"
|
sizes="(max-width: 768px) 100vw, 432px"
|
||||||
aspectRatio="432:768"
|
|
||||||
width={432}
|
width={432}
|
||||||
height={768}
|
height={768}
|
||||||
|
layout="cover"
|
||||||
src={image?.src}
|
src={image?.src}
|
||||||
alt={image?.alt || ""}
|
alt={image?.alt || ""}
|
||||||
/>
|
/>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
import Headline from '~/components/ui/Headline.astro';
|
import Headline from '~/components/ui/Headline.astro';
|
||||||
import WidgetWrapper from '~/components/ui/WidgetWrapper.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 type { Testimonials } from '~/types';
|
||||||
import CTA from '../ui/CTA.astro';
|
|
||||||
import { Picture } from '@astrojs/image/components';
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title = '',
|
title = '',
|
||||||
@ -43,12 +44,12 @@ const {
|
|||||||
{typeof image === 'string' ? (
|
{typeof image === 'string' ? (
|
||||||
<Fragment set:html={image} />
|
<Fragment set:html={image} />
|
||||||
) : (
|
) : (
|
||||||
<Picture
|
<Image
|
||||||
class="h-10 w-10 rounded-full border border-slate-200 dark:border-slate-600"
|
class="h-10 w-10 rounded-full border border-slate-200 dark:border-slate-600"
|
||||||
width={40}
|
width={40}
|
||||||
height={40}
|
height={40}
|
||||||
widths={[400, 768]}
|
widths={[400, 768]}
|
||||||
aspectRatio="1:1"
|
layout="fixed"
|
||||||
{...(image as any)}
|
{...(image as any)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
publishDate: 2023-07-17T00:00:00Z
|
publishDate: 2023-07-17T00:00:00Z
|
||||||
title: AstroWind template in depth
|
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.
|
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
|
category: Documentation
|
||||||
tags:
|
tags:
|
||||||
- astro
|
- astro
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
publishDate: 2023-01-12T00:00:00Z
|
publishDate: 2023-01-12T00:00:00Z
|
||||||
title: Get started with AstroWind to create a website using Astro and Tailwind CSS
|
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.
|
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
|
category: Tutorials
|
||||||
tags:
|
tags:
|
||||||
- astro
|
- astro
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
publishDate: 2023-01-06T00:00:00Z
|
publishDate: 2023-01-06T00:00:00Z
|
||||||
title: How to customize AstroWind template to suit your branding
|
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.
|
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:
|
tags:
|
||||||
- astro
|
- astro
|
||||||
- tailwind css
|
- tailwind css
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
publishDate: 2023-01-09T00:00:00Z
|
publishDate: 2023-01-09T00:00:00Z
|
||||||
title: Useful tools and resources to create a professional website
|
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.
|
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:
|
tags:
|
||||||
- front-end
|
- front-end
|
||||||
- tools
|
- tools
|
||||||
|
2
src/env.d.ts
vendored
@ -1,3 +1,3 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
||||||
/// <reference path="../.astro/types.d.ts" />
|
/// <reference path="../.astro/types.d.ts" />
|
||||||
/// <reference types="@astrojs/image/client" />
|
/// <reference types="astro/client-image" />
|
||||||
|
@ -7,7 +7,9 @@ import Pagination from '~/components/blog/Pagination.astro';
|
|||||||
|
|
||||||
import { blogListRobots, getStaticPathsBlogList } from '~/utils/blog';
|
import { blogListRobots, getStaticPathsBlogList } from '~/utils/blog';
|
||||||
|
|
||||||
export const getStaticPaths = getStaticPathsBlogList();
|
export async function getStaticPaths ({ paginate }) {
|
||||||
|
return await getStaticPathsBlogList({ paginate });
|
||||||
|
}
|
||||||
|
|
||||||
const { page } = Astro.props;
|
const { page } = Astro.props;
|
||||||
const currentPage = page.currentPage ?? 1;
|
const currentPage = page.currentPage ?? 1;
|
||||||
|
@ -6,7 +6,9 @@ import BlogList from '~/components/blog/List.astro';
|
|||||||
import Headline from '~/components/blog/Headline.astro';
|
import Headline from '~/components/blog/Headline.astro';
|
||||||
import Pagination from '~/components/blog/Pagination.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;
|
const { page, category } = Astro.props;
|
||||||
|
|
||||||
|
@ -6,7 +6,9 @@ import BlogList from '~/components/blog/List.astro';
|
|||||||
import Headline from '~/components/blog/Headline.astro';
|
import Headline from '~/components/blog/Headline.astro';
|
||||||
import Pagination from '~/components/blog/Pagination.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;
|
const { page, tag } = Astro.props;
|
||||||
|
|
||||||
|
@ -9,7 +9,9 @@ import { getCanonical, getPermalink } from '~/utils/permalinks';
|
|||||||
import { getStaticPathsBlogPost, blogPostRobots } from '~/utils/blog';
|
import { getStaticPathsBlogPost, blogPostRobots } from '~/utils/blog';
|
||||||
import { findImage } from '~/utils/images';
|
import { findImage } from '~/utils/images';
|
||||||
|
|
||||||
export const getStaticPaths = getStaticPathsBlogPost();
|
export async function getStaticPaths () {
|
||||||
|
return await getStaticPathsBlogPost();
|
||||||
|
}
|
||||||
|
|
||||||
const { post } = Astro.props;
|
const { post } = Astro.props;
|
||||||
|
|
||||||
@ -26,7 +28,7 @@ const metadata = merge(
|
|||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
type: 'article',
|
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 } : {}) }
|
{ ...(post?.metadata ? { ...post.metadata, canonical: post.metadata?.canonical || url } : {}) }
|
||||||
|
@ -14,7 +14,7 @@ const metadata = {
|
|||||||
<Layout metadata={metadata}>
|
<Layout metadata={metadata}>
|
||||||
<!-- Hero Widget ******************* -->
|
<!-- 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">
|
<Fragment slot="title">
|
||||||
Elevate your online presence with our <br />
|
Elevate your online presence with our <br />
|
||||||
<span class="text-accent dark:text-white highlight"> Beautiful Website Templates</span>
|
<span class="text-accent dark:text-white highlight"> Beautiful Website Templates</span>
|
||||||
@ -112,7 +112,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
image={{
|
||||||
src: import('~/assets/images/colors.jpg'),
|
src: '~/assets/images/colors.jpg',
|
||||||
alt: 'Colorful Image',
|
alt: 'Colorful Image',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -24,7 +24,7 @@ const metadata = {
|
|||||||
<Hero
|
<Hero
|
||||||
callToAction={{ text: 'Get template', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
|
callToAction={{ text: 'Get template', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
|
||||||
callToAction2={{ text: 'Learn more', href: '#features' }}
|
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">
|
<Fragment slot="title">
|
||||||
Free template for <span class="hidden xl:inline">creating websites with</span>
|
Free template for <span class="hidden xl:inline">creating websites with</span>
|
||||||
@ -96,8 +96,9 @@ const metadata = {
|
|||||||
<!-- Content Widget **************** -->
|
<!-- Content Widget **************** -->
|
||||||
|
|
||||||
<Content
|
<Content
|
||||||
|
isReversed
|
||||||
tagline="Inside template"
|
tagline="Inside template"
|
||||||
title="And what's inside? ..."
|
title="AstroWind's Blueprint: Fun Meets Functionality!"
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
title: 'Per ei quaeque sensibus',
|
title: 'Per ei quaeque sensibus',
|
||||||
@ -116,7 +117,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
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',
|
alt: 'Colorful Image',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -133,6 +134,43 @@ const metadata = {
|
|||||||
|
|
||||||
<!-- Content Widget **************** -->
|
<!-- 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
|
<Content
|
||||||
isReversed
|
isReversed
|
||||||
isAfterContent
|
isAfterContent
|
||||||
@ -157,8 +195,8 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
image={{
|
||||||
src: import('~/assets/images/vintage.jpg'),
|
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: 'Vintage Image',
|
alt: 'Astronauts Image',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Fragment slot="bg">
|
<Fragment slot="bg">
|
||||||
@ -174,19 +212,19 @@ const metadata = {
|
|||||||
{
|
{
|
||||||
title: 'Step 1: <span class="font-medium">Download</span>',
|
title: 'Step 1: <span class="font-medium">Download</span>',
|
||||||
description:
|
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',
|
icon: 'tabler:package',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Step 2: <span class="font-medium">Add content</em>',
|
title: 'Step 2: <span class="font-medium">Add content</em>',
|
||||||
description:
|
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',
|
icon: 'tabler:letter-case',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Step 3: <span class="font-medium">Customize styles</span>',
|
title: 'Step 3: <span class="font-medium">Customize styles</span>',
|
||||||
description:
|
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',
|
icon: 'tabler:paint',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -195,7 +233,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
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',
|
alt: 'Steps image',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -210,55 +248,55 @@ const metadata = {
|
|||||||
{
|
{
|
||||||
title: 'Headers',
|
title: 'Headers',
|
||||||
description:
|
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',
|
icon: 'flat-color-icons:template',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Heros',
|
title: 'Heros',
|
||||||
description:
|
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',
|
icon: 'flat-color-icons:gallery',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Features',
|
title: 'Features',
|
||||||
description:
|
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',
|
icon: 'flat-color-icons:approval',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Content',
|
title: 'Content',
|
||||||
description:
|
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',
|
icon: 'flat-color-icons:document',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Call-to-Action',
|
title: 'Call-to-Action',
|
||||||
description:
|
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',
|
icon: 'flat-color-icons:advertising',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Pricing',
|
title: 'Pricing',
|
||||||
description:
|
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',
|
icon: 'flat-color-icons:currency-exchange',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Testimonial',
|
title: 'Testimonial',
|
||||||
description:
|
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',
|
icon: 'flat-color-icons:voice-presentation',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Contact',
|
title: 'Contact',
|
||||||
description:
|
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',
|
icon: 'flat-color-icons:business-contact',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Footers',
|
title: 'Footers',
|
||||||
description:
|
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',
|
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."
|
subtitle="Duis turpis dui, fringilla mattis sem nec, fringilla euismod neque. Morbi tincidunt lacus nec tortor scelerisque pulvinar."
|
||||||
tagline="FAQs"
|
tagline="FAQs"
|
||||||
items={[
|
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?',
|
title: 'What do I need to start?',
|
||||||
description:
|
description:
|
||||||
@ -300,11 +343,6 @@ const metadata = {
|
|||||||
description:
|
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.",
|
"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?',
|
title: 'What is something that you would like to try again?',
|
||||||
description:
|
description:
|
||||||
|
@ -40,7 +40,7 @@ const metadata = {
|
|||||||
<Hero2
|
<Hero2
|
||||||
callToAction={{ text: 'Download App', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
|
callToAction={{ text: 'Download App', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
|
||||||
callToAction2={{ text: 'Learn more', href: '#features' }}
|
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">
|
<Fragment slot="title">
|
||||||
Free template for <span class="hidden lg:inline">create your website <br />with</span>
|
Free template for <span class="hidden lg:inline">create your website <br />with</span>
|
||||||
@ -91,7 +91,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
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',
|
alt: 'Colorful Image',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -99,6 +99,7 @@ const metadata = {
|
|||||||
<!-- Content Widget **************** -->
|
<!-- Content Widget **************** -->
|
||||||
|
|
||||||
<Content
|
<Content
|
||||||
|
isReversed
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
title: 'High-Quality Designs',
|
title: 'High-Quality Designs',
|
||||||
@ -128,7 +129,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
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',
|
alt: 'Colorful Image',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -140,7 +141,6 @@ const metadata = {
|
|||||||
<!-- Content Widget **************** -->
|
<!-- Content Widget **************** -->
|
||||||
|
|
||||||
<Content
|
<Content
|
||||||
isReversed
|
|
||||||
isAfterContent
|
isAfterContent
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
@ -171,7 +171,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
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',
|
alt: 'Vintage Image',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -202,7 +202,7 @@ const metadata = {
|
|||||||
name: 'Cary Kennedy',
|
name: 'Cary Kennedy',
|
||||||
job: 'Film director',
|
job: 'Film director',
|
||||||
image: {
|
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',
|
alt: 'Cary Kennedy Image',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -212,7 +212,7 @@ const metadata = {
|
|||||||
name: 'Josh Wilkinson',
|
name: 'Josh Wilkinson',
|
||||||
job: 'Product Manager',
|
job: 'Product Manager',
|
||||||
image: {
|
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',
|
alt: 'Josh Wilkinson Image',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -222,7 +222,7 @@ const metadata = {
|
|||||||
name: 'Sidney Hansen',
|
name: 'Sidney Hansen',
|
||||||
job: 'Decorator',
|
job: 'Decorator',
|
||||||
image: {
|
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',
|
alt: 'Sidney Hansen Image',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3,6 +3,7 @@ import Layout from '~/layouts/PageLayout.astro';
|
|||||||
|
|
||||||
import Header from '~/components/widgets/Header.astro';
|
import Header from '~/components/widgets/Header.astro';
|
||||||
import Hero2 from '~/components/widgets/Hero2.astro';
|
import Hero2 from '~/components/widgets/Hero2.astro';
|
||||||
|
import Features from '~/components/widgets/Features.astro';
|
||||||
import Steps2 from '~/components/widgets/Steps2.astro';
|
import Steps2 from '~/components/widgets/Steps2.astro';
|
||||||
import Content from '~/components/widgets/Content.astro';
|
import Content from '~/components/widgets/Content.astro';
|
||||||
import CallToAction from '~/components/widgets/CallToAction.astro';
|
import CallToAction from '~/components/widgets/CallToAction.astro';
|
||||||
@ -39,7 +40,7 @@ const metadata = {
|
|||||||
<Hero2
|
<Hero2
|
||||||
callToAction={{ text: 'Get template', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
|
callToAction={{ text: 'Get template', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
|
||||||
callToAction2={{ text: 'Learn more', href: '#features' }}
|
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">
|
<Fragment slot="title">
|
||||||
Free template for <br />
|
Free template for <br />
|
||||||
@ -56,6 +57,51 @@ const metadata = {
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
</Hero2>
|
</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 Widget **************** -->
|
||||||
|
|
||||||
<Content
|
<Content
|
||||||
@ -78,7 +124,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
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',
|
alt: 'Colorful Image',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -115,7 +161,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
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',
|
alt: 'Colorful Image',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -153,7 +199,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
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',
|
alt: 'Colorful Image',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -43,14 +43,18 @@ const metadata = {
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
||||||
<Fragment slot="image">
|
<Fragment slot="image">
|
||||||
<iframe
|
<div class="relative h-0 pb-[56.25%]">
|
||||||
width="560"
|
<iframe
|
||||||
height="315"
|
width="560"
|
||||||
src="https://www.youtube.com/embed/dsTXcSeAZq8"
|
height="315"
|
||||||
title="YouTube video player"
|
src="https://www.youtube.com/embed/dsTXcSeAZq8"
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
title="YouTube video player"
|
||||||
allowfullscreen
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture;"
|
||||||
style="width:100%"></iframe>
|
allowfullscreen
|
||||||
|
class="absolute top-0 left-0 w-full h-full"
|
||||||
|
>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
</Hero>
|
</Hero>
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
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',
|
alt: 'Steps image',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -16,7 +16,7 @@ export const get = async () => {
|
|||||||
|
|
||||||
return rss({
|
return rss({
|
||||||
title: `${SITE_CONFIG.name}’s Blog`,
|
title: `${SITE_CONFIG.name}’s Blog`,
|
||||||
description: METADATA_CONFIG?.description,
|
description: METADATA_CONFIG?.description || "",
|
||||||
site: import.meta.env.SITE,
|
site: import.meta.env.SITE,
|
||||||
|
|
||||||
items: posts.map((post) => ({
|
items: posts.map((post) => ({
|
||||||
|
@ -27,7 +27,7 @@ const metadata = {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
slot="bg"
|
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>
|
</div>
|
||||||
</Hero>
|
</Hero>
|
||||||
@ -104,7 +104,7 @@ const metadata = {
|
|||||||
name: 'Emily Kennedy',
|
name: 'Emily Kennedy',
|
||||||
job: 'Front-end developer',
|
job: 'Front-end developer',
|
||||||
image: {
|
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',
|
alt: 'Emily Kennedy Image',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -113,7 +113,7 @@ const metadata = {
|
|||||||
name: 'Sarah Hansen',
|
name: 'Sarah Hansen',
|
||||||
job: 'Photographer',
|
job: 'Photographer',
|
||||||
image: {
|
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',
|
alt: 'Sarah Hansen Image',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -122,7 +122,7 @@ const metadata = {
|
|||||||
name: 'Mark Wilkinson',
|
name: 'Mark Wilkinson',
|
||||||
job: 'Small business owner',
|
job: 'Small business owner',
|
||||||
image: {
|
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',
|
alt: 'Mark Wilkinson Image',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -162,18 +162,16 @@ export const findLatestPosts = async ({ count }: { count?: number }): Promise<Ar
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
export const getStaticPathsBlogList =
|
export const getStaticPathsBlogList = async ({ paginate }) => {
|
||||||
() =>
|
if (!isBlogEnabled || !isBlogListRouteEnabled) return [];
|
||||||
async ({ paginate }) => {
|
return paginate(await fetchPosts(), {
|
||||||
if (!isBlogEnabled || !isBlogListRouteEnabled) return [];
|
params: { blog: BLOG_BASE || undefined },
|
||||||
return paginate(await fetchPosts(), {
|
pageSize: blogPostsPerPage,
|
||||||
params: { blog: BLOG_BASE || undefined },
|
});
|
||||||
pageSize: blogPostsPerPage,
|
};
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
export const getStaticPathsBlogPost = () => async () => {
|
export const getStaticPathsBlogPost = async () => {
|
||||||
if (!isBlogEnabled || !isBlogPostRouteEnabled) return [];
|
if (!isBlogEnabled || !isBlogPostRouteEnabled) return [];
|
||||||
return (await fetchPosts()).map((post) => ({
|
return (await fetchPosts()).map((post) => ({
|
||||||
params: {
|
params: {
|
||||||
@ -184,49 +182,45 @@ export const getStaticPathsBlogPost = () => async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
export const getStaticPathsBlogCategory =
|
export const getStaticPathsBlogCategory = async ({ paginate }) => {
|
||||||
() =>
|
if (!isBlogEnabled || !isBlogCategoryRouteEnabled) return [];
|
||||||
async ({ paginate }) => {
|
|
||||||
if (!isBlogEnabled || !isBlogCategoryRouteEnabled) return [];
|
|
||||||
|
|
||||||
const posts = await fetchPosts();
|
const posts = await fetchPosts();
|
||||||
const categories = new Set();
|
const categories = new Set();
|
||||||
posts.map((post) => {
|
posts.map((post) => {
|
||||||
typeof post.category === 'string' && categories.add(post.category.toLowerCase());
|
typeof post.category === 'string' && categories.add(post.category.toLowerCase());
|
||||||
});
|
});
|
||||||
|
|
||||||
return Array.from(categories).map((category: string) =>
|
return Array.from(categories).map((category: string) =>
|
||||||
paginate(
|
paginate(
|
||||||
posts.filter((post) => typeof post.category === 'string' && category === post.category.toLowerCase()),
|
posts.filter((post) => typeof post.category === 'string' && category === post.category.toLowerCase()),
|
||||||
{
|
{
|
||||||
params: { category: category, blog: CATEGORY_BASE || undefined },
|
params: { category: category, blog: CATEGORY_BASE || undefined },
|
||||||
pageSize: blogPostsPerPage,
|
pageSize: blogPostsPerPage,
|
||||||
props: { category },
|
props: { category },
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
export const getStaticPathsBlogTag =
|
export const getStaticPathsBlogTag = async ({ paginate }) => {
|
||||||
() =>
|
if (!isBlogEnabled || !isBlogTagRouteEnabled) return [];
|
||||||
async ({ paginate }) => {
|
|
||||||
if (!isBlogEnabled || !isBlogTagRouteEnabled) return [];
|
|
||||||
|
|
||||||
const posts = await fetchPosts();
|
const posts = await fetchPosts();
|
||||||
const tags = new Set();
|
const tags = new Set();
|
||||||
posts.map((post) => {
|
posts.map((post) => {
|
||||||
Array.isArray(post.tags) && post.tags.map((tag) => tags.add(tag.toLowerCase()));
|
Array.isArray(post.tags) && post.tags.map((tag) => tags.add(tag.toLowerCase()));
|
||||||
});
|
});
|
||||||
|
|
||||||
return Array.from(tags).map((tag: string) =>
|
return Array.from(tags).map((tag: string) =>
|
||||||
paginate(
|
paginate(
|
||||||
posts.filter((post) => Array.isArray(post.tags) && post.tags.find((elem) => elem.toLowerCase() === tag)),
|
posts.filter((post) => Array.isArray(post.tags) && post.tags.find((elem) => elem.toLowerCase() === tag)),
|
||||||
{
|
{
|
||||||
params: { tag: tag, blog: TAG_BASE || undefined },
|
params: { tag: tag, blog: TAG_BASE || undefined },
|
||||||
pageSize: blogPostsPerPage,
|
pageSize: blogPostsPerPage,
|
||||||
props: { tag },
|
props: { tag },
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
321
src/utils/images-optimization.ts
Normal 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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
import { getImage } from '@astrojs/image';
|
import { getImage } from 'astro:assets';
|
||||||
import type { OpenGraph } from '@astrolib/seo/src/types';
|
|
||||||
import type { ImageMetadata } from 'astro';
|
import type { ImageMetadata } from 'astro';
|
||||||
|
import type { OpenGraph } from '@astrolib/seo/src/types';
|
||||||
|
|
||||||
const load = async function () {
|
const load = async function () {
|
||||||
let images: Record<string, () => Promise<unknown>> | undefined = undefined;
|
let images: Record<string, () => Promise<unknown>> | undefined = undefined;
|
||||||
try {
|
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) {
|
} catch (e) {
|
||||||
// continue regardless of error
|
// 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') {
|
if (typeof imagePath !== 'string') {
|
||||||
return null;
|
return imagePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Absolute paths
|
||||||
if (imagePath.startsWith('http://') || imagePath.startsWith('https://') || imagePath.startsWith('/')) {
|
if (imagePath.startsWith('http://') || imagePath.startsWith('https://') || imagePath.startsWith('/')) {
|
||||||
return imagePath;
|
return imagePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!imagePath.startsWith('~/assets')) {
|
// Relative paths or not "~/assets/"
|
||||||
return null;
|
if (!imagePath.startsWith('~/assets/images')) {
|
||||||
} // For now only consume images using ~/assets alias (or absolute)
|
return imagePath;
|
||||||
|
}
|
||||||
|
|
||||||
const images = await fetchLocalImages();
|
const images = await fetchLocalImages();
|
||||||
const key = imagePath.replace('~/', '/src/');
|
const key = imagePath.replace('~/', '/src/');
|
||||||
|
|
||||||
return images && typeof images[key] === 'function'
|
return images && typeof images[key] === 'function'
|
||||||
? ((await images[key]()) as { default: unknown })['default']
|
? ((await images[key]()) as { default: ImageMetadata })['default']
|
||||||
: null;
|
: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -91,4 +94,4 @@ export const adaptOpenGraphImages = async (
|
|||||||
);
|
);
|
||||||
|
|
||||||
return { ...openGraph, ...(adaptedImages ? { images: adaptedImages } : {}) };
|
return { ...openGraph, ...(adaptedImages ? { images: adaptedImages } : {}) };
|
||||||
};
|
};
|
53
src/utils/tasks.mjs
Normal 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;
|