first init of new theme...

This commit is contained in:
Adam Matthiesen 2024-01-31 13:12:54 -08:00
parent b0c44d5c4c
commit 820172283f
34 changed files with 1262 additions and 4 deletions

View File

@ -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.

View File

@ -0,0 +1 @@
export * from './tailwind-preset.js'

View File

@ -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"
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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">&bull;</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>
)}

View File

@ -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">
&larr; Prev
</a>
)}
{page.url.next && (
<a class="action__go-to-x" href={page.url.next} title="Go to Next">
Next &rarr;
</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>

View File

@ -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} />

View File

@ -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>

View File

@ -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">&bull;</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>

View File

@ -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">&bull;</span>
{post.reading_time} min read
</span>
</div>
</footer>
</div>
</article>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,7 @@
---
const { class: className } = Astro.props;
---
<div class:list={["max-w-screen-xl mx-auto px-5", className]}>
<slot />
</div>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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
}

View File

@ -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
);

View File

@ -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" })
];

View File

@ -78,7 +78,6 @@ const { post, settings, index, isHome = false } = Astro.props as Props;
.post-card-excerpt {
max-width: 56em;
color: color.scale($color-midgrey, $lightness: -8%);
}
.post-card-image-link {

View File

@ -1,16 +1,17 @@
import { defineConfig } from "astro/config";
import tailwind from "@astrojs/tailwind";
import ghostcms from "@matthiesenxyz/astro-ghostcms";
// https://astro.build/config
export default defineConfig({
site: "https://demo.astro-ghostcms.xyz/",
integrations: [
integrations: [tailwind(),
ghostcms({
disable404: false,
disableRSS: false,
disableRouteInjection: false,
disableConsoleOutput: false,
theme: "@matthiesenxyz/astro-ghostcms-theme-default",
theme: "@matthiesenxyz/astro-ghostcms-theme-catpuccin-dark",
ghostURL: "https://ghostdemo.matthiesen.xyz",
})
],

View File

@ -13,9 +13,12 @@
"dependencies": {
"astro": "^4.2.6",
"@matthiesenxyz/astro-ghostcms-theme-default": "*",
"@matthiesenxyz/astro-ghostcms": "workspace:*"
"@matthiesenxyz/astro-ghostcms": "workspace:*",
"@astrojs/tailwind": "^5.1.0",
"tailwindcss": "^3.3.5"
},
"devDependencies": {
"@matthiesenxyz/astro-ghostcms-theme-catppuccin-dark": "*",
"@astrojs/check": "^0.4.1",
"typescript": "^5.3.3"
}

View File

@ -0,0 +1,4 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
presets: [require('@matthiesenxyz/astro-ghostcms-theme-catppuccin-dark')]
};