markform-cli 1.1.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 +1 -1
- package/images/Markform | Devlog 3.png +0 -0
- package/package.json +4 -3
- package/src/build.js +36 -25
- package/src/index-page.js +13 -7
- package/themes/default.html +321 -276
package/README.md
CHANGED
|
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,27 +117,23 @@ function getMdFiles(dir) {
|
|
|
102
117
|
return results;
|
|
103
118
|
}
|
|
104
119
|
|
|
105
|
-
|
|
106
|
-
|
|
120
|
+
function buildSearchIndex(mdFiles, inputDir) {
|
|
121
|
+
return mdFiles.map((filePath) => {
|
|
107
122
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
108
123
|
const content = raw
|
|
109
|
-
.replace(/#{1,6}\s/g, '')
|
|
110
|
-
.replace(/\*\*|__|\*|_/g, '')
|
|
111
|
-
.replace(/`{1,3}[^`]*`{1,3}/g, '')
|
|
112
|
-
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
|
113
|
-
.replace(/^\s*[-*+]\s/gm, '')
|
|
114
|
-
.replace(/\n+/g, ' ')
|
|
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, ' ')
|
|
115
130
|
.trim();
|
|
131
|
+
|
|
116
132
|
const relativePath = path.relative(inputDir, filePath);
|
|
117
133
|
const href = relativePath.replace(/\.md$/, '.html');
|
|
118
134
|
const title = path.basename(filePath, '.md').replace(/-/g, ' ');
|
|
119
135
|
return { title, href, content };
|
|
120
136
|
});
|
|
121
|
-
|
|
122
|
-
await fs.writeFile(
|
|
123
|
-
path.join(outputDir, 'search-index.json'),
|
|
124
|
-
JSON.stringify(index, null, 2)
|
|
125
|
-
);
|
|
126
137
|
}
|
|
127
138
|
|
|
128
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/themes/default.html
CHANGED
|
@@ -1,283 +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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
+
}
|
|
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>
|
|
281
327
|
</body>
|
|
282
|
-
|
|
283
328
|
</html>
|