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 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 marked = new B(markedHighlight({
32694
- emptyLangClass: "hljs",
32695
- langPrefix: "hljs language-",
32696
- highlight(code, lang, info) {
32697
- const language = core_default.getLanguage(lang) ? lang : "json";
32698
- return core_default.highlight(code, { language }).value;
32699
- }
32700
- }));
32701
- marked.setOptions({
32702
- gfm: true,
32703
- breaks: true
32704
- });
32705
- marked.use({
32706
- walkTokens(token) {
32707
- if (token.type === "link") {
32708
- token.href = token.href || "";
32709
- const isExternal = token.href && (token.href.startsWith("http://") || token.href.startsWith("https://") || token.href.startsWith("//"));
32710
- if (isExternal) {
32711
- token.isExternalLink = true;
32712
- if (token.href.includes("youtube.com/watch") || token.href.includes("youtu.be/")) {
32713
- token.isYouTubeLink = true;
32714
- }
32715
- }
32716
- }
32717
- },
32718
- hooks: {
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
- postprocess(html) {
32723
- 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>');
32724
- html = html.replace(/<img /g, '<img loading="lazy" ');
32725
- return html.replace(/<a href="(https?:\/\/|\/\/)([^"]+)"/g, '<a href="$1$2" target="_blank" rel="noopener noreferrer"');
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: Object.values(this.site.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: Object.values(this.site.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: Object.values(this.site.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 marked = new B(markedHighlight({
30339
- emptyLangClass: "hljs",
30340
- langPrefix: "hljs language-",
30341
- highlight(code, lang, info) {
30342
- const language = core_default.getLanguage(lang) ? lang : "json";
30343
- return core_default.highlight(code, { language }).value;
30344
- }
30345
- }));
30346
- marked.setOptions({
30347
- gfm: true,
30348
- breaks: true
30349
- });
30350
- marked.use({
30351
- walkTokens(token) {
30352
- if (token.type === "link") {
30353
- token.href = token.href || "";
30354
- const isExternal = token.href && (token.href.startsWith("http://") || token.href.startsWith("https://") || token.href.startsWith("//"));
30355
- if (isExternal) {
30356
- token.isExternalLink = true;
30357
- if (token.href.includes("youtube.com/watch") || token.href.includes("youtu.be/")) {
30358
- token.isYouTubeLink = true;
30359
- }
30360
- }
30361
- }
30362
- },
30363
- hooks: {
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
- postprocess(html) {
30368
- 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>');
30369
- html = html.replace(/<img /g, '<img loading="lazy" ');
30370
- return html.replace(/<a href="(https?:\/\/|\/\/)([^"]+)"/g, '<a href="$1$2" target="_blank" rel="noopener noreferrer"');
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: Object.values(this.site.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: Object.values(this.site.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: Object.values(this.site.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)) {
@@ -4,6 +4,7 @@ export declare class SiteGenerator {
4
4
  private site;
5
5
  private formatRSSDate;
6
6
  private getPacificDate;
7
+ private getSortedTags;
7
8
  private createPagination;
8
9
  constructor(options: GeneratorOptions);
9
10
  initialize(): Promise<void>;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunki",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "An opinionated static site generator built with Bun featuring PostCSS integration and modern web development workflows",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",