first init of new theme...
This commit is contained in:
parent
b0c44d5c4c
commit
820172283f
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Adam Matthiesen
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './tailwind-preset.js'
|
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"name": "@matthiesenxyz/astro-ghostcms-catppuccin-dark",
|
||||||
|
"description": "A dark theme made with Catppuccin and TailwindCSS for Astro-GhostCMS",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"homepage": "https://astro-ghostcms.xyz/",
|
||||||
|
"type": "module",
|
||||||
|
"license": "MIT",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"sideEffects": false,
|
||||||
|
"author": {
|
||||||
|
"email": "adam@matthiesen.xyz",
|
||||||
|
"name": "Adam Matthiesen - MatthiesenXYZ",
|
||||||
|
"url": "https://matthiesen.xyz"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"astro-ghostcms"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/MatthiesenXYZ/astro-ghostcms.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/MatthiesenXYZ/astro-ghostcms/issues",
|
||||||
|
"email": "issues@astro-ghostcms.xyz"
|
||||||
|
},
|
||||||
|
"main": "index.ts",
|
||||||
|
"files": [
|
||||||
|
"src",
|
||||||
|
"index.ts",
|
||||||
|
"tailwind-preset.js"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": "./index.ts",
|
||||||
|
"./index.astro": "./src/routes/index.astro",
|
||||||
|
"./[slug].astro": "./src/routes/[slug].astro",
|
||||||
|
"./tags.astro": "./src/routes/tags.astro",
|
||||||
|
"./authors.astro": "./src/routes/authors.astro",
|
||||||
|
"./tag/[slug].astro": "./src/routes/tag/[slug].astro",
|
||||||
|
"./author/[slug].astro": "./src/routes/author/[slug].astro",
|
||||||
|
"./archives/[...page].astro": "./src/routes/archives/[...page].astro"
|
||||||
|
},
|
||||||
|
"scripts": { },
|
||||||
|
"devDependencies": {
|
||||||
|
"@matthiesenxyz/astro-ghostcms": "*",
|
||||||
|
"@catppuccin/tailwindcss": "0.1.6"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"astro": "^4.2.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/tailwind": "^5.1.0",
|
||||||
|
"@fontsource-variable/inter": "^5.0.16",
|
||||||
|
"@matthiesenxyz/astro-ghostcms": "^3.1.8",
|
||||||
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
|
"tailwindcss": "^3.3.5",
|
||||||
|
"astro-navbar": "^2.3.0",
|
||||||
|
"astro-seo": "^0.8.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
---
|
||||||
|
import { getGhostImgPath } from "../utils";
|
||||||
|
import type { Settings, Author } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
export type Props = {
|
||||||
|
author: Author;
|
||||||
|
wide?: boolean;
|
||||||
|
addClass?: string;
|
||||||
|
settings: Settings;
|
||||||
|
showCover?: boolean;
|
||||||
|
};
|
||||||
|
const {
|
||||||
|
author,
|
||||||
|
wide = false,
|
||||||
|
settings,
|
||||||
|
showCover = true,
|
||||||
|
} = Astro.props as Props;
|
||||||
|
---
|
||||||
|
<div class="flex">
|
||||||
|
{author.profile_image && (
|
||||||
|
<a href={`/author/${author.slug}`} class="author-card-media">
|
||||||
|
<img
|
||||||
|
class="author-card-img"
|
||||||
|
data-srcset={`
|
||||||
|
${getGhostImgPath(settings.url, author.profile_image, 100)} 100w,
|
||||||
|
${getGhostImgPath(settings.url, author.profile_image, 300)} 300w
|
||||||
|
`}
|
||||||
|
srcset={`
|
||||||
|
${getGhostImgPath(settings.url, author.profile_image, 100)} 100w,
|
||||||
|
${getGhostImgPath(settings.url, author.profile_image, 300)} 300w
|
||||||
|
`}
|
||||||
|
data-sizes="auto"
|
||||||
|
data-src={getGhostImgPath(settings.url, author.profile_image, 300)}
|
||||||
|
src={getGhostImgPath(settings.url, author.profile_image, 300)}
|
||||||
|
alt={author.name}
|
||||||
|
sizes="316px"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
<div class="flex flex-col ml-2">
|
||||||
|
<div class="text-ctp-teal text-3xl font-semibold">
|
||||||
|
<a href={`/author/${author.slug}`}>{author.name}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{author.bio && <div class="text-ctp-blue">{author.bio}</div>}
|
||||||
|
|
||||||
|
<div class="text-ctp-red">
|
||||||
|
{author.count && author.count.posts && (
|
||||||
|
<div>
|
||||||
|
{author.count.posts > 0 ? `${author.count.posts} posts` : "No posts"}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{author.location && (
|
||||||
|
<div class="text-ctp-sapphire">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pin-map-fill text-ctp-flamingo inline" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M3.1 11.2a.5.5 0 0 1 .4-.2H6a.5.5 0 0 1 0 1H3.75L1.5 15h13l-2.25-3H10a.5.5 0 0 1 0-1h2.5a.5.5 0 0 1 .4.2l3 4a.5.5 0 0 1-.4.8H.5a.5.5 0 0 1-.4-.8z"/> <path fill-rule="evenodd" d="M4 4a4 4 0 1 1 4.5 3.969V13.5a.5.5 0 0 1-1 0V7.97A4 4 0 0 1 4 3.999z"/> </svg> {author.location}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,95 @@
|
||||||
|
---
|
||||||
|
export type Props = {
|
||||||
|
name: string,
|
||||||
|
image?: string | null,
|
||||||
|
count: number,
|
||||||
|
bio: string | null,
|
||||||
|
location: string | null,
|
||||||
|
website: string | null,
|
||||||
|
twitter: string | null,
|
||||||
|
facebook: string | null
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
name, image, bio, location, website, twitter, facebook, count
|
||||||
|
} = Astro.props as Props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<header class="author-card">
|
||||||
|
{ image ? <img src={image} alt="Author Image" class="author-image"> : (
|
||||||
|
<span class="author-image">
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path
|
||||||
|
d="M3.513 18.998C4.749 15.504 8.082 13 12 13s7.251 2.504 8.487 5.998C18.47 21.442 15.417 23 12 23s-6.47-1.558-8.487-4.002zM12 12c2.21 0 4-2.79 4-5s-1.79-4-4-4-4 1.79-4 4 1.79 5 4 5z"
|
||||||
|
fill="#FFF"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<div class="author-info">
|
||||||
|
<div class="author-name text-ctp-teal">{name}</div>
|
||||||
|
<div class="author-bio text-ctp-blue">
|
||||||
|
{bio ? bio : count > 0? count + " Posts" : "No Posts"}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{location && (
|
||||||
|
<div class="author-location text-ctp-sapphire">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pin-map-fill text-ctp-flamingo inline" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M3.1 11.2a.5.5 0 0 1 .4-.2H6a.5.5 0 0 1 0 1H3.75L1.5 15h13l-2.25-3H10a.5.5 0 0 1 0-1h2.5a.5.5 0 0 1 .4.2l3 4a.5.5 0 0 1-.4.8H.5a.5.5 0 0 1-.4-.8z"/> <path fill-rule="evenodd" d="M4 4a4 4 0 1 1 4.5 3.969V13.5a.5.5 0 0 1-1 0V7.97A4 4 0 0 1 4 3.999z"/> </svg> {location}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div class="author-links">
|
||||||
|
{website && (<a href={website} target="_blank" rel="noopener">Website</a>)}
|
||||||
|
{twitter && (<a href={twitter} target="_blank" rel="noopener">Twitter</a>)}
|
||||||
|
{facebook && (<a href={facebook} target="_blank" rel="noopener">Facebook</a>)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.author-card {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 8vmin;
|
||||||
|
margin-bottom: 6vmin;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-image {
|
||||||
|
width: 180px;
|
||||||
|
height: 180px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-info {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-name {
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-bio {
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-location {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-links {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-links a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #3498db;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,35 @@
|
||||||
|
---
|
||||||
|
import { getGhostImgPath } from "../utils";
|
||||||
|
import type { Settings, Post } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
export type Props = {
|
||||||
|
post: Post;
|
||||||
|
settings: Settings;
|
||||||
|
};
|
||||||
|
const { post, settings } = Astro.props as Props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<ul class="flex flex-wrap mr-4">
|
||||||
|
{post.authors && post.authors.map((author) => (
|
||||||
|
<li class="relative flex-shrink-0 m-0 p-0">
|
||||||
|
{author.profile_image ? (
|
||||||
|
<a href={`/author/${author.slug}`} class="block overflow-hidden w-14 h-14 border-2 rounded-full">
|
||||||
|
<img
|
||||||
|
src={getGhostImgPath(settings.url, author.profile_image, 100)}
|
||||||
|
alt={author.name}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<a href={`/author/${author.slug}`} class="block overflow-hidden w-14 h-14 border-2 rounded-full">
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path
|
||||||
|
d="M3.513 18.998C4.749 15.504 8.082 13 12 13s7.251 2.504 8.487 5.998C18.47 21.442 15.417 23 12 23s-6.47-1.558-8.487-4.002zM12 12c2.21 0 4-2.79 4-5s-1.79-4-4-4-4 1.79-4 4 1.79 5 4 5z"
|
||||||
|
fill="#FFF"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
import { getGhostImgPath } from "../utils";
|
||||||
|
import type { Settings } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
export type Props = {
|
||||||
|
image: string;
|
||||||
|
alt?: string;
|
||||||
|
caption?: string;
|
||||||
|
settings: Settings;
|
||||||
|
transitionName?: string;
|
||||||
|
};
|
||||||
|
const { image, alt, caption = "", settings, transitionName } = Astro.props as Props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<figure class="article-image">
|
||||||
|
<img
|
||||||
|
srcset={`
|
||||||
|
${getGhostImgPath(settings.url, image, 300)} 300w,
|
||||||
|
${getGhostImgPath(settings.url, image, 600)} 600w,
|
||||||
|
`}
|
||||||
|
sizes="(min-width: 300px) 600px, 92vw"
|
||||||
|
src={getGhostImgPath(settings.url, image, 2000)}
|
||||||
|
alt={alt}
|
||||||
|
transition:name={transitionName}
|
||||||
|
/>
|
||||||
|
{caption && <figcaption class="text-ctp-overlay2"><Fragment set:html={caption}></figcaption>}
|
||||||
|
</figure>
|
|
@ -0,0 +1,66 @@
|
||||||
|
---
|
||||||
|
import type { Settings, Post } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
import FeatureImage from "./FeatureImage.astro";
|
||||||
|
import AuthorList from "./AuthorList.astro";
|
||||||
|
import { formatDate } from "utils";
|
||||||
|
export type Props = {
|
||||||
|
posts: Post[];
|
||||||
|
settings: Settings;
|
||||||
|
};
|
||||||
|
const { posts, settings } = Astro.props as Props;
|
||||||
|
|
||||||
|
const latestFeatured = posts[0]
|
||||||
|
---
|
||||||
|
{latestFeatured && (
|
||||||
|
<main
|
||||||
|
class="grid place-items-center pt-16 pb-8 md:pt-12 md:pb-24">
|
||||||
|
{latestFeatured.feature_image && (
|
||||||
|
<FeatureImage
|
||||||
|
image={latestFeatured.feature_image}
|
||||||
|
alt={latestFeatured.feature_image_alt ? latestFeatured.feature_image_alt : latestFeatured.title}
|
||||||
|
caption={latestFeatured.feature_image_caption || "" }
|
||||||
|
settings={settings}
|
||||||
|
transitionName={`img-${latestFeatured.title}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
{latestFeatured.primary_tag && (
|
||||||
|
<section class="text-ctp-lavender">
|
||||||
|
<a href={`/tag/${latestFeatured.primary_tag.slug}`}><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-tag inline" viewBox="0 0 16 16">
|
||||||
|
<path d="M6 4.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0m-1 0a.5.5 0 1 0-1 0 .5.5 0 0 0 1 0"/>
|
||||||
|
<path d="M2 1h4.586a1 1 0 0 1 .707.293l7 7a1 1 0 0 1 0 1.414l-4.586 4.586a1 1 0 0 1-1.414 0l-7-7A1 1 0 0 1 1 6.586V2a1 1 0 0 1 1-1m0 5.586 7 7L13.586 9l-7-7H2z"/>
|
||||||
|
</svg> {latestFeatured.primary_tag.name}</a>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
<h2
|
||||||
|
class="text-ctp-red text-4xl lg:text-6xl xl:text-7xl font-bold lg:tracking-tight xl:tracking-tighter">
|
||||||
|
{latestFeatured && latestFeatured.title}
|
||||||
|
</h2>
|
||||||
|
<div class="flex justify-between ml-5">
|
||||||
|
<section class="flex flex-grow align-middle">
|
||||||
|
<AuthorList post={latestFeatured} settings={settings} />
|
||||||
|
<div class="text-ctp-overlay2">
|
||||||
|
{ latestFeatured.primary_author && (
|
||||||
|
<h4 class="text-ctp-teal">
|
||||||
|
{latestFeatured.primary_author.name}
|
||||||
|
</h4>
|
||||||
|
)}
|
||||||
|
<div class="text-ctp-overlay2">
|
||||||
|
<time class="text-ctp-sapphire" datetime={formatDate(latestFeatured.published_at?latestFeatured.published_at:latestFeatured.created_at)}
|
||||||
|
>{formatDate(latestFeatured.published_at?latestFeatured.published_at:latestFeatured.created_at)}
|
||||||
|
</time>
|
||||||
|
<span class="text-ctp-peach"
|
||||||
|
><span class="text-ctp-overlay2">•</span>
|
||||||
|
{latestFeatured.reading_time} min read
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="divider my-4"/>
|
||||||
|
<section id="ghostimport" class="text-ctp-subtext1">
|
||||||
|
{latestFeatured && <Fragment set:html={latestFeatured.html} />}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
)}
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
import type { Page } from 'astro';
|
||||||
|
const { page } = Astro.props as {page: Page};
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="page__actions">
|
||||||
|
{page.url.prev && (
|
||||||
|
<a class="action__go-to-x" href={page.url.prev} title="Go to Previous">
|
||||||
|
← Prev
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{page.url.next && (
|
||||||
|
<a class="action__go-to-x" href={page.url.next} title="Go to Next">
|
||||||
|
Next →
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* .page__actions {
|
||||||
|
@apply flex justify-center md:justify-end py-6 gap-2;
|
||||||
|
}
|
||||||
|
.action__go-to-x {
|
||||||
|
@apply text-base uppercase text-gray-500 dark:text-gray-400 hover:underline;
|
||||||
|
} */
|
||||||
|
</style>
|
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
import PostHero from "../components/PostHero.astro";
|
||||||
|
import PostFooter from "../components/PostFooter.astro";
|
||||||
|
import {invariant, type Post, type Settings } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
export type Props = {
|
||||||
|
post: Post;
|
||||||
|
settings: Settings;
|
||||||
|
posts: Post[];
|
||||||
|
};
|
||||||
|
const { post, settings, posts } = Astro.props as Props;
|
||||||
|
invariant(settings, "Settings not found");
|
||||||
|
---
|
||||||
|
|
||||||
|
<PostHero post={post} settings={settings} />
|
||||||
|
<div id="ghostimport" class="mt-4 text-ctp-subtext1">
|
||||||
|
<Fragment set:html={post.html} />
|
||||||
|
</div>
|
||||||
|
<PostFooter post={post} settings={settings} posts={posts} />
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
---
|
||||||
|
import PostPreview from "../components/PostPreview.astro";
|
||||||
|
import type { Settings, Post } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
export type Props = {
|
||||||
|
post: Post;
|
||||||
|
settings: Settings;
|
||||||
|
posts: Post[];
|
||||||
|
};
|
||||||
|
const { post, settings, posts } = Astro.props as Props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<aside class="read-more-wrap">
|
||||||
|
<div class="read-more inner">
|
||||||
|
{posts
|
||||||
|
.filter((p: Post) => p.id !== post.id)
|
||||||
|
.slice(0, 3)
|
||||||
|
.map((post: Post) => <PostPreview post={post} settings={settings} />)}
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* 7.3. Subscribe
|
||||||
|
/* ---------------------------------------------------------- */
|
||||||
|
|
||||||
|
.footer-cta {
|
||||||
|
position: relative;
|
||||||
|
padding: 9vmin 4vmin 10vmin;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
background: var(--color-darkgrey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Increases the default h2 size by 15%, for small and large screens */
|
||||||
|
.footer-cta h2 {
|
||||||
|
margin: 0 0 30px;
|
||||||
|
font-size: 3.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.footer-cta h2 {
|
||||||
|
font-size: 2.65rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-cta-button {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
padding: 5px 5px 5px 15px;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
color: var(--color-midgrey);
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-cta-button span {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 20px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
background: var(--ghost-accent-color);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 7.4. Read more
|
||||||
|
/* ---------------------------------------------------------- */
|
||||||
|
|
||||||
|
.read-more-wrap {
|
||||||
|
width: 100%;
|
||||||
|
padding: 4vmin;
|
||||||
|
margin: 0 auto -40px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
background: color-mod(var(--color-darkgrey) l(-5%));
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-more {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
grid-gap: 4vmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-more .post-card-title {
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-more .post-card-excerpt {
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-more .post-card-byline-content a {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1000px) {
|
||||||
|
.read-more {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
.read-more article:nth-child(3) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.read-more {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.read-more article:nth-child(2) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,51 @@
|
||||||
|
---
|
||||||
|
import FeatureImage from "../components/FeatureImage.astro";
|
||||||
|
import AuthorList from "../components/AuthorList.astro";
|
||||||
|
import { formatDate } from "../utils";
|
||||||
|
import type { Settings, Post } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
export type Props = {
|
||||||
|
post: Post;
|
||||||
|
settings: Settings;
|
||||||
|
};
|
||||||
|
const { post, settings } = Astro.props as Props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<header class="article-header gh-canvas">
|
||||||
|
{post.primary_tag && (
|
||||||
|
<section class="text-ctp-lavender">
|
||||||
|
<a href={`/tag/${post.primary_tag.slug}`}>{post.primary_tag.name}</a>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
<h1 class="text-ctp-red text-4xl lg:text-6xl xl:text-7xl font-bold lg:tracking-tight xl:tracking-tighter" transition:name={post.title}>{post.title}</h1>
|
||||||
|
|
||||||
|
<div class="flex justify-between my-4">
|
||||||
|
<section class="flex flex-grow align-middle">
|
||||||
|
<AuthorList post={post} settings={settings} />
|
||||||
|
<div class="text-ctp-overlay2">
|
||||||
|
{ post.primary_author && (
|
||||||
|
<h4 class="text-ctp-teal">
|
||||||
|
{post.primary_author.name}
|
||||||
|
</h4>
|
||||||
|
)}
|
||||||
|
<div class="text-ctp-overlay2">
|
||||||
|
<time class="text-ctp-sapphire" datetime={formatDate(post.created_at)}
|
||||||
|
>{formatDate(post.created_at)}
|
||||||
|
</time>
|
||||||
|
<span class="text-ctp-peach"
|
||||||
|
><span class="text-ctp-overlay2">•</span>
|
||||||
|
{post.reading_time} min read
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{post.feature_image && (
|
||||||
|
<FeatureImage
|
||||||
|
image={post.feature_image}
|
||||||
|
alt={post.feature_image_alt ? post.feature_image_alt : post.title}
|
||||||
|
caption={post.feature_image_caption || "" }
|
||||||
|
settings={settings}
|
||||||
|
transitionName={`img-${post.title}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</header>
|
|
@ -0,0 +1,65 @@
|
||||||
|
---
|
||||||
|
import { getGhostImgPath, formatDate } from "../utils";
|
||||||
|
import AuthorList from "./AuthorList.astro";
|
||||||
|
import type { Settings, Post, Tag } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
export type Props = {
|
||||||
|
post: Post;
|
||||||
|
settings: Settings;
|
||||||
|
index?: number;
|
||||||
|
isHome?: boolean;
|
||||||
|
};
|
||||||
|
const { post, settings, index, isHome = false } = Astro.props as Props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<article
|
||||||
|
class={`relative flex flex-col bg-cover bg-ctp-surface0 break-words ${post.tags && post.tags
|
||||||
|
.map((tag: Tag) => `tag-${tag.slug}`)
|
||||||
|
.join(" ")} ${
|
||||||
|
isHome && post.feature_image && index == 0 ? "lg:grid-cols-1 lg:col-span-3 lg:gap-4 lg:border-t-0" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<a class="relative grid-cols-1 col-span-2 mb-0" href={`/${post.slug}`}>
|
||||||
|
<img
|
||||||
|
class="relative flex overflow-hidden border-r-2 mb-0"
|
||||||
|
srcset={`
|
||||||
|
${getGhostImgPath(settings.url, post.feature_image || "", 300)} 300w,
|
||||||
|
${getGhostImgPath(settings.url, post.feature_image || "", 600)} 600w
|
||||||
|
${getGhostImgPath(settings.url, post.feature_image || "", 1000)} 1000w
|
||||||
|
${getGhostImgPath(settings.url, post.feature_image || "", 2000)} 2000w
|
||||||
|
`}
|
||||||
|
src={`${getGhostImgPath(settings.url, post.feature_image || "", 600)}`}
|
||||||
|
alt={post.title}
|
||||||
|
sizes="(max-width: 1000px) 400px, 800px"
|
||||||
|
loading="lazy"
|
||||||
|
transition:name={`img-${post.title}`}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<div class="relative justify-center">
|
||||||
|
<a class="relative p-0" href={`/${post.slug}`} data-astro-reload>
|
||||||
|
<header class="mt-0 ml-5">
|
||||||
|
{post.primary_tag && (
|
||||||
|
<div class="ml-1 text-ctp-lavender text-sm">{post.primary_tag.name}</div>
|
||||||
|
)}
|
||||||
|
<h2 class="mt-0 text-2xl font-bold text-ctp-red" transition:name={post.title}>{post.title}</h2>
|
||||||
|
</header>
|
||||||
|
<div class="px-4 mb-6 text-base text-ctp-subtext1 max-w-fit">
|
||||||
|
<p>{post.excerpt}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<footer class="flex align-middle items-center p-2">
|
||||||
|
<AuthorList post={post} settings={settings} />
|
||||||
|
<div class="flex flex-1 flex-col ml-2 text-ctp-teal text-lg">
|
||||||
|
<span class="text-sm">{post.primary_author?.name ?? ""}</span>
|
||||||
|
<span class="text-sm text-ctp-sapphire"
|
||||||
|
><time datetime={formatDate(post.created_at)}
|
||||||
|
>{formatDate(post.created_at)}
|
||||||
|
</time>
|
||||||
|
<span class="text-ctp-peach"
|
||||||
|
><span class="text-ctp-overlay2">•</span>
|
||||||
|
{post.reading_time} min read
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</article>
|
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
import PostPreview from "./PostPreview.astro";
|
||||||
|
import type { Settings, Post } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
export type Props = {
|
||||||
|
posts: Post[];
|
||||||
|
settings: Settings;
|
||||||
|
isHome?: boolean;
|
||||||
|
};
|
||||||
|
const { posts, settings, isHome = false } = Astro.props as Props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="relative grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 lg:gap-8 p-4">
|
||||||
|
{posts.map((post: Post, index: number) => (
|
||||||
|
<PostPreview
|
||||||
|
post={post}
|
||||||
|
index={index}
|
||||||
|
settings={settings}
|
||||||
|
isHome={isHome}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
import { getGhostImgPath } from "../utils";
|
||||||
|
import type { Settings, Tag } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
export type Props = {
|
||||||
|
tag: Tag;
|
||||||
|
addClass?: string;
|
||||||
|
settings: Settings;
|
||||||
|
};
|
||||||
|
const { tag, addClass = "", settings } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<a
|
||||||
|
href={`/tag/${tag.slug}`}
|
||||||
|
title={tag.name}
|
||||||
|
aria-label={tag.name}
|
||||||
|
class={`text-center`}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
tag.feature_image && (
|
||||||
|
<div class="tag-card-media">
|
||||||
|
<img
|
||||||
|
class="tag-card-img"
|
||||||
|
data-srcset={`
|
||||||
|
${getGhostImgPath(settings.url, tag.feature_image, 100)} 100w,
|
||||||
|
${getGhostImgPath(settings.url, tag.feature_image, 300)} 300w
|
||||||
|
`}
|
||||||
|
srcset={`
|
||||||
|
${getGhostImgPath(settings.url, tag.feature_image, 100)} 100w,
|
||||||
|
${getGhostImgPath(settings.url, tag.feature_image, 300)} 300w
|
||||||
|
`}
|
||||||
|
data-sizes="auto"
|
||||||
|
data-src={getGhostImgPath(settings.url, tag.feature_image, 300)}
|
||||||
|
src={getGhostImgPath(settings.url, tag.feature_image, 300)}
|
||||||
|
alt={tag.name}
|
||||||
|
sizes="200px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<h3 class="text-ctp-lavender">{tag.name}</h4>
|
||||||
|
<h4 class="text-ctp-teal">{tag.count?.posts} Posts<h4>
|
||||||
|
|
||||||
|
</a>
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
const { class: className } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class:list={["max-w-screen-xl mx-auto px-5", className]}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
import { getSettings, invariant } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
const settings = await getSettings();
|
||||||
|
invariant(settings, 'Settings not found');
|
||||||
|
---
|
||||||
|
<footer class="my-20">
|
||||||
|
<p class="text-center text-sm text-slate-500">
|
||||||
|
Copyright © {new Date().getFullYear()} {settings.title}. All rights reserved.
|
||||||
|
</p>
|
||||||
|
<p class="text-center text-xs text-slate-500 mt-1">
|
||||||
|
Powered by <a
|
||||||
|
href="https://ghost.org"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
class="hover:underline">
|
||||||
|
Ghost
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</footer>
|
|
@ -0,0 +1,82 @@
|
||||||
|
---
|
||||||
|
import { facebook, getSettings, invariant, twitter } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
const settings = await getSettings();
|
||||||
|
invariant(settings, 'Settings not found');
|
||||||
|
import Container from "./container.astro";
|
||||||
|
import { Astronav, MenuItems, MenuIcon } from "astro-navbar";
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<Container>
|
||||||
|
<header class="flex flex-col lg:flex-row justify-between items-center my-5">
|
||||||
|
<Astronav>
|
||||||
|
<div class="flex w-full lg:w-auto items-center justify-between">
|
||||||
|
<a href="/" class="text-lg">
|
||||||
|
{settings.icon && <img src={settings.icon} width="64" class="flex-1 inline">}
|
||||||
|
<span class="font-bold flex-2 text-ctp-blue">{settings.title}</span>
|
||||||
|
</a>
|
||||||
|
<div class="block lg:hidden">
|
||||||
|
<MenuIcon class="w-4 h-4 text-ctp-pink" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MenuItems class="hidden w-full lg:w-auto mt-2 lg:flex lg:mt-0">
|
||||||
|
<ul class="flex flex-col lg:flex-row lg:gap-3">
|
||||||
|
{ settings.navigation.map(({ label, url }) => (
|
||||||
|
<li>
|
||||||
|
<a href={url}
|
||||||
|
class="flex lg:px-3 py-2 items-center text-ctp-teal hover:text-ctp-green">
|
||||||
|
<span> {label}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)) }
|
||||||
|
</ul>
|
||||||
|
<div class="lg:hidden flex items-center mt-3 gap-4">
|
||||||
|
{ settings.facebook && (
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href={facebook(settings.facebook)}
|
||||||
|
title="Facebook"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="text-ctp-blue bi bi-facebook" viewBox="0 0 16 16"> <path d="M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951"/> </svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{settings.twitter && (
|
||||||
|
<a
|
||||||
|
class="gh-social-twitter"
|
||||||
|
href={twitter(settings.twitter)}
|
||||||
|
title="Twitter"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="text-ctp-subtext1 bi bi-twitter-x" viewBox="0 0 16 16"> <path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865z"/> </svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</MenuItems>
|
||||||
|
</Astronav>
|
||||||
|
<div>
|
||||||
|
<div class="hidden lg:flex items-center gap-4">
|
||||||
|
{ settings.facebook && (
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href={facebook(settings.facebook)}
|
||||||
|
title="Facebook"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="text-ctp-blue bi bi-facebook" viewBox="0 0 16 16"> <path d="M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951"/> </svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{settings.twitter && (
|
||||||
|
<a
|
||||||
|
class="gh-social-twitter"
|
||||||
|
href={twitter(settings.twitter)}
|
||||||
|
title="Twitter"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="text-ctp-subtext1 bi bi-twitter-x" viewBox="0 0 16 16"> <path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865z"/> </svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</Container>
|
|
@ -0,0 +1,2 @@
|
||||||
|
/// <reference path="../.astro/types.d.ts" />
|
||||||
|
/// <reference types="astro/client" />
|
|
@ -0,0 +1,55 @@
|
||||||
|
---
|
||||||
|
import { SEO } from "astro-seo";
|
||||||
|
import Footer from "../components/footer.astro";
|
||||||
|
import Navbar from "../components/navbar.astro";
|
||||||
|
import "@fontsource-variable/inter/index.css";
|
||||||
|
import { type Settings } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
import { getOgImagePath } from "@matthiesenxyz/astro-ghostcms/satoriOG";
|
||||||
|
import "../styles/global.css"
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
settings: Settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
const canonicalURL = new URL(Astro.url.pathname, Astro.site).toString();
|
||||||
|
|
||||||
|
const ogI = new URL(getOgImagePath(Astro.url.pathname), Astro.url.origin).href;
|
||||||
|
|
||||||
|
const { title, description, settings } = Astro.props;
|
||||||
|
|
||||||
|
const makeTitle = title
|
||||||
|
? title + ' | ' + settings.title
|
||||||
|
: settings.title + ' | ' + settings.description;
|
||||||
|
|
||||||
|
const makeDesc = description ? description : settings.description
|
||||||
|
---
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="bg-ctp-base">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href={settings.icon} />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
|
||||||
|
<!-- <link rel="preload" as="image" href={src} alt="Hero" /> -->
|
||||||
|
<SEO
|
||||||
|
title={makeTitle}
|
||||||
|
description={makeDesc}
|
||||||
|
canonical={canonicalURL}
|
||||||
|
openGraph={{
|
||||||
|
basic: {url: canonicalURL, type: "website",
|
||||||
|
title: makeTitle, image: ogI,},
|
||||||
|
optional: {siteName:settings.title, description: makeDesc},
|
||||||
|
image: {alt:makeTitle},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<Navbar />
|
||||||
|
<slot />
|
||||||
|
<Footer />
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,42 @@
|
||||||
|
---
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
import Container from "../components/container.astro";
|
||||||
|
import { getAllPosts, getAllPages, getSettings, invariant } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
import type { InferGetStaticPropsType } from "astro";
|
||||||
|
import Post from "../components/Post.astro";
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const [posts, pages, settings] = await Promise.all([getAllPosts(), await getAllPages(), await getSettings()]);
|
||||||
|
const allPosts = [...posts, ...pages];
|
||||||
|
return allPosts.map((post) => ({
|
||||||
|
params: { slug: post.slug },
|
||||||
|
props: { post, posts, settings },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Props = InferGetStaticPropsType<typeof getStaticPaths>;
|
||||||
|
const {post, posts, settings} = Astro.props as Props;
|
||||||
|
invariant(settings, "Settings are required");
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout
|
||||||
|
title={post.title}
|
||||||
|
description={post.excerpt}
|
||||||
|
settings={settings}>
|
||||||
|
|
||||||
|
<Container>
|
||||||
|
|
||||||
|
{
|
||||||
|
post.primary_author && (
|
||||||
|
<Post
|
||||||
|
post={post}
|
||||||
|
settings={settings}
|
||||||
|
posts={posts}
|
||||||
|
/>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
</Layout>
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
import PostPreviewList from "../../components/PostPreviewList.astro";
|
||||||
|
import Container from "../../components/container.astro";
|
||||||
|
import Layout from "../../layouts/Layout.astro";
|
||||||
|
import { getAllPosts, getSettings, invariant, type Post } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
import type { GetStaticPathsOptions, Page } from "astro";
|
||||||
|
import Paginator from "../../components/Paginator.astro";
|
||||||
|
|
||||||
|
export async function getStaticPaths({ paginate }:GetStaticPathsOptions) {
|
||||||
|
const posts = await getAllPosts();
|
||||||
|
return paginate(posts, {
|
||||||
|
pageSize: 5,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
page: Page<Post>
|
||||||
|
};
|
||||||
|
|
||||||
|
const settings = await getSettings();
|
||||||
|
invariant(settings, "Settings are required");
|
||||||
|
|
||||||
|
const title = settings.title;
|
||||||
|
const description = settings.description;
|
||||||
|
const { page } = Astro.props as Props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title={title} description={description} settings={settings}>
|
||||||
|
<Container>
|
||||||
|
<div class="divider my-2" />
|
||||||
|
<PostPreviewList posts={page.data} settings={settings} />
|
||||||
|
<Paginator {page} />
|
||||||
|
</Container>
|
||||||
|
</Layout>
|
|
@ -0,0 +1,45 @@
|
||||||
|
---
|
||||||
|
import type { InferGetStaticParamsType, InferGetStaticPropsType } from 'astro';
|
||||||
|
import Container from "../../components/container.astro";
|
||||||
|
import Layout from "../../layouts/Layout.astro";
|
||||||
|
import AuthorDetailCard from '../../components/AuthorDetailCard.astro';
|
||||||
|
import { getAllPosts, getAllAuthors, getSettings, invariant } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
import PostPreviewList from '../../components/PostPreviewList.astro';
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const posts = await getAllPosts();
|
||||||
|
const { authors } = await getAllAuthors();
|
||||||
|
const settings = await getSettings();
|
||||||
|
|
||||||
|
return authors.map((author) => {
|
||||||
|
const filteredPosts = posts.filter((post) =>
|
||||||
|
post.authors?.map((author) => author.slug).includes(author.slug)
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
params: { slug: author.slug },
|
||||||
|
props: {
|
||||||
|
posts: filteredPosts,
|
||||||
|
settings,
|
||||||
|
author,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Params = InferGetStaticParamsType<typeof getStaticPaths>;
|
||||||
|
export type Props = InferGetStaticPropsType<typeof getStaticPaths>;
|
||||||
|
|
||||||
|
const { posts, settings, author } = Astro.props;
|
||||||
|
invariant(settings, "Settings are required");
|
||||||
|
const title = `Posts by author: ${author.name}`;
|
||||||
|
const description = `All of the articles we've posted and linked so far under the author: ${author.name}`;
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title={title} description={description} settings={settings}>
|
||||||
|
<Container>
|
||||||
|
<section class="outer">
|
||||||
|
<AuthorDetailCard name={author.name} count={author.count?.posts || 0} image={author.profile_image} bio={author.bio} location={author.location} website={author.website} twitter={author.twitter} facebook={author.facebook}/>
|
||||||
|
<PostPreviewList posts={posts} settings={settings} />
|
||||||
|
</section>
|
||||||
|
</Container>
|
||||||
|
</Layout>
|
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
import AuthorCard from "../components/AuthorCard.astro";
|
||||||
|
import Container from "../components/container.astro";
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
import { getAllAuthors, getSettings, invariant } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
|
||||||
|
let title = "All Authors";
|
||||||
|
let description = "All the authors";
|
||||||
|
const { authors } = await getAllAuthors();
|
||||||
|
const settings = await getSettings();
|
||||||
|
invariant(settings, 'Settings not found');
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title={title} description={description} settings={settings}>
|
||||||
|
<Container>
|
||||||
|
<section class="outer">
|
||||||
|
<div class="text-center text-4xl font-bold text-ctp-pink">
|
||||||
|
Collection of Authors
|
||||||
|
</div>
|
||||||
|
<div class="grid-flow-col grid grid-cols-3 items-center ">
|
||||||
|
{authors.map((author) => (
|
||||||
|
<article class="bg-ctp-surface0">
|
||||||
|
<AuthorCard author={author} settings={settings} />
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Container>
|
||||||
|
</Layout>
|
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
import PostPreviewList from "../components/PostPreviewList.astro";
|
||||||
|
import FeaturedPost from "../components/FeaturedPost.astro";
|
||||||
|
import Container from "../components/container.astro";
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
import { getPosts, getFeaturedPosts, getSettings, invariant, type Post } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
const { posts:featuredposts} = await getFeaturedPosts();
|
||||||
|
const settings = await getSettings();
|
||||||
|
invariant(settings, 'Settings not found');
|
||||||
|
|
||||||
|
async function getPostsSet(){
|
||||||
|
const featuredPosts = await getFeaturedPosts();
|
||||||
|
const { posts } = await getPosts();
|
||||||
|
if(featuredPosts.posts.length === 0){ return posts }
|
||||||
|
else {
|
||||||
|
const featured = featuredPosts.posts[0]
|
||||||
|
return posts.filter((p: Post)=>p.id !== featured.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mPosts = await getPostsSet()
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title="" description="" settings={settings}>
|
||||||
|
<Container>
|
||||||
|
<FeaturedPost posts={featuredposts} settings={settings}/>
|
||||||
|
<div class="divider my-2" />
|
||||||
|
<PostPreviewList posts={mPosts} settings={settings} />
|
||||||
|
</Container>
|
||||||
|
</Layout>
|
|
@ -0,0 +1,64 @@
|
||||||
|
---
|
||||||
|
import type { InferGetStaticParamsType, InferGetStaticPropsType } from 'astro';
|
||||||
|
import Container from "../../components/container.astro";
|
||||||
|
import Layout from "../../layouts/Layout.astro";
|
||||||
|
import { getAllPosts, getAllTags, getSettings, invariant } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
import PostPreview from '../../components/PostPreview.astro';
|
||||||
|
import { getGhostImgPath } from '../../utils';
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const posts = await getAllPosts();
|
||||||
|
const { tags } = await getAllTags();
|
||||||
|
const settings = await getSettings();
|
||||||
|
|
||||||
|
return tags.map((tag) => {
|
||||||
|
const filteredPosts = posts.filter((post) =>
|
||||||
|
post.tags?.map((tag) => tag.slug).includes(tag.slug)
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
params: { slug: tag.slug },
|
||||||
|
props: {
|
||||||
|
posts: filteredPosts,
|
||||||
|
settings,
|
||||||
|
tag,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Params = InferGetStaticParamsType<typeof getStaticPaths>;
|
||||||
|
export type Props = InferGetStaticPropsType<typeof getStaticPaths>;
|
||||||
|
|
||||||
|
const { posts, settings, tag } = Astro.props;
|
||||||
|
invariant(settings, "Settings are required");
|
||||||
|
const title = `Posts by author: ${tag.name}`;
|
||||||
|
const description = `All of the articles we've posted and linked so far under the author: ${tag.name}`;
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title={title} description={description} settings={settings}>
|
||||||
|
<Container>
|
||||||
|
<section class="post-card post-card-large">
|
||||||
|
|
||||||
|
<div class="post-card-content">
|
||||||
|
<div class="post-card-content-link">
|
||||||
|
<header class="post-card-header">
|
||||||
|
<div class="text-ctp-maroon text-4xl font-bold">Tagged</div>
|
||||||
|
<h2 class="text-ctp-lavender text-2xl font-semibold">{tag.name}</h2>
|
||||||
|
</header>
|
||||||
|
<div>
|
||||||
|
<p class="text-ctp-sky">
|
||||||
|
{tag.description
|
||||||
|
? tag.description
|
||||||
|
: `A collection of ${tag.count?.posts || 0 } Post${
|
||||||
|
tag.count?.posts ?? 0 > 1 ? "s" : ""
|
||||||
|
}`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{posts.map((post, index) => (
|
||||||
|
<PostPreview post={post} index={index} settings={settings} />
|
||||||
|
))}
|
||||||
|
</Container>
|
||||||
|
</Layout>
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
import TagCard from "../components/TagCard.astro";
|
||||||
|
import Container from "../components/container.astro";
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
import { getAllTags, getSettings, invariant } from "@matthiesenxyz/astro-ghostcms/api";
|
||||||
|
|
||||||
|
let title = "All Tags";
|
||||||
|
let description = "All the tags used so far...";
|
||||||
|
const { tags } = await getAllTags();
|
||||||
|
const settings = await getSettings();
|
||||||
|
invariant(settings, 'Settings not found');
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title={title} description={description} settings={settings}>
|
||||||
|
<Container>
|
||||||
|
<section class="outer">
|
||||||
|
<div class="text-center text-4xl font-bold text-ctp-pink">
|
||||||
|
Collection of Tags
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-flow-col gap-4">
|
||||||
|
{
|
||||||
|
tags
|
||||||
|
.filter((tag) => tag.slug && !tag.slug.startsWith("hash-"))
|
||||||
|
.map((tag) => (
|
||||||
|
<article class="bg-ctp-surface0">
|
||||||
|
<TagCard tag={tag} settings={settings} />
|
||||||
|
</article>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Container>
|
||||||
|
</Layout>
|
|
@ -0,0 +1,55 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Lots of classes hidden here to showcase the catppuccin color classes
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ghostimport h1 {
|
||||||
|
@apply font-black text-5xl lg:text-6xl mb-4 bg-gradient-to-r bg-clip-text text-ctp-pink;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ghostimport h2 {
|
||||||
|
@apply font-black text-4xl lg:text-6xl mb-4 bg-gradient-to-r bg-clip-text text-ctp-teal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ghostimport h3 {
|
||||||
|
@apply font-black text-2xl lg:text-6xl mb-4 bg-gradient-to-r bg-clip-text text-ctp-sapphire;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ghostimport h4 {
|
||||||
|
@apply font-black text-xl lg:text-6xl mb-4 bg-gradient-to-r bg-clip-text text-ctp-mauve;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ghostimport pre {
|
||||||
|
@apply bg-ctp-crust px-2 text-ctp-rosewater mb-3
|
||||||
|
}
|
||||||
|
|
||||||
|
#ghostimport p {
|
||||||
|
@apply py-2
|
||||||
|
}
|
||||||
|
|
||||||
|
#ghostimport table{
|
||||||
|
@apply border-2
|
||||||
|
}
|
||||||
|
#ghostimport table > thead{
|
||||||
|
@apply text-ctp-green border-2
|
||||||
|
}
|
||||||
|
#ghostimport table > tbody > tr{
|
||||||
|
@apply border-2
|
||||||
|
}
|
||||||
|
#ghostimport table > tbody > tr > td{
|
||||||
|
@apply border-2 p-2
|
||||||
|
}
|
||||||
|
|
||||||
|
#ghostimport ul {
|
||||||
|
list-style: circle;
|
||||||
|
margin-left: 2rem;
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ghostimport ul > li {
|
||||||
|
@apply text-ctp-blue
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
export const getGhostImgPath = (
|
||||||
|
baseUrl: string,
|
||||||
|
imgUrl: string,
|
||||||
|
width = 0
|
||||||
|
): string => {
|
||||||
|
if (!imgUrl) return "";
|
||||||
|
if (!imgUrl.startsWith(baseUrl)) {
|
||||||
|
return imgUrl;
|
||||||
|
}
|
||||||
|
const relativePath = imgUrl.substring(`${baseUrl}content/images`.length);
|
||||||
|
const cleanedBaseUrl = baseUrl.replace(/\/~/, "");
|
||||||
|
if (width && width > 0) {
|
||||||
|
return `${cleanedBaseUrl}content/images/size/w${width}/${relativePath}`;
|
||||||
|
}
|
||||||
|
return `${cleanedBaseUrl}content/images/${width}${relativePath}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const truncate = (input: string, size: number): string =>
|
||||||
|
input.length > size ? `${input.substring(0, size)}...` : input;
|
||||||
|
|
||||||
|
export const formatDate = (dateInput: string): string => {
|
||||||
|
const dateObject = new Date(dateInput);
|
||||||
|
return dateObject.toDateString();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const uniqWith = <T>(
|
||||||
|
arr: Array<T>,
|
||||||
|
fn: (element: T, step: T) => number
|
||||||
|
): Array<T> =>
|
||||||
|
arr.filter(
|
||||||
|
(element, index) => arr.findIndex((step) => fn(element, step)) === index
|
||||||
|
);
|
|
@ -0,0 +1,14 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
import { fontFamily as _fontFamily } from "tailwindcss/defaultTheme";
|
||||||
|
export const content = ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"];
|
||||||
|
export const theme = {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: ["Inter Variable", "Inter", ..._fontFamily.sans],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export const plugins = [
|
||||||
|
require("@tailwindcss/typography"),
|
||||||
|
require("@catppuccin/tailwindcss")({ prefix: 'ctp', defaultFlavour: "macchiato" })
|
||||||
|
];
|
|
@ -78,7 +78,6 @@ const { post, settings, index, isHome = false } = Astro.props as Props;
|
||||||
|
|
||||||
.post-card-excerpt {
|
.post-card-excerpt {
|
||||||
max-width: 56em;
|
max-width: 56em;
|
||||||
color: color.scale($color-midgrey, $lightness: -8%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-card-image-link {
|
.post-card-image-link {
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import { defineConfig } from "astro/config";
|
import { defineConfig } from "astro/config";
|
||||||
|
import tailwind from "@astrojs/tailwind";
|
||||||
import ghostcms from "@matthiesenxyz/astro-ghostcms";
|
import ghostcms from "@matthiesenxyz/astro-ghostcms";
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: "https://demo.astro-ghostcms.xyz/",
|
site: "https://demo.astro-ghostcms.xyz/",
|
||||||
integrations: [
|
integrations: [tailwind(),
|
||||||
ghostcms({
|
ghostcms({
|
||||||
disable404: false,
|
disable404: false,
|
||||||
disableRSS: false,
|
disableRSS: false,
|
||||||
disableRouteInjection: false,
|
disableRouteInjection: false,
|
||||||
disableConsoleOutput: false,
|
disableConsoleOutput: false,
|
||||||
theme: "@matthiesenxyz/astro-ghostcms-theme-default",
|
theme: "@matthiesenxyz/astro-ghostcms-theme-catpuccin-dark",
|
||||||
ghostURL: "https://ghostdemo.matthiesen.xyz",
|
ghostURL: "https://ghostdemo.matthiesen.xyz",
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
|
|
|
@ -13,9 +13,12 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^4.2.6",
|
"astro": "^4.2.6",
|
||||||
"@matthiesenxyz/astro-ghostcms-theme-default": "*",
|
"@matthiesenxyz/astro-ghostcms-theme-default": "*",
|
||||||
"@matthiesenxyz/astro-ghostcms": "workspace:*"
|
"@matthiesenxyz/astro-ghostcms": "workspace:*",
|
||||||
|
"@astrojs/tailwind": "^5.1.0",
|
||||||
|
"tailwindcss": "^3.3.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@matthiesenxyz/astro-ghostcms-theme-catppuccin-dark": "*",
|
||||||
"@astrojs/check": "^0.4.1",
|
"@astrojs/check": "^0.4.1",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
presets: [require('@matthiesenxyz/astro-ghostcms-theme-catppuccin-dark')]
|
||||||
|
};
|
Loading…
Reference in New Issue