bunki 0.18.6 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -1
- package/dist/cli.js +403 -246
- package/dist/fragments/json-ld.njk +59 -0
- package/dist/fragments/og-image.njk +21 -0
- package/dist/fragments/pagination.njk +12 -0
- package/dist/fragments/share-buttons.njk +21 -0
- package/dist/index.js +0 -32874
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,15 +5,29 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
8
13
|
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
9
21
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
22
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
23
|
for (let key of __getOwnPropNames(mod))
|
|
12
24
|
if (!__hasOwnProp.call(to, key))
|
|
13
25
|
__defProp(to, key, {
|
|
14
|
-
get: (
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
15
27
|
enumerable: true
|
|
16
28
|
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
17
31
|
return to;
|
|
18
32
|
};
|
|
19
33
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
@@ -28555,11 +28569,11 @@ function registerCssCommand(program2) {
|
|
|
28555
28569
|
}
|
|
28556
28570
|
|
|
28557
28571
|
// src/cli/commands/generate.ts
|
|
28558
|
-
import
|
|
28572
|
+
import path11 from "path";
|
|
28559
28573
|
|
|
28560
28574
|
// src/site-generator.ts
|
|
28561
28575
|
var import_slugify = __toESM(require_slugify(), 1);
|
|
28562
|
-
import
|
|
28576
|
+
import path10 from "path";
|
|
28563
28577
|
|
|
28564
28578
|
// src/parser.ts
|
|
28565
28579
|
import path5 from "path";
|
|
@@ -33136,6 +33150,12 @@ function createMarked(cdnConfig) {
|
|
|
33136
33150
|
return markdown2;
|
|
33137
33151
|
},
|
|
33138
33152
|
postprocess(html) {
|
|
33153
|
+
if (cdnConfig?.enabled && cdnConfig.postYear) {
|
|
33154
|
+
const year = cdnConfig.postYear;
|
|
33155
|
+
const base = cdnConfig.baseUrl;
|
|
33156
|
+
html = html.replace(/src=(["'])\.\/\_assets\/([^\s"']+)\1/g, (_m, q2, filename) => `src=${q2}${base}/${year}/${filename}${q2}`);
|
|
33157
|
+
html = html.replace(/!\[([^\]]*)\]\(\.\/\_assets\/([^\s)]+)\)/g, (_m, alt, filename) => `<img src="${base}/${year}/${filename}" alt="${alt}" loading="lazy">`);
|
|
33158
|
+
}
|
|
33139
33159
|
html = html.replace(YOUTUBE_EMBED_REGEX, '<div class="video-container"><iframe src="https://www.youtube.com/embed/$4" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen loading="lazy"></iframe></div>');
|
|
33140
33160
|
html = html.replace(/<img /g, '<img loading="lazy" ');
|
|
33141
33161
|
return html.replace(EXTERNAL_LINK_REGEX, (match, protocol, rest) => {
|
|
@@ -33374,7 +33394,9 @@ async function parseMarkdownFile(filePath, cdnConfig) {
|
|
|
33374
33394
|
const slug = getBaseFilename(filePath);
|
|
33375
33395
|
const pacificDate = toPacificTime(data.date);
|
|
33376
33396
|
const postYear = getPacificYear(data.date);
|
|
33377
|
-
const
|
|
33397
|
+
const yearFromPath = filePath.match(/\/(\d{4})\//)?.[1];
|
|
33398
|
+
const resolvedYear = String(postYear) !== "NaN" ? String(postYear) : yearFromPath;
|
|
33399
|
+
const cdnConfigWithYear = cdnConfig && resolvedYear ? { ...cdnConfig, postYear: resolvedYear } : undefined;
|
|
33378
33400
|
const sanitizedHtml = convertMarkdownToHtml(content, cdnConfigWithYear);
|
|
33379
33401
|
const post = {
|
|
33380
33402
|
title: data.title,
|
|
@@ -34432,8 +34454,13 @@ function displayMetrics(metrics) {
|
|
|
34432
34454
|
|
|
34433
34455
|
// src/utils/template-engine.ts
|
|
34434
34456
|
var import_nunjucks2 = __toESM(require_nunjucks(), 1);
|
|
34457
|
+
import path9 from "path";
|
|
34458
|
+
import { existsSync } from "fs";
|
|
34459
|
+
var _distFragments = path9.join(import.meta.dir, "fragments");
|
|
34460
|
+
var _srcFragments = path9.join(import.meta.dir, "../fragments");
|
|
34461
|
+
var BUNKI_FRAGMENTS_DIR = existsSync(_distFragments) ? _distFragments : _srcFragments;
|
|
34435
34462
|
function createTemplateEngine(templatesDir, watch = false) {
|
|
34436
|
-
const env = import_nunjucks2.default.configure(templatesDir, {
|
|
34463
|
+
const env = import_nunjucks2.default.configure([templatesDir, BUNKI_FRAGMENTS_DIR], {
|
|
34437
34464
|
autoescape: true,
|
|
34438
34465
|
watch
|
|
34439
34466
|
});
|
|
@@ -34484,12 +34511,24 @@ class SiteGenerator {
|
|
|
34484
34511
|
async initialize() {
|
|
34485
34512
|
this.metrics.startStage("initialization");
|
|
34486
34513
|
console.log("Initializing site generator...");
|
|
34514
|
+
const flatAssetsDir = path10.join(process.cwd(), "content", "_assets");
|
|
34515
|
+
try {
|
|
34516
|
+
const stat = await import("fs/promises").then((m3) => m3.stat(flatAssetsDir));
|
|
34517
|
+
if (stat.isDirectory()) {
|
|
34518
|
+
throw new Error(`Build error: content/_assets/ must not exist.
|
|
34519
|
+
Images must be placed in content/{year}/_assets/ (e.g. content/2025/_assets/).
|
|
34520
|
+
Move any files from content/_assets/ into the correct year folder and retry.`);
|
|
34521
|
+
}
|
|
34522
|
+
} catch (err) {
|
|
34523
|
+
if (err.code !== "ENOENT")
|
|
34524
|
+
throw err;
|
|
34525
|
+
}
|
|
34487
34526
|
await ensureDir(this.options.outputDir);
|
|
34488
34527
|
if (this.options.config.noFollowExceptions) {
|
|
34489
34528
|
setNoFollowExceptions(this.options.config.noFollowExceptions);
|
|
34490
34529
|
}
|
|
34491
34530
|
let tagDescriptions = {};
|
|
34492
|
-
const tagsTomlPath =
|
|
34531
|
+
const tagsTomlPath = path10.join(process.cwd(), "src", "tags.toml");
|
|
34493
34532
|
const tagsTomlFile = Bun.file(tagsTomlPath);
|
|
34494
34533
|
if (await tagsTomlFile.exists()) {
|
|
34495
34534
|
try {
|
|
@@ -34551,8 +34590,8 @@ class SiteGenerator {
|
|
|
34551
34590
|
this.metrics.startStage("cssProcessing");
|
|
34552
34591
|
let cssChanged = true;
|
|
34553
34592
|
if (this.cache && this.incrementalMode && this.options.config.css) {
|
|
34554
|
-
const cssInputPath =
|
|
34555
|
-
const cssOutputPath =
|
|
34593
|
+
const cssInputPath = path10.resolve(process.cwd(), this.options.config.css.input);
|
|
34594
|
+
const cssOutputPath = path10.join(this.options.outputDir, this.options.config.css.output);
|
|
34556
34595
|
const cssOutputExists = await Bun.file(cssOutputPath).exists();
|
|
34557
34596
|
cssChanged = await hasFileChanged(cssInputPath, this.cache);
|
|
34558
34597
|
if (!cssChanged && cssOutputExists) {
|
|
@@ -34586,19 +34625,19 @@ class SiteGenerator {
|
|
|
34586
34625
|
}
|
|
34587
34626
|
async generateFeeds() {
|
|
34588
34627
|
const rssContent = generateRSSFeed(this.site, this.options.config);
|
|
34589
|
-
await Bun.write(
|
|
34628
|
+
await Bun.write(path10.join(this.options.outputDir, "feed.xml"), rssContent);
|
|
34590
34629
|
const sitemapContent = generateSitemap(this.site, this.options.config, PAGINATION.DEFAULT_PAGE_SIZE);
|
|
34591
|
-
await Bun.write(
|
|
34630
|
+
await Bun.write(path10.join(this.options.outputDir, "sitemap.xml"), sitemapContent);
|
|
34592
34631
|
console.log("Generated sitemap.xml");
|
|
34593
34632
|
const urlCount = this.site.posts.length + Object.keys(this.site.tags).length + 10;
|
|
34594
34633
|
const sitemapSize = sitemapContent.length;
|
|
34595
34634
|
if (urlCount > FILES.MAX_SITEMAP_URLS || sitemapSize > FILES.MAX_SITEMAP_SIZE) {
|
|
34596
34635
|
const sitemapIndexContent = generateSitemapIndex(this.options.config);
|
|
34597
|
-
await Bun.write(
|
|
34636
|
+
await Bun.write(path10.join(this.options.outputDir, "sitemap_index.xml"), sitemapIndexContent);
|
|
34598
34637
|
console.log("Generated sitemap_index.xml");
|
|
34599
34638
|
}
|
|
34600
34639
|
const robotsTxtContent = generateRobotsTxt(this.options.config);
|
|
34601
|
-
await Bun.write(
|
|
34640
|
+
await Bun.write(path10.join(this.options.outputDir, "robots.txt"), robotsTxtContent);
|
|
34602
34641
|
console.log("Generated robots.txt");
|
|
34603
34642
|
}
|
|
34604
34643
|
async parseContent() {
|
|
@@ -34615,7 +34654,7 @@ class SiteGenerator {
|
|
|
34615
34654
|
return posts;
|
|
34616
34655
|
}
|
|
34617
34656
|
const allFiles = await findFilesByPattern("**/*.md", this.options.contentDir, true);
|
|
34618
|
-
const configPath =
|
|
34657
|
+
const configPath = path10.join(process.cwd(), "bunki.config.ts");
|
|
34619
34658
|
const configChanged = await hasConfigChanged(configPath, this.cache);
|
|
34620
34659
|
if (configChanged) {
|
|
34621
34660
|
console.log("Config changed, full rebuild required");
|
|
@@ -34694,10 +34733,10 @@ var defaultDeps2 = {
|
|
|
34694
34733
|
};
|
|
34695
34734
|
async function handleGenerateCommand(options2, deps = defaultDeps2) {
|
|
34696
34735
|
try {
|
|
34697
|
-
const configPath =
|
|
34698
|
-
const contentDir =
|
|
34699
|
-
const outputDir =
|
|
34700
|
-
const templatesDir =
|
|
34736
|
+
const configPath = path11.resolve(options2.config);
|
|
34737
|
+
const contentDir = path11.resolve(options2.content);
|
|
34738
|
+
const outputDir = path11.resolve(options2.output);
|
|
34739
|
+
const templatesDir = path11.resolve(options2.templates);
|
|
34701
34740
|
deps.logger.log("Generating site with:");
|
|
34702
34741
|
deps.logger.log(`- Config file: ${configPath}`);
|
|
34703
34742
|
deps.logger.log(`- Content directory: ${contentDir}`);
|
|
@@ -34731,11 +34770,11 @@ function registerGenerateCommand(program2) {
|
|
|
34731
34770
|
}
|
|
34732
34771
|
|
|
34733
34772
|
// src/utils/image-uploader.ts
|
|
34734
|
-
import
|
|
34773
|
+
import path13 from "path";
|
|
34735
34774
|
|
|
34736
34775
|
// src/utils/s3-uploader.ts
|
|
34737
34776
|
var {S3Client } = globalThis.Bun;
|
|
34738
|
-
import
|
|
34777
|
+
import path12 from "path";
|
|
34739
34778
|
|
|
34740
34779
|
class S3Uploader {
|
|
34741
34780
|
s3Config;
|
|
@@ -34858,7 +34897,7 @@ class S3Uploader {
|
|
|
34858
34897
|
let failedCount = 0;
|
|
34859
34898
|
const uploadTasks = imageFiles.map((imageFile) => async () => {
|
|
34860
34899
|
try {
|
|
34861
|
-
const imagePath =
|
|
34900
|
+
const imagePath = path12.join(imagesDir, imageFile);
|
|
34862
34901
|
const s3Key = keyTransform ? keyTransform(imageFile) : imageFile;
|
|
34863
34902
|
const file = Bun.file(imagePath);
|
|
34864
34903
|
if (process.env.BUNKI_DRY_RUN === "true") {} else {
|
|
@@ -34892,13 +34931,13 @@ function createUploader(config) {
|
|
|
34892
34931
|
}
|
|
34893
34932
|
|
|
34894
34933
|
// src/utils/image-uploader.ts
|
|
34895
|
-
var DEFAULT_IMAGES_DIR =
|
|
34896
|
-
var DEFAULT_CONTENT_DIR2 =
|
|
34934
|
+
var DEFAULT_IMAGES_DIR = path13.join(process.cwd(), "assets");
|
|
34935
|
+
var DEFAULT_CONTENT_DIR2 = path13.join(process.cwd(), "content");
|
|
34897
34936
|
async function uploadImages(options2 = {}) {
|
|
34898
34937
|
try {
|
|
34899
34938
|
const contentAssetsMode = options2.contentAssets === true;
|
|
34900
34939
|
const defaultDir = contentAssetsMode ? DEFAULT_CONTENT_DIR2 : DEFAULT_IMAGES_DIR;
|
|
34901
|
-
const imagesDir =
|
|
34940
|
+
const imagesDir = path13.resolve(options2.images || defaultDir);
|
|
34902
34941
|
if (!await fileExists(imagesDir)) {
|
|
34903
34942
|
console.log(`Creating images directory at ${imagesDir}...`);
|
|
34904
34943
|
await ensureDir(imagesDir);
|
|
@@ -34950,7 +34989,7 @@ async function uploadImages(options2 = {}) {
|
|
|
34950
34989
|
const uploader = createUploader(s3Config);
|
|
34951
34990
|
const imageUrlMap = await uploader.uploadImages(imagesDir, options2.minYear, keyTransform);
|
|
34952
34991
|
if (options2.outputJson) {
|
|
34953
|
-
const outputFile =
|
|
34992
|
+
const outputFile = path13.resolve(options2.outputJson);
|
|
34954
34993
|
await Bun.write(outputFile, JSON.stringify(imageUrlMap, null, 2));
|
|
34955
34994
|
console.log(`Image URL mapping saved to ${outputFile}`);
|
|
34956
34995
|
}
|
|
@@ -34998,7 +35037,7 @@ function registerImagesPushCommand(program2) {
|
|
|
34998
35037
|
}
|
|
34999
35038
|
|
|
35000
35039
|
// src/cli/commands/init.ts
|
|
35001
|
-
import
|
|
35040
|
+
import path14 from "path";
|
|
35002
35041
|
var defaultDependencies = {
|
|
35003
35042
|
createDefaultConfig,
|
|
35004
35043
|
ensureDir,
|
|
@@ -35008,7 +35047,7 @@ var defaultDependencies = {
|
|
|
35008
35047
|
};
|
|
35009
35048
|
async function handleInitCommand(options2, deps = defaultDependencies) {
|
|
35010
35049
|
try {
|
|
35011
|
-
const configPath =
|
|
35050
|
+
const configPath = path14.resolve(options2.config);
|
|
35012
35051
|
const configCreated = await deps.createDefaultConfig(configPath);
|
|
35013
35052
|
if (!configCreated) {
|
|
35014
35053
|
deps.logger.log(`
|
|
@@ -35017,19 +35056,19 @@ Skipped initialization because the config file already exists`);
|
|
|
35017
35056
|
}
|
|
35018
35057
|
deps.logger.log("Creating directory structure...");
|
|
35019
35058
|
const baseDir = process.cwd();
|
|
35020
|
-
const contentDir =
|
|
35021
|
-
const templatesDir =
|
|
35022
|
-
const stylesDir =
|
|
35023
|
-
const publicDir =
|
|
35059
|
+
const contentDir = path14.join(baseDir, "content");
|
|
35060
|
+
const templatesDir = path14.join(baseDir, "templates");
|
|
35061
|
+
const stylesDir = path14.join(templatesDir, "styles");
|
|
35062
|
+
const publicDir = path14.join(baseDir, "public");
|
|
35024
35063
|
await deps.ensureDir(contentDir);
|
|
35025
35064
|
await deps.ensureDir(templatesDir);
|
|
35026
35065
|
await deps.ensureDir(stylesDir);
|
|
35027
35066
|
await deps.ensureDir(publicDir);
|
|
35028
35067
|
for (const [filename, content] of Object.entries(getDefaultTemplates())) {
|
|
35029
|
-
await deps.writeFile(
|
|
35068
|
+
await deps.writeFile(path14.join(templatesDir, filename), content);
|
|
35030
35069
|
}
|
|
35031
|
-
await deps.writeFile(
|
|
35032
|
-
await deps.writeFile(
|
|
35070
|
+
await deps.writeFile(path14.join(stylesDir, "main.css"), getDefaultCss());
|
|
35071
|
+
await deps.writeFile(path14.join(contentDir, "welcome.md"), getSamplePost());
|
|
35033
35072
|
deps.logger.log(`
|
|
35034
35073
|
Initialization complete! Here are the next steps:`);
|
|
35035
35074
|
deps.logger.log("1. Edit bunki.config.ts to configure your site");
|
|
@@ -35049,221 +35088,302 @@ function registerInitCommand(program2, deps = defaultDependencies) {
|
|
|
35049
35088
|
function getDefaultTemplates() {
|
|
35050
35089
|
return {
|
|
35051
35090
|
"base.njk": String.raw`<!DOCTYPE html>
|
|
35052
|
-
|
|
35053
|
-
|
|
35054
|
-
|
|
35055
|
-
|
|
35056
|
-
|
|
35057
|
-
|
|
35058
|
-
|
|
35059
|
-
|
|
35060
|
-
|
|
35061
|
-
|
|
35062
|
-
|
|
35063
|
-
|
|
35064
|
-
|
|
35065
|
-
|
|
35066
|
-
|
|
35067
|
-
|
|
35068
|
-
|
|
35069
|
-
|
|
35070
|
-
|
|
35071
|
-
|
|
35072
|
-
|
|
35073
|
-
|
|
35074
|
-
|
|
35075
|
-
|
|
35076
|
-
|
|
35077
|
-
|
|
35078
|
-
|
|
35079
|
-
|
|
35080
|
-
|
|
35081
|
-
|
|
35082
|
-
|
|
35083
|
-
|
|
35084
|
-
|
|
35091
|
+
<html lang="en">
|
|
35092
|
+
<head>
|
|
35093
|
+
<meta charset="UTF-8">
|
|
35094
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
35095
|
+
<title>{% block title %}{{ site.title }}{% endblock %}</title>
|
|
35096
|
+
<meta name="description" content="{% block description %}{{ site.description }}{% endblock %}">
|
|
35097
|
+
|
|
35098
|
+
{# Canonical URL #}
|
|
35099
|
+
<link rel="canonical" href="{% block canonical %}{{ site.baseUrl }}/{% endblock %}">
|
|
35100
|
+
|
|
35101
|
+
{# Open Graph meta tags #}
|
|
35102
|
+
<meta property="og:type" content="{% block og_type %}website{% endblock %}">
|
|
35103
|
+
<meta property="og:title" content="{% block og_title %}{{ site.title }}{% endblock %}">
|
|
35104
|
+
<meta property="og:description" content="{% block og_description %}{{ site.description }}{% endblock %}">
|
|
35105
|
+
<meta property="og:url" content="{% block og_url %}{{ site.baseUrl }}/{% endblock %}">
|
|
35106
|
+
<meta property="og:site_name" content="{{ site.title }}">
|
|
35107
|
+
{% block og_image %}{% endblock %}
|
|
35108
|
+
|
|
35109
|
+
{# Twitter Card meta tags #}
|
|
35110
|
+
<meta name="twitter:card" content="{% block twitter_card %}summary{% endblock %}">
|
|
35111
|
+
<meta name="twitter:title" content="{% block twitter_title %}{{ site.title }}{% endblock %}">
|
|
35112
|
+
<meta name="twitter:description" content="{% block twitter_description %}{{ site.description }}{% endblock %}">
|
|
35113
|
+
{% block twitter_image %}{% endblock %}
|
|
35114
|
+
|
|
35115
|
+
<link rel="stylesheet" href="/css/style.css">
|
|
35116
|
+
<link rel="alternate" type="application/rss+xml" title="{{ site.title }} RSS Feed" href="{{ site.baseUrl }}/feed.xml">
|
|
35117
|
+
{% block head %}{% endblock %}
|
|
35118
|
+
</head>
|
|
35119
|
+
<body>
|
|
35120
|
+
<header>
|
|
35121
|
+
<div class="container">
|
|
35122
|
+
<h1><a href="/">{{ site.title }}</a></h1>
|
|
35123
|
+
<nav>
|
|
35124
|
+
<ul>
|
|
35125
|
+
<li><a href="/">Home</a></li>
|
|
35126
|
+
<li><a href="/tags/">Tags</a></li>
|
|
35127
|
+
</ul>
|
|
35128
|
+
</nav>
|
|
35129
|
+
</div>
|
|
35130
|
+
</header>
|
|
35131
|
+
|
|
35132
|
+
<main class="container">
|
|
35133
|
+
{% block content %}{% endblock %}
|
|
35134
|
+
</main>
|
|
35135
|
+
|
|
35136
|
+
<footer>
|
|
35137
|
+
<div class="container">
|
|
35138
|
+
<p>© {{ "now" | date("YYYY") }} {{ site.title }} - Powered by <a href="https://github.com/kahwee/bunki">Bunki</a></p>
|
|
35139
|
+
</div>
|
|
35140
|
+
</footer>
|
|
35141
|
+
</body>
|
|
35142
|
+
</html>`,
|
|
35085
35143
|
"index.njk": String.raw`{% extends "base.njk" %}
|
|
35086
35144
|
|
|
35087
|
-
|
|
35088
|
-
|
|
35089
|
-
|
|
35090
|
-
|
|
35091
|
-
|
|
35092
|
-
|
|
35093
|
-
|
|
35094
|
-
|
|
35095
|
-
|
|
35096
|
-
|
|
35097
|
-
|
|
35098
|
-
|
|
35099
|
-
|
|
35100
|
-
|
|
35101
|
-
|
|
35102
|
-
|
|
35103
|
-
|
|
35104
|
-
|
|
35105
|
-
|
|
35106
|
-
|
|
35107
|
-
</
|
|
35108
|
-
|
|
35109
|
-
|
|
35110
|
-
|
|
35111
|
-
{%
|
|
35112
|
-
|
|
35113
|
-
|
|
35114
|
-
|
|
35115
|
-
|
|
35116
|
-
|
|
35117
|
-
{% if pagination.
|
|
35118
|
-
|
|
35119
|
-
|
|
35120
|
-
|
|
35121
|
-
|
|
35122
|
-
</
|
|
35123
|
-
|
|
35124
|
-
{% else %}
|
|
35125
|
-
<p>No posts yet!</p>
|
|
35145
|
+
{% block canonical %}{{ site.baseUrl }}/{% if pagination.currentPage > 1 %}page/{{ pagination.currentPage }}/{% endif %}{% endblock %}
|
|
35146
|
+
{% block og_url %}{{ site.baseUrl }}/{% if pagination.currentPage > 1 %}page/{{ pagination.currentPage }}/{% endif %}{% endblock %}
|
|
35147
|
+
|
|
35148
|
+
{% block content %}
|
|
35149
|
+
<h1>Latest Posts</h1>
|
|
35150
|
+
|
|
35151
|
+
{% if posts.length > 0 %}
|
|
35152
|
+
<div class="posts">
|
|
35153
|
+
{% for post in posts %}
|
|
35154
|
+
<article class="post-card">
|
|
35155
|
+
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
|
35156
|
+
<div class="post-meta">
|
|
35157
|
+
<time datetime="{{ post.date }}">{{ post.date | date("MMMM D, YYYY") }}</time>
|
|
35158
|
+
{% if post.tags.length > 0 %}
|
|
35159
|
+
<span class="tags">
|
|
35160
|
+
{% for tag in post.tags %}
|
|
35161
|
+
<a href="/tags/{{ post.tagSlugs[tag] }}/">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
|
35162
|
+
{% endfor %}
|
|
35163
|
+
</span>
|
|
35164
|
+
{% endif %}
|
|
35165
|
+
</div>
|
|
35166
|
+
<div class="post-excerpt">{{ post.excerpt }}</div>
|
|
35167
|
+
<a href="{{ post.url }}" class="read-more">Read more \u2192</a>
|
|
35168
|
+
</article>
|
|
35169
|
+
{% endfor %}
|
|
35170
|
+
</div>
|
|
35171
|
+
|
|
35172
|
+
{% if pagination.totalPages > 1 %}
|
|
35173
|
+
<nav class="pagination">
|
|
35174
|
+
{% if pagination.hasPrevPage %}
|
|
35175
|
+
<a href="{{ pagination.pagePath }}{% if pagination.prevPage > 1 %}page/{{ pagination.prevPage }}/{% endif %}" class="prev">\u2190 Previous</a>
|
|
35176
|
+
{% endif %}
|
|
35177
|
+
{% if pagination.hasNextPage %}
|
|
35178
|
+
<a href="{{ pagination.pagePath }}page/{{ pagination.nextPage }}/" class="next">Next \u2192</a>
|
|
35179
|
+
{% endif %}
|
|
35180
|
+
<span class="page-info">Page {{ pagination.currentPage }} of {{ pagination.totalPages }}</span>
|
|
35181
|
+
</nav>
|
|
35126
35182
|
{% endif %}
|
|
35127
|
-
{%
|
|
35183
|
+
{% else %}
|
|
35184
|
+
<p>No posts yet.</p>
|
|
35185
|
+
{% endif %}
|
|
35186
|
+
{% endblock %}`,
|
|
35128
35187
|
"post.njk": String.raw`{% extends "base.njk" %}
|
|
35129
35188
|
|
|
35130
|
-
|
|
35131
|
-
|
|
35132
|
-
|
|
35133
|
-
|
|
35134
|
-
|
|
35135
|
-
|
|
35136
|
-
|
|
35137
|
-
|
|
35138
|
-
|
|
35139
|
-
|
|
35140
|
-
|
|
35141
|
-
|
|
35142
|
-
|
|
35143
|
-
|
|
35144
|
-
|
|
35145
|
-
|
|
35146
|
-
|
|
35147
|
-
|
|
35148
|
-
|
|
35149
|
-
|
|
35150
|
-
|
|
35189
|
+
{% from "og-image.njk" import og_image, twitter_image %}
|
|
35190
|
+
{% from "json-ld.njk" import blog_posting_schema %}
|
|
35191
|
+
|
|
35192
|
+
{% block title %}{{ post.title }} | {{ site.title }}{% endblock %}
|
|
35193
|
+
{% block description %}{{ post.excerpt }}{% endblock %}
|
|
35194
|
+
|
|
35195
|
+
{% block canonical %}{{ site.baseUrl }}{{ post.url }}{% endblock %}
|
|
35196
|
+
|
|
35197
|
+
{% block og_type %}article{% endblock %}
|
|
35198
|
+
{% block og_title %}{{ post.title }}{% endblock %}
|
|
35199
|
+
{% block og_description %}{{ post.excerpt }}{% endblock %}
|
|
35200
|
+
{% block og_url %}{{ site.baseUrl }}{{ post.url }}{% endblock %}
|
|
35201
|
+
{% block og_image %}{{ og_image(post, site) }}{% endblock %}
|
|
35202
|
+
|
|
35203
|
+
{% block twitter_card %}summary_large_image{% endblock %}
|
|
35204
|
+
{% block twitter_title %}{{ post.title }}{% endblock %}
|
|
35205
|
+
{% block twitter_description %}{{ post.excerpt }}{% endblock %}
|
|
35206
|
+
{% block twitter_image %}{{ twitter_image(post, site) }}{% endblock %}
|
|
35207
|
+
|
|
35208
|
+
{% block head %}
|
|
35209
|
+
{{ blog_posting_schema(post, site) }}
|
|
35210
|
+
{% endblock %}
|
|
35211
|
+
|
|
35212
|
+
{% block content %}
|
|
35213
|
+
<article class="post">
|
|
35214
|
+
<header class="post-header">
|
|
35215
|
+
<h1>{{ post.title }}</h1>
|
|
35216
|
+
<div class="post-meta">
|
|
35217
|
+
<time datetime="{{ post.date }}">{{ post.date | date("MMMM D, YYYY") }}</time>
|
|
35218
|
+
{% if post.tags.length > 0 %}
|
|
35219
|
+
<span class="tags">
|
|
35220
|
+
{% for tag in post.tags %}
|
|
35221
|
+
<a href="/tags/{{ post.tagSlugs[tag] }}/">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
|
35222
|
+
{% endfor %}
|
|
35223
|
+
</span>
|
|
35224
|
+
{% endif %}
|
|
35151
35225
|
</div>
|
|
35152
|
-
</
|
|
35153
|
-
{% endblock %}`,
|
|
35154
|
-
"tag.njk": String.raw`{% extends "base.njk" %}
|
|
35155
|
-
|
|
35156
|
-
{% block title %}{{ tag.name }} | {{ site.title }}{% endblock %}
|
|
35157
|
-
{% block description %}Posts tagged with {{ tag.name }} on {{ site.title }}{% endblock %}
|
|
35158
|
-
|
|
35159
|
-
{% block content %}
|
|
35160
|
-
<h1>Posts tagged "{{ tag.name }}"</h1>
|
|
35161
|
-
|
|
35162
|
-
{% if tag.description %}
|
|
35163
|
-
<div class="tag-description">{{ tag.description }}</div>
|
|
35164
|
-
{% endif %}
|
|
35226
|
+
</header>
|
|
35165
35227
|
|
|
35166
|
-
|
|
35167
|
-
|
|
35168
|
-
|
|
35169
|
-
|
|
35170
|
-
|
|
35171
|
-
|
|
35172
|
-
|
|
35173
|
-
|
|
35174
|
-
|
|
35175
|
-
|
|
35176
|
-
|
|
35177
|
-
|
|
35228
|
+
<div class="post-content">
|
|
35229
|
+
{{ post.html | safe }}
|
|
35230
|
+
</div>
|
|
35231
|
+
|
|
35232
|
+
<footer class="post-footer">
|
|
35233
|
+
<div class="share-buttons">
|
|
35234
|
+
<span class="share-label">Share:</span>
|
|
35235
|
+
<a href="https://twitter.com/intent/tweet?text={{ post.title | urlencode }}&url={{ site.baseUrl }}{{ post.url }}" target="_blank" rel="noopener noreferrer" class="share-button x" aria-label="Share on X">
|
|
35236
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
|
|
35237
|
+
</a>
|
|
35238
|
+
<a href="https://www.facebook.com/sharer/sharer.php?u={{ site.baseUrl }}{{ post.url }}" target="_blank" rel="noopener noreferrer" class="share-button facebook" aria-label="Share on Facebook">
|
|
35239
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 3.667h-3.533v7.98H9.101z"/></svg>
|
|
35240
|
+
</a>
|
|
35241
|
+
<a href="https://www.linkedin.com/sharing/share-offsite/?url={{ site.baseUrl }}{{ post.url }}" target="_blank" rel="noopener noreferrer" class="share-button linkedin" aria-label="Share on LinkedIn">
|
|
35242
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M6.5 21.5h-5v-13h5v13zM4 6.5C2.5 6.5 1.5 5.3 1.5 4s1-2.4 2.5-2.4c1.6 0 2.5 1 2.6 2.5 0 1.4-1 2.5-2.6 2.5zm11.5 6c-1 0-2 1-2 2v7h-5v-13h5V10s1.6-1.5 4-1.5c3 0 5 2.2 5 6.3v6.7h-5v-7c0-1-1-2-2-2z"/></svg>
|
|
35243
|
+
</a>
|
|
35244
|
+
<a href="mailto:?subject={{ post.title | urlencode }}&body=Check%20out%20this%20article%3A%20{{ site.baseUrl }}{{ post.url }}" class="share-button email" aria-label="Share via Email">
|
|
35245
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
|
|
35246
|
+
</a>
|
|
35178
35247
|
</div>
|
|
35248
|
+
</footer>
|
|
35249
|
+
</article>
|
|
35250
|
+
{% endblock %}`,
|
|
35251
|
+
"tag.njk": String.raw`{% extends "base.njk" %}
|
|
35179
35252
|
|
|
35180
|
-
|
|
35181
|
-
|
|
35182
|
-
|
|
35183
|
-
|
|
35184
|
-
|
|
35185
|
-
|
|
35186
|
-
|
|
35187
|
-
|
|
35188
|
-
|
|
35189
|
-
|
|
35190
|
-
|
|
35191
|
-
|
|
35192
|
-
|
|
35193
|
-
|
|
35194
|
-
|
|
35253
|
+
{% block title %}{{ tag.name }} | {{ site.title }}{% endblock %}
|
|
35254
|
+
{% block description %}Posts tagged with {{ tag.name }} on {{ site.title }}{% endblock %}
|
|
35255
|
+
|
|
35256
|
+
{% block canonical %}{{ site.baseUrl }}/tags/{{ tag.slug }}/{% if pagination.currentPage > 1 %}page/{{ pagination.currentPage }}/{% endif %}{% endblock %}
|
|
35257
|
+
|
|
35258
|
+
{% block og_title %}{{ tag.name }} | {{ site.title }}{% endblock %}
|
|
35259
|
+
{% block og_description %}Posts tagged with {{ tag.name }} on {{ site.title }}{% endblock %}
|
|
35260
|
+
{% block og_url %}{{ site.baseUrl }}/tags/{{ tag.slug }}/{% if pagination.currentPage > 1 %}page/{{ pagination.currentPage }}/{% endif %}{% endblock %}
|
|
35261
|
+
|
|
35262
|
+
{% block twitter_title %}{{ tag.name }} | {{ site.title }}{% endblock %}
|
|
35263
|
+
{% block twitter_description %}Posts tagged with {{ tag.name }} on {{ site.title }}{% endblock %}
|
|
35264
|
+
|
|
35265
|
+
{% block content %}
|
|
35266
|
+
<h1>Posts tagged "{{ tag.name }}"</h1>
|
|
35267
|
+
|
|
35268
|
+
{% if tag.description %}
|
|
35269
|
+
<div class="tag-description">{{ tag.description }}</div>
|
|
35270
|
+
{% endif %}
|
|
35271
|
+
|
|
35272
|
+
{% if tag.posts.length > 0 %}
|
|
35273
|
+
<div class="posts">
|
|
35274
|
+
{% for post in tag.posts %}
|
|
35275
|
+
<article class="post-card">
|
|
35276
|
+
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
|
35277
|
+
<div class="post-meta">
|
|
35278
|
+
<time datetime="{{ post.date }}">{{ post.date | date("MMMM D, YYYY") }}</time>
|
|
35279
|
+
</div>
|
|
35280
|
+
<div class="post-excerpt">{{ post.excerpt }}</div>
|
|
35281
|
+
<a href="{{ post.url }}" class="read-more">Read more \u2192</a>
|
|
35282
|
+
</article>
|
|
35283
|
+
{% endfor %}
|
|
35284
|
+
</div>
|
|
35285
|
+
|
|
35286
|
+
{% if pagination.totalPages > 1 %}
|
|
35287
|
+
<nav class="pagination">
|
|
35288
|
+
{% if pagination.hasPrevPage %}
|
|
35289
|
+
<a href="{{ pagination.pagePath }}{% if pagination.prevPage > 1 %}page/{{ pagination.prevPage }}/{% endif %}" class="prev">\u2190 Previous</a>
|
|
35290
|
+
{% endif %}
|
|
35291
|
+
{% if pagination.hasNextPage %}
|
|
35292
|
+
<a href="{{ pagination.pagePath }}page/{{ pagination.nextPage }}/" class="next">Next \u2192</a>
|
|
35293
|
+
{% endif %}
|
|
35294
|
+
<span class="page-info">Page {{ pagination.currentPage }} of {{ pagination.totalPages }}</span>
|
|
35295
|
+
</nav>
|
|
35195
35296
|
{% endif %}
|
|
35196
|
-
{%
|
|
35297
|
+
{% else %}
|
|
35298
|
+
<p>No posts with this tag yet.</p>
|
|
35299
|
+
{% endif %}
|
|
35300
|
+
{% endblock %}`,
|
|
35197
35301
|
"tags.njk": String.raw`{% extends "base.njk" %}
|
|
35198
35302
|
|
|
35199
|
-
|
|
35200
|
-
|
|
35303
|
+
{% block title %}Tags | {{ site.title }}{% endblock %}
|
|
35304
|
+
{% block description %}Browse all tags on {{ site.title }}{% endblock %}
|
|
35201
35305
|
|
|
35202
|
-
|
|
35203
|
-
<h1>All Tags</h1>
|
|
35306
|
+
{% block canonical %}{{ site.baseUrl }}/tags/{% endblock %}
|
|
35204
35307
|
|
|
35205
|
-
|
|
35206
|
-
|
|
35207
|
-
|
|
35208
|
-
<li>
|
|
35209
|
-
<a href="/tags/{{ tag.slug }}/">{{ tag.name }}</a>
|
|
35210
|
-
<span class="count">({{ tag.count }})</span>
|
|
35211
|
-
{% if tag.description %}
|
|
35212
|
-
<p class="description">{{ tag.description }}</p>
|
|
35213
|
-
{% endif %}
|
|
35214
|
-
</li>
|
|
35215
|
-
{% endfor %}
|
|
35216
|
-
</ul>
|
|
35217
|
-
{% else %}
|
|
35218
|
-
<p>No tags found!</p>
|
|
35219
|
-
{% endif %}
|
|
35220
|
-
{% endblock %}`,
|
|
35221
|
-
"archive.njk": String.raw`{% extends "base.njk" %}
|
|
35308
|
+
{% block og_title %}Tags | {{ site.title }}{% endblock %}
|
|
35309
|
+
{% block og_description %}Browse all tags on {{ site.title }}{% endblock %}
|
|
35310
|
+
{% block og_url %}{{ site.baseUrl }}/tags/{% endblock %}
|
|
35222
35311
|
|
|
35223
|
-
|
|
35224
|
-
|
|
35225
|
-
|
|
35226
|
-
{% block content %}
|
|
35227
|
-
<h1>Posts from {{ year }}</h1>
|
|
35228
|
-
|
|
35229
|
-
{% if posts.length > 0 %}
|
|
35230
|
-
<div class="posts">
|
|
35231
|
-
{% for post in posts %}
|
|
35232
|
-
<article class="post-card">
|
|
35233
|
-
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
|
35234
|
-
<div class="post-meta">
|
|
35235
|
-
<time datetime="{{ post.date }}">{{ post.date | date("MMMM D, YYYY") }}</time>
|
|
35236
|
-
{% if post.tags.length > 0 %}
|
|
35237
|
-
<span class="tags">
|
|
35238
|
-
{% for tag in post.tags %}
|
|
35239
|
-
<a href="/tags/{{ post.tagSlugs[tag] }}/">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
|
35240
|
-
{% endfor %}
|
|
35241
|
-
</span>
|
|
35242
|
-
{% endif %}
|
|
35243
|
-
</div>
|
|
35244
|
-
<div class="post-excerpt">{{ post.excerpt }}</div>
|
|
35245
|
-
<a href="{{ post.url }}" class="read-more">Read more \u2192</a>
|
|
35246
|
-
</article>
|
|
35247
|
-
{% endfor %}
|
|
35248
|
-
</div>
|
|
35312
|
+
{% block twitter_title %}Tags | {{ site.title }}{% endblock %}
|
|
35313
|
+
{% block twitter_description %}Browse all tags on {{ site.title }}{% endblock %}
|
|
35249
35314
|
|
|
35250
|
-
|
|
35251
|
-
|
|
35252
|
-
{% if pagination.hasPrevPage %}
|
|
35253
|
-
<a href="/{{ year }}/{% if pagination.prevPage > 1 %}page/{{ pagination.prevPage }}/{% endif %}" class="prev">\u2190 Previous</a>
|
|
35254
|
-
{% endif %}
|
|
35315
|
+
{% block content %}
|
|
35316
|
+
<h1>All Tags</h1>
|
|
35255
35317
|
|
|
35256
|
-
|
|
35257
|
-
|
|
35318
|
+
{% if tags.length > 0 %}
|
|
35319
|
+
<ul class="tags-list">
|
|
35320
|
+
{% for tag in tags %}
|
|
35321
|
+
<li>
|
|
35322
|
+
<a href="/tags/{{ tag.slug }}/">{{ tag.name }}</a>
|
|
35323
|
+
<span class="count">({{ tag.count }})</span>
|
|
35324
|
+
{% if tag.description %}
|
|
35325
|
+
<p class="description">{{ tag.description }}</p>
|
|
35258
35326
|
{% endif %}
|
|
35327
|
+
</li>
|
|
35328
|
+
{% endfor %}
|
|
35329
|
+
</ul>
|
|
35330
|
+
{% else %}
|
|
35331
|
+
<p>No tags yet.</p>
|
|
35332
|
+
{% endif %}
|
|
35333
|
+
{% endblock %}`,
|
|
35334
|
+
"archive.njk": String.raw`{% extends "base.njk" %}
|
|
35259
35335
|
|
|
35260
|
-
|
|
35261
|
-
|
|
35262
|
-
|
|
35263
|
-
|
|
35264
|
-
|
|
35336
|
+
{% block title %}Archive {{ year }} | {{ site.title }}{% endblock %}
|
|
35337
|
+
{% block description %}Posts from {{ year }} on {{ site.title }}{% endblock %}
|
|
35338
|
+
|
|
35339
|
+
{% block canonical %}{{ site.baseUrl }}/{{ year }}/{% if pagination.currentPage > 1 %}page/{{ pagination.currentPage }}/{% endif %}{% endblock %}
|
|
35340
|
+
|
|
35341
|
+
{% block og_title %}Archive {{ year }} | {{ site.title }}{% endblock %}
|
|
35342
|
+
{% block og_description %}Posts from {{ year }} on {{ site.title }}{% endblock %}
|
|
35343
|
+
{% block og_url %}{{ site.baseUrl }}/{{ year }}/{% if pagination.currentPage > 1 %}page/{{ pagination.currentPage }}/{% endif %}{% endblock %}
|
|
35344
|
+
|
|
35345
|
+
{% block twitter_title %}Archive {{ year }} | {{ site.title }}{% endblock %}
|
|
35346
|
+
{% block twitter_description %}Posts from {{ year }} on {{ site.title }}{% endblock %}
|
|
35347
|
+
|
|
35348
|
+
{% block content %}
|
|
35349
|
+
<h1>Posts from {{ year }}</h1>
|
|
35350
|
+
|
|
35351
|
+
{% if posts.length > 0 %}
|
|
35352
|
+
<div class="posts">
|
|
35353
|
+
{% for post in posts %}
|
|
35354
|
+
<article class="post-card">
|
|
35355
|
+
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
|
35356
|
+
<div class="post-meta">
|
|
35357
|
+
<time datetime="{{ post.date }}">{{ post.date | date("MMMM D, YYYY") }}</time>
|
|
35358
|
+
{% if post.tags.length > 0 %}
|
|
35359
|
+
<span class="tags">
|
|
35360
|
+
{% for tag in post.tags %}
|
|
35361
|
+
<a href="/tags/{{ post.tagSlugs[tag] }}/">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
|
35362
|
+
{% endfor %}
|
|
35363
|
+
</span>
|
|
35364
|
+
{% endif %}
|
|
35365
|
+
</div>
|
|
35366
|
+
<div class="post-excerpt">{{ post.excerpt }}</div>
|
|
35367
|
+
<a href="{{ post.url }}" class="read-more">Read more \u2192</a>
|
|
35368
|
+
</article>
|
|
35369
|
+
{% endfor %}
|
|
35370
|
+
</div>
|
|
35371
|
+
|
|
35372
|
+
{% if pagination.totalPages > 1 %}
|
|
35373
|
+
<nav class="pagination">
|
|
35374
|
+
{% if pagination.hasPrevPage %}
|
|
35375
|
+
<a href="/{{ year }}/{% if pagination.prevPage > 1 %}page/{{ pagination.prevPage }}/{% endif %}" class="prev">\u2190 Previous</a>
|
|
35376
|
+
{% endif %}
|
|
35377
|
+
{% if pagination.hasNextPage %}
|
|
35378
|
+
<a href="/{{ year }}/page/{{ pagination.nextPage }}/" class="next">Next \u2192</a>
|
|
35379
|
+
{% endif %}
|
|
35380
|
+
<span class="page-info">Page {{ pagination.currentPage }} of {{ pagination.totalPages }}</span>
|
|
35381
|
+
</nav>
|
|
35265
35382
|
{% endif %}
|
|
35266
|
-
{%
|
|
35383
|
+
{% else %}
|
|
35384
|
+
<p>No posts from {{ year }} yet.</p>
|
|
35385
|
+
{% endif %}
|
|
35386
|
+
{% endblock %}`
|
|
35267
35387
|
};
|
|
35268
35388
|
}
|
|
35269
35389
|
function getDefaultCss() {
|
|
@@ -35458,6 +35578,43 @@ function getDefaultCss() {
|
|
|
35458
35578
|
font-size: 0.9rem;
|
|
35459
35579
|
}
|
|
35460
35580
|
|
|
35581
|
+
/* Share buttons */
|
|
35582
|
+
.post-footer {
|
|
35583
|
+
margin-top: 2rem;
|
|
35584
|
+
padding-top: 1.5rem;
|
|
35585
|
+
border-top: 1px solid #eee;
|
|
35586
|
+
}
|
|
35587
|
+
|
|
35588
|
+
.share-buttons {
|
|
35589
|
+
display: flex;
|
|
35590
|
+
align-items: center;
|
|
35591
|
+
gap: 0.75rem;
|
|
35592
|
+
}
|
|
35593
|
+
|
|
35594
|
+
.share-label {
|
|
35595
|
+
font-size: 0.9rem;
|
|
35596
|
+
font-weight: 500;
|
|
35597
|
+
color: #6c757d;
|
|
35598
|
+
}
|
|
35599
|
+
|
|
35600
|
+
.share-button {
|
|
35601
|
+
display: inline-flex;
|
|
35602
|
+
align-items: center;
|
|
35603
|
+
justify-content: center;
|
|
35604
|
+
width: 2.25rem;
|
|
35605
|
+
height: 2.25rem;
|
|
35606
|
+
border-radius: 50%;
|
|
35607
|
+
background-color: #f5f5f5;
|
|
35608
|
+
color: #555;
|
|
35609
|
+
transition: background-color 0.2s, color 0.2s;
|
|
35610
|
+
}
|
|
35611
|
+
|
|
35612
|
+
.share-button:hover { text-decoration: none; }
|
|
35613
|
+
.share-button.x:hover { background-color: #000; color: #fff; }
|
|
35614
|
+
.share-button.facebook:hover { background-color: #1877f2; color: #fff; }
|
|
35615
|
+
.share-button.linkedin:hover { background-color: #0077b5; color: #fff; }
|
|
35616
|
+
.share-button.email:hover { background-color: #6c757d; color: #fff; }
|
|
35617
|
+
|
|
35461
35618
|
/* Footer */
|
|
35462
35619
|
footer {
|
|
35463
35620
|
text-align: center;
|
|
@@ -35521,7 +35678,7 @@ function hello() {
|
|
|
35521
35678
|
}
|
|
35522
35679
|
|
|
35523
35680
|
// src/cli/commands/new-post.ts
|
|
35524
|
-
import
|
|
35681
|
+
import path15 from "path";
|
|
35525
35682
|
var defaultDeps4 = {
|
|
35526
35683
|
writeFile: (filePath, data) => Bun.write(filePath, data),
|
|
35527
35684
|
now: () => new Date,
|
|
@@ -35545,7 +35702,7 @@ async function handleNewCommand(title, options2, deps = defaultDeps4) {
|
|
|
35545
35702
|
` + `# ${title}
|
|
35546
35703
|
|
|
35547
35704
|
`;
|
|
35548
|
-
const filePath =
|
|
35705
|
+
const filePath = path15.join(DEFAULT_CONTENT_DIR, `${slug}.md`);
|
|
35549
35706
|
await deps.writeFile(filePath, frontmatter);
|
|
35550
35707
|
deps.logger.log(`Created new post: ${filePath}`);
|
|
35551
35708
|
return filePath;
|
|
@@ -35562,11 +35719,11 @@ function registerNewCommand(program2) {
|
|
|
35562
35719
|
}
|
|
35563
35720
|
|
|
35564
35721
|
// src/cli/commands/serve.ts
|
|
35565
|
-
import
|
|
35722
|
+
import path17 from "path";
|
|
35566
35723
|
|
|
35567
35724
|
// src/server.ts
|
|
35568
35725
|
import fs2 from "fs";
|
|
35569
|
-
import
|
|
35726
|
+
import path16 from "path";
|
|
35570
35727
|
async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
35571
35728
|
try {
|
|
35572
35729
|
const stats = await fs2.promises.stat(outputDir);
|
|
@@ -35601,18 +35758,18 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
|
35601
35758
|
let filePath = "";
|
|
35602
35759
|
if (homePaginationMatch) {
|
|
35603
35760
|
const pageNumber = homePaginationMatch[1];
|
|
35604
|
-
filePath =
|
|
35761
|
+
filePath = path16.join(outputDir, "page", pageNumber, "index.html");
|
|
35605
35762
|
} else if (tagPaginationMatch) {
|
|
35606
35763
|
const tagSlug = tagPaginationMatch[1];
|
|
35607
35764
|
const pageNumber = tagPaginationMatch[2];
|
|
35608
|
-
filePath =
|
|
35765
|
+
filePath = path16.join(outputDir, "tags", tagSlug, "page", pageNumber, "index.html");
|
|
35609
35766
|
} else if (yearPaginationMatch) {
|
|
35610
35767
|
const year = yearPaginationMatch[1];
|
|
35611
35768
|
const pageNumber = yearPaginationMatch[2];
|
|
35612
|
-
filePath =
|
|
35769
|
+
filePath = path16.join(outputDir, year, "page", pageNumber, "index.html");
|
|
35613
35770
|
} else {
|
|
35614
|
-
const directPath =
|
|
35615
|
-
const withoutSlash =
|
|
35771
|
+
const directPath = path16.join(outputDir, pathname);
|
|
35772
|
+
const withoutSlash = path16.join(outputDir, pathname + ".html");
|
|
35616
35773
|
const withHtml = pathname.endsWith(".html") ? directPath : withoutSlash;
|
|
35617
35774
|
const bunFileDirect = Bun.file(directPath);
|
|
35618
35775
|
const bunFileHtml = Bun.file(withHtml);
|
|
@@ -35621,7 +35778,7 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
|
35621
35778
|
} else if (await bunFileHtml.exists()) {
|
|
35622
35779
|
filePath = withHtml;
|
|
35623
35780
|
} else {
|
|
35624
|
-
const indexPath =
|
|
35781
|
+
const indexPath = path16.join(outputDir, pathname, "index.html");
|
|
35625
35782
|
const bunFileIndex = Bun.file(indexPath);
|
|
35626
35783
|
if (await bunFileIndex.exists()) {
|
|
35627
35784
|
filePath = indexPath;
|
|
@@ -35635,7 +35792,7 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
|
35635
35792
|
}
|
|
35636
35793
|
}
|
|
35637
35794
|
console.log(`Serving file: ${filePath}`);
|
|
35638
|
-
const extname =
|
|
35795
|
+
const extname = path16.extname(filePath);
|
|
35639
35796
|
let contentType = "text/html";
|
|
35640
35797
|
switch (extname) {
|
|
35641
35798
|
case ".js":
|
|
@@ -35694,7 +35851,7 @@ var defaultDeps5 = {
|
|
|
35694
35851
|
};
|
|
35695
35852
|
async function handleServeCommand(options2, deps = defaultDeps5) {
|
|
35696
35853
|
try {
|
|
35697
|
-
const outputDir =
|
|
35854
|
+
const outputDir = path17.resolve(options2.output);
|
|
35698
35855
|
const port = parseInt(options2.port, 10);
|
|
35699
35856
|
await deps.startServer(outputDir, port);
|
|
35700
35857
|
} catch (error) {
|
|
@@ -35737,7 +35894,7 @@ function registerValidateCommand(program2) {
|
|
|
35737
35894
|
}
|
|
35738
35895
|
|
|
35739
35896
|
// src/cli/commands/validate-media.ts
|
|
35740
|
-
import { readdirSync, readFileSync, existsSync, statSync } from "fs";
|
|
35897
|
+
import { readdirSync, readFileSync, existsSync as existsSync2, statSync } from "fs";
|
|
35741
35898
|
import { join, dirname, resolve, basename } from "path";
|
|
35742
35899
|
var imageExtensions = [".jpg", ".jpeg", ".png", ".webp", ".gif"];
|
|
35743
35900
|
var videoExtensions = [".mp4", ".webm", ".mov"];
|
|
@@ -35745,7 +35902,7 @@ var mediaExtensions = [...imageExtensions, ...videoExtensions];
|
|
|
35745
35902
|
async function handleValidateMediaCommand(options2, deps = { logger: console, exit: (code) => process.exit(code) }) {
|
|
35746
35903
|
const contentDir = options2.contentDir || join(process.cwd(), "content");
|
|
35747
35904
|
const assetsDir = join(process.cwd(), "assets");
|
|
35748
|
-
if (!
|
|
35905
|
+
if (!existsSync2(contentDir)) {
|
|
35749
35906
|
deps.logger.error(`Content directory not found: ${contentDir}`);
|
|
35750
35907
|
deps.exit(1);
|
|
35751
35908
|
}
|
|
@@ -35867,7 +36024,7 @@ function validateMedia(contentDir, assetsDir) {
|
|
|
35867
36024
|
}
|
|
35868
36025
|
function getAllMediaFromContentAssets(contentDir) {
|
|
35869
36026
|
const mediaFiles = [];
|
|
35870
|
-
if (!
|
|
36027
|
+
if (!existsSync2(contentDir))
|
|
35871
36028
|
return [];
|
|
35872
36029
|
const years = readdirSync(contentDir).filter((f) => {
|
|
35873
36030
|
const fullPath = join(contentDir, f);
|
|
@@ -35875,7 +36032,7 @@ function getAllMediaFromContentAssets(contentDir) {
|
|
|
35875
36032
|
});
|
|
35876
36033
|
for (const year of years) {
|
|
35877
36034
|
const assetsDir = join(contentDir, year, "_assets");
|
|
35878
|
-
if (!
|
|
36035
|
+
if (!existsSync2(assetsDir))
|
|
35879
36036
|
continue;
|
|
35880
36037
|
const files = readdirSync(assetsDir);
|
|
35881
36038
|
for (const file of files) {
|
|
@@ -35897,7 +36054,7 @@ function getAllMediaFromContentAssets(contentDir) {
|
|
|
35897
36054
|
}
|
|
35898
36055
|
function getAllMediaFromAssets(assetsDir) {
|
|
35899
36056
|
const mediaFiles = [];
|
|
35900
|
-
if (!
|
|
36057
|
+
if (!existsSync2(assetsDir))
|
|
35901
36058
|
return [];
|
|
35902
36059
|
const years = readdirSync(assetsDir).filter((f) => {
|
|
35903
36060
|
const fullPath = join(assetsDir, f);
|
|
@@ -35929,7 +36086,7 @@ function getAllMediaFromAssets(assetsDir) {
|
|
|
35929
36086
|
function checkMediaReference(markdownFile, lineNumber, mediaPath, type, missingReferences) {
|
|
35930
36087
|
const markdownDir = dirname(markdownFile);
|
|
35931
36088
|
const resolvedPath = resolve(markdownDir, mediaPath);
|
|
35932
|
-
if (!
|
|
36089
|
+
if (!existsSync2(resolvedPath)) {
|
|
35933
36090
|
missingReferences.push({
|
|
35934
36091
|
file: markdownFile.replace(process.cwd() + "/", ""),
|
|
35935
36092
|
line: lineNumber,
|