feat: add TabFM extension page + pending website updates
- Add TabFM extension page (tabfm.md) with 15 soundscapes, FAQ, privacy section - Update content schema, Base layout, slug page, styles (pending changes) - Deploy to greasysprocket.com via rsync
This commit is contained in:
+134
-15
@@ -1,6 +1,6 @@
|
||||
---
|
||||
import Base from '../layouts/Base.astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { getCollection, type CollectionEntry } from 'astro:content';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const extensions = await getCollection('extensions');
|
||||
@@ -11,10 +11,85 @@ export async function getStaticPaths() {
|
||||
}
|
||||
|
||||
const { ext } = Astro.props;
|
||||
const { Content } = await ext.render();
|
||||
const { Content, headings } = await ext.render();
|
||||
|
||||
// Build table of contents from h2 headings only
|
||||
const tocItems = headings
|
||||
.filter((h: any) => h.depth === 2)
|
||||
.map((h: any) => ({
|
||||
text: h.text,
|
||||
slug: h.slug,
|
||||
}));
|
||||
|
||||
const siteUrl = 'https://greasysprocket.com';
|
||||
const canonicalUrl = `${siteUrl}/${ext.slug}`;
|
||||
const ogImage = ext.data.screenshot
|
||||
? new URL(ext.data.screenshot, siteUrl).href
|
||||
: undefined;
|
||||
|
||||
// JSON-LD: SoftwareApplication
|
||||
const softwareAppLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
name: ext.data.name,
|
||||
description: ext.data.description,
|
||||
applicationCategory: 'BrowserExtension',
|
||||
operatingSystem: 'Chrome',
|
||||
url: canonicalUrl,
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
price: '0',
|
||||
priceCurrency: 'USD',
|
||||
},
|
||||
...(ext.data.chromeStoreUrl && { installUrl: ext.data.chromeStoreUrl }),
|
||||
};
|
||||
|
||||
// JSON-LD: BreadcrumbList
|
||||
const breadcrumbLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: [
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 1,
|
||||
name: 'Extensions',
|
||||
item: siteUrl,
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 2,
|
||||
name: ext.data.name,
|
||||
item: canonicalUrl,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// JSON-LD: FAQPage (if FAQs exist)
|
||||
const faqLd = ext.data.faq
|
||||
? {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: ext.data.faq.map((f: any) => ({
|
||||
'@type': 'Question',
|
||||
name: f.question,
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: f.answer,
|
||||
},
|
||||
})),
|
||||
}
|
||||
: null;
|
||||
|
||||
const jsonLd = [softwareAppLd, breadcrumbLd, ...(faqLd ? [faqLd] : [])];
|
||||
---
|
||||
|
||||
<Base title={`${ext.data.name} — Greasy Sprocket`} description={ext.data.tagline}>
|
||||
<Base
|
||||
title={`${ext.data.name} — Free Chrome Extension | Greasy Sprocket`}
|
||||
description={ext.data.description}
|
||||
canonical={canonicalUrl}
|
||||
ogImage={ogImage}
|
||||
jsonLd={jsonLd}
|
||||
>
|
||||
<section class="ext-hero">
|
||||
<div class="container">
|
||||
<div class="info">
|
||||
@@ -43,19 +118,63 @@ const { Content } = await ext.render();
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="section-label">Features</div>
|
||||
<div class="feature-list">
|
||||
{ext.data.features?.map((feature: string) => (
|
||||
<div class="feature-item">
|
||||
<span class="check">✓</span>
|
||||
<h4>{feature}</h4>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div class="prose">
|
||||
<Content />
|
||||
<div class="container ext-layout">
|
||||
{tocItems.length > 0 && (
|
||||
<aside class="toc">
|
||||
<div class="section-label">Contents</div>
|
||||
<nav>
|
||||
<ol>
|
||||
{tocItems.map((item: any) => (
|
||||
<li>
|
||||
<a href={`#${item.slug}`}>{item.text}</a>
|
||||
</li>
|
||||
))}
|
||||
{ext.data.faq && ext.data.faq.length > 0 && (
|
||||
<li>
|
||||
<a href="#faq">FAQ</a>
|
||||
</li>
|
||||
)}
|
||||
</ol>
|
||||
</nav>
|
||||
</aside>
|
||||
)}
|
||||
|
||||
<div class="ext-content">
|
||||
<div class="section-label">Features</div>
|
||||
<div class="feature-list">
|
||||
{ext.data.features?.map((feature: string) => (
|
||||
<div class="feature-item">
|
||||
<span class="check">✓</span>
|
||||
<h4>{feature}</h4>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div class="prose">
|
||||
<Content />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{ext.data.faq && ext.data.faq.length > 0 && (
|
||||
<section class="section" id="faq">
|
||||
<div class="container faq-container">
|
||||
<div class="section-label">FAQ</div>
|
||||
<h2>Frequently asked questions</h2>
|
||||
<div class="faq-list">
|
||||
{ext.data.faq.map((f: any, i: number) => (
|
||||
<details class="faq-item" open={i === 0}>
|
||||
<summary>
|
||||
<span>{f.question}</span>
|
||||
<span class="faq-toggle" aria-hidden="true"></span>
|
||||
</summary>
|
||||
<div class="faq-answer">
|
||||
<p>{f.answer}</p>
|
||||
</div>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</Base>
|
||||
Reference in New Issue
Block a user