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 CHANGED
@@ -1,3 +1,5 @@
1
+ # markform 📝
2
+
1
3
  ![Markdown Banner](images/Markform%20|%20Banner.png)
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 windows), use:
28
+ If it doesn't work (mainly on Windows), use:
27
29
  ```bash
28
- npx markform-cli ./test -o ./output
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
- | `--theme <name>` | `default` | Theme to use |
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.1",
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 content = fs.readFileSync(filePath, 'utf-8');
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}}', path.basename(filePath, '.md'))
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.0.0')
10
- .argument('<input>', 'folder of Markdown files')
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
- buildSite(input, options);
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
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Post
3
+ date: 2026-02-27
4
+ description:
5
+ ---
6
+
7
+ # Post
8
+
9
+ Write something here...
@@ -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
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
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 { display: none; }
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 { color: var(--text); }
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 { border-color: #444; }
81
- #search::placeholder { color: var(--muted); }
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 { list-style: none; }
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 { list-style: none; }
110
- #search-results li { padding: 0.6rem 0.5rem; border-bottom: 1px solid var(--border); }
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
- #search-results a:hover { color: #fff; }
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 { font-size: 1.75rem; font-weight: 700; margin-bottom: 0.75rem; line-height: 1.2; }
143
- h2 { font-size: 1.2rem; font-weight: 600; margin: 2.25rem 0 0.5rem; }
144
- h3 { font-size: 1rem; font-weight: 600; margin: 1.5rem 0 0.4rem; }
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 { margin-bottom: 1rem; color: #aaa; }
191
+ p {
192
+ margin-bottom: 1rem;
193
+ color: #aaa;
194
+ }
147
195
 
148
- a { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }
149
- a:hover { color: #fff; }
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 { background: none; color: #ccc; padding: 0; border: none; font-size: inherit; }
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 { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }
181
- img { max-width: 100%; margin: 1rem 0; }
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
- table { width: 100%; border-collapse: collapse; margin-bottom: 1.25rem; font-size: 0.875rem; }
184
- th, td { padding: 0.6rem 0.875rem; border: 1px solid var(--border); text-align: left; }
185
- th { background: var(--surface); font-weight: 600; color: var(--text); }
186
- td { color: #aaa; }
276
+ ul,
277
+ ol {
278
+ padding-left: 1.5rem;
279
+ margin-bottom: 1rem;
280
+ color: #aaa;
281
+ }
187
282
 
188
- ul, ol { padding-left: 1.5rem; margin-bottom: 1rem; color: #aaa; }
189
- li { margin-bottom: 0.25rem; }
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 { background: var(--surface); }
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 { background: #2a2a2a; color: #fff; padding: 0 2px; }
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
- // Hide sidebar on index page
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>