Adapt Indiekit

This commit is contained in:
JP Hastings-Spital 2024-03-27 14:24:26 +00:00
parent 1191b224b8
commit 5c58632d42
17 changed files with 2816 additions and 5687 deletions

1
.env
View file

@ -1,2 +1,3 @@
HUGO_LAST_UPDATE_TIME=$(git log -1 --format=%cI)
HUGO_LAST_UPDATE_HASH=$(git log -1 --format=%h)
HUGO_INDIEKIT_URL=https://indiekit.byjp.me${DOMAIN_SUFFIX}

View file

@ -5,17 +5,24 @@ version: '3'
# TODO: Handle commands in dotenv
# dotenv: ['.env']
env:
DOMAIN: https://www.byjp.me
tasks:
check-links:
cmds:
- rm -rf ./public/
- lychee --cache --suggest --require-https .
dev:
env:
DOMAIN_SUFFIX: .test
cmds:
- hugo server --minify -D
- |
set -o allexport; source .env; set +o allexport
hugo server --minify --printUnusedTemplates -D --baseURL "${DOMAIN}${DOMAIN_SUFFIX}" --appendPort=false
build:
cmds:
- hugo --cacheDir /tmp/hugo/cache --minify --baseURL "https://www.byjp.me"
- hugo --cacheDir /tmp/hugo/cache --gc --minify --baseURL "${DOMAIN}"
- npm_config_yes=true npx pagefind@latest
import:
cmds:

1
indiekit/.gitignore vendored
View file

@ -1 +1,2 @@
node_modules
.env

98
indiekit/.indiekitrc.js Normal file
View file

@ -0,0 +1,98 @@
export default {
"application": {
"mongodbUrl": process.env.MONGO_URL,
},
"plugins": [
"indiekit-preset-byjp",
"@indiekit/store-github",
"@indiekit/syndicator-mastodon",
"@indiekit/endpoint-files",
"@indiekit/endpoint-image",
"@indiekit/endpoint-micropub",
"@indiekit/endpoint-posts",
"@indiekit/endpoint-share",
"@indiekit/post-type-bookmark",
"@indiekit/post-type-event",
"@indiekit/post-type-like",
"@indiekit/post-type-note",
"@indiekit/post-type-photo",
"@indiekit/post-type-jam",
"@indiekit/post-type-reply"
],
"publication": {
"me": process.env.SITE,
"enrichPostData": true,
"postTypes": {
"event": {
"name": "Event",
"post": {
"path": "content/calendar/{yyyy}-{MM}-{dd}/{slug}/index.md",
"url": "calendar/{yyyy}-{MM}-{dd}/{slug}"
}
},
"like": {
"name": "Like",
"post": {
"path": "content/notes/{yyyy}-{MM}-{dd}/{slug}/index.md",
"url": "notes/{yyyy}-{MM}-{dd}/{slug}/"
}
},
"note": {
"name": "Note",
"post": {
"path": "content/notes/{yyyy}-{MM}-{dd}/{slug}/index.md",
"url": "notes/{yyyy}-{MM}-{dd}/{slug}/"
}
},
"reply": {
"name": "Reply",
"post": {
"path": "content/notes/{yyyy}-{MM}-{dd}/{slug}/index.md",
"url": "notes/{yyyy}-{MM}-{dd}/{slug}/"
}
},
"photo": {
"name": "Photo",
"post": {
"path": "content/photos/{yyyy}-{MM}-{dd}/{slug}/index.md",
"url": "photos/{yyyy}-{MM}-{dd}/{slug}/"
},
"media": {
"path": "content/photos/{yyyy}-{MM}-{dd}/{slug}/{filename}"
}
}
}
},
"indiekit-preset-byjp": {},
"@indiekit/store-github": {
"user": "by-jp",
"repo": "www.byjp.me",
"branch": "main"
},
"@indiekit/syndicator-mastodon": {
"url": "https://hachyderm.io",
"user": "byjp"
},
"@indiekit/post-type-bookmark": {
"name": "Bookmark"
},
"@indiekit/post-type-photo": {
"name": "Photo"
},
"@indiekit/post-type-jam": {
"name": "Jam"
},
"@indiekit/post-type-like": {
"name": "Like"
},
"@indiekit/post-type-note": {
"name": "Note"
},
"@indiekit/post-type-reply": {
"name": "Reply"
},
"@indiekit/endpoint-micropub": {},
"@indiekit/endpoint-share": {},
"@indiekit/endpoint-files": {},
"@indiekit/endpoint-posts": {}
}

