arnaucube
arnaucube/md-live-serverPublic Self-hosted repository
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`