Compare commits

14 Commits

Author SHA1 Message Date
Hazel Noack
2ded0c7768 feat: setup documentation wegsite 2025-05-16 16:58:25 +02:00
Hazel Noack
be51f463a1 feat: setup documentation wegsite 2025-05-16 16:58:20 +02:00
Hazel Noack
4b5701e05b feat: added favicon 2025-05-16 16:00:05 +02:00
Hazel Noack
8874fb0935 feat: added favicon 2025-05-16 15:59:43 +02:00
Hazel Noack
f02d37a6af added link context 2025-05-16 15:18:02 +02:00
Hazel Noack
f84fb65aa7 feat: fallback to overview page 2025-05-16 15:12:22 +02:00
Hazel Noack
d019884dbe implemented data structure for cross article context 2025-05-16 15:01:09 +02:00
Hazel Noack
39a8d7c1db added some simple documentation 2025-05-16 14:09:29 +02:00
7d1ceded8d meow 2025-04-17 17:42:50 +02:00
b3e23a53d9 feat: added children to context 2025-04-17 14:39:14 +02:00
b2513f7caf improved child cards from overview 2025-04-17 14:22:48 +02:00
4743456bd8 feat: improved translation cards 2025-04-17 14:08:26 +02:00
6994662bb4 feat: shift the headers in the preview 2025-04-17 14:05:59 +02:00
db23ceac78 feat: removed unnecessary code 2025-04-17 13:54:37 +02:00
23 changed files with 300 additions and 326 deletions

2
.gitignore vendored
View File

@@ -174,4 +174,4 @@ cython_debug/
.pypirc .pypirc
dist dist
context.json *.json

View File

@@ -0,0 +1,36 @@
# Installing
```sh
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
```
# Execute
To start a local http server in the dist folder you can simply do:
```sh
python3 -m http.server 1312
```
Then visit `localhost:1312`
```
# build it normally
stsg
# start a hot reload server
stsg_dev
```
# build favicon
```
cd src/static/assets
inkscape -w 16 -h 16 -o 16.png logo.svg
inkscape -w 32 -h 32 -o 32.png logo.svg
inkscape -w 48 -h 48 -o 48.png logo.svg
convert 16.png 32.png 48.png ../icon.ico
```

9
src/articles/de.md Normal file
View File

