markform-cli 1.2.1 → 1.2.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.
- package/README.md +23 -3
- package/package.json +2 -1
- package/src/build.js +21 -6
- package/src/index-page.js +2 -1
- package/src/index.js +12 -4
- package/src/new.js +35 -0
- package/test/post.md +9 -0
- package/themes/default.html +152 -29
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# markform 📝
|
|
2
|
+
|
|
1
3
|

|
|
2
4
|
|
|
3
5
|
Turn any folder of Markdown files into a beautiful, searchable static site — in one command.
|
|
@@ -23,9 +25,9 @@ npm install -g markform-cli
|
|
|
23
25
|
markform ./my-notes -o ./output
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
If it doesn't work (mainly on
|
|
28
|
+
If it doesn't work (mainly on Windows), use:
|
|
27
29
|
```bash
|
|
28
|
-
npx markform-cli ./
|
|
30
|
+
npx markform-cli ./my-notes -o ./output
|
|
29
31
|
```
|
|
30
32
|
|
|
31
33
|
**Watch mode** — auto-rebuilds and live-reloads the browser on file changes:
|
|
@@ -33,6 +35,24 @@ npx markform-cli ./test -o ./output
|
|
|
33
35
|
markform ./my-notes -o ./output --watch
|
|
34
36
|
```
|
|
35
37
|
|
|
38
|
+
**Create a new page:**
|
|
39
|
+
```bash
|
|
40
|
+
markform --new my-page
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This scaffolds a new Markdown file with frontmatter ready to fill in:
|
|
44
|
+
```markdown
|
|
45
|
+
---
|
|
46
|
+
title: My Page
|
|
47
|
+
date: 2026-02-27
|
|
48
|
+
description:
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
# My Page
|
|
52
|
+
|
|
53
|
+
Write something here...
|
|
54
|
+
```
|
|
55
|
+
|
|
36
56
|
---
|
|
37
57
|
|
|
38
58
|
## (≖_≖ ) Options
|
|
@@ -41,7 +61,7 @@ markform ./my-notes -o ./output --watch
|
|
|
41
61
|
|------|---------|-------------|
|
|
42
62
|
| `-o, --output <dir>` | `./output` | Where to write the site |
|
|
43
63
|
| `-w, --watch` | off | Watch for changes and live reload |
|
|
44
|
-
|
|
|
64
|
+
| `-n, --new <name>` | — | Scaffold a new Markdown file |
|
|
45
65
|
|
|
46
66
|
---
|
|
47
67
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "markform-cli",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Turn any folder of Markdown files into a beautiful, searchable static site.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"express": "^5.2.1",
|
|
29
29
|
"fs-extra": "^11.3.3",
|
|
30
30
|
"fuse.js": "^7.1.0",
|
|
31
|
+
"gray-matter": "^4.0.3",
|
|
31
32
|
"marked": "^17.0.3",
|
|
32
33
|
"open": "^11.0.0"
|
|
33
34
|
}
|
package/src/build.js
CHANGED
|
@@ -6,6 +6,7 @@ const { buildIndexPage } = require('./index-page');
|
|
|
6
6
|
const chokidar = require('chokidar');
|
|
7
7
|
const express = require('express');
|
|
8
8
|
const { exec } = require('child_process');
|
|
9
|
+
const matter = require('gray-matter');
|
|
9
10
|
|
|
10
11
|
function openInBrowser(url) {
|
|
11
12
|
const cmd = process.platform === 'win32'
|
|
@@ -72,15 +73,28 @@ async function compile(inputDir, outputDir, watch = false) {
|
|
|
72
73
|
);
|
|
73
74
|
|
|
74
75
|
for (const filePath of mdFiles) {
|
|
75
|
-
const
|
|
76
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
77
|
+
const { content, data: frontmatter } = matter(raw);
|
|
76
78
|
const htmlContent = marked(content);
|
|
77
79
|
const relativePath = path.relative(inputDir, filePath);
|
|
78
80
|
const outputPath = path.join(outputDir, relativePath.replace(/\.md$/, '.html'));
|
|
79
81
|
|
|
82
|
+
const title = frontmatter.title || path.basename(filePath, '.md').replace(/-/g, ' ');
|
|
83
|
+
const description = frontmatter.description || '';
|
|
84
|
+
const date = frontmatter.date || '';
|
|
85
|
+
|
|
86
|
+
let metaHtml = '';
|
|
87
|
+
if (date || description) {
|
|
88
|
+
metaHtml = `<div class="page-meta">
|
|
89
|
+
${date ? `<span class="page-date">${date}</span>` : ''}
|
|
90
|
+
${description ? `<p class="page-description">${description}</p>` : ''}
|
|
91
|
+
</div>`;
|
|
92
|
+
}
|
|
93
|
+
|
|
80
94
|
let html = template
|
|
81
|
-
.replace('{{content}}', htmlContent)
|
|
95
|
+
.replace('{{content}}', metaHtml + htmlContent)
|
|
82
96
|
.replace('{{nav}}', nav)
|
|
83
|
-
.replace('{{title}}',
|
|
97
|
+
.replace('{{title}}', title)
|
|
84
98
|
.replace('{{search_index}}', searchIndexJson);
|
|
85
99
|
|
|
86
100
|
if (watch) {
|
|
@@ -120,7 +134,8 @@ function getMdFiles(dir) {
|
|
|
120
134
|
function buildSearchIndex(mdFiles, inputDir) {
|
|
121
135
|
return mdFiles.map((filePath) => {
|
|
122
136
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
123
|
-
const content = raw
|
|
137
|
+
const { content, data: frontmatter } = matter(raw);
|
|
138
|
+
const cleaned = content
|
|
124
139
|
.replace(/#{1,6}\s/g, '')
|
|
125
140
|
.replace(/\*\*|__|\*|_/g, '')
|
|
126
141
|
.replace(/`{1,3}[^`]*`{1,3}/g, '')
|
|
@@ -131,8 +146,8 @@ function buildSearchIndex(mdFiles, inputDir) {
|
|
|
131
146
|
|
|
132
147
|
const relativePath = path.relative(inputDir, filePath);
|
|
133
148
|
const href = relativePath.replace(/\.md$/, '.html');
|
|
134
|
-
const title = path.basename(filePath, '.md').replace(/-/g, ' ');
|
|
135
|
-
return { title, href, content };
|
|
149
|
+
const title = frontmatter.title || path.basename(filePath, '.md').replace(/-/g, ' ');
|
|
150
|
+
return { title, href, content: cleaned };
|
|
136
151
|
});
|
|
137
152
|
}
|
|
138
153
|
|
package/src/index-page.js
CHANGED
|
@@ -26,7 +26,8 @@ function buildIndexPage(mdFiles, inputDir, template, searchIndexJson) {
|
|
|
26
26
|
.replace('{{content}}', content)
|
|
27
27
|
.replace('{{nav}}', '')
|
|
28
28
|
.replace('{{title}}', 'Home')
|
|
29
|
-
.replace('{{search_index}}', searchIndexJson)
|
|
29
|
+
.replace('{{search_index}}', searchIndexJson)
|
|
30
|
+
.replace('<body>', '<body data-page="index">');
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
module.exports = { buildIndexPage };
|
package/src/index.js
CHANGED
|
@@ -2,16 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
const { program } = require('commander');
|
|
4
4
|
const { buildSite } = require('./build');
|
|
5
|
+
const { createNewPage } = require('./new');
|
|
5
6
|
|
|
6
7
|
program
|
|
7
8
|
.name('markform')
|
|
8
|
-
.description('Turn any folder of Markdown files into a beautiful, searchable static site
|
|
9
|
-
.version('1.
|
|
10
|
-
.argument('
|
|
9
|
+
.description('Turn any folder of Markdown files into a beautiful, searchable static site')
|
|
10
|
+
.version('1.2.2')
|
|
11
|
+
.argument('[input]', 'folder of Markdown files')
|
|
11
12
|
.option('-o, --output <dir>', 'output directory', './output')
|
|
12
13
|
.option('-w, --watch', 'watch for changes and rebuild')
|
|
14
|
+
.option('-n, --new <name>', 'create a new markdown file')
|
|
13
15
|
.action((input, options) => {
|
|
14
|
-
|
|
16
|
+
if (options.new) {
|
|
17
|
+
createNewPage(options.new);
|
|
18
|
+
} else if (input) {
|
|
19
|
+
buildSite(input, options);
|
|
20
|
+
} else {
|
|
21
|
+
program.help();
|
|
22
|
+
}
|
|
15
23
|
});
|
|
16
24
|
|
|
17
25
|
program.parse();
|
package/src/new.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function createNewPage(name) {
|
|
5
|
+
const filename = name.endsWith('.md') ? name : `${name}.md`;
|
|
6
|
+
const filepath = path.resolve(filename);
|
|
7
|
+
|
|
8
|
+
if (fs.existsSync(filepath)) {
|
|
9
|
+
console.error(`(˙◠˙ ) File already exists: ${filepath}`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const title = name
|
|
14
|
+
.replace(/-/g, ' ')
|
|
15
|
+
.replace(/\.md$/, '')
|
|
16
|
+
.replace(/\b\w/g, c => c.toUpperCase());
|
|
17
|
+
|
|
18
|
+
const date = new Date().toISOString().split('T')[0];
|
|
19
|
+
|
|
20
|
+
const content = `---
|
|
21
|
+
title: ${title}
|
|
22
|
+
date: ${date}
|
|
23
|
+
description:
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# ${title}
|
|
27
|
+
|
|
28
|
+
Write something here...
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
fs.writeFileSync(filepath, content);
|
|
32
|
+
console.log(`(ˆᗜˆ ) Created ${filepath}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { createNewPage };
|
package/test/post.md
ADDED
package/themes/default.html
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
|
+
|
|
3
4
|
<head>
|
|
4
5
|
<meta charset="UTF-8" />
|
|
5
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
7
|
<title>{{title}}</title>
|
|
7
8
|
<style>
|
|
8
|
-
*,
|
|
9
|
+
*,
|
|
10
|
+
*::before,
|
|
11
|
+
*::after {
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding: 0;
|
|
15
|
+
}
|
|
9
16
|
|
|
10
17
|
:root {
|
|
11
18
|
--bg: #0e0e0e;
|
|
@@ -43,7 +50,9 @@
|
|
|
43
50
|
gap: 0.25rem;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
nav.hidden {
|
|
53
|
+
nav.hidden {
|
|
54
|
+
display: none;
|
|
55
|
+
}
|
|
47
56
|
|
|
48
57
|
.nav-logo {
|
|
49
58
|
font-size: 0.65rem;
|
|
@@ -57,7 +66,9 @@
|
|
|
57
66
|
display: block;
|
|
58
67
|
}
|
|
59
68
|
|
|
60
|
-
.nav-logo:hover {
|
|
69
|
+
.nav-logo:hover {
|
|
70
|
+
color: var(--text);
|
|
71
|
+
}
|
|
61
72
|
|
|
62
73
|
.nav-divider {
|
|
63
74
|
height: 1px;
|
|
@@ -77,8 +88,13 @@
|
|
|
77
88
|
margin-bottom: 0.5rem;
|
|
78
89
|
}
|
|
79
90
|
|
|
80
|
-
#search:focus {
|
|
81
|
-
|
|
91
|
+
#search:focus {
|
|
92
|
+
border-color: #444;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#search::placeholder {
|
|
96
|
+
color: var(--muted);
|
|
97
|
+
}
|
|
82
98
|
|
|
83
99
|
.nav-section-label {
|
|
84
100
|
font-size: 0.6rem;
|
|
@@ -90,7 +106,10 @@
|
|
|
90
106
|
margin: 0.75rem 0 0.35rem;
|
|
91
107
|
}
|
|
92
108
|
|
|
93
|
-
#nav-list {
|
|
109
|
+
#nav-list {
|
|
110
|
+
list-style: none;
|
|
111
|
+
}
|
|
112
|
+
|
|
94
113
|
#nav-list li a {
|
|
95
114
|
color: var(--muted-light);
|
|
96
115
|
text-decoration: none;
|
|
@@ -106,8 +125,15 @@
|
|
|
106
125
|
border-left-color: var(--muted);
|
|
107
126
|
}
|
|
108
127
|
|
|
109
|
-
#search-results {
|
|
110
|
-
|
|
128
|
+
#search-results {
|
|
129
|
+
list-style: none;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
#search-results li {
|
|
133
|
+
padding: 0.6rem 0.5rem;
|
|
134
|
+
border-bottom: 1px solid var(--border);
|
|
135
|
+
}
|
|
136
|
+
|
|
111
137
|
#search-results a {
|
|
112
138
|
font-size: 0.82rem;
|
|
113
139
|
font-weight: 600;
|
|
@@ -117,7 +143,11 @@
|
|
|
117
143
|
display: block;
|
|
118
144
|
margin-bottom: 0.15rem;
|
|
119
145
|
}
|
|
120
|
-
|
|
146
|
+
|
|
147
|
+
#search-results a:hover {
|
|
148
|
+
color: #fff;
|
|
149
|
+
}
|
|
150
|
+
|
|
121
151
|
#search-results p {
|
|
122
152
|
color: var(--muted-light);
|
|
123
153
|
font-size: 0.72rem;
|
|
@@ -139,14 +169,39 @@
|
|
|
139
169
|
}
|
|
140
170
|
|
|
141
171
|
/* ── TYPOGRAPHY ── */
|
|
142
|
-
h1 {
|
|
143
|
-
|
|
144
|
-
|
|
172
|
+
h1 {
|
|
173
|
+
font-size: 1.75rem;
|
|
174
|
+
font-weight: 700;
|
|
175
|
+
margin-bottom: 0.75rem;
|
|
176
|
+
line-height: 1.2;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
h2 {
|
|
180
|
+
font-size: 1.2rem;
|
|
181
|
+
font-weight: 600;
|
|
182
|
+
margin: 2.25rem 0 0.5rem;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
h3 {
|
|
186
|
+
font-size: 1rem;
|
|
187
|
+
font-weight: 600;
|
|
188
|
+
margin: 1.5rem 0 0.4rem;
|
|
189
|
+
}
|
|
145
190
|
|
|
146
|
-
p {
|
|
191
|
+
p {
|
|
192
|
+
margin-bottom: 1rem;
|
|
193
|
+
color: #aaa;
|
|
194
|
+
}
|
|
147
195
|
|
|
148
|
-
a {
|
|
149
|
-
|
|
196
|
+
a {
|
|
197
|
+
color: var(--text);
|
|
198
|
+
text-decoration: underline;
|
|
199
|
+
text-underline-offset: 3px;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
a:hover {
|
|
203
|
+
color: #fff;
|
|
204
|
+
}
|
|
150
205
|
|
|
151
206
|
code {
|
|
152
207
|
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
@@ -167,7 +222,13 @@
|
|
|
167
222
|
line-height: 1.7;
|
|
168
223
|
}
|
|
169
224
|
|
|
170
|
-
pre code {
|
|
225
|
+
pre code {
|
|
226
|
+
background: none;
|
|
227
|
+
color: #ccc;
|
|
228
|
+
padding: 0;
|
|
229
|
+
border: none;
|
|
230
|
+
font-size: inherit;
|
|
231
|
+
}
|
|
171
232
|
|
|
172
233
|
blockquote {
|
|
173
234
|
border-left: 2px solid var(--border);
|
|
@@ -177,16 +238,51 @@
|
|
|
177
238
|
font-style: italic;
|
|
178
239
|
}
|
|
179
240
|
|
|
180
|
-
hr {
|
|
181
|
-
|
|
241
|
+
hr {
|
|
242
|
+
border: none;
|
|
243
|
+
border-top: 1px solid var(--border);
|
|
244
|
+
margin: 2rem 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
img {
|
|
248
|
+
max-width: 100%;
|
|
249
|
+
margin: 1rem 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
table {
|
|
253
|
+
width: 100%;
|
|
254
|
+
border-collapse: collapse;
|
|
255
|
+
margin-bottom: 1.25rem;
|
|
256
|
+
font-size: 0.875rem;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
th,
|
|
260
|
+
td {
|
|
261
|
+
padding: 0.6rem 0.875rem;
|
|
262
|
+
border: 1px solid var(--border);
|
|
263
|
+
text-align: left;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
th {
|
|
267
|
+
background: var(--surface);
|
|
268
|
+
font-weight: 600;
|
|
269
|
+
color: var(--text);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
td {
|
|
273
|
+
color: #aaa;
|
|
274
|
+
}
|
|
182
275
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
276
|
+
ul,
|
|
277
|
+
ol {
|
|
278
|
+
padding-left: 1.5rem;
|
|
279
|
+
margin-bottom: 1rem;
|
|
280
|
+
color: #aaa;
|
|
281
|
+
}
|
|
187
282
|
|
|
188
|
-
|
|
189
|
-
|
|
283
|
+
li {
|
|
284
|
+
margin-bottom: 0.25rem;
|
|
285
|
+
}
|
|
190
286
|
|
|
191
287
|
/* ── INDEX PAGE ── */
|
|
192
288
|
.index-header {
|
|
@@ -223,7 +319,9 @@
|
|
|
223
319
|
transition: background 0.1s;
|
|
224
320
|
}
|
|
225
321
|
|
|
226
|
-
.card:hover {
|
|
322
|
+
.card:hover {
|
|
323
|
+
background: var(--surface);
|
|
324
|
+
}
|
|
227
325
|
|
|
228
326
|
.card-folder {
|
|
229
327
|
font-size: 0.6rem;
|
|
@@ -241,9 +339,34 @@
|
|
|
241
339
|
}
|
|
242
340
|
|
|
243
341
|
/* ── SEARCH HIGHLIGHT ── */
|
|
244
|
-
mark {
|
|
342
|
+
mark {
|
|
343
|
+
background: #2a2a2a;
|
|
344
|
+
color: #fff;
|
|
345
|
+
padding: 0 2px;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.page-meta {
|
|
349
|
+
margin-bottom: 2rem;
|
|
350
|
+
padding-bottom: 1.25rem;
|
|
351
|
+
border-bottom: 1px solid var(--border);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.page-date {
|
|
355
|
+
font-size: 0.75rem;
|
|
356
|
+
color: var(--muted-light);
|
|
357
|
+
letter-spacing: 0.05em;
|
|
358
|
+
display: block;
|
|
359
|
+
margin-bottom: 0.4rem;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.page-description {
|
|
363
|
+
color: var(--muted-light) !important;
|
|
364
|
+
font-size: 0.9rem;
|
|
365
|
+
margin: 0 !important;
|
|
366
|
+
}
|
|
245
367
|
</style>
|
|
246
368
|
</head>
|
|
369
|
+
|
|
247
370
|
<body>
|
|
248
371
|
<nav id="sidebar">
|
|
249
372
|
<a class="nav-logo" href="index.html">markform</a>
|
|
@@ -259,10 +382,9 @@
|
|
|
259
382
|
|
|
260
383
|
<script src="https://cdn.jsdelivr.net/npm/fuse.js@7/dist/fuse.min.js"></script>
|
|
261
384
|
<script>
|
|
262
|
-
const searchData = {{search_index}};
|
|
385
|
+
const searchData = {{ search_index }};
|
|
263
386
|
|
|
264
|
-
|
|
265
|
-
if (document.title === 'Home') {
|
|
387
|
+
if (document.body.dataset.page === 'index') {
|
|
266
388
|
document.getElementById('sidebar').classList.add('hidden');
|
|
267
389
|
document.body.classList.add('index-page');
|
|
268
390
|
}
|
|
@@ -325,4 +447,5 @@
|
|
|
325
447
|
}
|
|
326
448
|
</script>
|
|
327
449
|
</body>
|
|
450
|
+
|
|
328
451
|
</html>
|