19 Commits
Author SHA1 Message Date
coja 6f12421b69 [Services] text update 2026-09-21 16:40:19 +02:00
coja 3eb1eca324 [Services] links are now opening in a new tab 2026-09-21 13:53:17 +02:00
coja 08932e4659 [Services] added invidious 2026-09-21 13:46:49 +02:00
coja b46bc133bf [Services] search listed again 2026-09-12 22:53:44 +02:00
fram3d a6392331ae add events for 09-2026 2026-08-20 11:02:49 +02:00
coja 904ed7e317 [Theme] fix persist pages 2026-08-09 06:53:25 +02:00
coja f6093d9876 [Fix] no more sudo for dev 2026-08-09 06:26:17 +02:00
coja 740a90536d [Fix] hamburger undefined 2026-08-09 06:08:56 +02:00
coja 26d1295006 [Fix] lang 2026-08-09 06:07:20 +02:00
coja 352709cec6 Revert "[Fix] no more sudo needed, runtime warn"
This reverts commit cc0127dbe3.
2026-08-09 06:02:28 +02:00
coja cc0127dbe3 [Fix] no more sudo needed, runtime warn 2026-08-09 05:49:52 +02:00
coja 2843f1763e [Lint] ran formated 2026-08-09 05:02:17 +02:00
coja d53aa42ecc [Makefile] lint and format 2026-08-09 05:01:59 +02:00
coja db0169f34b [Theme] light/dark persistance 2026-08-09 04:43:46 +02:00
coja b4a968d991 [Lang] fix persistance 2026-08-09 04:39:00 +02:00
coja a823f3a8bf [Services] pastebin 2026-08-09 04:14:56 +02:00
fram3d a3e9b61c5c added events for 08-2026 2026-07-29 00:50:30 +02:00
coja ec0b797f15 [Events] July update 2026-07-01 00:40:44 +02:00
coja e63f6a85be [Fix] typo and locations 2026-06-22 16:47:27 +02:00
18 changed files with 399 additions and 248 deletions
+8
View File
@@ -0,0 +1,8 @@
[tool.black]
line-length = 88
target-version = ['py312']
[tool.flake8]
max-line-length = 88
extend-ignore = "E203"
exclude = ".venv"
+1
View File
@@ -17,6 +17,7 @@ events.ical
.vscode/ .vscode/
events.html events.html
events_archive.html events_archive.html
nginx.pid
!site/lif* !site/lif*
+8 -2
View File
@@ -1,4 +1,4 @@
.PHONY: build events dev stop help prep .PHONY: build events dev stop help prep lint format
help: help:
@echo "Available commands:" @echo "Available commands:"
@@ -27,4 +27,10 @@ dev:
nginx -p . -c nginx.dev.conf nginx -p . -c nginx.dev.conf
stop: stop:
nginx -p . -s stop nginx -p . -c nginx.dev.conf -s stop
lint:
./.venv/bin/flake8 . --config .flake8 --exclude .venv
format:
./.venv/bin/black .
+17 -15
View File
@@ -11,7 +11,7 @@ import os
blogs_dir = os.fsencode("blog") blogs_dir = os.fsencode("blog")
#def blogposts_list_gen(): # def blogposts_list_gen():
# output_list = [] # output_list = []
# for file in os.listdir(blogs_dir): # for file in os.listdir(blogs_dir):
# filename = os.fsdecode(file) # filename = os.fsdecode(file)
@@ -26,6 +26,7 @@ blogs_dir = os.fsencode("blog")
# output_list.append([author, title, time, content_html, full_path]) # output_list.append([author, title, time, content_html, full_path])
# return output_list # return output_list
def events_list_gen(): def events_list_gen():
output_list = [] output_list = []
events_file = open("dogadjaji.csv", "r") events_file = open("dogadjaji.csv", "r")
@@ -38,34 +39,35 @@ def events_list_gen():
events_file.close() events_file.close()
return output_list return output_list
def feedgen(blogs, events): def feedgen(blogs, events):
fg_blog = FeedGenerator() fg_blog = FeedGenerator()
fg_blog.id('http://dmz.rs/') fg_blog.id("http://dmz.rs/")
fg_blog.title('Decentrala Blog') fg_blog.title("Decentrala Blog")
fg_blog.author( {'name':'Decentrala','email':'dmz@dmz.rs'} ) fg_blog.author({"name": "Decentrala", "email": "dmz@dmz.rs"})
fg_blog.link( href='https://dmz.rs/atom_blog.xml', rel='self' ) fg_blog.link(href="https://dmz.rs/atom_blog.xml", rel="self")
fg_events = FeedGenerator() fg_events = FeedGenerator()
fg_events.id('http://dmz.rs/') fg_events.id("http://dmz.rs/")
fg_events.title('Decentrala Blog') fg_events.title("Decentrala Blog")
fg_events.author( {'name':'Decentrala','email':'dmz@dmz.rs'} ) fg_events.author({"name": "Decentrala", "email": "dmz@dmz.rs"})
fg_events.link( href='https://dmz.rs/atom_events.xml', rel='self' ) fg_events.link(href="https://dmz.rs/atom_events.xml", rel="self")
for post in blogs: for post in blogs:
fe_blogs = fg_blog.add_entry() fe_blogs = fg_blog.add_entry()
fe_blogs.id("https://dmz.rs/" + post[4][:-3] + ".html") fe_blogs.id("https://dmz.rs/" + post[4][:-3] + ".html")
fe_blogs.author({'name': post[0]}) fe_blogs.author({"name": post[0]})
fe_blogs.title(post[1]) fe_blogs.title(post[1])
fe_blogs.updated(post[2]) fe_blogs.updated(post[2])
fe_blogs.content(content=post[3], type='html') fe_blogs.content(content=post[3], type="html")
for event in events: for event in events:
fe_events = fg_events.add_entry() fe_events = fg_events.add_entry()
fe_events.id("https://dmz.rs/pages/events.html") fe_events.id("https://dmz.rs/pages/events.html")
fe_events.author({'name': event[0]}) fe_events.author({"name": event[0]})
fe_events.title(event[1]) fe_events.title(event[1])
fe_events.updated(datetime.datetime.now(datetime.timezone.utc)) fe_events.updated(datetime.datetime.now(datetime.timezone.utc))
fe_events.content(content=event[2], type='html') fe_events.content(content=event[2], type="html")
fg_blog.atom_file('site/atom_blog.xml') fg_blog.atom_file("site/atom_blog.xml")
fg_events.atom_file('site/atom_events.xml') fg_events.atom_file("site/atom_events.xml")
+2 -2
View File
@@ -5,8 +5,8 @@ from markdown import markdown as to_markdown
blog = "" blog = ""
with open('blogs/Lorem Ipsum.md','rt') as file: with open("blogs/Lorem Ipsum.md", "rt") as file:
blog = file.read() blog = file.read()
with open('blogs/Lorem Ipsum.html', 'wt') as file: with open("blogs/Lorem Ipsum.html", "wt") as file:
file.write(to_markdown(blog)) file.write(to_markdown(blog))
+53 -23
View File
@@ -2,46 +2,76 @@
import os import os
PAGES = [ PAGES = [
{'name': 'index', 'titleSR': 'Početna', 'titleEN': 'Home', 'style': 'home'}, {"name": "index", "titleSR": "Početna", "titleEN": "Home", "style": "home"},
{'name': 'account', 'titleSR': 'Nalog', 'titleEN': 'Account', 'style': 'account'}, {"name": "account", "titleSR": "Nalog", "titleEN": "Account", "style": "account"},
{'name': 'about', 'titleSR': 'O nama', 'titleEN': 'About us', 'style': 'about'}, {"name": "about", "titleSR": "O nama", "titleEN": "About us", "style": "about"},
{'name': 'statute', 'titleSR': 'Statut', 'titleEN': 'Statute', 'style': 'statute'}, {"name": "statute", "titleSR": "Statut", "titleEN": "Statute", "style": "statute"},
{'name': 'events', 'titleSR': 'Događaji', 'titleEN': 'Events', 'style': 'events'}, {"name": "events", "titleSR": "Događaji", "titleEN": "Events", "style": "events"},
{'name': 'events_archive', 'titleSR': 'Arhiva događaja', 'titleEN': 'Events archive', 'style': 'events'}, {
{'name': 'services', 'titleSR': 'Servisi', 'titleEN': 'Services', 'style': 'services'}, "name": "events_archive",
{'name': 'webring', 'titleSR': 'Webring', 'titleEN': 'Webring', 'style': ''}, "titleSR": "Arhiva događaja",
{'name': 'support', 'titleSR': 'Podrška', 'titleEN': 'Support', 'style': 'support'}, "titleEN": "Events archive",
{'name': 'deconference', 'titleSR': 'Dekonferencija', 'titleEN': 'Deconference', 'style': 'deconference'}, "style": "events",
},
{
"name": "services",
"titleSR": "Servisi",
"titleEN": "Services",
"style": "services",
},
{"name": "webring", "titleSR": "Webring", "titleEN": "Webring", "style": ""},
{"name": "support", "titleSR": "Podrška", "titleEN": "Support", "style": "support"},
{
"name": "deconference",
"titleSR": "Dekonferencija",
"titleEN": "Deconference",
"style": "deconference",
},
] ]
def buildPage(filename: str, pageTitle: str, pageHtml: str, pageStyle: str, template: str) -> str:
template = template.replace('<!--TITLE-->', pageTitle) def buildPage(
style = '' if not pageStyle else f'<link rel=\"stylesheet\" href=\"/styles/{pageStyle}.css\">' filename: str, pageTitle: str, pageHtml: str, pageStyle: str, template: str
template = template.replace('<!--ADDITIONAL_STYLE-->', style) ) -> str:
template = template.replace('PAGE_NAME', filename) template = template.replace("<!--TITLE-->", pageTitle)
template = template.replace('<!--MAIN-->', pageHtml) style = (
""
if not pageStyle
else f'<link rel="stylesheet" href="/styles/{pageStyle}.css">'
)
template = template.replace("<!--ADDITIONAL_STYLE-->", style)
template = template.replace("PAGE_NAME", filename)
template = template.replace("<!--MAIN-->", pageHtml)
return template return template
def main(): def main():
os.makedirs('site/en/', exist_ok=True) os.makedirs("site/en/", exist_ok=True)
with open('template/page-en.html') as fTempEN, open('template/page-sr.html') as fTempSR: with open("template/page-en.html") as fTempEN, open(
"template/page-sr.html"
) as fTempSR:
templateSR = fTempSR.read() templateSR = fTempSR.read()
templateEN = fTempEN.read() templateEN = fTempEN.read()
for page in PAGES: for page in PAGES:
with open(f'pages/sr/{page["name"]}.html') as f: with open(f'pages/sr/{page["name"]}.html') as f:
pageHtml = "<div class='cover-wrap'><img src='/img/students_bug.jpg' alt='Studenti su nasli bug' /></div>" pageHtml = "<div class='cover-wrap'><img src='/img/students_bug.jpg' alt='Studenti su nasli bug' /></div>"
pageHtml += f.read() pageHtml += f.read()
html = buildPage(page['name'], page['titleSR'], pageHtml, page['style'], templateSR) html = buildPage(
f = open(f'site/{page["name"]}.html', 'w') page["name"], page["titleSR"], pageHtml, page["style"], templateSR
)
f = open(f'site/{page["name"]}.html', "w")
f.write(html) f.write(html)
f.close() f.close()
with open(f'pages/en/{page["name"]}.html') as f: with open(f'pages/en/{page["name"]}.html') as f:
pageHtml = "<div class='cover-wrap'><img src='/img/students_bug.jpg' alt='Students found the bug' /></div>" pageHtml = "<div class='cover-wrap'><img src='/img/students_bug.jpg' alt='Students found the bug' /></div>"
pageHtml += f.read() pageHtml += f.read()
html = buildPage(page['name'], page['titleEN'], pageHtml, page['style'], templateEN) html = buildPage(
f = open(f'site/en/{page["name"]}.html', 'w') page["name"], page["titleEN"], pageHtml, page["style"], templateEN
)
f = open(f'site/en/{page["name"]}.html', "w")
f.write(html) f.write(html)
f.close() f.close()
if __name__ == '__main__':
if __name__ == "__main__":
main() main()
+17 -4
View File
@@ -369,8 +369,21 @@ datum, vreme, lokacija, tema, tip, link, temaEN
01-06-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,Sysadmin radionica,workshop,,Sysadmin workshop 01-06-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,Sysadmin radionica,workshop,,Sysadmin workshop
02-06-2026, 18:00, Matematički fakultet (Učionica JAG2) https://www.openstreetmap.org/node/3807078606,Software hackathon,workshop,,Software hackathon 02-06-2026, 18:00, Matematički fakultet (Učionica JAG2) https://www.openstreetmap.org/node/3807078606,Software hackathon,workshop,,Software hackathon
07-06-2026, 10:00, KC Magacin - Marka Kraljevica 4 https://www.openstreetmap.org/#map=18/44.813246/20.453640, Zig Day,workshop,https://zig.day/europe/belgrade/1/,Zig Day 07-06-2026, 10:00, KC Magacin - Marka Kraljevica 4 https://www.openstreetmap.org/#map=18/44.813246/20.453640, Zig Day,workshop,https://zig.day/europe/belgrade/1/,Zig Day
08-06-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876, Decentrala offline forum,workshop,, Decentrala offline forum 08-06-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,Decentrala offline forum,workshop,,Decentrala offline forum
15-06-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876, OverTheWire - Bandit CTF,workshop,, OverTheWire - Bandit CTF 15-06-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,OverTheWire - Bandit CTF,workshop,,OverTheWire - Bandit CTF
16-06-2026, 18:00, Matematički fakultet (Učionica JAG2) https://www.openstreetmap.org/node/3807078606, OSM contributing,workshop,,OSM contributing 16-06-2026, 18:00, Matematički fakultet (Učionica JAG2) https://www.openstreetmap.org/node/3807078606, OSM contributing,workshop,,OSM contributing
22-06-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876, OverTheWire - Bandit CTF,workshop,, OverTheWire - Bandit CTF 22-06-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,OverTheWire - Bandit CTF,workshop,,OverTheWire - Bandit CTF
29-06-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876, LAN party,party,, LAN Party 29-06-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,LAN party,party,,LAN Party
06-07-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,Linux ricing,lightning,,Linux ricing
13-07-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,Hardware CTF/Logic analyzer,workshop,,Hardware CTF/Logic analyzer
20-07-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,T-shirt workshop,workshop,,T-shirt workshop
27-07-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,Lightning talks,lightning,,Lightning talks
03-08-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,Sysadmin radionica,workshop,,Sysadmin workshop
10-08-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,LAN party,party,,LAN Party
17-08-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,Mail server - 1. deo,workshop,, Mail server - part 1
24-08-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,Film: Primer (2004),movie,,Movie: Primer (2004)
31-08-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,Lightning talks,lightning,,Lightning talks
07-09-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,Sysadmin radionica,workshop,,Sysadmin workshop
14-09-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,Druzenje,party,,Party
21-09-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876,Mail server - 2. deo,workshop,, Mail server - part 2
28-09-2026, 19:00, Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876, Hakaton, hack, https://wiki.dmz.rs/decentrala/dogadjaji/hakaton, Hackathon
1 datum vreme lokacija tema tip link temaEN
369 01-06-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 Sysadmin radionica workshop Sysadmin workshop
370 02-06-2026 18:00 Matematički fakultet (Učionica JAG2) https://www.openstreetmap.org/node/3807078606 Software hackathon workshop Software hackathon
371 07-06-2026 10:00 KC Magacin - Marka Kraljevica 4 https://www.openstreetmap.org/#map=18/44.813246/20.453640 Zig Day workshop https://zig.day/europe/belgrade/1/ Zig Day
372 08-06-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 Decentrala offline forum workshop Decentrala offline forum
373 15-06-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 OverTheWire - Bandit CTF workshop OverTheWire - Bandit CTF
374 16-06-2026 18:00 Matematički fakultet (Učionica JAG2) https://www.openstreetmap.org/node/3807078606 OSM contributing workshop OSM contributing
375 22-06-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 OverTheWire - Bandit CTF workshop OverTheWire - Bandit CTF
376 29-06-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 LAN party party LAN Party
377 06-07-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 Linux ricing lightning Linux ricing
378 13-07-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 Hardware CTF/Logic analyzer workshop Hardware CTF/Logic analyzer
379 20-07-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 T-shirt workshop workshop T-shirt workshop
380 27-07-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 Lightning talks lightning Lightning talks
381 03-08-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 Sysadmin radionica workshop Sysadmin workshop
382 10-08-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 LAN party party LAN Party
383 17-08-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 Mail server - 1. deo workshop Mail server - part 1
384 24-08-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 Film: Primer (2004) movie Movie: Primer (2004)
385 31-08-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 Lightning talks lightning Lightning talks
386 07-09-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 Sysadmin radionica workshop Sysadmin workshop
387 14-09-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 Druzenje party Party
388 21-09-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 Mail server - 2. deo workshop Mail server - part 2
389 28-09-2026 19:00 Xecut - Jovana Ćirilova 15 - Local 3 https://www.openstreetmap.org/node/11749277876 Hakaton hack https://wiki.dmz.rs/decentrala/dogadjaji/hakaton Hackathon
+65 -34
View File
@@ -11,12 +11,36 @@ from cairosvg import svg2png
CURRENT_TIME = dt.date.today() CURRENT_TIME = dt.date.today()
NEXT_MONTH = CURRENT_TIME + relativedelta.relativedelta(months=1, day=1) NEXT_MONTH = CURRENT_TIME + relativedelta.relativedelta(months=1, day=1)
DAYS_OF_WEEK_SR = ("PON", "UTO", "SRE", "ČET", "PET", "SUB", "NED") DAYS_OF_WEEK_SR = ("PON", "UTO", "SRE", "ČET", "PET", "SUB", "NED")
DAYS_OF_WEEK_EN = ("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUn") DAYS_OF_WEEK_EN = ("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN")
MONTHS_SR = ("Januar", "Februar", "Mart", "April", "Maj", "Jun", "Jul", MONTHS_SR = (
"Avgust", "Septembar", "Oktobar", "Novembar", "Decembar") "Januar",
MONTHS_EN = ("January", "February", "March", "April", "May", "June", "July", "Februar",
"August", "September", "October", "November", "December") "Mart",
"April",
"Maj",
"Jun",
"Jul",
"Avgust",
"Septembar",
"Oktobar",
"Novembar",
"Decembar",
)
MONTHS_EN = (
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
)
HEADER_SR = "Plan za {}" HEADER_SR = "Plan za {}"
@@ -38,8 +62,13 @@ def parseArgs(parser):
""" """
Parse all arguments and return the list of argument values Parse all arguments and return the list of argument values
""" """
parser.add_argument("month", metavar="MM", parser.add_argument(
help="two digit number representing the month for which to generate poster", default="empty", nargs="?") "month",
metavar="MM",
help="two digit number representing the month for which to generate poster",
default="empty",
nargs="?",
)
return parser.parse_args() return parser.parse_args()
@@ -51,18 +80,19 @@ def load_events(csv_path: str, month: int) -> list[dict]:
next(csv_reader, None) next(csv_reader, None)
for event in csv_reader: for event in csv_reader:
event_date = event[0] event_date = event[0]
event_date_parsed = dt.datetime.strptime( event_date_parsed = dt.datetime.strptime(event_date, "%d-%m-%Y").date()
event_date, "%d-%m-%Y").date()
event_time = event[1] event_time = event[1]
event_title = event[3] event_title = event[3]
event_title_en = event[3] event_title_en = event[3]
if len(event) > 6: if len(event) > 6:
event_title_en = event[6] event_title_en = event[6]
current_event = {"date": event_date_parsed, current_event = {
"date": event_date_parsed,
"time": event_time, "time": event_time,
"title": event_title.strip(), "title": event_title.strip(),
"title_en": event_title_en.strip()} "title_en": event_title_en.strip(),
}
if event_date_parsed >= month and event_date_parsed < monthafter: if event_date_parsed >= month and event_date_parsed < monthafter:
events.append(current_event) events.append(current_event)
return events return events
@@ -71,8 +101,7 @@ def load_events(csv_path: str, month: int) -> list[dict]:
def drawMesh(draw, img, fg, bg, font, W, H): def drawMesh(draw, img, fg, bg, font, W, H):
def drawCircle(x, y): def drawCircle(x, y):
r = 50 r = 50
draw.ellipse((x - r, y - r, x + r, y+r), draw.ellipse((x - r, y - r, x + r, y + r), fill=fg, outline=(0, 0, 0), width=0)
fill=fg, outline=(0, 0, 0), width=0)
LCX = 415 # logo center x LCX = 415 # logo center x
LCY = 4350 # logo center y LCY = 4350 # logo center y
@@ -84,11 +113,15 @@ def drawMesh(draw, img, fg, bg, font, W, H):
drawCircle(LCX + d, LCY) drawCircle(LCX + d, LCY)
draw.line([(LCX - d, LCY), (LCX + d, LCY)], fill=fg, width=20, joint=None) draw.line([(LCX - d, LCY), (LCX + d, LCY)], fill=fg, width=20, joint=None)
draw.line([(LCX, LCY), (LCX, LCY + d), (LCX + d, LCY), draw.line(
(LCX, LCY - d)], fill=fg, width=20, joint=None) [(LCX, LCY), (LCX, LCY + d), (LCX + d, LCY), (LCX, LCY - d)],
draw.text((LCX - 1.7*d, LCY + 1.5*d), "dmz.rs", font=font, fill=fg) fill=fg,
width=20,
joint=None,
)
draw.text((LCX - 1.7 * d, LCY + 1.5 * d), "dmz.rs", font=font, fill=fg)
mesh_svg = svg2png(url='site/img/mesh-light.svg') mesh_svg = svg2png(url="site/img/mesh-light.svg")
mesh_svg_bytes = io.BytesIO(mesh_svg) mesh_svg_bytes = io.BytesIO(mesh_svg)
mesh_img = Image.open(mesh_svg_bytes) mesh_img = Image.open(mesh_svg_bytes)
if bg == (0, 0, 0): if bg == (0, 0, 0):
@@ -107,35 +140,32 @@ def drawMesh(draw, img, fg, bg, font, W, H):
def drawPoster(events, bg, fg, month: int, en: bool): def drawPoster(events, bg, fg, month: int, en: bool):
fontFacade = ImageFont.truetype('./site/font/Facade-Sud.woff', size=365) fontFacade = ImageFont.truetype("./site/font/Facade-Sud.woff", size=365)
fontIosevka = ImageFont.truetype( fontIosevka = ImageFont.truetype("./site/font/iosevka-regular.woff", size=200)
'./site/font/iosevka-regular.woff', size=200) fontIosevkaSmall = ImageFont.truetype("./site/font/iosevka-regular.woff", size=150)
fontIosevkaSmall = ImageFont.truetype(
'./site/font/iosevka-regular.woff', size=150)
W = 3508 W = 3508
H = 4960 H = 4960
img = Image.new('RGB', (W, H), bg) img = Image.new("RGB", (W, H), bg)
draw = ImageDraw.Draw(img) draw = ImageDraw.Draw(img)
drawMesh(draw, img, fg, bg, fontIosevka, W, H) drawMesh(draw, img, fg, bg, fontIosevka, W, H)
title = "DECENTRALA" title = "DECENTRALA"
_, _, w, _ = draw.textbbox((0, 0), title, font=fontFacade) _, _, w, _ = draw.textbbox((0, 0), title, font=fontFacade)
draw.text(((W-w)/2, 165), title, font=fontFacade, fill=fg) draw.text(((W - w) / 2, 165), title, font=fontFacade, fill=fg)
header = HEADER_EN if en else HEADER_SR header = HEADER_EN if en else HEADER_SR
months = MONTHS_EN if en else MONTHS_SR months = MONTHS_EN if en else MONTHS_SR
header = header.format(months[month.month - 1]) header = header.format(months[month.month - 1])
_, _, w, _ = draw.textbbox((0, 0), header, font=fontIosevka) _, _, w, _ = draw.textbbox((0, 0), header, font=fontIosevka)
draw.text(((W-w)/2, 560), header, font=fontIosevka, fill=fg) draw.text(((W - w) / 2, 560), header, font=fontIosevka, fill=fg)
height = 890 height = 890
sub_header = SUBHEADER_EN if en else SUBHEADER_SR sub_header = SUBHEADER_EN if en else SUBHEADER_SR
draw.text((165, height), sub_header, draw.text((165, height), sub_header, font=fontIosevkaSmall, fill=fg)
font=fontIosevkaSmall, fill=fg)
height += 800 height += 800
# Write list of events to sperate text file as well # Write list of events to sperate text file as well
@@ -167,8 +197,7 @@ def drawPoster(events, bg, fg, month: int, en: bool):
def main(): def main():
# Parse arguments # Parse arguments
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(description="Generate images of the poster")
description="Generate images of the poster")
args = parseArgs(parser) args = parseArgs(parser)
# Set month based on user input # Set month based on user input
@@ -176,22 +205,24 @@ def main():
if args.month.isdigit(): if args.month.isdigit():
month = dt.date(CURRENT_TIME.year, int(args.month), 1) month = dt.date(CURRENT_TIME.year, int(args.month), 1)
elif args.month != "empty": elif args.month != "empty":
print("Month has to be specified as a number. I will use next month as the default") print(
"Month has to be specified as a number. I will use next month as the default"
)
# Load events and draw a poseter # Load events and draw a poseter
events = load_events("dogadjaji.csv", month) events = load_events("dogadjaji.csv", month)
img = drawPoster(events, (0, 0, 0), (20, 250, 50), month, False) img = drawPoster(events, (0, 0, 0), (20, 250, 50), month, False)
img.save('poster_dark.png') img.save("poster_dark.png")
img = drawPoster(events, (255, 255, 255), (0, 0, 0), month, False) img = drawPoster(events, (255, 255, 255), (0, 0, 0), month, False)
img.save('poster_light.png') img.save("poster_light.png")
img = drawPoster(events, (0, 0, 0), (20, 250, 50), month, True) img = drawPoster(events, (0, 0, 0), (20, 250, 50), month, True)
img.save('poster_dark_en.png') img.save("poster_dark_en.png")
img = drawPoster(events, (255, 255, 255), (0, 0, 0), month, True) img = drawPoster(events, (255, 255, 255), (0, 0, 0), month, True)
img.save('poster_light_en.png') img.save("poster_light_en.png")
if __name__ == "__main__": if __name__ == "__main__":
+3
View File
@@ -1,9 +1,12 @@
# Start nginx in this directory with `nginx -p . -c nginx.conf` # Start nginx in this directory with `nginx -p . -c nginx.conf`
# Stop nginx with `nginx -p . -s stop` # Stop nginx with `nginx -p . -s stop`
pid nginx.pid;
events {} events {}
http { http {
# edit this for your system # edit this for your system
types_hash_max_size 2048;
types_hash_bucket_size 128; types_hash_bucket_size 128;
include /etc/nginx/mime.types; include /etc/nginx/mime.types;
+29 -26
View File
@@ -5,45 +5,45 @@
<th>Description</th> <th>Description</th>
</tr> </tr>
<tr> <tr>
<td><a href="/account">E-mail</a></td> <td><a href="/account" target="_blank">E-mail</a></td>
<td> <td>
E-mail account that you can use with any e-mail client (for example, with E-mail account that you can use with any e-mail client (for example, with
the <a href="https://www.thunderbird.net/">Thunderbird</a>). the <a href="https://www.thunderbird.net/" target="_blank">Thunderbird</a>).
</td> </td>
</tr> </tr>
<tr> <tr>
<td><a href="https://forum.dmz.rs/">Forum</a></td> <td><a href="https://forum.dmz.rs/" target="_blank">Forum</a></td>
<td> <td>
Forum for general discussion and <a href="/events">event</a> organization. Forum for general discussion and <a href="/events" target="_blank">event</a> organization.
</td> </td>
</tr> </tr>
<tr> <tr>
<td><a href="/account">Chat</a></td> <td><a href="/account" target="_blank">Chat</a></td>
<td> <td>
We have our XMPP server, on which you can make an account. If you already We have our XMPP server, on which you can make an account. If you already
have an account, you can find us at group have an account, you can find us at group
<a href="decentrala@conference.dmz.rs">decentrala@conference.dmz.rs</a>. <a href="decentrala@conference.dmz.rs" target="_blank">decentrala@conference.dmz.rs</a>.
</td> </td>
</tr> </tr>
<tr> <tr>
<td><a href="https://gitea.dmz.rs/">Git</a></td> <td><a href="https://gitea.dmz.rs/" target="_blank">Git</a></td>
<td> <td>
<a href="https://gitea.io/en-us/">Gitea</a> instance on which we host our <a href="https://gitea.io/en-us/" target="_blank">Gitea</a> instance on which we host our
code and resources (including the code for this site). code and resources (including the code for this site).
</td> </td>
</tr> </tr>
<tr> <tr>
<td><a href="https://wiki.dmz.rs/">Wiki</a></td> <td><a href="https://wiki.dmz.rs/" target="_blank">Wiki</a></td>
<td> <td>
<a href="https://js.wiki/">Wiki.js</a> instance on which we publish <a href="https://js.wiki/" target="_blank">Wiki.js</a> instance on which we publish
documentation for our projects, <a href="/events">events</a> resources, documentation for our projects, <a href="/events" target="_blank">events</a> resources,
and tutorials. and tutorials.
</td> </td>
</tr> </tr>
<tr> <tr>
<td><a href="https://jitsi.dmz.rs/">Jitsi</a></td> <td><a href="https://jitsi.dmz.rs/" target="_blank">Jitsi</a></td>
<td> <td>
<a href="https://jitsi.org/">Jitsi.org</a> meeting app, conferences, group video calls, online events, alternative <a href="https://jitsi.org/" target="_blank">Jitsi.org</a> meeting app, conferences, group video calls, online events, alternative
to zoom. to zoom.
</td> </td>
</tr> </tr>
@@ -59,33 +59,36 @@
<a href="https://cryptpad.org/">CryptPad</a> alternative to google office <a href="https://cryptpad.org/">CryptPad</a> alternative to google office
</td> </td>
</tr>--> </tr>-->
<!--<tr>
<tr> <tr>
<td><a href="https://search.dmz.rs/">Search</a></td> <tr>
<td><a href="https://invidious.dmz.rs/" target="_blank">Invidious</a></td>
<td> <td>
<a href="https://github.com/searxng/searxg/">SearXNG</a> instance used for <a href="https://invidious.io/" target="_blank">Invidious</a>, alternative font-end to YouTube.
Web searching.
</td> </td>
</tr> </tr>
<tr> <tr>
<td><a href="https://pastebin.dmz.rs/">Pastebin</a></td> <td><a href="https://search.dmz.rs/" target="_blank">Search</a></td>
<td> <td>
<a href="https://privatebin.info/">PrivateBin</a> instance we use for <a href="https://github.com/searxng/searxg/" target="_blank">SearXNG</a> instance used for Web searching.
sharing text files
</td> </td>
</tr> --> </tr>
<tr> <tr>
<td><a href="ssh://soft.dmz.rs:2222/">Soft Serve</a></td> <td><a href="https://pastebin.dmz.rs/" target="_blank">Pastebin</a></td>
<td> <td>
<a href="https://github.com/charmbracelet/soft-serve">Soft Serve</a> <a href="https://privatebin.info/" target="_blank">PrivateBin</a> instance for sharing textal data, like codes, logs, etc.
instance that we use as a replacement for the Gitea service. Soft Serve </td>
works entirely from the terminal. </tr>
<tr>
<td><a href="ssh://soft.dmz.rs:2222/" target="_blank">Soft Serve</a></td>
<td>
<a href="https://github.com/charmbracelet/soft-serve" target="_blank">Soft Serve</a>
TUI git instance. Soft Serve works entirely from the terminal over ssh.
</td> </td>
</tr> </tr>
</table> </table>
<p> <p>
These are some of the services we currently maintain on our servers. To use These are some of the services we currently maintain on our servers. To use
these services, you can register for each service separately, or you can these services, you can register for each service separately, or you can
create a unique <a href="/en/account">account</a> create a unique <a href="/en/account" target="_blank">account</a>
on our server and use all services with the same account. on our server and use all services with the same account.
</p> </p>
+30 -27
View File
@@ -5,33 +5,33 @@
<th>Opis</th> <th>Opis</th>
</tr> </tr>
<tr> <tr>
<td><a href="/account">E-mail</a></td> <td><a href="/account" target="_blank">E-mail</a></td>
<td> <td>
E-mail nalog koji možeš da koristiš sa bilo kojim email klijentom E-mail nalog koji možeš da koristiš sa bilo kojim email klijentom
generalne namene (na primer generalne namene (na primer
<a href="https://www.thunderbird.net/">Thunderbird</a>-om). <a href="https://www.thunderbird.net/" target="_blank">Thunderbird</a>-om).
</td> </td>
</tr> </tr>
<tr> <tr>
<td><a href="https://forum.dmz.rs/">Forum</a></td> <td><a href="https://forum.dmz.rs/" target="_blank">Forum</a></td>
<td> <td>
Forum na kom obično organizujemo naše <a href="/events">događaje</a>. Forum na kom obično organizujemo naše <a href="/events" target="_blank">događaje</a>.
</td> </td>
</tr> </tr>
<tr> <tr>
<td><a href="/account">Chat</a></td> <td><a href="/account" target="_blank">Chat</a></td>
<td> <td>
Održavamo sopstveni XMPP server, na kojem možeš da napraviš nalog. Ako već Održavamo sopstveni XMPP server, na kojem možeš da napraviš nalog. Ako već
poseduješ nalog možeš da nas nađeš u grupi poseduješ nalog možeš da nas nađeš u grupi
<a href="decentrala@conference.dmz.rs">decentrala@conference.dmz.rs</a>. <a href="decentrala@conference.dmz.rs" target="_blank">decentrala@conference.dmz.rs</a>.
</td> </td>
</tr> </tr>
<tr> <tr>
<td><a href="https://gitea.dmz.rs/">Git</a></td> <td><a href="https://gitea.dmz.rs/" target="_blank">Git</a></td>
<td> <td>
<a href="https://gitea.io/en-us/">Gitea</a> instanca na kojoj držimo kôd <a href="https://gitea.io/en-us/" target="_blank">Gitea</a> instanca na kojoj držimo kôd
kao i ostale resurse za naše <a href="/projects">projekte</a>, kao i ostale resurse za naše <a href="/projects" target="_blank">projekte</a>,
<a href="/events">događaje</a>, kao i projekte naših prijatelja. Ovo može <a href="/events" target="_blank">događaje</a>, kao i projekte naših prijatelja. Ovo može
biti dom tvog sledećeg projekta. Bolji od Github-a. biti dom tvog sledećeg projekta. Bolji od Github-a.
</td> </td>
</tr> </tr>
@@ -39,14 +39,14 @@
<td><a href="https://wiki.dmz.rs/">Wiki</a></td> <td><a href="https://wiki.dmz.rs/">Wiki</a></td>
<td> <td>
<a href="https://js.wiki/">Wiki.js</a> instanca koju koristimo da <a href="https://js.wiki/">Wiki.js</a> instanca koju koristimo da
dokumentujemo naše <a href="/projects">projekte</a> kao i ostale dokumentujemo naše <a href="/projects" target="_blank">projekte</a> kao i ostale
<a href="/events">događaje</a>. <a href="/events" target="_blank">događaje</a>.
</td> </td>
</tr> </tr>
<tr> <tr>
<td><a href="https://jitsi.dmz.rs/">Jitsi</a></td> <td><a href="https://jitsi.dmz.rs/" target="_blank">Jitsi</a></td>
<td> <td>
<a href="https://jitsi.org/">Jitsi.org</a> aplikacija za sastanke, groupne video pozive, online dogadjaje, alternativa za zoom. <a href="https://jitsi.org/" target="_blank">Jitsi.org</a> aplikacija za sastanke, groupne video pozive, online dogadjaje, alternativa za zoom.
</td> </td>
</tr> </tr>
<!--<tr> <!--<tr>
@@ -62,32 +62,35 @@
<a href="https://cryptpad.org/">CryptPad</a> zamena za google office <a href="https://cryptpad.org/">CryptPad</a> zamena za google office
</td> </td>
<tr>--> <tr>-->
<!--<tr> <tr>
<td><a href="https://search.dmz.rs/">Search</a></td> <td><a href="https://invidious.dmz.rs/" target="_blank">Invidious</a></td>
<td> <td>
<a href="https://github.com/searxng/searxng/">SearXNG</a> instanca koju <a href="https://invidious.io/" target="_blank">Invidious</a> instanca kao alternativa za YouTube.
koristimo za pretraživanje Interneta. Zamena za Google.
</td> </td>
</tr> </tr>
<tr> <tr>
<td><a href="https://pastebin.dmz.rs/">Pastebin</a></td> <td><a href="https://search.dmz.rs/" target="_blank">Search</a></td>
<td> <td>
<a href="https://privatebin.info/">PrivateBin</a> instanca koju koristimo <a href="https://github.com/searxng/searxng/" target="_blank">SearXNG</a> instanca za pretraživanje Interneta. Zamena za Google.
za brzo deljenje tekstualnih fajlova
</td> </td>
</tr>--> </tr>
<tr> <tr>
<td><a href="ssh://soft.dmz.rs:2222/">Soft Serve</a></td> <td><a href="https://pastebin.dmz.rs/" target="_blank">Pastebin</a></td>
<td> <td>
<a href="https://github.com/charmbracelet/soft-serve">Soft Serve</a> <a href="https://privatebin.info/" target="_blank">PrivateBin</a> instanca za brzo deljenje tekstualnih fajlova, kodova, logova i sl.
instanca koju koristimo kao zamenu za Gitea servis. Soft Serve radi </td>
potpuno iz terminala </tr>
<tr>
<td><a href="ssh://soft.dmz.rs:2222/" target="_blank">Soft Serve</a></td>
<td>
<a href="https://github.com/charmbracelet/soft-serve" target="_blank">Soft Serve</a>
TUI git instanca. Soft Serve radi potpuno iz terminala preko ssh.
</td> </td>
</tr> </tr>
</table> </table>
<p> <p>
Ovo su neki od servisa koje trenutno održavamo na našim serverima. Da bi Ovo su neki od servisa koje trenutno održavamo na našim serverima. Da bi
koristio ove servise, <em>možeš</em> da se registuješ na svaki servis posebno, koristio ove servise, <em>možeš</em> da se registuješ na svaki servis posebno,
a možeš i da napraviš jedinstveni <a href="/account">nalog</a> na našem a možeš i da napraviš jedinstveni <a href="/account" target="_blank">nalog</a> na našem
serveru i da koristiš sve servise sa istim nalogom. serveru i da koristiš sve servise sa istim nalogom.
</p> </p>
+29 -10
View File
@@ -7,10 +7,23 @@ EVENTS_CSV_PATH = "dogadjaji.csv"
CURRENT_TIME = dt.date.today() CURRENT_TIME = dt.date.today()
NEXT_MONTH = CURRENT_TIME + relativedelta.relativedelta(months=1, day=1) NEXT_MONTH = CURRENT_TIME + relativedelta.relativedelta(months=1, day=1)
DAYS_OF_WEEK_SR = ("PON", "UTO", "SRE", "ČET", "PET", "SUB", "NED") DAYS_OF_WEEK_SR = ("PON", "UTO", "SRE", "ČET", "PET", "SUB", "NED")
MONTHS_SR = ("Januar", "Februar", "Mart", "April", "Maj", "Jun", "Jul", "Avgust",\ MONTHS_SR = (
"Septembar", "Oktobar", "Novembar", "Decembar") "Januar",
"Februar",
"Mart",
"April",
"Maj",
"Jun",
"Jul",
"Avgust",
"Septembar",
"Oktobar",
"Novembar",
"Decembar",
)
def load_events(csv_path:str) -> list[dict]:
def load_events(csv_path: str) -> list[dict]:
events = [] events = []
with open(csv_path) as csv_file: with open(csv_path) as csv_file:
csv_reader = csv.reader(csv_file) csv_reader = csv.reader(csv_file)
@@ -20,14 +33,17 @@ def load_events(csv_path:str) -> list[dict]:
event_date_parsed = dt.datetime.strptime(event_date, "%d-%m-%Y").date() event_date_parsed = dt.datetime.strptime(event_date, "%d-%m-%Y").date()
event_time = event[1] event_time = event[1]
event_title = event[3] event_title = event[3]
current_event = {"date":event_date_parsed, current_event = {
"time":event_time, "date": event_date_parsed,
"title":event_title.strip()} "time": event_time,
"title": event_title.strip(),
}
if event_date_parsed >= NEXT_MONTH: if event_date_parsed >= NEXT_MONTH:
events.append(current_event) events.append(current_event)
return events return events
def render_table(events:list[dict])-> str:
def render_table(events: list[dict]) -> str:
html = "" html = ""
for event in events: for event in events:
date = DAYS_OF_WEEK_SR[event["date"].weekday()] date = DAYS_OF_WEEK_SR[event["date"].weekday()]
@@ -36,12 +52,13 @@ def render_table(events:list[dict])-> str:
html += f"\t\t\t<tr> <td>{date}</td> <td>{day}.</td> <td>{title}</td> </tr>\n" html += f"\t\t\t<tr> <td>{date}</td> <td>{day}.</td> <td>{title}</td> </tr>\n"
return html return html
def render_page(table: str) -> str: def render_page(table: str) -> str:
head = "<head><meta charset=\"UTF-8\"><link rel=\"stylesheet\"\ head = '<head><meta charset="UTF-8"><link rel="stylesheet"\
href=\"styles/poster.css\"><head>" href="styles/poster.css"><head>'
header = "<h1>DECENTRALA</h1>" header = "<h1>DECENTRALA</h1>"
subheader = f"<h2>Plan za {MONTHS_SR[NEXT_MONTH.month - 1]}</h2>" subheader = f"<h2>Plan za {MONTHS_SR[NEXT_MONTH.month - 1]}</h2>"
link = "<div id=link><img src=\"/img/logo-light.svg\"> dmz.rs</div>" link = '<div id=link><img src="/img/logo-light.svg"> dmz.rs</div>'
p1 = "<p>Radionice počinju u <strong>19h</strong> u Društvenom centru Krov\ p1 = "<p>Radionice počinju u <strong>19h</strong> u Društvenom centru Krov\
u <strong>Kraljice Marije 47</strong>.</p>" u <strong>Kraljice Marije 47</strong>.</p>"
p2 = "<p>Ulaz u zgradu je u prolazu pored Štark prodavnice slatkiša, odmah\ p2 = "<p>Ulaz u zgradu je u prolazu pored Štark prodavnice slatkiša, odmah\
@@ -50,6 +67,7 @@ pored menjačnice. DC Krov je na poslednjem spratu.</p>"
return f"<html>{head}<body><main>{header}{subheader}\ return f"<html>{head}<body><main>{header}{subheader}\
<table>{table}</table>{footer}</main></body></html>" <table>{table}</table>{footer}</main></body></html>"
def main(): def main():
events = load_events(EVENTS_CSV_PATH) events = load_events(EVENTS_CSV_PATH)
table = render_table(events) table = render_table(events)
@@ -58,5 +76,6 @@ def main():
f.write(page) f.write(page)
f.close() f.close()
if __name__ == "__main__": if __name__ == "__main__":
main() main()
+57 -25
View File
@@ -5,6 +5,13 @@ from datetime import datetime
DAYS_SR = ["PON", "UTO", "SRE", "ČET", "PET", "SUB", "NED"] DAYS_SR = ["PON", "UTO", "SRE", "ČET", "PET", "SUB", "NED"]
DAYS_EN = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"] DAYS_EN = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"]
ICAL_LOCATION_ADDRESSES = {
"Xecut": "XECUT\\, Jovana Ćirilova 15\\, Local 3\\, Beograd\\, Serbia",
"Matematički fakultet (Učionica JAG2)": "Matematički fakultet (JAG2)\\, Vatroslava Jagića 5\\, Beograd\\, Serbia",
"Matematički fakultet (Učionica JAG3)": "Matematički fakultet (JAG3)\\, Vatroslava Jagića 5\\, Beograd\\, Serbia",
"Matematički fakultet (Učionica 153)": "Matematički fakultet\\, Svetog Nikole 39\\, Beograd\\, Serbia",
}
TYPES_DICT = { TYPES_DICT = {
"hack": ("hakaton", "hackathon"), "hack": ("hakaton", "hackathon"),
"lecture": ("predavanje", "lecture"), "lecture": ("predavanje", "lecture"),
@@ -18,9 +25,10 @@ TYPES_DICT = {
"party": ("zabava", "entertainment"), "party": ("zabava", "entertainment"),
} }
def load_events(csv_path:str) -> list[dict]:
def load_events(csv_path: str) -> list[dict]:
events = [] events = []
with open(csv_path, encoding='utf-8') as csv_file: with open(csv_path, encoding="utf-8") as csv_file:
csv_reader = csv.DictReader(csv_file, skipinitialspace=True) csv_reader = csv.DictReader(csv_file, skipinitialspace=True)
for event in csv_reader: for event in csv_reader:
event_date = event["datum"] event_date = event["datum"]
@@ -30,12 +38,14 @@ def load_events(csv_path:str) -> list[dict]:
event_title = event["tema"] event_title = event["tema"]
types = event["tip"].split() types = event["tip"].split()
link = event.get("link", "") link = event.get("link", "")
current_event = {"date":event_date_parsed, current_event = {
"time":event_time, "date": event_date_parsed,
"time": event_time,
"location": event_location, "location": event_location,
"title":event_title.strip(), "title": event_title.strip(),
"types": types, "types": types,
"link": link} "link": link,
}
events.append(current_event) events.append(current_event)
return events return events
@@ -46,17 +56,30 @@ def build_html(events: list[dict], dayNames: list[str], typesNames: dict) -> str
title = event["title"] title = event["title"]
location = event["location"] location = event["location"]
date = event["date"] date = event["date"]
date = dayNames[date.weekday()]+", "+str(date.day)+". "+str(date.month)+". "+str(date.year)+", " date = (
time = event["time"]+"h" dayNames[date.weekday()]
+ ", "
+ str(date.day)
+ ". "
+ str(date.month)
+ ". "
+ str(date.year)
+ ", "
)
time = event["time"] + "h"
event_html = [] event_html = []
event_html.append(f"<div class='date'>{date} {time}</div>") event_html.append(f"<div class='date'>{date} {time}</div>")
if event["link"] != "": if event["link"] != "":
event_html.append(f"<div class='title'><a href=\"{event['link']}\">{title}</a></div>") event_html.append(
f"<div class='title'><a href=\"{event['link']}\">{title}</a></div>"
)
else: else:
event_html.append(f"<div class='title'>{title}</div>") event_html.append(f"<div class='title'>{title}</div>")
if "https://" in location: if "https://" in location:
place,link = location.split("https://") place, link = location.split("https://")
event_html.append(f"<div class='place'><a href=\"https://{link}\" target='_blank'>@{place.strip()}</a></div>") event_html.append(
f"<div class='place'><a href=\"https://{link}\" target='_blank'>@{place.strip()}</a></div>"
)
else: else:
event_html.append(f"<div class='place'>@{location.strip()}</div>") event_html.append(f"<div class='place'>@{location.strip()}</div>")
@@ -67,7 +90,7 @@ def build_html(events: list[dict], dayNames: list[str], typesNames: dict) -> str
if typesNames.get(t) is not None: if typesNames.get(t) is not None:
types_list += typesNames.get(t) types_list += typesNames.get(t)
if t != last_item: if t != last_item:
types_list += ', ' types_list += ", "
else: else:
print(f"Unknown type {t}!") print(f"Unknown type {t}!")
types_list += "</div>" types_list += "</div>"
@@ -77,6 +100,7 @@ def build_html(events: list[dict], dayNames: list[str], typesNames: dict) -> str
events_html.append(f"\n<div class='event'>{event_html}</div>") events_html.append(f"\n<div class='event'>{event_html}</div>")
return events_html return events_html
def build_ical(events: list[dict]) -> str: def build_ical(events: list[dict]) -> str:
today = datetime.today().now() today = datetime.today().now()
# Header # Header
@@ -93,7 +117,16 @@ def build_ical(events: list[dict]) -> str:
uid = str(date.month).zfill(2) + str(date.day).zfill(2) + time[:2] uid = str(date.month).zfill(2) + str(date.day).zfill(2) + time[:2]
date = str(date.year) + str(date.month).zfill(2) + str(date.day).zfill(2) date = str(date.year) + str(date.month).zfill(2) + str(date.day).zfill(2)
created = str(today.year) + str(today.month).zfill(2) + str(today.day).zfill(2) + "T" + str(today.hour).zfill(2) + str(today.minute).zfill(2) + str(today.second).zfill(2) + "Z" created = (
str(today.year)
+ str(today.month).zfill(2)
+ str(today.day).zfill(2)
+ "T"
+ str(today.hour).zfill(2)
+ str(today.minute).zfill(2)
+ str(today.second).zfill(2)
+ "Z"
)
date = date + "T" + time.replace(":", "") + "00" date = date + "T" + time.replace(":", "") + "00"
event_template = "" event_template = ""
@@ -104,13 +137,12 @@ def build_ical(events: list[dict]) -> str:
event_template = event_template.replace("<!--DATE-->", date) event_template = event_template.replace("<!--DATE-->", date)
event_template = event_template.replace("<!--TITLE-->", title) event_template = event_template.replace("<!--TITLE-->", title)
event_template = event_template.replace("<!--URL-->", url) event_template = event_template.replace("<!--URL-->", url)
if location.startswith("DC Krov"): ical_location = location
event_template = event_template.replace("<!--LOCATION-->", "DC Krov\\, Kraljice Marije 47\\, 6\\, Beograd\\, Serbia") for prefix, address in ICAL_LOCATION_ADDRESSES.items():
elif location.startswith("Matematički fakultet (Učionica 153)"): if location.startswith(prefix):
event_template = event_template.replace("<!--LOCATION-->", "Matematički fakultet\\, Svetog Nikole 39\\, Beograd\\, Serbia") ical_location = address
else: break
event_template = event_template.replace("<!--LOCATION-->", location) event_template = event_template.replace("<!--LOCATION-->", ical_location)
events_ical += event_template events_ical += event_template
# Footer # Footer
@@ -118,6 +150,7 @@ def build_ical(events: list[dict]) -> str:
events_ical += file.read() events_ical += file.read()
return events_ical return events_ical
events = sorted(load_events("dogadjaji.csv"), key=lambda e: e["date"]) events = sorted(load_events("dogadjaji.csv"), key=lambda e: e["date"])
today = datetime.today().date() today = datetime.today().date()
@@ -139,7 +172,7 @@ for key, value_pair in TYPES_DICT.items():
# Build Serbian Events page # Build Serbian Events page
new_events_html = build_html(new_events, DAYS_SR, sr_types) new_events_html = build_html(new_events, DAYS_SR, sr_types)
with open("template/events-sr.html", "r") as file: with open("template/events-sr.html", "r") as file:
page_template = ([line for line in file]) page_template = [line for line in file]
with open("pages/sr/events.html", "w") as file: with open("pages/sr/events.html", "w") as file:
file.writelines(page_template + new_events_html) file.writelines(page_template + new_events_html)
@@ -147,7 +180,7 @@ with open("pages/sr/events.html", "w") as file:
# Build English Events page # Build English Events page
new_events_html = build_html(new_events, DAYS_EN, en_types) new_events_html = build_html(new_events, DAYS_EN, en_types)
with open("template/events-en.html", "r") as file: with open("template/events-en.html", "r") as file:
page_template = ([line for line in file]) page_template = [line for line in file]
with open("pages/en/events.html", "w") as file: with open("pages/en/events.html", "w") as file:
file.writelines(page_template + new_events_html) file.writelines(page_template + new_events_html)
@@ -155,7 +188,7 @@ with open("pages/en/events.html", "w") as file:
# Build Serbian Archive page # Build Serbian Archive page
past_events_html = build_html(past_events, DAYS_SR, sr_types) past_events_html = build_html(past_events, DAYS_SR, sr_types)
with open("template/events_archive-sr.html", "r") as file: with open("template/events_archive-sr.html", "r") as file:
page_template = ([line for line in file]) page_template = [line for line in file]
with open("pages/sr/events_archive.html", "w") as file: with open("pages/sr/events_archive.html", "w") as file:
file.writelines(page_template + past_events_html) file.writelines(page_template + past_events_html)
@@ -163,7 +196,7 @@ with open("pages/sr/events_archive.html", "w") as file:
# Build English Archive page # Build English Archive page
past_events_html = build_html(past_events, DAYS_EN, en_types) past_events_html = build_html(past_events, DAYS_EN, en_types)
with open("template/events_archive-en.html", "r") as file: with open("template/events_archive-en.html", "r") as file:
page_template = ([line for line in file]) page_template = [line for line in file]
with open("pages/en/events_archive.html", "w") as file: with open("pages/en/events_archive.html", "w") as file:
file.writelines(page_template + past_events_html) file.writelines(page_template + past_events_html)
@@ -173,4 +206,3 @@ new_events_ical = build_ical(new_events)
# Build ical # Build ical
with open("site/events.ical", "w") as file: with open("site/events.ical", "w") as file:
file.write(build_ical(new_events)) file.write(build_ical(new_events))
+1 -1
View File
@@ -6,7 +6,7 @@
<link rel="stylesheet" href="/styles/style.css"> <link rel="stylesheet" href="/styles/style.css">
<link rel="stylesheet" href="/styles/404.css"> <link rel="stylesheet" href="/styles/404.css">
<link rel="shortcut icon" href="/img/favicon.ico" type="image/x-icon"> <link rel="shortcut icon" href="/img/favicon.ico" type="image/x-icon">
<script src="/scripts/main.js" defer></script> <script src="/scripts/main.js"></script>
<title>404</title> <title>404</title>
</head> </head>
<body> <body>
+1 -11
View File
@@ -1,16 +1,6 @@
<!doctype html> <!doctype html>
<html lang="sr"> <html lang="sr">
<head> <head>
<script>
(function () {
const theme = localStorage.getItem("theme");
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)",
).matches;
if (theme === "dark" || (!theme && prefersDark))
document.documentElement.classList.add("dark");
})();
</script>
<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, initial-scale=1.0" />
@@ -42,7 +32,7 @@
<link rel="stylesheet" href="/styles/style.css" /> <link rel="stylesheet" href="/styles/style.css" />
<link rel="stylesheet" href="/styles/deconference.css"> <link rel="stylesheet" href="/styles/deconference.css">
<link rel="shortcut icon" href="/img/favicon.ico" type="image/x-icon" /> <link rel="shortcut icon" href="/img/favicon.ico" type="image/x-icon" />
<script src="/scripts/main.js" defer></script> <script src="/scripts/main.js"></script>
<title>Dekonferencija Decentrala</title> <title>Dekonferencija Decentrala</title>
<link rel="alternate" hreflang="en" href="/en/deconference" /> <link rel="alternate" hreflang="en" href="/en/deconference" />
</head> </head>
+62 -32
View File
@@ -1,35 +1,49 @@
window.addEventListener("DOMContentLoaded", () => { (function () {
const theme = window.localStorage.getItem("theme");
const prefersDark = window?.matchMedia?.("(prefers-color-scheme: dark)").matches;
const isDark = theme === "dark" || (!theme && prefersDark);
if (isDark) document.documentElement.classList.add("dark");
// Swap images as soon as DOM is ready (script runs in <head>, imgs not parsed yet)
if (isDark) {
window.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll("img[src*='-light']").forEach((img) => {
img.src = img.src.replace("-light", "-dark");
});
}, { once: true });
}
window.changeTheme = (toDark) => {
if (toDark) {
window.localStorage.setItem("theme", "dark");
document.documentElement.classList.add("dark");
document.querySelectorAll("img[src*='-light']").forEach((img) => {
img.src = img.src.replace("-light", "-dark");
});
} else {
window.localStorage.setItem("theme", "light");
document.documentElement.classList.remove("dark");
document.querySelectorAll("img[src*='-dark']").forEach((img) => {
img.src = img.src.replace("-dark", "-light");
});
}
}
})();
window.addEventListener("DOMContentLoaded", () => {
const getById = (id) => document.getElementById(id); const getById = (id) => document.getElementById(id);
const getByClass = (className) => document.getElementsByClassName(className)[0]; const getByClass = (className) => document.getElementsByClassName(className)[0];
const themeBtn = getById("theme-switcher"); const themeBtn = getById("theme-switcher");
const hamburger = getByClass("hamburger"); const hamburger = getByClass("hamburger");
const hamburgerIcon = hamburger.children[0] const hamburgerIcon = hamburger?.children[0];
const menu = document.getElementsByTagName("nav")[0]; const menu = document.getElementsByTagName("nav")[0];
const imgs = document.getElementsByTagName("img");
const main = document.getElementsByTagName("main")[0]; const main = document.getElementsByTagName("main")[0];
const isMenuOpen = () => hamburger.classList.contains("open"); const isMenuOpen = () => hamburger?.classList.contains("open");
const theme = window.localStorage.getItem("theme");
/* Functions */ /* Functions */
const changeToDarkTheme = () => {
document.documentElement.classList.add("dark");
themeBtn?.setAttribute("title", "turn the light on");
Array.from(imgs).forEach((img) => {
if (img.src.includes("-light")) img.src = img.src.replace("-light", "-dark");
});
}
const changeToLightTheme = () => {
document.documentElement.classList.remove("dark");
themeBtn?.setAttribute("title", "turn the light off");
Array.from(imgs).forEach((img) => {
if (img.src.includes("-dark")) img.src = img.src.replace("-dark", "-light");
});
}
const closeMenu = () => { const closeMenu = () => {
hamburger.classList = "hamburger closed" hamburger.classList = "hamburger closed"
hamburgerIcon.src = hamburgerIcon.src.replace("opened", "closed"); hamburgerIcon.src = hamburgerIcon.src.replace("opened", "closed");
@@ -45,18 +59,34 @@ window.addEventListener("DOMContentLoaded", () => {
/* Listeners */ /* Listeners */
window.addEventListener("resize", () => isMenuOpen() && closeMenu()); window.addEventListener("resize", () => isMenuOpen() && closeMenu());
main.addEventListener("click", () => isMenuOpen() && closeMenu()); if (main) main.addEventListener("click", () => isMenuOpen() && closeMenu());
/* Language persistence */
const currentLang = document.documentElement.lang || "sr";
const anchors = document.getElementsByTagName("a");
Array.from(anchors).forEach((a) => {
// Skip language switcher links
if (a.classList.contains("lang")) return;
const href = a.getAttribute("href");
if (!href) return;
if (href.startsWith("/")) {
// Replace existing language prefix or add one
if (href.startsWith("/sr") || href.startsWith("/en")) {
if (!href.startsWith(`/${currentLang}`)) {
a.setAttribute("href", `/${currentLang}${href.slice(3)}`);
}
} else if (currentLang !== "sr") {
// Only prepend language prefix for non-default languages
a.setAttribute("href", `/${currentLang}${href}`);
}
}
});
hamburger?.addEventListener("click", () => isMenuOpen() ? closeMenu() : openMenu()); hamburger?.addEventListener("click", () => isMenuOpen() ? closeMenu() : openMenu());
if (themeBtn) {
themeBtn.addEventListener("click", () => { themeBtn.addEventListener("click", () => {
const title = themeBtn.getAttribute("title") ?? "off" const isDark = document.documentElement.classList.contains("dark");
if (title.indexOf("off") !== -1) changeToDarkTheme(); changeTheme(!isDark);
else changeToLightTheme();
}); });
}
/* Rest */ });
const userPerfersDark = window?.matchMedia?.("(prefers-color-scheme: dark)").matches
if (!theme && userPerfersDark) changeToDarkTheme();
else theme === "light" ? changeToLightTheme() : changeToDarkTheme();
})
-10
View File
@@ -1,16 +1,6 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<script>
(function () {
const theme = localStorage.getItem("theme");
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)",
).matches;
if (theme === "dark" || (!theme && prefersDark))
document.documentElement.classList.add("dark");
})();
</script>
<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, initial-scale=1.0" />
+1 -11
View File
@@ -1,16 +1,6 @@
<!doctype html> <!doctype html>
<html lang="sr"> <html lang="sr">
<head> <head>
<script>
(function () {
const theme = localStorage.getItem("theme");
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)",
).matches;
if (theme === "dark" || (!theme && prefersDark))
document.documentElement.classList.add("dark");
})();
</script>
<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, initial-scale=1.0" />
@@ -42,7 +32,7 @@
<link rel="stylesheet" href="/styles/style.css" /> <link rel="stylesheet" href="/styles/style.css" />
<!--ADDITIONAL_STYLE--> <!--ADDITIONAL_STYLE-->
<link rel="shortcut icon" href="/img/favicon.ico" type="image/x-icon" /> <link rel="shortcut icon" href="/img/favicon.ico" type="image/x-icon" />
<script src="/scripts/main.js" defer></script> <script src="/scripts/main.js"></script>
<title><!--TITLE--> Decentrala</title> <title><!--TITLE--> Decentrala</title>
<link rel="alternate" hreflang="en" href="/en/PAGE_NAME" /> <link rel="alternate" hreflang="en" href="/en/PAGE_NAME" />
</head> </head>