1
0
mirror of https://github.com/tiyn/beaker-blog.git synced 2025-10-17 05:11:16 +02:00

Compare commits

16 Commits

Author SHA1 Message Date
9fd714eef3 added robots 2024-04-22 06:14:32 +02:00
a927a18e39 tts: added error handling 2024-04-22 02:29:14 +02:00
6e844a3cb1 added tts functionality 2024-04-22 02:06:12 +02:00
9ea9ab3c53 improved css for footer placement 2024-04-21 04:00:41 +02:00
73d9faea42 added feed to the footer 2024-04-21 02:58:33 +02:00
586549a7f9 added changable timezone for rss feed 2024-04-21 02:14:11 +02:00
41ba108e3f fixed tabbing for doc strings 2024-04-21 00:53:28 +02:00
84750323c1 automatically convert to absolute links 2024-04-21 00:51:17 +02:00
e4744ee451 fixed typos 2024-04-21 00:02:04 +02:00
6fb7411156 made tabbing in css uniform 2024-04-20 23:41:39 +02:00
0ff2bffc99 fixed scrolling behaviour 2024-04-20 23:38:16 +02:00
60be3da149 tried to change scrolling behaviour 2024-04-20 20:30:04 +02:00
a862ac0966 make preview use the first paragraph 2024-04-20 20:12:43 +02:00
070be5b0e2 fixed html for basic sites 2024-04-20 20:01:55 +02:00
2a2a5f77b6 fixed rss errors 2024-04-20 18:34:51 +02:00
a4d2290e47 redirect to uniform site links 2024-04-20 17:18:54 +02:00
14 changed files with 434 additions and 294 deletions

View File

@@ -2,11 +2,9 @@ FROM python:3
MAINTAINER tiyn tiyn@mail-mk.eu MAINTAINER tiyn tiyn@mail-mk.eu
COPY src /blog ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
WORKDIR /blog ENV LC_ALL en_US.UTF-8
RUN pip3 install -r requirements.txt
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y locales && \ apt-get install -y locales && \
@@ -14,9 +12,13 @@ RUN apt-get update && \
sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && \ sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && \
dpkg-reconfigure --frontend=noninteractive locales dpkg-reconfigure --frontend=noninteractive locales
ENV LANG en_US.UTF-8 RUN apt-get install -y espeak
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8 COPY src /blog
WORKDIR /blog
RUN pip3 install -r requirements.txt
VOLUME /blog/templates/entry VOLUME /blog/templates/entry

View File

@@ -22,6 +22,7 @@ via plain text files.
- [x] Links to standalone article - [x] Links to standalone article
- [x] Standalone article page - [x] Standalone article page
- [x] Links to scrolling blog page - [x] Links to scrolling blog page
- [x] TTS Functionality
- [x] RSS feed - [x] RSS feed
- [x] Navigation - [x] Navigation
- [x] Header - [x] Header

View File

