markserv 1.17.3 → 1.19.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 +33 -10
- package/lib/cli-defs.js +23 -8
- package/lib/cli-help.txt +6 -3
- package/lib/cli.js +1 -15
- package/lib/readme.js +2 -1
- package/lib/server.js +310 -83
- package/lib/templates/directory.html +158 -2
- package/lib/templates/error.html +159 -3
- package/lib/templates/github-markdown-dark.css +1124 -0
- package/lib/templates/github-markdown-light.css +1124 -0
- package/lib/templates/github-markdown-synthwave.css +987 -0
- package/lib/templates/highlight-js-github-gist.css +243 -1
- package/lib/templates/markdown.html +157 -1
- package/lib/templates/markserv.css +191 -519
- package/package.json +32 -30
- package/CHANGELOG.md +0 -145
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
2
|
+
<html data-theme="{{theme}}">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
7
7
|
<title>{{title}}</title>
|
|
8
8
|
<meta charset="utf-8">
|
|
9
|
+
<link rel="stylesheet" id="theme-dark" href="{markserv}templates/github-markdown-dark.css"{{#unless themeDark}} disabled{{/unless}}>
|
|
10
|
+
<link rel="stylesheet" id="theme-light" href="{markserv}templates/github-markdown-light.css"{{#unless themeLight}} disabled{{/unless}}>
|
|
11
|
+
<link rel="stylesheet" id="theme-synthwave" href="{markserv}templates/github-markdown-synthwave.css"{{#unless themeSynthwave}} disabled{{/unless}}>
|
|
12
|
+
<link rel="stylesheet" id="theme-solarized" href="{markserv}templates/github-markdown-solarized.css"{{#unless themeSolarized}} disabled{{/unless}}>
|
|
9
13
|
<link rel="stylesheet" href="{markserv}templates/markserv.css">
|
|
10
14
|
<link rel="stylesheet" href="{markserv}templates/highlight-js-github-gist.css">
|
|
11
15
|
<link rel="stylesheet" href="{markserv}icons/icons.css">
|
|
@@ -16,5 +20,157 @@
|
|
|
16
20
|
{{{content}}}
|
|
17
21
|
<footer><sup><hr> Served by <a href="https://www.npmjs.com/package/markserv">MarkServ</a> | PID: {{pid}}</sup></footer>
|
|
18
22
|
</article>
|
|
23
|
+
<div class="width-control" id="width-control">
|
|
24
|
+
<span id="width-tooltip"></span>
|
|
25
|
+
<input type="range" id="width-slider" min="0" max="1920" value="978" step="1">
|
|
26
|
+
</div>
|
|
27
|
+
<button class="theme-toggle" id="theme-toggle" title="Toggle theme"></button>
|
|
28
|
+
<script>
|
|
29
|
+
(function() {
|
|
30
|
+
var themes = ['dark', 'light', 'synthwave', 'solarized'];
|
|
31
|
+
var icons = {dark: '\u{1F319}', light: '\u{2600}\u{FE0F}', synthwave: '\u{1F9D1}\u{200D}\u{1F3A4}', solarized: '\u{262F}\u{FE0F}'};
|
|
32
|
+
var serverTheme = '{{theme}}' || 'dark';
|
|
33
|
+
var current = localStorage.getItem('markserv-theme') || serverTheme;
|
|
34
|
+
|
|
35
|
+
function applyTheme(t) {
|
|
36
|
+
document.documentElement.setAttribute('data-theme', t);
|
|
37
|
+
themes.forEach(function(name) {
|
|
38
|
+
var el = document.getElementById('theme-' + name);
|
|
39
|
+
if (el) el.disabled = (name !== t);
|
|
40
|
+
});
|
|
41
|
+
var btn = document.getElementById('theme-toggle');
|
|
42
|
+
if (btn) btn.textContent = icons[t] || icons.dark;
|
|
43
|
+
current = t;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
applyTheme(current);
|
|
47
|
+
|
|
48
|
+
document.getElementById('theme-toggle').addEventListener('click', function() {
|
|
49
|
+
var idx = themes.indexOf(current);
|
|
50
|
+
var next = themes[(idx + 1) % themes.length];
|
|
51
|
+
localStorage.setItem('markserv-theme', next);
|
|
52
|
+
applyTheme(next);
|
|
53
|
+
});
|
|
54
|
+
})();
|
|
55
|
+
</script>
|
|
56
|
+
<script>
|
|
57
|
+
(function() {
|
|
58
|
+
var slider = document.getElementById('width-slider');
|
|
59
|
+
var tooltip = document.getElementById('width-tooltip');
|
|
60
|
+
if (!slider) return;
|
|
61
|
+
|
|
62
|
+
var CSS_DEFAULT = 978; // must match body { width } in markserv.css
|
|
63
|
+
var storageKey = 'markserv-width:' + '{{rootDir}}';
|
|
64
|
+
var stored = localStorage.getItem(storageKey);
|
|
65
|
+
|
|
66
|
+
function updateSliderMax() {
|
|
67
|
+
// Slider max = current viewport width so user can slide up to full-bleed
|
|
68
|
+
slider.max = window.innerWidth;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function applyWidth(px) {
|
|
72
|
+
if (px === CSS_DEFAULT) {
|
|
73
|
+
// Matches stylesheet default — remove inline style so CSS rules apply normally
|
|
74
|
+
document.body.style.width = '';
|
|
75
|
+
} else {
|
|
76
|
+
document.body.style.width = px + 'px';
|
|
77
|
+
}
|
|
78
|
+
slider.value = Math.min(px, parseInt(slider.max, 10));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Initialize
|
|
82
|
+
updateSliderMax();
|
|
83
|
+
if (stored) {
|
|
84
|
+
applyWidth(parseInt(stored, 10));
|
|
85
|
+
} else {
|
|
86
|
+
// No stored value — keep the 978px stylesheet default; slider reflects it
|
|
87
|
+
slider.value = Math.min(CSS_DEFAULT, parseInt(slider.max, 10));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function positionTooltip() {
|
|
91
|
+
if (!tooltip) return;
|
|
92
|
+
var val = parseInt(slider.value, 10);
|
|
93
|
+
var min = parseInt(slider.min, 10);
|
|
94
|
+
var max = parseInt(slider.max, 10);
|
|
95
|
+
var pct = (val - min) / (max - min);
|
|
96
|
+
var sliderW = slider.offsetWidth;
|
|
97
|
+
var thumbW = 14;
|
|
98
|
+
var offset = thumbW / 2 + pct * (sliderW - thumbW);
|
|
99
|
+
tooltip.textContent = val + 'px';
|
|
100
|
+
tooltip.style.left = offset + 'px';
|
|
101
|
+
tooltip.style.transform = 'translateX(-50%)';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Slider input → live update
|
|
105
|
+
slider.addEventListener('input', function() {
|
|
106
|
+
var val = parseInt(slider.value, 10);
|
|
107
|
+
applyWidth(val);
|
|
108
|
+
localStorage.setItem(storageKey, String(val));
|
|
109
|
+
if (tooltip) tooltip.classList.add('visible');
|
|
110
|
+
positionTooltip();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
slider.addEventListener('mousedown', function() {
|
|
114
|
+
if (tooltip) tooltip.classList.add('visible');
|
|
115
|
+
positionTooltip();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
window.addEventListener('mouseup', function() {
|
|
119
|
+
if (tooltip) tooltip.classList.remove('visible');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
slider.addEventListener('focus', function() {
|
|
123
|
+
if (tooltip) tooltip.classList.add('visible');
|
|
124
|
+
positionTooltip();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
slider.addEventListener('blur', function() {
|
|
128
|
+
if (tooltip) tooltip.classList.remove('visible');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Double-click to reset to default width
|
|
132
|
+
slider.addEventListener('dblclick', function() {
|
|
133
|
+
applyWidth(CSS_DEFAULT);
|
|
134
|
+
localStorage.removeItem(storageKey);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// On window resize, update slider max to reflect new viewport
|
|
138
|
+
window.addEventListener('resize', function() {
|
|
139
|
+
updateSliderMax();
|
|
140
|
+
});
|
|
141
|
+
})();
|
|
142
|
+
</script>
|
|
143
|
+
{{#if hotreload}}
|
|
144
|
+
<script>
|
|
145
|
+
(function() {
|
|
146
|
+
var ws;
|
|
147
|
+
var delay = 1000;
|
|
148
|
+
var maxDelay = 30000;
|
|
149
|
+
var maxRetries = 20;
|
|
150
|
+
var retries = 0;
|
|
151
|
+
|
|
152
|
+
function connect() {
|
|
153
|
+
ws = new WebSocket('ws://localhost:{{wsPort}}');
|
|
154
|
+
ws.onopen = function() {
|
|
155
|
+
delay = 1000;
|
|
156
|
+
retries = 0;
|
|
157
|
+
ws.send(JSON.stringify({path: location.pathname}));
|
|
158
|
+
};
|
|
159
|
+
ws.onmessage = function(e) {
|
|
160
|
+
var el = document.querySelector('.markdown-body');
|
|
161
|
+
if (el) el.innerHTML = e.data;
|
|
162
|
+
};
|
|
163
|
+
ws.onclose = function() {
|
|
164
|
+
if (retries < maxRetries) {
|
|
165
|
+
retries++;
|
|
166
|
+
setTimeout(connect, delay);
|
|
167
|
+
delay = Math.min(delay * 2, maxDelay);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
connect();
|
|
172
|
+
})();
|
|
173
|
+
</script>
|
|
174
|
+
{{/if}}
|
|
19
175
|
</body>
|
|
20
|
-
</html>
|
|
176
|
+
</html>
|
package/lib/templates/error.html
CHANGED
|
@@ -1,22 +1,178 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
2
|
+
<html data-theme="{{theme}}">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
7
7
|
<title>{{code}}: {{filePath}}</title>
|
|
8
8
|
<meta charset="utf-8">
|
|
9
|
+
<link rel="stylesheet" id="theme-dark" href="{markserv}templates/github-markdown-dark.css"{{#unless themeDark}} disabled{{/unless}}>
|
|
10
|
+
<link rel="stylesheet" id="theme-light" href="{markserv}templates/github-markdown-light.css"{{#unless themeLight}} disabled{{/unless}}>
|
|
11
|
+
<link rel="stylesheet" id="theme-synthwave" href="{markserv}templates/github-markdown-synthwave.css"{{#unless themeSynthwave}} disabled{{/unless}}>
|
|
12
|
+
<link rel="stylesheet" id="theme-solarized" href="{markserv}templates/github-markdown-solarized.css"{{#unless themeSolarized}} disabled{{/unless}}>
|
|
9
13
|
<link rel="stylesheet" href="{markserv}templates/markserv.css">
|
|
10
14
|
<link rel="stylesheet" href="{markserv}templates/highlight-js-github-gist.css">
|
|
11
15
|
<link rel="stylesheet" href="{markserv}icons/icons.css">
|
|
12
16
|
</head>
|
|
13
17
|
<body class="dir">
|
|
14
18
|
<article class="markdown-body">
|
|
15
|
-
<h1
|
|
19
|
+
<h1>Error {{code}}</h1>
|
|
16
20
|
<p class="errorMsg">{{errorMsg}}</p>
|
|
17
21
|
<p><pre>{{errorStack}}</pre></p>
|
|
18
22
|
<p><a class="button back" href="{{referer}}">Go Back</a></p>
|
|
19
23
|
<footer><sup><hr> Served by <a href="https://www.npmjs.com/package/markserv">MarkServ</a> | PID: {{pid}}</sup></footer>
|
|
20
24
|
</article>
|
|
25
|
+
<div class="width-control" id="width-control">
|
|
26
|
+
<span id="width-tooltip"></span>
|
|
27
|
+
<input type="range" id="width-slider" min="0" max="1920" value="978" step="1">
|
|
28
|
+
</div>
|
|
29
|
+
<button class="theme-toggle" id="theme-toggle" title="Toggle theme"></button>
|
|
30
|
+
<script>
|
|
31
|
+
(function() {
|
|
32
|
+
var themes = ['dark', 'light', 'synthwave', 'solarized'];
|
|
33
|
+
var icons = {dark: '\u{1F319}', light: '\u{2600}\u{FE0F}', synthwave: '\u{1F9D1}\u{200D}\u{1F3A4}', solarized: '\u{262F}\u{FE0F}'};
|
|
34
|
+
var serverTheme = '{{theme}}' || 'dark';
|
|
35
|
+
var current = localStorage.getItem('markserv-theme') || serverTheme;
|
|
36
|
+
|
|
37
|
+
function applyTheme(t) {
|
|
38
|
+
document.documentElement.setAttribute('data-theme', t);
|
|
39
|
+
themes.forEach(function(name) {
|
|
40
|
+
var el = document.getElementById('theme-' + name);
|
|
41
|
+
if (el) el.disabled = (name !== t);
|
|
42
|
+
});
|
|
43
|
+
var btn = document.getElementById('theme-toggle');
|
|
44
|
+
if (btn) btn.textContent = icons[t] || icons.dark;
|
|
45
|
+
current = t;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
applyTheme(current);
|
|
49
|
+
|
|
50
|
+
document.getElementById('theme-toggle').addEventListener('click', function() {
|
|
51
|
+
var idx = themes.indexOf(current);
|
|
52
|
+
var next = themes[(idx + 1) % themes.length];
|
|
53
|
+
localStorage.setItem('markserv-theme', next);
|
|
54
|
+
applyTheme(next);
|
|
55
|
+
});
|
|
56
|
+
})();
|
|
57
|
+
</script>
|
|
58
|
+
<script>
|
|
59
|
+
(function() {
|
|
60
|
+
var slider = document.getElementById('width-slider');
|
|
61
|
+
var tooltip = document.getElementById('width-tooltip');
|
|
62
|
+
if (!slider) return;
|
|
63
|
+
|
|
64
|
+
var CSS_DEFAULT = 978; // must match body { width } in markserv.css
|
|
65
|
+
var storageKey = 'markserv-width:' + '{{rootDir}}';
|
|
66
|
+
var stored = localStorage.getItem(storageKey);
|
|
67
|
+
|
|
68
|
+
function updateSliderMax() {
|
|
69
|
+
// Slider max = current viewport width so user can slide up to full-bleed
|
|
70
|
+
slider.max = window.innerWidth;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function applyWidth(px) {
|
|
74
|
+
if (px === CSS_DEFAULT) {
|
|
75
|
+
// Matches stylesheet default — remove inline style so CSS rules apply normally
|
|
76
|
+
document.body.style.width = '';
|
|
77
|
+
} else {
|
|
78
|
+
document.body.style.width = px + 'px';
|
|
79
|
+
}
|
|
80
|
+
slider.value = Math.min(px, parseInt(slider.max, 10));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Initialize
|
|
84
|
+
updateSliderMax();
|
|
85
|
+
if (stored) {
|
|
86
|
+
applyWidth(parseInt(stored, 10));
|
|
87
|
+
} else {
|
|
88
|
+
// No stored value — keep the 978px stylesheet default; slider reflects it
|
|
89
|
+
slider.value = Math.min(CSS_DEFAULT, parseInt(slider.max, 10));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function positionTooltip() {
|
|
93
|
+
if (!tooltip) return;
|
|
94
|
+
var val = parseInt(slider.value, 10);
|
|
95
|
+
var min = parseInt(slider.min, 10);
|
|
96
|
+
var max = parseInt(slider.max, 10);
|
|
97
|
+
var pct = (val - min) / (max - min);
|
|
98
|
+
var sliderW = slider.offsetWidth;
|
|
99
|
+
var thumbW = 14;
|
|
100
|
+
var offset = thumbW / 2 + pct * (sliderW - thumbW);
|
|
101
|
+
tooltip.textContent = val + 'px';
|
|
102
|
+
tooltip.style.left = offset + 'px';
|
|
103
|
+
tooltip.style.transform = 'translateX(-50%)';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Slider input → live update
|
|
107
|
+
slider.addEventListener('input', function() {
|
|
108
|
+
var val = parseInt(slider.value, 10);
|
|
109
|
+
applyWidth(val);
|
|
110
|
+
localStorage.setItem(storageKey, String(val));
|
|
111
|
+
if (tooltip) tooltip.classList.add('visible');
|
|
112
|
+
positionTooltip();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
slider.addEventListener('mousedown', function() {
|
|
116
|
+
if (tooltip) tooltip.classList.add('visible');
|
|
117
|
+
positionTooltip();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
window.addEventListener('mouseup', function() {
|
|
121
|
+
if (tooltip) tooltip.classList.remove('visible');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
slider.addEventListener('focus', function() {
|
|
125
|
+
if (tooltip) tooltip.classList.add('visible');
|
|
126
|
+
positionTooltip();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
slider.addEventListener('blur', function() {
|
|
130
|
+
if (tooltip) tooltip.classList.remove('visible');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Double-click to reset to default width
|
|
134
|
+
slider.addEventListener('dblclick', function() {
|
|
135
|
+
applyWidth(CSS_DEFAULT);
|
|
136
|
+
localStorage.removeItem(storageKey);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// On window resize, update slider max to reflect new viewport
|
|
140
|
+
window.addEventListener('resize', function() {
|
|
141
|
+
updateSliderMax();
|
|
142
|
+
});
|
|
143
|
+
})();
|
|
144
|
+
</script>
|
|
145
|
+
{{#if hotreload}}
|
|
146
|
+
<script>
|
|
147
|
+
(function() {
|
|
148
|
+
var ws;
|
|
149
|
+
var delay = 1000;
|
|
150
|
+
var maxDelay = 30000;
|
|
151
|
+
var maxRetries = 20;
|
|
152
|
+
var retries = 0;
|
|
153
|
+
|
|
154
|
+
function connect() {
|
|
155
|
+
ws = new WebSocket('ws://localhost:{{wsPort}}');
|
|
156
|
+
ws.onopen = function() {
|
|
157
|
+
delay = 1000;
|
|
158
|
+
retries = 0;
|
|
159
|
+
ws.send(JSON.stringify({path: location.pathname}));
|
|
160
|
+
};
|
|
161
|
+
ws.onmessage = function(e) {
|
|
162
|
+
var el = document.querySelector('.markdown-body');
|
|
163
|
+
if (el) el.innerHTML = e.data;
|
|
164
|
+
};
|
|
165
|
+
ws.onclose = function() {
|
|
166
|
+
if (retries < maxRetries) {
|
|
167
|
+
retries++;
|
|
168
|
+
setTimeout(connect, delay);
|
|
169
|
+
delay = Math.min(delay * 2, maxDelay);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
connect();
|
|
174
|
+
})();
|
|
175
|
+
</script>
|
|
176
|
+
{{/if}}
|
|
21
177
|
</body>
|
|
22
|
-
</html>
|
|
178
|
+
</html>
|