1
indiekit/.node-version Normal file
View file

@ -0,0 +1 @@
20

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,10 @@
"version": "1.0.0",
"description": "Indiekit server for https://www.byjp.me",
"main": "index.js",
"type": "module",
"scripts": {
"start": "indiekit serve"
"start": "indiekit serve",
"dotenv": "dotenv-run-script .env --"
},
"keywords": [
"indiekit",
@ -15,106 +17,6 @@
"engines": {
"node": "20"
},
"indiekit": {
"application": {
"mongodbUrl": "mongodb://127.0.0.1:27017/indiekit"
},
"plugins": [
"@indiekit/preset-hugo",
"@indiekit/store-github",
"@indiekit/syndicator-mastodon",
"@indiekit/endpoint-files",
"@indiekit/endpoint-image",
"@indiekit/endpoint-micropub",
"@indiekit/endpoint-posts",
"@indiekit/endpoint-share",
"@indiekit/post-type-bookmark",
"@indiekit/post-type-event",
"@indiekit/post-type-like",
"@indiekit/post-type-note",
"@indiekit/post-type-photo",
"@indiekit/post-type-jam",
"@indiekit/post-type-reply"
],
"publication": {
"me": "https://www.byjp.me",
"enrichPostData": true,
"postTypes": {
"event": {
"name": "Event",
"post": {
"path": "content/calendar/{yyyy}-{MM}-{dd}/{slug}/index.md",
"url": "calendar/{yyyy}-{MM}-{dd}/{slug}"
}
},
"like": {
"name": "Like",
"post": {
"path": "content/notes/{yyyy}-{MM}-{dd}/{slug}/index.md",
"url": "notes/{yyyy}-{MM}-{dd}/{slug}/"
}
},
"note": {
"name": "Note",
"post": {
"path": "content/notes/{yyyy}-{MM}-{dd}/{slug}/index.md",
"url": "notes/{yyyy}-{MM}-{dd}/{slug}/"
}
},
"reply": {
"name": "Reply",
"post": {
"path": "content/notes/{yyyy}-{MM}-{dd}/{slug}/index.md",
"url": "notes/{yyyy}-{MM}-{dd}/{slug}/"
}
},
"photo": {
"name": "Photo",
"post": {
"path": "content/photos/{yyyy}-{MM}-{dd}/{slug}/index.md",
"url": "photos/{yyyy}-{MM}-{dd}/{slug}/"
},
"media": {
"path": "content/photos/{yyyy}-{MM}-{dd}/{slug}/{filename}"
}
}
}
},
"@indiekit/preset-hugo": {
"frontMatterFormat": "yaml"
},
"@indiekit/store-github": {
"user": "by-jp",
"repo": "www.byjp.me",
"branch": "main"
},
"@indiekit/syndicator-mastodon": {
"url": "https://hachyderm.io",
"user": "byjp"
},
"@indiekit/post-type-bookmark": {
"name": "Bookmark"
},
"@indiekit/post-type-photo": {
"name": "Photo"
},
"@indiekit/post-type-jam": {
"name": "Jam"
},
"@indiekit/post-type-like": {
"name": "Like"
},
"@indiekit/post-type-note": {
"name": "Note"
},
"@indiekit/post-type-reply": {
"name": "Reply"
},
"@indiekit/endpoint-micropub": {},
"@indiekit/endpoint-share": {},
"@indiekit/endpoint-files": {},
"@indiekit/endpoint-posts": {}
},
"dependencies": {
"@indiekit/endpoint-files": "^1.0.0-beta.8",
"@indiekit/endpoint-image": "^1.0.0-beta.8",
@ -129,8 +31,11 @@
"@indiekit/post-type-note": "^1.0.0-beta.8",
"@indiekit/post-type-photo": "^1.0.0-beta.8",
"@indiekit/post-type-reply": "^1.0.0-beta.8",
"@indiekit/preset-hugo": "^1.0.0-beta.8",
"@indiekit/store-github": "^1.0.0-beta.8",
"@indiekit/syndicator-mastodon": "^1.0.0-beta.8"
"@indiekit/syndicator-mastodon": "^1.0.0-beta.8",
"indiekit-preset-byjp": "file:./packages/indiekit-preset-byjp"
},
"devDependencies": {
"dotenv-run-script": "^0.4.1"
}
}

View file

