diff --git a/README.md b/README.md index bd4f43d..19b23af 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ We recommend you to structure your project directory like this: │   └── titlepage.html ├── meta.yml ├── onpoint + │ ├── autocompile.sh │ ├── main.py │ ├── README.md │ └── requirements.txt @@ -45,4 +46,10 @@ We recommend you to structure your project directory like this: ## Updating onPoint -In order to update the version of onPoint in an existing project, simply enter the `onpoint` directory and run `git pull`. \ No newline at end of file +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. \ No newline at end of file diff --git a/autocompile.sh b/autocompile.sh new file mode 100755 index 0000000..f10a240 --- /dev/null +++ b/autocompile.sh @@ -0,0 +1,6 @@ +#! /bin/bash +# Requirements: inotify-tools, python3 + +while inotifywait -e close_write ./slides/*.md; do + python3 ./onpoint/main.py . +done \ No newline at end of file diff --git a/fragments.py b/fragments.py new file mode 100644 index 0000000..cf3ffcb --- /dev/null +++ b/fragments.py @@ -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') diff --git a/main.py b/main.py index 378c3ab..015ea21 100644 --- a/main.py +++ b/main.py @@ -6,6 +6,7 @@ import pypandoc import re import sys import yaml +import fragments root = "" lang = "de" @@ -20,6 +21,7 @@ def compile(directory, language='en'): wrapper = open(os.path.join(root, 'layouts/root.html'), 'r').read() wrapper = wrapper.replace('@slides', compile_chapters()) wrapper = insert_metadata(wrapper, lang=lang) + wrapper = fragments.defragmentize(wrapper) with open(os.path.join(root, 'slides.' + lang + '.html'), 'w+') as output: output.write(wrapper) print('done') @@ -31,7 +33,7 @@ def insert_metadata(content, lang=None): for key, value in metadata.items(): placeholder = '@' + key filler = value[lang] - print('replace', placeholder, 'with', filler) + # print('replace', placeholder, 'with', filler) if '@' + key in content: content = content.replace(placeholder, filler) return content @@ -72,7 +74,7 @@ def compile_slide(slide): for key, value in slide_data.items(): placeholder = '@' + key filler = convert_slide_content(value) - print('replace', placeholder, 'with', filler) + # print('replace', placeholder, 'with', filler) if '@' + key in slide: slide = slide.replace(placeholder, filler) # 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. # Returns an HTML string. 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 # directory. Returns a dictionary with the YAML content. @@ -124,11 +135,20 @@ def read_yaml(path): except yaml.YAMLError as 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__': parser = argparse.ArgumentParser() 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() - 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) diff --git a/requirements.txt b/requirements.txt index 9e22c61..9d690da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ pypandoc==1.4 +lxml==4.5.0 \ No newline at end of file diff --git a/slidify.js b/slidify.js index 77e195b..6b78bd6 100644 --- a/slidify.js +++ b/slidify.js @@ -2,16 +2,27 @@ window.addEventListener('load', init); let slides; let currentSlide; +let topicList; function init() { slides = Array.from(document.querySelectorAll('article')); for (let i = 0; i < slides.length; i++) { slides[i].id = `slide${i}`; } - goToSlide(0); + resumeOrGoToStart(); + topicList = createTopicList(); 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) { if (index >= 0 && index < slides.length) { currentSlide = index; @@ -20,11 +31,15 @@ function goToSlide(index) { } function goToPreviousSlide() { - goToSlide(currentSlide - 1); + if (!showPreviousFragment()) { + goToSlide(currentSlide - 1); + } } function goToNextSlide() { - goToSlide(currentSlide + 1); + if (!showNextFragment()) { + goToSlide(currentSlide + 1); + } } function goToFirstSlide() { @@ -39,23 +54,118 @@ function onKeyPressed(event) { switch (event.keyCode) { case 34: // page down case 40: // arrow down - case 39: - // arrow right + case 39: // arrow right goToNextSlide(); break; case 33: // page up case 38: // arrow up - case 37: - // arrow left + case 37: // arrow left goToPreviousSlide(); break; - case 36: - // pos1 + case 36: // pos1 goToFirstSlide(); break; - case 35: - // end + case 35: // end goToLastSlide(); 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 = ''; +} + +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(); + } } } \ No newline at end of file