@@ -0,0 +1,9 @@
# STSG
![stsg logo](/static/assets/logo.png)
Dies ist ein Static Site Generator mit fokus auf Nutzer:innenfreundlichkeit und Barrierearmut der generierten Seite. So kann man die Artikel in alle relevanten Sprachen übersetzen. Diese Dokumentation ist ebenfalls mit STSG gemacht.
Die Templates sind komplet anpassbar, trotzdem empfehle ich [Bulma](https://bulma.io/) als CSS-Framework zu verwenden.
Polizei und das Millitär jeglichen Staates dürfen dieses Tool nicht verwenden.

View File

@@ -0,0 +1,3 @@
# Dokumentation
Dies ist die Dokumentation von STSG.

View File

@@ -0,0 +1,3 @@
# documentation
This is the documentation for stsg.

View File

@@ -1,75 +1,9 @@
# Populumque avidosque Sparte quoque auctore equidem # STSG
## Sunt aevis ![](/static/assets/logo.png)
Lorem markdownum turbavere prisca Aeacidae morando esse. Quam Styga spectata, This is a static-site-generator with focus on ease of use and making accessible websites. Thus the articles can be created in all relevant languages.
pariter Iove iunctis exercere Solis? Atlantis possit succurrere quam!
if (stationRecord < ctp(rup, columnBase, dtd)) { The templates are completely customizable, but I still reccomend using [bulma](https://bulma.io/) as css-framework.
impact_qbe_symbolic(bank_c(exploit_gnutella_social), inbox);
marketing = telnetWebmasterFpu;
circuitSoapDns(dac, ieee);
} else {
ebook.spider(wildcard_publishing_memory.tcpDisk(encoding, 48149), -1 +
copyright_flash_icmp, superscalar_cluster + kofficeIsp);
reimage.mac = dslWebmail;
kilobyteVariable.margin_keylogger = dvd + microcomputer;
}
font_switch(servlet, file(1, protocol) * skinTouchscreen,
wheel_station_computer);
var startRom = bit(php_touchscreen_icio);
var unicode_hover_tiger = command(scan_install_cd(ruby(mail_chipset, web,
clientMemeSoftware), optical, wirelessMegahertz), oasis);
## Est nec locumque anxia et Cops and Millitary of any state are stricly prohibited from using this tool.
Maligno puppes potuit petit. **Ipse regnat venit** tangit mitti opibus est unus
spectacula erat! Bacche qui dedit in ardet Phrygiis Liternum ipso ille Orphei
Canentem *ut*, parenti terrae! Frondibus deus sine leoni frustraque lentus. Est
deos cum corripuit erat sibi concussit simul; suus tantum.
Camenis Lucifer ex geniti sitis quem. Styga si Ceae nova media remugis: haerens
ridet, nam [Pindo](http://www.murra.com/argolicaemaximus) est tritis flamma
[dixit una licebit](http://www.sedare-mopsus.net/) Pelia perdite! Aura *aurea
mecum* una mirabantur mansit domum simul de Euboica altis vincula tenentis vires
sub, *Scythicae*. Mora sitis pocula. Ultimus idem triplices inquit.
## Et atque ministris imagine fas tenuit fornacibus
Adeunda suffundit ille: Bacchi moribundo et quam **cacumina videre Tamasenum**.
Gauderet in [non arbitrium caelo](http://www.madidusperiuraque.io/).
## In corpora in micat Phoebus corque transitus
Mihi in macies, ab avoque malorum decusque. Appellant expellam unus colore
exiguo, maior ara loqui sit vires.
var uat = cardPrinterLocalhost.pppoe(display, operating_row_fsb(
scalable_lion, compilerHeuristicTweet - 22));
var toslink_software = tokenPciPrompt.source_x_firmware(drive,
boxSdkDlc.servicesIcsDrive(certificate_cycle_illegal(resolution)),
outboxTftMap + rssTextZebibyte.flashImpactDisk(3, eup_ad));
var compression = programWebPort;
if (intellectual_left_system) {
favorites(lossyDramDay + 7, upsSliTruncate, 2);
laptop.android = ocr_piracy(clean, flatbedRte - -1);
directx_file_cable = -1;
} else {
point_sink_controller(flash(filenameDataAccess, vleSoftwareListserv),
point_rate_cmos(control_soa_restore));
javascript *= nic_format;
address = nameDcim(touchscreenLink.port_port(diskHeat, vaporware));
}
## Enim nunc solvi
Est pudet citharae, corpus? Modo in armentaque pennisque videri aquarum, est
equos ne in vulnere domus; maris. Quodque quoque orbe omnes metus, sol
[putas](http://www.uno.com/deorumvolumine) marmore fuit secreta haut vobis,
faciendo, oro.
Quid sequenti, supersunt quoque, sortite; in in tenetur vecta horriferamque
amabat. Vos nudae anni amor chelydri [Picus candentibus
et](http://pugnare.io/angulus.html) sonum, et sentibus geminato volucrem
mercibus bracchia, cum *posito* delubraque. Templa extrahit in totidem altera
Nioben, honoris sui, fibris!

View File

@@ -1,57 +0,0 @@
meow
# Abstrahere reddita celebrare in ossa
## Usque de celebrabant puer
Lorem markdownum, nec ora et vero me nec natae suadent. Nec damno ignorat
propiore aliquid temptata decipienda habetur. Vulnera lacrimis aequoreo madidos,
copia uvae, herbosaque quoque, per harenas, canos fui monstro Peleus.
Et fuge cum liquidum puer Herculis arentis, tantum caudaque et generi vilior, in
rubore. Caeli modo palmis, suo tria accipe visus non similis qui remittat
retentus porrigit fluxit ubi testis.
> Sulphura et color reliquit dextera: quid summa continuere obductos egesto
> moriens **fluentia vult iunctamque**, mihi patres spiro. Est Iovis **imperat
> quem est** putavi annis *omnis*; flumina, leporem constabat grave pelagi,
> insiluit, igne invito? Auctor circuit quod.
## Rhoetus gravis
Parari modo in sustulerat: ora sic verba meruit, uti. Pignora citus facto
amplectimur cupido amentes foedantem multoque datura.
[Quid](http://www.prima.net/se) ex tanti armaque exhibita descenderat!
1. Alis ima
2. Mirantur nive sit
3. Maris ut adlabimur humana fine quam vultusque
## A idonea miserum
Montes tibi deorum igitur. Poterat nunc porrigitur perdidimus *sidereos animi
praesepia* nihil praeferri functa, in **Pleias removete** oculos sollemni
Tatius: **modo**. Inde dedit! Atque in matrem spinae foret ponti quam dixit,
aras. Gladii addendum fiducia magno, se quo fata humo esse **tellure corpora
discederet** sucis manibus, parentem ante, Iovem.
Laqueique honore sequentia tyranni Harpalos, paelex, foedat tempestiva nomen.
Sit ter indiciumque requirit utrumque in nil et *suspectus*, quaerite patriam
nec facta [securi](http://heuqua.io/adhaut). Confessa ut per sit nostro futura
metaque oblitae fameque exit adspiciens. Morte flectere invidiam certe cum
vixque *nubes clamor viderunt* praeceps infamis collo percussis axem plena saxum
urbes ferre undae. Totoque utuntur ore lupus inplet sibila ullam, qui corpore
*contermina aera*?
Convocat ipse abeunt sententia concolor a Auguste epops solent iubet qui
aequora. In illi, solvente resecare violentam nescio accipit multarum [aureus
exspectatus](http://oris-tibi.io/nasci) ergo ora. Positis **inquit**, sit iamque
hederae ulterius, pontus linguae matutina terra sic isse Graium passosque
sanguinis secus petit!
Adfore dei volucrum Lydas; [hoc](http://similemnescitve.net/ipsamquee) quam
[superosque](http://www.quoqueabit.com/cumqueiam.html) caligine vulnera quoque
corpus foedaque mentis qui nectare, fatendo sensit! Rursus nulli miraris nuda
*Acrisio*. Cum modo, satis dissuadet luce; cum *freta* ab et diu, labor *tenuata
ieiunia*? Caesosque thalamique precor, dedit nulla loca [arce
et](http://viscera-rerum.com/mixta) tinguit aera.

View File

@@ -1,75 +0,0 @@
# Populumque avidosque Sparte quoque auctore equidem
## Sunt aevis
Lorem markdownum turbavere prisca Aeacidae morando esse. Quam Styga spectata,
pariter Iove iunctis exercere Solis? Atlantis possit succurrere quam!
if (stationRecord < ctp(rup, columnBase, dtd)) {
impact_qbe_symbolic(bank_c(exploit_gnutella_social), inbox);
marketing = telnetWebmasterFpu;
circuitSoapDns(dac, ieee);
} else {
ebook.spider(wildcard_publishing_memory.tcpDisk(encoding, 48149), -1 +
copyright_flash_icmp, superscalar_cluster + kofficeIsp);
reimage.mac = dslWebmail;
kilobyteVariable.margin_keylogger = dvd + microcomputer;
}
font_switch(servlet, file(1, protocol) * skinTouchscreen,
wheel_station_computer);
var startRom = bit(php_touchscreen_icio);
var unicode_hover_tiger = command(scan_install_cd(ruby(mail_chipset, web,
clientMemeSoftware), optical, wirelessMegahertz), oasis);
## Est nec locumque anxia et
Maligno puppes potuit petit. **Ipse regnat venit** tangit mitti opibus est unus
spectacula erat! Bacche qui dedit in ardet Phrygiis Liternum ipso ille Orphei
Canentem *ut*, parenti terrae! Frondibus deus sine leoni frustraque lentus. Est
deos cum corripuit erat sibi concussit simul; suus tantum.
Camenis Lucifer ex geniti sitis quem. Styga si Ceae nova media remugis: haerens
ridet, nam [Pindo](http://www.murra.com/argolicaemaximus) est tritis flamma
[dixit una licebit](http://www.sedare-mopsus.net/) Pelia perdite! Aura *aurea
mecum* una mirabantur mansit domum simul de Euboica altis vincula tenentis vires
sub, *Scythicae*. Mora sitis pocula. Ultimus idem triplices inquit.
## Et atque ministris imagine fas tenuit fornacibus
Adeunda suffundit ille: Bacchi moribundo et quam **cacumina videre Tamasenum**.
Gauderet in [non arbitrium caelo](http://www.madidusperiuraque.io/).
## In corpora in micat Phoebus corque transitus
Mihi in macies, ab avoque malorum decusque. Appellant expellam unus colore
exiguo, maior ara loqui sit vires.
var uat = cardPrinterLocalhost.pppoe(display, operating_row_fsb(
scalable_lion, compilerHeuristicTweet - 22));
var toslink_software = tokenPciPrompt.source_x_firmware(drive,
boxSdkDlc.servicesIcsDrive(certificate_cycle_illegal(resolution)),
outboxTftMap + rssTextZebibyte.flashImpactDisk(3, eup_ad));
var compression = programWebPort;
if (intellectual_left_system) {
favorites(lossyDramDay + 7, upsSliTruncate, 2);
laptop.android = ocr_piracy(clean, flatbedRte - -1);
directx_file_cable = -1;
} else {
point_sink_controller(flash(filenameDataAccess, vleSoftwareListserv),
point_rate_cmos(control_soa_restore));
javascript *= nic_format;
address = nameDcim(touchscreenLink.port_port(diskHeat, vaporware));
}
## Enim nunc solvi
Est pudet citharae, corpus? Modo in armentaque pennisque videri aquarum, est
equos ne in vulnere domus; maris. Quodque quoque orbe omnes metus, sol
[putas](http://www.uno.com/deorumvolumine) marmore fuit secreta haut vobis,
faciendo, oro.
Quid sequenti, supersunt quoque, sortite; in in tenetur vecta horriferamque
amabat. Vos nudae anni amor chelydri [Picus candentibus
et](http://pugnare.io/angulus.html) sonum, et sentibus geminato volucrem
mercibus bracchia, cum *posito* delubraque. Templa extrahit in totidem altera
Nioben, honoris sui, fibris!

View File

@@ -1,35 +0,0 @@
# Navê Gotarê example
Ev gotar, mînakek placeholder ya bi zimanê Kurdî ye. Nivîs, weşan û rûpelên nûçe di vê belgeyê de têne pêşniyar kirin. Ev metn ji bo testkirina layout an jî demo-yê hatiye çêkirin û çend bingehên cihanî yên naveroka gotarê nîşan dide.
## Destpêk
Di destpêka gotarê de, dikarin rêvekên bingehîn û armancên mijarê vebêjin. Tu dikarî vê metnê bikar bînin ji bo şexsî an jî projeyên xwe:
Lorem ipsum ji bo kurdî:
"Rojên nû û şevên hêja, ev nivîs têne çêkirin ku bi awayek nîşanbideke cîhan û hunermendî were afirandin."
## Lêkolîn û Naverok
Di vê beşê de hinek îzahiyên naverokî yên placeholder hatine peyda kirin. Hûn dikarin bi rêza li jêr anînên mifteyî yên gotarê şop bikin:
- **Mijar:** Di vê gotarê de, mijarên bi zor û hêja têne nîşandan.
- **Rêbaz:** Ew kesên ku ji ber vê gotarê şîrove û daxuyanî dikin, çavkaniyên xwe diguhezînin.
- **Agahî:** Di vê derbarê de, agahîyên cihanî, dîtin û têgihiştin hatine berhev kirin.
Di bin vê şexsiyeta, hertişt di dema xwe de têgihiştin û anîn pêşiyê dayîn.
## Şirove û Kod
Di vê paragrafa kêm-kêm de, em dikarin bingehên şiroveyê yên gotarê di kodê de jî şop bikin:
```python
def peyama_kurdî():
mesaj = "Her bijî Kurdistan! Rojên baş û serkeftin!"
return mesaj
print(peyama_kurdî())
```
Kodê li ser vê koda simplesa gotinê ye û dikare weşana nûçe an demo-yê projeyan bixebite.
# Encama Gotarê
Di encama gotarê de, hûn dikarin her çend beşên bingehîn yên nûçe, daxuyanî û şirove yên navekî bifikirin.
Bê guman, ev placeholder bi awayek qelew li ser çalakiya te ya malper, blog an jî her sedema dijîtal tê de karîger e.

View File

@@ -1,2 +1,2 @@
name="index_page" name="stsg"
datetime="2024-04-15 13:45:12.123456" datetime="2024-04-15 13:45:12.123456"

View File

@@ -1,38 +0,0 @@
# Navê Gotarê
[{article_title:example}]({article_url:example})
Ev gotar, mînakek placeholder ya bi zimanê Kurdî ye. Nivîs, weşan û rûpelên nûçe di vê belgeyê de têne pêşniyar kirin. Ev metn ji bo testkirina layout an jî demo-yê hatiye çêkirin û çend bingehên cihanî yên naveroka gotarê nîşan dide.
## Destpêk
Di destpêka gotarê de, dikarin rêvekên bingehîn û armancên mijarê vebêjin. Tu dikarî vê metnê bikar bînin ji bo şexsî an jî projeyên xwe:
Lorem ipsum ji bo kurdî:
"Rojên nû û şevên hêja, ev nivîs têne çêkirin ku bi awayek nîşanbideke cîhan û hunermendî were afirandin."
## Lêkolîn û Naverok
Di vê beşê de hinek îzahiyên naverokî yên placeholder hatine peyda kirin. Hûn dikarin bi rêza li jêr anînên mifteyî yên gotarê şop bikin:
- **Mijar:** Di vê gotarê de, mijarên bi zor û hêja têne nîşandan.
- **Rêbaz:** Ew kesên ku ji ber vê gotarê şîrove û daxuyanî dikin, çavkaniyên xwe diguhezînin.
- **Agahî:** Di vê derbarê de, agahîyên cihanî, dîtin û têgihiştin hatine berhev kirin.
Di bin vê şexsiyeta, hertişt di dema xwe de têgihiştin û anîn pêşiyê dayîn.
## Şirove û Kod
Di vê paragrafa kêm-kêm de, em dikarin bingehên şiroveyê yên gotarê di kodê de jî şop bikin:
```python
def peyama_kurdî():
mesaj = "Her bijî Kurdistan! Rojên baş û serkeftin!"
return mesaj
print(peyama_kurdî())
```
Kodê li ser vê koda simplesa gotinê ye û dikare weşana nûçe an demo-yê projeyan bixebite.
# Encama Gotarê
Di encama gotarê de, hûn dikarin her çend beşên bingehîn yên nûçe, daxuyanî û şirove yên navekî bifikirin.
Bê guman, ev placeholder bi awayek qelew li ser çalakiya te ya malper, blog an jî her sedema dijîtal tê de karîger e.

BIN
src/static/assets/16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

BIN
src/static/assets/32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
src/static/assets/48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
src/static/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="210mm"
height="210mm"
viewBox="0 0 210 210"
version="1.1"
id="svg1"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
sodipodi:docname="logo.svg"
xml:space="preserve"
inkscape:export-filename="logo.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.84452862"
inkscape:cx="396.67099"
inkscape:cy="391.34257"
inkscape:window-width="1672"
inkscape:window-height="957"
inkscape:window-x="816"
inkscape:window-y="1259"
inkscape:window-maximized="0"
inkscape:current-layer="svg1"
showgrid="true"><inkscape:grid
id="grid1"
units="mm"
originx="0"
originy="0"
spacingx="105"
spacingy="105"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="5"
enabled="true"
visible="true" /></sodipodi:namedview><defs
id="defs1"><rect
x="396.8504"
y="396.8504"
width="396.8504"
height="396.8504"
id="rect4" /><rect
x="396.8504"
y="0"
width="396.8504"
height="396.8504"
id="rect3" /><rect
x="0"
y="0"
width="396.8504"
height="396.8504"
id="rect2" /><rect
x="0"
y="0"
width="793.70079"
height="793.70079"
id="rect1" /><rect
x="0"
y="0"
width="396.8504"
height="396.8504"
id="rect2-4" /></defs><circle
style="fill:#ff7575;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.99999;stroke-dasharray:none;stroke-opacity:1"
id="path5"
cx="105"
cy="105"
r="105" /><g
style="fill:#3eebff;stroke:none;stroke-opacity:1;fill-opacity:1"
id="g5"
transform="matrix(0.96400101,0,0,0.96400101,0,-0.83284847)"><path
d="M 109.786,0 C 49.25,0 0,49.25 0,109.785 c 0,60.536 49.25,109.786 109.786,109.786 60.536,0 109.785,-49.25 109.785,-109.786 C 219.571,49.25 170.322,0 109.786,0 Z m 94.488,102.285 h -35.978 c -1.143,-33.154 -9.811,-61.858 -22.93,-80.348 32.479,13.202 56.043,43.909 58.908,80.348 z M 102.286,16.584 v 85.702 H 66.293 C 67.946,56.265 84.634,23.895 102.286,16.584 Z m 0,100.701 v 85.703 C 84.634,195.676 67.946,163.306 66.293,117.285 Z m 15,85.703 v -85.703 h 35.993 c -1.653,46.021 -18.341,78.391 -35.993,85.703 z m 0,-100.703 V 16.584 c 17.651,7.312 34.34,39.682 35.993,85.702 H 117.286 Z M 74.206,21.937 C 61.087,40.428 52.418,69.131 51.276,102.286 h -35.98 c 2.867,-36.44 26.431,-67.147 58.91,-80.349 z m -58.91,95.348 h 35.98 c 1.142,33.155 9.811,61.859 22.93,80.35 -32.479,-13.203 -56.043,-43.91 -58.91,-80.35 z m 130.07,80.349 c 13.119,-18.49 21.787,-47.194 22.93,-80.349 h 35.978 c -2.865,36.44 -26.429,67.147 -58.908,80.349 z"
id="path1"
style="stroke:none;stroke-opacity:1;fill:#3eebff;fill-opacity:1" /></g></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
src/static/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

13
src/static/style.css Normal file
View File

@@ -0,0 +1,13 @@
img {
float: right;
margin: 1em;
max-width: 20%;
height: auto;
}
/* Responsive: remove float on small screens */
@media screen and (max-width: 768px) {
img {
max-width: 50%;
}
}

View File

@@ -3,11 +3,12 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="/static/icon.ico">
<title>{{slug}}</title> <title>{{slug}}</title>
<link rel="stylesheet" href="/static/bulma.min.css" /> <link rel="stylesheet" href="/static/bulma.min.css" />
<link rel="stylesheet" href="/static/style.css" />
</head> </head>
<body> <body>
<!-- Header (Navbar) -->
<nav <nav
class="navbar is-primary" class="navbar is-primary"
role="navigation" role="navigation"
@@ -23,23 +24,28 @@
<section class="section"> <section class="section">
{% if translations|length %} {% if translations|length %}
<div class="container content"> <div class="container content">
<div class="column is-half is-offset-one-quarter"> <div class="content">
<h1>Translations</h1> <h1>Translations</h1>
</div> </div>
<div class="column is-half is-offset-one-quarter"> <div class="columns is-multiline">
{% for t in translations %} {% for t in translations %}
<div class="card mb-4" lang="{{t.language.code}}"> <div class="column is-half">
<div class="card mb-4" lang="{{t.language.code}}" style="height: 100%;">
<a href="{{t.url}}" hreflang="{{t.language.code}}" class="card mb-4" style="color: inherit; text-decoration: none;"> <a href="{{t.url}}" hreflang="{{t.language.code}}" class="card mb-4" style="color: inherit; text-decoration: none;">
<div class="card-content"> <div class="card-content">
<p class="title">{{t.language.flag}} {{t.title}} </p> <p class="title">{{t.language.flag}} {{t.title}}</p>
<hr />
<p class="content"> <p class="content">
{{t.preview}} {{t.preview}}
<br />
<time datetime="{{iso_date}}">{{date}}</time>
</p> </p>
</div> </div>
<div class="card-footer">
<time class="card-footer-item" datetime="{{iso_date}}">{{date}}</time>
</div>
</a> </a>
</div> </div>
</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@@ -49,7 +55,7 @@
{% if children|length %} {% if children|length %}
<div class="container content"> <div class="container content">
<div class="column is-half is-offset-one-quarter"> <div class="column is-half is-offset-one-quarter">
<h1>Child Articles</h1> <h1>Related Articles</h1>
</div> </div>
<div class="column is-half is-offset-one-quarter"> <div class="column is-half is-offset-one-quarter">
{% for c in children %} {% for c in children %}
@@ -57,9 +63,14 @@
<a href="{{c.url}}" class="card mb-4" style="color: inherit; text-decoration: none;"> <a href="{{c.url}}" class="card mb-4" style="color: inherit; text-decoration: none;">
<div class="card-content"> <div class="card-content">
<p class="title">{{c.slug}} </p> <p class="title">{{c.slug}} </p>
<p class="content"> <hr />
<time datetime="{{iso_date}}">{{date}}</time> <p class="content is-flex is-flex-direction-column" style="gap: 10px;">
{% for ct in c.translations %}
<a href="{{ct.url}}" hreflang="{{ct.language.code}}">{{ct.language.flag}}: {{ct.title}}</a>
{% endfor %}
</p> </p>
<hr />
<time datetime="{{iso_date}}">{{date}}</time>
</div> </div>
</a> </a>
</div> </div>

View File

@@ -3,8 +3,10 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{language.flag}} {{title}}</title> <link rel="icon" type="image/x-icon" href="/static/icon.ico">
<title>{{title}}</title>
<link rel="stylesheet" href="/static/bulma.min.css" /> <link rel="stylesheet" href="/static/bulma.min.css" />
<link rel="stylesheet" href="/static/style.css" />
</head> </head>
<body> <body>
<!-- Header (Navbar) --> <!-- Header (Navbar) -->
@@ -28,6 +30,34 @@
</div> </div>
</section> </section>
{% if children|length %}
<section class="section">
<div class="content">
<div class="columns is-multiline">
{% for c in children %}
<div class="column is-half">
<div class="card mb-4" >
<a href="{{c.url}}" hreflang="{{c.language.code}}" class="card mb-4" style="color: inherit; text-decoration: none;">
<div class="card-content">
<p class="title">{{c.title}}</p>
<hr />
<p class="content">
{{c.preview}}
</p>
</div>
<div class="card-footer">
<time class="card-footer-item" datetime="{{c.meta.iso_date}}">{{c.meta.date}}</time>
</div>
</a>
</div>
</div>
{% endfor %}
</div>
</div>
</section>
{% endif %}
<!-- Footer --> <!-- Footer -->
<footer class="footer"> <footer class="footer">
<div class="content has-text-centered"> <div class="content has-text-centered">

View File

@@ -3,7 +3,8 @@ source_directory = "src"
dist_directory = "dist" dist_directory = "dist"
[formatting] [formatting]
article_preview_length = 400 preview_length = 400
preview_header_shift = 2
datetime_format = "%d. %B %Y" datetime_format = "%d. %B %Y"
default_language = "de" default_language = "de"

View File

@@ -4,9 +4,10 @@ class config:
dist_directory = "dist" dist_directory = "dist"
class formatting: class formatting:
article_preview_length = 200
datetime_format = "%d. %B %Y" datetime_format = "%d. %B %Y"
fallback_language = "en" fallback_language = "en"
preview_length = 400
preview_header_shift = 2
languages = { languages = {
"af": { "af": {

View File

@@ -4,7 +4,7 @@ import shutil
from pathlib import Path from pathlib import Path
import os import os
import markdown import markdown
from typing import Optional, Union, Dict, Generator, List, DefaultDict, Any from typing import Optional, Union, Dict, Generator, List, DefaultDict, Any, TypedDict, Set
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from collections import defaultdict from collections import defaultdict
import toml import toml
@@ -13,14 +13,6 @@ import jinja2
from . import config from . import config
def replace_values(template: str, values: Dict[str, str]) -> str:
for key, value in values.items():
template = template.replace("{" + key + "}", value)
return template
def get_first_header_content(content, fallback: str = ""): def get_first_header_content(content, fallback: str = ""):
soup = BeautifulSoup(content, 'html.parser') soup = BeautifulSoup(content, 'html.parser')
for level in range(1, 7): for level in range(1, 7):
@@ -31,7 +23,7 @@ def get_first_header_content(content, fallback: str = ""):
return fallback return fallback
def shorten_text_and_clean(html_string, max_length=config.formatting.article_preview_length): def shorten_text_and_clean(html_string, max_length=config.formatting.preview_length):
soup = BeautifulSoup(html_string, 'html.parser') soup = BeautifulSoup(html_string, 'html.parser')
# Keep track of total characters added # Keep track of total characters added
@@ -69,6 +61,23 @@ def shorten_text_and_clean(html_string, max_length=config.formatting.article_pre
return str(soup) return str(soup)
def shift_headings(html_string, header_shift=config.formatting.preview_header_shift):
soup = BeautifulSoup(html_string, 'html.parser')
for level in range(6, 0, -1): # Start from h6 to h1 to avoid overwriting
old_tag = f'h{level}'
for tag in soup.find_all(old_tag):
new_level = min(level + header_shift, 6) # Cap at h6
new_tag = f'h{new_level}'
tag.name = new_tag
return str(soup)
def get_preview_text(html_string: str):
return shift_headings(shorten_text_and_clean(html_string))
def stem_to_language_code(stem: str) -> str: def stem_to_language_code(stem: str) -> str:
language_code = stem.lower().replace("-", "_") language_code = stem.lower().replace("-", "_")
@@ -83,7 +92,6 @@ def stem_to_language_code(stem: str) -> str:
exit(1) exit(1)
class TemplateDict(dict): class TemplateDict(dict):
def __init__(self, folder: Path): def __init__(self, folder: Path):
self.folder = folder self.folder = folder
@@ -99,8 +107,10 @@ class TemplateDict(dict):
self[name] = t self[name] = t
return t return t
TEMPLATE: Dict[str, jinja2.Template] = TemplateDict(Path(config.setup.source_directory, "templates")) TEMPLATE: Dict[str, jinja2.Template] = TemplateDict(Path(config.setup.source_directory, "templates"))
class LanguageDict(dict): class LanguageDict(dict):
def __missing__(self, key: str): def __missing__(self, key: str):
if key not in config.languages: if key not in config.languages:
@@ -116,9 +126,17 @@ class LanguageDict(dict):
return lang_dict return lang_dict
LANGUAGES = LanguageDict() LANGUAGES = LanguageDict()
def compile_cross_article_context(cross_article_context):
title = cross_article_context["title"]
url = cross_article_context["url"]
cross_article_context["link"] = f'<a href="{url}">{title}</a>'
class ArticleTranslation: class ArticleTranslation:
def __init__(self, file: Path, article: Article): def __init__(self, file: Path, article: Article):
self.file = file self.file = file
@@ -131,32 +149,44 @@ class ArticleTranslation:
self.location_in_tree = [self.language_code, *self.article.location_in_tree] self.location_in_tree = [self.language_code, *self.article.location_in_tree]
self.url = "/" + "/".join(self.location_in_tree) self.url = "/" + "/".join(self.location_in_tree)
self.dist_path = Path(config.setup.dist_directory, *self.location_in_tree) self.dist_path = Path(config.setup.dist_directory, *self.location_in_tree)
self.cross_article_context = TRANSLATED_CROSS_ARTICLE_CONTEXT[self.language_code][self.article.slug] = {}
self.priority = LANGUAGES[self.language_code]["priority"] self.priority = LANGUAGES[self.language_code]["priority"]
self.real_language_code = LANGUAGES[self.language_code]["code"] self.real_language_code = LANGUAGES[self.language_code]["code"]
# TODO remove self.html_content = self.file.read_text()
self.article_content = self.file.read_text()
self.article_preview = self.article_content[:config.formatting.article_preview_length] + "..."
if self.file.suffix == ".md": if self.file.suffix == ".md":
self.article_content = markdown.markdown(self.article_content) self.html_content = markdown.markdown(self.html_content)
self.article_preview = markdown.markdown(self.article_preview)
self.title = get_first_header_content(self.article_content, fallback="")
def __init_context__(self): def __init_context__(self):
self.context["meta"] = self.article.context_shared self.context["meta"] = self.article.context_shared
self.context["url"] = self.url self.context["url"] = self.url
self.context["language"] = LANGUAGES[self.language_code] self.context["language"] = LANGUAGES[self.language_code]
self.context["article_url"] = self.article.url self.context["article_url"] = self.article.url
self.context["title"] = get_first_header_content(self.html_content, fallback=LANGUAGES[self.language_code]["native_name"])
html_content = self.file.read_text() self.cross_article_context.update(self.article.context_shared)
if self.file.suffix == ".md": self.cross_article_context["title"] = self.context["title"]
html_content = markdown.markdown(html_content) self.cross_article_context["article_url"] = self.article.url
self.cross_article_context["url"] = self.url
compile_cross_article_context(self.cross_article_context)
self.context["title"] = get_first_header_content(html_content, fallback=LANGUAGES[self.language_code]["native_name"]) # get children
self.context["content"] = html_content self.context["children"] = [
self.context["preview"] = shorten_text_and_clean(html_string=html_content) c.article_translations_map[self.language_code].context for c in self.article.child_articles
if self.language_code in c.article_translations_map
]
def __init_content_context__(self):
template = jinja2.Template(self.html_content)
self.html_content = template.render({
**CROSS_ARTICLE_CONTEXT,
**TRANSLATED_CROSS_ARTICLE_CONTEXT[self.language_code],
})
self.context["content"] = self.html_content
self.context["preview"] = get_preview_text(html_string=self.html_content)
def build(self): def build(self):
self.dist_path.mkdir(parents=True, exist_ok=True) self.dist_path.mkdir(parents=True, exist_ok=True)
@@ -183,6 +213,7 @@ class Article:
if self.slug in ARTICLE_LAKE: if self.slug in ARTICLE_LAKE:
logger.error("two articles have the same name at %s and %r", ARTICLE_LAKE[self.slug].directory, self.directory) logger.error("two articles have the same name at %s and %r", ARTICLE_LAKE[self.slug].directory, self.directory)
exit(1) exit(1)
self.cross_article_context = CROSS_ARTICLE_CONTEXT[self.slug] = {}
ARTICLE_LAKE[self.slug] = self ARTICLE_LAKE[self.slug] = self
self.location_in_tree: List[str] = location_in_tree or [] self.location_in_tree: List[str] = location_in_tree or []
@@ -225,6 +256,11 @@ class Article:
self.context.update(self.context_shared) self.context.update(self.context_shared)
self.cross_article_context.update(self.context_shared)
self.cross_article_context["title"] = self.context_shared["slug"]
self.cross_article_context["article_url"] = self.context_shared["url"]
compile_cross_article_context(self.cross_article_context)
# recursive context structures # recursive context structures
translation_list = self.context["translations"] = [] translation_list = self.context["translations"] = []
child_article_list = self.context["children"] = [] child_article_list = self.context["children"] = []
@@ -242,6 +278,12 @@ class Article:
for a in self.child_articles: for a in self.child_articles:
a.__init_context__() a.__init_context__()
def __init_content_context__(self):
for at in self.article_translations_list:
at.__init_content_context__()
for a in self.child_articles:
a.__init_content_context__()
def build(self): def build(self):
self.dist_path.mkdir(parents=True, exist_ok=True) self.dist_path.mkdir(parents=True, exist_ok=True)
@@ -257,9 +299,12 @@ class Article:
# GLOBALS # GLOBALS
logger = logging.getLogger("stsg.build") logger = logging.getLogger("stsg.build")
CROSS_ARTICLE_CONTEXT: Dict[str, Dict[str, Any]] = {}
TRANSLATED_CROSS_ARTICLE_CONTEXT: Dict[str, Dict[str, Dict[str, Any]]] = defaultdict(dict)
ARTICLE_LAKE: Dict[str, Article] = {} ARTICLE_LAKE: Dict[str, Article] = {}
ARTICLE_REFERENCE_VALUES: DefaultDict[str, Dict[str, str]] = defaultdict(dict) ARTICLE_REFERENCE_VALUES: DefaultDict[str, Dict[str, str]] = defaultdict(dict)
def build(): def build():
logger.info("starting build process...") logger.info("starting build process...")
@@ -271,10 +316,15 @@ def build():
logger.info("compiling tree context...") logger.info("compiling tree context...")
tree.__init_context__() tree.__init_context__()
tree.__init_content_context__()
import json import json
with Path("context.json").open("w") as f: with Path("context.json").open("w") as f:
json.dump(tree.context, f, indent=4) json.dump(tree.context, f, indent=4)
with Path("cross_article_context.json").open("w") as f:
json.dump(CROSS_ARTICLE_CONTEXT, f, indent=4)
with Path("t_cross_article_context.json").open("w") as f:
json.dump(TRANSLATED_CROSS_ARTICLE_CONTEXT, f, indent=4)
logger.info("dumping page tree...") logger.info("dumping page tree...")
tree.build() tree.build()