Skip to content

Instantly share code, notes, and snippets.

@Adammatthiesen
Last active January 13, 2024 00:44
Show Gist options
  • Save Adammatthiesen/2ed296220e74c43653ee826d879fd359 to your computer and use it in GitHub Desktop.
Save Adammatthiesen/2ed296220e74c43653ee826d879fd359 to your computer and use it in GitHub Desktop.
Ghost
CONTENT_API_KEY=YOUR_GHOST_API_KEY
CONTENT_API_URL=https://ghost.io
CONTENT_API_VER=v5.0
---
import SupportOurWork from '@components/crumbs/SupportOurWork.astro';
import { PI, ghostClient } from '@lib/ghost';
import LiveRender from '@layouts/LiveRender.astro';
import Container from '@components/ui/container.astro';
import BackToBlog from '@components/crumbs/BackToBlog.astro';
import GhostPowered from '@components/crumbs/GhostPowered.astro';
import { Picture } from 'astro:assets';
// GET SLUG
const { slug } = Astro.params;
// IF SLUG DOSNT EXIST THEN THROW ERROR
if(slug === undefined){ throw new Error('Slug is required'); }
// GET POSTS FROM GHOST
const ghostPost = await ghostClient.posts.read({
slug: `${slug}`, include: `${PI}`})
// IF NO POST AVAILABLE THEN REDIRECT TO 404
if(ghostPost === undefined) { return Astro.redirect('/404'); }
// GET PAGE VARIABLES AND SETTINGS FROM PAGE CREATION
const { title, html, feature_image, feature_image_alt, published_at, updated_at, primary_author, tags } = await ghostPost;
// DATE FORMATING/FIXES
const locale = "en-US";
const dateOptions: object = { year: "numeric", month: "long", day: "numeric" };
const formattedPublishDate = new Date(published_at).toLocaleDateString(locale, dateOptions );
const formattedUpdatedDate = new Date(updated_at).toLocaleDateString(locale, dateOptions );
---
<LiveRender title={title}>
<Container>
<div class="mx-auto max-w-5xl mt-5">
<center>
<Picture
src={feature_image? feature_image : "/mxyz-hero.png"}
alt={feature_image_alt? feature_image_alt : "alt"}
sizes="(max-width: 800px) 100vw, 800px"
width={800} height={600}
class="w-full rounded-md object-cover object-center"/>
</center>
<h1 class="text-4xl lg:text-5xl font-bold lg:tracking-tight mt-1 lg:leading-tight">
{title}
</h1>
{tags && (<h3 class="text-gray-500">{tags.map((tag) => (<a href={`/blog/tag/${tag.slug}`}> #{tag.name}</a>))}</h3>) }
<div class="flex gap-2 mt-3 items-center flex-wrap md:flex-nowrap">
Author:
<a href="/blog/authors" class="inline-flex pr-2 mb-2 focus:outline-none rounded-full border focus:z-10 focus:ring-4 focus:ring-gray-700 bg-slate-800 text-slate-300 border-gray-600 hover:bg-slate-600">
<img class="w-7 h-7 me-1 rounded-full" src={primary_author.profile_image} alt= {primary_author.name}>
<span class="text-slate-100"> {primary_author.name}</span>
</a>
</div>
</div>
<!-- DIVIDER BAR -->
<div class="mb-0 mt-0 mx-auto max-w-5xl flex flex-row">
<!-- TOP OF DIVIDER BAR -->
<!-- LEFT -->
<time class="text-slate-300 flex-1" datetime={published_at}>
Published:
<span class=" text-xs font-medium inline-flex items-center px-2.5 py-0.5 rounded me-2 bg-slate-700 text-slate-300 border border-slate-500 ">
<svg class="w-2.5 h-2.5 me-1.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 0a10 10 0 1 0 10 10A10.011 10.011 0 0 0 10 0Zm3.982 13.982a1 1 0 0 1-1.414 0l-3.274-3.274A1.012 1.012 0 0 1 9 10V6a1 1 0 0 1 2 0v3.586l2.982 2.982a1 1 0 0 1 0 1.414Z"/>
</svg>
{formattedPublishDate}
</span>
</time>
<!-- RIGHT -->
<time class="text-slate-300 flex-2" datetime={updated_at}>
Last Modified:
<span class="text-xs font-medium inline-flex items-center px-2.5 py-0.5 rounded bg-slate-700 text-blue-400 border border-blue-400">
<svg class="w-2.5 h-2.5 me-1.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 0a10 10 0 1 0 10 10A10.011 10.011 0 0 0 10 0Zm3.982 13.982a1 1 0 0 1-1.414 0l-3.274-3.274A1.012 1.012 0 0 1 9 10V6a1 1 0 0 1 2 0v3.586l2.982 2.982a1 1 0 0 1 0 1.414Z"/>
</svg>
{formattedUpdatedDate}
</span>
</time>
</div>
<div class="divider mx-auto max-w-5xl my-0" />
<!-- BOTTOM OF DIVIDER BAR -->
<div class="mb-0 mt-0 mx-auto max-w-5xl flex flex-row">
<!-- LEFT -->
<div class="flex-1">
<GhostPowered />
</div>
<!-- RIGHT -->
<div class="flex-2">
<SupportOurWork />
</div>
</div>
<!-- END OF DIVIDER BAR -->
<div class="mx-auto prose prose-lg mt-6 max-w-5xl">
<!-- CONTENT -->
<Fragment set:html={html} />
</div>
<BackToBlog />
</Container>
</LiveRender>
---
import GhostAuthors from "@components/blog/GhostAuthors.astro";
import BackToBlog from "@components/crumbs/BackToBlog.astro";
import Container from "@components/ui/container.astro";
import Sectionhead from "@components/ui/sectionhead.astro";
import LiveRender from "@layouts/LiveRender.astro";
import { getGhostAuthors } from "@lib/ghost";
const authors = await getGhostAuthors();
---
<LiveRender title="Blog: Authors">
<Container>
<Sectionhead>
<Fragment slot="title">Our Blog Authors</Fragment>
<Fragment slot="desc">These are the Authors who make our blog what it is!</Fragment>
</Sectionhead>
<div class="grid md:grid-cols-3 items-center gap-10 mx-auto max-w-4xl mt-12">
{authors.map((author) => <GhostAuthors ghostAuthor={author} />) }
</div>
<div class="my-20" />
<BackToBlog />
</Container>
</LiveRender>
---
import { getGhostFeaturedPosts } from "@lib/ghost";
import { Picture } from "astro:assets";
const posts = await getGhostFeaturedPosts();
---
<div class="mt-16 md:mt-10">
<h2 class="text-4xl lg:text-5xl font-bold lg:tracking-tight">
Featured posts from our Blog
</h2>
</div>
{posts.length === 0 ? (
<div class="font-bold mt-10 ml-10"> Sorry</div>
<p class="text-gray-500 dark:text-gray-400 ml-10">There are no Featured posts at the moment! Check back later!</p>
) : (
<div class="grid sm:grid-cols-2 md:grid-cols-3 mt-10 gap-6">
{posts.map((post) => (
<article class="p-4 border rounded-lg shadow-xl shadow-slate-950 bg-slate-900 border-slate-600">
<div class="w-full rounded-md transition object-cover object-center">
<Picture
class="w-full rounded-md transition object-cover object-center"
width={800} height={600}
src={post.feature_image}
alt={post.feature_image_alt}/>
</div>
<div class="mt-1">
<h3 class="font-semibold inline text-lg">{post.title}</h3>
<p class="text-slate-300 mt-0 leading-relaxed">{post.excerpt}</p>
</div>
<div class="flex flex-row mt-5">
<div class="flex-1" />
<div class="flex-none justify-end justify-self-end justify-items-end">
<a href={`/blog/${post.slug}`} class="text-slate-200 bg-gradient-to-r from-blue-500 via-blue-700 to-blue-900 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-blue-800 font-medium rounded-lg text-sm px-3 py-1.5 text-center me-2 mb-0 ">Read More</a>
</div>
</div>
</article>
))}
</div>
)}
import GhostContentAPI from '@tryghost/content-api';
const env = import.meta.env;
export const PI = ['authors', 'tags']
export const ghostClient = new GhostContentAPI({
url: env.CONTENT_API_URL, key: env.CONTENT_API_KEY,
version: env.CONTENT_API_VER })
export const getGhostPosts = async () => {
const posts = await ghostClient.posts.browse({
limit: 'all', include: `${PI}`, filter: 'visibility:public' })
return posts;
}
export const getGhostFeaturedPosts = async () => {
const posts = await ghostClient.posts.browse({
limit: '3', include: `${PI}`, filter: 'featured:true' })
return posts;
}
export const getGhostAuthors = async () => {
const authors = await ghostClient.authors.browse()
return authors;
}
export const getGhostTags = async () => {
const tags = await ghostClient.tags.browse()
.include({"count.posts": true,})
return tags;
}
---
import { Picture } from "astro:assets";
const { ghostAuthor } = Astro.props;
---
<div class="group bg-slate-800 rounded-badge hover:-translate-y-1 shadow-xl shadow-slate-950">
<div class="w-full aspect-square">
<Picture
src={ghostAuthor.profile_image}
alt={ghostAuthor.name}
sizes="(max-width: 800px) 100vw, 400px"
width={400} height={400}
class="w-full rounded-md transition bg-white object-cover object-center aspect-square"/>
</div>
<div class="my-2 text-center">
<h2 class="text-lg text-gray-200">{ghostAuthor.name}</h2>
{ghostAuthor.location && <h3 class="text-blue-300 font-semibold"> <i class="bi bi-geo me-2" />{ghostAuthor.location}</h3>}
<p class="text-sm text-slate-300 ml-4 text-left">{ghostAuthor.bio && ghostAuthor.bio}</p>
<div class="mt-3">
{ghostAuthor.website && <a href={ghostAuthor.website}>
<span class="inline-flex items-center justify-center w-8 h-8 me-2 text-sm font-semibold text-gray-800 bg-gray-100 rounded-full dark:bg-gray-700 dark:text-gray-300" aria-label="Website">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-5 h-5 bi bi-globe-americas" viewBox="0 0 16 16">
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0M2.04 4.326c.325 1.329 2.532 2.54 3.717 3.19.48.263.793.434.743.484q-.121.12-.242.234c-.416.396-.787.749-.758 1.266.035.634.618.824 1.214 1.017.577.188 1.168.38 1.286.983.082.417-.075.988-.22 1.52-.215.782-.406 1.48.22 1.48 1.5-.5 3.798-3.186 4-5 .138-1.243-2-2-3.5-2.5-.478-.16-.755.081-.99.284-.172.15-.322.279-.51.216-.445-.148-2.5-2-1.5-2.5.78-.39.952-.171 1.227.182.078.099.163.208.273.318.609.304.662-.132.723-.633.039-.322.081-.671.277-.867.434-.434 1.265-.791 2.028-1.12.712-.306 1.365-.587 1.579-.88A7 7 0 1 1 2.04 4.327Z"/>
</svg>
<span class="sr-only">Website</span>
</span>
</a>}
{ghostAuthor.facebook && <a href={ghostAuthor.facebook}>
<span class="inline-flex items-center justify-center w-8 h-8 me-2 text-sm font-semibold text-gray-800 bg-gray-100 rounded-full dark:bg-gray-700 dark:text-gray-300" aria-label="Facebook">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-5 h-5 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>
<span class="sr-only">Facebook</span>
</span>
</a>}
{ghostAuthor.twitter && <a href={ghostAuthor.twitter}>
<span class="inline-flex items-center justify-center w-8 h-8 me-2 text-sm font-semibold text-gray-800 bg-gray-100 rounded-full dark:bg-gray-700 dark:text-gray-300">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-5 h-5 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>
<span class="sr-only">Twitter</span>
</span>
</a>}
</div>
</div>
</div>
---
import { Picture } from "astro:assets";
const { post } = Astro.props;
const { title, excerpt, feature_image, feature_image_alt, published_at, primary_author } = post;
const locale = "en-US";
const dateOptions: object = { year: "numeric", month: "long", day: "numeric" };
const formattedPublishDate = new Date(published_at).toLocaleDateString(locale, dateOptions );
---
<li>
<div class="divider" />
<a href={`/blog/${post.slug}`}> <div class="sr-only">Blog Post</div>
<div class="grid md:grid-cols-2 gap-5 md:gap-10 items-center">
<Picture
src={feature_image? feature_image : "/mxyz-hero.png"}
alt={feature_image_alt? feature_image_alt : "alt"}
sizes="(max-width: 800px) 100vw, 800px"
width={800} height={600}
class="w-full rounded-md object-cover object-center"/>
<div class="sr-only">Blog Post Image</div>
<div>
<h2 class="text-3xl font-semibold leading-snug tracking-tight mt-1 ">{title}</h2>
<div class="flex gap-2 mt-3"> <div class="sr-only">Author</div>
<a href="/blog/authors" class="inline-flex pr-2 mb-2 focus:outline-none rounded-full border focus:z-10 focus:ring-4 focus:ring-gray-700 bg-slate-800 text-slate-300 border-gray-600 hover:bg-slate-600">
<img class="w-7 h-7 me-1 rounded-full" src={primary_author.profile_image} alt={primary_author.name}>
<span class="text-slate-300 mt-1">{primary_author.name}</span>
</a>
<time class="text-slate-300 mt-1" datetime={published_at}>{formattedPublishDate}</time>
</div>
{excerpt && (<p class="text-slate-400">{excerpt}</p>)}
</div>
</div>
</a>
</li>
---
import GhostPost from '@components/blog/GhostPost.astro';
import { getGhostPosts } from '@lib/ghost';
import LiveRender from '@layouts/LiveRender.astro';
import Container from '@components/ui/container.astro';
import Sectionhead from '@components/ui/sectionhead.astro';
import GhostPowered from '@components/crumbs/GhostPowered.astro';
import RssIcon from '@components/crumbs/RssIcon.astro';
import OurBlogAuthors from '@components/blog/OurBlogAuthors.astro';
const posts = await getGhostPosts();
const tags = await getGhostTags();
---
<LiveRender title="Blog">
<Container>
<GhostPowered />
<Sectionhead>
<Fragment slot="title"> Blog <RssIcon /></Fragment>
<Fragment slot="desc">Our Posts & Tutorials, Our Tutorials mainly Focus on FOSS (Free Open Source Software).</Fragment>
</Sectionhead>
<main class="mt-5 max-w-5xl mx-auto">
{tags && (
<div class="text-white mt-2">
Blog Tags: <span class="text-gray-400">
{tags.map((tag) => (
<a href={`/blog/tag/${tag.slug}`}> #{tag.name}</a>
))} </span>
</div>
)}
<OurBlogAuthors />
<ul class="grid gap-16 ">
{ posts.map((post) => <GhostPost post={post} />) }
</ul>
</main>
</Container>
</LiveRender>
---
import LiveRender from '@layouts/LiveRender.astro';
import Container from '@components/ui/container.astro';
import { ghostClient } from '@lib/ghost';
import GhostPowered from '@components/crumbs/GhostPowered.astro';
import Sectionhead from '@components/ui/sectionhead.astro';
import OurBlogAuthors from '@components/blog/OurBlogAuthors.astro';
import GhostPost from '@components/blog/GhostPost.astro';
import BackToBlog from '@components/crumbs/BackToBlog.astro';
const { slug } = Astro.params;
// IF SLUG DOSNT EXIST THEN THROW ERROR
if(slug === undefined){ throw new Error('Slug is required'); }
const ghostTag = await ghostClient.tags.read({
slug: `${slug}`,
include: `count.posts`
})
// IF NO TAG AVAILABLE THEN REDIRECT TO 404
if(ghostTag === undefined) { return Astro.redirect('/404'); }
const posts = await ghostClient.posts.browse({
filter: `tag:${ghostTag.slug}`,
limit: 'all',
include: ['tags', 'authors'],
});
---
<LiveRender title="tags">
<Container>
<GhostPowered />
<Sectionhead>
<Fragment slot="title"> Blog ( #{ghostTag.name} )</Fragment>
<Fragment slot="desc">Total Posts: {ghostTag.count.posts}</Fragment>
</Sectionhead>
<BackToBlog />
<main class="mt-5 max-w-5xl mx-auto">
<OurBlogAuthors />
<ul class="grid gap-16 ">
{ posts.map((post) => <GhostPost post={post} />) }
</ul>
</main>
</Container>
</LiveRender>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment