fossbook 0.0.2

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.
Files changed (53) hide show
  1. package/README.md +246 -0
  2. package/bin/fossbook.js +119 -0
  3. package/lib/all_posts.js +32 -0
  4. package/lib/assets/placeholder.svg +4 -0
  5. package/lib/deploy.js +202 -0
  6. package/lib/home.js +59 -0
  7. package/lib/index.js +79 -0
  8. package/lib/init.js +253 -0
  9. package/lib/mod/config.js +98 -0
  10. package/lib/mod/marked.js +95 -0
  11. package/lib/mod/page.js +115 -0
  12. package/lib/mod/page_base.js +97 -0
  13. package/lib/new.js +48 -0
  14. package/lib/posts.js +42 -0
  15. package/lib/server.js +14 -0
  16. package/lib/tag/index.js +69 -0
  17. package/lib/tag/tag.js +29 -0
  18. package/package.json +45 -0
  19. package/themes/archie/assets/fonts/ComicNeue-Bold.ttf +0 -0
  20. package/themes/archie/assets/fonts/ComicNeue-BoldItalic.ttf +0 -0
  21. package/themes/archie/assets/fonts/ComicNeue-Italic.ttf +0 -0
  22. package/themes/archie/assets/fonts/ComicNeue-Light.ttf +0 -0
  23. package/themes/archie/assets/fonts/ComicNeue-LightItalic.ttf +0 -0
  24. package/themes/archie/assets/fonts/ComicNeue-Regular.ttf +0 -0
  25. package/themes/archie/assets/fonts/LyonDisplay-Bold.otf +0 -0
  26. package/themes/archie/assets/fonts/OFL.txt +93 -0
  27. package/themes/archie/assets/fonts/fira-sans-v10-latin-regular.eot +0 -0
  28. package/themes/archie/assets/fonts/fira-sans-v10-latin-regular.svg +330 -0
  29. package/themes/archie/assets/fonts/fira-sans-v10-latin-regular.ttf +0 -0
  30. package/themes/archie/assets/fonts/fira-sans-v10-latin-regular.woff +0 -0
  31. package/themes/archie/assets/fonts/fira-sans-v10-latin-regular.woff2 +0 -0
  32. package/themes/archie/assets/fonts/ibm-plex-mono-v6-latin-500italic.eot +0 -0
  33. package/themes/archie/assets/fonts/ibm-plex-mono-v6-latin-500italic.svg +365 -0
  34. package/themes/archie/assets/fonts/ibm-plex-mono-v6-latin-500italic.ttf +0 -0
  35. package/themes/archie/assets/fonts/ibm-plex-mono-v6-latin-500italic.woff +0 -0
  36. package/themes/archie/assets/fonts/ibm-plex-mono-v6-latin-500italic.woff2 +0 -0
  37. package/themes/archie/assets/fonts/roboto-mono-v12-latin-regular.eot +0 -0
  38. package/themes/archie/assets/fonts/roboto-mono-v12-latin-regular.svg +405 -0
  39. package/themes/archie/assets/fonts/roboto-mono-v12-latin-regular.ttf +0 -0
  40. package/themes/archie/assets/fonts/roboto-mono-v12-latin-regular.woff +0 -0
  41. package/themes/archie/assets/fonts/roboto-mono-v12-latin-regular.woff2 +0 -0
  42. package/themes/archie/assets/images/node-ssg-1.png +0 -0
  43. package/themes/archie/assets/images/node-ssg-2.png +0 -0
  44. package/themes/archie/assets/styles/fonts.css +51 -0
  45. package/themes/archie/assets/styles/highlights.css +75 -0
  46. package/themes/archie/assets/styles/main.css +402 -0
  47. package/themes/archie/layouts/all_posts.html +42 -0
  48. package/themes/archie/layouts/home.html +62 -0
  49. package/themes/archie/layouts/page.html +50 -0
  50. package/themes/archie/layouts/partials/footer.html +10 -0
  51. package/themes/archie/layouts/post.html +82 -0
  52. package/themes/archie/layouts/tag.html +43 -0
  53. package/themes/archie/layouts/tag_list.html +44 -0
