@ayushshanker/mdo 0.2.0 → 0.4.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 +3 -3
- package/lib/browser.js +42 -0
- package/lib/cli.js +31 -3
- package/lib/constants.js +2 -2
- package/lib/file-mode.js +8 -4
- package/lib/folder-mode.js +15 -16
- package/lib/renderer.js +47 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# mdo
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Contains `mdo` CLI for previewing Markdown files and folders in your browser.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -89,7 +89,7 @@ mdo docs --port 3000
|
|
|
89
89
|
## Flags
|
|
90
90
|
|
|
91
91
|
- `--dark` uses the GitHub dark theme
|
|
92
|
-
- `--theme <name>` selects `github`, `github-dark`, `
|
|
92
|
+
- `--theme <name>` selects `github-light`, `github-dark`, `belafonte-day`, `belafonte-night`, `earth`, `earthsong`, or `dracula`
|
|
93
93
|
- `--output <file>` writes HTML to disk instead of opening the browser; file mode only
|
|
94
94
|
- `--port <port>` uses a fixed port for folder mode
|
|
95
95
|
- `--help` prints usage information
|
package/lib/browser.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { spawn } = require("node:child_process");
|
|
4
|
+
|
|
5
|
+
function getBrowserCommand(target) {
|
|
6
|
+
switch (process.platform) {
|
|
7
|
+
case "darwin":
|
|
8
|
+
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(target)) {
|
|
9
|
+
return { command: "/usr/bin/open", args: ["-u", target] };
|
|
10
|
+
}
|
|
11
|
+
return { command: "/usr/bin/open", args: [target] };
|
|
12
|
+
case "win32":
|
|
13
|
+
return {
|
|
14
|
+
command: "cmd",
|
|
15
|
+
args: ["/c", "start", "", target]
|
|
16
|
+
};
|
|
17
|
+
default:
|
|
18
|
+
return { command: "xdg-open", args: [target] };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function openInBrowser(target) {
|
|
23
|
+
const { command, args } = getBrowserCommand(target);
|
|
24
|
+
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const child = spawn(command, args, {
|
|
27
|
+
detached: true,
|
|
28
|
+
stdio: "ignore"
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
child.once("error", reject);
|
|
32
|
+
child.once("spawn", () => {
|
|
33
|
+
child.unref();
|
|
34
|
+
resolve();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
getBrowserCommand,
|
|
41
|
+
openInBrowser
|
|
42
|
+
};
|
package/lib/cli.js
CHANGED
|
@@ -12,15 +12,38 @@ const {
|
|
|
12
12
|
validatePort
|
|
13
13
|
} = require("./utils");
|
|
14
14
|
|
|
15
|
+
function themeNames() {
|
|
16
|
+
return Object.keys(THEMES);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function hasBareThemeFlag(argv) {
|
|
20
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
21
|
+
if (argv[index] !== "--theme") {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const next = argv[index + 1];
|
|
26
|
+
if (!next || next.startsWith("-")) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function printThemeList() {
|
|
35
|
+
process.stdout.write(`Available themes:\n${themeNames().map((name) => `- ${name}`).join("\n")}\n`);
|
|
36
|
+
}
|
|
37
|
+
|
|
15
38
|
function resolveThemeOption(options) {
|
|
16
39
|
if (options.theme) {
|
|
17
40
|
if (!Object.hasOwn(THEMES, options.theme)) {
|
|
18
|
-
throw new Error(`Theme must be one of: ${
|
|
41
|
+
throw new Error(`Theme must be one of: ${themeNames().join(", ")}.`);
|
|
19
42
|
}
|
|
20
43
|
return options.theme;
|
|
21
44
|
}
|
|
22
45
|
|
|
23
|
-
return options.dark ? "github-dark" : "github";
|
|
46
|
+
return options.dark ? "github-dark" : "github-light";
|
|
24
47
|
}
|
|
25
48
|
|
|
26
49
|
function buildProgram() {
|
|
@@ -34,7 +57,7 @@ function buildProgram() {
|
|
|
34
57
|
.option("--dark", "Use GitHub dark theme.")
|
|
35
58
|
.option(
|
|
36
59
|
"--theme <name>",
|
|
37
|
-
`Use a named theme: ${
|
|
60
|
+
`Use a named theme: ${themeNames().join(", ")}.`
|
|
38
61
|
)
|
|
39
62
|
.option("--output <file>", "Write HTML to a file instead of opening the browser.")
|
|
40
63
|
.option("--port <port>", "Use a fixed port for folder mode.", validatePort)
|
|
@@ -56,6 +79,11 @@ Examples:
|
|
|
56
79
|
}
|
|
57
80
|
|
|
58
81
|
async function runCli(argv = process.argv) {
|
|
82
|
+
if (hasBareThemeFlag(argv.slice(2))) {
|
|
83
|
+
printThemeList();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
59
87
|
const program = buildProgram();
|
|
60
88
|
program.exitOverride();
|
|
61
89
|
|
package/lib/constants.js
CHANGED
|
@@ -11,7 +11,7 @@ const MERMAID_VERSION = "11.12.0";
|
|
|
11
11
|
const MERMAID_CDN_URL = `https://cdn.jsdelivr.net/npm/mermaid@${MERMAID_VERSION}/dist/mermaid.min.js`;
|
|
12
12
|
const LIGHT_HLJS_THEME = "github.css";
|
|
13
13
|
const DARK_HLJS_THEME = "github-dark.css";
|
|
14
|
-
const
|
|
14
|
+
const GITHUB_MARKDOWN_LIGHT_CSS = "github-markdown-light.css";
|
|
15
15
|
const GITHUB_MARKDOWN_DARK_CSS = "github-markdown-dark.css";
|
|
16
16
|
const DEFAULT_ARTICLE_TITLE = "Markdown Preview";
|
|
17
17
|
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
@@ -19,7 +19,7 @@ const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
|
19
19
|
module.exports = {
|
|
20
20
|
DARK_HLJS_THEME,
|
|
21
21
|
DEFAULT_ARTICLE_TITLE,
|
|
22
|
-
|
|
22
|
+
GITHUB_MARKDOWN_LIGHT_CSS,
|
|
23
23
|
GITHUB_MARKDOWN_DARK_CSS,
|
|
24
24
|
LIGHT_HLJS_THEME,
|
|
25
25
|
LOOPBACK_HOST,
|
package/lib/file-mode.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require("node:fs/promises");
|
|
4
4
|
const path = require("node:path");
|
|
5
|
-
const
|
|
5
|
+
const { openInBrowser } = require("./browser");
|
|
6
6
|
const { renderMarkdownFile } = require("./renderer");
|
|
7
7
|
const {
|
|
8
8
|
humanUrlPath,
|
|
@@ -26,13 +26,17 @@ async function runFileMode({ filePath, outputPath, cwdRealPath, themeName }) {
|
|
|
26
26
|
await writeUtf8File(htmlPath, html);
|
|
27
27
|
|
|
28
28
|
try {
|
|
29
|
-
await
|
|
29
|
+
await openInBrowser(htmlPath);
|
|
30
30
|
} catch (error) {
|
|
31
31
|
process.stderr.write(`Browser open failed. File available at ${htmlPath}\n`);
|
|
32
|
-
|
|
32
|
+
return {
|
|
33
|
+
browserOpened: false,
|
|
34
|
+
outputPath: htmlPath,
|
|
35
|
+
visiblePath: humanUrlPath(cwdRealPath, filePath)
|
|
36
|
+
};
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
return { outputPath: htmlPath,
|
|
39
|
+
return { browserOpened: true, outputPath: htmlPath, visiblePath: humanUrlPath(cwdRealPath, filePath) };
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
module.exports = {
|
package/lib/folder-mode.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
const http = require("node:http");
|
|
4
4
|
const fs = require("node:fs/promises");
|
|
5
5
|
const path = require("node:path");
|
|
6
|
-
const
|
|
6
|
+
const { openInBrowser } = require("./browser");
|
|
7
7
|
const { LOOPBACK_HOST, MAX_PORT, MIN_PORT, PORT_RETRIES } = require("./constants");
|
|
8
8
|
const { renderDirectoryListing, renderMarkdownFile } = require("./renderer");
|
|
9
|
-
const { isMarkdownPath
|
|
9
|
+
const { isMarkdownPath } = require("./utils");
|
|
10
10
|
|
|
11
11
|
async function resolveInsideRoot(rootRealPath, requestPath) {
|
|
12
12
|
const normalized = decodeURIComponent(requestPath || "/");
|
|
@@ -145,19 +145,18 @@ function installShutdownHandlers(server) {
|
|
|
145
145
|
return;
|
|
146
146
|
}
|
|
147
147
|
shuttingDown = true;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
});
|
|
148
|
+
server.close();
|
|
149
|
+
if (signal === "SIGINT") {
|
|
150
|
+
process.exit(130);
|
|
151
|
+
}
|
|
152
|
+
if (signal === "SIGTERM") {
|
|
153
|
+
process.exit(143);
|
|
154
|
+
}
|
|
155
|
+
process.exit(0);
|
|
157
156
|
};
|
|
158
157
|
|
|
159
|
-
process.
|
|
160
|
-
process.
|
|
158
|
+
process.once("SIGINT", shutdown);
|
|
159
|
+
process.once("SIGTERM", shutdown);
|
|
161
160
|
}
|
|
162
161
|
|
|
163
162
|
async function runFolderMode({ directoryPath, fixedPort, themeName }) {
|
|
@@ -215,13 +214,13 @@ async function runFolderMode({ directoryPath, fixedPort, themeName }) {
|
|
|
215
214
|
|
|
216
215
|
const url = `http://${LOOPBACK_HOST}:${port}/`;
|
|
217
216
|
try {
|
|
218
|
-
await
|
|
217
|
+
await openInBrowser(url);
|
|
219
218
|
} catch {
|
|
220
219
|
process.stderr.write(`Browser open failed. Server available at ${url}\n`);
|
|
221
|
-
|
|
220
|
+
return { browserOpened: false, port, rootRealPath, url, server };
|
|
222
221
|
}
|
|
223
222
|
|
|
224
|
-
return { port, rootRealPath, url, server };
|
|
223
|
+
return { browserOpened: true, port, rootRealPath, url, server };
|
|
225
224
|
}
|
|
226
225
|
|
|
227
226
|
module.exports = {
|
package/lib/renderer.js
CHANGED
|
@@ -10,7 +10,7 @@ const hljs = require("highlight.js");
|
|
|
10
10
|
const {
|
|
11
11
|
DARK_HLJS_THEME,
|
|
12
12
|
DEFAULT_ARTICLE_TITLE,
|
|
13
|
-
|
|
13
|
+
GITHUB_MARKDOWN_LIGHT_CSS,
|
|
14
14
|
GITHUB_MARKDOWN_DARK_CSS,
|
|
15
15
|
LIGHT_HLJS_THEME,
|
|
16
16
|
MERMAID_CDN_URL,
|
|
@@ -26,7 +26,7 @@ function loadCss(relativePath) {
|
|
|
26
26
|
return fs.readFileSync(cssPath, "utf8");
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const markdownCssLight = loadCss(`github-markdown-css/${
|
|
29
|
+
const markdownCssLight = loadCss(`github-markdown-css/${GITHUB_MARKDOWN_LIGHT_CSS}`);
|
|
30
30
|
const markdownCssDark = loadCss(`github-markdown-css/${GITHUB_MARKDOWN_DARK_CSS}`);
|
|
31
31
|
const hljsCssLight = loadCss(`highlight.js/styles/${LIGHT_HLJS_THEME}`);
|
|
32
32
|
const hljsCssDark = loadCss(`highlight.js/styles/${DARK_HLJS_THEME}`);
|
|
@@ -56,7 +56,7 @@ function createMarkdownRenderer() {
|
|
|
56
56
|
const renderer = createMarkdownRenderer();
|
|
57
57
|
|
|
58
58
|
const THEMES = {
|
|
59
|
-
github: {
|
|
59
|
+
"github-light": {
|
|
60
60
|
accent: "#0969da",
|
|
61
61
|
background: "#ffffff",
|
|
62
62
|
color: "#1f2328",
|
|
@@ -78,28 +78,59 @@ const THEMES = {
|
|
|
78
78
|
navFontColor: "#8b949e",
|
|
79
79
|
overlayCss: ""
|
|
80
80
|
},
|
|
81
|
-
|
|
82
|
-
accent: "#
|
|
83
|
-
background: "#
|
|
84
|
-
color: "#
|
|
81
|
+
"belafonte-day": {
|
|
82
|
+
accent: "#8f5a2b",
|
|
83
|
+
background: "#f6f1e7",
|
|
84
|
+
color: "#3d3126",
|
|
85
85
|
darkMode: false,
|
|
86
86
|
hljsCss: hljsCssLight,
|
|
87
87
|
markdownCss: markdownCssLight,
|
|
88
88
|
mermaidTheme: "neutral",
|
|
89
|
-
navFontColor: "#
|
|
89
|
+
navFontColor: "#6f5a46",
|
|
90
90
|
overlayCss: `
|
|
91
|
-
body { background-image: linear-gradient(
|
|
91
|
+
body { background-image: linear-gradient(135deg, rgba(143, 90, 43, 0.08), rgba(191, 143, 84, 0.05)); }
|
|
92
92
|
.markdown-body {
|
|
93
|
-
color: #
|
|
93
|
+
color: #3d3126;
|
|
94
94
|
}
|
|
95
95
|
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
|
|
96
|
-
color: #
|
|
96
|
+
color: #2b2219;
|
|
97
97
|
}
|
|
98
98
|
.markdown-body pre, .markdown-body code, .markdown-body blockquote {
|
|
99
|
-
background-color: rgba(
|
|
99
|
+
background-color: rgba(120, 90, 59, 0.09);
|
|
100
100
|
}
|
|
101
101
|
.markdown-body table tr {
|
|
102
|
-
background-color: rgba(255,
|
|
102
|
+
background-color: rgba(255, 248, 238, 0.5);
|
|
103
|
+
}
|
|
104
|
+
.markdown-body a, .mdo-nav a {
|
|
105
|
+
color: #8f5a2b;
|
|
106
|
+
}
|
|
107
|
+
`
|
|
108
|
+
},
|
|
109
|
+
"belafonte-night": {
|
|
110
|
+
accent: "#d7995b",
|
|
111
|
+
background: "#20111b",
|
|
112
|
+
color: "#f2e7d5",
|
|
113
|
+
darkMode: true,
|
|
114
|
+
hljsCss: hljsCssDark,
|
|
115
|
+
markdownCss: markdownCssDark,
|
|
116
|
+
mermaidTheme: "dark",
|
|
117
|
+
navFontColor: "#ccb9a4",
|
|
118
|
+
overlayCss: `
|
|
119
|
+
body { background-image: radial-gradient(circle at top, rgba(215, 153, 91, 0.2), rgba(32, 17, 27, 0) 42%); }
|
|
120
|
+
.markdown-body {
|
|
121
|
+
color: #f2e7d5;
|
|
122
|
+
}
|
|
123
|
+
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
|
|
124
|
+
color: #ffd7a8;
|
|
125
|
+
}
|
|
126
|
+
.markdown-body pre, .markdown-body code, .markdown-body blockquote {
|
|
127
|
+
background-color: rgba(75, 49, 44, 0.94);
|
|
128
|
+
}
|
|
129
|
+
.markdown-body table tr {
|
|
130
|
+
background-color: rgba(92, 62, 54, 0.45);
|
|
131
|
+
}
|
|
132
|
+
.markdown-body a, .mdo-nav a {
|
|
133
|
+
color: #d7995b;
|
|
103
134
|
}
|
|
104
135
|
`
|
|
105
136
|
},
|
|
@@ -157,34 +188,6 @@ body { background-image: radial-gradient(circle at top, rgba(185, 119, 50, 0.2),
|
|
|
157
188
|
.markdown-body table tr {
|
|
158
189
|
background-color: rgba(90, 66, 49, 0.48);
|
|
159
190
|
}
|
|
160
|
-
`
|
|
161
|
-
},
|
|
162
|
-
gruvbox: {
|
|
163
|
-
accent: "#d79921",
|
|
164
|
-
background: "#282828",
|
|
165
|
-
color: "#ebdbb2",
|
|
166
|
-
darkMode: true,
|
|
167
|
-
hljsCss: hljsCssDark,
|
|
168
|
-
markdownCss: markdownCssDark,
|
|
169
|
-
mermaidTheme: "dark",
|
|
170
|
-
navFontColor: "#d5c4a1",
|
|
171
|
-
overlayCss: `
|
|
172
|
-
body { background-image: linear-gradient(180deg, rgba(215, 153, 33, 0.08), rgba(40, 40, 40, 0)); }
|
|
173
|
-
.markdown-body {
|
|
174
|
-
color: #ebdbb2;
|
|
175
|
-
}
|
|
176
|
-
.markdown-body a, .mdo-nav a {
|
|
177
|
-
color: #d79921;
|
|
178
|
-
}
|
|
179
|
-
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
|
|
180
|
-
color: #fabd2f;
|
|
181
|
-
}
|
|
182
|
-
.markdown-body pre, .markdown-body code, .markdown-body blockquote {
|
|
183
|
-
background-color: rgba(60, 56, 54, 0.95);
|
|
184
|
-
}
|
|
185
|
-
.markdown-body table tr {
|
|
186
|
-
background-color: rgba(80, 73, 69, 0.45);
|
|
187
|
-
}
|
|
188
191
|
`
|
|
189
192
|
},
|
|
190
193
|
dracula: {
|
|
@@ -215,7 +218,7 @@ body { background-image: radial-gradient(circle at top, rgba(189, 147, 249, 0.18
|
|
|
215
218
|
};
|
|
216
219
|
|
|
217
220
|
function resolveTheme(themeName) {
|
|
218
|
-
return THEMES[themeName] || THEMES
|
|
221
|
+
return THEMES[themeName] || THEMES["github-light"];
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
function buildStyles(themeName) {
|
|
@@ -306,7 +309,7 @@ async function renderMarkdownFile(filePath, options = {}) {
|
|
|
306
309
|
return {
|
|
307
310
|
html: buildDocument({
|
|
308
311
|
bodyHtml,
|
|
309
|
-
themeName: options.themeName || "github",
|
|
312
|
+
themeName: options.themeName || "github-light",
|
|
310
313
|
title,
|
|
311
314
|
backHref: options.backHref,
|
|
312
315
|
backLabel: options.backLabel,
|
|
@@ -336,7 +339,7 @@ function renderDirectoryListing({ directoryName, entries, themeName, requestPath
|
|
|
336
339
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
337
340
|
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline';">
|
|
338
341
|
<title>${escapeHtml(heading)}</title>
|
|
339
|
-
<style>${buildStyles(themeName || "github")}</style>
|
|
342
|
+
<style>${buildStyles(themeName || "github-light")}</style>
|
|
340
343
|
</head>
|
|
341
344
|
<body>
|
|
342
345
|
${backHref}
|