Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ repositories {
val fritz2Version = "1.0-RC5"

kotlin {
jvm()
jvm {
withJava()
}
js(IR) {
browser()
}.binaries.executable()
Expand All @@ -24,6 +26,7 @@ kotlin {
}
val jvmMain by getting {
dependencies {
implementation("org.commonmark:commonmark:0.21.0")
}
}
val jsMain by getting {
Expand All @@ -34,6 +37,21 @@ kotlin {
}
}

val generateBlogPages by tasks.registering(JavaExec::class) {
group = "build"
description = "Generate static HTML pages for blog posts (SSG)"
val jvmCompilation = kotlin.targets.getByName("jvm").compilations.getByName("main")
classpath = jvmCompilation.output.allOutputs + jvmCompilation.compileDependencyFiles
mainClass.set("codes.miley.generator.BlogGeneratorKt")
val jsDistDir = layout.buildDirectory.dir("dist/js/productionExecutable")
args = listOf(jsDistDir.get().asFile.absolutePath)
dependsOn("jvmJar")
}

tasks.named("jsBrowserProductionWebpack") {
finalizedBy(generateBlogPages)
}

dependencies {
add("kspCommonMainMetadata", "dev.fritz2:lenses-annotation-processor:$fritz2Version")
}
Expand Down
2 changes: 2 additions & 0 deletions src/commonMain/resources/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Miley Chandonnet</title>
<meta name="description" content="Native Android Application Engineer">
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="canonical" href="https://miley.codes">

<!-- Open Graph -->
<meta property="og:type" content="website">
Expand Down
4 changes: 2 additions & 2 deletions src/jsMain/kotlin/codes/miley/frontend/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ fun main() {
updateMetaTags(
title = post.title,
description = post.excerpt,
url = "https://miley.codes/#page=blog&post=${post.id}",
url = "https://miley.codes/blog/${post.id}",
image = "https://miley.codes/blog/${post.id}/demo.gif",
)
setMetaTag("property", "og:type", "article")
Expand All @@ -131,7 +131,7 @@ fun main() {
updateMetaTags(
title = "Blog | Miley Chandonnet",
description = "Native Android Application Engineer",
url = "https://miley.codes/#page=blog",
url = "https://miley.codes/blog",
image = "https://miley.codes/blog/dark-factory-needs-windows/demo.gif",
)
setMetaTag("property", "og:type", "website")
Expand Down
223 changes: 223 additions & 0 deletions src/jvmMain/kotlin/codes/miley/generator/BlogGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package codes.miley.generator

import codes.miley.model.ALL_POSTS
import codes.miley.model.BlogPost
import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer
import java.io.File

private val markdownParser = Parser.builder().build()
private val htmlRenderer = HtmlRenderer.builder().build()

private fun renderMarkdown(markdown: String): String {
val document = markdownParser.parse(markdown)
return htmlRenderer.render(document)
}

private fun generateBlogPostHtml(post: BlogPost): String {
val renderedContent = renderMarkdown(post.content)
val postUrl = "https://miley.codes/blog/${post.id}"
val imageUrl = "https://miley.codes/blog/${post.id}/demo.gif"
val escapedTitle = post.title.replace("\"", "&quot;").replace("<", "&lt;")
val escapedExcerpt = post.excerpt.replace("\"", "&quot;").replace("<", "&lt;")
val escapedSubtitle = post.subtitle.replace("\"", "&quot;").replace("<", "&lt;")

return """<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${escapedTitle} | Miley Chandonnet</title>
<meta name="description" content="${escapedExcerpt}">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="canonical" href="${postUrl}">

<!-- Open Graph -->
<meta property="og:type" content="article">
<meta property="og:site_name" content="Miley Chandonnet">
<meta property="og:title" content="${escapedTitle}">
<meta property="og:description" content="${escapedExcerpt}">
<meta property="og:url" content="${postUrl}">
<meta property="og:image" content="${imageUrl}">
<meta property="article:published_time" content="${post.publishedDate}">

<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${escapedTitle}">
<meta name="twitter:description" content="${escapedExcerpt}">
<meta name="twitter:image" content="${imageUrl}">

<!-- JSON-LD Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "${escapedTitle}",
"description": "${escapedExcerpt}",
"image": "${imageUrl}",
"datePublished": "${post.publishedDate}",
"author": {
"@type": "Person",
"name": "Miley Chandonnet",
"url": "https://miley.codes"
},
"publisher": {
"@type": "Person",
"name": "Miley Chandonnet"
},
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "${postUrl}"
},
"url": "${postUrl}"
}
</script>

<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-9MPHTXCPC7"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-9MPHTXCPC7');
</script>
<link rel="stylesheet" href="/index.css">
<link rel="stylesheet" href="/gallery.css">
<link rel="stylesheet" href="/blog.css">
</head>
<body>
<div id="target">
<div class="categories">
<h2>Miley Chandonnet</h2>
<h4>Native Android Application Engineer</h4>
</div>
<nav class="page-nav">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog" class="selected">Blog</a></li>
</ul>
</nav>
<div class="blog-page">
<main>
<article>
<header class="article-header">
<div class="article-date">${post.date}</div>
<h1>${escapedTitle}</h1>
<p class="article-subtitle">${escapedSubtitle}</p>
</header>
<div class="article-body">
${renderedContent}
</div>
<div class="article-cta">
<p>
<a href="https://github.com/socket-link/ampere">AMPERE</a>
is an open-source cognitive engine built in Kotlin Multiplatform where AI reasoning is transparent by design. It's early, it's opinionated, and it's available now.
</p>
</div>
</article>
</main>
</div>
</div>

<!-- SPA bootstrap for interactivity after initial render -->
<script src="/fritz2-ktor-todomvc.js"></script>
</body>
</html>"""
}

private fun generateBlogIndexHtml(): String {
return """<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Blog | Miley Chandonnet</title>
<meta name="description" content="Blog posts by Miley Chandonnet — Native Android Application Engineer">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="canonical" href="https://miley.codes/blog">

<!-- Open Graph -->
<meta property="og:type" content="website">
<meta property="og:site_name" content="Miley Chandonnet">
<meta property="og:title" content="Blog | Miley Chandonnet">
<meta property="og:description" content="Blog posts by Miley Chandonnet — Native Android Application Engineer">
<meta property="og:url" content="https://miley.codes/blog">
<meta property="og:image" content="https://miley.codes/blog/dark-factory-needs-windows/demo.gif">

<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Blog | Miley Chandonnet">
<meta name="twitter:description" content="Blog posts by Miley Chandonnet — Native Android Application Engineer">
<meta name="twitter:image" content="https://miley.codes/blog/dark-factory-needs-windows/demo.gif">

<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-9MPHTXCPC7"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-9MPHTXCPC7');
</script>
<link rel="stylesheet" href="/index.css">
<link rel="stylesheet" href="/gallery.css">
<link rel="stylesheet" href="/blog.css">
</head>
<body>
<div id="target">
<div class="categories">
<h2>Miley Chandonnet</h2>
<h4>Native Android Application Engineer</h4>
</div>
<nav class="page-nav">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog" class="selected">Blog</a></li>
</ul>
</nav>
<div class="blog-page">
<section class="blog-hero">
<h1>Blog</h1>
<p class="subtitle">Thoughts on Android development, AI, and building software.</p>
</section>
<ul class="post-list">
${ALL_POSTS.joinToString("\n") { post -> """ <li class="post-item">
<a href="/blog/${post.id}">
<div class="post-date">${post.date}</div>
<div class="post-title">${post.title}</div>
<div class="post-subtitle">${post.subtitle}</div>
<div class="post-excerpt">${post.excerpt}</div>
</a>
</li>"""
}}
</ul>
</div>
</div>

<!-- SPA bootstrap for interactivity after initial render -->
<script src="/fritz2-ktor-todomvc.js"></script>
</body>
</html>"""
}

fun main(args: Array<String>) {
val outputDir = if (args.isNotEmpty()) File(args[0]) else File("build/dist/js/productionExecutable")

println("Generating static blog pages to: ${outputDir.absolutePath}")

// Generate individual blog post pages
for (post in ALL_POSTS) {
val postDir = File(outputDir, "blog/${post.id}")
postDir.mkdirs()
val html = generateBlogPostHtml(post)
File(postDir, "index.html").writeText(html)
println(" Generated: blog/${post.id}/index.html")
}

// Generate blog index page
val blogDir = File(outputDir, "blog")
blogDir.mkdirs()
File(blogDir, "index.html").writeText(generateBlogIndexHtml())
println(" Generated: blog/index.html")

println("Done! Generated ${ALL_POSTS.size} blog post page(s) + index.")
}
Loading