Compare commits
41 Commits
feature/is
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
a4aa73b1aa | ||
|
73e89ca513 | ||
|
a1900a77e4 | ||
|
258d062ff4 | ||
|
8fee8d879e | ||
|
373cade0a7 | ||
|
5196913266 | ||
|
d3ff002901 | ||
|
08e7e343f5 | ||
|
433e2f6023 | ||
|
2690f0af87 | ||
|
f2a3d7ada4 | ||
|
753de66e08 | ||
|
036d5fb30a | ||
|
632f47e017 | ||
|
2a7ebaa298 | ||
|
d43d6505b1 | ||
|
e7558d996b | ||
|
15707ada59 | ||
|
54d1f0292e | ||
|
c33307e714 | ||
|
e3fe49bed7 | ||
|
693022ac23 | ||
|
9630eb07a1 | ||
|
a01f94379d | ||
|
6093e8df32 | ||
|
e82d841588 | ||
|
2ded0c7768 | ||
|
be51f463a1 | ||
|
4b5701e05b | ||
|
8874fb0935 | ||
|
f02d37a6af | ||
|
f84fb65aa7 | ||
|
d019884dbe | ||
|
39a8d7c1db | ||
7d1ceded8d | |||
b3e23a53d9 | |||
b2513f7caf | |||
4743456bd8 | |||
6994662bb4 | |||
db23ceac78 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -174,4 +174,4 @@ cython_debug/
|
|||||||
.pypirc
|
.pypirc
|
||||||
|
|
||||||
dist
|
dist
|
||||||
context.json
|
*.json
|
||||||
|
57
LICENSE
Normal file
57
LICENSE
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# 🏳️🌈 Opinionated Queer License v1.2
|
||||||
|
|
||||||
|
© Copyright {Licensor}
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
|
||||||
|
The creators of this Work (“The Licensor”) grant permission
|
||||||
|
to any person, group or legal entity that doesn't violate the prohibitions below (“The User”),
|
||||||
|
to do everything with this Work that would otherwise infringe their copyright or any patent claims,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
## Obligations
|
||||||
|
|
||||||
|
The User must give appropriate credit to the Licensor,
|
||||||
|
provide a copy of this license or a (clickable, if the medium allows) link to
|
||||||
|
[oql.avris.it/license/v1.2](https://oql.avris.it/license/v1.2),
|
||||||
|
and indicate whether and what kind of changes were made.
|
||||||
|
The User may do so in any reasonable manner,
|
||||||
|
but not in any way that suggests the Licensor endorses the User or their use.
|
||||||
|
|
||||||
|
## Prohibitions
|
||||||
|
|
||||||
|
No one may use this Work for prejudiced or bigoted purposes, including but not limited to:
|
||||||
|
racism, xenophobia, queerphobia, queer exclusionism, homophobia, transphobia, enbyphobia, misogyny.
|
||||||
|
|
||||||
|
No one may use this Work to inflict or facilitate violence or abuse of human rights,
|
||||||
|
as defined in either of the following documents:
|
||||||
|
[Universal Declaration of Human Rights](https://www.un.org/en/about-us/universal-declaration-of-human-rights),
|
||||||
|
[European Convention on Human Rights](https://prd-echr.coe.int/web/echr/european-convention-on-human-rights)
|
||||||
|
along with the rulings of the [European Court of Human Rights](https://www.echr.coe.int/).
|
||||||
|
|
||||||
|
No law enforcement, carceral institutions, immigration enforcement entities, military entities or military contractors
|
||||||
|
may use the Work for any reason. This also applies to any individuals employed by those entities.
|
||||||
|
|
||||||
|
No business entity where the ratio of pay (salaried, freelance, stocks, or other benefits)
|
||||||
|
between the highest and lowest individual in the entity is greater than 50 : 1
|
||||||
|
may use the Work for any reason.
|
||||||
|
|
||||||
|
No private business run for profit with more than a thousand employees
|
||||||
|
may use the Work for any reason.
|
||||||
|
|
||||||
|
Unless the User has made substantial changes to the Work,
|
||||||
|
or uses it only as a part of a new work (eg. as a library, as a part of an anthology, etc.),
|
||||||
|
they are prohibited from selling the Work.
|
||||||
|
That prohibition includes processing the Work with machine learning models.
|
||||||
|
|
||||||
|
## Sanctions
|
||||||
|
|
||||||
|
If the Licensor notifies the User that they have not complied with the rules of the license,
|
||||||
|
they can keep their license by complying within 30 days after the notice.
|
||||||
|
If they do not do so, their license ends immediately.
|
||||||
|
|
||||||
|
## Warranty
|
||||||
|
|
||||||
|
This Work is provided “as is”, without warranty of any kind, express or implied.
|
||||||
|
The Licensor will not be liable to anyone for any damages related to the Work or this license,
|
||||||
|
under any kind of legal claim as far as the law allows.
|
37
README.md
37
README.md
@ -0,0 +1,37 @@
|
|||||||
|
<a href="https://oql.avris.it/license/v1.2" target="_blank" rel="noopener"><img src="https://badgers.space/badge/License/OQL/pink" alt="License: OQL" style="vertical-align: middle;"/></a>
|
||||||
|
|
||||||
|
# STSG
|
||||||
|
|
||||||
|
## Planned features
|
||||||
|
|
||||||
|
- [ ] auto uploading to ftp and other online services using a plugin system
|
||||||
|
- [ ] build a git wiki in a specified languages instead of a static website
|
||||||
|
- [ ] hosting the documentation somehow
|
||||||
|
- [ ] multiple templates + a way to choose templates
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
@ -2,17 +2,20 @@
|
|||||||
name = "stsg"
|
name = "stsg"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"watchdog~=6.0.0",
|
"watchdog~=6.0.0",
|
||||||
"markdown~=3.3.6",
|
"markdown2~=2.5.3",
|
||||||
"bs4~=0.0.2",
|
"bs4~=0.0.2",
|
||||||
"toml~0.10.2",
|
"toml~=0.10.2",
|
||||||
"jinja2~=3.1.6",
|
"jinja2~=3.1.6",
|
||||||
]
|
]
|
||||||
dynamic = []
|
dynamic = []
|
||||||
authors = []
|
authors = []
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.8"
|
||||||
classifiers = []
|
classifiers = []
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
license-files = [
|
||||||
|
"LICENSE"
|
||||||
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
stsg = "stsg.__main__:build"
|
stsg = "stsg.__main__:build"
|
||||||
|
9
src/articles/de.md
Normal file
9
src/articles/de.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# STSG
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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. Für mehr Info zur Nutzung lest [die Lizenz]({{license.url}}).
|
16
src/articles/documentation/de.md
Normal file
16
src/articles/documentation/de.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Dokumentation
|
||||||
|
|
||||||
|
Dies ist die Dokumentation von STSG.
|
||||||
|
|
||||||
|
## Favicon bauen
|
||||||
|
|
||||||
|
Dies ist einfach nur ein hilfreiches Skript um favicons zu erstellen. Dies ist nicht unbedingt notwendig. Aber bei verwendung muss inkscape und imagemagick installiert sein.
|
||||||
|
|
||||||
|
```
|
||||||
|
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
|
||||||
|
```
|
16
src/articles/documentation/en.md
Normal file
16
src/articles/documentation/en.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# documentation
|
||||||
|
|
||||||
|
This is the documentation for stsg.
|
||||||
|
|
||||||
|
## build favicon
|
||||||
|
|
||||||
|
This is just a helpfull script to build the favicon. It is not strictly required to use. Make sure inkscape and imagemagick is installed.
|
||||||
|
|
||||||
|
```
|
||||||
|
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
|
||||||
|
```
|
26
src/articles/documentation/get_started/de.md
Normal file
26
src/articles/documentation/get_started/de.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Get Started
|
||||||
|
|
||||||
|
Hier wird gezeigt wie man seine eigene website erstellen kann.
|
||||||
|
|
||||||
|
1. Fork das Projekt und Klone es lokal
|
||||||
|
2. {{installation.link}}
|
||||||
|
|
||||||
|
## Dateistruktur
|
||||||
|
|
||||||
|
Die Dateien die den Content definieren findet man in `src`. Dort gibt es 3 Unterordner. In `templates` wird der Style und die Funktionalität der Webseite definiert. Wenn du nicht programmieren kannst, lass den in Ruhe. In `static` befinden sich Statische Dateien wie das css oder Bilder. Dieser Ordner wird bei dem Build einfach kopiert. Bei `articles` wird es interesannt. Dort befinden sich wie der Name schon sagt die Artikle.
|
||||||
|
|
||||||
|
Wenn irgendetwas unklar ist, dann kann der code dieser Dokumentation (ist in eurer Fork) auch hilfreich sein.
|
||||||
|
|
||||||
|
## Artikel schreiben
|
||||||
|
|
||||||
|
Der Text für die Artikel ist immer in einer Datei mit folgendem namen `<language_code>.md`. In diesem Fall wäre dies `de.md`. Dann kann der Artikel ganz normal mit dem [Markdown Syntax](https://www.markdownguide.org/) geschrieben werden.
|
||||||
|
|
||||||
|
Soll der Artikel untergeordnete Artikel haben, so kann man dafür einfach ein neuen Ordner in dem jeweiligen Artikel erstellen. Der Name ist dann auch der Name der z.B. in der Url zu sehen ist.
|
||||||
|
|
||||||
|
Will man Metadaten für den Artikel definieren (z.B. Name, Erstellungsdatum, Autor:in), dann kann man dies in der `index.toml` machen. Wenn noch keine existiert kann sie einfach erstellt werden. Hier ist ein Beispiel einer solchen Datei.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
name="stsg"
|
||||||
|
datetime="2024-04-15 13:45:12.123456"
|
||||||
|
author="Hazel"
|
||||||
|
```
|
26
src/articles/documentation/get_started/en.md
Normal file
26
src/articles/documentation/get_started/en.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# get started
|
||||||
|
|
||||||
|
Here you will learn how to get started making your own website.
|
||||||
|
|
||||||
|
1. fork the project and clone the fork
|
||||||
|
2. {{installation.link}}
|
||||||
|
|
||||||
|
## File structure
|
||||||
|
|
||||||
|
The files that define the content can be found in `src`. There are 3 subfolders. The style and functionality is defined in `templates`. If you can't code, or you don't know what your doing, leave it alone. The static files like stylesheets or pictures exist in `static`. This folder is simply copied on build. The interesting part starts with `articles`. Here are (like the name implies) all articles.
|
||||||
|
|
||||||
|
If something remains unclear, then the code of this documentation (should be found in your fork) could be helpful.
|
||||||
|
|
||||||
|
## Write articles
|
||||||
|
|
||||||
|
The text for the articles follows the following naming scheme `<language_code>.md`. In this case it would be `en.md` then you can write the article normally using [markdown](https://www.markdownguide.org/) for formatting.
|
||||||
|
|
||||||
|
If there should be subarticles, just create a new folder in the parent article folder. The folder name will be used as slug. That means it will appear in the url, and you can use it to link to other articles.
|
||||||
|
|
||||||
|
If you want to define the metadata for the article (the name, creation date, or author), then you can do so in `index.toml`. If none exists you can just create one. Here an example of such a file.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
name="stsg"
|
||||||
|
datetime="2024-04-15 13:45:12.123456"
|
||||||
|
author="Hazel"
|
||||||
|
```
|
19
src/articles/documentation/installation/de.md
Normal file
19
src/articles/documentation/installation/de.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Installierung des Programms
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Programm ausführen
|
||||||
|
|
||||||
|
Um einen lokalen http server zu starten kann folgender Befehl ausgeführt werden:
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 -m http.server 1312
|
||||||
|
```
|
||||||
|
|
||||||
|
Dann ist die Seite auf `localhost:1312` zu finden
|
||||||
|
|
||||||
|
Man kann die Seite entweder normal bauen mit `stsg` oder mit `stsg_dev` einen hot reload server starten.
|
19
src/articles/documentation/installation/en.md
Normal file
19
src/articles/documentation/installation/en.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# setup the programm
|
||||||
|
|
||||||
|
```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:
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 -m http.server 1312
|
||||||
|
```
|
||||||
|
|
||||||
|
Then visit `localhost:1312`
|
||||||
|
|
||||||
|
You can either build it normally with `stsg` or start a hot reload server with `stsg_dev`.
|
@ -1,75 +1,9 @@
|
|||||||
# Populumque avidosque Sparte quoque auctore equidem
|
# STSG
|
||||||
|
|
||||||
## Sunt aevis
|

|
||||||
|
|
||||||
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. For more information about the usage of that tool, read [our license]({{license.url}}).
|
||||||
|
|
||||||
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!
|
|
||||||
|
@ -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.
|
|
@ -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!
|
|
@ -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.
|
|
@ -1,2 +1,2 @@
|
|||||||
name="index_page"
|
name="stsg"
|
||||||
datetime="2024-04-15 13:45:12.123456"
|
iso_date="2024-04-15 13:45:12.123456"
|
||||||
|
@ -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.
|
|
57
src/articles/license/de.md
Normal file
57
src/articles/license/de.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# 🏳️🌈 Opinionated Queer License v1.2
|
||||||
|
|
||||||
|
© Copyright {Licensor}
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
|
||||||
|
The creators of this Work (“The Licensor”) grant permission
|
||||||
|
to any person, group or legal entity that doesn't violate the prohibitions below (“The User”),
|
||||||
|
to do everything with this Work that would otherwise infringe their copyright or any patent claims,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
## Obligations
|
||||||
|
|
||||||
|
The User must give appropriate credit to the Licensor,
|
||||||
|
provide a copy of this license or a (clickable, if the medium allows) link to
|
||||||
|
[oql.avris.it/license/v1.2](https://oql.avris.it/license/v1.2),
|
||||||
|
and indicate whether and what kind of changes were made.
|
||||||
|
The User may do so in any reasonable manner,
|
||||||
|
but not in any way that suggests the Licensor endorses the User or their use.
|
||||||
|
|
||||||
|
## Prohibitions
|
||||||
|
|
||||||
|
No one may use this Work for prejudiced or bigoted purposes, including but not limited to:
|
||||||
|
racism, xenophobia, queerphobia, queer exclusionism, homophobia, transphobia, enbyphobia, misogyny.
|
||||||
|
|
||||||
|
No one may use this Work to inflict or facilitate violence or abuse of human rights,
|
||||||
|
as defined in either of the following documents:
|
||||||
|
[Universal Declaration of Human Rights](https://www.un.org/en/about-us/universal-declaration-of-human-rights),
|
||||||
|
[European Convention on Human Rights](https://prd-echr.coe.int/web/echr/european-convention-on-human-rights)
|
||||||
|
along with the rulings of the [European Court of Human Rights](https://www.echr.coe.int/).
|
||||||
|
|
||||||
|
No law enforcement, carceral institutions, immigration enforcement entities, military entities or military contractors
|
||||||
|
may use the Work for any reason. This also applies to any individuals employed by those entities.
|
||||||
|
|
||||||
|
No business entity where the ratio of pay (salaried, freelance, stocks, or other benefits)
|
||||||
|
between the highest and lowest individual in the entity is greater than 50 : 1
|
||||||
|
may use the Work for any reason.
|
||||||
|
|
||||||
|
No private business run for profit with more than a thousand employees
|
||||||
|
may use the Work for any reason.
|
||||||
|
|
||||||
|
Unless the User has made substantial changes to the Work,
|
||||||
|
or uses it only as a part of a new work (eg. as a library, as a part of an anthology, etc.),
|
||||||
|
they are prohibited from selling the Work.
|
||||||
|
That prohibition includes processing the Work with machine learning models.
|
||||||
|
|
||||||
|
## Sanctions
|
||||||
|
|
||||||
|
If the Licensor notifies the User that they have not complied with the rules of the license,
|
||||||
|
they can keep their license by complying within 30 days after the notice.
|
||||||
|
If they do not do so, their license ends immediately.
|
||||||
|
|
||||||
|
## Warranty
|
||||||
|
|
||||||
|
This Work is provided “as is”, without warranty of any kind, express or implied.
|
||||||
|
The Licensor will not be liable to anyone for any damages related to the Work or this license,
|
||||||
|
under any kind of legal claim as far as the law allows.
|
57
src/articles/license/en.md
Normal file
57
src/articles/license/en.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# 🏳️🌈 Opinionated Queer License v1.2
|
||||||
|
|
||||||
|
© Copyright {Licensor}
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
|
||||||
|
The creators of this Work (“The Licensor”) grant permission
|
||||||
|
to any person, group or legal entity that doesn't violate the prohibitions below (“The User”),
|
||||||
|
to do everything with this Work that would otherwise infringe their copyright or any patent claims,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
## Obligations
|
||||||
|
|
||||||
|
The User must give appropriate credit to the Licensor,
|
||||||
|
provide a copy of this license or a (clickable, if the medium allows) link to
|
||||||
|
[oql.avris.it/license/v1.2](https://oql.avris.it/license/v1.2),
|
||||||
|
and indicate whether and what kind of changes were made.
|
||||||
|
The User may do so in any reasonable manner,
|
||||||
|
but not in any way that suggests the Licensor endorses the User or their use.
|
||||||
|
|
||||||
|
## Prohibitions
|
||||||
|
|
||||||
|
No one may use this Work for prejudiced or bigoted purposes, including but not limited to:
|
||||||
|
racism, xenophobia, queerphobia, queer exclusionism, homophobia, transphobia, enbyphobia, misogyny.
|
||||||
|
|
||||||
|
No one may use this Work to inflict or facilitate violence or abuse of human rights,
|
||||||
|
as defined in either of the following documents:
|
||||||
|
[Universal Declaration of Human Rights](https://www.un.org/en/about-us/universal-declaration-of-human-rights),
|
||||||
|
[European Convention on Human Rights](https://prd-echr.coe.int/web/echr/european-convention-on-human-rights)
|
||||||
|
along with the rulings of the [European Court of Human Rights](https://www.echr.coe.int/).
|
||||||
|
|
||||||
|
No law enforcement, carceral institutions, immigration enforcement entities, military entities or military contractors
|
||||||
|
may use the Work for any reason. This also applies to any individuals employed by those entities.
|
||||||
|
|
||||||
|
No business entity where the ratio of pay (salaried, freelance, stocks, or other benefits)
|
||||||
|
between the highest and lowest individual in the entity is greater than 50 : 1
|
||||||
|
may use the Work for any reason.
|
||||||
|
|
||||||
|
No private business run for profit with more than a thousand employees
|
||||||
|
may use the Work for any reason.
|
||||||
|
|
||||||
|
Unless the User has made substantial changes to the Work,
|
||||||
|
or uses it only as a part of a new work (eg. as a library, as a part of an anthology, etc.),
|
||||||
|
they are prohibited from selling the Work.
|
||||||
|
That prohibition includes processing the Work with machine learning models.
|
||||||
|
|
||||||
|
## Sanctions
|
||||||
|
|
||||||
|
If the Licensor notifies the User that they have not complied with the rules of the license,
|
||||||
|
they can keep their license by complying within 30 days after the notice.
|
||||||
|
If they do not do so, their license ends immediately.
|
||||||
|
|
||||||
|
## Warranty
|
||||||
|
|
||||||
|
This Work is provided “as is”, without warranty of any kind, express or implied.
|
||||||
|
The Licensor will not be liable to anyone for any damages related to the Work or this license,
|
||||||
|
under any kind of legal claim as far as the law allows.
|
1
src/articles/license/index.toml
Normal file
1
src/articles/license/index.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
author="Andrea Vos"
|
BIN
src/static/assets/16.png
Normal file
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
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
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
BIN
src/static/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
88
src/static/assets/logo.svg
Normal file
88
src/static/assets/logo.svg
Normal 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
BIN
src/static/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
13
src/static/style.css
Normal file
13
src/static/style.css
Normal 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%;
|
||||||
|
}
|
||||||
|
}
|
@ -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" />
|
||||||
<title>{{slug}}</title>
|
<link rel="icon" type="image/x-icon" href="/static/icon.ico">
|
||||||
|
<title>{{name}}</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"
|
||||||
@ -15,30 +16,48 @@
|
|||||||
>
|
>
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item" href="#">
|
<a class="navbar-item" href="#">
|
||||||
<strong>Static Translated Site Generator</strong>
|
<strong>{{name}}</strong>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
{% if breadcrumbs|length %}
|
||||||
|
<!-- Breadcrumbs Section -->
|
||||||
|
<nav class="breadcrumb is-small has-succeeds-separator p-3" aria-label="breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
{% for b in breadcrumbs %}
|
||||||
|
<li class="{{'is-active' if b.slug == slug}}">
|
||||||
|
<a {{'aria-current="page"' if b.slug == slug}} href="{{b.url}}">{{b.name}}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<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">
|
||||||
<a href="{{t.url}}" hreflang="{{t.language.code}}" class="card mb-4" style="color: inherit; text-decoration: none;">
|
<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;">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p class="title">{{t.language.flag}} {{t.title}} </p>
|
<p class="title">{{t.language.flag}} {{t.name}}</p>
|
||||||
<p class="content">
|
<hr />
|
||||||
{{t.preview}}
|
<p class="content">
|
||||||
<br />
|
{{t.preview}}
|
||||||
<time datetime="{{iso_date}}">{{date}}</time>
|
</p>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
|
||||||
</a>
|
<div class="card-footer">
|
||||||
|
<time class="card-footer-item" datetime="{{t.iso_date}}">{{t.date}}</time>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
@ -49,17 +68,22 @@
|
|||||||
{% 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 %}
|
||||||
<div class="card mb-4" >
|
<div class="card mb-4" >
|
||||||
<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.name}} </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.name}}</a>
|
||||||
|
{% endfor %}
|
||||||
</p>
|
</p>
|
||||||
|
<hr />
|
||||||
|
<time datetime="{{c.iso_date}}">{{c.date}}</time>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -72,7 +96,7 @@
|
|||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<div class="content has-text-centered">
|
<div class="content has-text-centered">
|
||||||
<p><strong>STSG</strong> by Hazel. © 2025</p>
|
<p><strong>{{name}}</strong> by {{author}}. © {{year}}</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
@ -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>{{name}}</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) -->
|
||||||
@ -16,11 +18,24 @@
|
|||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item" href="{{article_url}}">
|
<a class="navbar-item" href="{{article_url}}">
|
||||||
<strong>{{language.flag}} {{title}}</strong>
|
<strong>{{language.flag}} {{title}}</strong>
|
||||||
<time datetime="{{meta.iso_date}}">{{meta.date}}</time>
|
<time datetime="{{iso_date}}">{{date}}</time>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
{% if breadcrumbs|length %}
|
||||||
|
<!-- Breadcrumbs Section -->
|
||||||
|
<nav class="breadcrumb is-small has-succeeds-separator p-3" aria-label="breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
{% for b in breadcrumbs %}
|
||||||
|
<li class="{{'is-active' if b.slug == slug}}">
|
||||||
|
<a {{'aria-current="page"' if b.slug == slug}} href="{{b.url}}">{{b.name}}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@ -28,10 +43,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{% if related|length %}
|
||||||
|
<section class="section">
|
||||||
|
<div class="content">
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
{% for c in related %}
|
||||||
|
<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="content">
|
||||||
|
{{c.preview}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-footer">
|
||||||
|
<time class="card-footer-item" datetime="{{c.iso_date}}">{{c.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">
|
||||||
<p><strong>STSG</strong> by Hazel. © 2025</p>
|
<p><strong>{{name}}</strong> by {{author}}. © {{year}}</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
|
fall_back_to_overview_in_translation = false
|
||||||
|
|
||||||
[setup]
|
[setup]
|
||||||
source_directory = "src"
|
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"
|
||||||
|
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
class config:
|
class config:
|
||||||
|
default_author = "anonymous"
|
||||||
|
fall_back_to_overview_in_translation = True
|
||||||
|
|
||||||
class setup:
|
class setup:
|
||||||
source_directory = "src"
|
source_directory = "src"
|
||||||
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
|
||||||
|
markdown_extras = [
|
||||||
|
"fenced-code-blocks"
|
||||||
|
]
|
||||||
|
|
||||||
languages = {
|
languages = {
|
||||||
"af": {
|
"af": {
|
||||||
|
402
stsg/build.py
402
stsg/build.py
@ -3,35 +3,21 @@ import logging
|
|||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
import markdown
|
from markdown2 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, UserList
|
||||||
import toml
|
import toml
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import jinja2
|
import jinja2
|
||||||
|
from functools import cached_property
|
||||||
|
|
||||||
|
|
||||||
|
from .definitions import *
|
||||||
from . import config
|
from . import config
|
||||||
|
|
||||||
|
|
||||||
def replace_values(template: str, values: Dict[str, str]) -> str:
|
def shorten_text_and_clean(html_string, max_length=config.formatting.preview_length):
|
||||||
for key, value in values.items():
|
|
||||||
template = template.replace("{" + key + "}", value)
|
|
||||||
|
|
||||||
return template
|
|
||||||
|
|
||||||
|
|
||||||
def get_first_header_content(content, fallback: str = ""):
|
|
||||||
soup = BeautifulSoup(content, 'html.parser')
|
|
||||||
for level in range(1, 7):
|
|
||||||
header = soup.find(f'h{level}')
|
|
||||||
if header:
|
|
||||||
return header.get_text(strip=True)
|
|
||||||
|
|
||||||
return fallback
|
|
||||||
|
|
||||||
|
|
||||||
def shorten_text_and_clean(html_string, max_length=config.formatting.article_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,19 +55,22 @@ def shorten_text_and_clean(html_string, max_length=config.formatting.article_pre
|
|||||||
return str(soup)
|
return str(soup)
|
||||||
|
|
||||||
|
|
||||||
def stem_to_language_code(stem: str) -> str:
|
def shift_headings(html_string, header_shift=config.formatting.preview_header_shift):
|
||||||
language_code = stem.lower().replace("-", "_")
|
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))
|
||||||
|
|
||||||
if language_code in config.languages:
|
|
||||||
return language_code
|
|
||||||
|
|
||||||
language_code = language_code.split("_")[0]
|
|
||||||
if language_code in config.languages:
|
|
||||||
return language_code
|
|
||||||
|
|
||||||
logger.error("Didn't recognize %s as a valid language code, add it to the config, or fix your structure.", stem)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateDict(dict):
|
class TemplateDict(dict):
|
||||||
@ -99,8 +88,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,47 +107,169 @@ class LanguageDict(dict):
|
|||||||
|
|
||||||
return lang_dict
|
return lang_dict
|
||||||
|
|
||||||
|
|
||||||
LANGUAGES = LanguageDict()
|
LANGUAGES = LanguageDict()
|
||||||
|
|
||||||
|
|
||||||
|
def add_html_link(c):
|
||||||
|
name = c["name"]
|
||||||
|
url = c["url"]
|
||||||
|
|
||||||
|
c["link"] = f'<a href="{url}">{name}</a>'
|
||||||
|
|
||||||
|
|
||||||
|
def get_translated_articles(articles: List[Article], language_code: str = None) -> List[Union[ArticleTranslation, Article]]:
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
for a in articles:
|
||||||
|
if a.slug in result:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if language_code is None:
|
||||||
|
result[a.slug] = a
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not config.fall_back_to_overview_in_translation and language_code not in a.article_translations_map:
|
||||||
|
continue
|
||||||
|
|
||||||
|
result[a.slug] = a.article_translations_map.get(language_code, a)
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleList(UserList):
|
||||||
|
def __init__(self, iterable):
|
||||||
|
super().__init__(item for item in iterable)
|
||||||
|
|
||||||
|
self.used_slugs = set()
|
||||||
|
|
||||||
|
def append(self, a: Union[Article, str]):
|
||||||
|
if isinstance(a, str):
|
||||||
|
a = ARTICLE_LAKE[a]
|
||||||
|
|
||||||
|
if a.slug in self.used_slugs:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.used_slugs.add(a.slug)
|
||||||
|
self.data.append(a)
|
||||||
|
|
||||||
|
def extend(self, other):
|
||||||
|
for a in other:
|
||||||
|
self.append(a)
|
||||||
|
|
||||||
|
def get_translated(self, language_code: str) -> ArticleList[Union[ArticleTranslation, Article]]:
|
||||||
|
res = ArticleList([])
|
||||||
|
|
||||||
|
for a in self:
|
||||||
|
if not config.fall_back_to_overview_in_translation and language_code not in a.article_translations_map:
|
||||||
|
continue
|
||||||
|
|
||||||
|
res.append(a.article_translations_map.get(language_code, a))
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
@property
|
||||||
|
def context(self) -> List[Union[ArticleContext, ArticleTranslationContext]]:
|
||||||
|
return [a.context for a in self]
|
||||||
|
|
||||||
|
|
||||||
class ArticleTranslation:
|
class ArticleTranslation:
|
||||||
def __init__(self, file: Path, article: Article):
|
article: Article
|
||||||
self.file = file
|
slug: str = property(fget=lambda self: self.article.slug)
|
||||||
self.article = article
|
file: Path
|
||||||
|
|
||||||
self.context: Dict[str, Any] = {}
|
@cached_property
|
||||||
|
def html_content(self) -> str:
|
||||||
# initializing the location of the article translation
|
html_content = self.file.read_text()
|
||||||
self.language_code = stem_to_language_code(self.file.stem)
|
|
||||||
self.location_in_tree = [self.language_code, *self.article.location_in_tree]
|
|
||||||
self.url = "/" + "/".join(self.location_in_tree)
|
|
||||||
self.dist_path = Path(config.setup.dist_directory, *self.location_in_tree)
|
|
||||||
|
|
||||||
self.priority = LANGUAGES[self.language_code]["priority"]
|
|
||||||
self.real_language_code = LANGUAGES[self.language_code]["code"]
|
|
||||||
|
|
||||||
# TODO remove
|
|
||||||
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)
|
return markdown(html_content, extras=config.formatting.markdown_extras)
|
||||||
self.article_preview = markdown.markdown(self.article_preview)
|
return html_content
|
||||||
|
|
||||||
self.title = get_first_header_content(self.article_content, fallback="")
|
@cached_property
|
||||||
|
def language_code(self) -> str:
|
||||||
|
language_code = self.file.stem.lower().replace("-", "_")
|
||||||
|
|
||||||
|
if language_code in config.languages:
|
||||||
|
return language_code
|
||||||
|
|
||||||
|
language_code = language_code.split("_")[0]
|
||||||
|
if language_code in config.languages:
|
||||||
|
return language_code
|
||||||
|
|
||||||
|
logger.error("Didn't recognize %s as a valid language code, add it to the config, or fix your structure.", stem)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def priority(self) -> int:
|
||||||
|
return LANGUAGES[self.language_code]["priority"]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def slug_path(self) -> List[str]:
|
||||||
|
return [self.language_code, *self.article.slug_path]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def url(self) -> str:
|
||||||
|
return "/" + "/".join(self.slug_path)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def dist_path(self) -> Path:
|
||||||
|
return Path(config.setup.dist_directory, *self.slug_path)
|
||||||
|
|
||||||
|
context: ArticleTranslationContext
|
||||||
|
cross_article_context: Dict[str, Any]
|
||||||
|
|
||||||
|
def __init__(self, file: Path, article: Article):
|
||||||
|
self.article = article
|
||||||
|
self.file = file
|
||||||
|
|
||||||
|
self.context = TRANSLATED_CROSS_ARTICLE_CONTEXT[self.language_code][self.article.slug] = {}
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def name(self) -> str:
|
||||||
|
soup = BeautifulSoup(self.html_content, 'html.parser')
|
||||||
|
for level in range(1, 7):
|
||||||
|
header = soup.find(f'h{level}')
|
||||||
|
if header:
|
||||||
|
return header.get_text(strip=True)
|
||||||
|
|
||||||
|
return self.article.name
|
||||||
|
|
||||||
def __init_context__(self):
|
def __init_context__(self):
|
||||||
self.context["meta"] = self.article.context_shared
|
self.context["slug"] = self.article.slug
|
||||||
|
self.context["name"] = self.name
|
||||||
self.context["url"] = self.url
|
self.context["url"] = self.url
|
||||||
|
add_html_link(self.context)
|
||||||
|
self.context["date"] = self.article.modified_at.strftime(config.formatting.datetime_format)
|
||||||
|
self.context["year"] = str(self.article.modified_at.year)
|
||||||
|
self.context["iso_date"] = self.article.modified_at.isoformat()
|
||||||
|
self.context["author"] = self.article.author
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
html_content = self.file.read_text()
|
# get children
|
||||||
if self.file.suffix == ".md":
|
self.context["children"] = self.article.child_articles.get_translated(self.language_code).context
|
||||||
html_content = markdown.markdown(html_content)
|
self.context["breadcrumbs"] = ArticleList(self.article.article_path).get_translated(self.language_code).context
|
||||||
|
|
||||||
self.context["title"] = get_first_header_content(html_content, fallback=LANGUAGES[self.language_code]["native_name"])
|
def __init_content_context__(self):
|
||||||
self.context["content"] = html_content
|
template = jinja2.Template(self.html_content)
|
||||||
self.context["preview"] = shorten_text_and_clean(html_string=html_content)
|
template.environment.accessed_keys = []
|
||||||
|
template.environment.context_class = ContextDict
|
||||||
|
|
||||||
|
self.html_content = template.render({
|
||||||
|
**CROSS_ARTICLE_CONTEXT,
|
||||||
|
**TRANSLATED_CROSS_ARTICLE_CONTEXT[self.language_code],
|
||||||
|
})
|
||||||
|
|
||||||
|
template.environment.context_class = jinja2.runtime.Context
|
||||||
|
accessed_keys = template.environment.accessed_keys
|
||||||
|
|
||||||
|
for key in accessed_keys:
|
||||||
|
self.article.linked_articles.append(key)
|
||||||
|
|
||||||
|
self.context["content"] = self.html_content
|
||||||
|
self.context["preview"] = get_preview_text(html_string=self.html_content)
|
||||||
|
|
||||||
|
self.context["linked"] = self.article.linked_articles.get_translated(self.language_code).context
|
||||||
|
self.context["related"] = self.article.related_articles.get_translated(self.language_code).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)
|
||||||
@ -166,35 +279,69 @@ class ArticleTranslation:
|
|||||||
|
|
||||||
|
|
||||||
class Article:
|
class Article:
|
||||||
def __init__(self, directory: Path, location_in_tree: Optional[List[str]] = None, is_root: bool = False, parent: Optional[Article] = None):
|
directory: Path
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def config(self) -> ArticleConfig:
|
||||||
|
config_file = self.directory / "index.toml"
|
||||||
|
return toml.load(config_file) if config_file.exists() else {}
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def slug(self) -> str:
|
||||||
|
slug = self.config.get("name", self.directory.name)
|
||||||
|
if slug in ARTICLE_LAKE:
|
||||||
|
logger.error("two articles have the same name at %s and %r", ARTICLE_LAKE[slug].directory, self.directory)
|
||||||
|
exit(1)
|
||||||
|
return slug
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self.config.get("name", self.slug)
|
||||||
|
|
||||||
|
article_path: List[Article]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def slug_path(self) -> List[str]:
|
||||||
|
return [a.slug for a in self.article_path[1:]]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def url(self) -> str:
|
||||||
|
return "/" + "/".join(self.slug_path)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def dist_path(self) -> Path:
|
||||||
|
return Path(config.setup.dist_directory, *self.slug_path)
|
||||||
|
|
||||||
|
context: ArticleContext
|
||||||
|
|
||||||
|
child_articles: ArticleList[Article]
|
||||||
|
article_translations_list: List[ArticleTranslation]
|
||||||
|
article_translations_map: Dict[str, ArticleTranslation]
|
||||||
|
|
||||||
|
linked_articles: ArticleList[Article]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def related_articles(self) -> ArticleList[Article]:
|
||||||
|
res = ArticleList(self.child_articles)
|
||||||
|
res.extend(self.linked_articles)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def __init__(self, directory: Path, article_path: Optional[List[str]] = None, is_root: bool = False, parent: Optional[Article] = None):
|
||||||
self.directory = directory
|
self.directory = directory
|
||||||
|
|
||||||
self.context: Dict[str, Any] = {}
|
self.article_path: List[Article] = article_path or []
|
||||||
self.context_shared: Dict[str, Any] = {}
|
self.article_path.append(self)
|
||||||
if parent is not None:
|
|
||||||
self.context["parent"] = parent.context_shared
|
|
||||||
|
|
||||||
# initializing the config values of the article
|
self.context = CROSS_ARTICLE_CONTEXT[self.slug] = {}
|
||||||
config_file = self.directory / "index.toml"
|
|
||||||
self.config = toml.load(config_file) if config_file.exists() else {}
|
|
||||||
|
|
||||||
# initializing the location and slug of the article
|
|
||||||
self.slug = self.config.get("name", self.directory.name)
|
|
||||||
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)
|
|
||||||
exit(1)
|
|
||||||
ARTICLE_LAKE[self.slug] = self
|
ARTICLE_LAKE[self.slug] = self
|
||||||
|
|
||||||
self.location_in_tree: List[str] = location_in_tree or []
|
self.linked_articles = ArticleList([])
|
||||||
if not is_root:
|
|
||||||
self.location_in_tree.append(self.slug)
|
|
||||||
self.url = "/" + "/".join(self.location_in_tree)
|
|
||||||
self.dist_path = Path(config.setup.dist_directory, *self.location_in_tree)
|
|
||||||
|
|
||||||
# build the tree
|
# build the tree
|
||||||
self.child_articles: List[Article] = []
|
self.child_articles = ArticleList([])
|
||||||
self.article_translations_list: List[ArticleTranslation] = []
|
self.article_translations_list = []
|
||||||
self.article_translations_map: Dict[str, ArticleTranslation] = {}
|
self.article_translations_map = {}
|
||||||
|
|
||||||
for c in self.directory.iterdir():
|
for c in self.directory.iterdir():
|
||||||
if c.name == "index.toml":
|
if c.name == "index.toml":
|
||||||
@ -207,44 +354,66 @@ class Article:
|
|||||||
elif c.is_dir():
|
elif c.is_dir():
|
||||||
self.child_articles.append(Article(
|
self.child_articles.append(Article(
|
||||||
directory=c,
|
directory=c,
|
||||||
location_in_tree=self.location_in_tree.copy(),
|
article_path=self.article_path.copy(),
|
||||||
parent=self,
|
parent=self,
|
||||||
))
|
))
|
||||||
|
|
||||||
self.article_translations_list.sort(key=lambda a: a.priority, reverse=True)
|
self.article_translations_list.sort(key=lambda a: a.priority, reverse=True)
|
||||||
|
|
||||||
logger.info("found %s at %s with the translations %s", self.slug, ".".join(list(self.location_in_tree)), ",".join(self.article_translations_map.keys()))
|
logger.info("found %s at %s with the translations %s", self.slug, ".".join(list(self.slug_path)), ",".join(self.article_translations_map.keys()))
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def modified_at(self) -> datetime:
|
||||||
|
if "iso_date" in self.config:
|
||||||
|
return datetime.fromisoformat(self.config["iso_date"])
|
||||||
|
|
||||||
|
"""
|
||||||
|
TODO
|
||||||
|
scann every article file and use the youngest article file
|
||||||
|
"""
|
||||||
|
|
||||||
|
return datetime.fromtimestamp(self.directory.stat().st_mtime)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def author(self) -> str:
|
||||||
|
return self.config.get("author", config.default_author)
|
||||||
|
|
||||||
def __init_context__(self):
|
def __init_context__(self):
|
||||||
self.context_shared["url"] = self.url
|
self.context["slug"] = self.slug
|
||||||
self.context_shared["slug"] = self.slug
|
self.context["name"] = self.name
|
||||||
|
self.context["url"] = self.url
|
||||||
modified_at = datetime.fromisoformat(self.config["datetime"]) if "datetime" in self.config else datetime.fromtimestamp(self.directory.stat().st_mtime)
|
add_html_link(self.context)
|
||||||
self.context_shared["date"] = modified_at.strftime(config.formatting.datetime_format)
|
self.context["date"] = self.modified_at.strftime(config.formatting.datetime_format)
|
||||||
self.context_shared["iso_date"] = modified_at.isoformat()
|
self.context["year"] = str(self.modified_at.year)
|
||||||
|
self.context["iso_date"] = self.modified_at.isoformat()
|
||||||
self.context.update(self.context_shared)
|
self.context["author"] = self.author
|
||||||
|
|
||||||
# recursive context structures
|
# recursive context structures
|
||||||
translation_list = self.context["translations"] = []
|
self.context["translations"] = [c.context for c in self.article_translations_list]
|
||||||
child_article_list = self.context["children"] = []
|
self.context["children"] = self.child_articles.context
|
||||||
|
self.context["breadcrumbs"] = [b.context for b in self.article_path]
|
||||||
|
for lang, article in self.article_translations_map.items():
|
||||||
|
self.context[lang] = article.context
|
||||||
|
|
||||||
for article_translation in self.article_translations_list:
|
|
||||||
self.context[article_translation.real_language_code] = article_translation.context
|
|
||||||
translation_list.append(article_translation.context)
|
|
||||||
|
|
||||||
for child_article in self.child_articles:
|
|
||||||
child_article_list.append(child_article.context)
|
|
||||||
|
|
||||||
# recursively build context
|
|
||||||
for at in self.article_translations_list:
|
for at in self.article_translations_list:
|
||||||
at.__init_context__()
|
at.__init_context__()
|
||||||
|
|
||||||
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__()
|
||||||
|
|
||||||
|
self.context["linked"] = self.linked_articles.context
|
||||||
|
self.context["related"] = self.related_articles.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)
|
||||||
|
|
||||||
with Path(self.dist_path, "index.html").open("w") as f:
|
with Path(self.dist_path, "index.html").open("w") as f:
|
||||||
f.write(TEMPLATE["article"].render(self.context))
|
f.write(TEMPLATE["article"].render(self.context))
|
||||||
|
|
||||||
@ -255,26 +424,43 @@ class Article:
|
|||||||
ac.build()
|
ac.build()
|
||||||
|
|
||||||
|
|
||||||
|
class ContextDict(jinja2.runtime.Context):
|
||||||
|
def resolve_or_missing(self, key: str) -> Any:
|
||||||
|
self.environment.accessed_keys.append(key)
|
||||||
|
return super().resolve_or_missing(key)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# GLOBALS
|
# GLOBALS
|
||||||
logger = logging.getLogger("stsg.build")
|
logger = logging.getLogger("stsg.build")
|
||||||
ARTICLE_LAKE: Dict[str, Article] = {}
|
ARTICLE_LAKE: Dict[str, Article] = {}
|
||||||
|
CROSS_ARTICLE_CONTEXT: Dict[str, Dict[str, Any]] = {}
|
||||||
|
TRANSLATED_CROSS_ARTICLE_CONTEXT: Dict[str, Dict[str, Dict[str, Any]]] = defaultdict(dict)
|
||||||
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...")
|
||||||
|
|
||||||
logger.info("copying static folder...")
|
logger.info("copying static folder...")
|
||||||
shutil.copytree(Path(config.setup.source_directory, "static"), Path(config.setup.dist_directory, "static"), dirs_exist_ok=True)
|
shutil.copytree(Path(config.setup.source_directory, "static"), Path(config.setup.dist_directory, "static"), dirs_exist_ok=True)
|
||||||
|
|
||||||
logger.info("building page tree...")
|
logger.info("building page tree...")
|
||||||
tree = Article(directory=Path(config.setup.source_directory, "articles"), is_root=True)
|
tree = Article(directory=Path(config.setup.source_directory, "articles"), is_root=True)
|
||||||
|
|
||||||
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()
|
||||||
|
62
stsg/definitions.py
Normal file
62
stsg/definitions.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from typing import TypedDict, List, Union
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleConfig(TypedDict):
|
||||||
|
slug: str
|
||||||
|
name: str
|
||||||
|
iso_date: str
|
||||||
|
author: str
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleContext(TypedDict):
|
||||||
|
slug: str
|
||||||
|
name: str
|
||||||
|
url: str
|
||||||
|
link: str
|
||||||
|
date: str
|
||||||
|
year: str
|
||||||
|
iso_date: str
|
||||||
|
author: str
|
||||||
|
|
||||||
|
translations: List[ArticleTranslationContext]
|
||||||
|
children: List[ArticleContext]
|
||||||
|
breadcrumbs: List[ArticleContext]
|
||||||
|
linked: List[ArticleContext]
|
||||||
|
related: List[ArticleContext]
|
||||||
|
|
||||||
|
|
||||||
|
class TypedLanguage(TypedDict):
|
||||||
|
flag: str
|
||||||
|
name: str
|
||||||
|
native_name: str
|
||||||
|
priority: int
|
||||||
|
code: str
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleTranslationContext(TypedDict):
|
||||||
|
slug: str
|
||||||
|
name: str
|
||||||
|
url: str
|
||||||
|
link: str
|
||||||
|
date: str
|
||||||
|
year: str
|
||||||
|
iso_date: str
|
||||||
|
author: str
|
||||||
|
|
||||||
|
language: TypedLanguage
|
||||||
|
article_url: str
|
||||||
|
"""
|
||||||
|
The type Union[ArticleTranslationContext, ArticleContext] exist,
|
||||||
|
because if the article it is linked to doesn't exist in the same languages it uses the overview instead.
|
||||||
|
If you dislike this behavior set:
|
||||||
|
config.fall_back_to_overview_in_translation = False
|
||||||
|
"""
|
||||||
|
children: List[Union[ArticleTranslationContext, ArticleContext]]
|
||||||
|
breadcrumbs: List[Union[ArticleTranslationContext, ArticleContext]]
|
||||||
|
|
||||||
|
# you can't use these within the markdown text itself
|
||||||
|
content: str
|
||||||
|
preview: str
|
||||||
|
linked: List[Union[ArticleTranslationContext, ArticleContext]]
|
||||||
|
related: List[Union[ArticleTranslationContext, ArticleContext]]
|
Loading…
x
Reference in New Issue
Block a user