bunki 0.4.1 → 0.5.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/dist/cli.js +68 -39
- package/dist/index.js +68 -39
- package/dist/site-generator.d.ts +1 -0
- package/dist/types.d.ts +4 -0
- package/dist/utils/markdown-utils.d.ts +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -32690,42 +32690,65 @@ core_default.registerLanguage("diff", diff);
|
|
|
32690
32690
|
core_default.registerLanguage("python", python);
|
|
32691
32691
|
core_default.registerLanguage("json", json);
|
|
32692
32692
|
core_default.registerLanguage("swift", swift);
|
|
32693
|
-
var
|
|
32694
|
-
|
|
32695
|
-
|
|
32696
|
-
|
|
32697
|
-
|
|
32698
|
-
|
|
32699
|
-
|
|
32700
|
-
})
|
|
32701
|
-
|
|
32702
|
-
|
|
32703
|
-
|
|
32704
|
-
|
|
32705
|
-
|
|
32706
|
-
|
|
32707
|
-
|
|
32708
|
-
|
|
32709
|
-
|
|
32710
|
-
|
|
32711
|
-
token.
|
|
32712
|
-
if (
|
|
32713
|
-
token.
|
|
32714
|
-
|
|
32715
|
-
|
|
32716
|
-
|
|
32717
|
-
|
|
32718
|
-
|
|
32719
|
-
preprocess(markdown2) {
|
|
32720
|
-
return markdown2;
|
|
32693
|
+
var noFollowExceptions = new Set;
|
|
32694
|
+
function createMarked() {
|
|
32695
|
+
const marked = new B(markedHighlight({
|
|
32696
|
+
emptyLangClass: "hljs",
|
|
32697
|
+
langPrefix: "hljs language-",
|
|
32698
|
+
highlight(code, lang, info) {
|
|
32699
|
+
const language = core_default.getLanguage(lang) ? lang : "json";
|
|
32700
|
+
return core_default.highlight(code, { language }).value;
|
|
32701
|
+
}
|
|
32702
|
+
}));
|
|
32703
|
+
marked.setOptions({
|
|
32704
|
+
gfm: true,
|
|
32705
|
+
breaks: true
|
|
32706
|
+
});
|
|
32707
|
+
marked.use({
|
|
32708
|
+
walkTokens(token) {
|
|
32709
|
+
if (token.type === "link") {
|
|
32710
|
+
token.href = token.href || "";
|
|
32711
|
+
const isExternal = token.href && (token.href.startsWith("http://") || token.href.startsWith("https://") || token.href.startsWith("//"));
|
|
32712
|
+
if (isExternal) {
|
|
32713
|
+
token.isExternalLink = true;
|
|
32714
|
+
if (token.href.includes("youtube.com/watch") || token.href.includes("youtu.be/")) {
|
|
32715
|
+
token.isYouTubeLink = true;
|
|
32716
|
+
}
|
|
32717
|
+
}
|
|
32718
|
+
}
|
|
32721
32719
|
},
|
|
32722
|
-
|
|
32723
|
-
|
|
32724
|
-
|
|
32725
|
-
|
|
32720
|
+
hooks: {
|
|
32721
|
+
preprocess(markdown2) {
|
|
32722
|
+
return markdown2;
|
|
32723
|
+
},
|
|
32724
|
+
postprocess(html) {
|
|
32725
|
+
html = html.replace(/<a href="(https?:\/\/(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)[^"]*)"[^>]*>(.*?)<\/a>/g, '<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>');
|
|
32726
|
+
html = html.replace(/<img /g, '<img loading="lazy" ');
|
|
32727
|
+
return html.replace(/<a href="(https?:\/\/|\/\/)([^"]+)"/g, (match, protocol, rest) => {
|
|
32728
|
+
const fullUrl = protocol + rest;
|
|
32729
|
+
let relAttr = 'rel="noopener noreferrer';
|
|
32730
|
+
try {
|
|
32731
|
+
const url = new URL(fullUrl);
|
|
32732
|
+
const domain = url.hostname.replace(/^www\./, "");
|
|
32733
|
+
if (!noFollowExceptions.has(domain)) {
|
|
32734
|
+
relAttr += " nofollow";
|
|
32735
|
+
}
|
|
32736
|
+
} catch {
|
|
32737
|
+
relAttr += " nofollow";
|
|
32738
|
+
}
|
|
32739
|
+
relAttr += '"';
|
|
32740
|
+
return `<a href="${fullUrl}" target="_blank" ${relAttr}`;
|
|
32741
|
+
});
|
|
32742
|
+
}
|
|
32726
32743
|
}
|
|
32727
|
-
}
|
|
32728
|
-
|
|
32744
|
+
});
|
|
32745
|
+
return marked;
|
|
32746
|
+
}
|
|
32747
|
+
var marked = createMarked();
|
|
32748
|
+
function setNoFollowExceptions(exceptions) {
|
|
32749
|
+
noFollowExceptions = new Set(exceptions.map((domain) => domain.toLowerCase().replace(/^www\./, "")));
|
|
32750
|
+
marked = createMarked();
|
|
32751
|
+
}
|
|
32729
32752
|
function extractExcerpt(content, maxLength = 200) {
|
|
32730
32753
|
const plainText = content.replace(/^#.*$/gm, "").replace(/```[\s\S]*?```/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[*_]{1,2}([^*_]+)[*_]{1,2}/g, "$1").replace(/\n+/g, " ").trim();
|
|
32731
32754
|
if (plainText.length <= maxLength) {
|
|
@@ -32988,6 +33011,10 @@ class SiteGenerator {
|
|
|
32988
33011
|
timeZone: "America/Los_Angeles"
|
|
32989
33012
|
}));
|
|
32990
33013
|
}
|
|
33014
|
+
getSortedTags(limit) {
|
|
33015
|
+
const sorted = Object.values(this.site.tags).sort((a, b2) => b2.count - a.count);
|
|
33016
|
+
return limit ? sorted.slice(0, limit) : sorted;
|
|
33017
|
+
}
|
|
32991
33018
|
createPagination(items, currentPage, pageSize, pagePath) {
|
|
32992
33019
|
const totalItems = items.length;
|
|
32993
33020
|
const totalPages = Math.ceil(totalItems / pageSize);
|
|
@@ -33051,6 +33078,9 @@ class SiteGenerator {
|
|
|
33051
33078
|
async initialize() {
|
|
33052
33079
|
console.log("Initializing site generator...");
|
|
33053
33080
|
await ensureDir(this.options.outputDir);
|
|
33081
|
+
if (this.options.config.noFollowExceptions) {
|
|
33082
|
+
setNoFollowExceptions(this.options.config.noFollowExceptions);
|
|
33083
|
+
}
|
|
33054
33084
|
let tagDescriptions = {};
|
|
33055
33085
|
const tagsTomlPath = path5.join(process.cwd(), "src", "tags.toml");
|
|
33056
33086
|
const tagsTomlFile = Bun.file(tagsTomlPath);
|
|
@@ -33129,7 +33159,7 @@ class SiteGenerator {
|
|
|
33129
33159
|
const yearPageHtml = import_nunjucks.default.render("archive.njk", {
|
|
33130
33160
|
site: this.options.config,
|
|
33131
33161
|
posts: paginatedPosts,
|
|
33132
|
-
tags:
|
|
33162
|
+
tags: this.getSortedTags(this.options.config.maxTagsOnHomepage),
|
|
33133
33163
|
year,
|
|
33134
33164
|
pagination
|
|
33135
33165
|
});
|
|
@@ -33154,7 +33184,7 @@ class SiteGenerator {
|
|
|
33154
33184
|
const pageHtml = import_nunjucks.default.render("index.njk", {
|
|
33155
33185
|
site: this.options.config,
|
|
33156
33186
|
posts: paginatedPosts,
|
|
33157
|
-
tags:
|
|
33187
|
+
tags: this.getSortedTags(this.options.config.maxTagsOnHomepage),
|
|
33158
33188
|
pagination
|
|
33159
33189
|
});
|
|
33160
33190
|
if (page === 1) {
|
|
@@ -33173,8 +33203,7 @@ class SiteGenerator {
|
|
|
33173
33203
|
await ensureDir(postDir);
|
|
33174
33204
|
const postHtml = import_nunjucks.default.render("post.njk", {
|
|
33175
33205
|
site: this.options.config,
|
|
33176
|
-
post
|
|
33177
|
-
tags: Object.values(this.site.tags)
|
|
33206
|
+
post
|
|
33178
33207
|
});
|
|
33179
33208
|
await Bun.write(path5.join(postDir, "index.html"), postHtml);
|
|
33180
33209
|
}
|
|
@@ -33184,7 +33213,7 @@ class SiteGenerator {
|
|
|
33184
33213
|
await ensureDir(tagsDir);
|
|
33185
33214
|
const tagIndexHtml = import_nunjucks.default.render("tags.njk", {
|
|
33186
33215
|
site: this.options.config,
|
|
33187
|
-
tags:
|
|
33216
|
+
tags: this.getSortedTags()
|
|
33188
33217
|
});
|
|
33189
33218
|
await Bun.write(path5.join(tagsDir, "index.html"), tagIndexHtml);
|
|
33190
33219
|
for (const [tagName, tagData] of Object.entries(this.site.tags)) {
|
package/dist/index.js
CHANGED
|
@@ -30335,42 +30335,65 @@ core_default.registerLanguage("diff", diff);
|
|
|
30335
30335
|
core_default.registerLanguage("python", python);
|
|
30336
30336
|
core_default.registerLanguage("json", json);
|
|
30337
30337
|
core_default.registerLanguage("swift", swift);
|
|
30338
|
-
var
|
|
30339
|
-
|
|
30340
|
-
|
|
30341
|
-
|
|
30342
|
-
|
|
30343
|
-
|
|
30344
|
-
|
|
30345
|
-
})
|
|
30346
|
-
|
|
30347
|
-
|
|
30348
|
-
|
|
30349
|
-
|
|
30350
|
-
|
|
30351
|
-
|
|
30352
|
-
|
|
30353
|
-
|
|
30354
|
-
|
|
30355
|
-
|
|
30356
|
-
token.
|
|
30357
|
-
if (
|
|
30358
|
-
token.
|
|
30359
|
-
|
|
30360
|
-
|
|
30361
|
-
|
|
30362
|
-
|
|
30363
|
-
|
|
30364
|
-
preprocess(markdown2) {
|
|
30365
|
-
return markdown2;
|
|
30338
|
+
var noFollowExceptions = new Set;
|
|
30339
|
+
function createMarked() {
|
|
30340
|
+
const marked = new B(markedHighlight({
|
|
30341
|
+
emptyLangClass: "hljs",
|
|
30342
|
+
langPrefix: "hljs language-",
|
|
30343
|
+
highlight(code, lang, info) {
|
|
30344
|
+
const language = core_default.getLanguage(lang) ? lang : "json";
|
|
30345
|
+
return core_default.highlight(code, { language }).value;
|
|
30346
|
+
}
|
|
30347
|
+
}));
|
|
30348
|
+
marked.setOptions({
|
|
30349
|
+
gfm: true,
|
|
30350
|
+
breaks: true
|
|
30351
|
+
});
|
|
30352
|
+
marked.use({
|
|
30353
|
+
walkTokens(token) {
|
|
30354
|
+
if (token.type === "link") {
|
|
30355
|
+
token.href = token.href || "";
|
|
30356
|
+
const isExternal = token.href && (token.href.startsWith("http://") || token.href.startsWith("https://") || token.href.startsWith("//"));
|
|
30357
|
+
if (isExternal) {
|
|
30358
|
+
token.isExternalLink = true;
|
|
30359
|
+
if (token.href.includes("youtube.com/watch") || token.href.includes("youtu.be/")) {
|
|
30360
|
+
token.isYouTubeLink = true;
|
|
30361
|
+
}
|
|
30362
|
+
}
|
|
30363
|
+
}
|
|
30366
30364
|
},
|
|
30367
|
-
|
|
30368
|
-
|
|
30369
|
-
|
|
30370
|
-
|
|
30365
|
+
hooks: {
|
|
30366
|
+
preprocess(markdown2) {
|
|
30367
|
+
return markdown2;
|
|
30368
|
+
},
|
|
30369
|
+
postprocess(html) {
|
|
30370
|
+
html = html.replace(/<a href="(https?:\/\/(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)[^"]*)"[^>]*>(.*?)<\/a>/g, '<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>');
|
|
30371
|
+
html = html.replace(/<img /g, '<img loading="lazy" ');
|
|
30372
|
+
return html.replace(/<a href="(https?:\/\/|\/\/)([^"]+)"/g, (match, protocol, rest) => {
|
|
30373
|
+
const fullUrl = protocol + rest;
|
|
30374
|
+
let relAttr = 'rel="noopener noreferrer';
|
|
30375
|
+
try {
|
|
30376
|
+
const url = new URL(fullUrl);
|
|
30377
|
+
const domain = url.hostname.replace(/^www\./, "");
|
|
30378
|
+
if (!noFollowExceptions.has(domain)) {
|
|
30379
|
+
relAttr += " nofollow";
|
|
30380
|
+
}
|
|
30381
|
+
} catch {
|
|
30382
|
+
relAttr += " nofollow";
|
|
30383
|
+
}
|
|
30384
|
+
relAttr += '"';
|
|
30385
|
+
return `<a href="${fullUrl}" target="_blank" ${relAttr}`;
|
|
30386
|
+
});
|
|
30387
|
+
}
|
|
30371
30388
|
}
|
|
30372
|
-
}
|
|
30373
|
-
|
|
30389
|
+
});
|
|
30390
|
+
return marked;
|
|
30391
|
+
}
|
|
30392
|
+
var marked = createMarked();
|
|
30393
|
+
function setNoFollowExceptions(exceptions) {
|
|
30394
|
+
noFollowExceptions = new Set(exceptions.map((domain) => domain.toLowerCase().replace(/^www\./, "")));
|
|
30395
|
+
marked = createMarked();
|
|
30396
|
+
}
|
|
30374
30397
|
function extractExcerpt(content, maxLength = 200) {
|
|
30375
30398
|
const plainText = content.replace(/^#.*$/gm, "").replace(/```[\s\S]*?```/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[*_]{1,2}([^*_]+)[*_]{1,2}/g, "$1").replace(/\n+/g, " ").trim();
|
|
30376
30399
|
if (plainText.length <= maxLength) {
|
|
@@ -30860,6 +30883,10 @@ class SiteGenerator {
|
|
|
30860
30883
|
timeZone: "America/Los_Angeles"
|
|
30861
30884
|
}));
|
|
30862
30885
|
}
|
|
30886
|
+
getSortedTags(limit) {
|
|
30887
|
+
const sorted = Object.values(this.site.tags).sort((a, b2) => b2.count - a.count);
|
|
30888
|
+
return limit ? sorted.slice(0, limit) : sorted;
|
|
30889
|
+
}
|
|
30863
30890
|
createPagination(items, currentPage, pageSize, pagePath) {
|
|
30864
30891
|
const totalItems = items.length;
|
|
30865
30892
|
const totalPages = Math.ceil(totalItems / pageSize);
|
|
@@ -30923,6 +30950,9 @@ class SiteGenerator {
|
|
|
30923
30950
|
async initialize() {
|
|
30924
30951
|
console.log("Initializing site generator...");
|
|
30925
30952
|
await ensureDir(this.options.outputDir);
|
|
30953
|
+
if (this.options.config.noFollowExceptions) {
|
|
30954
|
+
setNoFollowExceptions(this.options.config.noFollowExceptions);
|
|
30955
|
+
}
|
|
30926
30956
|
let tagDescriptions = {};
|
|
30927
30957
|
const tagsTomlPath = path5.join(process.cwd(), "src", "tags.toml");
|
|
30928
30958
|
const tagsTomlFile = Bun.file(tagsTomlPath);
|
|
@@ -31001,7 +31031,7 @@ class SiteGenerator {
|
|
|
31001
31031
|
const yearPageHtml = import_nunjucks.default.render("archive.njk", {
|
|
31002
31032
|
site: this.options.config,
|
|
31003
31033
|
posts: paginatedPosts,
|
|
31004
|
-
tags:
|
|
31034
|
+
tags: this.getSortedTags(this.options.config.maxTagsOnHomepage),
|
|
31005
31035
|
year,
|
|
31006
31036
|
pagination
|
|
31007
31037
|
});
|
|
@@ -31026,7 +31056,7 @@ class SiteGenerator {
|
|
|
31026
31056
|
const pageHtml = import_nunjucks.default.render("index.njk", {
|
|
31027
31057
|
site: this.options.config,
|
|
31028
31058
|
posts: paginatedPosts,
|
|
31029
|
-
tags:
|
|
31059
|
+
tags: this.getSortedTags(this.options.config.maxTagsOnHomepage),
|
|
31030
31060
|
pagination
|
|
31031
31061
|
});
|
|
31032
31062
|
if (page === 1) {
|
|
@@ -31045,8 +31075,7 @@ class SiteGenerator {
|
|
|
31045
31075
|
await ensureDir(postDir);
|
|
31046
31076
|
const postHtml = import_nunjucks.default.render("post.njk", {
|
|
31047
31077
|
site: this.options.config,
|
|
31048
|
-
post
|
|
31049
|
-
tags: Object.values(this.site.tags)
|
|
31078
|
+
post
|
|
31050
31079
|
});
|
|
31051
31080
|
await Bun.write(path5.join(postDir, "index.html"), postHtml);
|
|
31052
31081
|
}
|
|
@@ -31056,7 +31085,7 @@ class SiteGenerator {
|
|
|
31056
31085
|
await ensureDir(tagsDir);
|
|
31057
31086
|
const tagIndexHtml = import_nunjucks.default.render("tags.njk", {
|
|
31058
31087
|
site: this.options.config,
|
|
31059
|
-
tags:
|
|
31088
|
+
tags: this.getSortedTags()
|
|
31060
31089
|
});
|
|
31061
31090
|
await Bun.write(path5.join(tagsDir, "index.html"), tagIndexHtml);
|
|
31062
31091
|
for (const [tagName, tagData] of Object.entries(this.site.tags)) {
|
package/dist/site-generator.d.ts
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -54,6 +54,10 @@ export interface SiteConfig {
|
|
|
54
54
|
s3?: S3Config;
|
|
55
55
|
/** CSS processing configuration */
|
|
56
56
|
css?: CSSConfig;
|
|
57
|
+
/** Optional number of tags to display on homepage (sorted by count). If not set, all tags are shown */
|
|
58
|
+
maxTagsOnHomepage?: number;
|
|
59
|
+
/** Optional list of domains to exclude from nofollow attribute. Links to these domains will have follow attribute. */
|
|
60
|
+
noFollowExceptions?: string[];
|
|
57
61
|
/** Additional custom configuration options */
|
|
58
62
|
[key: string]: any;
|
|
59
63
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Post } from "../types";
|
|
2
|
+
export declare function setNoFollowExceptions(exceptions: string[]): void;
|
|
2
3
|
export declare function extractExcerpt(content: string, maxLength?: number): string;
|
|
3
4
|
export declare function convertMarkdownToHtml(markdownContent: string): string;
|
|
4
5
|
export declare function parseMarkdownFile(filePath: string): Promise<Post | null>;
|
package/package.json
CHANGED