md-live-server/templates.go
templates.goRaw
1package main
2
3import (
4 "strings"
5)
6
7// html templates are here instead of an .html file to avoid depending on external files
8// in this way, everything is inside the binary
9
10func fillCSSintoTemplate(template string) string {
11 if strings.Contains(template, "[CSS]") {
12 template = strings.Replace(template, "[CSS]", cssTemplate, -1)
13 }
14 return template
15}
16
17var dirTemplate = `
18<!DOCTYPE html>
19<html>
20<title>{{.Title}}</title>
21[CSS]
22<style>
23body {
24 box-sizing: border-box;
25 min-width: 200px;
26 max-width: 980px;
27 margin: 0 auto;
28 padding: 45px;
29}
30
31@media (prefers-color-scheme: dark) {
32 body {
33 background-color: #0d1117;
34 }
35}
36</style>
37<body class="dark-theme">
38<article class="markdown-body">
39 <input onclick="switchThemeClick()" type="checkbox" id="themeSwitcher" style="float:right;">
40
41{{.Content}}
42
43<script>
44 let theme = localStorage.getItem("theme");
45 if (theme === "light") {
46 document.getElementById("themeSwitcher").checked = false;
47 document.body.className = theme;
48 } else {
49 document.getElementById("themeSwitcher").checked = true;
50 }
51
52 function switchThemeClick() {
53 theme = localStorage.getItem("theme");
54 if (theme === "light") {
55 document.getElementById("themeSwitcher").checked = true;
56 theme = "dark-theme";
57 localStorage.setItem("theme", theme);
58 } else {
59 document.getElementById("themeSwitcher").checked = false;
60 theme = "light";
61 localStorage.setItem("theme", theme);
62 }
63 document.body.className = theme;
64 }
65</script>
66
67</article>
68</body>
69</html>
70`
71
72var htmlTemplate = `
73<!DOCTYPE html>
74<html>
75<title>{{.Title}}</title>
76[CSS]
77<style>
78body {
79 box-sizing: border-box;
80 display: flex;
81 align-items: flex-start;
82 min-width: 200px;
83 margin: 0;
84 padding: 0;
85}
86
87.page-layout {
88 box-sizing: border-box;
89 flex: 1 1 auto;
90 max-width: 980px;
91 margin-left: 40px;
92 margin-right: auto;
93 padding: 45px;
94}
95
96.toc {
97 position: sticky;
98 top: 0;
99 flex: 0 0 280px;
100 box-sizing: border-box;
101 width: 280px;
102 height: 100vh;
103 max-height: 100vh;
104 padding: 45px 14px;
105 overflow-y: auto;
106 border-right: 1px solid var(--color-border-muted);
107}
108
109.toc-title {
110 margin: 0 0 12px;
111 color: var(--color-fg-muted);
112 font-size: 12px;
113 font-weight: 600;
114 text-transform: uppercase;
115}
116
117.toc-list {
118 margin: 0;
119 padding: 0;
120 list-style: none;
121}
122
123.toc-list li {
124 margin: 2px 0;
125}
126
127.toc-list a {
128 display: block;
129 overflow: hidden;
130 padding: 4px 0;
131 color: var(--color-fg-muted);
132 font-size: 13px;
133 line-height: 1.35;
134 text-overflow: ellipsis;
135 white-space: nowrap;
136}
137
138.toc-list a:hover {
139 color: var(--color-accent-fg);
140 text-decoration: none;
141}
142
143.toc-level-2 { padding-left: 2px; }
144.toc-level-3 { padding-left: 14px; }
145.toc-level-4 { padding-left: 26px; }
146.toc-level-5 { padding-left: 38px; }
147.toc-level-6 { padding-left: 40px; }
148
149.toc-empty {
150 display: none;
151}
152
153@media (max-width: 899px) {
154 .toc {
155 flex-basis: 240px;
156 width: 240px;
157 padding: 24px 14px;
158 }
159
160 .page-layout {
161 margin-left: 20px;
162 }
163}
164
165@media (max-width: 767px) {
166 .page-layout {
167 margin-left: 0;
168 padding: 24px;
169 }
170
171 .toc {
172 flex-basis: 200px;
173 width: 200px;
174 padding: 24px 12px;
175 }
176}
177
178</style>
179
180<body class="dark-theme">
181<nav id="toc" class="toc markdown-body" aria-label="Table of contents">
182 <div class="toc-title">Contents</div>
183 <ul id="tocList" class="toc-list"></ul>
184</nav>
185<main class="page-layout">
186<article class="markdown-body">
187<input onclick="switchThemeClick()" type="checkbox" id="themeSwitcher" style="float:right;">
188<a href="/" title="go to root">root</a>
189<div id="content" class="container">
190{{.Content}}
191</div>
192
193
194<!-- TODO: make all the imports locally, to allow working offline -->
195
196<!-- LaTex renderization -->
197<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css" integrity="sha384-Um5gpz1odJg5Z4HAmzPtgZKdTBHZdw8S29IecapCSB31ligYPhHQZMIlWLYQGVoc" crossorigin="anonymous">
198
199<!-- The loading of KaTeX is deferred to speed up page rendering -->
200<script defer src="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.js" integrity="sha384-YNHdsYkH6gMx9y3mRkmcJ2mFUjTd0qNQQvY9VYZgQd7DcN7env35GzlmFaZ23JGp" crossorigin="anonymous"></script>
201
202<!-- To automatically render math in text elements, include the auto-render extension: -->
203<script defer src="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/contrib/auto-render.min.js" integrity="sha384-vZTG03m+2yp6N6BNi5iM4rW4oIwk5DfcNdFfxkk9ZWpDriOkXX8voJBFrAO7MpVl" crossorigin="anonymous"></script>
204
205<!-- Mermaidjs -->
206<script type="module">
207 import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
208 mermaid.initialize({ startOnLoad: false });
209
210 window.renderMermaid = async function() {
211 document.querySelectorAll('pre > code.language-mermaid').forEach(function(codeBlock) {
212 const pre = codeBlock.parentElement;
213 pre.className = 'mermaid';
214 pre.textContent = codeBlock.textContent;
215 });
216
217 await mermaid.run({ querySelector: '.mermaid' });
218 };
219</script>
220
221<script>
222 document.addEventListener("DOMContentLoaded", function() {
223 buildTableOfContents();
224
225 renderMathInElement(document.body, {
226 // customised options
227 // • auto-render specific keys, e.g.:
228 delimiters: [
229 {left: '$$', right: '$$', display: true},
230 {left: '$', right: '$', display: true},
231 {left: '\\(', right: '\\)', display: false},
232 {left: '\\[', right: '\\]', display: true}
233 ],
234 // • rendering keys, e.g.:
235 throwOnError : false
236 });
237
238 if (window.renderMermaid) {
239 window.renderMermaid().catch(console.error);
240 }
241 });
242</script>
243
244<script>
245(function() {
246
247 var conn = new WebSocket("ws://127.0.0.1:8080/ws/{{.Title}}");
248 conn.onclose = function(evt) {
249 console.log('Connection closed');
250 alert('Connection closed');
251 }
252 conn.onmessage = function(evt) {
253 console.log('file updated');
254 // location.reload();
255 // console.log("evt", evt);
256 document.getElementById("content").innerHTML = evt.data;
257 buildTableOfContents();
258
259 renderMathInElement(document.body, {
260 // customised options
261 // • auto-render specific keys, e.g.:
262 delimiters: [
263 {left: '$$', right: '$$', display: true},
264 {left: '$', right: '$', display: false},
265 {left: '\\(', right: '\\)', display: false},
266 {left: '\\[', right: '\\]', display: true}
267 ],
268 // • rendering keys, e.g.:
269 throwOnError : false
270 });
271
272 if (window.renderMermaid) {
273 window.renderMermaid().catch(console.error);
274 }
275 }
276})();
277
278 function slugifyHeading(text) {
279 return text.trim().toLowerCase()
280 .replace(/[^\w\s-]/g, "")
281 .replace(/\s+/g, "-")
282 .replace(/-+/g, "-") || "heading";
283 }
284
285 function buildTableOfContents() {
286 const content = document.getElementById("content");
287 const toc = document.getElementById("toc");
288 const tocList = document.getElementById("tocList");
289 if (!content || !toc || !tocList) {
290 return;
291 }
292
293 const headings = content.querySelectorAll("h1, h2, h3, h4, h5, h6");
294 const usedIds = new Set();
295 tocList.innerHTML = "";
296
297 headings.forEach(function(heading) {
298 const level = parseInt(heading.tagName.substring(1), 10);
299 let id = heading.id || slugifyHeading(heading.textContent);
300 const baseId = id;
301 let suffix = 2;
302 while (usedIds.has(id)) {
303 id = baseId + "-" + suffix;
304 suffix++;
305 }
306 heading.id = id;
307 usedIds.add(id);
308
309 const item = document.createElement("li");
310 item.className = "toc-level-" + level;
311
312 const link = document.createElement("a");
313 link.href = "#" + id;
314 link.textContent = heading.textContent;
315 link.title = heading.textContent;
316
317 item.appendChild(link);
318 tocList.appendChild(item);
319 });
320
321 toc.classList.toggle("toc-empty", headings.length === 0);
322 }
323
324
325 let theme = localStorage.getItem("theme");
326 if (theme === "light") {
327 document.getElementById("themeSwitcher").checked = false;
328 document.body.className = theme;
329 } else {
330 document.getElementById("themeSwitcher").checked = true;
331 }
332
333 function switchThemeClick() {
334 theme = localStorage.getItem("theme");
335 if (theme === "light") {
336 document.getElementById("themeSwitcher").checked = true;
337 theme = "dark-theme";
338 localStorage.setItem("theme", theme);
339 } else {
340 document.getElementById("themeSwitcher").checked = false;
341 theme = "light";
342 localStorage.setItem("theme", theme);
343 }
344 document.body.className = theme;
345 }
346
347</script>
348</article>
349</main>
350</body>
351</html>
352`
353
354const errTemplate = `
355<!DOCTYPE html>
356<html>
357<title>{{.Title}}</title>
358<style>
359body {
360 font-family: Arial, Helvetica, sans-serif;
361 background:#000000;
362 color:#cccccc;
363}
364</style>
365<body>
366 <a href="/" title="go to root">root</a>
367
368<h3>File doesn't exist</h3>
369<br><br>
370<a href="/">Back to dir menu</a>
371
372</body>
373</html>
374`