This commit is contained in:
2020-07-09 11:26:30 +02:00
commit 1f9bbbd2ca
28 changed files with 992 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
js/
css/
*.swp
*.jpg
*.png
cache.json
auth.txt

106
README.md Normal file
View File

@@ -0,0 +1,106 @@
# Requirements
This Softwares runs python3-flask with markdown, json and caldav.
python3 -m pip install flask, json, caldav, markdown2
This Software requires bootstrap > 4.13 which can be downloaded [here](https://getbootstrap.com/docs/4.3/getting-started/download/). It must be unpacked into the *static/*-directory into *js* and *css* respectively.
Additionally bootstrap depends on [jquery](https://code.jquery.com) which you have to download into a file called *jquery.min.js* in *static/js/*.
# Usage
./server.py -h
usage: server.py [-h] [-i INTERFACE] [-p PORT] --cal-info CAL_INFO
[--no-update-on-start]
optional arguments:
-h, --help show this help message and exit
-i INTERFACE Interface to listen on (default: 0.0.0.0)
-p PORT, --port PORT Port to listen on (default: 5000)
--cal-info CAL_INFO File Containing a public calendar link (default: None)
--no-update-on-start Don't update the calendar on start (default: False)
# Configuration
The page and most of it's content is configured via json. To use the CalDav-events section, you need to add a comma seperated file with the following format format/information:
URL,USER,PASSWORD
## Main Config
The main Config ``config.json`` which must be placed in the project-root must contain the following values:
{
"siteTitle":"the default site title",
}
Additionally it may contain the following information:
"teamspeak-server" : "TS_SERVER",
"discord-server" : "DISCORD_LINK",
"facebook" : "FACEBOOK_LINK",
"instagram" : "INSTAGRAM_LINK",
"twitter" : "TWITTER_LINK"
## Startpage Sections
### Events
The events section from the start-page is imported from the calendar and will show events a given time in the future or past. It will by default show three events. If there are more than three events, a *'More'*-button will be displayed. If there are no events the section will not be shown at all.
### News/Announcements
This Section will read and display JSON configuration from the *news/*-direcotry. A news-configuration must contain these information:
{
"title" : "title of the announcement",
"uid" : a_unique_integer_number,
"markdown-file" : "path to markdown file containing the actual announcement",
"active" : boolean_if_it_should_be_displayed,
"description" : "a short description",
"date" : date_posted_as_unix_timestamp,
}
Additionally it must contain either a 'fixed-timeout' or 'relative-timeout-weeks' after which it will no longer be displayed. If both a specified the fixed timeout takes precedence.
"timeout-relative-weeks" : 12,
"timeout-fixed" : a_date_as_unix_timestamp
Finally it MAY contain a text to display on the button leading to the article (otherwise it will use a default).
"link-text" : "maximum 25 characters"
Obviously the markdown file referenced in the configuration must also be created.
### Other Sections
All following sections are read and created from the *vereinSection* directory. Json configuration for these sections much contain these information. The pictures for all the sections should have a similar aspect ratio.
{
"picture" : "path to a picture for this section",
"title" : "A title for this card",
"text" : "A potentially very long text of multiple lines that will be displayed next to the picture...",
}
The configuration may contain the following information, which add a button-like link to the section.
"moreInfoButtonText" : "less than 25 charaters",
"moreInfoButtonHref" : "href to go to"
The alpha-numeric order of the filenames specifies the order in which the sections will be displayed on the website, so the files should be prefixed with a number, for example *10_section_hello.json* and *90_section_ending.json*.
## People
To display a person on the people-subpage create a JSON-file in the *people/*-directory containing the following information:
{
"title" : "Name of the Person",
"subtitle" : "Function of the Person",
"image" : "path to image",
"text" : "Potentially long text describing the person and their functions."
}
The order is again specified by the alpha-numeric order of the files.
# Adding new Subpages
New subpages must be added as a new location in the *server.py* like this:
@app.route("/subpage")
def subpage():
return flask.render_template("subpage.html", conf=mainConfig)
See the example subpage-templates in *templates/*.

10
config.json Normal file
View File

@@ -0,0 +1,10 @@
{
"siteTitle":"Site Title",
"teamspeak-server" : "teamspeak.com",
"discord-server" : "https://discord.gg/",
"facebook" : "https://www.facebook.com/",
"instagram" : "https://www.instagram.com/",
"twitter" : "https://twitter.com/its_a_sheppy",
"twitch-channel" : "esports-erlangen",
"twitch-placeholder-img" : "placeholder.png"
}

View File

@@ -0,0 +1,54 @@
# Utero est
## Plurima duro adeo viros spargere fletumque feras
Lorem markdownum Eurytion Troiana *nescioquam* fuit, tenuesque manebant illic
admittitur vultu. Praereptaque potitus vento.
1. Nec ostia sinus tellure
2. Esse focus labori hastas quippe
3. Vivos misit claustraque ille
4. Nusquam simulacra daedalus inde servor accessit obliquis
5. Restabam aestusque videri puerum dum videri nomen
6. Et radice cladis natus surgit ante
## Indicio hoc
Te spoliare lux sonuere genas: et medi Iuppiter litoris fama Piraeaque Phrygia
amorem obsessa animas? Nostros sustulit cauda cautus videntur consiste si
videtur *copia sed* inmemor ope nimia, proles. Sumpta Calydonida avido ipsorum
Phoebus tum fortuna rupit res: haud illa inpulsu et referre duabus. Ossaque
[albescere](http://contra-servavique.net/) nulla facta foedumque thalamos
deposuit accipit imago iactu percutiens **hostes**.
**Utinam versasse** sorores mirantes: rigidum cognoscite vicit ulterius famulis,
suis. Iam navita ferarum cacumina sincera tuli, iusserat pharetrae quod carne
at? Sororum videbitur aethera amor, mera lugubre [aetas artes
captantur](http://levibus.net/) conantem suae thalamos Minervaetransformabantur,
querenda capro ab media *quoque*. Cum sibila ait illa velo, et ab vestes iubent
crevit.
## Primusque Phaethon ramis
[Felici maduisse](http://latonaetransitus.io/non). Sub bimembres decipienda est
texta stipes. Ense ordine poscit rescindere **vidit** fulmina cubile leones!
Virgo per quatit ducem intabescere locus et superi ab quotiens amantes nos
dissimiles si pateres hastas. Magna imago, imperat discederet tanto loquebatur
turbatus, aether esse missus, in.
## Socialis Amor delubraque non et anseribus bracchia
Addicere graia qualis passu orantemque honorem Achaia coruscant Talia in ripae
perstat refert mediusve nemorum parentis. Cupit specie patuisse ad laniata et
patuit capillis pulsatus; primo est bis! Minervae accensus unda, tactosque
corpus, dea Iuppiter, vota herba, per deam inmitem **negata** visa inmania
extimuit? *Forent atque fulvis* vultu fidibusque causa, maestis purus per.
> In meritorum Lycaona in est velum ambo: si dixit et nubibus, illa quid Icelon.
> Sententia possis dumque; relinquam mandatam, ad leti terrebat gramen aenum,
> templis Issen atrorum.
Et tegebat eodem minimus curae, miseri illo huic *viscera ulli*, atque toros non
ego certaminis facibus. Sinus nec [Althaea](http://www.fortiatelum.io/), cum
Laelapa arbore, [contentus quam](http://www.vincere.io/nostras-qui): non, *et*.
Manebit dentes dedisses licet Vix Ilion quam putares gemino.

10
news/example_news.json Normal file
View File

@@ -0,0 +1,10 @@
{
"title" : "Title",
"uid" : 15,
"markdown-file" : "markdown/example_markdown.md",
"active" : true,
"description" : "Lorem markdownum Eurytion Troiana *nescioquam* fuit, tenuesque manebant illic admittitur vultu. Praereptaque potitus vento.",
"date" : 1594282463,
"timeout-relative-weeks" : 60,
"timeout-fixed" : 2094282463
}

6
people/10_john_doe.json Normal file
View File

@@ -0,0 +1,6 @@
{
"title" : "John Doe",
"subtitle" : "CEO & Founder",
"image" : "placeholder.png",
"text" : "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English."
}

6
people/20_jane_doe.json Normal file
View File

@@ -0,0 +1,6 @@
{
"title" : "Jane Doe",
"subtitle" : "COO & Founder",
"image" : "placeholder.png",
"text" : "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English."
}

View File

@@ -0,0 +1,7 @@
{
"picture" : "/static/pictures/placeholder.png",
"title" : "Section Title",
"text" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"moreInfoButtonText" : "Mehr..",
"moreInfoButtonHref" : "/impressum"
}

View File

@@ -0,0 +1,5 @@
{
"picture" : "/static/pictures/placeholder.png",
"title" : "Section Title",
"text" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
}

View File

@@ -0,0 +1,7 @@
{
"picture" : "/static/pictures/placeholder.png",
"title" : "Section Title",
"text" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"moreInfoButtonText" : "Mehr..",
"moreInfoButtonHref" : "/impressum"
}

197
server.py Executable file
View File

@@ -0,0 +1,197 @@
#!/usr/bin/python3
import json
import os
import flask
import argparse
import caldav
import datetime as dt
import markdown2
VEREIN_SECTIONS_DIR = "sections/"
MAIN_LINKS_DIR = "main-links/"
NEWS_DIR = "news/"
app = flask.Flask("athq-landing-page")
mainConfig = dict()
with open("config.json") as f:
mainConfig = json.load(f)
caldavUrl = None
caldavPassword = None
caldavUsername = None
def updateEventsFromCalDav():
if app.config["USE_CALENDAR"]:
client = caldav.DAVClient(url=caldavUrl, username=caldavUsername, password=caldavPassword)
authenticatedClient = client.principal()
defaultCal = authenticatedClient.calendars()[0]
start = dt.datetime.now()
start = start - dt.timedelta(seconds=start.timestamp() % dt.timedelta(days=1).total_seconds())
end = start + dt.timedelta(days=90)
# TODO remove this
# start = start - dt.timedelta(days=90)
events = sorted(defaultCal.date_search(start, end),
key=lambda e: e.vobject_instance.vevent.dtstart.value)
eventsDictList = []
for e in events:
date = e.vobject_instance.vevent.dtstart.value
date += dt.timedelta(hours=2)
newEventDict = { "description" : e.vobject_instance.vevent.summary.value,
"time" : date.strftime("%H:%M"),
"day" : date.strftime("%d"),
"month" : date.strftime("%b"),
"year" : date.strftime("%Y") }
try:
newEventDict.update({ "location" : e.vobject_instance.vevent.location.value })
except AttributeError:
pass
eventsDictList += [newEventDict]
else:
eventsDictList = []
with open("cache.json", "w") as f:
json.dump(eventsDictList, f)
def getEventsCache():
with open("cache.json", "r") as f:
return json.load(f)
def readJsonDir(basedir):
# load json files from projects/ dir #
jsonDictList =[]
for root, dirs, files in os.walk(basedir):
for filename in sorted(files):
if filename.endswith(".json"):
with open(os.path.join(basedir, filename)) as f:
jsonDictList += [json.load(f)]
return jsonDictList
def parseNewsDirWithTimeout():
TIMEOUT_RELATIVE = "timeout-relative-weeks"
TIMEOUT_FIXED = "timeout-fixed"
PARSED_TIME = "parsed-time"
ACTIVE = "active"
DATE = "date"
news = readJsonDir(NEWS_DIR)
now = dt.datetime.now()
for n in news:
n.update( { PARSED_TIME : dt.datetime.fromtimestamp(n[DATE]) } )
if n.get(ACTIVE):
continue
if n.get(TIMEOUT_FIXED):
if dt.datetime.fromtimestamp(n[TIMEOUT_FIXED]) < now:
n[ACTIVE] = False
elif n.get(TIMEOUT_RELATIVE):
if n[PARSED_TIME] + dt.timedelta(weeks=n[TIMEOUT_RELATIVE]) < now:
n[ACTIVE] = False
else:
raise ValueError("No timeout for news {} specified!", n)
return sorted(news, key=lambda n: n[PARSED_TIME], reverse=True)
@app.route("/invalidate")
def invalidateEventCache():
updateEventsFromCalDav();
return ("", 204)
@app.route("/")
def root():
announcements = parseNewsDirWithTimeout()
return flask.render_template("index.html", mainLinks=readJsonDir(MAIN_LINKS_DIR),
siteTitle=mainConfig["siteTitle"],
conf=mainConfig,
events=getEventsCache(),
moreEvents=len(getEventsCache())>3,
vereinSections=readJsonDir(VEREIN_SECTIONS_DIR),
announcements=announcements)
@app.route("/impressum")
def impressum():
return flask.render_template("impressum.html", conf=mainConfig)
@app.route("/verein")
def verein():
return flask.render_template("verein.html", conf=mainConfig)
@app.route("/stammtisch")
def stammtisch():
return flask.render_template("stammtisch.html", conf=mainConfig)
@app.route("/people")
def people():
return flask.render_template("people.html", conf=mainConfig,
people=readJsonDir("people/"))
@app.route("/news")
def news():
uid = int(flask.request.args.get("uid"))
news = parseNewsDirWithTimeout()
newsDict = dict()
for n in news:
newsDict.update( { n["uid"] : n } )
if not uid or not newsDict[uid]:
return ("", 404)
article = newsDict[uid]
try:
with open(article["markdown-file"]) as f:
article.update( { "markdown-content" : markdown2.markdown(f.read()) } )
except FileNotFoundError as e:
return ("File not found Error ({})".format(e), 404)
return flask.render_template("news.html", conf=mainConfig, article=article)
@app.route("/static/<path:path>")
def sendStatic(path):
if "pictures" in path:
cache_timeout = 2592000
else:
cache_timeout = None
return flask.send_from_directory('static', path, cache_timeout=cache_timeout)
@app.route('/defaultFavicon.ico')
def icon():
return app.send_static_file('defaultFavicon.ico')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Projects Showcase',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
# general parameters #
parser.add_argument("-i", "--interface", default="0.0.0.0", help="Interface to listen on")
parser.add_argument("-p", "--port", default="5000", help="Port to listen on")
parser.add_argument("--cal-info", help="File Containing a public calendar link")
parser.add_argument("--no-update-on-start", action="store_const", const=True, default=False,
help="Don't update the calendar on start")
# startup #
app.config['TEMPLATES_AUTO_RELOAD'] = True
args = parser.parse_args()
if args.cal_info:
app.config["USE_CALENDAR"] = True
with open(args.cal_info) as f:
caldavUrl, caldavUsername, caldavPassword = f.read().strip().split(",")
else:
app.config["USE_CALENDAR"] = False
if not args.no_update_on_start:
updateEventsFromCalDav()
app.run(host=args.interface, port=args.port)

BIN
static/defaultFavicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1 @@
placeholder.png

70
static/site.css Normal file
View File

@@ -0,0 +1,70 @@
html {
height: 100vh;
}
.masthead {
height: 50vh;
min-height: 250px;
background-image: url('/static/pictures/wallpaper.jpg');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.bg-special{
background-color: #eae9e9;
}
.text-color-special{
color: #eae9e9;
}
.stretched-link::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
pointer-events: auto;
content: "";
background-color: rgba(0,0,0,0);
}
.hover-to-75:hover *{
opacity: 0.75;
display: unset;
}
.position-relative{
position: relative;
}
.centered-text-in-image{
position: absolute;
color: black !important;
font-weight: bold;
font-size: x-large;
top: 50%;
text-decoration: none !important;
left: 50%;
transform: translate(-50%, -50%);
opacity: 0;
}
.centered-text-in-image:hover{
display: none;
opacity: 1 !important;
}
.image-min-dimensions{
min-width: 330px;
/*min-height: 220px; */
}
.text-min-dimensions{
min-width: 300px;
}
.image-max-dimensions{
max-width: 400px;
}

63
static/toggle_button.css Normal file
View File

@@ -0,0 +1,63 @@
.switch {
position: relative;
float: left;
width: 60px;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: #2196F3;
}
input:focus + .slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
.switch-caption {
float: left;
}

View File

@@ -0,0 +1,18 @@
<div class="row">
{% for a in announcements if a.get("active") %}
<div class="col-md-4 text-dark">
<div class="card mb-4 box-shadow bg-special">
<div class="card-body">
<h4>{{ a["parsed-time"].strftime("%d.%m.%y") }}</h4>
<h2>{{ a["title"] }}</h2>
<p>{{ a["description"] }}</p>
<p>
<a class="mt-3 btn btn-secondary float-right" href="/news?uid={{ a['uid'] }}" role="button">
{% if a["link-title"] %} {{ a["link-title"] }} {% else %} Mehr.. {% endif %}
</a>
</p>
</div>
</div>
</div>
{% endfor %}
</div>

43
templates/events.html Normal file
View File

@@ -0,0 +1,43 @@
{% if events %}
<div class="col grid-margin-md stretch-card d-flex_" style="font-size: x-large;">
<div class="card bg-transparent border-0">
<div class="card-body">
<div class="d-flex justify-content-between"></div>
{% for event in events %}
<div class="moreDates" {% if loop.index > 3 %} style="display: none;" {% endif %}>
<div class="row">
<div class="pt-1 border-bottom {% if loop.index == 1 %} border-top {% endif %} col">
<div class="d-flex justify-content-center align-items-center">
<h1 class="mr-5 text-primary font-weight-bold text-info">{{ event["day"] }}</h1>
<div>
<p class="font-weight-bold mb-0 text-dark">{{ event["month"] }}</p>
<p class="mb-2">{{ event["year"] }}</p>
</div>
</div>
</div>
<div class="pt-1 border-bottom {% if loop.index == 1 %} border-top {% endif %} col pl-3">
<p class="text-dark font-weight-bold mb-0">
{{ event["description"] }}
{% if event.get("location") %} @{{ event["location"] }} {% endif %}
</p>
<p class="mb-0">{{ event["time"] }}</p>
</div>
</div>
</div>
{% endfor %}
{% if moreEvents %}
<button id="moreDatesButton" class="float-right mt-3 btn btn-light" onClick=showAdditionalDates()>Mehr..</button>
{% endif %}
</div>
</div>
</div>
<!-- mehr Termine Button -->
<script>
function showAdditionalDates(){
Array.from(document.getElementsByClassName("moreDates")).forEach(element => {
element.style.display = "";
});
document.getElementById("moreDatesButton").style.display = "none"
}
</script>
{% endif %}

3
templates/footer.html Normal file
View File

@@ -0,0 +1,3 @@
<div class="footer-copyright text-center py-3 bg-dark" style="position: relative; bottom: 0;">
<a style="color: rgba(255,255,255,.5);" href="/impressum">Impressum/Kontakt</a>
</div>

14
templates/head.html Normal file
View File

@@ -0,0 +1,14 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="shortcut icon" href="/defaultFavicon.ico">
<!-- Bootstrap core CSS -->
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/site.css" rel="stylesheet">
<link href="/static/toggle_button.css" rel="stylesheet">
<!-- Bootstrap core JS -->
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>

51
templates/impressum.html Normal file
View File

@@ -0,0 +1,51 @@
<!doctype html>
<html lang="en">
<head>
{% include 'head.html' %}
<title>{{ conf["siteTitle"] }}</title>
</head>
<body style="background-color: #eae9e9">
{% include 'navbar.html' %}
<div class="navbar navbar-default"></div>
<div class="container h-100 mt-5">
<h1>Impressum</h1>
<div class="row impressum mt-5 pl-3">
<div class="col-sm">
<h4>Hello World</h4>
<p>Organisation<br>
Hello World: Hello World<br>
Hello World: Hello World<br>
Mail: <a href="mailto:noreply@example.com">
noreply@example.com</a></p>
<h4>Person</h4>
<p>John Doe<br>
Location<br> Postal City<br>
Phone: 12345 6789 </p>
</div>
<div class="col-sm pr-5">
<h4>Hello World</h4>
<p>Jane Doe<br>
Location<br> Postal City<br>
<p>
</p>
<h4>Admin C/Hello World</h4>
<p>
Free Text
</p>
</div>
</div>
<div class="row impressum mt-5"></div>
<div class="col-lg-12">
{% include 'impressum_text.html' %}
</div>
</div>
</div>
{% include 'footer.html' %}
</body>
</html>

View File

@@ -0,0 +1,21 @@
<h1>HTML Ipsum</h1>
<p><strong>Pellentesque habitant morbi tristique</strong> senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, <code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. <a href="#">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.</p>
<h2>Header Level 2</h2>
<ol>
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
<li>Aliquam tincidunt mauris eu risus.</li>
</ol>
<blockquote><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.</p></blockquote>
<h3>Header Level 3</h3>
<ul>
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
<li>Aliquam tincidunt mauris eu risus.</li>
</ul>
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>

126
templates/index.html Normal file
View File

@@ -0,0 +1,126 @@
<!doctype html>
<html lang="en">
<head>
{% include 'head.html' %}
<title>{{ conf["siteTitle"] }}</title>
<!-- Load the Twitch embed script -->
<!-- <script src="https://embed.twitch.tv/embed/v1.js"></script> -->
<script src="https://sslrelay.atlantishq.de/twitch"></script>
</head>
<body>
<!-- NAVBAR -->
{% include 'navbar.html' %}
<!-- Site wellcome header -->
<header class="masthead">
<div class="container h-50">
<div class="row h-100 align-items-center">
<div class="col-12 text-center">
<div style="opacity: 0;"></div>
<div class="mt-5" style="opacity: 0;"></div>
<!--
<h1 class="font-weight-light">{{ conf["siteWellcomeMsg"] }}</h1>
<p class="lead">{{ conf["siteWellcomeMsgSub"] }}</p>
-->
</div>
</div>
</div>
</header>
<!-- main links -->
<div class="bg-secondary">
<div class="container pb-2 pt-2">
{% include 'events.html' %}
</div>
</div>
<!-- announcements -->
{% if announcements %}
<div class="bg-secondary">
<div class="container pb-2 pt-2">
{% include 'announcements.html' %}
</div>
</div>
{% endif %}
<!-- twitch -->
{% if conf["twitch-channel"] %}
<div class="bg-dark pb-4">
<div class="container pt-5 text-color-special">
<div id="twitch-consent-placeholder" class="card bg-dark text-white">
<img style="min-width: 80%; min-height: 200px;"
class="card-img" src="/static/pictures/{{ conf['twitch-placeholder-img'] }}" >
<div class="card-img-overlay">
<label class="switch mt-3">
<input id="toogle-twitch" class="custom-control-input"
type="checkbox" onchange="handleToggle(this.id)">
<span class="slider"></span>
</label>
<div class="switch-caption ml-3 mt-3" style="font-size: x-large;">
Laden externe Inhalte von Twitch.tv zulassen
</div>
</div>
</div>
<!-- Add a placeholder for the Twitch embed -->
<div id="twitch-embed"></div>
<script>
document.getElementById("toogle-twitch").checked = false
function handleToggle(id){
if(document.getElementById(id).checked){
createTwitchFrame()
document.getElementById("twitch-consent-placeholder").style.display = "none";
}else{
document.getElementById("twitch-consent-placeholder").style.display = "";
destroyTwitchFrame()
}
}
function createTwitchFrame(){
options = { width: "100%", height: 480, channel: "{{ conf['twitch-channel'] }}" }
new Twitch.Embed("twitch-embed", options);
}
function destroyTwitchFrame(){
window.location.reload()
}
</script>
<!-- Create a Twitch.Embed object that will render within the "twitch-embed" root element. -->
</div>
</div>
{% endif %}
{% for section in vereinSections %}
<div class="{% if loop.index %2 == 1 %} bg-secondary {% else %} bg-dark {% endif %} pt-2 pb-2">
<div class="container text-color-special">
<div class="row" {% if loop.index %2 == 1 %} style="flex-direction: row-reverse;" {% endif %}>
<div class="mt-3 col image-min-dimensions">
<img class="img-responsive w-100" src="{{ section['picture'] }}">
</div>
<div class="mt-3 col text-min-dimensions">
<h1>{{ section['title'] }}</h1>
<p class="mt-3">
{{ section["text"] }}
</p>
{% if section["moreInfoButtonText"] %}
<button type=button
onclick="window.location.href='{{ section['moreInfoButtonHref'] }}'"
class="mt-3 btn btn-light">{{ section["moreInfoButtonText"] }}
</button>
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% include 'footer.html' %}
</body>
</html>

