fossbook 0.1.4 → 0.2.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 CHANGED
@@ -105,12 +105,7 @@ module.exports = {
105
105
  defaultLanguageInSubdir: false,
106
106
  languages: {
107
107
  en: { languageName: "English", locale: "en-US" },
108
- ko: {
109
- languageName: "한국어",
110
- locale: "ko-KR",
111
- blogName: "나의 블로그",
112
- blogDescription: "한국어 블로그",
113
- },
108
+ ko: { languageName: "한국어", locale: "ko-KR" },
114
109
  },
115
110
 
116
111
  // Optional comments (see "Comments" below)
@@ -132,6 +127,11 @@ module.exports = {
132
127
 
133
128
  ## Content Format
134
129
 
130
+ Fossbook supports GitHub Flavored Markdown plus extensions for image sizing
131
+ and alignment, captions, comic dialogue, aligned links, Mermaid diagrams, and
132
+ syntax-highlighted code. See [Fossbook Markdown](fossbook_markdown.md) for
133
+ syntax and examples.
134
+
135
135
  ### Post front-matter
136
136
 
137
137
  ```markdown
@@ -192,8 +192,6 @@ module.exports = {
192
192
  ko: {
193
193
  languageName: "한국어",
194
194
  locale: "ko-KR",
195
- blogName: "나의 블로그",
196
- blogDescription: "한국어 블로그",
197
195
  },
198
196
  ja: {
199
197
  languageName: "日本語",
@@ -203,14 +201,43 @@ module.exports = {
203
201
  };
204
202
  ```
205
203
 
204
+ Put localized blog information in a sibling config file for each language. For
205
+ example, `fossbook.config.en.js` contains:
206
+
207
+ ```js
208
+ module.exports = {
209
+ blogName: "My Blog",
210
+ authorName: "Jane Doe",
211
+ authorDescription: "Open source developer and writer",
212
+ blogDescription: "An English-language blog",
213
+ };
214
+ ```
215
+
216
+ And `fossbook.config.ko.js` contains:
217
+
218
+ ```js
219
+ module.exports = {
220
+ blogName: "나의 블로그",
221
+ authorName: "이수현",
222
+ authorDescription: "오픈 소스 개발자이자 작가",
223
+ blogDescription: "한국어 블로그",
224
+ homeLabel: "대문",
225
+ allPostsLabel: "모든 글",
226
+ aboutLabel: "소개",
227
+ tagsLabel: "태그",
228
+ allTagsLabel: "모든 태그",
229
+ };
230
+ ```
231
+
206
232
  - `defaultLanguage` identifies the language represented by `index.md` and
207
233
  `about.md`.
208
234
  - `defaultLanguageInSubdir: false` keeps the default language at the site root.
209
235
  Other languages are generated below their language code, such as `/ko/`.
210
236
  - Set `defaultLanguageInSubdir: true` to generate the default language below
211
237
  its code as well, such as `/en/`.
212
- - Each language can override site values such as `blogName` and
213
- `blogDescription`.
238
+ - Language config files use the main config filename with the language code
239
+ inserted before the extension: `fossbook.config.<language>.js`.
240
+ - Values omitted from a language config fall back to `fossbook.config.js`.
214
241
  - `locale` controls language-specific date formatting.
215
242
  - Language keys should use standard language tags such as `en`, `ko`, `ja`, or
216
243
  `zh-Hant`.
package/lib/all_posts.js CHANGED
@@ -9,15 +9,15 @@ module.exports = class AllPostsPage extends PageBase {
9
9
 
10
10
  // posts: array of Page objects
11
11
  generateContent(posts) {
12
- const pageTitle = "All posts";
12
+ const pageTitle = this.config.allPostsLabel || "All posts";
13
13
  const allPostsDir = path.join(this.config.dev.outdir, "all_posts");
14
14
  if (!fs.existsSync(allPostsDir))
15
15
  fs.mkdirSync(allPostsDir);
16
16
 
17
17
  const data = { posts: posts, pageTitle: pageTitle };
18
- this.url = `${this.config.blogsite}/posts.html`;
18
+ this.url = `${this.config.blogsite}/all_posts/`;
19
19
  this.title = `${this.config.blogName}: ${data.pageTitle}`;
20
- this.description = "All posts";
20
+ this.description = pageTitle;
21
21
  this.imageURL = this.config.image;
22
22
 
23
23
  const templatePath = path.join(this.config.themePath, "layouts", "all_posts.html");
package/lib/index.js CHANGED
@@ -75,6 +75,36 @@ function createAboutTranslations(languageConfigs) {
75
75
  });
76
76
  }
77
77
 
78
+ function createHomeTranslations(languageConfigs) {
79
+ return languageConfigs.map((config) => ({
80
+ language: config.language,
81
+ languageName: config.languageName,
82
+ path: config.basePath || "/",
83
+ url: `${config.blogsite}/`,
84
+ isDefault: config.language === config.defaultLanguage,
85
+ }));
86
+ }
87
+
88
+ function createAllPostsTranslations(languageConfigs) {
89
+ return languageConfigs.map((config) => ({
90
+ language: config.language,
91
+ languageName: config.languageName,
92
+ path: `${config.basePath || "/"}all_posts/`,
93
+ url: `${config.blogsite}/all_posts/`,
94
+ isDefault: config.language === config.defaultLanguage,
95
+ }));
96
+ }
97
+
98
+ function createTagListTranslations(languageConfigs) {
99
+ return languageConfigs.map((config) => ({
100
+ language: config.language,
101
+ languageName: config.languageName,
102
+ path: `${config.basePath || "/"}tags/`,
103
+ url: `${config.blogsite}/tags/`,
104
+ isDefault: config.language === config.defaultLanguage,
105
+ }));
106
+ }
107
+
78
108
  function buildLanguage(config) {
79
109
  fs.mkdirSync(config.dev.outdir, { recursive: true });
80
110
 
@@ -88,14 +118,17 @@ function buildLanguage(config) {
88
118
 
89
119
  // Create home page and pagination in output/page directory
90
120
  const homePagination = new HomePagination(config);
121
+ homePagination.translations = config.homeTranslations || [];
91
122
  homePagination.generateContent(postObjects);
92
123
 
93
124
  // Create all posts page in output/all_posts directory
94
125
  const allPostsPage = new AllPostsPage(config);
126
+ allPostsPage.translations = config.allPostsTranslations || [];
95
127
  allPostsPage.generateContent(postObjects);
96
128
 
97
129
  // Create tag pages in output/tags directory
98
130
  const tagPages = new TagPages(config);
131
+ tagPages.translations = config.tagListTranslations || [];
99
132
  tagPages.generateContent(postObjects);
100
133
 
101
134
  // Create about page in output/about directory
@@ -132,9 +165,15 @@ function build(config) {
132
165
  const languageConfigs = config.languageConfigs || [config];
133
166
  const postTranslationIndex = createPostTranslationIndex(languageConfigs);
134
167
  const aboutTranslations = createAboutTranslations(languageConfigs);
168
+ const homeTranslations = createHomeTranslations(languageConfigs);
169
+ const allPostsTranslations = createAllPostsTranslations(languageConfigs);
170
+ const tagListTranslations = createTagListTranslations(languageConfigs);
135
171
  languageConfigs.forEach((languageConfig) => {
136
172
  languageConfig.postTranslationIndex = postTranslationIndex;
137
173
  languageConfig.aboutTranslations = aboutTranslations;
174
+ languageConfig.homeTranslations = homeTranslations;
175
+ languageConfig.allPostsTranslations = allPostsTranslations;
176
+ languageConfig.tagListTranslations = tagListTranslations;
138
177
  });
139
178
  languageConfigs.forEach(buildLanguage);
140
179
 
@@ -150,4 +189,7 @@ module.exports = {
150
189
  buildLanguage,
151
190
  createPostTranslationIndex,
152
191
  createAboutTranslations,
192
+ createHomeTranslations,
193
+ createAllPostsTranslations,
194
+ createTagListTranslations,
153
195
  };
package/lib/init.js CHANGED
@@ -124,8 +124,11 @@ function initProject(options = {}) {
124
124
  defaultLanguageInSubdir: false,
125
125
  // languages: {
126
126
  // en: { languageName: "English", locale: "en-US" },
127
- // ko: { languageName: "한국어", locale: "ko-KR", blogName: "나의 블로그" },
127
+ // ko: { languageName: "한국어", locale: "ko-KR" },
128
128
  // },
129
+ // Add localized blog information to fossbook.config.en.js,
130
+ // fossbook.config.ko.js, and so on. Menu labels can also be localized with
131
+ // homeLabel, allPostsLabel, aboutLabel, tagsLabel, and allTagsLabel.
129
132
 
130
133
  // Comment system (optional)
131
134
  // comments: { provider: "utterances", repo: "user/repo", issueTerm: "pathname", theme: "github-light" },
package/lib/mod/config.js CHANGED
@@ -98,12 +98,12 @@ function loadConfig(configPath) {
98
98
  // Normalize postsPath into a bare segment without surrounding slashes
99
99
  merged.postsPath = (merged.postsPath || "").replace(/^\/+|\/+$/g, "");
100
100
 
101
- merged.languageConfigs = createLanguageConfigs(merged);
101
+ merged.languageConfigs = createLanguageConfigs(merged, resolvedConfigPath);
102
102
 
103
103
  return merged;
104
104
  }
105
105
 
106
- function createLanguageConfigs(config) {
106
+ function createLanguageConfigs(config, configPath) {
107
107
  const configuredLanguages = config.languages;
108
108
  const hasMultipleLanguages = configuredLanguages !== null && configuredLanguages !== undefined;
109
109
  const languages = hasMultipleLanguages
@@ -125,6 +125,10 @@ function createLanguageConfigs(config) {
125
125
  throw new Error(`languages.${language} must be an object`);
126
126
  }
127
127
 
128
+ const fileOverrides = configPath
129
+ ? loadLanguageConfig(configPath, language)
130
+ : {};
131
+
128
132
  const useSubdirectory = config.defaultLanguageInSubdir || language !== config.defaultLanguage;
129
133
  const languagePrefix = useSubdirectory ? language : "";
130
134
  const basePath = appendPathSegment(config.basePath, languagePrefix);
@@ -133,9 +137,12 @@ function createLanguageConfigs(config) {
133
137
  return {
134
138
  ...config,
135
139
  ...overrides,
140
+ ...fileOverrides,
141
+ defaultLanguage: config.defaultLanguage,
142
+ defaultLanguageInSubdir: config.defaultLanguageInSubdir,
136
143
  language,
137
- languageName: overrides.languageName || language,
138
- locale: overrides.locale || language,
144
+ languageName: fileOverrides.languageName || overrides.languageName || language,
145
+ locale: fileOverrides.locale || overrides.locale || language,
139
146
  languagePrefix,
140
147
  basePath,
141
148
  blogsite,
@@ -149,6 +156,22 @@ function createLanguageConfigs(config) {
149
156
  });
150
157
  }
151
158
 
159
+ function loadLanguageConfig(configPath, language) {
160
+ const extension = path.extname(configPath) || ".js";
161
+ const configStem = extension
162
+ ? configPath.slice(0, -extension.length)
163
+ : configPath;
164
+ const languageConfigPath = `${configStem}.${language}${extension}`;
165
+
166
+ if (!fs.existsSync(languageConfigPath)) return {};
167
+
168
+ const languageConfig = require(languageConfigPath);
169
+ if (!languageConfig || typeof languageConfig !== "object" || Array.isArray(languageConfig)) {
170
+ throw new Error(`${path.basename(languageConfigPath)} must export an object`);
171
+ }
172
+ return languageConfig;
173
+ }
174
+
152
175
  function appendPathSegment(basePath, segment) {
153
176
  const normalizedBase = `/${basePath || ""}`.replace(/\/{2,}/g, "/");
154
177
  const withTrailingSlash = normalizedBase.endsWith("/")
package/lib/tag/index.js CHANGED
@@ -46,10 +46,11 @@ module.exports = class TagList extends PageBase {
46
46
  if (!fs.existsSync(tagsDir))
47
47
  fs.mkdirSync(tagsDir);
48
48
 
49
+ const pageTitle = this.config.allTagsLabel || "All tags";
49
50
  this.imageURL = this.config.image;
50
- this.description = "Tag List";
51
- const data = { tags: tagArray, pageTitle: "All tags" };
52
- this.url = `${this.config.blogsite}/tag_list.html`;
51
+ this.description = pageTitle;
52
+ const data = { tags: tagArray, pageTitle };
53
+ this.url = `${this.config.blogsite}/tags/`;
53
54
  this.title = `${this.config.blogName}: ${data.pageTitle}`;
54
55
 
55
56
  const templatePath = path.join(this.config.themePath, "layouts", "tag_list.html");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fossbook",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "A lightweight static blog site generator for GitHub Pages",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -158,7 +158,7 @@
158
158
  }
159
159
 
160
160
  a {
161
- border-bottom: 3px solid var(--maincolor);
161
+ border-bottom: 2px solid var(--maincolor);
162
162
  color: inherit;
163
163
  text-decoration: none;
164
164
  }
@@ -291,16 +291,20 @@
291
291
  font-size: 1.5rem;
292
292
  }
293
293
  h1, h2, h3, h4, h5, h6 {
294
- font-size: 1.2rem;
294
+ line-height: 1.3;
295
+ margin-bottom: 0.75em;
295
296
  margin-top: 2em;
296
297
  }
297
-
298
- h1::before { color: var(--maincolor); content: '# '; }
299
- h2::before { color: var(--maincolor); content: '## '; }
300
- h3::before { color: var(--maincolor); content: '### '; }
301
- h4::before { color: var(--maincolor); content: '#### '; }
302
- h5::before { color: var(--maincolor); content: '##### '; }
303
- h6::before { color: var(--maincolor); content: '###### '; }
298
+
299
+ h1 { font-size: 1.4rem; }
300
+ h2 { font-size: 1.2rem; }
301
+ h3 { font-size: 1.1rem; }
302
+ h4 {
303
+ font-size: 1rem;
304
+ font-weight: 700;
305
+ }
306
+ h5 { font-size: 0.9rem; }
307
+ h6 { font-size: 0.8rem; }
304
308
 
305
309
  .meta {
306
310
  color: #999;
@@ -331,6 +335,10 @@
331
335
  nav {
332
336
  font-family: 'Roboto Mono', monospace;
333
337
  }
338
+ html:lang(ko) header,
339
+ html:lang(ko) nav {
340
+ font-family: 'NanumSquareNeo', sans-serif;
341
+ }
334
342
  nav.post-navigation {
335
343
  display: flex;
336
344
  justify-content: end;
@@ -365,6 +373,7 @@
365
373
 
366
374
  /* Common */
367
375
  .title h1 {
376
+ font-size: 1.7rem;
368
377
  margin-bottom: 0;
369
378
  }
370
379
 
@@ -394,7 +403,7 @@
394
403
  }
395
404
 
396
405
  .callout a {
397
- border-bottom: 3px solid #fff;
406
+ border-bottom: 2px solid #fff;
398
407
  }
399
408
 
400
409
  .callout a:hover {
@@ -410,7 +419,7 @@
410
419
  content: "🏷 ";
411
420
  }
412
421
  .tags a{
413
- border-bottom: 3px solid var(--maincolor);
422
+ border-bottom: 2px solid var(--maincolor);
414
423
  }
415
424
  .tags a:hover{
416
425
  color:white;
@@ -10,6 +10,8 @@
10
10
  <!-- Google tag (gtag.js) -->
11
11
  ${page.config.googleAnalyticsID ? page.googleAnalytics(page.config.googleAnalyticsID) : ""}
12
12
  <title>${page.config.blogName}: ${data.pageTitle}</title>
13
+ <link rel="canonical" href="${page.url}" />
14
+ ${page.alternateLanguageLinks()}
13
15
  ${page.openGraph("website")}
14
16
  </head>
15
17
  <body>
@@ -17,13 +19,16 @@
17
19
  <header>
18
20
  <div class="main">${page.config.blogName}</div>
19
21
  <nav>
20
- <a href="${page.config.basePath}">Home</a>
22
+ <a href="${page.config.basePath}">${page.config.homeLabel || "Home"}</a>
21
23
  ${data.pageTitle}
22
- <a href="${page.config.basePath}about">About</a>
23
- <a href="${page.config.basePath}tags">Tags</a>
24
+ <a href="${page.config.basePath}about">${page.config.aboutLabel || "About"}</a>
25
+ <a href="${page.config.basePath}tags">${page.config.tagsLabel || "Tags"}</a>
24
26
  </nav>
25
27
  </header>
26
- <h1 class="page-title">${data.pageTitle}</h1>
28
+ <div class="title">
29
+ <h1 class="page-title">${data.pageTitle}</h1>
30
+ ${page.languageSwitcher()}
31
+ </div>
27
32
  <ul class="posts">
28
33
  ${data.posts
29
34
  .map(
@@ -10,6 +10,8 @@
10
10
  ${page.config.googleAnalyticsID ? page.googleAnalytics(page.config.googleAnalyticsID) : ""}
11
11
  <title>${page.config.blogName}</title>
12
12
  <meta name="description" content="${page.config.blogDescription}" />
13
+ <link rel="canonical" href="${page.url}" />
14
+ ${page.alternateLanguageLinks()}
13
15
  ${page.openGraph("website")}
14
16
  <!-- Twitter Card -->
15
17
  ${page.twitterCard("summary")}
@@ -17,12 +19,15 @@
17
19
  <body>
18
20
  <div class="content">
19
21
  <header>
20
- <div class="main">${page.config.blogName}</div>
22
+ <div>
23
+ <div class="main">${page.config.blogName}</div>
24
+ ${page.languageSwitcher()}
25
+ </div>
21
26
  <nav>
22
- Home
23
- <a href="${page.config.basePath}all_posts">All posts</a>
24
- <a href="${page.config.basePath}about">About</a>
25
- <a href="${page.config.basePath}tags">Tags</a>
27
+ ${page.config.homeLabel || "Home"}
28
+ <a href="${page.config.basePath}all_posts">${page.config.allPostsLabel || "All posts"}</a>
29
+ <a href="${page.config.basePath}about">${page.config.aboutLabel || "About"}</a>
30
+ <a href="${page.config.basePath}tags">${page.config.tagsLabel || "Tags"}</a>
26
31
  </nav>
27
32
  </header>
28
33
 
@@ -28,10 +28,10 @@
28
28
  ${page.config.blogName}
29
29
  </div>
30
30
  <nav class="site-navigation">
31
- <a href="${page.config.basePath}">Home</a>
32
- <a href="${page.config.basePath}all_posts">All posts</a>
33
- About
34
- <a href="${page.config.basePath}tags">Tags</a>
31
+ <a href="${page.config.basePath}">${page.config.homeLabel || "Home"}</a>
32
+ <a href="${page.config.basePath}all_posts">${page.config.allPostsLabel || "All posts"}</a>
33
+ ${page.config.aboutLabel || "About"}
34
+ <a href="${page.config.basePath}tags">${page.config.tagsLabel || "Tags"}</a>
35
35
  </nav>
36
36
  </header>
37
37
  <main>
@@ -42,10 +42,10 @@
42
42
  ${page.config.blogName}
43
43
  </div>
44
44
  <nav class="site-navigation">
45
- <a href="${page.config.basePath}">Home</a>
46
- <a href="${page.config.basePath}all_posts">All posts</a>
47
- <a href="${page.config.basePath}about">About</a>
48
- <a href="${page.config.basePath}tags">Tags</a>
45
+ <a href="${page.config.basePath}">${page.config.homeLabel || "Home"}</a>
46
+ <a href="${page.config.basePath}all_posts">${page.config.allPostsLabel || "All posts"}</a>
47
+ <a href="${page.config.basePath}about">${page.config.aboutLabel || "About"}</a>
48
+ <a href="${page.config.basePath}tags">${page.config.tagsLabel || "Tags"}</a>
49
49
  </nav>
50
50
  </header>
51
51
  <main>
@@ -17,10 +17,10 @@
17
17
  <header>
18
18
  <div class="main">${page.config.blogName}</div>
19
19
  <nav>
20
- <a href="${page.config.basePath}">Home</a>
21
- <a href="${page.config.basePath}all_posts">All posts</a>
22
- <a href="${page.config.basePath}about">About</a>
23
- <a href="${page.config.basePath}tags">Tags</a>
20
+ <a href="${page.config.basePath}">${page.config.homeLabel || "Home"}</a>
21
+ <a href="${page.config.basePath}all_posts">${page.config.allPostsLabel || "All posts"}</a>
22
+ <a href="${page.config.basePath}about">${page.config.aboutLabel || "About"}</a>
23
+ <a href="${page.config.basePath}tags">${page.config.tagsLabel || "Tags"}</a>
24
24
  </nav>
25
25
  </header>
26
26
  <h1 class="page-title">Entries tagged - "${data.tag}"</h1>
@@ -11,6 +11,8 @@
11
11
  <!-- Google tag (gtag.js) -->
12
12
  ${page.config.googleAnalyticsID ? page.googleAnalytics(page.config.googleAnalyticsID) : ""}
13
13
  <title>${page.config.blogName}: ${data.pageTitle}</title>
14
+ <link rel="canonical" href="${page.url}" />
15
+ ${page.alternateLanguageLinks()}
14
16
  ${page.openGraph("website")}
15
17
  </head>
16
18
  <body>
@@ -18,13 +20,16 @@
18
20
  <header>
19
21
  <div class="main">${page.config.blogName}</div>
20
22
  <nav>
21
- <a href="${page.config.basePath}">Home</a>
22
- <a href="${page.config.basePath}all_posts">All posts</a>
23
- <a href="${page.config.basePath}about">About</a>
24
- Tags
23
+ <a href="${page.config.basePath}">${page.config.homeLabel || "Home"}</a>
24
+ <a href="${page.config.basePath}all_posts">${page.config.allPostsLabel || "All posts"}</a>
25
+ <a href="${page.config.basePath}about">${page.config.aboutLabel || "About"}</a>
26
+ ${page.config.tagsLabel || "Tags"}
25
27
  </nav>
26
28
  </header>
27
- <h1 class="page-title">${data.pageTitle}</h1>
29
+ <div class="title">
30
+ <h1 class="page-title">${data.pageTitle}</h1>
31
+ ${page.languageSwitcher()}
32
+ </div>
28
33
  <div class="tag-cloud">
29
34
  <ul class="tags">
30
35
  ${data.tags