@ -0,0 +1,3 @@
# indiekit-preset-byjp
A (work in progress) adaptation of [@indiekit/preset-hugo](https://www.npmjs.com/package/@indiekit/preset-hugo) to create posts in the right format for this blog.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -0,0 +1,32 @@
import { getPostTemplate } from "./lib/post-template.js";
import { getPostTypes } from "./lib/post-types.js";
const defaults = {};
export default class HugoPreset {
constructor(options = {}) {
this.name = "byJP preset";
this.options = { ...defaults, ...options };
}
get info() {
return {
name: "byJP",
};
}
get prompts() {
return [];
}
postTemplate(properties) {
return getPostTemplate(properties);
}
init(Indiekit) {
const { application } = Indiekit.config;
this.postTypes = getPostTypes(application.postTypes);
Indiekit.addPreset(this);
}
}

View file

@ -0,0 +1,75 @@
import camelcaseKeys from "camelcase-keys";
import YAML from "yaml";
/**
* Get content
* @access private
* @param {object} properties - JF2 properties
* @returns {string} Content
*/
const getContent = (properties) => {
if (properties.content) {
const content =
properties.content.text || properties.content.html || properties.content;
return `\n${content}\n`;
} else {
return "";
}
};
/**
* Get front matter
* @access private
* @param {object} properties - JF2 properties
* @returns {string} Front matter in chosen format
*/
const getFrontMatter = (properties) => {
/**
* Go templates dont accept hyphens in property names
* and Hugo camelCases its predefined front matter keys
* @see {@link https://gohugo.io/content-management/front-matter/}
*/
properties = camelcaseKeys(properties, { deep: true });
/**
* Replace Microformat properties with Hugo equivalents
* @see {@link https://gohugo.io/content-management/front-matter/}
*/
properties = {
date: properties.published,
publishDate: properties.published,
...(properties.postStatus === "draft" && { draft: true }),
...(properties.updated && { lastmod: properties.updated }),
...(properties.deleted && { expiryDate: properties.deleted }),
...(properties.name && { title: properties.name }),
...(properties.photo && {
images: properties.photo.map((image) => image.url),
}),
...properties,
};
delete properties.content; // Shown below front matter
delete properties.deleted; // Use `expiryDate`
delete properties.name; // Use `title`
delete properties.postStatus; // Use `draft`
delete properties.published; // Use `date`
delete properties.type; // Not required
delete properties.updated; // Use `lastmod`
delete properties.url; // Not required
const frontMatter = YAML.stringify(properties, { lineWidth: 0 });
return `---\n${frontMatter}---\n`;
};
/**
* Get post template
* @param {object} properties - JF2 properties
* @param {string} [frontMatterFormat] - Front matter format
* @returns {string} Rendered template
*/
export const getPostTemplate = (properties) => {
const content = getContent(properties);
const frontMatter = getFrontMatter(properties);
return frontMatter + content;
};

View file

@ -0,0 +1,31 @@
import plur from "plur";
/**
* Get paths and URLs for configured post types
* @param {object} postTypes - Post type configuration
* @returns {object} Updated post type configuration
*/
export const getPostTypes = (postTypes) => {
for (const type of Object.keys(postTypes)) {
const section = plur(type);
/**
* Follow Hugo content management guidelines
* @see {@link https://gohugo.io/content-management/organization/}
* @see {@link https://gohugo.io/content-management/static-files/}
*/
postTypes[type] = {
...postTypes[type],
post: {
path: `content/${section}/{slug}.md`,
url: `${section}/{slug}`,
},
media: {
path: `static/${section}/{filename}`,
url: `${section}/{filename}`,
},
};
}
return postTypes;
};

View file

@ -0,0 +1,44 @@
{
"name": "indiekit-preset-byjp",
"version": "1.0.0-beta.8",
"description": "Hugo publication preset for Indiekit",
"keywords": [
"indiekit",
"indiekit-plugin",
"indieweb",
"micropub",
"jekyll"
],
"homepage": "https://getindiekit.com",
"author": {
"name": "Paul Robert Lloyd",
"url": "https://paulrobertlloyd.com"
},
"license": "MIT",
"engines": {
"node": ">=20"
},
"type": "module",
"main": "index.js",
"files": [
"assets",
"lib",
"index.js"
],
"bugs": {
"url": "https://github.com/getindiekit/indiekit/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/getindiekit/indiekit.git",
"directory": "packages/preset-hugo"
},
"dependencies": {
"camelcase-keys": "^9.0.0",
"plur": "^5.1.0",
"yaml": "^2.4.0"
},
"publishConfig": {
"access": "public"
}
}

View file

@ -0,0 +1,55 @@
import { strict as assert } from "node:assert";
import { describe, it } from "node:test";
import { Indiekit } from "@indiekit/indiekit";
import HugoPreset from "../index.js";
describe("preset-hugo", async () => {
const hugo = new HugoPreset();
const indiekit = await Indiekit.initialize({
config: {
publication: {
me: "https://website.example",
postTypes: {
puppy: {
name: "Puppy posts",
},
},
},
},
});
const bootstrappedConfig = await indiekit.bootstrap();
hugo.init(bootstrappedConfig);
it("Initiates plug-in", async () => {
assert.equal(indiekit.publication.preset.info.name, "Hugo");
});
it("Gets plug-in info", () => {
assert.equal(hugo.name, "Hugo preset");
assert.equal(hugo.info.name, "Hugo");
});
it("Gets plug-in installation prompts", () => {
assert.equal(
hugo.prompts[0].message,
"Which front matter format are you using?",
);
});
it("Gets publication post types", () => {
assert.deepEqual(hugo.postTypes.article.post, {
path: "content/articles/{slug}.md",
url: "articles/{slug}",
});
});
it("Renders post template", () => {
const result = hugo.postTemplate({
published: "2020-02-02",
name: "Lunchtime",
});
assert.equal(result.includes("date: 2020-02-02"), true);
});
});

View file

@ -0,0 +1,216 @@
import { strict as assert } from "node:assert";
import { describe, it } from "node:test";
import { getFixture } from "@indiekit-test/fixtures";
import { getPostTemplate } from "../../lib/post-template.js";
describe("preset-jekyll/lib/post-template", async () => {
const properties = JSON.parse(getFixture("jf2/post-template-properties.jf2"));
it("Renders post template without content", () => {
const result = getPostTemplate({
published: "2020-02-02",
updated: "2022-12-11",
deleted: "2022-12-12",
name: "What I had for lunch",
});
assert.equal(
result,
`---
date: 2020-02-02
publishDate: 2020-02-02
lastmod: 2022-12-11
expiryDate: 2022-12-12
title: What I had for lunch
---
`,
);
});
it("Renders post template with basic draft content", () => {
const result = getPostTemplate({
published: "2020-02-02",
name: "What I had for lunch",
content:
"I ate a [cheese](https://en.wikipedia.org/wiki/Cheese) sandwich, which was nice.",
"post-status": "draft",
});
assert.equal(
result,
`---
date: 2020-02-02
publishDate: 2020-02-02
draft: true
title: What I had for lunch
---
I ate a [cheese](https://en.wikipedia.org/wiki/Cheese) sandwich, which was nice.
`,
);
});
it("Renders post template with HTML content", () => {
const result = getPostTemplate({
published: "2020-02-02",
name: "What I had for lunch",
content: {
html: '<p>I ate a <a href="https://en.wikipedia.org/wiki/Cheese">cheese</a> sandwich, which was nice.</p>',
},
});
assert.equal(
result,
`---
date: 2020-02-02
publishDate: 2020-02-02
title: What I had for lunch
---
<p>I ate a <a href="https://en.wikipedia.org/wiki/Cheese">cheese</a> sandwich, which was nice.</p>
`,
);
});
it("Renders post template with JSON front matter", () => {
const result = getPostTemplate(properties, "json");
assert.equal(
result,
`{
"date": "2020-02-02",
"publishDate": "2020-02-02",
"title": "What I had for lunch",
"images": [
"https://website.example/photo.jpg"
],
"summary": "A very satisfactory meal.",
"category": [
"lunch",
"food"
],
"audio": [
{
"url": "https://website.example/audio.mp3"
}
],
"photo": [
{
"alt": "Alternative text",
"url": "https://website.example/photo.jpg"
}
],
"video": [
{
"url": "https://website.example/video.mp4"
}
],
"start": "2020-02-02",
"end": "2020-02-20",
"rsvp": "Yes",
"location": {
"type": "geo",
"latitude": "37.780080",
"longitude": "-122.420160",
"name": "37° 46 48.29″ N 122° 25 12.576″ W"
},
"bookmarkOf": "https://website.example",
"likeOf": "https://website.example",
"repostOf": "https://website.example",
"inReplyTo": "https://website.example",
"visibility": "private",
"syndication": "https://website.example/post/12345"
}
I ate a [cheese](https://en.wikipedia.org/wiki/Cheese) sandwich, which was nice.
`,
);
});
it("Renders post template with TOML front matter", () => {
const result = getPostTemplate(properties, "toml");
assert.equal(
result,
`+++
date = "2020-02-02"
publishDate = "2020-02-02"
title = "What I had for lunch"
images = [ "https://website.example/photo.jpg" ]
summary = "A very satisfactory meal."
category = [ "lunch", "food" ]
start = "2020-02-02"
end = "2020-02-20"
rsvp = "Yes"
bookmarkOf = "https://website.example"
likeOf = "https://website.example"
repostOf = "https://website.example"
inReplyTo = "https://website.example"
visibility = "private"
syndication = "https://website.example/post/12345"
[[audio]]
url = "https://website.example/audio.mp3"
[[photo]]
alt = "Alternative text"
url = "https://website.example/photo.jpg"
[[video]]
url = "https://website.example/video.mp4"
[location]
type = "geo"
latitude = "37.780080"
longitude = "-122.420160"
name = "37° 46 48.29″ N 122° 25 12.576″ W"
+++
I ate a [cheese](https://en.wikipedia.org/wiki/Cheese) sandwich, which was nice.
`,
);
});
it("Renders post template with YAML front matter", () => {
const result = getPostTemplate(properties, "yaml");
assert.equal(
result,
`---
date: 2020-02-02
publishDate: 2020-02-02
title: What I had for lunch
images:
- https://website.example/photo.jpg
summary: A very satisfactory meal.
category:
- lunch
- food
audio:
- url: https://website.example/audio.mp3
photo:
- alt: Alternative text
url: https://website.example/photo.jpg
video:
- url: https://website.example/video.mp4
start: 2020-02-02
end: 2020-02-20
rsvp: Yes
location:
type: geo
latitude: "37.780080"
longitude: "-122.420160"
name: 37° 46 48.29 N 122° 25 12.576 W
bookmarkOf: https://website.example
likeOf: https://website.example
repostOf: https://website.example
inReplyTo: https://website.example
visibility: private
syndication: https://website.example/post/12345
---
I ate a [cheese](https://en.wikipedia.org/wiki/Cheese) sandwich, which was nice.
`,
);
});
});

View file

@ -0,0 +1,55 @@
import { strict as assert } from "node:assert";
import { describe, it } from "node:test";
import { getPostTypes } from "../../lib/post-types.js";
const postTypes = {
article: {
name: "Journal post",
},
note: {
name: "Micro post",
},
puppy: {
name: "Puppy post",
},
};
describe("preset-hugo/lib/post-types", () => {
it("Gets paths and URLs for configured post types", () => {
assert.deepEqual(getPostTypes(postTypes), {
article: {
name: "Journal post",
post: {
path: "content/articles/{slug}.md",
url: "articles/{slug}",
},
media: {
path: "static/articles/{filename}",
url: "articles/{filename}",
},
},
note: {
name: "Micro post",
post: {
path: "content/notes/{slug}.md",
url: "notes/{slug}",
},
media: {
path: "static/notes/{filename}",
url: "notes/{filename}",
},
},
puppy: {
name: "Puppy post",
post: {
path: "content/puppies/{slug}.md",
url: "puppies/{slug}",
},
media: {
path: "static/puppies/{filename}",
url: "puppies/{filename}",
},
},
});
});
});

View file

@ -15,10 +15,12 @@
{{/* IndieWeb */}}
<link rel="webmention" href="https://webmention.io/www.byjp.me/webmention">
<link rel="authorization_endpoint" href="https://indiekit.byjp.me/auth">
<link rel="token_endpoint" href="https://indiekit.byjp.me/auth/token">
<link rel="indieauth-metadata" href="https://indiekit.byjp.me/.well-known/oauth-authorization-server">
<link rel="micropub" href="https://indiekit.byjp.me/micropub">
{{ with getenv "HUGO_INDIEKIT_URL" }}
<link rel="authorization_endpoint" href="{{ . }}/auth">
<link rel="token_endpoint" href="{{ . }}/auth/token">
<link rel="indieauth-metadata" href="{{ . }}/.well-known/oauth-authorization-server">
<link rel="micropub" href="{{ . }}/micropub">
{{ end }}
{{ block "title" . -}}
<title>
{{- if .IsHome -}}