16 Commits

Author SHA1 Message Date
coja
9eca59f80f [Doc] csv check 2026-05-02 05:05:26 +02:00
coja
c3ae746d83 [Doc] update 2026-05-02 04:49:06 +02:00
coja
a66da9b2d5 [Page] statute update 2026-05-02 04:17:07 +02:00
coja
e9147b34cb [Page] support monero wallet removed 2026-05-02 04:03:12 +02:00
coja
02c2dcc9fc [CSV] parsing csv with DictReader instead 2026-05-02 04:00:17 +02:00
coja
749574c8dc [Doc] cleanup 2026-05-02 03:58:58 +02:00
coja
c40f101540 [JS] Wrapped everything to DOM Loaded 2026-05-02 03:56:03 +02:00
coja
5958bbc24f [Doc] readme update for makefile 2026-05-02 03:45:29 +02:00
coja
d9c93d1182 [Makefile] init 2026-05-02 03:44:52 +02:00
coja
d001d6b287 [Prep] location in new tab 2026-05-02 03:43:43 +02:00
coja
910f375c94 [Requirements] missing pkgs 2026-05-02 03:36:28 +02:00
coja
69b0e5b63a [Events] readability improvement, banner spacing 2026-05-02 03:16:51 +02:00
coja
ec64147c6b [Events] spacing fix 2026-05-02 02:43:04 +02:00
coja
b6cfef830a [Theme] dark/light theme fix 2026-05-02 02:37:39 +02:00
coja
e3330ad1bd [Page] events 2026-05-02 02:13:59 +02:00
coja
6d31b3915b [Cleanup] libreoffice lock file 2026-05-02 02:13:48 +02:00
20 changed files with 267 additions and 147 deletions

View File

@@ -1 +0,0 @@
,user,localhost,03.05.2025 20:05,file:///home/user/.config/libreoffice/4;

30
Makefile Normal file
View File

@@ -0,0 +1,30 @@
.PHONY: build events dev stop help prep
help:
@echo "Available commands:"
@echo " make prep - Create venv and install requirements"
@echo " make events - Update site from CSV (build pages + images)"
@echo " make dev - Start development server"
@echo " make stop - Stop development server"
@echo " make build - Full website build sequence"
@echo " make help - Show this help message"
prep:
python3 -m venv .venv
./.venv/bin/pip install --upgrade pip
./.venv/bin/pip install -r requirements.txt
build:
./.venv/bin/python atom_gen.py
./.venv/bin/python prep.py
./.venv/bin/python build_pages.py
events:
./.venv/bin/python build_pages.py
./.venv/bin/python image_poster.py
dev:
nginx -p . -c nginx.dev.conf
stop:
nginx -p . -s stop

View File

