- Adicionado estrutura completa do projeto - Configurado MCP server para Premiere Pro - Adicionado documentação e skills - Configurado Gitignore para o projeto
219 lines
9.2 KiB
TypeScript
Executable File
219 lines
9.2 KiB
TypeScript
Executable File
import type { Metadata } from "next"
|
|
import Link from "next/link"
|
|
import { notFound } from "next/navigation"
|
|
import { TrackedLink } from "@/components/ui/tracked-link"
|
|
import { articleBySlug, articles } from "@/lib/articles"
|
|
|
|
type ArticlePageProps = {
|
|
params: Promise<{ slug: string }>
|
|
}
|
|
|
|
export const dynamic = "force-static"
|
|
const socialImage = "/marketing/premiere-pro-mcp-social-square-v1.png"
|
|
|
|
const articleDateFormatter = new Intl.DateTimeFormat("en-US", {
|
|
day: "numeric",
|
|
month: "long",
|
|
timeZone: "UTC",
|
|
year: "numeric",
|
|
})
|
|
|
|
function formatArticleDate(date: string) {
|
|
return articleDateFormatter.format(new Date(`${date}T00:00:00Z`))
|
|
}
|
|
|
|
export function generateStaticParams() {
|
|
return articles.map(({ slug }) => ({ slug }))
|
|
}
|
|
|
|
export async function generateMetadata({ params }: ArticlePageProps): Promise<Metadata> {
|
|
const { slug } = await params
|
|
const article = articleBySlug.get(slug)
|
|
|
|
if (!article) {
|
|
return {}
|
|
}
|
|
|
|
return {
|
|
title: article.title,
|
|
description: article.description,
|
|
keywords: article.keywords,
|
|
alternates: { canonical: `/blog/${article.slug}/` },
|
|
openGraph: {
|
|
title: article.title,
|
|
description: article.description,
|
|
url: `/blog/${article.slug}/`,
|
|
type: "article",
|
|
publishedTime: article.publishedAt,
|
|
modifiedTime: article.modifiedAt,
|
|
authors: ["MCP for Adobe Premiere Pro contributors"],
|
|
images: [{ url: socialImage, width: 1254, height: 1254, alt: "Premiere Pro MCP — reviewable workflow automation" }],
|
|
},
|
|
twitter: {
|
|
card: "summary_large_image",
|
|
title: article.title,
|
|
description: article.description,
|
|
images: [socialImage],
|
|
},
|
|
}
|
|
}
|
|
|
|
export default async function ArticlePage({ params }: ArticlePageProps) {
|
|
const { slug } = await params
|
|
const article = articleBySlug.get(slug)
|
|
|
|
if (!article) {
|
|
notFound()
|
|
}
|
|
|
|
const relatedArticles = article.relatedSlugs
|
|
? article.relatedSlugs
|
|
.map((relatedSlug) => articleBySlug.get(relatedSlug))
|
|
.filter((related): related is (typeof articles)[number] => Boolean(related))
|
|
: articles.filter((candidate) => candidate.slug !== article.slug).slice(0, 2)
|
|
const articleUrl = `https://premiere-pro-mcp.com/blog/${article.slug}/`
|
|
const structuredData = {
|
|
"@context": "https://schema.org",
|
|
"@graph": [
|
|
{
|
|
"@type": "Article",
|
|
"@id": `${articleUrl}#article`,
|
|
headline: article.title,
|
|
description: article.description,
|
|
url: articleUrl,
|
|
datePublished: article.publishedAt,
|
|
dateModified: article.modifiedAt,
|
|
inLanguage: "en-US",
|
|
author: {
|
|
"@type": "Organization",
|
|
name: "MCP for Adobe Premiere Pro contributors",
|
|
url: "https://github.com/leancoderkavy/premiere-pro-mcp",
|
|
},
|
|
publisher: { "@id": "https://premiere-pro-mcp.com/#organization" },
|
|
mainEntityOfPage: articleUrl,
|
|
keywords: article.keywords.join(", "),
|
|
image: `https://premiere-pro-mcp.com${socialImage}`,
|
|
},
|
|
{
|
|
"@type": "FAQPage",
|
|
"@id": `${articleUrl}#faq`,
|
|
mainEntity: article.faqs.map((faq) => ({
|
|
"@type": "Question",
|
|
name: faq.question,
|
|
acceptedAnswer: { "@type": "Answer", text: faq.answer },
|
|
})),
|
|
},
|
|
{
|
|
"@type": "BreadcrumbList",
|
|
"@id": `${articleUrl}#breadcrumb`,
|
|
itemListElement: [
|
|
{ "@type": "ListItem", position: 1, name: "MCP for Adobe Premiere Pro", item: "https://premiere-pro-mcp.com/" },
|
|
{ "@type": "ListItem", position: 2, name: "Guides", item: "https://premiere-pro-mcp.com/blog/" },
|
|
{ "@type": "ListItem", position: 3, name: article.title, item: articleUrl },
|
|
],
|
|
},
|
|
],
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
|
|
/>
|
|
<main id="main-content" className="min-h-screen bg-black px-5 py-12 text-zinc-100 sm:py-20">
|
|
<article className="mx-auto max-w-3xl">
|
|
<nav aria-label="Breadcrumb" className="text-sm text-zinc-500">
|
|
<Link href="/" className="hover:text-purple-200">MCP for Adobe Premiere Pro</Link>{" "}
|
|
<span aria-hidden="true">/</span>{" "}
|
|
<Link href="/blog/" className="hover:text-purple-200">Guides</Link>{" "}
|
|
<span aria-hidden="true">/</span> <span className="text-zinc-400">{article.eyebrow}</span>
|
|
</nav>
|
|
|
|
<header className="border-b border-zinc-800 pb-10 pt-10 sm:pb-14 sm:pt-14">
|
|
<p className="font-mono text-sm font-medium uppercase tracking-[0.15em] text-purple-300">{article.eyebrow}</p>
|
|
<h1 className="mt-5 text-balance text-4xl font-bold tracking-tight text-white sm:text-6xl">{article.title}</h1>
|
|
<p className="mt-6 text-lg leading-8 text-zinc-400">{article.description}</p>
|
|
<div className="mt-7 flex items-center gap-3 text-sm text-zinc-500">
|
|
<time dateTime={article.publishedAt}>Published {formatArticleDate(article.publishedAt)}</time>
|
|
<span aria-hidden="true">·</span>
|
|
<span>{article.readingTime}</span>
|
|
</div>
|
|
</header>
|
|
|
|
<div className="py-10 sm:py-14">
|
|
{article.sections.map((section) => (
|
|
<section key={section.heading} className="border-b border-zinc-900 py-9 first:pt-0 last:border-b-0">
|
|
<h2 className="text-2xl font-semibold tracking-tight text-white sm:text-3xl">{section.heading}</h2>
|
|
<div className="mt-5 space-y-5 text-[1.0625rem] leading-8 text-zinc-300">
|
|
{section.paragraphs.map((paragraph) => <p key={paragraph}>{paragraph}</p>)}
|
|
</div>
|
|
{section.bullets ? (
|
|
<ul className="mt-6 list-disc space-y-3 pl-5 leading-7 text-zinc-300 marker:text-purple-300">
|
|
{section.bullets.map((item) => <li key={item}>{item}</li>)}
|
|
</ul>
|
|
) : null}
|
|
</section>
|
|
))}
|
|
</div>
|
|
|
|
<section className="border-y border-zinc-800 py-10" aria-labelledby="faq-heading">
|
|
<h2 id="faq-heading" className="text-2xl font-semibold tracking-tight text-white">Questions editors ask</h2>
|
|
<div className="mt-6 divide-y divide-zinc-800">
|
|
{article.faqs.map((faq) => (
|
|
<details key={faq.question} className="group py-5">
|
|
<summary className="cursor-pointer list-none pr-8 font-medium text-zinc-100 marker:content-none group-open:text-purple-200">
|
|
{faq.question}
|
|
</summary>
|
|
<p className="mt-3 leading-7 text-zinc-400">{faq.answer}</p>
|
|
</details>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
<section className="py-10" aria-labelledby="resources-heading">
|
|
<h2 id="resources-heading" className="text-2xl font-semibold tracking-tight text-white">Keep learning</h2>
|
|
<ul className="mt-5 space-y-3">
|
|
{article.resources.map((resource) => (
|
|
<li key={resource.href}>
|
|
<a href={resource.href} className="font-medium text-purple-200 hover:text-white">
|
|
{resource.label} <span aria-hidden="true">→</span>
|
|
</a>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</section>
|
|
|
|
<section className="border border-purple-300/40 bg-purple-300/10 p-7 sm:p-9" aria-labelledby="start-heading">
|
|
<p className="font-mono text-xs font-medium uppercase tracking-[0.15em] text-purple-200">A practical next step</p>
|
|
<h2 id="start-heading" className="mt-3 text-2xl font-semibold tracking-tight text-white">Start with a safe Premiere connection check.</h2>
|
|
<p className="mt-3 max-w-2xl leading-7 text-zinc-300">
|
|
Connect your assistant, verify the local bridge without changing a project, then inspect the active sequence before requesting a supported edit.
|
|
</p>
|
|
<TrackedLink
|
|
href="/#install"
|
|
trackingLocation={`guide:${article.slug}`}
|
|
trackingDestination="safe_connection_check"
|
|
className="mt-6 inline-flex bg-purple-300 px-5 py-3 font-medium text-black transition-colors hover:bg-white"
|
|
>
|
|
Run a safe connection check <span aria-hidden="true" className="ml-2">→</span>
|
|
</TrackedLink>
|
|
</section>
|
|
|
|
<aside className="border-t border-zinc-800 py-10" aria-labelledby="related-heading">
|
|
<h2 id="related-heading" className="text-xl font-semibold text-white">Related guides</h2>
|
|
<div className="mt-5 grid gap-4 sm:grid-cols-2">
|
|
{relatedArticles.map((related) => (
|
|
<Link key={related.slug} href={`/blog/${related.slug}/`} className="border border-zinc-800 p-5 transition-colors hover:border-purple-400/60">
|
|
<p className="font-mono text-xs uppercase tracking-[0.12em] text-purple-300">{related.eyebrow}</p>
|
|
<p className="mt-3 font-semibold text-zinc-100">{related.title}</p>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</aside>
|
|
</article>
|
|
</main>
|
|
</>
|
|
)
|
|
}
|