@@ -1,4 +1,4 @@
from flask import Flask, abort, make_response, render_template, request from flask import (Flask, abort, make_response, redirect, render_template, request, url_for)
from flask_font_awesome import FontAwesome from flask_font_awesome import FontAwesome
import config import config
@@ -28,8 +28,12 @@ def page_not_found(e):
language=LANGUAGE), 404 language=LANGUAGE), 404
@app.route("/")
@app.route("/index.html") @app.route("/index.html")
def index_re():
return redirect(url_for("index"))
@app.route("/")
def index(): def index():
content = con_gen.gen_index_string() content = con_gen.gen_index_string()
return render_template("index.html", return render_template("index.html",
@@ -40,8 +44,12 @@ def index():
language=LANGUAGE) language=LANGUAGE)
@app.route("/search", methods=["GET", "POST"])
@app.route("/search.html", methods=["GET", "POST"]) @app.route("/search.html", methods=["GET", "POST"])
def search_re():
return redirect(url_for("search"))
@app.route("/search", methods=["GET", "POST"])
def search(): def search():
form = SearchForm() form = SearchForm()
if request.method == "POST": if request.method == "POST":
@@ -63,8 +71,12 @@ def search():
language=LANGUAGE), 200 language=LANGUAGE), 200
@app.route("/imprint")
@app.route("/imprint.html") @app.route("/imprint.html")
def imprint_re():
return redirect(url_for("imprint"))
@app.route("/imprint")
def imprint(): def imprint():
return render_template("imprint.html", return render_template("imprint.html",
title=TITLE, title=TITLE,
@@ -74,8 +86,12 @@ def imprint():
language=LANGUAGE) language=LANGUAGE)
@app.route("/archive")
@app.route("/archive.html") @app.route("/archive.html")
def archive_re():
return redirect(url_for("archive"))
@app.route("/archive")
def archive(): def archive():
content = con_gen.gen_arch_string() content = con_gen.gen_arch_string()
return render_template("archive.html", return render_template("archive.html",
@@ -101,17 +117,29 @@ def entry(path):
@app.route("/feed.xml") @app.route("/feed.xml")
@app.route("/rss.xml") @app.route("/rss.xml")
@app.route("/rss")
def feed_re():
return redirect(url_for("feed"))
@app.route("/robots.txt")
def robots():
return render_template("robots.txt")
@app.route("/feed")
def feed(): def feed():
content = con_gen.get_rss_string() content = con_gen.get_rss_string()
rss_xml = render_template("rss.xml", feed_xml = render_template("feed.xml",
content_string=content, content_string=content,
title=TITLE, title=TITLE,
description=DESCRIPTION, description=DESCRIPTION,
website=WEBSITE) website=WEBSITE,
response = make_response(rss_xml) language=LANGUAGE)
response = make_response(feed_xml)
response.headers["Content-Type"] = "application/rss+xml" response.headers["Content-Type"] = "application/rss+xml"
return response return response
if __name__ == "__main__": if __name__ == "__main__":
con_gen.prepare_tts()
app.run(host="0.0.0.0") app.run(host="0.0.0.0")

View File

@@ -21,3 +21,6 @@ MAIL = "dummy@mail.com"
# Directory to store entries in # Directory to store entries in
ENTRY_DIR = "templates/entry" ENTRY_DIR = "templates/entry"
# Set the timezone of your blog
TIMEZONE = "+0000"

View File

@@ -1,28 +1,34 @@
import glob
import locale import locale
import os import os
import pathlib import pathlib
import urllib.parse
from datetime import datetime from datetime import datetime
from os import path from os import path
import markdown import markdown
from bs4 import BeautifulSoup
from gtts import gTTS, gTTSError
import config import config
import search import search
WEBSITE = config.WEBSITE
ENTRY_DIR = config.ENTRY_DIR ENTRY_DIR = config.ENTRY_DIR
LANGUAGE = config.LANGUAGE LANGUAGE = config.LANGUAGE
LOCAL = "de_DE.UTF-8" if LANGUAGE == "de-de" else "en_US.UTF-8" LOCAL = "de_DE.UTF-8" if LANGUAGE == "de-de" else "en_US.UTF-8"
TIMEZONE = config.TIMEZONE
locale.setlocale(locale.LC_TIME, LOCAL) locale.setlocale(locale.LC_TIME, LOCAL)
def gen_arch_string(): def gen_arch_string():
""" """
Creates and returns a archive string of every file in ENTRY_DIR. Creates and returns a archive string of every file in ENTRY_DIR.
Returns: Returns:
string: html-formatted archive-string string: html-formatted archive-string
""" """
path_ex = ENTRY_DIR path_ex = ENTRY_DIR
if path.exists(path_ex): if path.exists(path_ex):
name_list = os.listdir(path_ex) name_list = os.listdir(path_ex)
@@ -57,11 +63,11 @@ def gen_arch_string():
def gen_index_string(): def gen_index_string():
""" """
Create and returns a string including every file in the ENTRY_DIR as an index. Create and returns a string including every file in the ENTRY_DIR as an index.
Returns: Returns:
string: html-formatted index string string: html-formatted index string
""" """
path_ex = ENTRY_DIR path_ex = ENTRY_DIR
content_string = "" content_string = ""
if path.exists(path_ex): if path.exists(path_ex):
@@ -90,21 +96,44 @@ def gen_index_string():
if file.endswith(".md"): if file.endswith(".md"):
content_string += gen_md_content(file, 2) content_string += gen_md_content(file, 2)
content_string += "</div>" content_string += "</div>"
content_string = absolutize_html(content_string)
return content_string return content_string
def absolutize_html(string):
"""
Creates a html string from another string that only uses absolute links that use the full domain.
Parameters:
string: html-formatted string.
Returns:
string: html-formatted string with absolute linksn
"""
soup = BeautifulSoup(string, "html.parser")
for a_tag in soup.find_all("a"):
href = str(a_tag.get("href"))
if href.startswith("/") or href.startswith("."):
a_tag["href"] = urllib.parse.urljoin(WEBSITE, href)
for img_tag in soup.find_all("img"):
src = str(img_tag.get("src"))
if src.startswith("/") or src.startswith("."):
img_tag["src"] = urllib.parse.urljoin(WEBSITE, src)
return str(soup)
def gen_stand_string(path_ex): def gen_stand_string(path_ex):
""" """
Creates a html-string for a file. Creates a html-string for a file.
If the file is markdown it will convert it. If the file is markdown it will convert it.
This functions ensures upscaling for future formats. This functions ensures upscaling for future formats.
Parameters: Parameters:
path_ex: path to a file. path_ex: path to a file.
Returns: Returns:
string: html-formatted string string equivalent to the file string: html-formatted string string equivalent to the file
""" """
filename = os.path.join(ENTRY_DIR, path_ex) filename = os.path.join(ENTRY_DIR, path_ex)
content_string = "" content_string = ""
if path.exists(filename): if path.exists(filename):
@@ -117,31 +146,35 @@ def gen_stand_string(path_ex):
content_string += "<a href=\"" + "/index.html#" + \ content_string += "<a href=\"" + "/index.html#" + \
filename_no_end + "\">" + curr_date + "</a>" filename_no_end + "\">" + curr_date + "</a>"
content_string += "<br><br>\n" content_string += "<br><br>\n"
if os.path.isfile("static/tmp/" + filename_no_end + ".mp3"):
content_string += "<audio controls>\n"
content_string += '<source src="/static/tmp/' + filename_no_end + '.mp3" type="audio/mp3">\n'
content_string += "</audio>\n"
content_string += "<br><br>\n"
if filename.endswith(".html"): if filename.endswith(".html"):
for line in text: for line in text:
content_string += line content_string += line
if filename.endswith(".md"): if filename.endswith(".md"):
content_string += gen_md_content(filename, 1) content_string += gen_md_content(filename, 1)
content_string = absolutize_html(content_string)
return content_string return content_string
def gen_md_content(path_ex, depth): def gen_md_content(path_ex, depth):
""" """
Convert a markdown file to a html string. Convert a markdown file to a html string.
Parameters: Parameters:
path_ex (string): path to the markdown file path_ex (string): path to the markdown file
depth (int): starting depth for markdown headings depth (int): starting depth for markdown headings
Returns: Returns:
string: html-formatted string string equivalent to the markdown file string: html-formatted string string equivalent to the markdown file
""" """
content_string = "" content_string = ""
if path.exists(path_ex): if path.exists(path_ex):
filename = path_ex.split(".", 1)
fileend = filename[len(filename) - 1]
header = "#" header = "#"
for i in range(depth): for _ in range(depth):
header += "#" header += "#"
header += " " header += " "
markdown_lines = open(path_ex, "r").readlines()[1:] markdown_lines = open(path_ex, "r").readlines()[1:]
@@ -154,11 +187,11 @@ def gen_md_content(path_ex, depth):
def get_rss_string(): def get_rss_string():
""" """
Create a rss-string of the blog and return it. Create a rss-string of the blog and return it.
Returns: Returns:
string: rss-string of everything that is in the ENTRY_DIR. string: rss-string of everything that is in the ENTRY_DIR.
""" """
path_ex = ENTRY_DIR path_ex = ENTRY_DIR
content_string = "" content_string = ""
if path.exists(path_ex): if path.exists(path_ex):
@@ -174,29 +207,33 @@ def get_rss_string():
filename = filename.split(".", 1)[0] filename = filename.split(".", 1)[0]
content_string += "<item>\n" content_string += "<item>\n"
content_string += "<title>" + title + "</title>\n" content_string += "<title>" + title + "</title>\n"
content_string += "<guid>" + config.WEBSITE + \ content_string += "<guid>" + WEBSITE + \
"/index.html#" + filename + "</guid>\n" "/index.html#" + filename + "</guid>\n"
locale.setlocale(locale.LC_TIME, "en_US.UTF-8")
content_string += "<pubDate>" + \ content_string += "<pubDate>" + \
datetime.fromtimestamp(os.path.getmtime(file)).strftime( datetime.fromtimestamp(os.path.getmtime(file)).strftime(
"%Y-%m-%d") + "</pubDate>\n" "%a, %d %b %Y %H:%M:%S") + " " + TIMEZONE + "</pubDate>\n"
content_string += "<description>" locale.setlocale(locale.LC_TIME, LOCAL)
content_string += "<description>\n<![CDATA[<html>\n<head>\n</head>\n<body>\n"
html_string = ""
for line in text: for line in text:
content_string += line html_string += line
content_string += "</description>\n" content_string += absolutize_html(html_string)
content_string += "\n</body></html>\n]]>\n</description>\n"
content_string += "</item>\n" content_string += "</item>\n"
return content_string return content_string
def gen_query_res_string(query_str): def gen_query_res_string(query_str):
""" """
Return the results of a query. Return the results of a query.
Parameters: Parameters:
query_str (string): term to search query_str (string): term to search
Returns: Returns:
string: html-formated search result string: html-formated search result
""" """
src_results = search.search(query_str) src_results = search.search(query_str)
res_string = "" res_string = ""
for result in src_results: for result in src_results:
@@ -222,29 +259,73 @@ def gen_query_res_string(query_str):
def create_preview(path, is_markdown): def create_preview(path, is_markdown):
""" """
Create a preview of a given article and return it. Create a preview of a given article and return it.
Parameters: Parameters:
path (string): path to the article path (string): path to the article
Returns: Returns:
string: html-formated preview string: html-formated preview
""" """
file = open(path, "r", encoding="utf-8") file = open(path, "r", encoding="utf-8")
first_lines = file.readlines() lines = file.read()
if is_markdown:
lines += markdown.markdown(lines)
preview = "" preview = ""
preview_length = 3 first_p = BeautifulSoup(lines).find('p')
for i, line in enumerate(first_lines): if first_p is not None:
if i == 0: preview = "\n<p>" + first_p.text + "</p>\n"
continue preview += "...<br>"
if i > preview_length:
break
if not line.isspace():
if is_markdown:
preview += markdown.markdown(line)
else:
preview += line
else:
preview_length += 1
preview += "<br>...<br>"
return preview return preview
def get_text_only(filename):
"""
Convert a file to text only to use in tts
Parameters:
path (string): path to the article
Returns:
string: unformatted string containing the contents of the file
"""
# filename = os.path.join(ENTRY_DIR, path)
clean_text = ""
if path.exists(filename):
title = open(filename).readline().rstrip("\n")
text = open(filename).readlines()[1:]
filename_no_end = filename.split(".", 1)[0]
filename_no_end = filename_no_end.split("/")[-1]
content_string = ""
if filename.endswith(".html"):
for line in text:
content_string += line
if filename.endswith(".md"):
content_string += gen_md_content(filename, 1)
content_string = absolutize_html(content_string)
soup = BeautifulSoup(content_string, "html.parser")
tag_to_remove = soup.find("figure")
if tag_to_remove:
tag_to_remove.decompose()
clean_text = soup.get_text(separator=" ")
clean_text = title + "\n\n" + clean_text
return clean_text
def prepare_tts():
files = glob.glob('static/tmp/*')
for f in files:
os.remove(f)
files = glob.glob('templates/entry/*')
clean_text = ""
for f in files:
clean_text = get_text_only(f)
_, tail = os.path.split(f)
new_filename = "static/tmp/" + os.path.splitext(tail)[0] + ".mp3"
try:
tts = gTTS(clean_text, lang=LANGUAGE.split("-")[0])
tts.save(new_filename)
except gTTSError as e:
print("Too many request to the google servers. Try it again later.")
os.remove(new_filename)
return e

View File

@@ -4,3 +4,5 @@ Whoosh
WTForms WTForms
Flask_WTF Flask_WTF
Font-Awesome-Flask Font-Awesome-Flask
BeautifulSoup4
gTTS

View File

@@ -14,11 +14,10 @@ ENTRY_DIR = config.ENTRY_DIR
def createSearchableData(root): def createSearchableData(root):
""" """
Schema definition: title(name of file), path(as ID), content(indexed but not stored), textdata (stored text content)
Schema definition: title(name of file), path(as ID), content(indexed but not stored), textdata (stored text content) source:
source: https://appliedmachinelearning.blog/2018/07/31/developing-a-fast-indexing-and-full-text-search-engine-with-whoosh-a-pure-pythhon-library/
https://appliedmachinelearning.blog/2018/07/31/developing-a-fast-indexing-and-full-text-search-engine-with-whoosh-a-pure-pythhon-library/ """
"""
schema = Schema(title=TEXT(stored=True), path=ID(stored=True), content=TEXT) schema = Schema(title=TEXT(stored=True), path=ID(stored=True), content=TEXT)
if not os.path.exists(INDEX_DIR): if not os.path.exists(INDEX_DIR):
os.mkdir(INDEX_DIR) os.mkdir(INDEX_DIR)
@@ -37,15 +36,15 @@ def createSearchableData(root):
def search_times(query_str, topN): def search_times(query_str, topN):
""" """
Search for a given term and returns a specific amount of results. Search for a given term and returns a specific amount of results.
Parameters: Parameters:
query_str (string): term to search for query_str (string): term to search for
topN (int): number of results to return topN (int): number of results to return
Returns: Returns:
string: html-formatted string including the hits of the search string: html-formatted string including the hits of the search
""" """
ix = open_dir(INDEX_DIR) ix = open_dir(INDEX_DIR)
results = [] results = []
with ix.searcher(weighting=scoring.BM25F) as s: with ix.searcher(weighting=scoring.BM25F) as s:
@@ -58,14 +57,14 @@ def search_times(query_str, topN):
def search(query_str): def search(query_str):
""" """
Search for a given term and show the predefined amount of results. Search for a given term and show the predefined amount of results.
Parameters: Parameters:
query_str (string): term to search for query_str (string): term to search for
Returns: Returns:
string: html-formatted string including the hits of the search string: html-formatted string including the hits of the search
""" """
return search_times(query_str, DEF_TOPN) return search_times(query_str, DEF_TOPN)

View File

@@ -1,88 +1,88 @@
@import 'style.css'; @import 'style.css';
:root { :root {
--bg0: rgb(29,32,33); --bg0: rgb(29,32,33);
--color0: rgb(220,120,0); --color0: rgb(220,120,0);
--color1: rgb(280,180,0); --color1: rgb(280,180,0);
--error: rgb(255,0,0); --error: rgb(255,0,0);
--footerbg0: rgb(29,32,33); --footerbg0: rgb(29,32,33);
--link0: rgb(220, 120, 0); --link0: rgb(220, 120, 0);
--link1: rgb(255,255,255); --link1: rgb(255,255,255);
--menulink0: rgb(220, 120, 0); --menulink0: rgb(220, 120, 0);
--menulink1: rgb(255,255,255); --menulink1: rgb(255,255,255);
--menubg0: rgb(29,32,33); --menubg0: rgb(29,32,33);
--text0: rgb(235,219,178); --text0: rgb(235,219,178);
--text1: rgb(220, 120, 0); --text1: rgb(220, 120, 0);
} }
a { a {
color: var(--link0); color: var(--link0);
transition: var(--transtime); transition: var(--transtime);
} }
a:hover { a:hover {
color: var(--link1); color: var(--link1);
} }
body { body {
background: var(--bg0); background: var(--bg0);
} }
footer { footer {
background: var(--footerbg0); background: var(--footerbg0);
color: var(--text0); color: var(--text0);
} }
span { span {
color: var(--text1); color: var(--text1);
} }
.container { .container {
color: var(--text0); color: var(--text0);
} }
.container h1, .container h1,
.container h2 { .container h2 {
color: var(--text1); color: var(--text1);
} }
.container .flash { .container .flash {
background-color: var(--error); background-color: var(--error);
} }
.hide-menu:hover, .hide-menu:hover,
.main-menu a:hover, .main-menu a:hover,
.main-menu-dropdown a:hover, .main-menu-dropdown a:hover,
.show-menu:hover { .show-menu:hover {
color: var(--menulink1); color: var(--menulink1);
} }
.main-menu a, .main-menu a,
.main-menu-dropdown a { .main-menu-dropdown a {
color: var(--menulink0); color: var(--menulink0);
} }
.main-menu-dropdown { .main-menu-dropdown {
background: var(--menubg0); background: var(--menubg0);
color: var(--menulink0); color: var(--menulink0);
} }
@media screen and (max-width:800px) { @media screen and (max-width:800px) {
.main-menu { .main-menu {
background: var(--menubg0); background: var(--menubg0);
} }
} }
.entry { .entry {
background: var(--bg0); background: var(--bg0);
border-left: 10px solid var(--color0); border-left: 10px solid var(--color0);
color: var(--text0); color: var(--text0);
} }
.entry h1, .entry h1,
.entry h2 { .entry h2 {
color: var(--text1); color: var(--text1);
} }

View File

@@ -1,96 +1,95 @@
@import 'style.css'; @import 'style.css';
:root { :root {
--bg0: rgb(255,255,255); --bg0: rgb(255,255,255);
--color0: rgb(0,0,120); --color0: rgb(0,0,120);
--color1: rgb(0,0,200); --color1: rgb(0,0,200);
--error: rgb(255,0,0); --error: rgb(255,0,0);
--footerbg0: rgb(192,192,192); --footerbg0: rgb(192,192,192);
--link0: rgb(0,0,120); --link0: rgb(0,0,120);
--link1: rgb(192,192,192); --link1: rgb(192,192,192);
--menulink0: rgb(0,0,120); --menulink0: rgb(0,0,120);
--menulink1: rgb(255,255,255); --menulink1: rgb(255,255,255);
--menubg0: rgb(192,192,192); --menubg0: rgb(192,192,192);
--text0: rgb(0,0,0); --text0: rgb(0,0,0);
--text1: rgb(0,0,120); --text1: rgb(0,0,120);
} }
a { a {
color: var(--link0); color: var(--link0);
transition: var(--transtime); transition: var(--transtime);
} }
a:hover { a:hover {
color: var(--link1); color: var(--link1);
} }
body { body {
background: var(--bg0); background: var(--bg0);
} }
footer { footer {
background: var(--footerbg0); background: var(--footerbg0);
color: var(--text0); color: var(--text0);
} }
footer a { footer a {
color: var(--menulink0); color: var(--menulink0);
} }
footer a:hover { footer a:hover {
color: var(--bg0); color: var(--bg0);
} }
span { span {
color: var(--text1); color: var(--text1);
} }
.container { .container {
color: var(--text0); color: var(--text0);
} }
.container h1, .container h1,
.container h2 { .container h2 {
color: var(--text1); color: var(--text1);
} }
.container .flash { .container .flash {
background-color: var(--error); background-color: var(--error);
} }
.hide-menu:hover, .hide-menu:hover,
.main-menu a:hover, .main-menu a:hover,
.main-menu-dropdown a:hover, .main-menu-dropdown a:hover,
.show-menu:hover { .show-menu:hover {
color: var(--menulink1); color: var(--menulink1);
} }
.main-menu a, .main-menu a,
.main-menu-dropdown a { .main-menu-dropdown a {
color: var(--menulink0); color: var(--menulink0);
} }
.main-menu-dropdown { .main-menu-dropdown {
background: var(--menubg0); background: var(--menubg0);
color: var(--menulink0); color: var(--menulink0);
} }
@media screen and (max-width:800px) { @media screen and (max-width:800px) {
.main-menu {
.main-menu { background: var(--menubg0);
background: var(--menubg0); }
}
} }
.entry { .entry {
background: var(--bg0); background: var(--bg0);
border-left: 10px solid var(--color0); border-left: 10px solid var(--color0);
color: var(--text0); color: var(--text0);
} }
.entry h1, .entry h1,
.entry h2 { .entry h2 {
color: var(--text1); color: var(--text1);
} }
form.search button[type=submit] { form.search button[type=submit] {

View File

@@ -1,237 +1,244 @@
:root { :root {
--error: rgb(255,0,0); --error: rgb(255,0,0);
--transtime: 0.7s; --transtime: 0.7s;
} }
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
a { a {
text-decoration: none; text-decoration: none;
transition: var(--transtime); transition: var(--transtime);
} }
a:hover { a:hover {
cursor: pointer; cursor: pointer;
} }
body { body {
margin: 0; margin: 0;
} }
body, body,
html { html {
font-family: sans-serif; font-family: sans-serif;
height: 100%; max-width: 100%;
max-width: 100%; overflow-x: hidden;
overflow-x: hidden;
} }
footer { footer {
height: 100px; height: 100px;
padding-top: 20px; padding-top: 20px;
width: 100%;
} }
footer .center { footer .center {
text-align: center; text-align: center;
} }
.container { .container {
min-height: 100%; padding-bottom: 50px;
padding-bottom: 50px; padding-left: 10%;
padding-left: 10%; padding-right: 10%;
padding-right: 10%; padding-top: 5vh;
padding-top: 5%; /* position: relative; */
min-height: calc(100vh - 50px - 5vh - 100px - 100px);
} }
.container .flash { .container .flash {
padding: 10px; padding: 10px;
width: 400px; width: 400px;
} }
.hide-menu, .hide-menu,
.show-menu { .show-menu {
cursor: pointer; cursor: pointer;
display: none; display: none;
font-size: 30px; font-size: 30px;
transition: var(--transtime); transition: var(--transtime);
} }
.important { .important {
font-size: xx-large; font-size: xx-large;
padding-left: 25vw; padding-left: 25vw;
padding-right: 25vw; padding-right: 25vw;
padding-top: 30vh; padding-top: 30vh;
text-align: left; text-align: left;
} }
.important span { .important span {
font-weight: bold; font-weight: bold;
} }
.logo { .logo {
height: 80px; height: 80px;
padding-top: 10px; padding-top: 10px;
} }
.main-menu-dropdown img { .main-menu-dropdown img {
float: left; float: left;
} }
.main-menu-dropdown span, .main-menu-dropdown span,
.main-menu-dropdown a { .main-menu-dropdown a {
float: left; float: left;
font-family: Georgia, serif; font-family: Georgia, serif;
font-size: 30px; font-size: 30px;
font-weight: bold; font-weight: bold;
line-height: 100px; line-height: 100px;
padding: 0 10px; padding: 0 10px;
text-decoration: none; text-decoration: none;
text-transform: uppercase; text-transform: uppercase;
transition: 0.7s; transition: 0.7s;
} }
.main-menu { .main-menu {
float: right; float: right;
font-family: Georgia, serif; font-family: Georgia, serif;
font-size: 30px; font-size: 30px;
font-weight: bold; font-weight: bold;
line-height: 100px; line-height: 100px;
} }
.main-menu a { .main-menu a {
padding: 0 10px; padding: 0 10px;
text-decoration: none; text-decoration: none;
text-transform: uppercase; text-transform: uppercase;
transition: 0.7s; transition: 0.7s;
} }
.main-menu-dropdown { .main-menu-dropdown {
height: 100px; height: 100px;
padding: 0 20px; padding: 0 20px;
} }
.show-menu { .show-menu {
float: right; float: right;
line-height: 100px; line-height: 100px;
} }
#main-menu-check { #main-menu-check {
position: absolute; position: absolute;
visibility: hidden; visibility: hidden;
z-index: -1111; z-index: -1111;
} }
@media screen and (max-width:800px) { @media screen and (max-width:800px) {
.hide-menu { .hide-menu {
position: absolute; position: absolute;
right: 40px; right: 40px;
top: 40px; top: 40px;
} }
.hide-menu, .hide-menu,
.show-menu { .show-menu {
display: block; display: block;
} }
.main-menu { .main-menu {
height: 100vh; height: 100vh;
line-height: normal; line-height: normal;
padding: 80px 0; padding: 80px 0;
position: fixed; position: fixed;
right: -100%; right: -100%;
text-align: center; text-align: center;
top: 0; top: 0;
transition: var(--transtime); transition: var(--transtime);
width: 100%; width: 100%;
} }
.main-menu a {
display: block;
padding: 20px;
}
#main-menu-check:checked ~ .main-menu { .main-menu a {
right: 0; display: block;
} padding: 20px;
}
#main-menu-check:checked ~ .main-menu {
right: 0;
}
} }
form { form {
margin-bottom: 40px; margin-bottom: 40px;
}
.standalone {
min-height: 100vh;
} }
.entry { .entry {
border-radius: 0 10px 30px 0; /* border-radius: 0 10px 30px 0; */
margin-bottom: 20px; margin-bottom: 20px;
padding-left: 20px; padding-left: 20px;
} }
h1, h2 { h1, h2 {
padding-top: 20px; padding-top: 20px;
padding-bottom: 20px; padding-bottom: 20px;
} }
h3 { h3 {
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;
} }
.standalone h1:first-child { .standalone h1:first-child {
padding-bottom: 0; padding-bottom: 0;
} }
.imprint h1:first-child { .imprint h1:first-child {
padding-bottom: 20px; padding-bottom: 20px;
} }
.blog h1:first-child { .blog h1:first-child {
padding-bottom: 20px; padding-bottom: 20px;
} }
.entry h2:first-child { .entry h2:first-child {
padding-top: 0; padding-top: 0;
padding-bottom: 0; padding-bottom: 0;
} }
.blogarchive h1:first-child { .blogarchive h1:first-child {
padding-bottom: 0; padding-bottom: 0;
} }
.blogarchive h2:first-child { .blogarchive h2:first-child {
padding-top: 0; padding-top: 0;
padding-bottom: 0; padding-bottom: 0;
} }
.entry ul { .entry ul {
padding-left: 20; padding-left: 20;
} }
figure { figure {
padding-top: 20px; padding-top: 20px;
padding-bottom: 20px; padding-bottom: 20px;
} }
.entry figure:last-child { .entry figure:last-child {
padding-bottom:0 padding-bottom:0
} }
ul { ul {
padding-left:20px; padding-left:20px;
} }
ol { ol {
padding-left:20px; padding-left:20px;
} }
code { code {
border-radius: 25px; /* border-radius: 25px; */
padding-left: 20px; padding-left: 20px;
padding-right: 20px; padding-right: 20px;
page-break-inside: avoid; page-break-inside: avoid;
font-family: monospace; font-family: monospace;
white-space: pre; white-space: pre;
display: inline-block display: inline-block
} }
form.search input[type=text] { form.search input[type=text] {
@@ -246,7 +253,7 @@ form.search input[type=text] {
form.search button[type=submit] { form.search button[type=submit] {
float: left; float: left;
width: 5%; width: 5%;
padding: 12px; padding: 10px;
font-size: 17px; font-size: 17px;
border: 1px solid grey; border: 1px solid grey;
border-left: none; /* Prevent double borders */ border-left: none; /* Prevent double borders */
@@ -258,3 +265,16 @@ form.search::after {
clear: both; clear: both;
display: table; display: table;
} }
.standalone img {
width: 50%;
}
.entry img {
width: 50%;
}
audio {
width: 50%;
height: 5vh;
}

0
src/static/tmp/.gitkeep Normal file
View File

View File

@@ -5,8 +5,8 @@
<title>{{ title }}</title> <title>{{ title }}</title>
<description>{{ description }}</description> <description>{{ description }}</description>
<language>{{ language }}</language> <language>{{ language }}</language>
<link>{{ website }}/feed.xml</link> <link>{{ website }}</link>
<atom:link href="/feed.xml" rel="self" type="application/rss+xml" /> <atom:link href="{{ website }}{{ url_for('feed') }}" rel="self" type="application/rss+xml"/>
{% autoescape off %} {% autoescape off %}
{{ content_string }} {{ content_string }}

2
src/templates/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow: /static/

View File

@@ -1,11 +1,12 @@
<html> <!DOCTYPE html>
<html lang={% if language=="de-de" %}de{% else %}en{% endif %}>
<head> <head>
<title>{{ title }}</title> <title>{{ title }}</title>
<link href="{{ url_for('static', filename='css/' + style + '.css') }}" rel="stylesheet" type="text/css"> <link href="{{ url_for('static', filename='css/' + style + '.css') }}" rel="stylesheet" type="text/css">
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='graphics/logo.png') }}"> <link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='graphics/logo.png')}}">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width" initial-scale=1.0> <meta name="viewport" content="width=device-width">
{{ font_awesome.load_js() }} {{ font_awesome.load_js() }}
</head> </head>
@@ -13,7 +14,9 @@
<!-- Menu --> <!-- Menu -->
<div class="main-menu-dropdown"> <div class="main-menu-dropdown">
<a href="{{ url_for('index') }}"> <a href="{{ url_for('index') }}">
<img class="logo" src="{{ url_for('static', filename='graphics/logo.png') }}"> <img class="logo" src="{{ url_for('static', filename='graphics/logo.png') }}" alt="Logo von
Mittelerde mit Marten. Zu sehen sind 3 'M's mit Serifen, wobei die beiden Äußeren etwas
kleiner sind">
{{ stitle }} {{ stitle }}
</a> </a>
<input type="checkbox" id="main-menu-check"> <input type="checkbox" id="main-menu-check">
@@ -32,9 +35,9 @@
<!-- Content --> <!-- Content -->
<footer> <footer>
<div class="center"> <div class="center">
<a href="{{ url_for('imprint') }}"> <a href="{{ url_for('imprint') }}">{% if language=="de-de" %}Impressum und Kontakt{% else %}Imprint and Contact{%
{% if language=="de-de" %}Impressum und Kontakt{% else %}Imprint and Contact{% endif %} endif %}</a>.<br>
</a><br> <a href="{{ url_for('feed') }}">{% if language=="de-de" %}RSS-Feed{% else %}RSS feed{% endif %}</a>.<br>
Made with <a href="https://github.com/tiyn/beaker-blog">Beaker Blog</a>. Made with <a href="https://github.com/tiyn/beaker-blog">Beaker Blog</a>.
</div> </div>
</footer> </footer>