@@ -1,40 +1,50 @@
# Decentrala
dmz.rs/decentrala.org website
[dmz.rs](https://dmz.rs/) / [decentrala.org](https://decentrala.org) website
## Setup
First, prepare the virtual environment and install dependencies:
```sh
make prep
```
## Build site
Run
To build the complete website:
```sh
python atom_gen.py
python prep.py
python build_pages.py
make build
```
Complete website will be contained in `site/`. You can copy this to server.
The complete website will be contained in `site/`. You can copy this to your server.
## Development server
To start a development server, first build site, then run (possibly with `sudo`)
To start a development server, first build the site, then run (possibly with `sudo`):
```sh
nginx -p . -c nginx.dev.conf
make dev
```
To stop it:
```sh
nginx -p . -s stop
make stop
```
## Events
To update events, update the `events.csv` then run commands
To update events, update `dogadjaji.csv` then run:
```sh
python build_pages.py # builds html out of csv
python image_poster.py # generates images for events
make events
```
For checking the csv data, suggestion is the [tennis pkg](https://github.com/gurgeous/tennis), with example command:
```sh
tennis -nt --zebra --color on --theme dark --tail 20 dogadjaji.csv
```

View File

@@ -36,7 +36,7 @@ def main():
f.write(html)
f.close()
with open(f'pages/en/{page["name"]}.html') as f:
pageHtml = "<div><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()
html = buildPage(page['name'], page['titleEN'], pageHtml, page['style'], templateEN)
f = open(f'site/en/{page["name"]}.html', 'w')

View File

@@ -1,4 +1,4 @@
datum, vreme, lokacija, tema, tip, link, temaE
datum, vreme, lokacija, tema, tip, link, temaEN
20-12-2022, 19:00, DC Krov https://www.openstreetmap.org/node/10594728522, Uvod u računarske mreže,,,
03-01-2023, 19:00, DC Krov https://www.openstreetmap.org/node/10594728522, Hackathon žurka, hack,,
16-01-2023, 19:00, DC Krov https://www.openstreetmap.org/node/10594728522, Privatnost na internetu, workshop,,
1 datum vreme lokacija tema tip link temaE temaEN
2 20-12-2022 19:00 DC Krov https://www.openstreetmap.org/node/10594728522 Uvod u računarske mreže
3 03-01-2023 19:00 DC Krov https://www.openstreetmap.org/node/10594728522 Hackathon žurka hack
4 16-01-2023 19:00 DC Krov https://www.openstreetmap.org/node/10594728522 Privatnost na internetu workshop

View File

@@ -4,12 +4,14 @@
<p>
Decentrala is a community, united around the principles of decentralized technology and the spreading of knowledge. 
</p>
<label>Key values include:</label>
<ul>
<li>Equal access to technology</li>
<li>Free software</li>
<li>Privacy and security</li>
</ul>
<div class="values">
<label>Our key values include:</label>
<ul>
<li>Equal access to technology</li>
<li>Free and open source software</li>
<li>Privacy and security</li>
</ul>
</div>
<p> Donations are accepted only from individuals, with no conditions or obligations. </p>
<p> Only open-source services are hosted on the Decentrala infrastructure. </p>
<p> Parts of the statute can be defined immutable, and cannot be voted on. </p>

View File

@@ -29,11 +29,11 @@
</p>
<ul>
<li>Bitcoin: <i>bc1qjhsfgq79wuzzv32yml9zglwzf9qcwfj3atuy74</i></li>
<li>
<!-- <li>
Monero:
<i
>8BESz45LnxrgCwZP32KieiN1D4LinCfsS1YjdFHfGXrVCmPs35167QsW1gd7qbff4UAtBbT6oWrkbfZnJm71HornVRiRZFS</i
>
</li>
</li> -->
</ul>
</dd>

View File

@@ -3,9 +3,16 @@
<div>
<p>
Decentrala je zajednica okupljena oko decentralizacije tehnologija i širenja
znanja. Decentralizacija uključuje ravnopravnost korišćenja tehnologije,
slobodnog softvera, privatnost i bezbednost.
znanja.
</p>
<div class="values">
<label> Decentralizacija uključuje: </label>
<ul>
<li>Ravnopravnost korišćenja tehnologije</li>
<li>Slobodan software otvorenog koda</li>
<li>Privatnost i bezbednost.</li>
</ul>
</div>
<p>
Na Decentralinoj arhitekturi hostuju se samo servisi koji su otvorenog koda
</p>
@@ -51,7 +58,8 @@
dodavanja novog clana, ako je prisutan na tom sastanku.
</p>
<p>
Trenutni članovi glasačkog tela su pod pseudonimom malin, coja, bora, mad3v, txrpe, euffrat, netstat.
Trenutni članovi glasačkog tela su pod pseudonimom malin, coja, bora, mad3v,
txrpe, euffrat, netstat.
</p>
</div>

View File

@@ -27,11 +27,13 @@
<p>Takođe primamo donacije u bitcoinu i moneru na adresama:</p>
<ul>
<li>Bitcoin: <i>bc1qjhsfgq79wuzzv32yml9zglwzf9qcwfj3atuy74</i></li>
<!--
<li>
Monero:
<i
>8BESz45LnxrgCwZP32KieiN1D4LinCfsS1YjdFHfGXrVCmPs35167QsW1gd7qbff4UAtBbT6oWrkbfZnJm71HornVRiRZFS</i
>
</li>
-->
</ul>
</dd>

23
prep.py
View File

@@ -20,20 +20,16 @@ TYPES_DICT = {
def load_events(csv_path:str) -> list[dict]:
events = []
with open(csv_path) as csv_file:
csv_reader = csv.reader(csv_file, skipinitialspace=True)
next(csv_reader, None)
with open(csv_path, encoding='utf-8') as csv_file:
csv_reader = csv.DictReader(csv_file, skipinitialspace=True)
for event in csv_reader:
event_date = event[0]
event_date = event["datum"]
event_date_parsed = datetime.strptime(event_date, "%d-%m-%Y").date()
event_time = event[1]
event_location = event[2]
event_title = event[3]
types = event[4].split()
try:
link = event[5]
except IndexError:
link = ""
event_time = event["vreme"]
event_location = event["lokacija"]
event_title = event["tema"]
types = event["tip"].split()
link = event.get("link", "")
current_event = {"date":event_date_parsed,
"time":event_time,
"location": event_location,
@@ -43,6 +39,7 @@ def load_events(csv_path:str) -> list[dict]:
events.append(current_event)
return events
def build_html(events: list[dict], dayNames: list[str], typesNames: dict) -> str:
events_html = []
for event in events:
@@ -59,7 +56,7 @@ def build_html(events: list[dict], dayNames: list[str], typesNames: dict) -> str
event_html.append(f"<div class='title'>{title}</div>")
if "https://" in location:
place,link = location.split("https://")
event_html.append(f"<div class='place'><a href=\"https://{link}\">@{place.strip()}</a></div>")
event_html.append(f"<div class='place'><a href=\"https://{link}\" target='_blank'>@{place.strip()}</a></div>")
else:
event_html.append(f"<div class='place'>@{location.strip()}</div>")

View File

@@ -2,3 +2,5 @@ cairosvg
markdown
freetype-py
python-dateutil
feedgen
pillow

View File

@@ -1,6 +1,16 @@
<!doctype html>
<html lang="sr">
<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 name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -36,7 +46,7 @@
<title>Dekonferencija Decentrala</title>
<link rel="alternate" hreflang="en" href="/en/deconference" />
</head>
<body class="theme light">
<body>
<header>
<a id="logo" href="/">
<img src="/img/logo-light.svg" alt="Logo" />

View File

@@ -1,58 +1,62 @@
const getById = (id) => document.getElementById(id);
const getByClass = (className) => document.getElementsByClassName(className)[0];
window.addEventListener("DOMContentLoaded", () => {
const themeBtn = getById("theme-switcher");
const hamburger = getByClass("hamburger");
const hamburgerIcon = hamburger.children[0]
const menu = document.getElementsByTagName("nav")[0];
const imgs = document.getElementsByTagName("img");
const main = document.getElementsByTagName("main")[0];
const isMenuOpen = () => hamburger.classList.contains("open");
const theme = window.localStorage.getItem("theme");
const body = document.getElementsByClassName("theme")[0];
const getById = (id) => document.getElementById(id);
const getByClass = (className) => document.getElementsByClassName(className)[0];
/* Functions */
const themeBtn = getById("theme-switcher");
const hamburger = getByClass("hamburger");
const hamburgerIcon = hamburger.children[0]
const menu = document.getElementsByTagName("nav")[0];
const imgs = document.getElementsByTagName("img");
const main = document.getElementsByTagName("main")[0];
const isMenuOpen = () => hamburger.classList.contains("open");
const theme = window.localStorage.getItem("theme");
const changeToDarkTheme = () => {
body.classList = "theme dark"
window.localStorage.setItem("theme", "dark");
themeBtn?.setAttribute("title", "turn the light on");
Array.from(imgs).forEach((img) => img.src = img.src.replace("-light", "-dark"));
}
/* Functions */
const changeToLightTheme = () => {
body.classList = "theme light"
window.localStorage.setItem("theme", "light");
themeBtn?.setAttribute("title", "turn the light off");
Array.from(imgs).forEach((img) => img.src = img.src.replace("-dark", "-light"));
}
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 closeMenu = () => {
hamburger.classList = "hamburger closed"
hamburgerIcon.src = hamburgerIcon.src.replace("opened", "closed");
menu.classList = "menu closed";
}
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 openMenu = () => {
hamburger.classList = "hamburger open"
hamburgerIcon.src = hamburgerIcon.src.replace("closed", "opened");
menu.classList = "menu open";
}
const closeMenu = () => {
hamburger.classList = "hamburger closed"
hamburgerIcon.src = hamburgerIcon.src.replace("opened", "closed");
menu.classList = "menu closed";
}
/* Listeners */
const openMenu = () => {
hamburger.classList = "hamburger open"
hamburgerIcon.src = hamburgerIcon.src.replace("closed", "opened");
menu.classList = "menu open";
}
window.addEventListener("resize", () => isMenuOpen() && closeMenu());
main.addEventListener("click", () => isMenuOpen() && closeMenu());
hamburger.addEventListener("click", () => isMenuOpen() ? closeMenu() : openMenu());
/* Listeners */
themeBtn.addEventListener("click", () => {
const title = themeBtn.getAttribute("title") ?? "off"
if (title.indexOf("off") !== -1) changeToDarkTheme();
else changeToLightTheme();
});
window.addEventListener("resize", () => isMenuOpen() && closeMenu());
main.addEventListener("click", () => isMenuOpen() && closeMenu());
hamburger?.addEventListener("click", () => isMenuOpen() ? closeMenu() : openMenu());
/* Rest */
themeBtn.addEventListener("click", () => {
const title = themeBtn.getAttribute("title") ?? "off"
if (title.indexOf("off") !== -1) changeToDarkTheme();
else changeToLightTheme();
});
const userPerfersDark = window?.matchMedia?.("(prefers-color-scheme: dark)").matches
if (!theme && userPerfersDark) changeToDarkTheme();
else theme === "light" ? changeToLightTheme() : changeToDarkTheme();
/* Rest */
const userPerfersDark = window?.matchMedia?.("(prefers-color-scheme: dark)").matches
if (!theme && userPerfersDark) changeToDarkTheme();
else theme === "light" ? changeToLightTheme() : changeToDarkTheme();
})

View File

@@ -1,25 +1,30 @@
.description {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 40px;
}
.event {
display: flex;
flex-direction: row;
}
.event:hover {
border-bottom: 5px var(--hightlight) solid;
}
.event:hover > div {
padding-bottom: calc(0.5rem - 5px);
}
.event > div {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
white-space: nowrap;
&:hover {
border-bottom: 5px var(--hightlight) solid;
& > div {
padding-bottom: calc(0.5rem - 5px);
}
}
& > div {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
white-space: nowrap;
}
}
.date {
width: 250px;
font-size: 0.9em;
width: 220px;
font-size: 0.75em;
overflow-x: hidden;
white-space: nowrap;
}
@@ -27,38 +32,44 @@
.title {
border-left: 2px solid var(--border);
font-weight: bold;
padding-left: 1rem;
padding-left: 2rem;
padding-right: 0.5em;
}
.place {
font-size: 0.9em;
font-size: 0.7em;
opacity: 0.7;
&:before {
content: "-";
margin-right: 12px;
}
}
.types {
font-size: 0.9em;
font-size: 0.8em;
margin-left: auto;
font-style: italic;
letter-spacing: 2px;
font-weight: 900;
opacity: 0.7;
}
@media screen and (max-width: 1160px) {
.event {
flex-direction: column;
margin-bottom: 1rem;
margin-bottom: 2.2rem;
border-left: 3px solid var(--border);
}
.event > div {
padding: 0 0.5rem;
white-space: normal;
}
.event:hover {
border-bottom: none;
}
.event:hover > div {
padding-bottom: 0;
&:hover {
border-bottom: none;
border-left: 3px solid var(--hightlight);
& > div {
padding-bottom: 0;
}
}
& > div {
padding: 0 0.5rem;
white-space: normal;
}
}
.date {
@@ -68,6 +79,9 @@
.title {
border-left: none;
}
.place:before {
display: none;
}
.types {
margin-left: 0;

View File

@@ -8,3 +8,15 @@ h2 {
p {
padding-bottom: 1.5rem;
}
.values {
margin-bottom: 1.5rem;
label {
display: block;
padding-bottom: 1rem;
}
ul > li {
padding-left: 1.5rem;
}
}

View File

@@ -11,7 +11,7 @@
--bg: var(--light-bg);
}
body.dark {
html.dark {
--border: var(--dark-border);
--text: var(--dark-text);
--bg: var(--dark-bg);

View File

@@ -1,8 +1,19 @@
<h1>Events</h1>
<p>
Following list contains all forthcoming events. Held events are listed in
<a href="/en/events_archive">archive</a>.
</p>
<br />
<p>We also provide <a href="https://dmz.rs/events.ical">ical file</a></p>
<br />
<div class="description">
<p>
Following list contains all forthcoming events. Held events are listed in
<a href="/en/events_archive">archive</a>.
</p>
<p>
Events are also available as an
<a href="https://dmz.rs/events.ical">ical</a> file.
</p>
<p>We also provide <a href="https://dmz.rs/events.ical">ical</a> file</p>
<p>
<a href="https://wiki.dmz.rs/en/kako-pronaci-prostor" target="_blank">
Short description
</a>
how to find a space
</p>
</div>

View File

@@ -1,19 +1,18 @@
<h1>Događaji</h1>
<p>
Naredna lista sadrži sve predstojeće događaje. Za listu održanih događaja
pogledaj <a href="/events_archive">arhivu</a>.
</p>
<br />
<p>
Događaje možeš učitati i sa
<a href="https://dmz.rs/events.ical">ical</a> datotekom.
</p>
<br />
<p>
<a href="https://wiki.dmz.rs/en/kako-pronaci-prostor" target="_blank">
Kratako uputstvo
</a>
kako pronaci prostor
</p>
<br />
<br />
<div class="description">
<p>
Naredna lista sadrži sve predstojeće događaje. Za listu održanih događaja
pogledaj <a href="/events_archive">arhivu</a>.
</p>
<p>
Događaje možeš učitati i sa
<a href="https://dmz.rs/events.ical">ical</a> datotekom.
</p>
<p>
<a href="https://wiki.dmz.rs/en/kako-pronaci-prostor" target="_blank">
Kratako uputstvo
</a>
kako pronaci prostor
</p>
</div>

View File

@@ -1,6 +1,16 @@
<!doctype html>
<html lang="en">
<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 name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -36,7 +46,7 @@
<title><!--TITLE--> Decentrala</title>
<link rel="alternate" hreflang="sr" href="/PAGE_NAME" />
</head>
<body class="theme light">
<body>
<header>
<a id="logo" href="/en/index">
<img src="/img/logo-light.svg" alt="Logo" /> Decentrala

View File

@@ -1,6 +1,16 @@
<!doctype html>
<html lang="sr">
<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 name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -36,7 +46,7 @@
<title><!--TITLE--> Decentrala</title>
<link rel="alternate" hreflang="en" href="/en/PAGE_NAME" />
</head>
<body class="theme light">
<body>
<header>
<a id="logo" href="/">
<img src="/img/logo-light.svg" alt="Logo" />