61
templates/navbar.html Normal file
View File

@@ -0,0 +1,61 @@
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<a class="navbar-brand" href="#"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar"
aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar">
<!-- left side -->
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
{% if conf["teamspeak-server"] %}
<li class="nav-item right">
<a class="nav-link" href="ts3server://{{ conf['teamspeak-server'] }}">Teamspeak</a>
</li>
{% endif %}
{% if conf["discord-server"] %}
<li class="nav-item right">
<a class="nav-link" href="{{ conf['discord-server'] }}">Discord</a>
</li>
{% endif %}
</ul>
<!-- right side -->
<ul class="navbar-nav">
{% if conf["instragram"] %}
<li class="nav-item right">
<a class="nav-link" href="{{ conf['instragram'] }}">Instagram</a>
</li>
{% endif %}
{% if conf["facebook"] %}
<li class="nav-item right">
<a class="nav-link" href="{{ conf['facebook'] }}">Facebook</a>
</li>
{% endif %}
{% if conf["twitter"] %}
<li>
<a class="nav-link" href="{{ conf['twitter'] }}">Twitter</a>
</li>
{% endif %}
</ul>
</div>
</nav>
<nav class="navbar navbar-dark bg-dark fake">
<li class="navbar-nav">
<a class="nav-link" href="#">placeholder</a>
</li>
</nav>

