Skip to content

Commit afbed40

Browse files
committed
release
1 parent 230121c commit afbed40

File tree

103 files changed

+4319
-142
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+4319
-142
lines changed

.stylelintrc.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"extends": [
3+
"stylelint-config-standard-scss",
4+
"stylelint-config-property-sort-order-smacss"
5+
]
6+
}

README.md

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
11
# jrson.me
22

3+
![Deno JS](https://img.shields.io/badge/built%20with-deno.land-000000?style=flat-square&logo=deno&logoColor=ffffff)
4+
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/jrson83/jrson.me/Deploy%20Site?style=flat-square&logo=github)
5+
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/jrson83/jrson.me?style=flat-square&logo=visual-studio-code)
6+
![GitHub top language](https://img.shields.io/github/languages/top/jrson83/jrson.me?logo=typescript&style=flat-square)
7+
![GitHub](https://img.shields.io/github/license/jrson83/jrson.me?style=flat-square)
8+
39
## Description
410

5-
My personal blog, built with:
11+
My [personal blog](https://jrson.me/), built with Lume 🔥 on Deno 🦕.
612

7-
- [Lume](https://lume.land/)
13+
- [Lume](https://lume.land/) - A Static site generator for Deno
14+
- [Preact](https://preactjs.com/) - Fast 3kB alternative to React with the same
15+
modern API
16+
- [TypeScript](https://www.typescriptlang.org/) - JavaScript with syntax for
17+
types
18+
- [shrtcss](https://github.com/jrson83/shrtcss) - A lightweight SCSS/CSS
19+
framework (WIP)

_config.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import slugify_urls from "lume/plugins/slugify_urls.ts";
66
import sass from "lume/plugins/sass.ts";
77
import terser from "lume/plugins/terser.ts";
88

9+
import readingTime from "#plugins/readingTime/mod.ts";
910
import unified from "#plugins/unified/mod.ts";
1011
import remarkPlugins from "#plugins/unified/remark/mod.ts";
1112
import rehypePlugins from "#plugins/unified/rehype/mod.ts";
1213
import preactjsx from "#plugins/preactjsx/mod.ts";
14+
import atomFeed from "#plugins/atom-feed/mod.ts";
1315
import sitemap from "#plugins/sitemap/mod.ts";
14-
15-
const buildTime = `v${Date.now()}`;
16+
import md5CacheBuster from "#plugins/md5-cache-buster/mod.ts";
1617

1718
const site = lume({
1819
src: "./src",
@@ -30,7 +31,7 @@ const site = lume({
3031

3132
site
3233
.copy("assets", ".")
33-
.data("cacheBusting", buildTime)
34+
.use(readingTime())
3435
.use(date())
3536
.use(slugify_urls())
3637
.use(unified({
@@ -41,6 +42,7 @@ site
4142
.use(sass())
4243
.loadAssets([".js"])
4344
.use(terser())
45+
.use(atomFeed())
4446
.use(sitemap({
4547
query: ["url!=/404/"],
4648
}));
@@ -51,12 +53,8 @@ site.process([".html"], (page: Page) => {
5153
}
5254
});
5355

54-
if (Deno.env.get("BUILD_MODE") === "dev") {
55-
site.process([".css", ".js"], function (page: Page) {
56-
page.updateDest({
57-
path: `${page.dest.path}.${buildTime}`,
58-
});
59-
});
56+
if (Deno.env.get("BUILD_MODE") === "prod") {
57+
site.use(md5CacheBuster());
6058
}
6159

6260
export default site;

deno.json

+25-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,31 @@
66
},
77
"tasks": {
88
"lume": "deno eval \"import 'lume/task.ts'\" --",
9-
"clean": "rm -rf _site/",
10-
"build": "deno task lume",
9+
"clean": "rm -rf ./_site",
10+
"build": "BUILD_MODE=\"prod\" deno task lume",
1111
"serve": "BUILD_MODE=\"dev\" deno task lume -s"
12+
},
13+
"lint": {
14+
"files": {
15+
"exclude": [
16+
"./_site"
17+
]
18+
},
19+
"rules": {
20+
"tags": [
21+
"recommended"
22+
],
23+
"exclude": [
24+
"no-explicit-any",
25+
"no-unused-vars"
26+
]
27+
}
28+
},
29+
"fmt": {
30+
"files": {
31+
"exclude": [
32+
"./_site"
33+
]
34+
}
1235
}
1336
}

plugins/atom-feed/deps.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export type FormatOptions = {
2+
indentation?: string;
3+
// deno-lint-ignore no-explicit-any
4+
filter?: (node: any) => boolean;
5+
stripComments?: boolean;
6+
collapseContent?: boolean;
7+
lineSeparator?: string;
8+
whiteSpaceAtEndOfSelfclosingTag?: boolean;
9+
};
10+
export { default as formatXML } from "https://esm.sh/[email protected]";

plugins/atom-feed/mod.ts

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { merge } from "lume/core/utils.ts";
2+
import { Page } from "lume/core/filesystem.ts";
3+
import { buildSort } from "lume/plugins/search.ts";
4+
5+
import { formatXML } from "./deps.ts";
6+
import type { FormatOptions } from "./deps.ts";
7+
8+
import type { Site } from "lume/core.ts";
9+
import type { Search } from "lume/plugins/search.ts";
10+
import type { MetaData } from "lume/plugins/metas.ts";
11+
12+
export interface Options {
13+
/** The query to search pages included in the feed. defaults to `type=post` */
14+
query: string[];
15+
16+
/** The values to sort the feeds pages. defaults to `date=desc` */
17+
sort: string[];
18+
19+
/** The limit to display pages. defaults to `10` */
20+
limit: number;
21+
22+
/** Options passed to xml-formatter */
23+
options: Partial<FormatOptions>;
24+
}
25+
26+
// Default options
27+
export const defaults: Options = {
28+
query: ["type=post"],
29+
sort: ["date=desc"],
30+
limit: 10,
31+
options: {
32+
indentation: " ",
33+
collapseContent: true,
34+
lineSeparator: "\n",
35+
},
36+
};
37+
38+
export interface FeedMetaData extends MetaData {
39+
author: {
40+
name: string;
41+
email: string;
42+
url: string;
43+
};
44+
}
45+
46+
/** A plugin to <description> */
47+
export default function (userOptions?: Partial<Options>) {
48+
const options = merge(defaults, userOptions);
49+
50+
return (site: Site) => {
51+
site.addEventListener("afterRender", () => {
52+
// Create the sitemap.xml page
53+
const feed = Page.create("feed.xml", getFeedContent(site));
54+
55+
// Add to the sitemap page to pages
56+
site.pages.push(feed);
57+
});
58+
59+
function getFeedContent(site: Site) {
60+
// Get the pages
61+
const search = site.globalData.search as Search;
62+
const feedPages = search.pages(
63+
options.query,
64+
options.sort,
65+
options.limit,
66+
);
67+
68+
// Sort the pages
69+
feedPages.sort(buildSort(options.sort));
70+
71+
const metas = isActiveProcessor(site, "metas")
72+
? feedPages[0].data.metas as FeedMetaData
73+
: feedPages[0].data.site as FeedMetaData;
74+
75+
// deno-fmt-ignore
76+
const atomfeed = `<?xml version="1.0" encoding="utf-8"?>
77+
<feed xmlns="http://www.w3.org/2005/Atom">
78+
<title>${metas.title}</title>
79+
<subtitle>${metas.description}</subtitle>
80+
<link href="${site.url("feed.xml", true)}" rel="self"/>
81+
<link href="${site.url("/", true)}"/>
82+
<updated>${feedPages[0].data.date?.toISOString()}</updated>
83+
<id>${site.url("/", true)}</id>
84+
<author>
85+
<name>${metas.author.name}</name>
86+
</author>
87+
88+
${feedPages.map((post: Page) => {
89+
return `<entry>
90+
<title>${post.data.title}</title>
91+
<link href="${site.url(post.data.url as string, true)}"/>
92+
<id>${site.url(post.data.url as string, true)}</id>
93+
<updated>${post.data.date?.toISOString()}</updated>
94+
<summary>${post.data.excerpt}</summary>
95+
</entry>
96+
`}).join("").trim()}
97+
</feed>`.trim();
98+
99+
return formatXML(atomfeed, options.options);
100+
}
101+
102+
function isActiveProcessor(site: Site, processor: string): boolean {
103+
const { processors } = site.processors;
104+
105+
// deno-lint-ignore no-unused-vars
106+
for (const [value, key] of processors) {
107+
if (typeof value === "function" && value.name === processor) {
108+
return true;
109+
}
110+
}
111+
return false;
112+
}
113+
};
114+
}

plugins/md5-cache-buster/deps.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { Md5 } from "https://deno.land/[email protected]/hash/md5.ts";
2+
export type { Message } from "https://deno.land/[email protected]/hash/md5.ts";

plugins/md5-cache-buster/mod.ts

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Md5 } from "./deps.ts";
2+
import { merge } from "lume/core/utils.ts";
3+
import modifyUrls from "lume/plugins/modify_urls.ts";
4+
5+
import type { Message } from "./deps.ts";
6+
import type { Page, Site } from "lume/core.ts";
7+
8+
export interface Options {
9+
/** The list of extensions this plugin applies to */
10+
extensions: string[];
11+
}
12+
13+
// Default options
14+
export const defaults: Options = {
15+
extensions: [".css", ".js"],
16+
};
17+
18+
export type HashedAssets = Array<{
19+
filename: string;
20+
hashFilename: string;
21+
ext: string;
22+
hash: string;
23+
}>;
24+
25+
/** A plugin to add an MD5 hash (cache buster) to `.css` & `.js` files */
26+
export default function (userOptions?: Partial<Options>) {
27+
const options = merge(defaults, userOptions);
28+
29+
const hashedAssets: HashedAssets = [];
30+
31+
return (site: Site) => {
32+
site.addEventListener("afterRender", () => {
33+
site.process(options.extensions, buildHash);
34+
35+
site.process([".html"], replaceUrls);
36+
});
37+
38+
function buildHash(file: Page) {
39+
const hash = new Md5().update(file.content as Message).toString();
40+
41+
hashedAssets.push({
42+
filename: `${file.dest.path}${file.dest.ext}`,
43+
hashFilename: `${file.dest.path}.${hash}${file.dest.ext}`,
44+
ext: file.dest.ext,
45+
hash: hash,
46+
});
47+
48+
file.updateDest({
49+
path: `${file.dest.path}.${hash}`,
50+
});
51+
}
52+
53+
function replaceUrls() {
54+
site.use(modifyUrls({
55+
fn(url, page) {
56+
if (url.endsWith(".css") || url.endsWith(".js")) {
57+
const result = hashedAssets.find((asset) => {
58+
return asset.filename.toLowerCase() === url.toLowerCase();
59+
});
60+
61+
if (result !== undefined) {
62+
return result?.hashFilename;
63+
}
64+
}
65+
return url;
66+
},
67+
}));
68+
}
69+
};
70+
}

plugins/readingTime/mod.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { merge } from "lume/core/utils.ts";
2+
3+
import type { Page, Site } from "lume/core.ts";
4+
5+
export interface Options {
6+
extensions: string[];
7+
wpm: number;
8+
text: string;
9+
}
10+
11+
export const defaults: Options = {
12+
extensions: [".md"],
13+
wpm: 275,
14+
text: "min read",
15+
};
16+
17+
export default function (userOptions?: Partial<Options>) {
18+
const options = merge(defaults, userOptions);
19+
20+
return (site: Site) => {
21+
site.preprocess(options.extensions, readTime);
22+
23+
function readTime(page: Page) {
24+
const content = page.data.content as string;
25+
26+
if (!content || typeof content !== "string") {
27+
return page.data.readingTime = {
28+
text: `0 ${options.text}`,
29+
words: 0,
30+
minutes: 0,
31+
time: 0,
32+
};
33+
}
34+
35+
const matches = content.match(/\w+/g);
36+
37+
const wordCount = matches ? matches.length : 0;
38+
const minutes = wordCount / options.wpm;
39+
40+
const time = Math.round(minutes * 60 * 1000);
41+
const displayTime = Math.ceil(parseFloat(minutes.toFixed(2)));
42+
43+
page.data.readingTime = {
44+
text: `${displayTime} ${options.text}`,
45+
words: wordCount,
46+
minutes: displayTime,
47+
time,
48+
};
49+
}
50+
};
51+
}

0 commit comments

Comments
 (0)