package/lib/index.js ADDED
@@ -0,0 +1,79 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const Page = require("./mod/page");
5
+ const HomePagination = require("./home");
6
+ const AllPostsPage = require("./all_posts");
7
+ const Posts = require("./posts");
8
+ const TagPages = require("./tag");
9
+
10
+ function copyDirectoryRecursive(src, dest) {
11
+ fs.mkdirSync(dest, { recursive: true });
12
+ const entries = fs.readdirSync(src, { withFileTypes: true });
13
+
14
+ for (const entry of entries) {
15
+ const srcPath = path.join(src, entry.name);
16
+ const destPath = path.join(dest, entry.name);
17
+
18
+ if (entry.isDirectory()) {
19
+ copyDirectoryRecursive(srcPath, destPath);
20
+ } else {
21
+ fs.copyFileSync(srcPath, destPath);
22
+ }
23
+ }
24
+ }
25
+
26
+ function build(config) {
27
+ // Remove the output directory
28
+ if (fs.existsSync(config.dev.outdir))
29
+ fs.rmSync(config.dev.outdir, { recursive: true });
30
+ fs.mkdirSync(config.dev.outdir);
31
+
32
+ // Create post pages in output directory
33
+ const posts = new Posts(config);
34
+ const postObjects = posts.createPostObjects();
35
+
36
+ postObjects.forEach((post) => {
37
+ post.generateContent("post.html");
38
+ });
39
+
40
+ // Create home page and pagination in output/page directory
41
+ const homePagination = new HomePagination(config);
42
+ homePagination.generateContent(postObjects);
43
+
44
+ // Create all posts page in output/all_posts directory
45
+ const allPostsPage = new AllPostsPage(config);
46
+ allPostsPage.generateContent(postObjects);
47
+
48
+ // Create tag pages in output/tags directory
49
+ const tagPages = new TagPages(config);
50
+ tagPages.generateContent(postObjects);
51
+
52
+ // Create about page in output/about directory
53
+ const aboutPath = config.dev.about;
54
+ if (fs.existsSync(aboutPath)) {
55
+ const aboutPage = new Page(config);
56
+ aboutPage.readSource(aboutPath);
57
+ aboutPage.generateContent("page.html", "about");
58
+ }
59
+
60
+ // Copy static directory to output directory
61
+ const staticImagesDir = path.join(config.dev.staticDir, "images");
62
+ if (fs.existsSync(staticImagesDir)) {
63
+ copyDirectoryRecursive(staticImagesDir, path.join(config.dev.outdir, "images"));
64
+ }
65
+
66
+ // Copy theme assets to output directory
67
+ const themeAssetsDir = path.join(config.themePath, "assets");
68
+ if (fs.existsSync(themeAssetsDir)) {
69
+ copyDirectoryRecursive(themeAssetsDir, config.dev.outdir);
70
+ }
71
+
72
+ // Create CNAME file for GitHub Pages
73
+ if (config.githubCNAME)
74
+ fs.writeFileSync(path.join(config.dev.outdir, "CNAME"), config.githubCNAME);
75
+
76
+ console.log("Build completed successfully");
77
+ }
78
+
79
+ module.exports = { build };
package/lib/init.js ADDED
@@ -0,0 +1,253 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { execSync } = require("child_process");
4
+
5
+ const DEPLOY_WORKFLOW = `name: Deploy to GitHub Pages
6
+
7
+ on:
8
+ push:
9
+ branches: [main]
10
+
11
+ permissions:
12
+ contents: read
13
+ pages: write
14
+ id-token: write
15
+
16
+ concurrency:
17
+ group: "pages"
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ build-and-deploy:
22
+ runs-on: ubuntu-latest
23
+ environment:
24
+ name: github-pages
25
+ url: \${{ steps.deployment.outputs.page_url }}
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+
29
+ - name: Setup Node.js
30
+ uses: actions/setup-node@v4
31
+ with:
32
+ node-version: '20'
33
+ cache: 'npm'
34
+
35
+ - name: Install dependencies
36
+ run: npm ci
37
+
38
+ - name: Build site
39
+ run: npx fossbook build
40
+
41
+ - name: Setup Pages
42
+ uses: actions/configure-pages@v4
43
+
44
+ - name: Upload artifact
45
+ uses: actions/upload-pages-artifact@v3
46
+ with:
47
+ path: './public'
48
+
49
+ - name: Deploy to GitHub Pages
50
+ id: deployment
51
+ uses: actions/deploy-pages@v4
52
+ `;
53
+
54
+ function initProject(options = {}) {
55
+ const cwd = process.cwd();
56
+
57
+ // Create directories
58
+ const dirs = [
59
+ "content/posts",
60
+ "static/images",
61
+ ];
62
+
63
+ dirs.forEach((dir) => {
64
+ const dirPath = path.join(cwd, dir);
65
+ if (!fs.existsSync(dirPath)) {
66
+ fs.mkdirSync(dirPath, { recursive: true });
67
+ console.log(`Created: ${dir}/`);
68
+ }
69
+ });
70
+
71
+ // Create .github/workflows/deploy.yml
72
+ const workflowDir = path.join(cwd, ".github", "workflows");
73
+ const workflowPath = path.join(workflowDir, "deploy.yml");
74
+ if (!fs.existsSync(workflowPath)) {
75
+ fs.mkdirSync(workflowDir, { recursive: true });
76
+ fs.writeFileSync(workflowPath, DEPLOY_WORKFLOW);
77
+ console.log("Created: .github/workflows/deploy.yml");
78
+ }
79
+
80
+ // Create .gitignore
81
+ const gitignorePath = path.join(cwd, ".gitignore");
82
+ if (!fs.existsSync(gitignorePath)) {
83
+ fs.writeFileSync(gitignorePath, "node_modules/\npublic/\n");
84
+ console.log("Created: .gitignore");
85
+ }
86
+
87
+ // Create fossbook.config.js
88
+ const configPath = path.join(cwd, "fossbook.config.js");
89
+ if (!fs.existsSync(configPath)) {
90
+ const configContent = `module.exports = {
91
+ blogName: "My Blog",
92
+ authorName: "",
93
+ authorDescription: "",
94
+ authorWebsite: "",
95
+ blogDescription: "A blog powered by fossbook",
96
+ blogsite: "http://localhost:3000",
97
+
98
+ // Optional
99
+ githubCNAME: "",
100
+ googleAnalyticsID: "",
101
+ authorTwitter: "",
102
+ siteTwitter: "",
103
+ githubRepository: "",
104
+ image: "",
105
+ theme: "archie",
106
+
107
+ // Comment system (optional)
108
+ // comments: { provider: "utterances", repo: "user/repo", issueTerm: "pathname", theme: "github-light" },
109
+
110
+ // Deployment
111
+ // deploy: { branch: "main", remote: "origin" },
112
+
113
+ // Directory overrides (defaults shown)
114
+ content: "./content",
115
+ postsDir: "./content/posts",
116
+ outputDir: "./public",
117
+ staticDir: "./static",
118
+ themesDir: "./themes",
119
+ };
120
+ `;
121
+ fs.writeFileSync(configPath, configContent);
122
+ console.log("Created: fossbook.config.js");
123
+ }
124
+
125
+ // Create content/about.md
126
+ const aboutPath = path.join(cwd, "content", "about.md");
127
+ if (!fs.existsSync(aboutPath)) {
128
+ const aboutContent = `---
129
+ title: About
130
+ ---
131
+
132
+ Welcome to my blog!
133
+ `;
134
+ fs.writeFileSync(aboutPath, aboutContent);
135
+ console.log("Created: content/about.md");
136
+ }
137
+
138
+ // Create package.json if it doesn't exist
139
+ const pkgPath = path.join(cwd, "package.json");
140
+ const createdPkg = !fs.existsSync(pkgPath);
141
+ if (createdPkg) {
142
+ const pkg = {
143
+ name: path.basename(cwd),
144
+ version: "1.0.0",
145
+ private: true,
146
+ scripts: {
147
+ build: "fossbook build",
148
+ start: "fossbook serve",
149
+ deploy: "fossbook deploy",
150
+ },
151
+ dependencies: {
152
+ fossbook: "latest",
153
+ },
154
+ };
155
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
156
+ console.log("Created: package.json");
157
+ }
158
+
159
+ // Generate package-lock.json (required by CI/CD npm ci + cache)
160
+ const lockPath = path.join(cwd, "package-lock.json");
161
+ if (!fs.existsSync(lockPath)) {
162
+ console.log("Running npm install to generate package-lock.json...");
163
+ try {
164
+ execSync("npm install", { cwd, stdio: "inherit" });
165
+ console.log("Created: package-lock.json");
166
+ } catch {
167
+ console.warn("Warning: npm install failed. You may need to run it manually.");
168
+ }
169
+ }
170
+
171
+ // Handle --github flag: create repo and push
172
+ if (options.github) {
173
+ initGitHub(cwd);
174
+ }
175
+
176
+ console.log("\nSite initialized! Next steps:");
177
+ if (!options.github) {
178
+ console.log(' 1. Edit fossbook.config.js with your site settings');
179
+ console.log(' 2. Run: fossbook new "My First Post"');
180
+ console.log(" 3. Edit your post in content/posts/");
181
+ console.log(" 4. Run: fossbook build");
182
+ console.log(" 5. Run: fossbook serve");
183
+ } else {
184
+ console.log(' 1. Edit fossbook.config.js with your site settings');
185
+ console.log(' 2. Run: fossbook new "My First Post"');
186
+ console.log(" 3. Edit your post in content/posts/");
187
+ console.log(" 4. Run: fossbook deploy");
188
+ }
189
+ }
190
+
191
+ function initGitHub(cwd) {
192
+ // Check if gh CLI is available
193
+ try {
194
+ execSync("gh --version", { stdio: "ignore" });
195
+ } catch {
196
+ console.error(
197
+ "Error: GitHub CLI (gh) is not installed.\n" +
198
+ "Install it: https://cli.github.com/\n" +
199
+ " Linux: sudo apt install gh\n" +
200
+ " macOS: brew install gh\n" +
201
+ " Windows: winget install GitHub.cli"
202
+ );
203
+ process.exit(1);
204
+ }
205
+
206
+ // Check if gh is authenticated
207
+ try {
208
+ execSync("gh auth status", { stdio: "ignore" });
209
+ } catch {
210
+ console.error("Error: GitHub CLI is not authenticated. Run: gh auth login");
211
+ process.exit(1);
212
+ }
213
+
214
+ // Initialize git repo if needed
215
+ if (!fs.existsSync(path.join(cwd, ".git"))) {
216
+ console.log("Initializing git repository...");
217
+ execSync("git init", { cwd, stdio: "inherit" });
218
+ execSync("git branch -M main", { cwd, stdio: "inherit" });
219
+ }
220
+
221
+ // Create GitHub repository
222
+ const repoName = path.basename(cwd);
223
+ console.log(`Creating GitHub repository: ${repoName}...`);
224
+ try {
225
+ execSync(`gh repo create ${repoName} --public --source=. --remote=origin`, {
226
+ cwd,
227
+ stdio: "inherit",
228
+ });
229
+ } catch {
230
+ // Repo may already exist or remote may already be set
231
+ console.warn("Note: GitHub repo creation skipped (may already exist).");
232
+ }
233
+
234
+ // Initial commit and push
235
+ console.log("Committing and pushing to GitHub...");
236
+ execSync("git add -A", { cwd, stdio: "inherit" });
237
+ try {
238
+ execSync('git commit -m "Initial fossbook site"', { cwd, stdio: "inherit" });
239
+ } catch {
240
+ // Nothing to commit
241
+ }
242
+ try {
243
+ execSync("git push -u origin main", { cwd, stdio: "inherit" });
244
+ } catch (e) {
245
+ console.warn("Warning: Could not push to origin. You may need to push manually.");
246
+ }
247
+
248
+ console.log("\nGitHub repository created and pushed!");
249
+ console.log("Enable GitHub Pages in your repo settings:");
250
+ console.log(" Settings → Pages → Source → GitHub Actions");
251
+ }
252
+
253
+ module.exports = { initProject };
@@ -0,0 +1,98 @@
1
+ const path = require("path");
2
+ const fs = require("fs");
3
+
4
+ const defaults = {
5
+ blogName: "My Blog",
6
+ authorName: "",
7
+ authorDescription: "",
8
+ authorWebsite: "",
9
+ blogDescription: "",
10
+ blogsite: "http://localhost:3000",
11
+ githubCNAME: "",
12
+ googleAnalyticsID: "",
13
+ authorTwitter: "",
14
+ siteTwitter: "",
15
+ githubRepository: "",
16
+ image: "",
17
+ theme: "archie",
18
+ comments: null, // { provider: "utterances", repo: "user/repo", issueTerm: "pathname", theme: "github-light" }
19
+ content: "./content",
20
+ postsDir: "./content/posts",
21
+ outputDir: "./public",
22
+ staticDir: "./static",
23
+ themesDir: "./themes",
24
+ };
25
+
26
+ /**
27
+ * Resolve the theme path, checking the user's themesDir first,
28
+ * then falling back to the built-in themes directory.
29
+ */
30
+ function resolveThemePath(config) {
31
+ const themeName = config.theme || "archie";
32
+ const userThemePath = path.resolve(config.themesDir || "./themes", themeName);
33
+ const builtinThemePath = path.resolve(__dirname, "../../themes", themeName);
34
+
35
+ if (fs.existsSync(userThemePath)) {
36
+ return userThemePath;
37
+ }
38
+ if (fs.existsSync(builtinThemePath)) {
39
+ return builtinThemePath;
40
+ }
41
+ console.error(`Theme "${themeName}" not found in ${userThemePath} or ${builtinThemePath}`);
42
+ process.exit(1);
43
+ }
44
+
45
+ /**
46
+ * Load configuration from the user's fossbook.config.js (or a custom path),
47
+ * merge with defaults, and build the internal dev paths.
48
+ */
49
+ function loadConfig(configPath) {
50
+ const resolvedConfigPath = path.resolve(configPath || "./fossbook.config.js");
51
+
52
+ let userConfig = {};
53
+ if (fs.existsSync(resolvedConfigPath)) {
54
+ userConfig = require(resolvedConfigPath);
55
+ } else if (configPath) {
56
+ // Only error if user explicitly specified a config path
57
+ console.error(`Config file not found: ${resolvedConfigPath}`);
58
+ process.exit(1);
59
+ } else {
60
+ console.warn("No fossbook.config.js found, using defaults.");
61
+ }
62
+
63
+ const merged = { ...defaults, ...userConfig };
64
+
65
+ // Build the internal dev paths object used by the generator modules
66
+ const themePath = resolveThemePath(merged);
67
+
68
+ merged.dev = {
69
+ postsdir: path.resolve(merged.postsDir),
70
+ content: path.resolve(merged.content),
71
+ about: path.resolve(merged.content, "about.md"),
72
+ outdir: path.resolve(merged.outputDir),
73
+ themePath: path.dirname(themePath), // e.g. /path/to/themes
74
+ staticDir: path.resolve(merged.staticDir),
75
+ };
76
+
77
+ merged.themePath = themePath; // full path to the active theme directory
78
+
79
+ // Inject version and build date
80
+ merged.version = require("../../package.json").version;
81
+ merged.date_time = formatDate(new Date());
82
+
83
+ return merged;
84
+ }
85
+
86
+ function formatDate(
87
+ date,
88
+ locale = "en-US",
89
+ options = {
90
+ year: "numeric",
91
+ month: "short",
92
+ day: "numeric",
93
+ },
94
+ ) {
95
+ return date.toLocaleDateString(locale, options);
96
+ }
97
+
98
+ module.exports = { loadConfig, resolveThemePath, formatDate };
@@ -0,0 +1,95 @@
1
+ const marked = require("marked");
2
+
3
+ marked.setOptions({
4
+ renderer: new marked.Renderer(),
5
+ highlight: function (code, language) {
6
+ const hljs = require("highlight.js");
7
+ const validLanguage = hljs.getLanguage(language) ? language : "plaintext";
8
+ return hljs.highlight(validLanguage, code).value;
9
+ },
10
+ pedantic: false,
11
+ gfm: true,
12
+ breaks: false,
13
+ sanitize: false,
14
+ smartLists: true,
15
+ smartypants: false,
16
+ xhtml: false,
17
+ });
18
+
19
+ // Override function
20
+ const renderer = {
21
+ image(href, title, text) {
22
+ let size = null;
23
+ // Check if the title contains a size specification
24
+ if (title && title.includes("size:")) {
25
+ const sizeMatch = title.match(/size:(\d+%)/);
26
+ if (sizeMatch && sizeMatch[1]) {
27
+ size = sizeMatch[1];
28
+ // Remove the size specification from the title
29
+ title = title.replace(/size:\d+%/g, "").trim();
30
+ }
31
+ }
32
+ let align = "center";
33
+ if (title && title.includes("align:")) {
34
+ // align: left, right, center
35
+ const alignMatch = title.match(/align:(left|right|center)/);
36
+ if (alignMatch && alignMatch[1]) {
37
+ align = alignMatch[1];
38
+ // Remove the alignment specification from the title
39
+ title = title.replace(/align:(left|right|center)/g, "").trim();
40
+ }
41
+ }
42
+
43
+ // Construct the image tag with optional size and title
44
+ let imageTag = `<img src="${href}" alt="${text}"`;
45
+ if (size) {
46
+ imageTag += ` style="width: ${size};"`;
47
+ }
48
+ imageTag += ">";
49
+
50
+ return `
51
+ <div style="text-align: ${align};">
52
+ <figure style="text-align: ${align};">
53
+ ${imageTag}
54
+ ${title ? `<figcaption>${title}</figcaption>` : ""}
55
+ </figure>
56
+ </div>
57
+ `;
58
+ },
59
+ link(href, title, text) {
60
+ let align = null;
61
+ if (title && title.includes("align:")) {
62
+ // align: left, right, center
63
+ const alignMatch = title.match(/align:(left|right|center)/);
64
+ if (alignMatch && alignMatch[1]) {
65
+ align = alignMatch[1];
66
+ // Remove the alignment specification from the title
67
+ title = title.replace(/align:(left|right|center)/g, "").trim();
68
+ }
69
+ }
70
+ // if align is not null, add div with text-align style
71
+ if (align) {
72
+ return `
73
+ <div style="text-align: ${align};">
74
+ <a href="${href} "${title ? `title="${title}"` : ""}>${text}</a>
75
+ </div>`;
76
+ } else {
77
+ return `<a href="${href} "${title ? `title="${title}"` : ""}>${text}</a>`;
78
+ }
79
+ },
80
+ blockquote(quote) {
81
+ return `<div class="blockquote-container"><blockquote>${quote}</blockquote></div>`;
82
+ },
83
+ paragraph(text) {
84
+ // remove <p> surrounding the image
85
+ if (text.includes('<figure style="text-align: center;">')) {
86
+ return text;
87
+ } else {
88
+ return `<p>${text}</p>`;
89
+ }
90
+ },
91
+ };
92
+
93
+ marked.use({ renderer });
94
+
95
+ module.exports = marked;
@@ -0,0 +1,115 @@
1
+ const fm = require("front-matter");
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const marked = require("./marked");
5
+ const PageBase = require("./page_base");
6
+
7
+ module.exports = class Page extends PageBase {
8
+ constructor(config) {
9
+ super(config);
10
+ this.srcFilePath = "";
11
+ this.markdownFilePath = ""; // markdown file path
12
+ this.contentPath = config.dev.content;
13
+ }
14
+
15
+ readSource(filePath) {
16
+ if (filePath.indexOf(".md") === -1)
17
+ this.srcFilePath = filePath + "/index.md";
18
+ else this.srcFilePath = filePath;
19
+
20
+ const mdContent = fs.readFileSync(this.srcFilePath, "utf8");
21
+
22
+ if (path.extname(filePath) === ".md") {
23
+ // If the file has a .md extension, extract the file name without the extension
24
+ this.path = path.basename(filePath, ".md");
25
+ } else {
26
+ // If there's no .md extension, just get the last part of the path
27
+ this.path = path.basename(filePath);
28
+ }
29
+ // parsed content by fields and body
30
+ const content = fm(mdContent);
31
+
32
+ this.title = `${content.attributes.title}`;
33
+ this.date = content.attributes.date;
34
+ this.url = `${this.config.blogsite}/${this.path}/`;
35
+ this.image = content.attributes.image;
36
+ this.description = content.attributes.description;
37
+ // default image if no imageURL is specified
38
+ this.imageURL = this.config.image;
39
+
40
+ if (content.attributes.tags && content.attributes.tags.length > 0) {
41
+ const tagArray = content.attributes.tags.split(",");
42
+ this.tags = tagArray
43
+ .map((tag) => tag.trim())
44
+ .sort((a, b) => a.localeCompare(b));
45
+ }
46
+
47
+ // generated HTML from markdown
48
+ this.body = marked.parse(content.body);
49
+ // remove <p></p> and <p> </p> from the beginning and end of the content.body
50
+ this.body = this.body.replace(/<p><\/p>/g, "").replace(/<p> <\/p>/g, "");
51
+
52
+ // for generating the navigation link in the post.html template
53
+ this.next = null;
54
+ this.previous = null;
55
+ }
56
+
57
+ // about/index.html or about/hello.html
58
+ generateContent(templateFile, outputPath) {
59
+ // For a series of posts
60
+ if (outputPath === undefined) outputPath = `${this.path}/index.html`;
61
+ // if file name is not included in the path
62
+ if (outputPath.indexOf(".htm") === -1) {
63
+ if (outputPath[outputPath.length - 1] !== "/") outputPath += "/";
64
+ outputPath += "index.html";
65
+ }
66
+
67
+ // if a directory path is included in the path
68
+ if (outputPath.indexOf("/") !== -1) {
69
+ if (this.path === "")
70
+ this.path = outputPath.split("/").slice(0, -1).join("/");
71
+
72
+ if (fs.existsSync(`${this.config.dev.outdir}/${this.path}`))
73
+ fs.rmSync(`${this.config.dev.outdir}/${this.path}`, {
74
+ recursive: true,
75
+ });
76
+
77
+ fs.mkdirSync(`${this.config.dev.outdir}/${this.path}`);
78
+ } else {
79
+ // remove the outputPath file if it exists
80
+ if (fs.existsSync(`${this.config.dev.outdir}/${this.path}`))
81
+ fs.unlinkSync(`${this.config.dev.outdir}/${this.path}`);
82
+ }
83
+
84
+ const layoutsPath = path.join(this.config.themePath, "layouts", templateFile);
85
+ const postHTML = this.generateHTML(layoutsPath);
86
+
87
+ fs.writeFileSync(
88
+ `${this.config.dev.outdir}/${outputPath}`,
89
+ postHTML,
90
+ (e) => {
91
+ if (e) throw e;
92
+ console.log(`${outputPath}/index.html was created successfully`);
93
+ },
94
+ );
95
+
96
+ // if there is the images folder in the output directory.
97
+ if (
98
+ fs.existsSync(`${this.config.dev.postsdir}/${this.path}/images`) &&
99
+ this.path !== ""
100
+ ) {
101
+ // Copy images folder from postsdir to outdir
102
+ if (!fs.existsSync(`${this.config.dev.outdir}/${this.path}/images`))
103
+ fs.mkdirSync(`${this.config.dev.outdir}/${this.path}/images`);
104
+
105
+ fs.readdirSync(`${this.config.dev.postsdir}/${this.path}/images`).forEach(
106
+ (image) => {
107
+ fs.copyFileSync(
108
+ `${this.config.dev.postsdir}/${this.path}/images/${image}`,
109
+ `${this.config.dev.outdir}/${this.path}/images/${image}`,
110
+ );
111
+ },
112
+ );
113
+ }
114
+ }
115
+ };