fossbook 0.0.4 → 0.0.6
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 +246 -246
- package/bin/fossbook.js +120 -119
- package/lib/all_posts.js +33 -32
- package/lib/assets/placeholder.svg +3 -3
- package/lib/deploy.js +257 -237
- package/lib/home.js +60 -59
- package/lib/index.js +79 -79
- package/lib/init.js +311 -253
- package/lib/mod/config.js +136 -136
- package/lib/mod/marked.js +95 -95
- package/lib/mod/page.js +119 -115
- package/lib/mod/page_base.js +97 -97
- package/lib/new.js +48 -48
- package/lib/posts.js +47 -42
- package/lib/server.js +20 -14
- package/lib/tag/index.js +70 -69
- package/lib/tag/tag.js +30 -29
- package/package.json +45 -45
- package/themes/archie/assets/fonts/fira-sans-v10-latin-regular.svg +330 -330
- package/themes/archie/assets/fonts/ibm-plex-mono-v6-latin-500italic.svg +365 -365
- package/themes/archie/assets/fonts/roboto-mono-v12-latin-regular.svg +405 -405
- package/themes/archie/assets/styles/fonts.css +51 -51
- package/themes/archie/assets/styles/highlights.css +75 -75
- package/themes/archie/assets/styles/main.css +402 -402
- package/themes/archie/layouts/all_posts.html +41 -41
- package/themes/archie/layouts/home.html +61 -61
- package/themes/archie/layouts/page.html +50 -50
- package/themes/archie/layouts/partials/footer.html +9 -9
- package/themes/archie/layouts/post.html +81 -81
- package/themes/archie/layouts/tag.html +42 -42
- package/themes/archie/layouts/tag_list.html +43 -43
package/lib/mod/config.js
CHANGED
|
@@ -1,136 +1,136 @@
|
|
|
1
|
-
const path = require("path");
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const { execSync } = require("child_process");
|
|
4
|
-
|
|
5
|
-
const defaults = {
|
|
6
|
-
blogName: "My Blog",
|
|
7
|
-
authorName: "",
|
|
8
|
-
authorDescription: "",
|
|
9
|
-
authorWebsite: "",
|
|
10
|
-
blogDescription: "",
|
|
11
|
-
blogsite: "http://localhost:3000",
|
|
12
|
-
githubCNAME: "",
|
|
13
|
-
googleAnalyticsID: "",
|
|
14
|
-
authorTwitter: "",
|
|
15
|
-
siteTwitter: "",
|
|
16
|
-
githubRepository: "",
|
|
17
|
-
image: "",
|
|
18
|
-
theme: "archie",
|
|
19
|
-
basePath: "", // auto-detected for GitHub Pages project sites, e.g. "/repo-name/"
|
|
20
|
-
comments: null, // { provider: "utterances", repo: "user/repo", issueTerm: "pathname", theme: "github-light" }
|
|
21
|
-
content: "./content",
|
|
22
|
-
postsDir: "./content/posts",
|
|
23
|
-
outputDir: "./public",
|
|
24
|
-
staticDir: "./static",
|
|
25
|
-
themesDir: "./themes",
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Resolve the theme path, checking the user's themesDir first,
|
|
30
|
-
* then falling back to the built-in themes directory.
|
|
31
|
-
*/
|
|
32
|
-
function resolveThemePath(config) {
|
|
33
|
-
const themeName = config.theme || "archie";
|
|
34
|
-
const userThemePath = path.resolve(config.themesDir || "./themes", themeName);
|
|
35
|
-
const builtinThemePath = path.resolve(__dirname, "../../themes", themeName);
|
|
36
|
-
|
|
37
|
-
if (fs.existsSync(userThemePath)) {
|
|
38
|
-
return userThemePath;
|
|
39
|
-
}
|
|
40
|
-
if (fs.existsSync(builtinThemePath)) {
|
|
41
|
-
return builtinThemePath;
|
|
42
|
-
}
|
|
43
|
-
console.error(`Theme "${themeName}" not found in ${userThemePath} or ${builtinThemePath}`);
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Load configuration from the user's fossbook.config.js (or a custom path),
|
|
49
|
-
* merge with defaults, and build the internal dev paths.
|
|
50
|
-
*/
|
|
51
|
-
function loadConfig(configPath) {
|
|
52
|
-
const resolvedConfigPath = path.resolve(configPath || "./fossbook.config.js");
|
|
53
|
-
|
|
54
|
-
let userConfig = {};
|
|
55
|
-
if (fs.existsSync(resolvedConfigPath)) {
|
|
56
|
-
userConfig = require(resolvedConfigPath);
|
|
57
|
-
} else if (configPath) {
|
|
58
|
-
// Only error if user explicitly specified a config path
|
|
59
|
-
console.error(`Config file not found: ${resolvedConfigPath}`);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
} else {
|
|
62
|
-
console.warn("No fossbook.config.js found, using defaults.");
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const merged = { ...defaults, ...userConfig };
|
|
66
|
-
|
|
67
|
-
// Build the internal dev paths object used by the generator modules
|
|
68
|
-
const themePath = resolveThemePath(merged);
|
|
69
|
-
|
|
70
|
-
merged.dev = {
|
|
71
|
-
postsdir: path.resolve(merged.postsDir),
|
|
72
|
-
content: path.resolve(merged.content),
|
|
73
|
-
about: path.resolve(merged.content, "about.md"),
|
|
74
|
-
outdir: path.resolve(merged.outputDir),
|
|
75
|
-
themePath: path.dirname(themePath), // e.g. /path/to/themes
|
|
76
|
-
staticDir: path.resolve(merged.staticDir),
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
merged.themePath = themePath; // full path to the active theme directory
|
|
80
|
-
|
|
81
|
-
// Inject version and build date
|
|
82
|
-
merged.version = require("../../package.json").version;
|
|
83
|
-
merged.date_time = formatDate(new Date());
|
|
84
|
-
|
|
85
|
-
// Auto-detect basePath if not explicitly set
|
|
86
|
-
if (!merged.basePath) {
|
|
87
|
-
merged.basePath = detectBasePath(merged);
|
|
88
|
-
}
|
|
89
|
-
// Ensure basePath has trailing slash
|
|
90
|
-
if (!merged.basePath.endsWith("/")) {
|
|
91
|
-
merged.basePath += "/";
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return merged;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function formatDate(
|
|
98
|
-
date,
|
|
99
|
-
locale = "en-US",
|
|
100
|
-
options = {
|
|
101
|
-
year: "numeric",
|
|
102
|
-
month: "short",
|
|
103
|
-
day: "numeric",
|
|
104
|
-
},
|
|
105
|
-
) {
|
|
106
|
-
return date.toLocaleDateString(locale, options);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Auto-detect basePath for GitHub Pages project sites.
|
|
111
|
-
* Returns "/repo-name/" for project sites, or "/" for user sites / custom domains.
|
|
112
|
-
*/
|
|
113
|
-
function detectBasePath(config) {
|
|
114
|
-
// If user has a custom domain, no prefix needed
|
|
115
|
-
if (config.githubCNAME) return "/";
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
const remoteUrl = execSync("git remote get-url origin", {
|
|
119
|
-
encoding: "utf-8",
|
|
120
|
-
stdio: ["pipe", "pipe", "ignore"],
|
|
121
|
-
}).trim();
|
|
122
|
-
const match = remoteUrl.match(/github\.com[:/](.+?)(?:\.git)?$/);
|
|
123
|
-
if (match) {
|
|
124
|
-
const [owner, repo] = match[1].split("/");
|
|
125
|
-
// User/org sites (e.g. username.github.io) are served at root
|
|
126
|
-
if (repo === `${owner}.github.io`) return "/";
|
|
127
|
-
// Project sites need the repo name as prefix
|
|
128
|
-
return `/${repo}/`;
|
|
129
|
-
}
|
|
130
|
-
} catch {
|
|
131
|
-
// Not a git repo or no remote — fall back to root
|
|
132
|
-
}
|
|
133
|
-
return "/";
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
module.exports = { loadConfig, resolveThemePath, formatDate };
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const { execSync } = require("child_process");
|
|
4
|
+
|
|
5
|
+
const defaults = {
|
|
6
|
+
blogName: "My Blog",
|
|
7
|
+
authorName: "",
|
|
8
|
+
authorDescription: "",
|
|
9
|
+
authorWebsite: "",
|
|
10
|
+
blogDescription: "",
|
|
11
|
+
blogsite: "http://localhost:3000",
|
|
12
|
+
githubCNAME: "",
|
|
13
|
+
googleAnalyticsID: "",
|
|
14
|
+
authorTwitter: "",
|
|
15
|
+
siteTwitter: "",
|
|
16
|
+
githubRepository: "",
|
|
17
|
+
image: "",
|
|
18
|
+
theme: "archie",
|
|
19
|
+
basePath: "", // auto-detected for GitHub Pages project sites, e.g. "/repo-name/"
|
|
20
|
+
comments: null, // { provider: "utterances", repo: "user/repo", issueTerm: "pathname", theme: "github-light" }
|
|
21
|
+
content: "./content",
|
|
22
|
+
postsDir: "./content/posts",
|
|
23
|
+
outputDir: "./public",
|
|
24
|
+
staticDir: "./static",
|
|
25
|
+
themesDir: "./themes",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the theme path, checking the user's themesDir first,
|
|
30
|
+
* then falling back to the built-in themes directory.
|
|
31
|
+
*/
|
|
32
|
+
function resolveThemePath(config) {
|
|
33
|
+
const themeName = config.theme || "archie";
|
|
34
|
+
const userThemePath = path.resolve(config.themesDir || "./themes", themeName);
|
|
35
|
+
const builtinThemePath = path.resolve(__dirname, "../../themes", themeName);
|
|
36
|
+
|
|
37
|
+
if (fs.existsSync(userThemePath)) {
|
|
38
|
+
return userThemePath;
|
|
39
|
+
}
|
|
40
|
+
if (fs.existsSync(builtinThemePath)) {
|
|
41
|
+
return builtinThemePath;
|
|
42
|
+
}
|
|
43
|
+
console.error(`Theme "${themeName}" not found in ${userThemePath} or ${builtinThemePath}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Load configuration from the user's fossbook.config.js (or a custom path),
|
|
49
|
+
* merge with defaults, and build the internal dev paths.
|
|
50
|
+
*/
|
|
51
|
+
function loadConfig(configPath) {
|
|
52
|
+
const resolvedConfigPath = path.resolve(configPath || "./fossbook.config.js");
|
|
53
|
+
|
|
54
|
+
let userConfig = {};
|
|
55
|
+
if (fs.existsSync(resolvedConfigPath)) {
|
|
56
|
+
userConfig = require(resolvedConfigPath);
|
|
57
|
+
} else if (configPath) {
|
|
58
|
+
// Only error if user explicitly specified a config path
|
|
59
|
+
console.error(`Config file not found: ${resolvedConfigPath}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
} else {
|
|
62
|
+
console.warn("No fossbook.config.js found, using defaults.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const merged = { ...defaults, ...userConfig };
|
|
66
|
+
|
|
67
|
+
// Build the internal dev paths object used by the generator modules
|
|
68
|
+
const themePath = resolveThemePath(merged);
|
|
69
|
+
|
|
70
|
+
merged.dev = {
|
|
71
|
+
postsdir: path.resolve(merged.postsDir),
|
|
72
|
+
content: path.resolve(merged.content),
|
|
73
|
+
about: path.resolve(merged.content, "about.md"),
|
|
74
|
+
outdir: path.resolve(merged.outputDir),
|
|
75
|
+
themePath: path.dirname(themePath), // e.g. /path/to/themes
|
|
76
|
+
staticDir: path.resolve(merged.staticDir),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
merged.themePath = themePath; // full path to the active theme directory
|
|
80
|
+
|
|
81
|
+
// Inject version and build date
|
|
82
|
+
merged.version = require("../../package.json").version;
|
|
83
|
+
merged.date_time = formatDate(new Date());
|
|
84
|
+
|
|
85
|
+
// Auto-detect basePath if not explicitly set
|
|
86
|
+
if (!merged.basePath) {
|
|
87
|
+
merged.basePath = detectBasePath(merged);
|
|
88
|
+
}
|
|
89
|
+
// Ensure basePath has trailing slash
|
|
90
|
+
if (!merged.basePath.endsWith("/")) {
|
|
91
|
+
merged.basePath += "/";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return merged;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function formatDate(
|
|
98
|
+
date,
|
|
99
|
+
locale = "en-US",
|
|
100
|
+
options = {
|
|
101
|
+
year: "numeric",
|
|
102
|
+
month: "short",
|
|
103
|
+
day: "numeric",
|
|
104
|
+
},
|
|
105
|
+
) {
|
|
106
|
+
return date.toLocaleDateString(locale, options);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Auto-detect basePath for GitHub Pages project sites.
|
|
111
|
+
* Returns "/repo-name/" for project sites, or "/" for user sites / custom domains.
|
|
112
|
+
*/
|
|
113
|
+
function detectBasePath(config) {
|
|
114
|
+
// If user has a custom domain, no prefix needed
|
|
115
|
+
if (config.githubCNAME) return "/";
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const remoteUrl = execSync("git remote get-url origin", {
|
|
119
|
+
encoding: "utf-8",
|
|
120
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
121
|
+
}).trim();
|
|
122
|
+
const match = remoteUrl.match(/github\.com[:/](.+?)(?:\.git)?$/);
|
|
123
|
+
if (match) {
|
|
124
|
+
const [owner, repo] = match[1].split("/");
|
|
125
|
+
// User/org sites (e.g. username.github.io) are served at root
|
|
126
|
+
if (repo === `${owner}.github.io`) return "/";
|
|
127
|
+
// Project sites need the repo name as prefix
|
|
128
|
+
return `/${repo}/`;
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
// Not a git repo or no remote — fall back to root
|
|
132
|
+
}
|
|
133
|
+
return "/";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = { loadConfig, resolveThemePath, formatDate };
|
package/lib/mod/marked.js
CHANGED
|
@@ -1,95 +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;
|
|
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;
|