6 Commits

Author SHA1 Message Date
Burer 62746818af language selector refactroring and optimization 2026-01-06 18:46:33 +02:00
Burer b2018f1df0 rewrite gallery to css 2026-01-06 18:23:50 +02:00
Sweetbread 2c0f65e59f fix: handle trailing slashes
Docker Build and Push / build-and-push (push) Successful in 2m2s
2025-12-31 23:05:41 +03:00
Burer 03cbd2f60b cleanup: delete leftover files
Docker Build and Push / build-and-push (push) Successful in 1m17s
2025-12-30 07:57:21 +02:00
Burer 72bb6c2e79 tweak: enable JS minification for non-debug mode
Docker Build and Push / build-and-push (push) Successful in 1m14s
2025-12-29 21:20:56 +02:00
Sweetbread 002cf1ad07 ci: add docker image creation
Docker Build and Push / build-and-push (push) Successful in 1m22s
2025-12-29 21:49:08 +03:00
12 changed files with 232 additions and 171 deletions
+31
View File
@@ -0,0 +1,31 @@
name: Docker Build and Push
on:
push:
branches: [main]
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Login to Docker Registry
uses: docker/login-action@v2
with:
registry: git.kolibrios.org
username: kolibrios
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: ${{ github.event_name == 'push' }}
tags: git.kolibrios.org/kolibrios/kolibrios.org:latest
cache-from: type=gha
cache-to: type=gha,mode=max
-1
View File
@@ -17,7 +17,6 @@ htmlcov/
docs/_build/
# Our's
Dockerfile
.env
static/*.css
static/*.css.map
+23
View File
@@ -0,0 +1,23 @@
FROM node:18-alpine as sass
RUN npm install -g sass
WORKDIR /build
COPY ./static ./static
RUN sass ./static:./static \
--no-source-map \
--style=compressed
FROM python:3.11-slim
WORKDIR /app
COPY . .
COPY --from=sass /build/static/ ./static/
RUN pip install --no-cache-dir -r requirements.txt
ENV FLASK_ENV=production
ENV PYTHONUNBUFFERED=1
CMD ["gunicorn", "app:app", "-b", "0.0.0.0:80", "--workers", "4"]
+19 -12
View File
@@ -4,7 +4,7 @@ import datetime
from sass import compile as compile_sass
from flask import Flask, redirect, request, url_for, g, Response
from modules import autobuild, locales, helpers
from modules import autobuild, locales, helpers, slides
# ---------- APP CONFIG ------------------------------------------------------
@@ -12,22 +12,24 @@ from modules import autobuild, locales, helpers
app = Flask(__name__)
# load locales and slides
locales.ensure_loaded()
SLIDES = slides.load_slides(app.root_path)
# CSS Compilation and minification
if app.debug:
# CSS Compilation and minification
css = compile_sass(filename="static/style.scss", output_style="compressed")
with open("static/style.css", "w", encoding="utf-8") as f:
f.write(css)
# JS minification
with open("static/script.js", encoding="utf-8") as f:
js = f.read()
js = re.sub(r"/\*.*?\*/", "", js, flags=re.S)
js = re.sub(r"//.*", "", js)
js = re.sub(r"\s+", " ", js).strip()
with open("static/script.min.js", "w", encoding="utf-8") as f:
f.write(js)
# JS minification
with open("static/script.js", encoding="utf-8") as f:
js = f.read()
js = re.sub(r"/\*.*?\*/", "", js, flags=re.S)
js = re.sub(r"//.*", "", js)
js = re.sub(r"\s+", " ", js).strip()
with open("static/script.min.js", "w", encoding="utf-8") as f:
f.write(js)
@app.before_request
@@ -53,6 +55,11 @@ def _inject_autobuild_date():
return {'autobuild_date': autobuild.autobuild_date}
@app.context_processor
def _inject_slides():
return {"slides": SLIDES}
@app.context_processor
def inject_translations():
def translate(text, **kwargs):
@@ -78,12 +85,12 @@ def home():
return redirect(url_for("index", lang=helpers.get_best_lang()))
@app.route("/<lang>")
@app.route("/<lang>", strict_slashes=False)
def index(lang):
return helpers.render_localized_template(lang, "index.html")
@app.route("/<lang>/download")
@app.route("/<lang>/download", strict_slashes=False)
def download(lang):
return helpers.render_localized_template(lang, "download.html")
+31
View File
@@ -0,0 +1,31 @@
from pathlib import Path
def load_slides(root_path):
shots_dir = Path(root_path) / "static" / "img" / "screenshots"
if not shots_dir.exists():
return []
slides = []
for path in shots_dir.iterdir():
if not path.is_file():
continue
if path.suffix.lower() not in {".png", ".jpg", ".jpeg", ".webp", ".gif"}:
continue
stem = path.stem
num = int(stem) if stem.isdigit() else None
slides.append(
{
"filename": f"img/screenshots/{path.name}",
"key": stem,
"num": num,
}
)
slides.sort(
key=lambda item: (
item["num"] is None,
item["num"] if item["num"] is not None else item["key"],
)
)
return slides
+1
View File
@@ -8,3 +8,4 @@ Jinja2==3.1.6
libsass==0.23.0
MarkupSafe==3.0.2
Werkzeug==3.1.3
gunicorn
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