28
templates/news.html Normal file
View File

@@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
{% include 'head.html' %}
<title>{{ article["title"] }}</title>
</head>
<body class="bg-special">
<style>
p{
margin-top: 30px;
}
</style>
{% include 'navbar.html' %}
<div class="container mt-5 mb-5">
<div class="row impressum mt-5"></div>
<div class="col-lg-12" style="font-size: large;">
<h3 mb-2>{{ article["parsed-time"].strftime("%d.%m. %Y") }}</h3>
{{ article["markdown-content"] | safe }}
<div class="pb-3"></div>
</div>
</div>
</div>
{% include 'footer.html' %}
</body>
</html>

34
templates/people.html Normal file
View File

@@ -0,0 +1,34 @@
<!doctype html>
<html lang="en">
<head>
<title>Menschen</title>
{% include 'head.html' %}
</head>
<body class="bg-special">
{% include 'navbar.html' %}
<div class="container mt-5 mb-5">
{% for p in people if not p.get("inactive") %}
<div class="row impressum mt-5">
<div class="col text-min-dimensions">
<h2>{{ p["title"] }}</h2>
<h4>{{ p["subtitle"] }}</h4>
<p>
{{ p["text"] }}
</p>
</div>
<div class="col image-min-dimensions">
<img class="img-responsive w-100 image-max-dimensions"
src="/static/pictures/{{ p['image'] }}"></img>
</div>
</div>
{% endfor %}
</div>
{% include 'footer.html' %}
</body>
</html>

View File

@@ -0,0 +1,23 @@
<!doctype html>
<html lang="en">
<head>
{% include 'head.html' %}
<title>Stammtisch</title>
</head>
<body class="h-100 bg-special">
{% include 'navbar.html' %}
<div class="container mt-5 mb-5 h-100">
<div class="row impressum mt-5"></div>
<div class="col-lg-12">
{% include 'stammtisch_text.html' %}
<div class="pb-3"></div>
</div>
</div>
</div>
{% include 'footer.html' %}
</body>
</html>

View File

@@ -0,0 +1,21 @@
<h1>HTML Ipsum</h1>
<p><strong>Pellentesque habitant morbi tristique</strong> senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, <code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. <a href="#">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.</p>
<h2>Header Level 2</h2>
<ol>
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
<li>Aliquam tincidunt mauris eu risus.</li>
</ol>
<blockquote><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.</p></blockquote>
<h3>Header Level 3</h3>
<ul>
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
<li>Aliquam tincidunt mauris eu risus.</li>
</ul>
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>