diff --git a/.gitignore b/.gitignore index 21345cf835b8d3e03f98561d813f31dce9736463..2ca4fc00325ed6238029d8b6e95c9cc09196ccd1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ /output /public /content -/__pycache__ +__pycache__ *.swp diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f2ed0f213d068b38e04aa5aafc143dded75dd0f4..6fcd50118a50ac8cc79399e814fef7371342c430 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,8 +12,7 @@ site: before_script: - apt-get update && apt-get install -y --no-install-recommends python3-pip python3 make git wget software-properties-common - wget -O pandoc.deb https://github.com/jgm/pandoc/releases/download/2.18/pandoc-2.18-1-amd64.deb && dpkg -i pandoc.deb && rm --interactive=never pandoc.deb - - ./installpelican.sh - - pip3 install tzlocal markdown pelican-pandoc-reader gitpython typogrify + - pip3 install -r requirements.txt - git clone https://gitlab.gwdg.de/GAUMI-fginfo/fg-website-data.git content script: - make publish diff --git a/Makefile b/Makefile index 598b3270b3abb0e27795488172a2a38d26d56654..bdfdb460dca80e7e2f615ea6373000b0fdd122e3 100644 --- a/Makefile +++ b/Makefile @@ -1,91 +1,9 @@ -PY?= -PELICAN?=pelican -PELICANOPTS= +.PHONY: html +html: run -BASEDIR=$(CURDIR) -INPUTDIR=$(BASEDIR)/content -OUTPUTDIR=$(BASEDIR)/output -CONFFILE=$(BASEDIR)/pelicanconf.py -PUBLISHCONF=$(BASEDIR)/publishconf.py +.PHONY: publish +publish: run -SSH_HOST=fg.informatik.uni-goettingen.de -SSH_PORT=22 -SSH_USER=gitlab -SSH_TARGET_DIR=/home/gitlab/public - - -DEBUG ?= 0 -ifeq ($(DEBUG), 1) - PELICANOPTS += -D -endif - -RELATIVE ?= 0 -ifeq ($(RELATIVE), 1) - PELICANOPTS += --relative-urls -endif - -SERVER ?= "0.0.0.0" - -PORT ?= 0 -ifneq ($(PORT), 0) - PELICANOPTS += -p $(PORT) -endif - -.PHONY: default -default: html - -help: - @echo 'Makefile für die Webseite der Fachgruppe Informatik. ' - @echo ' ' - @echo 'Usage: ' - @echo ' make html (re)generate the web site ' - @echo ' make clean remove the generated files ' - @echo ' make regenerate regenerate files upon modification ' - @echo ' make publish generate using production settings ' - @echo ' make serve [PORT=8000] serve site at http://localhost:8000' - @echo ' make serve-global [SERVER=0.0.0.0] serve (as root) to $(SERVER):80 ' - @echo ' make devserver [PORT=8000] serve and regenerate together ' - @echo ' make devserver-global regenerate and serve on 0.0.0.0 ' - @#echo ' make ssh_upload upload the web site via SSH ' - @#echo ' make sftp_upload upload the web site via SFTP ' - @#echo ' make rsync_upload upload the web site via rsync+ssh ' - @echo ' ' - @echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html ' - @echo 'Set the RELATIVE variable to 1 to enable relative urls ' - @echo ' ' - -html: - "$(PELICAN)" "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) - -clean: - [ ! -d "$(OUTPUTDIR)" ] || rm -rf "$(OUTPUTDIR)" - -regenerate: - "$(PELICAN)" -r "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) - -serve: - "$(PELICAN)" -l "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) - -serve-global: - "$(PELICAN)" -l "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) -b $(SERVER) - -devserver: - "$(PELICAN)" -lr "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) - -devserver-global: - $(PELICAN) -lr $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -b 0.0.0.0 - -publish: - "$(PELICAN)" "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(PUBLISHCONF)" $(PELICANOPTS) - -ssh_upload: publish - scp -P $(SSH_PORT) -r "$(OUTPUTDIR)"/* "$(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)" - -sftp_upload: publish - printf 'put -r $(OUTPUTDIR)/*' | sftp $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) - -rsync_upload: publish - rsync -e "ssh -p $(SSH_PORT)" -P -rvzc --include tags --cvs-exclude --delete "$(OUTPUTDIR)"/ "$(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)" - - -.PHONY: html help clean regenerate serve serve-global devserver publish ssh_upload rsync_upload +.PHONY: run +run: + make -C fgs diff --git a/config.json b/config.json new file mode 100644 index 0000000000000000000000000000000000000000..9acaf807aa1c485346cf1749283d2d77ccfab50b --- /dev/null +++ b/config.json @@ -0,0 +1,89 @@ +{ + "relative_urls": true, + "static_directories": [ + "images" + ], + "theme": { + "default_tag_color": "hsl(289, 43%, 56%)", + "homepage_slug": "index", + "static_dir": "theme", + "css_file": "main.css", + "default_template": "page.html" + }, + "default_status": "published", + "toc_depth": 3, + "pandoc": { + "base": "markdown", + "args": [ + "--table-of-contents" + ], + "extensions": [ + "+abbreviations", + "+all_symbols_escapable", + "+ascii_identifiers", + "+auto_identifiers", + "+autolink_bare_uris", + "+backtick_code_blocks", + "+bracketed_spans", + "+definition_lists", + "+emoji", + "+escaped_line_breaks", + "+example_lists", + "+fancy_lists", + "+fenced_code_attributes", + "+fenced_code_blocks", + "+fenced_divs", + "+footnotes", + "+gfm_auto_identifiers", + "+grid_tables", + "+hard_line_breaks", + "+header_attributes", + "+implicit_figures", + "+implicit_header_references", + "+inline_code_attributes", + "+inline_notes", + "+intraword_underscores", + "+latex_macros", + "+line_blocks", + "+link_attributes", + "+lists_without_preceding_blankline", + "+multiline_tables", + "+native_divs", + "+native_spans", + "+pipe_tables", + "+raw_attribute", + "+raw_html", + "+raw_tex", + "+shortcut_reference_links", + "+simple_tables", + "+smart", + "+space_in_atx_header", + "+startnum", + "+strikeout", + "+subscript", + "+superscript", + "+table_captions", + "+tex_math_dollars", + "+tex_math_single_backslash", + "+yaml_metadata_block", + "-angle_brackets_escapable", + "-blank_before_blockquote", + "-blank_before_header", + "-citations", + "-compact_definition_lists", + "-east_asian_line_breaks", + "-four_space_rule", + "-ignore_line_breaks", + "-literate_haskell", + "-markdown_attribute", + "-markdown_in_html_blocks", + "-mmd_header_identifiers", + "-mmd_link_attributes", + "-mmd_title_block", + "-old_dashes", + "-pandoc_title_block", + "-spaced_reference_links", + "-tex_math_double_backslash" + ] + } +} diff --git a/fgs/Makefile b/fgs/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..6cfa260bbf2297c11be043f09ddb62a9ada3e30e --- /dev/null +++ b/fgs/Makefile @@ -0,0 +1,4 @@ + +.PHONY: run +run: + python3 __main__.py diff --git a/fgs/__main__.py b/fgs/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..0d984cd755b893141d14f1ead70d85963dd47e81 --- /dev/null +++ b/fgs/__main__.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 + +import json +import os +import sys + + +import reader +import generator +import writer + +CONTENT_DIR = '../content' +OUTPUT_DIR = '../output' +THEME_DIR = '../theme' + + +def main(): + print("Hello World") + config = {} + with open('../config.json') as f: + config.update(json.loads(f.read())) + with open('../lang.json') as f: + config['lang'] = json.loads(f.read()) + with open(CONTENT_DIR + '/config.json') as f: + config.update(json.loads(f.read())) + print(config) + + mdreader = reader.MarkdownReader(config) + pages = [] + directory = os.path.join(CONTENT_DIR, ".") + parse_dir(directory, pages, mdreader) + + static_dirs = [] + for sdir in config['static_directories']: + if "." in sdir: + raise Exception("Illegal static directory name: ", sdir) + static_dirs.append(CONTENT_DIR + "/"+ sdir) + static_files = {} + for sdir in static_dirs: + read_static_dir(sdir, static_files) + + read_static_dir(THEME_DIR + '/static', static_files, [config['theme']['static_dir']]) + + + context = {} + gen = generator.Generator(config, context) + gen.generate_context(pages, static_files) + + wrt = writer.Writer(config, context, OUTPUT_DIR, THEME_DIR) + + gen.generate_output(wrt) + + + +def read_static_dir(directory, static_files, subpath = []): + print("static_dir: " + directory); + for filename in os.listdir(directory): + fp = os.path.join(directory, filename) + if os.path.isfile(fp) and not filename.startswith("."): + lpath = '/'.join(subpath + [filename]) + with open(fp, "rb") as f: + static_files[lpath] = f.read() + print("read: ", fp, " as: ", lpath) + elif os.path.isdir(fp) and not filename.startswith("."): + read_static_dir(fp, static_files, subpath + [filename]) + + +def parse_dir(directory, pages, mdreader, subpath = []): + print("parse_dir: " + directory); + for filename in os.listdir(directory): + f = os.path.join(directory, filename) + if os.path.isfile(f) and filename.endswith(".md"): + pages.append(mdreader.read_and_parse_file(f, subpath)) + elif os.path.isdir(f) and not filename.startswith("."): + parse_dir(f, pages, mdreader, subpath + [filename]) + + + + + + + + +if __name__ == '__main__': + main() + diff --git a/fgs/generator.py b/fgs/generator.py new file mode 100644 index 0000000000000000000000000000000000000000..e620c4aa0da86495de7be445d12e81d09b6ad8bb --- /dev/null +++ b/fgs/generator.py @@ -0,0 +1,92 @@ + +class Generator: + def __init__(self, config, context): + self.config = config + self.context = context + + def generate_context(self, pages, static_files): + # static_files + self.context['static_files'] = static_files + + published_pages = [] + for page in pages: + if page.status == "published": + published_pages.append(page) + + + # pages + all_pages = {} + for page in published_pages: + if page.lang not in all_pages: + all_pages[page.lang] = {} + if page.slug in all_pages[page.lang]: + raise Exception("duplicate language (",lang,") for slug '", slug ,"'") + all_pages[page.lang][page.slug] = page + self.context['pages'] = all_pages + + # pages_modified + pages_modified = {} + for lang in self.config['lang']['supported']: + lang_pages = [] + for page in published_pages: + if page.lang == lang: + lang_pages.append(page) + lang_pages.sort() + pages_modified[lang] = lang_pages + self.context['pages_modified'] = pages_modified + + # TODO hidden pages + # TODO draft pages + # TODO authors + + # categories + categories = {} + for page in published_pages: + if page.lang not in categories: + categories[page.lang] = {} + if page.category not in categories[page.lang]: + categories[page.lang][page.category] = [] + categories[page.lang][page.category].append(page) + self.context['categories'] = categories + + # tags + tags = {} + for page in published_pages: + for tag in page.tags: + if page.lang not in tags: + tags[page.lang] = {} + if tag not in tags[page.lang]: + tags[page.lang][tag] = [] + tags[page.lang][tag].append(page) + self.context['tags'] = tags + + + + def generate_homepage(self, writer, lang, path): + page = self.context['pages'][lang][self.config['theme']['homepage_slug']] + writer.write_template(page.template, path , lang, {'page': page}) + + + def generate_output(self, writer): + for sf, raw in self.context['static_files'].items(): + print ("writing binary file: ", sf) + writer.write_file(sf, raw, mode="wb") + + for lang in self.config['lang']['supported']: + # all pages + for page in self.context['pages'][lang].values(): + writer.write_template(page.template, page.url, lang, {'page': page}) + + # all tags + for tag, tagpages in self.context['tags'][lang].items(): + writer.write_template('tag.html', lang + '/tag/' + tag + '.html', lang, {'tag': tag, 'tagpages': tagpages}) + + # homepages for languages + self.generate_homepage(writer, lang, lang + "/index.html") + + + # homepage + self.generate_homepage(writer, self.config['lang']['default'], "index.html") + + + diff --git a/fgs/page.py b/fgs/page.py new file mode 100644 index 0000000000000000000000000000000000000000..801b5959f0795e08ed237509f302746723fe7f96 --- /dev/null +++ b/fgs/page.py @@ -0,0 +1,103 @@ +class Page: + + #filename = None + #subpath = None + #raw = None + #metadata = None + #content = None + #toc = None + #title = None + #category = None + #slug = None + #lang = None + #date_created = None + #date_modified = None + #status = None + #authors = None + #tags = None + #template = None + + + def __init__(self, filename, subpath, raw, metadata, content, toc, title, category, slug, lang, date_created, date_modified, authors, last_modification_author, status, config): + self.filename = filename + self.subpath = subpath + self.raw = raw + self.metadata = metadata + self.content = content + self.toc = toc + self.title = title + self.category = category + self.slug = slug + self.lang = lang + self.date_created = Date(date_created, config) + self.date_modified = Date(date_modified, config) + self.status = status + + #self.config = config + + # authors + self.authors = [] + for local_part, domain, name in authors: + self.authors.append(Author(local_part, domain, name, config)) + if last_modification_author: + self.last_modification_author = Author(last_modification_author[0], last_modification_author[1], last_modification_author[2], config) + else: + self.last_modification_author = None + #print(authors) + #print(last_modification_author) + + + # tags + self.tags = [] + if 'tags' in metadata: + tags = self.tags_str_to_list(metadata['tags']) + for t in tags: + if (t != category): + self.tags.append(t) + self.tags.append(self.category) # the category is also a default tag + + # template + if 'template' in metadata: + self.template = metadata['template'] + else: + self.template = config['theme']['default_template'] + + # url + self.url = self.lang + '/' + self.category + '/' + self.slug + ".html" + + def tags_str_to_list(self, s): + res = [] + l = s.split(',') + for t in l: + t = t.lower() + t = t.lstrip() + t = t.rstrip() + if len(t) > 0: + res.append(t) + return res + + + + def __lt__(self, other): + return self.date_modified < other.date_modified + + +class Date: + def __init__(self, dt, config): + self.dt = dt + #self.config = config + # TODO various formats + def isoformat(self): + return self.dt.isoformat() + + def __lt__(self, other): + return self.dt.timestamp() < other.dt.timestamp() + +class Author: + def __init__(self, email_local_part, email_domain, name, config): + self.email_local_part = email_local_part + self.email_domain = email_domain + self.email = email_local_part + '@' + email_domain + self.name = name + #self.config = config + # TODO md5 hash etc. diff --git a/fgs/pandoc_toc.html b/fgs/pandoc_toc.html new file mode 100644 index 0000000000000000000000000000000000000000..2fd5a7bd68b1d9474ccc9e42a8dc058a8801e06b --- /dev/null +++ b/fgs/pandoc_toc.html @@ -0,0 +1 @@ +$table-of-contents$ diff --git a/fgs/reader.py b/fgs/reader.py new file mode 100644 index 0000000000000000000000000000000000000000..9cc14065d9781c638546842e82415482dac9354a --- /dev/null +++ b/fgs/reader.py @@ -0,0 +1,218 @@ +import page + +import frontmatter + +from datetime import datetime +from dateutil import parser as dtparser + +import subprocess +import os + +class MarkdownReader: + + def __init__(self, config): + self.config = config + + + def read_and_parse_file(self, path, subpath): + if not path.endswith(".md"): + raise Exception("can only parse markdown files: ", path) + elif len(subpath) > 1: + raise Exception("markdown file is too deep in directory structure: ", path, subpath) + + + print("parsing file: ", path, subpath) + + f = open(path) + rawcontent = f.read() + f.close() + metadata, _ = frontmatter.parse(rawcontent) + + #print(metadata) + + category_name = self.get_category_name(metadata, subpath) + + # content + content = self.run_pandoc(rawcontent, self.config['pandoc']['base'], self.config['pandoc']['extensions'], "html5") + #print(content) + + # TOC + toc = self.run_pandoc(rawcontent, self.config['pandoc']['base'], self.config['pandoc']['extensions'], "html5", ["--template", "./pandoc_toc.html", "--toc", "--toc-depth", str(self.config['toc_depth'])]) + #print((toc)) + + # title + if 'title' not in metadata: + raise Exception("File is missing title in metadata: ", path, subpath) + title = metadata['title'] + + # slug and lang + pathlist = path.split('/') + filename = pathlist[-1] + filenamelist = filename.split('.') + filenamelist = filenamelist[:-1] # remove .md + slug = None + lang = None + if len(filenamelist) < 1: + raise Exception("filename is empty?", path, subpath) + elif len(filenamelist) == 1: + slug = filenamelist[0] + elif len(filenamelist) == 2: + slug = filenamelist[0] + lang = filenamelist[1] + if 'slug' in metadata: + slug = metadata['slug'] + if 'lang' in metadata: + lang = metadata['lang'] + + if lang == None: + lang = self.config['lang']['default'] + + if not self.is_supported_lang(lang): + raise Exception("language is not supported: ", lang) + slug = self.secure_slug(slug) + #print("slug: ", slug) + #print("lang: ", lang) + + # date_created and date_modified + date_modified = datetime.now() + date_created = datetime.now() + date_changes = self.run_git(path, "log", ["--follow", "--format=%ad", "--date", "iso-strict"]).splitlines() + #print("date_changes: ", date_changes) + if (len(date_changes) > 0): + date_modified = datetime.fromisoformat(date_changes[0]) + date_created = datetime.fromisoformat(date_changes[-1]) + if 'date' in metadata: + date_created = dtparser.parse(metadata['date']) + if 'modified' in metadata: + date_modified = dtparser.parse(metadata['modified']) + #print("created: ", date_created) + #print("last changed: ", date_modified) + + # author + # TODO author from metadata + authors_raw = self.run_git(path, "log", ["--follow", "--format=%aE@%aN", "--use-mailmap"]).splitlines() + authors = [] + known_author_raws = [] + for author_raw in authors_raw: + if author_raw not in known_author_raws: + authors.append(self.extract_author(author_raw)) + known_author_raws.append(author_raw) + if len(authors_raw) > 0: + last_modification_author = self.extract_author(authors_raw[0]) + else: + last_modification_author = None + + + + # status + status = self.config['default_status'] + if 'status' in metadata: + status = metadata['status'] + valid_status = ["published", "draft", "hidden"] + if status not in valid_status: + raise Exception("invalid status '", status, "' must be one of ", valid_status) + + # TODO summary + + p = page.Page( + filename, + subpath, + rawcontent, + metadata, + content, + toc, + title, + category_name, + slug, + lang, + date_created, + date_modified, + authors, + last_modification_author, + status, + self.config) + return p + + def extract_author(self, raw): + author_split = raw.split('@') + author_local_part = author_split[0] + author_domain = author_split[1] + author_name = '@'.join(author_split[2:]) + return (author_local_part, author_domain, author_name) + + def is_supported_lang(self, lang): + if not isinstance(lang, str): + return False + return (lang in self.config['lang']['supported']) + + + def secure_slug(self, slug): + if not isinstance(slug, str): + raise Exception("slug is not a string: '", slug, "'") + slug = slug.lower() + whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-" + res = "" + for c in slug: + if c in whitelist: + #print ("c: '", c,"'") + res += c + #print("res: '", res, "'") + if len(res) == 0: + raise Exception("slug is empty") + return res + + + def get_category_name(self, metadata, subpath): + if 'category' in metadata: + return metadata['category'] + elif len(subpath) == 1: + return subpath[0] + else: + return 'misc' + + def run_git(self, path, subcmd, extra_args): + real_path = os.path.realpath(path) + filename = os.path.basename(real_path) + dir_path = os.path.dirname(real_path) + git_bin = "git" + args = [git_bin, subcmd] + extra_args + ["--", filename] + p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=dir_path) + out, _ = p.communicate("".encode('utf-8', errors='strict')) + out_str = out.decode('utf-8') + return out_str + + + def run_pandoc(self, source, base="markdown", extensions=[], to="json", extra_args=[]): + ext_str = "" + if isinstance(extensions, list): + for ext in extensions: + if ext.startswith('#'): + continue + if ext.startswith('+') or ext.startswith('-'): + ext_str = ext_str + ext + elif len(ext) > 0: + ext_str = ext_str + '+' + ext + elif isinstance(extensions, dict): + for ext_key in extensions: + # TODO catch 'illegal' ext_keys (containing spaces for example) + ext = extensions[ext_key] + if "ignore" in ext and ext["ignore"]: + continue + flag='+' + if "enabled" in ext and not ext["enabled"]: + flag='-' + ext_str = ext_str + flag + ext_key + + #print(ext_str) + pandoc_bin = "pandoc" + args = [pandoc_bin, "-f", base + ext_str, "-t", to] + extra_args + p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + out, _ = p.communicate(source.encode('utf-8', errors='strict')) + out_str = out.decode('utf-8') + #print("----------------------") + #print(out_str) + #print("----------------------") + #json_dict = json.loads(out.decode('utf-8')) + return out_str + + diff --git a/fgs/writer.py b/fgs/writer.py new file mode 100644 index 0000000000000000000000000000000000000000..a5eb1e6098b5f427b24e4e72d6ad7eddd6e26d25 --- /dev/null +++ b/fgs/writer.py @@ -0,0 +1,61 @@ +from jinja2 import Environment, FileSystemLoader + +import os + +class Writer: + + def __init__(self, config, context, output_dir, theme_dir): + self.config = config + self.context = context + self.output_dir = output_dir + self.theme_dir = theme_dir + + self.env = Environment( + loader=FileSystemLoader(theme_dir + "/templates"), + autoescape=False + ) + + print("templates: ", self.env.list_templates()) + + def write_template(self, template, path, lang, extra_context): + tmpl = self.env.get_template(template) + + pathsplit = path.split('/') + #pathsplit.remove('.') # TODO remove all not just one + #pathsplit.remove('..') # TODO remove all not just one + + siteurl = "." + if self.config['relative_urls']: + count = len(pathsplit) - 1 + for i in range(count): + siteurl = siteurl + '/..' + else: + siteurl = config['siteurl'] + + # render template + context = {} + context.update(self.context) + context["config"] = self.config + context["theme"] = self.config['theme'] + context["template"] = template + context["t"] = self.config["lang"] # translate + context["l"] = lang # current language + context["path"] = path + context["siteurl"] = siteurl + context.update(extra_context) + out = tmpl.render(context) + + # write file + self.write_file(path, out) + + def write_file(self, path, out, mode="w"): + # write to file + fullpath = self.output_dir + '/' + path + directory = os.path.dirname(fullpath) + print("fullpath: ", fullpath) + print("dir: ", directory) + os.makedirs(directory, exist_ok=True) + with open(fullpath, mode) as f: + f.write(out) + + diff --git a/installpelican.sh b/installpelican.sh deleted file mode 100755 index 639a843245a3868b73776768d4515dbf354d7b85..0000000000000000000000000000000000000000 --- a/installpelican.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -set -e - -# Install Pelican with dependencies -pip3 install git+https://gitlab.gwdg.de/GAUMI-fginfo/pelican.git - -# Reinstall Pelican to make sure it is up-to-date -pip3 install --force-reinstall --no-cache-dir --no-deps git+https://gitlab.gwdg.de/GAUMI-fginfo/pelican.git diff --git a/lang.json b/lang.json index 510c99970da47f20d02524bb6ace12a5c8638ee9..56055dc73d694c7ec93b3c53a18cc55145f2511f 100644 --- a/lang.json +++ b/lang.json @@ -1,4 +1,8 @@ { + "default": "de", + "supported": [ + "de" + ], "de": { "langname": "Deutsch", "sitename": "Fachgruppe Informatik", diff --git a/pelican-plugins b/pelican-plugins deleted file mode 160000 index 483215d2e1998684b7f413b6b1c8e26058fd2e6b..0000000000000000000000000000000000000000 --- a/pelican-plugins +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 483215d2e1998684b7f413b6b1c8e26058fd2e6b diff --git a/pelicanconf.py b/pelicanconf.py deleted file mode 100644 index 39e4fdc4922c900be343d4fbd306521850bc2c0b..0000000000000000000000000000000000000000 --- a/pelicanconf.py +++ /dev/null @@ -1,120 +0,0 @@ -import json - -AUTHOR = 'Fachgruppe Informatik' -SITENAME = 'Dieser Wert wird ignoriert und soll stattdessen in lang.json geändert werden.' -SITEURL = '' - -PATH = 'content' - -TIMEZONE = 'Europe/Berlin' - -DEFAULT_LANG = 'de' - -# Feed generation is usually not desired when developing -FEED_ALL_ATOM = None -CATEGORY_FEED_ATOM = None -TRANSLATION_FEED_ATOM = None -AUTHOR_FEED_ATOM = None -AUTHOR_FEED_RSS = None - -#DEFAULT_PAGINATION = 10 -DEFAULT_PAGINATION = False - -# Uncomment following line if you want document-relative URLs when developing -RELATIVE_URLS = False - -OUTPUT_SOURCES = True - -TYPOGRIFY = True - -STATIC_PATHS = ['images'] - - -PANDOC_ARGS = [ -# "--mathjax", -# "--citeproc", - "--table-of-contents", -] - -PANDOC_EXTENSIONS = [ - "+abbreviations", - "+all_symbols_escapable", - "+ascii_identifiers", - "+auto_identifiers", - "+autolink_bare_uris", - "+backtick_code_blocks", - "+bracketed_spans", - "+definition_lists", - "+emoji", - "+escaped_line_breaks", - "+example_lists", - "+fancy_lists", - "+fenced_code_attributes", - "+fenced_code_blocks", - "+fenced_divs", - "+footnotes", - "+gfm_auto_identifiers", - "+grid_tables", - "+hard_line_breaks", - "+header_attributes", - "+implicit_figures", - "+implicit_header_references", - "+inline_code_attributes", - "+inline_notes", - "+intraword_underscores", - "+latex_macros", - "+line_blocks", - "+link_attributes", - "+lists_without_preceding_blankline", - "+multiline_tables", - "+native_divs", - "+native_spans", - "+pipe_tables", - "+raw_attribute", - "+raw_html", - "+raw_tex", - "+shortcut_reference_links", - "+simple_tables", - "+smart", - "+space_in_atx_header", - "+startnum", - "+strikeout", - "+subscript", - "+superscript", - "+table_captions", - "+tex_math_dollars", - "+tex_math_single_backslash", - "+yaml_metadata_block", - "-angle_brackets_escapable", - "-blank_before_blockquote", - "-blank_before_header", - "-citations", - "-compact_definition_lists", - "-east_asian_line_breaks", - "-four_space_rule", - "-ignore_line_breaks", - "-literate_haskell", - "-markdown_attribute", - "-markdown_in_html_blocks", - "-mmd_header_identifiers", - "-mmd_link_attributes", - "-mmd_title_block", - "-old_dashes", - "-pandoc_title_block", - "-spaced_reference_links", - "-tex_math_double_backslash", -] - -CALCULATE_READING_TIME = True - - -PLUGIN_PATHS = ['./pelican-plugins'] -PLUGINS = ['filetime_from_git'] - -THEME = './theme' -JINJA_GLOBALS = {} -with open('./lang.json') as json_file: - JINJA_GLOBALS['l'] = json.load(json_file) -with open(PATH + '/config.json') as json_file: - JINJA_GLOBALS['sc'] = json.load(json_file) - diff --git a/publishconf.py b/publishconf.py deleted file mode 100644 index 7ca19fcc6061a8fcfc8df72b5b1a6be5bcaa5b9e..0000000000000000000000000000000000000000 --- a/publishconf.py +++ /dev/null @@ -1,21 +0,0 @@ -# This file is only used if you use `make publish` or -# explicitly specify it as your config file. - -import os -import sys -sys.path.append(os.curdir) -from pelicanconf import * - -# If your site is available via HTTPS, make sure SITEURL begins with https:// -SITEURL = 'https://fg.informatik.uni-goettingen.de' -RELATIVE_URLS = False - -FEED_ALL_ATOM = 'feeds/all.atom.xml' -CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml' - -DELETE_OUTPUT_DIRECTORY = True - -# Following items are often useful when publishing - -#DISQUS_SITENAME = "" -#GOOGLE_ANALYTICS = "" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..82138ee947a568907e0ced324fa2529ed5ef3058 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +python-frontmatter +pypandoc +jinja2 +dateutils diff --git a/tasks.py b/tasks.py deleted file mode 100644 index 559d52b49ece3ecb47abc1b33046f1aa2b5f150d..0000000000000000000000000000000000000000 --- a/tasks.py +++ /dev/null @@ -1,143 +0,0 @@ -# -*- coding: utf-8 -*- - -import os -import shlex -import shutil -import sys -import datetime - -from invoke import task -from invoke.main import program -from invoke.util import cd -from pelican import main as pelican_main -from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer -from pelican.settings import DEFAULT_CONFIG, get_settings_from_file - -OPEN_BROWSER_ON_SERVE = True -SETTINGS_FILE_BASE = 'pelicanconf.py' -SETTINGS = {} -SETTINGS.update(DEFAULT_CONFIG) -LOCAL_SETTINGS = get_settings_from_file(SETTINGS_FILE_BASE) -SETTINGS.update(LOCAL_SETTINGS) - -CONFIG = { - 'settings_base': SETTINGS_FILE_BASE, - 'settings_publish': 'publishconf.py', - # Output path. Can be absolute or relative to tasks.py. Default: 'output' - 'deploy_path': SETTINGS['OUTPUT_PATH'], - # Remote server configuration - 'ssh_user': 'gitlab', - 'ssh_host': 'fg.informatik.uni-goettingen.de', - 'ssh_port': '22', - 'ssh_path': '/home/gitlab/public', - # Host and port for `serve` - 'host': 'localhost', - 'port': 8000, -} - -@task -def clean(c): - """Remove generated files""" - if os.path.isdir(CONFIG['deploy_path']): - shutil.rmtree(CONFIG['deploy_path']) - os.makedirs(CONFIG['deploy_path']) - -@task -def build(c): - """Build local version of site""" - pelican_run('-s {settings_base}'.format(**CONFIG)) - -@task -def rebuild(c): - """`build` with the delete switch""" - pelican_run('-d -s {settings_base}'.format(**CONFIG)) - -@task -def regenerate(c): - """Automatically regenerate site upon file modification""" - pelican_run('-r -s {settings_base}'.format(**CONFIG)) - -@task -def serve(c): - """Serve site at http://$HOST:$PORT/ (default is localhost:8000)""" - - class AddressReuseTCPServer(RootedHTTPServer): - allow_reuse_address = True - - server = AddressReuseTCPServer( - CONFIG['deploy_path'], - (CONFIG['host'], CONFIG['port']), - ComplexHTTPRequestHandler) - - if OPEN_BROWSER_ON_SERVE: - # Open site in default browser - import webbrowser - webbrowser.open("http://{host}:{port}".format(**CONFIG)) - - sys.stderr.write('Serving at {host}:{port} ...\n'.format(**CONFIG)) - server.serve_forever() - -@task -def reserve(c): - """`build`, then `serve`""" - build(c) - serve(c) - -@task -def preview(c): - """Build production version of site""" - pelican_run('-s {settings_publish}'.format(**CONFIG)) - -@task -def livereload(c): - """Automatically reload browser tab upon file modification.""" - from livereload import Server - - def cached_build(): - cmd = '-s {settings_base} -e CACHE_CONTENT=True LOAD_CONTENT_CACHE=True' - pelican_run(cmd.format(**CONFIG)) - - cached_build() - server = Server() - theme_path = SETTINGS['THEME'] - watched_globs = [ - CONFIG['settings_base'], - '{}/templates/**/*.html'.format(theme_path), - ] - - content_file_extensions = ['.md', '.rst'] - for extension in content_file_extensions: - content_glob = '{0}/**/*{1}'.format(SETTINGS['PATH'], extension) - watched_globs.append(content_glob) - - static_file_extensions = ['.css', '.js'] - for extension in static_file_extensions: - static_file_glob = '{0}/static/**/*{1}'.format(theme_path, extension) - watched_globs.append(static_file_glob) - - for glob in watched_globs: - server.watch(glob, cached_build) - - if OPEN_BROWSER_ON_SERVE: - # Open site in default browser - import webbrowser - webbrowser.open("http://{host}:{port}".format(**CONFIG)) - - server.serve(host=CONFIG['host'], port=CONFIG['port'], root=CONFIG['deploy_path']) - - -@task -def publish(c): - """Publish to production via rsync""" - pelican_run('-s {settings_publish}'.format(**CONFIG)) - c.run( - 'rsync --delete --exclude ".DS_Store" -pthrvz -c ' - '-e "ssh -p {ssh_port}" ' - '{} {ssh_user}@{ssh_host}:{ssh_path}'.format( - CONFIG['deploy_path'].rstrip('/') + '/', - **CONFIG)) - - -def pelican_run(cmd): - cmd += ' ' + program.core.remainder # allows to pass-through args to pelican - pelican_main(shlex.split(cmd)) \ No newline at end of file diff --git a/theme/templates/Makefile b/theme/templates/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..396366804620681312cf4cb22a4f89294ed09b6f --- /dev/null +++ b/theme/templates/Makefile @@ -0,0 +1,3 @@ +.PHONY: run +run: + make -C ../../fgs diff --git a/theme/templates/article.html b/theme/templates/article.html deleted file mode 100644 index 1a2c28ab4adf8e48fd37a66eaa57e7d741091353..0000000000000000000000000000000000000000 --- a/theme/templates/article.html +++ /dev/null @@ -1,69 +0,0 @@ -{% extends "base.html" %} - -{%- macro article_translation_link(translation, is_current=False) -%} -<a href="{{ SITEURL }}/{{ translation.url }}" hreflang="{{ translation.lang }}" title="{{ l[translation.lang].langname|e }}"> - <img alt="{{ l[translation.lang].langname|e }}" src="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/images/flags/{{ translation.lang }}.svg"> -</a> -{%- endmacro -%} - -{% block html_lang %}{{ article.lang }}{% endblock %} - -{% block title %}{{ article.title|striptags }}{% endblock %} - -{% block extra_head %} - {%- for translation in article.translations -%} - <link rel="alternate" hreflang="{{ translation.lang }}" href="{{ SITEURL }}/{{ translation.url }}"> - {%- endfor -%} - - {% if article.summary %} - <meta name="description" content="{{ article.summary | striptags | safe | truncate(150) }}" /> - {% endif %} -{% endblock %} - -{% block content %} - <article> - <header> - <h1>{{ article.title }}</h1> - </header> - {{ article.content }} - <footer> - {%- if article.authors -%} - <address> - {{ l[lang].article.authors_prefix }} - {% for author in article.authors %} - <a href="{{ SITEURL }}/{{ author.url }}">{{ author }}</a> - {% endfor %} - {{ l[lang].article.authors_suffix }} - </address> - {%- endif -%} - - <span>{{ l[lang].article.published_prefix }} - <abbr title="{{ article.date.isoformat() }}"> - {{ article.locale_date }} - </abbr>{{ l[lang].article.published_suffix }}</span> - - {%- if article.modified -%} - <br /> - <span>{{ l[lang].article.modified_prefix }} - <abbr title="{{ article.modified.isoformat() }}">{{ article.locale_modified }}</abbr> - {{ l[lang].article.modified_suffix }}</span> - {%- endif -%} - - <br /> - <span>{{ l[lang].article.category_prefix }}<a href="{{ SITEURL }}/{{ article.category.url }}">{{ article.category }}</a>{{ l[lang].article.category_suffix }}</span> - <br /> - <span>{{ l[lang].article.languages_prefix }}</span> - <ul class="languages"> - {%- for translation in article.translations -%} - <li> - {{ article_translation_link(translation) }} - </li> - {%- endfor -%} - <li> - {{ article_translation_link(article, True) }} - </li> - </ul> - {{ l[lang].article.languages_suffix }} - </footer> - </article> -{% endblock %} diff --git a/theme/templates/base.html b/theme/templates/base.html index 6466a8f7566c435c8e4cba8909ab67d03bbc5a38..8a346355e90b1416b3231d6471c7890cf684ffc1 100644 --- a/theme/templates/base.html +++ b/theme/templates/base.html @@ -1,42 +1,35 @@ <!DOCTYPE html> -{#- TODO lang irgendwie vernünftig setzen/erkennen -#} -{%- if lang is not defined -%} - {%- set lang = DEFAULT_LANG -%} -{%- else -%} -Hurra!!! lang ist definiert als {{ lang }}. -{{ diese_variable_existiert_nicht_werfe_fehler }} -{%- endif -%} {%- import 'macros/getters.html' as get with context -%} {%- import 'macros/cards.html' as cards with context -%} {%- import 'macros/renderers.html' as render with context -%} -<html lang="{%- block html_lang -%}{{ DEFAULT_LANG }}{%- endblock html_lang -%}"> +<html lang="{%- block html_lang -%}{{ l }}{%- endblock html_lang -%}"> <head> {% block head %} <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>{% block title %}{{ l[lang].title_prefix }}{{ l[lang].sitename }}{{ l[lang].title_suffix }}{%endblock%}</title> + <title>{% block title %}{{ t[l].title_prefix }}{{ t[l].sitename }}{{ t[l].title_suffix }}{%endblock%}</title> <!-- <base target="_blank"> --> <!-- <meta HTTP-EQUIV="REFRESH" content="500; url=#"> --> - <link rel="preload" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/css/{{ CSS_FILE }}" as="style" /> - <link rel="stylesheet" type="text/css" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/css/{{ CSS_FILE }}" /> + <link rel="preload" href="{{ siteurl }}/{{ theme.static_dir }}/css/{{ theme.css_file }}" as="style" /> + <link rel="stylesheet" type="text/css" href="{{ siteurl }}/{{ theme.static_dir }}/css/{{ theme.css_file }}" /> {#- TODO load javascript? -#} {#- TODO og: meta tags -#} {#- TODO favicon -#} <!-- <link rel="icon" type="image/png" sizes="192x192" href="static/img/favicon-192x192.png"/> <link rel="icon" type="image/png" sizes="32x32" href="static/img/favicon-32x32.png"/>--> <!-- <link rel="icon" type="image/x-icon" sizes="16x16" href="static/img/favicon.ico"/> --> - {%- if FEED_ALL_ATOM %} - <link href="{{ FEED_DOMAIN }}/{% if FEED_ALL_ATOM_URL %}{{ FEED_ALL_ATOM_URL }}{% else %}{{ FEED_ALL_ATOM }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ l[lang].atom.title|e }}" /> - {% endif -%} + {#{%- if FEED_ALL_ATOM %} + <link href="{{ FEED_DOMAIN }}/{% if FEED_ALL_ATOM_URL %}{{ FEED_ALL_ATOM_URL }}{% else %}{{ FEED_ALL_ATOM }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ t[l].atom.title|e }}" /> + {% endif -%}#} {% block extra_head %}{% endblock extra_head %} {% endblock head %} </head> <body> <header> {% block header %} - <h1><a href="{{ SITEURL }}/" title="{{ l[lang].banner.title|e }}" ><img alt="{{ l[lang].banner.alt|e }}" src="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/images/banner-logo.png"><span>{{ l[lang].banner.prefix|e }}{{ l[lang].sitename|e }}{{ l[lang].banner.suffix|e }}</span></a></h1> + <h1><a href="{{ siteurl }}/{{ l }}/" title="{{ t[l].banner.title|e }}" ><img alt="{{ t[l].banner.alt|e }}" src="{{ siteurl }}/{{ theme.static_dir }}/images/banner-logo.png"><span>{{ t[l].banner.prefix|e }}{{ t[l].sitename|e }}{{ t[l].banner.suffix|e }}</span></a></h1> {% block extra_header %}{% endblock extra_header %} {% endblock header %} </header> @@ -46,19 +39,15 @@ Hurra!!! lang ist definiert als {{ lang }}. <label for="show-header-menu" class="show-header-menu-bg"> </label> <nav> <ul> - {% for item in sc.menuitems -%} - {%- if item.category is defined -%} - {%- call(nativecat, sccat, _) get.category_by_name(item.category) -%} - <li style="--category-color: {{ sccat.color }}"><a href="{{ SITEURL }}/{{ nativecat.url }}">{{ sccat[lang]|e }}</a></li> - {%- endcall -%} - {%- elif item.tag is defined -%} - {%- call(nativetag, sccat, _) get.tag_by_name(item.tag) -%} - <li style="--category-color: {{ sccat.color }}"><a href="{{ SITEURL }}/{{ nativetag.url }}">{{ sccat[lang]|e }}</a></li> + {% for item in config.menuitems -%} + {%- if item.tag is defined -%} + {%- call(tagtitle, tagcolor, tagurl, tagpage, tagpages) get.tag_by_name(item.tag, l) -%} + <li style="--category-color: {{ tagcolor }}"><a href="{{ siteurl }}/{{ tagurl }}">{{ tagtitle|e }}</a></li> {%- endcall -%} {%- elif item.slug is defined -%} - {%- call(aop, _) get.article_or_page_by_slug(item.slug, lang) -%} - {%- call(__, sccat, ___) get.category_by_name(aop.category.name, True) -%} - <li style="--category-color: {{ sccat.color }}"><a href="{{ SITEURL }}/{{ aop.url }}">{{ aop.title }}</a></li> + {%- call(page) get.page_by_slug(item.slug, l) -%} + {%- call(cattitle, catcolor, caturl, catpage, catpages) get.tag_by_name(page.category, l) -%} + <li style="--category-color: {{ catcolor }}"><a href="{{ siteurl }}/{{ page.url }}">{{ page.title|e }}</a></li> {%- endcall -%} {%- endcall -%} {%- else -%} diff --git a/theme/templates/category.html b/theme/templates/category.html deleted file mode 100644 index 0c2b6e0ff9f6a25c9462dc5910bfcccf62a7aaf7..0000000000000000000000000000000000000000 --- a/theme/templates/category.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "base.html" %} -{% block title %}{{ l[lang].title_prefix }}{{ l[lang].sitename }}{{ l[lang].title_suffix }} - {{ category }}{%endblock%} -{% block content %} - <section> - {{ render.section({"type": "category", "category": category.slug, "num": None}) }} - </section> -{% endblock content %} diff --git a/theme/templates/index.html b/theme/templates/index.html deleted file mode 100644 index d4f2b290de95d8c0f5e42938e0348405c51773ec..0000000000000000000000000000000000000000 --- a/theme/templates/index.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "base.html" %} -{% block content_title %}{% endblock %} - - -{% block content %} -{%- for s in sc.startpage -%} - <section> - {{ render.section(s) }} - </section> -{%- endfor -%} -{% endblock content %} diff --git a/theme/templates/macros/README.md b/theme/templates/macros/README.md deleted file mode 100644 index 75ed3122989219278ca7cac37db4a07f01cc2e7a..0000000000000000000000000000000000000000 --- a/theme/templates/macros/README.md +++ /dev/null @@ -1,184 +0,0 @@ -# Macros - -[TOC] - -## [getters.html](getters.html) - -``` -{%- import 'macros/getters.html' as get with context -%} -``` - -Mit diesen Macros können Informationen abgefragt werden. - -### `category_by_name(catname, ignore_native = False)` - -**Argumente:** - -- `catname` (String): Der Name der Kategorie. -- `ignore_native` (Boolean): Setzt den ersten und dritten Rückgabewert zu `None`. (Verkürzt die Laufzeit dieser Funktion.) - -**Rückgabewerte:** - -1. ([Category](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#object-author-cat-tag)): Die native Kategorie. -2. (Dict): Der Eintrag zu der Kategorie aus der config.json-Datei aus dem Inhaltsrepo. -3. (List\<[Article](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#article)\>): Die Artikel in der Kategorie. - -**Beispiel Aufruf:** - -``` -{%- call(nativecat, sccat, catarticles) get.category_by_name(article.category.name) -%} - <ul> - <li>Kategorie: {{ nativecat.name }}</li> - <li>Farbe: {{ sccat.color }}</li> - <li>Anzahl Artikel: {{ catarticles|length }}</li> - </ul> -{%- endcall -%} -``` - -### `tag_by_name(tagname)` - -**Argumente:** - -- `catname` (String): Der Name des Tags. - -**Rückgabewerte:** - -1. ([Tag](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#object-author-cat-tag)): Der native Tag. -2. (Dict): Falls der Tag gleichnamig zu einer Kategorie ist, dann der Eintrag zu der Kategorie aus der config.json-Datei aus dem Inhaltsrepo, sonst `None`. -3. (List\<[Article](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#article)\>): Die Artikel die diesen Tag haben. - -**Beispiel Aufruf:** - -``` -{%- call(nativetag, sccat, tagarticles) get.tag_by_name("event") -%} - <ul> - <li>Tag: {{ nativetag.name }}</li> - <li>Farbe: {% if sccat %}{{ sccat.color }}{% else %}Dieser Tag ist nicht gleichnamig zu einer Kategorie und hat deshalb keine Farbe.{% endif %}</li> - <li>Anzahl Artikel mit diesem Tag: {{ tagarticles|length }}</li> - </ul> -{%- endcall -%} -``` - - -### `article_by_slug(slug, lang)` - -**Argumente:** - -- `slug` (String): Der Slug von dem Artikel. -- `lang` (String): Die Sprache von dem Artikel. - -**Rückgabewerte:** - -1. ([Article](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#article)): Der Artikel. - -### `page_by_slug(slug, lang)` - -**Argumente:** - -- `slug` (String): Der Slug von der Seite. -- `lang` (String): Die Sprache von der Seite. - -**Rückgabewerte:** - -1. ([Page](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#page)): Die Seite. - -### `article_or_page_by_slug(slug, lang)` - -**Argumente:** - -- `slug` (String): Der Slug von dem Artikel oder der Seite. -- `lang` (String): Die Sprache von dem Artikel oder der Seite. - -**Rückgabewerte:** - -1. ([Article](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#article) | [Page](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#page)): Der Artikel oder die Seite. -2. (String): "article" falls es ein Artikel ist. "page" falls es eine Seite ist. - -## [cards.html](cards.html) - -``` -{%- import 'macros/cards.html' as cards with context -%} -``` - -Mit diesen Macros können Kachelblöcke erstellt werden. - -In einem Kachelblock dürfen nur Kacheln sein und sonst nichts! - -**Beispiel:** - -``` -{{ cards.open() }} - -{%- for article in all_articles -%} - {{ cards.card_from_article_or_page(article) }} -{%- endfor -%} - -{{ cards.card(title="Dies ist auch eine Kachel", url="https://www.youtube.com/watch?v=dQw4w9WgXcQ", catcolor="green") }} - -{{ cards.close() }} -``` - -### `open(id = None, classes = None)` - -Beginnt einen Kachelblock. - -**Argumente:** - -- `id` (String): Die ID von dem Kachelblock. -- `classes` (List\<String\>): Zusätzliche Klassen für den Kachelblock. - -### `close()` - -Schließt einen Kachelblock. - -### `card(title, url, catcolor, escape_title = True, is_url_external = True)` - -Erstellt eine Kachel. - -**Argumente:** - -- `title` (String): Der Titel. -- `url` (String): Der URL. -- `catcolor` (String): Die Farbe. -- `escape_title` (Boolean): Ob `title` escaped werden soll. -- `is_url_external` (Boolean): Ob `url` auf eine externe Resource zeigt, oder als relativen Link interpretiert werden soll. - -### `card_from_article_or_page(aop)` - -Erstellt eine Kachel von einem Artikel oder einer Seite. - -**Argumente:** - -1. `aop` ([Article](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#article) | [Page](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#page)): Der Artikel oder die Seite. - - -### `cards_from_articles_or_pages(asops, max = None, standalone = True)` - -Erstellt mehrere Kacheln oder einen vollständigen Kachelblock von mehreren Artikeln oder Seiten. - -**Argumente:** - -1. `asops` (List\<[Article](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#article) | [Page](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#page)\>): Die Artikel oder Seiten. Kann auch gemischt sein. -2. `max` (Int | None): Falls vorhanden werden maximal `max` Kacheln gerendert. -3. `standalone` (Boolean): Ob `open()` und `close()` automatisch mit ausgeführt werden soll. - -**Beispiel Aufruf:** - -``` -{{ cards_from_articles_or_pages(all_articles, max=5) }} -``` - -## [renderers.html](renderers.html) - -``` -{%- import 'macros/renderers.html' as render with context -%} -``` - -**TODO: ** Die Dokumentation für diese Macros ist abhängig von der Dokumentation von der config.json-Datei im Inhaltsrepo. - -### `section_news(s)` -### `section_custom(s)` -### `section_iframe(s)` -### `section_category(s)` -### `section_tag(s)` -### `section(s)` diff --git a/theme/templates/macros/cards.html b/theme/templates/macros/cards.html index bea528c91af935481ca7b08df074b8c83a0c8876..2360ed07469635ba2ecd8c8afb39b029938caa0a 100644 --- a/theme/templates/macros/cards.html +++ b/theme/templates/macros/cards.html @@ -9,23 +9,23 @@ </ul> {%- endmacro -%} -{%- macro card(title, url, catcolor, escape_title = True, is_url_external = True) -%} - <li style="--category-color: {{ catcolor }}"><a href="{% if not is_url_external %}{{ SITEURL }}/{% endif %}{{ url }}">{% if escape_title %}{{ title|e }}{% else %}{{ title }}{% endif %}</a></li> +{%- macro card(title, url, catcolor, is_url_external = True) -%} + <li style="--category-color: {{ catcolor }}"><a href="{% if not is_url_external %}{{ SITEURL }}/{% endif %}{{ url }}">{{ title|e }}</a></li> {%- endmacro -%} -{%- macro card_from_article_or_page(aop) -%} - {%- call(_, sccat, __) get.category_by_name(aop.category.name, True) -%} - {{ card(title=aop.title, url=aop.url, catcolor=sccat.color, escape_title=False, is_url_external = False) }} +{%- macro card_from_page(page, lang) -%} + {%- call(cattitle, catcolor, caturl, catpage, catpages) get.tag_by_name(page.category, l) -%} + {{ card(title=page.title, url=page.url, catcolor=catcolor, is_url_external = False) }} {%- endcall -%} {%- endmacro -%} -{%- macro cards_from_articles_or_pages(asops, max = None, standalone = True) -%} +{%- macro cards_from_pages(pages, lang, max = None, standalone = True) -%} {%- if standalone -%} {{ open() }} {%- endif -%} - {%- for aop in asops -%} + {%- for page in pages -%} {%- if max == None or loop.index < max -%} - {{ cards.card_from_article_or_page(aop) }} + {{ cards.card_from_page(page) }} {%- endif -%} {%- endfor -%} {%- if standalone -%} diff --git a/theme/templates/macros/getters.html b/theme/templates/macros/getters.html index 1c1088d4e26ed72f34c271193e9883acf3d3a986..1684d18c4e3a3e1bb601d94e0d230368a19db20c 100644 --- a/theme/templates/macros/getters.html +++ b/theme/templates/macros/getters.html @@ -1,53 +1,26 @@ -{%- macro category_by_name(catname, ignore_native = False) -%} - {%- if ignore_native -%} - {{- caller(None, sc.categories[catname], None) -}} - {%- else -%} - {%- for nativecat, catarticles in categories -%} - {%- if nativecat.name == catname -%} - {{- caller(nativecat, sc.categories[catname], catarticles) -}} - {%- endif -%} - {%- endfor -%} - {%- endif -%} -{%- endmacro -%} +{%- macro tag_by_name(tagname, lang) -%} + {%- set ns = namespace(tagpage=None, tagcolor=config.theme.default_tag_color, tagtitle=tagname) -%} + {%- set tagpageslug = ["tag", tagname]|join('-') -%} -{%- macro tag_by_name(tagname) -%} - {%- for nativetag, tagarticles in tags -%} - {%- if nativetag.name == tagname -%} - {%- if tagname in sc.categories -%} - {{- caller(nativetag, sc.categories[tagname], tagarticles) -}} - {%- else -%} - {{- caller(nativetag, None, tagarticles) -}} - {%- endif -%} - {%- endif -%} - {%- endfor -%} + {%- if tagpageslug in pages[lang] -%} + {%- set ns.tagpage = pages[lang][tagpageslug] -%} + {%- set ns.tagtitle = ns.tagpage.title -%} + {%- call(tagcolor) metadata_entry(tagpageslug, lang, 'color') -%} + {%- set ns.tagcolor = tagcolor -%} + {%- endcall -%} + {%- endif -%} + + {{- caller(ns.tagtitle, ns.tagcolor, [lang, 'tag', tagname + '.html']|join('/') , ns.tagpage, tags[lang][tagname]) -}} {%- endmacro -%} -{%- macro article_by_slug(slug, lang) -%} - {%- for a in all_articles -%} - {%- if a.slug == slug and a.lang == lang -%} - {{- caller(a) -}} - {%- endif -%} - {%- endfor -%} +{%- macro metadata_entry(slug, lang, key) -%} + {%- if key in pages[lang][slug].metadata -%} + {{- caller(pages[lang][slug].metadata[key]) -}} + {%- elif key in pages[t.default][slug].metadata -%} + {{- caller(pages[t.default][slug].metadata[key]) -}} + {%- endif -%} {%- endmacro -%} {%- macro page_by_slug(slug, lang) -%} - {%- for p in pages -%} - {%- if p.slug == slug and p.lang == lang -%} - {{- caller(p) -}} - {%- endif -%} - {%- endfor -%} + {{- caller(pages[lang][slug]) -}} {%- endmacro -%} - -{%- macro article_or_page_by_slug(slug, lang) -%} - {%- for a in all_articles -%} - {%- if a.slug == slug and a.lang == lang -%} - {{- caller(a, "article") -}} - {%- endif -%} - {%- endfor -%} - {%- for p in pages -%} - {%- if p.slug == slug and p.lang == lang -%} - {{- caller(p, "page") -}} - {%- endif -%} - {%- endfor -%} -{%- endmacro -%} - diff --git a/theme/templates/macros/renderers.html b/theme/templates/macros/renderers.html index 698dbcccbd9ad24f5a77b13e2687ebff69bce05d..56351467715cfd0882aceb9394b91c9f9936dc82 100644 --- a/theme/templates/macros/renderers.html +++ b/theme/templates/macros/renderers.html @@ -1,11 +1,11 @@ {%- import 'macros/getters.html' as get with context -%} {%- import 'macros/cards.html' as cards with context -%} -{%- macro section_news(s) -%} - {{ cards.cards_from_articles_or_pages(all_articles, max=s.num) }} +{%- macro section_news(s, lang) -%} + {{ cards.cards_from_pages(pages_modified, max=s.num) }} {%- endmacro -%} -{%- macro section_custom(s) -%} +{%- macro section_custom(s, lang) -%} {{ cards.open() }} {%- for c in s.content -%} {{ cards.card(title=c[lang], url=c.url, catcolor=c.color) }} @@ -13,59 +13,50 @@ {{ cards.close() }} {%- endmacro -%} -{%- macro section_iframe(s) -%} +{%- macro section_iframe(s, lang) -%} <iframe src="{{ s.url }}"></iframe> {%- endmacro -%} -{%- macro section_category(s) -%} - {%- call(nativecat, sccat, catarticles) get.category_by_name(s.category) -%} - {%- if s.title is not defined -%} - <header> - <h2 {% if s.id is defined -%}id="{{ s.id }}"{%- endif %}>{{ sccat[lang]|e }}</h2> - </header> - {%- endif -%} - {{ cards.cards_from_articles_or_pages(catarticles, max=s.num) }} - {%- endcall -%} -{%- endmacro -%} - -{%- macro section_tag(s) -%} - {%- call(nativetag, sccat, tagarticles) get.tag_by_name(s.tag) -%} - {%- if s.title is not defined -%} - <header> - <h2 {% if s.id is defined -%}id="{{ s.id }}"{%- endif %}> - {%- if sccat -%} - {{- sccat[lang]|e -}} - {%- else -%} - {{- s.tag|e -}} - {%- endif -%} - </h2> - </header> - {%- endif -%} - {{ cards.cards_from_articles_or_pages(tagarticles, max=s.num) }} - - +{%- macro section_tag(s, lang) -%} + {%- call(tagtitle, tagcolor, tagurl, tagpage, tagpages) get.tag_by_name(s.tag, lang) -%} + {%- if s.title is not defined -%} + <header> + <h2 {% if s.id is defined -%}id="{{ s.id }}"{%- endif %}>{{ tagtitle|e }}</h2> + </header> + {%- endif -%} + {{ cards.cards_from_pages(tagpages, lang, max=s.num) }} + + {%- if tagpage -%} + {{ tagpage.content }} + {%- endif -%} {%- endcall -%} {%- endmacro -%} -{%- macro section(s) -%} +{%- macro section(s, lang) -%} {%- if s.title is defined -%} <header> <h2 {% if s.id is defined -%}id="{{ s.id }}"{%- endif %}>{{ s.title[lang]|e }}</h2> </header> {%- endif -%} {%- if s.type == "news" -%} - {{ section_news(s) }} + {{ section_news(s, lang) }} {%- elif s.type == "iframe" -%} - {{ section_iframe(s) }} + {{ section_iframe(s, lang) }} {%- elif s.type == "custom" -%} - {{ section_custom(s) }} - {%- elif s.type == "category" -%} - {{ section_category(s) }} + {{ section_custom(s, lang) }} {%- elif s.type == "tag" -%} - {{ section_tag(s) }} + {{ section_tag(s, lang) }} {%- else -%} <br /> <strong>ERROR: render.section: Unknown section type: {{ s.type|e }}</strong><br /> <br /> {%- endif -%} {%- endmacro -%} + +{%- macro sections(sl, lang) -%} + {%- for s in sl -%} + <section> + {{ section(s, lang) }} + </section> + {%- endfor -%} +{%- endmacro -%} diff --git a/theme/templates/page.html b/theme/templates/page.html index af4e389ee2929cd5d3155aac3a7f8cd914899e2f..fdbef287039e822a368a5f6e0702340bd424aaaa 100644 --- a/theme/templates/page.html +++ b/theme/templates/page.html @@ -1,8 +1,8 @@ {% extends "base.html" %} {%- macro page_translation_link(translation, is_current=False) -%} -<a href="{{ SITEURL }}/{{ translation.url }}" hreflang="{{ translation.lang }}" title="{{ l[translation.lang].langname|e }}"> - <img alt="{{ l[translation.lang].langname|e }}" src="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/images/flags/{{ translation.lang }}.svg"> +<a href="{{ siteurl }}/{{ translation.url }}" hreflang="{{ translation.lang }}" title="{{ l[translation.lang].langname|e }}"> + <img alt="{{ l[translation.lang].langname|e }}" src="{{ siteurl }}/{{ THEME_STATIC_DIR }}/images/flags/{{ translation.lang }}.svg"> </a> {%- endmacro -%} @@ -12,7 +12,7 @@ {% block extra_head %} {%- for translation in page.translations -%} - <link rel="alternate" hreflang="{{ translation.lang }}" href="{{ SITEURL }}/{{ translation.url }}"> + <link rel="alternate" hreflang="{{ translation.lang }}" href="{{ siteurl }}/{{ translation.url }}"> {%- endfor -%} {% if page.summary %} @@ -25,35 +25,41 @@ <header> <h1>{{ page.title }}</h1> </header> + {%- call(s) get.metadata_entry(page.slug, l, 'before') -%} + {{ render.sections(s, l) }} + {%- endcall -%} {{ page.content }} + {%- call(s) get.metadata_entry(page.slug, l, 'after') -%} + {{ render.sections(s, l) }} + {%- endcall -%} <footer> {%- if page.authors -%} <address> - {{ l[lang].page.authors_prefix }} + {{ t[l].page.authors_prefix }} {% for author in page.authors %} - <a href="{{ SITEURL }}/{{ author.url }}">{{ author }}</a> + <a href="{{ siteurl }}/{{ author.url }}">{{ author }}</a> {% endfor %} - {{ l[lang].page.authors_suffix }} + {{ t[l].page.authors_suffix }} </address> {%- endif -%} - <span>{{ l[lang].page.published_prefix }} - <abbr title="{{ page.date.isoformat() }}"> - {{ page.locale_date }} - </abbr>{{ l[lang].page.published_suffix }}</span> + <span>{{ t[l].page.published_prefix }} + <abbr title="{{ page.date_created.isoformat() }}"> + {{ page.date_created.isoformat() }} + </abbr>{{ t[l].page.published_suffix }}</span> {%- if page.modified -%} <br /> - <span>{{ l[lang].page.modified_prefix }} + <span>{{ t[l].page.modified_prefix }} <abbr title="{{ page.modified.isoformat() }}">{{ page.locale_modified }}</abbr> - {{ l[lang].page.modified_suffix }}</span> + {{ t[l].page.modified_suffix }}</span> {%- endif -%} <br /> - <span>{{ l[lang].page.category_prefix }}<a href="{{ SITEURL }}/{{ page.category.url }}">{{ page.category }}</a>{{ l[lang].page.category_suffix }}</span> + {#<span>{{ t[l].page.category_prefix }}<a href="{{ siteurl }}/{{ page.category.url }}">{{ page.category }}</a>{{ t[l].page.category_suffix }}</span>#} <br /> - <span>{{ l[lang].page.languages_prefix }}</span> - <ul class="languages"> + <span>{{ t[l].page.languages_prefix }}</span> + {#<ul class="languages"> {%- for translation in page.translations -%} <li> {{ page_translation_link(translation) }} @@ -62,8 +68,8 @@ <li> {{ page_translation_link(page, True) }} </li> - </ul> - {{ l[lang].page.languages_suffix }} + </ul>#} + {{ t[l].page.languages_suffix }} </footer> </article> {% endblock %} diff --git a/theme/templates/tag.html b/theme/templates/tag.html index 6a636023257c22e38fb88b74bb4d631af4718362..3c0cca1b0d16799bf01dacf967c4f2c742ed4ecd 100644 --- a/theme/templates/tag.html +++ b/theme/templates/tag.html @@ -1,7 +1,12 @@ {% extends "base.html" %} -{% block title %}{{ l[lang].title_prefix }}{{ l[lang].sitename }}{{ l[lang].title_suffix }} - {{ tag }}{%endblock%} +{% block title -%} + {%- call(tagtitle, tagcolor, tagurl, tagpage, tagpages) get.tag_by_name(tag, l) -%} + {{ t[l].title_prefix }}{{ t[l].sitename }}{{ t[l].title_suffix }} - {{ tagtitle|e }} + {%- endcall -%} +{%- endblock -%} + {% block content %} <section> - {{ render.section({"type": "tag", "tag": tag.slug, "num": None}) }} + {{ render.section({"type": "tag", "tag": tag, "num": None}, l) }} </section> {% endblock content %}