+29 -106
View File
@@ -1,119 +1,42 @@
/* LANGUAGE DROPDOWN */
function dropdown_show(obj)
{
var x = y = 0;
while(obj)
{
x += obj.offsetLeft;
y += obj.offsetTop;
obj = obj.offsetParent;
function dropdown_show(el) {
let x = 0;
let y = 0;
while (el) {
x += el.offsetLeft;
y += el.offsetTop;
el = el.offsetParent;
}
ddown = document.getElementById("lang-dropdown");
const ddown = document.getElementById("lang-dropdown");
ddown.style.display = "block";
ddown.style.left = (x - 72) + "px";
if (ddown.offsetLeft + ddown.offsetWidth +10 > document.body.offsetWidth)
{
ddown.style.left = document.body.offsetWidth - ddown.offsetWidth - 82 + "px";
ddown.style.left = (x - 72) + "px";
if (ddown.offsetLeft + ddown.offsetWidth + 10 > document.body.offsetWidth) {
ddown.style.left = (document.body.offsetWidth - ddown.offsetWidth - 82) + "px";
}
ddown.style.top = (y + 48) + "px";
op = 0;
appear(1);
fade_to(ddown, 1);
}
function dropdown_hide()
{
ddown = document.getElementById("lang-dropdown");
ddown.style.display="none";
}
function appear(x)
{
if(op < x)
{
op += 0.2;
ddown.style.opacity = op;
ddown.style.filter = 'alpha(opacity=' + op * 100 + ')';
t = setTimeout('appear(' + x + ')', 20);
function dropdown_hide(e) {
for (let t = e && e.target; t; t = t.parentElement) {
if (t.id === "lang-dropdown" || t.id === "lang-butt") return;
}
}
/* SCREENSHOTS GALLERY */
var FIRST_IMG_ID = 1;
var LAST_IMG_ID = 6;
var current = LAST_IMG_ID; // start with last slide so that next() shows the first
window.onload = function() {
// Dynamically create dots based on number of slides
var dots = document.getElementById("dots");
for (var i = FIRST_IMG_ID; i <= LAST_IMG_ID; i++) {
var dot = document.createElement("span");
dot.className = "dot" + (i === current ? " active" : "");
dot.setAttribute("data-slide", i);
dot.onclick = function() {
goToSlide(parseInt(this.getAttribute("data-slide")));
};
dots.appendChild(dot);
}
// If a carousel element exists, advance to the first slide on load
if (document.getElementById("carousel")) next();
};
function updateDots() {
var dots = document.querySelectorAll("#dots .dot");
dots.forEach(function(dot, index) {
// index starts at 0 so add FIRST_IMG_ID to match your slide IDs
dot.classList.toggle("active", (index + FIRST_IMG_ID) === current);
const ddown = document.getElementById("lang-dropdown");
fade_to(ddown, 0, function() {
ddown.style.display = "none";
});
}
function goToSlide(n) {
if (n === current) return;
document.getElementById("slide" + current).className = "minislide";
current = n;
document.getElementById("slide" + current).className = "visible";
if (document.getElementById("carousel")) {
document.getElementById("carousel").innerHTML = document.getElementById("slide" + current).alt;
function fade_to(el, target, done) {
const from = parseFloat(el.style.opacity) || 0;
const delta = target - from;
let start;
function step(ts) {
if (!start) start = ts;
const t = Math.min((ts - start) / 200, 1);
el.style.opacity = from + delta * t;
if (t < 1) requestAnimationFrame(step);
else if (done) done();
}
updateDots();
requestAnimationFrame(step);
}
function next() {
document.getElementById("slide" + current).className = "minislide";
if (current >= LAST_IMG_ID) {
current = FIRST_IMG_ID;
} else {
current++;
}
document.getElementById("slide" + current).className = "visible";
if (document.getElementById("carousel")) {
document.getElementById("carousel").innerHTML = document.getElementById("slide" + current).alt;
}
updateDots();
}
function previous() {
document.getElementById("slide" + current).className = "minislide";
if (current <= FIRST_IMG_ID) {
current = LAST_IMG_ID;
} else {
current--;
}
document.getElementById("slide" + current).className = "visible";
if (document.getElementById("carousel")) {
document.getElementById("carousel").innerHTML = document.getElementById("slide" + current).alt;
}
updateDots();
}
function checkkey(e) {
var keycode = window.event ? e.keyCode : e.which;
if (keycode == 37) { previous(); }
else if (keycode == 39) { next(); }
else if (keycode == 27) { dropdown_hide(); }
}
+61 -38
View File
@@ -375,26 +375,73 @@ acronym {
/* SCREENS.CSS */
#show {
max-width: 1280px;
max-height: 800px;
width: 100%;
height: 100%;
cursor: pointer;
border: 1px solid $c-screen-border;
display: block;
position: relative;
border-radius: 4px;
overflow: hidden;
.gallery {
display: flex;
flex-wrap: wrap;
justify-content: center;
img {
&-input {
display: none;
max-width: 100%;
pointer-events: none;
}
&.visible {
&-slide {
display: none;
margin: 0;
order: 1;
flex: 0 0 100%;
}
&-input:checked + &-slide {
display: block;
}
&-show {
max-width: 1280px;
width: 100%;
height: auto;
cursor: pointer;
border: 1px solid $c-screen-border;
display: block;
position: relative;
border-radius: 4px;
overflow: hidden;
img {
display: block;
max-width: 100%;
}
}
&-next {
position: absolute;
inset: 0;
cursor: pointer;
}
&-caption {
text-align: center;
margin: 1.25em auto 1em;
}
&-dot {
display: inline-block;
order: 2;
.dot {
display: inline-block;
width: 0.625em;
height: 0.625em;
background: $c-dot;
border-radius: 50%;
margin: 0 0.25em;
cursor: pointer;
}
}
&-input:checked + &-slide + &-dot .dot {
background: $c-text;
}
}
iframe {
@@ -404,30 +451,6 @@ iframe {
aspect-ratio: 16 / 9;
}
#carousel {
text-align: center;
margin: 1.25em auto 1em;
}
#dots {
text-align: center;
line-height: 1;
}
.dot {
display: inline-block;
width: 0.625em;
height: 0.625em;
background: $c-dot;
border-radius: 50%;
margin: 0 0.25em;
cursor: pointer;
&.active {
background: $c-text;
}
}
/* FOOTER */
#footer {
+35 -12
View File
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="{{ lang }}" onmouseup="dropdown_hide()">
<html lang="{{ lang }}" onmouseup="dropdown_hide(event)">
{% include 'tmpl/_header.htm' %}
@@ -115,20 +115,43 @@
<h1>{{ _('screenshots:header') }}</h1>
<div id="screen" onclick="next()">
<div id="show">
{% for i in range(1, 7) %}
<img
id="slide{{ i }}"
src="{{ url_for('static', filename='img/screenshots/%d.png' % i ) }}"
{% if i == 1 %}class="visible"{% endif %}
alt="{{ _('screenshots:%d' % i) }}"
{% if slides %}
<div id="screen" class="gallery">
{% for slide in slides %}
<input
type="radio"
name="slide"
id="slide-{{ loop.index0 }}"
class="gallery-input"
{% if loop.first %}checked{% endif %}
>
<figure class="gallery-slide">
<div class="gallery-show">
<img
src="{{ url_for('static', filename=slide.filename) }}"
alt="{{ _('screenshots:%s' % slide.key) }}"
>
{% set next_index = 0 if loop.last else loop.index0 + 1 %}
<label
class="gallery-next"
for="slide-{{ next_index }}"
aria-label="Next slide"
></label>
</div>
<figcaption class="gallery-caption">
{{ _('screenshots:%s' % slide.key) }}
</figcaption>
</figure>
<div class="gallery-dot">
<label
class="dot"
for="slide-{{ loop.index0 }}"
aria-label="{{ _('screenshots:%s' % slide.key) }}"
></label>
</div>
{% endfor %}
</div>
</div>
<div id="carousel"></div>
<div id="dots"></div>
{% endif %}
</div>
{% include 'tmpl/_footer.htm' %}
+2 -2
View File
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="{{ lang }}" onmouseup="dropdown_hide()">
<html lang="{{ lang }}" onmouseup="dropdown_hide(event)">
{% include 'tmpl/_header.htm' %}
@@ -20,4 +20,4 @@
{% include 'tmpl/_footer.htm' %}
</body>
</html>
</html>