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.
@@ -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>
@@ -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>⛔ Error {{code}}</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>