This commit is contained in:
Yannik Schmidt
2020-07-26 22:50:45 +02:00
parent f21a979467
commit c9838954eb
21 changed files with 194 additions and 170 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
js/ js/
__pycache__/
css/ css/
*.swp *.swp
*.jpg *.jpg

View File

@@ -1,50 +1,14 @@
# Requirements # Requirements
This Softwares runs python3-flask with markdown, json and caldav. This Softwares runs python3-flask with markdown, json and caldav.
python3 -m pip install flask, json, caldav, markdown2 python3 -m pip install flask, 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. 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/*. 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 # 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: The page and most of it's content is configured via json, basic configuration is done in *config.py*.
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",
"siteDescription" : "a description for this site",
"siteLogo" : "url to logo",
"siteURL": "the url of this site"
}
Additionally it may contain the following information:
"teamspeak-server" : "TS_SERVER",
"discord-server" : "DISCORD_LINK",
"facebook" : "FACEBOOK_LINK",
"instagram" : "INSTAGRAM_LINK",
"twitter" : "TWITTER_LINK",
"twitch-channel" : "TWITCH_CHANNEL_NAME",
"twitch-placeholder-img" : "PLACEHOLDER_IMG"
## Startpage Sections ## Startpage Sections
### Events ### Events
@@ -108,4 +72,4 @@ New subpages must be added as a new location in the *server.py* like this:
def subpage(): def subpage():
return flask.render_template("subpage.html", conf=mainConfig) return flask.render_template("subpage.html", conf=mainConfig)
See the example subpage-templates in *templates/*. See the example *subpage\_example.html* in *templates/*.

4
app.py Normal file
View File

@@ -0,0 +1,4 @@
import server as moduleContainingApp
def createApp(envivorment=None, start_response=None):
return moduleContainingApp.app

31
config.py Normal file
View File

@@ -0,0 +1,31 @@
# calendar configuration #
SHOW_CALENDAR=False
CALENDAR_URL=""
CALENDAR_USERNAME=""
CALENDAR_PASSWORD=""
# content directory #
CONTENT_DIR="content.example"
# reload calendar on start #
RELOAD_CALENDAR_ON_START=True
# other
NEWS_MAX_AGE=90
SITEMAP_IGNORE = ["icon", "siteMap", "invalidate", "news"]
# site parameters
SITE_TITLE = "Site Title"
SITE_DESCRIPTION = "Site Description"
SITE_AUTHOR = "Site Author"
SITE_LOGO_URL = "Site Logo URL"
SITE_BASE_URL = "Site Base URL"
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

@@ -1,5 +1,5 @@
{ {
"picture" : "/static/pictures/placeholder.png", "picture" : "/pictures/placeholder.png",
"title" : "Section Title", "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.", "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..", "moreInfoButtonText" : "Mehr..",

View File

@@ -1,5 +1,5 @@
{ {
"picture" : "/static/pictures/placeholder.png", "picture" : "/pictures/placeholder.png",
"title" : "Section Title", "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." "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

@@ -1,5 +1,5 @@
{ {
"picture" : "/static/pictures/placeholder.png", "picture" : "/pictures/placeholder.png",
"title" : "Section Title", "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.", "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..", "moreInfoButtonText" : "Mehr..",

3
req.txt Normal file
View File

@@ -0,0 +1,3 @@
flask
caldav
markdown2

191
server.py
View File

@@ -8,40 +8,55 @@ import caldav
import datetime as dt import datetime as dt
import markdown2 import markdown2
# sitemap utilities # # sitemap utilities
from werkzeug.routing import BuildError from werkzeug.routing import BuildError
import xml.etree.ElementTree as et import xml.etree.ElementTree as et
VEREIN_SECTIONS_DIR = "sections/" # paths
MAIN_LINKS_DIR = "main-links/" SECTIONS_DIR = "sections/"
NEWS_DIR = "news/" NEWS_DIR = "news/"
PICTURES_DIR = "pictures/"
app = flask.Flask("athq-landing-page", static_folder=None) # json config keys
mainConfig = dict() TIMEOUT_RELATIVE = "timeout-relative-weeks"
with open("config.json") as f: TIMEOUT_FIXED = "timeout-fixed"
mainConfig = json.load(f) PARSED_TIME = "parsed-time"
ACTIVE = "active"
DATE = "date"
UID = "uid"
caldavUrl = None MARKDOWN_FILE_KEY = "markdown-file"
caldavPassword = None MARKDOWN_CONTENT_KEY = "markdown-content"
caldavUsername = None
# sitemap
PRIORITY_PRIMARY = 1.0
PRIORITY_SECONDARY = 0.8
# other
HTTP_NOT_FOUND = 404
EMPTY_STRING = ""
CACHE_FILE = "cache.json"
READ = "r"
WRITE = "w"
app = flask.Flask("FLASK_JSON_DREAM_WEBSITE", static_folder=None)
app.config.from_object("config")
def updateEventsFromCalDav(): def updateEventsFromCalDav():
'''Load event from a remote calendar'''
if app.config["SHOW_CALENDAR"]:
if app.config["USE_CALENDAR"]:
client = caldav.DAVClient(url=caldavUrl, username=caldavUsername, password=caldavPassword) client = caldav.DAVClient(url=caldavUrl, username=caldavUsername, password=caldavPassword)
authenticatedClient = client.principal() authenticatedClient = client.principal()
defaultCal = authenticatedClient.calendars()[0] defaultCal = authenticatedClient.calendars()[0]
start = dt.datetime.now() start = dt.datetime.now()
start = start - dt.timedelta(seconds=start.timestamp() % dt.timedelta(days=1).total_seconds()) start -= dt.timedelta(seconds=start.timestamp() % dt.timedelta(days=1).total_seconds())
end = start + dt.timedelta(days=90) end = start + dt.timedelta(days=app.config["NEWS_MAX_AGE"])
# TODO remove this
# start = start - dt.timedelta(days=90)
events = sorted(defaultCal.date_search(start, end), events = sorted(defaultCal.date_search(start, end),
key=lambda e: e.vobject_instance.vevent.dtstart.value) key=lambda e: e.vobject_instance.vevent.dtstart.value)
eventsDictList = [] eventsDictList = []
for e in events: for e in events:
date = e.vobject_instance.vevent.dtstart.value date = e.vobject_instance.vevent.dtstart.value
@@ -50,7 +65,7 @@ def updateEventsFromCalDav():
"time" : date.strftime("%H:%M"), "time" : date.strftime("%H:%M"),
"day" : date.strftime("%d"), "day" : date.strftime("%d"),
"month" : date.strftime("%b"), "month" : date.strftime("%b"),
"year" : date.strftime("%Y") } "year" : date.strftime("%Y")}
try: try:
newEventDict.update({ "location" : e.vobject_instance.vevent.location.value }) newEventDict.update({ "location" : e.vobject_instance.vevent.location.value })
except AttributeError: except AttributeError:
@@ -59,17 +74,20 @@ def updateEventsFromCalDav():
else: else:
eventsDictList = [] eventsDictList = []
with open("cache.json", "w") as f: # dump to cache file #
with open(CACHE_FILE, WRITE) as f:
json.dump(eventsDictList, f) json.dump(eventsDictList, f)
def getEventsCache(): def getEventsCache():
with open("cache.json", "r") as f: '''Return the cached events'''
with open(CACHE_FILE, READ) as f:
return json.load(f) return json.load(f)
def readJsonDir(basedir): def readJsonDir(basedir):
'''Read a directory containing json information'''
# load json files from projects/ dir #
jsonDictList =[] jsonDictList =[]
for root, dirs, files in os.walk(basedir): for root, dirs, files in os.walk(basedir):
for filename in sorted(files): for filename in sorted(files):
@@ -80,14 +98,10 @@ def readJsonDir(basedir):
return jsonDictList return jsonDictList
def parseNewsDirWithTimeout(): def parseNewsDirWithTimeout():
'''Parse a directory containing news-json structs and filter out
entries that have exceeded the max age'''
TIMEOUT_RELATIVE = "timeout-relative-weeks" news = readJsonDir(app.config["NEWS_DIR"])
TIMEOUT_FIXED = "timeout-fixed"
PARSED_TIME = "parsed-time"
ACTIVE = "active"
DATE = "date"
news = readJsonDir(NEWS_DIR)
now = dt.datetime.now() now = dt.datetime.now()
for n in news: for n in news:
n.update( { PARSED_TIME : dt.datetime.fromtimestamp(n[DATE]) } ) n.update( { PARSED_TIME : dt.datetime.fromtimestamp(n[DATE]) } )
@@ -104,96 +118,107 @@ def parseNewsDirWithTimeout():
return sorted(news, key=lambda n: n[PARSED_TIME], reverse=True) return sorted(news, key=lambda n: n[PARSED_TIME], reverse=True)
@app.route("/invalidate") @app.route("/invalidate")
def invalidateEventCache(): def invalidateEventCache():
'''Reload the calendar events'''
updateEventsFromCalDav(); updateEventsFromCalDav();
return ("", 204) return (EMPTY_STRING, 204)
@app.route("/") @app.route("/")
def root(): def root():
announcements = parseNewsDirWithTimeout() return flask.render_template("index.html", conf=app.config,
return flask.render_template("index.html", mainLinks=readJsonDir(MAIN_LINKS_DIR),
siteTitle=mainConfig["siteTitle"],
conf=mainConfig,
events=getEventsCache(), events=getEventsCache(),
moreEvents=len(getEventsCache())>3, moreEvents=len(getEventsCache())>3,
vereinSections=readJsonDir(VEREIN_SECTIONS_DIR), sections=readJsonDir(app.config["SECTIONS_DIR"]),
announcements=announcements) announcements=parseNewsDirWithTimeout())
@app.route("/impressum") @app.route("/impressum")
def impressum(): def impressum():
return flask.render_template("impressum.html", conf=mainConfig) return flask.render_template("impressum.html", conf=app.config)
@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") @app.route("/people")
def people(): def people():
return flask.render_template("people.html", conf=mainConfig, return flask.render_template("people.html", conf=app.config,
people=readJsonDir("people/")) people=readJsonDir("people/"))
@app.route("/news") @app.route("/news")
def news(): def news():
'''Display news-articles based on a UID-parameter'''
uid = flask.request.args.get("uid") requestedId = flask.request.args.get(UID)
# load news and map UIDs #
news = parseNewsDirWithTimeout() news = parseNewsDirWithTimeout()
newsDict = dict() newsDict = dict()
for n in news: for n in news:
newsDict.update( { n["uid"] : n } ) newsDict.update( { n[UID] : n } )
if not uid: # set newest article config if there is not UID #
article = sorted(news, key=lambda n: n["parsed-time"])[-1] # return 404 if the UID doesnt exist #
elif not newsDict[int(uid)]: # set article config of matching article otherwiese #
return ("", 404) if not requestedId:
article = sorted(news, key=lambda n: n[PARSED_TIME])[-1]
elif not newsDict[int(requestedId)]:
return (EMPTY_STRING, HTTP_NOT_FOUND)
else: else:
article = newsDict[int(uid)] article = newsDict[int(requestedId)]
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) # load article based on config #
try:
with open(article[MARKDOWN_FILE_KEY]) as f:
article.update( { MARKDOWN_CONTENT_KEY : markdown2.markdown(f.read()) } )
except FileNotFoundError as e:
return ("File not found Error ({})".format(e), HTTP_NOT_FOUND)
return flask.render_template("news.html", conf=app.config, article=article)
@app.route("/static/<path:path>") @app.route("/static/<path:path>")
def sendStatic(path): def sendStatic(path):
if "pictures" in path:
cache_timeout = 2592000
else:
cache_timeout = None cache_timeout = None
return flask.send_from_directory('static', path, cache_timeout=cache_timeout) return flask.send_from_directory('static', path, cache_timeout=cache_timeout)
@app.route("/picture/<path:path>")
def sendPicture(path):
cache_timeout = 2592000
return flask.send_from_directory(PICTURES_DIR, path, cache_timeout=cache_timeout)
@app.route('/defaultFavicon.ico') @app.route('/defaultFavicon.ico')
def icon(): def icon():
return flask.send_from_directory('static', 'defaultFavicon.ico') return flask.send_from_directory('static', 'defaultFavicon.ico')
@app.route("/sitemap.xml") @app.route("/sitemap.xml")
def siteMap(): def siteMap():
'''Return an XML-sitemap for SEO'''
# search for urls to add to sitemap #
urls = [] urls = []
# iterate through all endpoints #
for rule in app.url_map.iter_rules(): for rule in app.url_map.iter_rules():
skips = ["icon", "siteMap", "invalidate", "news"]
if any([s in rule.endpoint for s in skips]): # skip all endpoints #
if any([s in rule.endpoint for s in app.config["SITEMAP_IGNORE"]]):
continue continue
if "GET" in rule.methods:
# skip all non-GET endpoints #
if not "GET" in rule.methods:
continue
# get url for endpoint, get start time and set priority #
try: try:
url = flask.url_for(rule.endpoint, **(rule.defaults or {})) url = flask.url_for(rule.endpoint, **(rule.defaults or {}))
priority = 0.8 priority = PRIORITY_SECONDARY
if rule.endpoint == "root": if rule.endpoint == "root":
priority = 1.0 priority = PRIORITY_PRIMARY
urls += [(url, app.config["START_TIME"], priority)] urls += [(url, app.config["START_TIME"], priority)]
except BuildError: except BuildError:
pass pass
# add news articles to sitemap #
news = parseNewsDirWithTimeout() news = parseNewsDirWithTimeout()
for n in filter(lambda x: x["active"], news): for n in filter(lambda x: x["active"], news):
urls += [("/news?uid={}".format(n["uid"]), n["parsed-time"], 0.8)] urls += [("/news?uid={}".format(n[UID]), n[PARSED_TIME], PRIORITY_SECONDARY)]
hostname = flask.request.headers.get("X-REAL-HOSTNAME") hostname = flask.request.headers.get("X-REAL-HOSTNAME")
if not hostname: if not hostname:
@@ -215,32 +240,30 @@ def siteMap():
xmlDump += et.tostring(top, encoding='UTF-8', method='xml').decode() xmlDump += et.tostring(top, encoding='UTF-8', method='xml').decode()
return flask.Response(xmlDump, mimetype='application/xml') return flask.Response(xmlDump, mimetype='application/xml')
@app.before_first_request
def init():
app.config["SECTIONS_DIR"] = os.path.join(app.config["CONTENT_DIR"], SECTIONS_DIR)
app.config["NEWS_DIR"] = os.path.join(app.config["CONTENT_DIR"], NEWS_DIR)
if app.config["RELOAD_CALENDAR_ON_START"]:
updateEventsFromCalDav()
app.config["START_TIME"] = dt.datetime.now()
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Projects Showcase', parser = argparse.ArgumentParser(description='Projects Showcase',
formatter_class=argparse.ArgumentDefaultsHelpFormatter) formatter_class=argparse.ArgumentDefaultsHelpFormatter)
# general parameters # # general parameters #
parser.add_argument("-i", "--interface", default="0.0.0.0", help="Interface to listen on") parser.add_argument("-i", "--interface", default="127.0.0.1", help="Interface to listen on")
parser.add_argument("-p", "--port", default="5000", help="Port 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("--auto-reload", action="store_const", default=False, const=True,
help="Automaticly reload HTTP templates (impacts performance)")
parser.add_argument("--no-update-on-start", action="store_const", const=True, default=False, parser.add_argument("--no-update-on-start", action="store_const", const=True, default=False,
help="Don't update the calendar on start") help="Don't update the calendar on start")
# startup # # startup #
app.config['TEMPLATES_AUTO_RELOAD'] = True
args = parser.parse_args() args = parser.parse_args()
app.config['TEMPLATES_AUTO_RELOAD'] = args.auto_reload
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.config["START_TIME"] = dt.datetime.now()
app.run(host=args.interface, port=args.port) app.run(host=args.interface, port=args.port)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

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

View File

@@ -1,7 +1,10 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content=""> <meta name="description" content="{{ conf['SITE_DESCRIPTION'] }}">
<meta name="author" content=""> <meta name="author" content="{{ conf['SITE_AUTHOR'] }}">
<meta name="title" content="{{ conf['SITE_TITLE'] }}">
<link rel="shortcut icon" href="/defaultFavicon.ico"> <link rel="shortcut icon" href="/defaultFavicon.ico">
<!-- Bootstrap core CSS --> <!-- Bootstrap core CSS -->
@@ -14,16 +17,16 @@
<script src="/static/js/bootstrap.min.js"></script> <script src="/static/js/bootstrap.min.js"></script>
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:title" content="{{ conf['siteTitle'] }}" /> <meta property="og:title" content="{{ conf['SITE_TITLE'] }}" />
<meta property="og:description" content="{{ conf['siteDescription'] }}" /> <meta property="og:description" content="{{ conf['SITE_DESCRIPTION'] }}" />
<meta property="og:url" content="{{ url_for(request.endpoint) }}" /> <meta property="og:url" content="{{ url_for(request.endpoint) }}" />
<meta property="og:image" content="{{ conf['siteLogo'] }}"> <meta property="og:image" content="{{ conf['SITE_LOG_URL'] }}">
<script type="application/ld+json"> <script type="application/ld+json">
{ {
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "Organization", "@type": "Organization",
"url": "{{ conf['siteURL'] }}", "url": "{{ conf['SITE_BASE_URL'] }}",
"logo": "{{ conf['siteLogo'] }}" "logo": "{{ conf['SITE_LOGO_URL'] }}"
} }
</script> </script>

View File

@@ -4,8 +4,6 @@
{% include 'head.html' %} {% include 'head.html' %}
<title>{{ conf["siteTitle"] }}</title>
</head> </head>
<body style="background-color: #eae9e9"> <body style="background-color: #eae9e9">

View File

@@ -5,8 +5,6 @@
{% include 'head.html' %} {% include 'head.html' %}
<title>{{ conf["siteTitle"] }}</title>
<!-- Load the Twitch embed script --> <!-- Load the Twitch embed script -->
<!-- <script src="https://embed.twitch.tv/embed/v1.js"></script> --> <!-- <script src="https://embed.twitch.tv/embed/v1.js"></script> -->
<script src="https://sslrelay.atlantishq.de/twitch"></script> <script src="https://sslrelay.atlantishq.de/twitch"></script>
@@ -55,7 +53,7 @@
<div id="twitch-consent-placeholder" class="card bg-dark text-white"> <div id="twitch-consent-placeholder" class="card bg-dark text-white">
<img style="min-width: 80%; min-height: 200px;" <img style="min-width: 80%; min-height: 200px;"
class="card-img" src="/static/pictures/{{ conf['twitch-placeholder-img'] }}" > class="card-img" src="/pictures/{{ conf['twitch-placeholder-img'] }}" >
<div class="card-img-overlay"> <div class="card-img-overlay">
<label class="switch mt-3 mt-0-u440"> <label class="switch mt-3 mt-0-u440">
<input id="toogle-twitch" class="custom-control-input" <input id="toogle-twitch" class="custom-control-input"
@@ -97,7 +95,7 @@
</div> </div>
{% endif %} {% endif %}
{% for section in vereinSections %} {% for section in sections %}
<div class="{% if loop.index %2 == 1 %} bg-secondary {% else %} bg-dark {% endif %} pt-2 pb-2"> <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="container text-color-special">
<div class="row" {% if loop.index %2 == 1 %} style="flex-direction: row-reverse;" {% endif %}> <div class="row" {% if loop.index %2 == 1 %} style="flex-direction: row-reverse;" {% endif %}>

View File

@@ -15,15 +15,15 @@
<a class="nav-link" href="/">Home</a> <a class="nav-link" href="/">Home</a>
</li> </li>
{% if conf["teamspeak-server"] %} {% if conf["TEAMSPEAK_SERVER"] %}
<li class="nav-item right"> <li class="nav-item right">
<a class="nav-link" href="ts3server://{{ conf['teamspeak-server'] }}">Teamspeak</a> <a class="nav-link" href="ts3server://{{ conf['TEAMSPEAK_SERVER'] }}">Teamspeak</a>
</li> </li>
{% endif %} {% endif %}
{% if conf["discord-server"] %} {% if conf["DISCORD_SERVER"] %}
<li class="nav-item right"> <li class="nav-item right">
<a class="nav-link" href="{{ conf['discord-server'] }}">Discord</a> <a class="nav-link" href="{{ conf['DISCORD_SERVER'] }}">Discord</a>
</li> </li>
{% endif %} {% endif %}
@@ -32,21 +32,21 @@
<!-- right side --> <!-- right side -->
<ul class="navbar-nav"> <ul class="navbar-nav">
{% if conf["instagram"] %} {% if conf["INSTAGRAM"] %}
<li class="nav-item right"> <li class="nav-item right">
<a class="nav-link" href="{{ conf['instagram'] }}">Instagram</a> <a class="nav-link" href="{{ conf['INSTAGRAM'] }}">Instagram</a>
</li> </li>
{% endif %} {% endif %}
{% if conf["facebook"] %} {% if conf["FACEBOOK"] %}
<li class="nav-item right"> <li class="nav-item right">
<a class="nav-link" href="{{ conf['facebook'] }}">Facebook</a> <a class="nav-link" href="{{ conf['FACEBOOK'] }}">Facebook</a>
</li> </li>
{% endif %} {% endif %}
{% if conf["twitter"] %} {% if conf["TWITTER"] %}
<li> <li>
<a class="nav-link" href="{{ conf['twitter'] }}">Twitter</a> <a class="nav-link" href="{{ conf['TWITTER'] }}">Twitter</a>
</li> </li>
{% endif %} {% endif %}

View File

@@ -22,7 +22,7 @@
</div> </div>
<div class="col image-min-dimensions"> <div class="col image-min-dimensions">
<img class="img-responsive w-100 image-max-dimensions" <img class="img-responsive w-100 image-max-dimensions"
src="/static/pictures/{{ p['image'] }}"></img> src="/pictures/{{ p['image'] }}"></img>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}