mirror of
https://github.com/jpetazzo/container.training.git
synced 2026-02-14 09:39:56 +00:00
📃 Make it easier to serve single markdown files
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,6 +18,7 @@ slides/index.html
|
||||
slides/past.html
|
||||
slides/slides.zip
|
||||
slides/_academy_*
|
||||
slides/fragments
|
||||
node_modules
|
||||
|
||||
### macOS ###
|
||||
|
||||
@@ -23,3 +23,10 @@
|
||||
|
||||
# Survey form
|
||||
/please https://docs.google.com/forms/d/e/1FAIpQLSfIYSgrV7tpfBNm1hOaprjnBHgWKn5n-k5vtNXYJkOX1sRxng/viewform
|
||||
|
||||
# Serve individual lessons with special URLs:
|
||||
# - access http://container.training/k8s/ingress
|
||||
# - ...redirects to http://container.training/view?k8s/ingress
|
||||
# - ...proxies to http://container.training/workshop.html?k8s/ingress
|
||||
/view /workshop.html 200
|
||||
/* /view?:splat
|
||||
|
||||
@@ -87,26 +87,6 @@ def flatten(titles):
|
||||
|
||||
|
||||
def generatefromyaml(manifest, filename):
|
||||
manifest = yaml.safe_load(manifest)
|
||||
|
||||
for k in manifest:
|
||||
override = os.environ.get("OVERRIDE_"+k)
|
||||
if override:
|
||||
manifest[k] = override
|
||||
|
||||
for k in ["chat", "gitrepo", "slides", "title"]:
|
||||
if k not in manifest:
|
||||
manifest[k] = ""
|
||||
|
||||
if "zip" not in manifest:
|
||||
if manifest["slides"].endswith('/'):
|
||||
manifest["zip"] = manifest["slides"] + "slides.zip"
|
||||
else:
|
||||
manifest["zip"] = manifest["slides"] + "/slides.zip"
|
||||
|
||||
if "html" not in manifest:
|
||||
manifest["html"] = filename + ".html"
|
||||
|
||||
markdown, titles = processcontent(manifest["content"], filename)
|
||||
logging.debug("Found {} titles.".format(len(titles)))
|
||||
toc = gentoc(titles)
|
||||
@@ -121,39 +101,39 @@ def generatefromyaml(manifest, filename):
|
||||
exclude = ",".join('"{}"'.format(c) for c in exclude)
|
||||
|
||||
# Insert build info. This is super hackish.
|
||||
|
||||
markdown = markdown.replace(
|
||||
".debug[",
|
||||
".debug[\n```\n{}\n```\n\nThese slides have been built from commit: {}\n\n".format(dirtyfiles, commit),
|
||||
1)
|
||||
|
||||
markdown = markdown.replace("@@TITLE@@", manifest["title"].replace("\n", "<br/>"))
|
||||
|
||||
html = open("workshop.html").read()
|
||||
html = html.replace("@@TITLE@@", manifest["title"].replace("\n", " "))
|
||||
html = html.replace("@@MARKDOWN@@", markdown)
|
||||
html = html.replace("@@EXCLUDE@@", exclude)
|
||||
html = html.replace("@@CHAT@@", manifest["chat"])
|
||||
html = html.replace("@@GITREPO@@", manifest["gitrepo"])
|
||||
html = html.replace("@@SLIDES@@", manifest["slides"])
|
||||
html = html.replace("@@ZIP@@", manifest["zip"])
|
||||
html = html.replace("@@HTML@@", manifest["html"])
|
||||
html = html.replace("@@TITLE@@", manifest["title"].replace("\n", " "))
|
||||
html = html.replace("@@SLIDENUMBERPREFIX@@", manifest.get("slidenumberprefix", ""))
|
||||
return html
|
||||
|
||||
def processAtAtStrings(text):
|
||||
text = text.replace("@@CHAT@@", manifest["chat"])
|
||||
text = text.replace("@@GITREPO@@", manifest["gitrepo"])
|
||||
text = text.replace("@@SLIDES@@", manifest["slides"])
|
||||
text = text.replace("@@ZIP@@", manifest["zip"])
|
||||
text = text.replace("@@HTML@@", manifest["html"])
|
||||
text = text.replace("@@TITLE@@", manifest["title"].replace("\n", "<br/>"))
|
||||
# Process @@LINK[file] and @@INCLUDE[file] directives
|
||||
local_anchor_path = ".."
|
||||
# FIXME use dynamic repo and branch?
|
||||
online_anchor_path = "https://github.com/jpetazzo/container.training/tree/master"
|
||||
for atatlink in re.findall(r"@@LINK\[[^]]*\]", html):
|
||||
online_anchor_path = "https://github.com/jpetazzo/container.training/tree/main"
|
||||
for atatlink in re.findall(r"@@LINK\[[^]]*\]", text):
|
||||
logging.debug("Processing {}".format(atatlink))
|
||||
file_name = atatlink[len("@@LINK["):-1]
|
||||
html = html.replace(atatlink, "[{}]({}/{})".format(file_name, online_anchor_path, file_name ))
|
||||
for atatinclude in re.findall(r"@@INCLUDE\[[^]]*\]", html):
|
||||
text = text.replace(atatlink, "[{}]({}/{})".format(file_name, online_anchor_path, file_name ))
|
||||
for atatinclude in re.findall(r"@@INCLUDE\[[^]]*\]", text):
|
||||
logging.debug("Processing {}".format(atatinclude))
|
||||
file_name = atatinclude[len("@@INCLUDE["):-1]
|
||||
file_path = os.path.join(local_anchor_path, file_name)
|
||||
html = html.replace(atatinclude, open(file_path).read())
|
||||
return html
|
||||
text = text.replace(atatinclude, open(file_path).read())
|
||||
return text
|
||||
|
||||
|
||||
# Maps a title (the string just after "^# ") to its position in the TOC
|
||||
@@ -213,7 +193,14 @@ def processcontent(content, filename):
|
||||
content += "\n" + slidefooter
|
||||
return (content, titles)
|
||||
if os.path.isfile(content):
|
||||
return processcontent(open(content).read(), content)
|
||||
markdown = open(content).read()
|
||||
markdown = processAtAtStrings(markdown)
|
||||
fragmentfile = os.path.join("fragments", content)
|
||||
fragmentdir = os.path.dirname(fragmentfile)
|
||||
os.makedirs(fragmentdir, exist_ok=True)
|
||||
with open(fragmentfile, "w") as f:
|
||||
f.write(markdown)
|
||||
return processcontent(markdown, content)
|
||||
logging.warning("Content spans only one line (it's probably a file name) but no file found: {}".format(content))
|
||||
if isinstance(content, list):
|
||||
subparts = [processcontent(c, filename) for c in content]
|
||||
@@ -271,5 +258,22 @@ else:
|
||||
else:
|
||||
manifest = open(filename)
|
||||
logging.info("Processing {}...".format(filename))
|
||||
|
||||
manifest = yaml.safe_load(manifest)
|
||||
for k in manifest:
|
||||
override = os.environ.get("OVERRIDE_"+k)
|
||||
if override:
|
||||
manifest[k] = override
|
||||
for k in ["chat", "gitrepo", "slides", "title"]:
|
||||
if k not in manifest:
|
||||
manifest[k] = ""
|
||||
if "zip" not in manifest:
|
||||
if manifest["slides"].endswith('/'):
|
||||
manifest["zip"] = manifest["slides"] + "slides.zip"
|
||||
else:
|
||||
manifest["zip"] = manifest["slides"] + "/slides.zip"
|
||||
if "html" not in manifest:
|
||||
manifest["html"] = filename + ".html"
|
||||
|
||||
sys.stdout.write(generatefromyaml(manifest, filename))
|
||||
logging.info("Processed {}.".format(filename))
|
||||
|
||||
@@ -1,43 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>@@TITLE@@</title>
|
||||
<title>Training Materials</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<link rel="stylesheet" href="workshop.css">
|
||||
</head>
|
||||
<body>
|
||||
<!--
|
||||
<div style="position: absolute; left: 20%; right: 20%; top: 30%;">
|
||||
<h1 style="font-size: 3em;">Loading ...</h1>
|
||||
The slides should show up here. If they don't, it might be
|
||||
because you are accessing this file directly from your filesystem.
|
||||
It needs to be served from a web server. You can try this:
|
||||
<pre>
|
||||
docker-compose up -d
|
||||
open http://localhost:8888/workshop.html # on MacOS
|
||||
xdg-open http://localhost:8888/workshop.html # on Linux
|
||||
</pre>
|
||||
Once the slides are loaded, this notice disappears when you
|
||||
go full screen (e.g. by hitting "f").
|
||||
</div>
|
||||
-->
|
||||
<textarea id="source">@@MARKDOWN@@</textarea>
|
||||
<script src="remark.min.js" type="text/javascript">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
<div id="loading">⏳️ Loading...</div>
|
||||
<textarea id="source" style="display: none;">@@MARKDOWN@@</textarea>
|
||||
<script type="module">
|
||||
if (window.location.search[0] === '?') {
|
||||
const contentName = window.location.search.substr(1);
|
||||
let contentText = "⚠️ Failed to load content!"
|
||||
for (const url of [
|
||||
`fragments/${contentName}.md`,
|
||||
`${contentName}.md`
|
||||
]) {
|
||||
const contentResponse = await fetch(url);
|
||||
if (contentResponse.ok) {
|
||||
contentText = await contentResponse.text();
|
||||
break;
|
||||
}
|
||||
}
|
||||
document.querySelector('#source').textContent = contentText;
|
||||
}
|
||||
/*
|
||||
This tmpl() function helps us to use the same HTML file for entire
|
||||
decks (used for live classes and pre-processed by markmaker.py to
|
||||
replace @@-strings) and to display individual chapters (used for
|
||||
video recording and not pre-processed by markmarker.py, so we still
|
||||
have @@-strings in this HTML file in that case).
|
||||
*/
|
||||
function tmpl(atString, templateValue, defaultValue) {
|
||||
if (atString[0] === "@") {
|
||||
return defaultValue;
|
||||
}
|
||||
return JSON.parse(templateValue);
|
||||
}
|
||||
var tmplEXCLUDE = tmpl('@@EXCLUDE@@', '[@@EXCLUDE@@]', []);
|
||||
var tmplTITLE = tmpl('@@TITLE@@', '"@@TITLE@@"', document.title);
|
||||
var tmplSLIDENUMBERPREFIX = tmpl('@@SLIDENUMBERPREFIX@@', '"@@SLIDENUMBERPREFIX@@"', "");
|
||||
document.title = tmplTITLE;
|
||||
var slideshow = remark.create({
|
||||
ratio: '16:9',
|
||||
highlightSpans: true,
|
||||
slideNumberFormat: '@@SLIDENUMBERPREFIX@@%current%/%total%',
|
||||
excludedClasses: [@@EXCLUDE@@]
|
||||
slideNumberFormat: `${tmplSLIDENUMBERPREFIX}%current%/%total%`,
|
||||
excludedClasses: tmplEXCLUDE
|
||||
});
|
||||
</script>
|
||||
<script type="module">
|
||||
document.querySelector('#loading').style.display = 'none';
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||
mermaid.initialize({ startOnLoad: false });
|
||||
slideshow.on('afterShowSlide', function (slide) {
|
||||
mermaid.run({
|
||||
nodes: document.querySelectorAll('div.remark-visible .mermaid'),
|
||||
nodes: document.querySelectorAll('div.remark-visible.mermaid'),
|
||||
});
|
||||
});
|
||||
// Reminder, if you want to tinker with mermaid,
|
||||
|
||||
Reference in New Issue
Block a user