Merge branch 'master' into release
This commit is contained in:
commit
327f7b7d14
@ -23,6 +23,7 @@ We recommend you to structure your project directory like this:
|
|||||||
│ └── titlepage.html
|
│ └── titlepage.html
|
||||||
├── meta.yml
|
├── meta.yml
|
||||||
├── onpoint
|
├── onpoint
|
||||||
|
│ ├── autocompile.sh
|
||||||
│ ├── main.py
|
│ ├── main.py
|
||||||
│ ├── README.md
|
│ ├── README.md
|
||||||
│ └── requirements.txt
|
│ └── requirements.txt
|
||||||
@ -45,4 +46,10 @@ We recommend you to structure your project directory like this:
|
|||||||
|
|
||||||
## Updating onPoint
|
## Updating onPoint
|
||||||
|
|
||||||
In order to update the version of onPoint in an existing project, simply enter the `onpoint` directory and run `git pull`.
|
In order to update the version of onPoint in an existing project, simply enter the `onpoint` directory and run `git pull`.
|
||||||
|
|
||||||
|
## Auto-compile during Development
|
||||||
|
|
||||||
|
You might want to have all slides auto-compiled for you on safe. For this case, we wrote a small bash script that spawns a file watcher to compile your presentation once any markdown file in the slides folder is saved. Simply run `./onpoint/autocompile.sh` from your project root folder.
|
||||||
|
|
||||||
|
You will need Python and `inotify-tools` to execute the script.
|
||||||
6
autocompile.sh
Executable file
6
autocompile.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
# Requirements: inotify-tools, python3
|
||||||
|
|
||||||
|
while inotifywait -e close_write ./slides/*.md; do
|
||||||
|
python3 ./onpoint/main.py .
|
||||||
|
done
|
||||||
28
fragments.py
Normal file
28
fragments.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import string
|
||||||
|
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
FRAGMENT_TAG = "++"
|
||||||
|
FRAGMENT_CLASS = "fragment"
|
||||||
|
|
||||||
|
# Search and delete the FRAGMENT_TAG anywhere in the given HTML
|
||||||
|
# while adding the FRAGMENT_CLASS as an attribute
|
||||||
|
# @returns the HTML with all FRAGMENT_TAG instances shifted to an attribute
|
||||||
|
def defragmentize(html):
|
||||||
|
dom = etree.fromstring(html)
|
||||||
|
fragments = dom.xpath("//*[contains(text(), '%s')]" % FRAGMENT_TAG)
|
||||||
|
|
||||||
|
for fragment in fragments:
|
||||||
|
class_list = fragment.get('class')
|
||||||
|
if class_list == None:
|
||||||
|
class_list = FRAGMENT_CLASS
|
||||||
|
else:
|
||||||
|
class_list += " %s" % FRAGMENT_CLASS
|
||||||
|
|
||||||
|
fragment.set('class', class_list)
|
||||||
|
fragment.text = re.sub(r"\s*%s\s*" % re.escape(FRAGMENT_TAG), '', fragment.text)
|
||||||
|
|
||||||
|
return etree.tostring(dom, method='html', encoding='utf-8',
|
||||||
|
pretty_print=True).decode('utf-8')
|
||||||
30
main.py
30
main.py
@ -6,6 +6,7 @@ import pypandoc
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import yaml
|
import yaml
|
||||||
|
import fragments
|
||||||
|
|
||||||
root = ""
|
root = ""
|
||||||
lang = "de"
|
lang = "de"
|
||||||
@ -20,6 +21,7 @@ def compile(directory, language='en'):
|
|||||||
wrapper = open(os.path.join(root, 'layouts/root.html'), 'r').read()
|
wrapper = open(os.path.join(root, 'layouts/root.html'), 'r').read()
|
||||||
wrapper = wrapper.replace('@slides', compile_chapters())
|
wrapper = wrapper.replace('@slides', compile_chapters())
|
||||||
wrapper = insert_metadata(wrapper, lang=lang)
|
wrapper = insert_metadata(wrapper, lang=lang)
|
||||||
|
wrapper = fragments.defragmentize(wrapper)
|
||||||
with open(os.path.join(root, 'slides.' + lang + '.html'), 'w+') as output:
|
with open(os.path.join(root, 'slides.' + lang + '.html'), 'w+') as output:
|
||||||
output.write(wrapper)
|
output.write(wrapper)
|
||||||
print('done')
|
print('done')
|
||||||
@ -31,7 +33,7 @@ def insert_metadata(content, lang=None):
|
|||||||
for key, value in metadata.items():
|
for key, value in metadata.items():
|
||||||
placeholder = '@' + key
|
placeholder = '@' + key
|
||||||
filler = value[lang]
|
filler = value[lang]
|
||||||
print('replace', placeholder, 'with', filler)
|
# print('replace', placeholder, 'with', filler)
|
||||||
if '@' + key in content:
|
if '@' + key in content:
|
||||||
content = content.replace(placeholder, filler)
|
content = content.replace(placeholder, filler)
|
||||||
return content
|
return content
|
||||||
@ -72,7 +74,7 @@ def compile_slide(slide):
|
|||||||
for key, value in slide_data.items():
|
for key, value in slide_data.items():
|
||||||
placeholder = '@' + key
|
placeholder = '@' + key
|
||||||
filler = convert_slide_content(value)
|
filler = convert_slide_content(value)
|
||||||
print('replace', placeholder, 'with', filler)
|
# print('replace', placeholder, 'with', filler)
|
||||||
if '@' + key in slide:
|
if '@' + key in slide:
|
||||||
slide = slide.replace(placeholder, filler)
|
slide = slide.replace(placeholder, filler)
|
||||||
# very unelegant attempt at inline elements
|
# very unelegant attempt at inline elements
|
||||||
@ -112,7 +114,16 @@ def get_slide_layout(layout_name):
|
|||||||
# Calls the pandoc converter to convert a given Markdown string to HTML.
|
# Calls the pandoc converter to convert a given Markdown string to HTML.
|
||||||
# Returns an HTML string.
|
# Returns an HTML string.
|
||||||
def convert_slide_content(content):
|
def convert_slide_content(content):
|
||||||
return pypandoc.convert_text(content, 'html', format='md')
|
filters = []
|
||||||
|
pdoc_args = [
|
||||||
|
'--mathjax', # Preparing formulas
|
||||||
|
'--smart' # Smart typography (dashes, ellipses, …)
|
||||||
|
]
|
||||||
|
return pypandoc.convert_text(
|
||||||
|
content, 'html',
|
||||||
|
format='md',
|
||||||
|
extra_args=pdoc_args,
|
||||||
|
filters=filters)
|
||||||
|
|
||||||
# Calls a YAML parser for the given relative path inside the presentation root
|
# Calls a YAML parser for the given relative path inside the presentation root
|
||||||
# directory. Returns a dictionary with the YAML content.
|
# directory. Returns a dictionary with the YAML content.
|
||||||
@ -124,11 +135,20 @@ def read_yaml(path):
|
|||||||
except yaml.YAMLError as exc:
|
except yaml.YAMLError as exc:
|
||||||
print(exc)
|
print(exc)
|
||||||
|
|
||||||
|
# Retrieve the available languages as stated in the meta.yml (key: language).
|
||||||
|
def get_available_languages(root):
|
||||||
|
return read_yaml(os.path.join(root, 'meta.yml'))['language']
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("rootdirectory", help="your project's root directory")
|
parser.add_argument("rootdirectory", help="your project's root directory")
|
||||||
parser.add_argument("-l", "--language", default="de", help="the presentation language (default: de)")
|
parser.add_argument("-l", "--language", default="all", help="the presentation language (default: all)")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
compile(args.rootdirectory, language=args.language)
|
|
||||||
|
if args.language == "all":
|
||||||
|
for language in get_available_languages(args.rootdirectory):
|
||||||
|
compile(args.rootdirectory, language=language)
|
||||||
|
else:
|
||||||
|
compile(args.rootdirectory, language=args.language)
|
||||||
|
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
pypandoc==1.4
|
pypandoc==1.4
|
||||||
|
lxml==4.5.0
|
||||||
132
slidify.js
132
slidify.js
@ -2,16 +2,27 @@ window.addEventListener('load', init);
|
|||||||
|
|
||||||
let slides;
|
let slides;
|
||||||
let currentSlide;
|
let currentSlide;
|
||||||
|
let topicList;
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
slides = Array.from(document.querySelectorAll('article'));
|
slides = Array.from(document.querySelectorAll('article'));
|
||||||
for (let i = 0; i < slides.length; i++) {
|
for (let i = 0; i < slides.length; i++) {
|
||||||
slides[i].id = `slide${i}`;
|
slides[i].id = `slide${i}`;
|
||||||
}
|
}
|
||||||
goToSlide(0);
|
resumeOrGoToStart();
|
||||||
|
topicList = createTopicList();
|
||||||
document.addEventListener('keydown', onKeyPressed);
|
document.addEventListener('keydown', onKeyPressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resumeOrGoToStart() {
|
||||||
|
let urlParts = window.location.href.split('#');
|
||||||
|
if (urlParts.length > 1) {
|
||||||
|
goToSlide(parseInt(urlParts[1].replace('slide', '')))
|
||||||
|
} else {
|
||||||
|
goToSlide(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function goToSlide(index) {
|
function goToSlide(index) {
|
||||||
if (index >= 0 && index < slides.length) {
|
if (index >= 0 && index < slides.length) {
|
||||||
currentSlide = index;
|
currentSlide = index;
|
||||||
@ -20,11 +31,15 @@ function goToSlide(index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function goToPreviousSlide() {
|
function goToPreviousSlide() {
|
||||||
goToSlide(currentSlide - 1);
|
if (!showPreviousFragment()) {
|
||||||
|
goToSlide(currentSlide - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToNextSlide() {
|
function goToNextSlide() {
|
||||||
goToSlide(currentSlide + 1);
|
if (!showNextFragment()) {
|
||||||
|
goToSlide(currentSlide + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToFirstSlide() {
|
function goToFirstSlide() {
|
||||||
@ -39,23 +54,118 @@ function onKeyPressed(event) {
|
|||||||
switch (event.keyCode) {
|
switch (event.keyCode) {
|
||||||
case 34: // page down
|
case 34: // page down
|
||||||
case 40: // arrow down
|
case 40: // arrow down
|
||||||
case 39:
|
case 39: // arrow right
|
||||||
// arrow right
|
|
||||||
goToNextSlide();
|
goToNextSlide();
|
||||||
break;
|
break;
|
||||||
case 33: // page up
|
case 33: // page up
|
||||||
case 38: // arrow up
|
case 38: // arrow up
|
||||||
case 37:
|
case 37: // arrow left
|
||||||
// arrow left
|
|
||||||
goToPreviousSlide();
|
goToPreviousSlide();
|
||||||
break;
|
break;
|
||||||
case 36:
|
case 36: // pos1
|
||||||
// pos1
|
|
||||||
goToFirstSlide();
|
goToFirstSlide();
|
||||||
break;
|
break;
|
||||||
case 35:
|
case 35: // end
|
||||||
// end
|
|
||||||
goToLastSlide();
|
goToLastSlide();
|
||||||
break;
|
break;
|
||||||
|
case 17: // ctrl
|
||||||
|
toggleTopicList();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showNextFragment() {
|
||||||
|
let fragments = [...slides[currentSlide].querySelectorAll('.fragment')]
|
||||||
|
let visible = [...slides[currentSlide].querySelectorAll('.fragment.visible')]
|
||||||
|
|
||||||
|
if (fragments.length == visible.length) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
fragments[visible.length].classList.add('visible')
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPreviousFragment() {
|
||||||
|
let visible = [...slides[currentSlide].querySelectorAll('.fragment.visible')]
|
||||||
|
|
||||||
|
if (visible.length == 0) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
visible[visible.length - 1].classList.remove('visible')
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTopicListContent() {
|
||||||
|
let results = [...document.querySelectorAll('h1, h2, h3, h4, h5, h6')];
|
||||||
|
let resultHtml = '<ul>';
|
||||||
|
let currentLevel = 1;
|
||||||
|
|
||||||
|
results.forEach(element => {
|
||||||
|
let node = element.nodeName;
|
||||||
|
let level = parseInt(node[node.length - 1]);
|
||||||
|
|
||||||
|
let parentElement = element.parentElement
|
||||||
|
while (parentElement.nodeName.toLowerCase() != 'article') {
|
||||||
|
parentElement = parentElement.parentElement;
|
||||||
|
}
|
||||||
|
let parentSlideID = parentElement.id;
|
||||||
|
|
||||||
|
if (level > currentLevel) {
|
||||||
|
resultHtml += `<ul><li><a href="#${parentSlideID}">${element.textContent}</a></li>`;
|
||||||
|
currentLevel = level;
|
||||||
|
} else if (level < currentLevel) {
|
||||||
|
resultHtml += `</ul></li><li><a href="#${parentSlideID}">${element.textContent}</a></li></li>`;
|
||||||
|
currentLevel = level;
|
||||||
|
} else {
|
||||||
|
resultHtml += `<li><a href="#${parentSlideID}">${element.textContent}</a>`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return resultHtml + '</ul>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTopicList() {
|
||||||
|
let topicList = document.createElement(`div`);
|
||||||
|
topicList.innerHTML = getTopicListContent();
|
||||||
|
topicList.classList.add('topic-list')
|
||||||
|
document.body.appendChild(topicList);
|
||||||
|
topicList.style.position = 'fixed';
|
||||||
|
topicList.style.top = '4rem';
|
||||||
|
topicList.style.left = '50%';
|
||||||
|
topicList.style.transform = 'translateX(-50%)';
|
||||||
|
topicList.style.backgroundColor = 'white';
|
||||||
|
topicList.style.padding = '2rem';
|
||||||
|
topicList.style.width = '800px';
|
||||||
|
topicList.style.boxShadow = '0 5px 15px rgba(0, 0, 0, 0.3)';
|
||||||
|
topicList.style.maxHeight = 'calc(100vh - 8rem)';
|
||||||
|
topicList.style.boxSizing = 'border-box';
|
||||||
|
topicList.style.visibility = 'hidden';
|
||||||
|
topicList.style.overflowY = 'scroll';
|
||||||
|
|
||||||
|
topicList.querySelectorAll('a').forEach(link => {
|
||||||
|
link.addEventListener('click', e => topicList.style.visibility = 'hidden');
|
||||||
|
})
|
||||||
|
return topicList;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastCtrlPress = null;
|
||||||
|
|
||||||
|
function toggleTopicList() {
|
||||||
|
if (lastCtrlPress == null) {
|
||||||
|
lastCtrlPress = Date.now();
|
||||||
|
} else {
|
||||||
|
let now = Date.now();
|
||||||
|
if (now - lastCtrlPress < 400) {
|
||||||
|
lastCtrlPress = null;
|
||||||
|
if (topicList.style.visibility === 'visible') {
|
||||||
|
topicList.style.visibility = 'hidden';
|
||||||
|
} else {
|
||||||
|
topicList.style.visibility = 'visible';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lastCtrlPress = Date.now();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user