markform-cli 1.0.1 → 1.2.1
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 +6 -1
- package/images/Markform | Devlog 3.png +0 -0
- package/package.json +4 -3
- package/src/build.js +39 -20
- package/src/index-page.js +13 -7
- package/test/hello.md +1 -1
- package/themes/default.html +321 -255
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-

|
|
2
2
|
|
|
3
3
|
Turn any folder of Markdown files into a beautiful, searchable static site — in one command.
|
|
4
4
|
```bash
|
|
@@ -23,6 +23,11 @@ npm install -g markform-cli
|
|
|
23
23
|
markform ./my-notes -o ./output
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
If it doesn't work (mainly on windows), use:
|
|
27
|
+
```bash
|
|
28
|
+
npx markform-cli ./test -o ./output
|
|
29
|
+
```
|
|
30
|
+
|
|
26
31
|
**Watch mode** — auto-rebuilds and live-reloads the browser on file changes:
|
|
27
32
|
```bash
|
|
28
33
|
markform ./my-notes -o ./output --watch
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "markform-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Turn any folder of Markdown files into a beautiful, searchable static site.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"author": "Aryan Madan",
|
|
19
19
|
"license": "GPL-3.0-only",
|
|
20
|
-
|
|
20
|
+
"homepage": "https://github.com/yourusername/markform",
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
23
|
"url": "https://github.com/yourusername/markform"
|
|
@@ -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
|
-
"marked": "^17.0.3"
|
|
31
|
+
"marked": "^17.0.3",
|
|
32
|
+
"open": "^11.0.0"
|
|
32
33
|
}
|
|
33
34
|
}
|
package/src/build.js
CHANGED
|
@@ -2,9 +2,19 @@ const fs = require('fs-extra');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { marked } = require('marked');
|
|
4
4
|
const { buildNav } = require('./nav');
|
|
5
|
+
const { buildIndexPage } = require('./index-page');
|
|
5
6
|
const chokidar = require('chokidar');
|
|
6
7
|
const express = require('express');
|
|
7
|
-
const {
|
|
8
|
+
const { exec } = require('child_process');
|
|
9
|
+
|
|
10
|
+
function openInBrowser(url) {
|
|
11
|
+
const cmd = process.platform === 'win32'
|
|
12
|
+
? `start "" "${url}"`
|
|
13
|
+
: process.platform === 'darwin'
|
|
14
|
+
? `open "${url}"`
|
|
15
|
+
: `xdg-open "${url}"`;
|
|
16
|
+
exec(cmd);
|
|
17
|
+
}
|
|
8
18
|
|
|
9
19
|
async function buildSite(input, options) {
|
|
10
20
|
const inputDir = path.resolve(input);
|
|
@@ -16,7 +26,7 @@ async function buildSite(input, options) {
|
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
console.log(`⛏ Building site from ${inputDir} to ${outputDir}...`);
|
|
19
|
-
await compile(inputDir, outputDir,
|
|
29
|
+
await compile(inputDir, outputDir, false);
|
|
20
30
|
console.log(`(ˆᗜˆ ) Site built to ${outputDir}`);
|
|
21
31
|
|
|
22
32
|
if (options.watch) {
|
|
@@ -30,19 +40,22 @@ async function buildSite(input, options) {
|
|
|
30
40
|
});
|
|
31
41
|
|
|
32
42
|
app.listen(3000, () => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
console.log(`(°ㅁ°) Serving at http://localhost:3000/index.html`);
|
|
43
|
+
console.log(`(°ㅁ° ) Serving at http://localhost:3000/index.html`);
|
|
44
|
+
openInBrowser('http://localhost:3000/index.html');
|
|
36
45
|
});
|
|
37
46
|
|
|
38
47
|
chokidar.watch(inputDir).on('change', async (filePath) => {
|
|
39
48
|
console.log(`↺ Changed: ${filePath}, rebuilding...`);
|
|
40
49
|
await compile(inputDir, outputDir, true);
|
|
41
50
|
shouldReload = true;
|
|
42
|
-
console.log(`(ˆᗜˆ ) Site rebuilt
|
|
51
|
+
console.log(`(ˆᗜˆ ) Site rebuilt.`);
|
|
43
52
|
});
|
|
44
53
|
|
|
45
54
|
console.log(`(≖_≖ ) Watching for changes...`);
|
|
55
|
+
} else {
|
|
56
|
+
const indexPath = path.join(outputDir, 'index.html');
|
|
57
|
+
console.log(`(°ㅁ° ) Opening ${indexPath}`);
|
|
58
|
+
openInBrowser(indexPath);
|
|
46
59
|
}
|
|
47
60
|
}
|
|
48
61
|
|
|
@@ -51,12 +64,13 @@ async function compile(inputDir, outputDir, watch = false) {
|
|
|
51
64
|
|
|
52
65
|
const mdFiles = getMdFiles(inputDir);
|
|
53
66
|
const nav = buildNav(mdFiles, inputDir);
|
|
67
|
+
const searchIndex = buildSearchIndex(mdFiles, inputDir);
|
|
68
|
+
const searchIndexJson = JSON.stringify(searchIndex);
|
|
69
|
+
|
|
54
70
|
const template = fs.readFileSync(
|
|
55
71
|
path.join(__dirname, '../themes/default.html'), 'utf-8'
|
|
56
72
|
);
|
|
57
73
|
|
|
58
|
-
await buildSearchIndex(mdFiles, inputDir, outputDir);
|
|
59
|
-
|
|
60
74
|
for (const filePath of mdFiles) {
|
|
61
75
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
62
76
|
const htmlContent = marked(content);
|
|
@@ -66,7 +80,8 @@ async function compile(inputDir, outputDir, watch = false) {
|
|
|
66
80
|
let html = template
|
|
67
81
|
.replace('{{content}}', htmlContent)
|
|
68
82
|
.replace('{{nav}}', nav)
|
|
69
|
-
.replace('{{title}}', path.basename(filePath, '.md'))
|
|
83
|
+
.replace('{{title}}', path.basename(filePath, '.md'))
|
|
84
|
+
.replace('{{search_index}}', searchIndexJson);
|
|
70
85
|
|
|
71
86
|
if (watch) {
|
|
72
87
|
html = html.replace('</body>', `
|
|
@@ -80,12 +95,12 @@ async function compile(inputDir, outputDir, watch = false) {
|
|
|
80
95
|
</body>`);
|
|
81
96
|
}
|
|
82
97
|
|
|
83
|
-
const indexHtml = buildIndexPage(mdFiles, inputDir, template);
|
|
84
|
-
await fs.writeFile(path.join(outputDir, 'index.html'), indexHtml);
|
|
85
|
-
|
|
86
98
|
await fs.ensureDir(path.dirname(outputPath));
|
|
87
99
|
await fs.writeFile(outputPath, html);
|
|
88
100
|
}
|
|
101
|
+
|
|
102
|
+
const indexHtml = buildIndexPage(mdFiles, inputDir, template, searchIndexJson);
|
|
103
|
+
await fs.writeFile(path.join(outputDir, 'index.html'), indexHtml);
|
|
89
104
|
}
|
|
90
105
|
|
|
91
106
|
function getMdFiles(dir) {
|
|
@@ -102,19 +117,23 @@ function getMdFiles(dir) {
|
|
|
102
117
|
return results;
|
|
103
118
|
}
|
|
104
119
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
120
|
+
function buildSearchIndex(mdFiles, inputDir) {
|
|
121
|
+
return mdFiles.map((filePath) => {
|
|
122
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
123
|
+
const content = raw
|
|
124
|
+
.replace(/#{1,6}\s/g, '')
|
|
125
|
+
.replace(/\*\*|__|\*|_/g, '')
|
|
126
|
+
.replace(/`{1,3}[^`]*`{1,3}/g, '')
|
|
127
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
|
128
|
+
.replace(/^\s*[-*+]\s/gm, '')
|
|
129
|
+
.replace(/\n+/g, ' ')
|
|
130
|
+
.trim();
|
|
131
|
+
|
|
108
132
|
const relativePath = path.relative(inputDir, filePath);
|
|
109
133
|
const href = relativePath.replace(/\.md$/, '.html');
|
|
110
134
|
const title = path.basename(filePath, '.md').replace(/-/g, ' ');
|
|
111
135
|
return { title, href, content };
|
|
112
136
|
});
|
|
113
|
-
|
|
114
|
-
await fs.writeFile(
|
|
115
|
-
path.join(outputDir, 'search-index.json'),
|
|
116
|
-
JSON.stringify(index, null, 2)
|
|
117
|
-
);
|
|
118
137
|
}
|
|
119
138
|
|
|
120
139
|
module.exports = { buildSite };
|
package/src/index-page.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
|
|
3
|
-
function buildIndexPage(mdFiles, inputDir, template) {
|
|
3
|
+
function buildIndexPage(mdFiles, inputDir, template, searchIndexJson) {
|
|
4
4
|
const cards = mdFiles.map((filePath) => {
|
|
5
5
|
const relativePath = path.relative(inputDir, filePath);
|
|
6
6
|
const href = relativePath.replace(/\.md$/, '.html');
|
|
@@ -8,19 +8,25 @@ function buildIndexPage(mdFiles, inputDir, template) {
|
|
|
8
8
|
const folder = path.dirname(relativePath) === '.' ? 'root' : path.dirname(relativePath);
|
|
9
9
|
|
|
10
10
|
return `
|
|
11
|
-
<a href="
|
|
11
|
+
<a href="${href}" class="card">
|
|
12
12
|
<span class="card-folder">${folder}</span>
|
|
13
13
|
<span class="card-title">${title}</span>
|
|
14
14
|
</a>`;
|
|
15
15
|
}).join('\n');
|
|
16
16
|
|
|
17
|
+
const content = `
|
|
18
|
+
<div class="index-header">
|
|
19
|
+
<h1>markform</h1>
|
|
20
|
+
<p>${mdFiles.length} page${mdFiles.length !== 1 ? 's' : ''}</p>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="card-grid">${cards}</div>
|
|
23
|
+
`;
|
|
24
|
+
|
|
17
25
|
return template
|
|
18
|
-
.replace('{{content}}',
|
|
19
|
-
<h1>All Pages</h1>
|
|
20
|
-
<div class="card-grid">${cards}</div>
|
|
21
|
-
`)
|
|
26
|
+
.replace('{{content}}', content)
|
|
22
27
|
.replace('{{nav}}', '')
|
|
23
|
-
.replace('{{title}}', 'Home')
|
|
28
|
+
.replace('{{title}}', 'Home')
|
|
29
|
+
.replace('{{search_index}}', searchIndexJson);
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
module.exports = { buildIndexPage };
|
package/test/hello.md
CHANGED
package/themes/default.html
CHANGED
|
@@ -1,262 +1,328 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
4
3
|
<head>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{title}}</title>
|
|
7
|
+
<style>
|
|
8
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
+
|
|
10
|
+
:root {
|
|
11
|
+
--bg: #0e0e0e;
|
|
12
|
+
--surface: #161616;
|
|
13
|
+
--border: #222222;
|
|
14
|
+
--text: #e2e2e2;
|
|
15
|
+
--muted: #444;
|
|
16
|
+
--muted-light: #777;
|
|
17
|
+
--nav-width: 240px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
body {
|
|
21
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
22
|
+
display: flex;
|
|
23
|
+
min-height: 100vh;
|
|
24
|
+
background: var(--bg);
|
|
25
|
+
color: var(--text);
|
|
26
|
+
line-height: 1.7;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* ── NAV ── */
|
|
30
|
+
nav {
|
|
31
|
+
width: var(--nav-width);
|
|
32
|
+
min-height: 100vh;
|
|
33
|
+
background: var(--surface);
|
|
34
|
+
border-right: 1px solid var(--border);
|
|
35
|
+
padding: 1.75rem 1rem;
|
|
36
|
+
position: sticky;
|
|
37
|
+
top: 0;
|
|
38
|
+
height: 100vh;
|
|
39
|
+
overflow-y: auto;
|
|
40
|
+
flex-shrink: 0;
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
gap: 0.25rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
nav.hidden { display: none; }
|
|
47
|
+
|
|
48
|
+
.nav-logo {
|
|
49
|
+
font-size: 0.65rem;
|
|
50
|
+
font-weight: 800;
|
|
51
|
+
letter-spacing: 0.2em;
|
|
52
|
+
text-transform: uppercase;
|
|
53
|
+
color: var(--muted-light);
|
|
54
|
+
text-decoration: none;
|
|
55
|
+
padding: 0 0.5rem;
|
|
56
|
+
margin-bottom: 0.75rem;
|
|
57
|
+
display: block;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.nav-logo:hover { color: var(--text); }
|
|
61
|
+
|
|
62
|
+
.nav-divider {
|
|
63
|
+
height: 1px;
|
|
64
|
+
background: var(--border);
|
|
65
|
+
margin: 0.5rem 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#search {
|
|
69
|
+
width: 100%;
|
|
70
|
+
padding: 0.5rem 0.6rem;
|
|
71
|
+
border: 1px solid var(--border);
|
|
72
|
+
background: var(--bg);
|
|
73
|
+
color: var(--text);
|
|
74
|
+
font-size: 0.8rem;
|
|
75
|
+
outline: none;
|
|
76
|
+
font-family: inherit;
|
|
77
|
+
margin-bottom: 0.5rem;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#search:focus { border-color: #444; }
|
|
81
|
+
#search::placeholder { color: var(--muted); }
|
|
82
|
+
|
|
83
|
+
.nav-section-label {
|
|
84
|
+
font-size: 0.6rem;
|
|
85
|
+
font-weight: 700;
|
|
86
|
+
letter-spacing: 0.15em;
|
|
87
|
+
text-transform: uppercase;
|
|
88
|
+
color: var(--muted);
|
|
89
|
+
padding: 0 0.5rem;
|
|
90
|
+
margin: 0.75rem 0 0.35rem;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#nav-list { list-style: none; }
|
|
94
|
+
#nav-list li a {
|
|
95
|
+
color: var(--muted-light);
|
|
96
|
+
text-decoration: none;
|
|
97
|
+
font-size: 0.82rem;
|
|
98
|
+
display: block;
|
|
99
|
+
padding: 0.3rem 0.5rem;
|
|
100
|
+
text-transform: capitalize;
|
|
101
|
+
border-left: 2px solid transparent;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#nav-list li a:hover {
|
|
105
|
+
color: var(--text);
|
|
106
|
+
border-left-color: var(--muted);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#search-results { list-style: none; }
|
|
110
|
+
#search-results li { padding: 0.6rem 0.5rem; border-bottom: 1px solid var(--border); }
|
|
111
|
+
#search-results a {
|
|
112
|
+
font-size: 0.82rem;
|
|
113
|
+
font-weight: 600;
|
|
114
|
+
color: var(--text);
|
|
115
|
+
text-decoration: none;
|
|
116
|
+
text-transform: capitalize;
|
|
117
|
+
display: block;
|
|
118
|
+
margin-bottom: 0.15rem;
|
|
119
|
+
}
|
|
120
|
+
#search-results a:hover { color: #fff; }
|
|
121
|
+
#search-results p {
|
|
122
|
+
color: var(--muted-light);
|
|
123
|
+
font-size: 0.72rem;
|
|
124
|
+
line-height: 1.5;
|
|
125
|
+
margin: 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* ── MAIN ── */
|
|
129
|
+
main {
|
|
130
|
+
flex: 1;
|
|
131
|
+
max-width: 700px;
|
|
132
|
+
margin: 0 auto;
|
|
133
|
+
padding: 4rem 2.5rem;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* index page gets full width */
|
|
137
|
+
body.index-page main {
|
|
138
|
+
max-width: 900px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* ── 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; }
|
|
145
|
+
|
|
146
|
+
p { margin-bottom: 1rem; color: #aaa; }
|
|
147
|
+
|
|
148
|
+
a { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }
|
|
149
|
+
a:hover { color: #fff; }
|
|
150
|
+
|
|
151
|
+
code {
|
|
152
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
153
|
+
font-size: 0.82em;
|
|
154
|
+
background: var(--surface);
|
|
155
|
+
color: #ccc;
|
|
156
|
+
padding: 0.15em 0.4em;
|
|
157
|
+
border: 1px solid var(--border);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
pre {
|
|
161
|
+
background: var(--surface);
|
|
162
|
+
border: 1px solid var(--border);
|
|
163
|
+
padding: 1.25rem 1.5rem;
|
|
164
|
+
overflow-x: auto;
|
|
165
|
+
margin-bottom: 1.25rem;
|
|
166
|
+
font-size: 0.82rem;
|
|
167
|
+
line-height: 1.7;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
pre code { background: none; color: #ccc; padding: 0; border: none; font-size: inherit; }
|
|
171
|
+
|
|
172
|
+
blockquote {
|
|
173
|
+
border-left: 2px solid var(--border);
|
|
174
|
+
padding: 0.4rem 0 0.4rem 1rem;
|
|
175
|
+
color: var(--muted-light);
|
|
176
|
+
margin: 1.25rem 0;
|
|
177
|
+
font-style: italic;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
hr { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }
|
|
181
|
+
img { max-width: 100%; margin: 1rem 0; }
|
|
182
|
+
|
|
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; }
|
|
187
|
+
|
|
188
|
+
ul, ol { padding-left: 1.5rem; margin-bottom: 1rem; color: #aaa; }
|
|
189
|
+
li { margin-bottom: 0.25rem; }
|
|
190
|
+
|
|
191
|
+
/* ── INDEX PAGE ── */
|
|
192
|
+
.index-header {
|
|
193
|
+
margin-bottom: 3rem;
|
|
194
|
+
border-bottom: 1px solid var(--border);
|
|
195
|
+
padding-bottom: 2rem;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.index-header h1 {
|
|
199
|
+
font-size: 2rem;
|
|
200
|
+
margin-bottom: 0.4rem;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.index-header p {
|
|
204
|
+
color: var(--muted-light);
|
|
205
|
+
font-size: 0.9rem;
|
|
206
|
+
margin: 0;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.card-grid {
|
|
210
|
+
display: grid;
|
|
211
|
+
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
212
|
+
gap: 1px;
|
|
213
|
+
background: var(--border);
|
|
214
|
+
border: 1px solid var(--border);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.card {
|
|
218
|
+
display: flex;
|
|
219
|
+
flex-direction: column;
|
|
220
|
+
padding: 1.25rem;
|
|
221
|
+
background: var(--bg);
|
|
222
|
+
text-decoration: none;
|
|
223
|
+
transition: background 0.1s;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.card:hover { background: var(--surface); }
|
|
227
|
+
|
|
228
|
+
.card-folder {
|
|
229
|
+
font-size: 0.6rem;
|
|
230
|
+
color: var(--muted);
|
|
231
|
+
text-transform: uppercase;
|
|
232
|
+
letter-spacing: 0.12em;
|
|
233
|
+
margin-bottom: 0.4rem;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.card-title {
|
|
237
|
+
font-size: 0.875rem;
|
|
238
|
+
font-weight: 500;
|
|
239
|
+
color: var(--text);
|
|
240
|
+
text-transform: capitalize;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* ── SEARCH HIGHLIGHT ── */
|
|
244
|
+
mark { background: #2a2a2a; color: #fff; padding: 0 2px; }
|
|
245
|
+
</style>
|
|
208
246
|
</head>
|
|
209
|
-
|
|
210
247
|
<body>
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
<
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
248
|
+
<nav id="sidebar">
|
|
249
|
+
<a class="nav-logo" href="index.html">markform</a>
|
|
250
|
+
<div class="nav-divider"></div>
|
|
251
|
+
<input id="search" type="text" placeholder="Search..." />
|
|
252
|
+
<span class="nav-section-label">Pages</span>
|
|
253
|
+
<ul id="nav-list">{{nav}}</ul>
|
|
254
|
+
<ul id="search-results" style="display:none;"></ul>
|
|
255
|
+
</nav>
|
|
256
|
+
<main>
|
|
257
|
+
{{content}}
|
|
258
|
+
</main>
|
|
259
|
+
|
|
260
|
+
<script src="https://cdn.jsdelivr.net/npm/fuse.js@7/dist/fuse.min.js"></script>
|
|
261
|
+
<script>
|
|
262
|
+
const searchData = {{search_index}};
|
|
263
|
+
|
|
264
|
+
// Hide sidebar on index page
|
|
265
|
+
if (document.title === 'Home') {
|
|
266
|
+
document.getElementById('sidebar').classList.add('hidden');
|
|
267
|
+
document.body.classList.add('index-page');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const fuse = new Fuse(searchData, {
|
|
271
|
+
keys: ['title', 'content'],
|
|
272
|
+
threshold: 0.1,
|
|
273
|
+
includeScore: true,
|
|
274
|
+
ignoreLocation: true,
|
|
275
|
+
minMatchCharLength: 3,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
function highlight(text, query) {
|
|
279
|
+
if (!query) return text;
|
|
280
|
+
const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
|
281
|
+
return text.replace(regex, '<mark>$1</mark>');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function getSnippet(content, query) {
|
|
285
|
+
const idx = content.toLowerCase().indexOf(query.toLowerCase());
|
|
286
|
+
if (idx === -1) return content.slice(0, 120) + '...';
|
|
287
|
+
const start = Math.max(0, idx - 40);
|
|
288
|
+
const end = Math.min(content.length, idx + 100);
|
|
289
|
+
return (start > 0 ? '...' : '') + content.slice(start, end) + (end < content.length ? '...' : '');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function resolveHref(href) {
|
|
293
|
+
const isFile = window.location.protocol === 'file:';
|
|
294
|
+
if (!isFile) return '/' + href;
|
|
295
|
+
const parts = window.location.pathname.split('/');
|
|
296
|
+
parts.pop();
|
|
297
|
+
return 'file://' + parts.join('/') + '/' + href;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const searchEl = document.getElementById('search');
|
|
301
|
+
if (searchEl) {
|
|
302
|
+
searchEl.addEventListener('input', function () {
|
|
303
|
+
const query = this.value.trim();
|
|
304
|
+
const navList = document.getElementById('nav-list');
|
|
305
|
+
const results = document.getElementById('search-results');
|
|
306
|
+
|
|
307
|
+
if (!query || query.length < 2) {
|
|
308
|
+
navList.style.display = '';
|
|
309
|
+
results.style.display = 'none';
|
|
310
|
+
results.innerHTML = '';
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
261
313
|
|
|
314
|
+
const matches = fuse.search(query).slice(0, 8);
|
|
315
|
+
navList.style.display = 'none';
|
|
316
|
+
results.style.display = '';
|
|
317
|
+
results.innerHTML = matches.length
|
|
318
|
+
? matches.map(m => `
|
|
319
|
+
<li>
|
|
320
|
+
<a href="${resolveHref(m.item.href)}">${highlight(m.item.title, query)}</a>
|
|
321
|
+
<p>${highlight(getSnippet(m.item.content, query), query)}</p>
|
|
322
|
+
</li>`).join('')
|
|
323
|
+
: `<li><p>No results found.</p></li>`;
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
</script>
|
|
327
|
+
</body>
|
|
262
328
|
</html>
|