Compare commits

...

2 Commits

Author SHA1 Message Date
David Soulayrol
b438d9113f Nouvelle chaîne de traitement de statistiques.
Ceci marque le début du support des fichiers propriétés pour la
fourniture de statistiques à https://stats.chatons.org.

Les fichiers sont traitées au moment de la génération du site pour
compléter les informations pouvant être obtenues automatiquement.
2023-03-12 15:10:08 +01:00
David Soulayrol
d0f95cdecf Support du calendrier NextCloud (fix #7). 2023-02-13 09:01:17 +01:00
19 changed files with 3535 additions and 25 deletions

View File

@@ -3,15 +3,15 @@
L'association Ti Nuage offre une palette des services numériques pour ses adhérents et les internautes, et promeut les logiciels libres et un usage éclairé et raisonné d'Internet. L'association Ti Nuage offre une palette des services numériques pour ses adhérents et les internautes, et promeut les logiciels libres et un usage éclairé et raisonné d'Internet.
Le site de l'association doit décrire ses missions et fournir un aperçu des services qu'elle propose, ainsi que de leurs conditions d'usage. Le site de l'association doit décrire ses missions et fournir un aperçu des services qu'elle propose, ainsi que de leurs conditions d'usage.
## Technologie ## Génération du site
Le site est créé à l'aide du logiciel libre [MetalSmith](http://www.metalsmith.io). Il s'agit d'un outil de tansformation de fichiers qui permet en particulier la génération de sites statiques. L'intérêt de tels sites est leur facilité de déploiement, le peu d'usage qu'ils font en terme de ressources et leur robustesse. L'inconvénient est la nécessité de connaître quelques rudiments sur l'usage d'une ligne de comande pour re-générer et re-déployer le site à chaque modification. Le site est créé à l'aide du logiciel libre [MetalSmith](http://www.metalsmith.io). Il s'agit d'un outil de tansformation de fichiers qui permet en particulier la génération de sites statiques. L'intérêt de tels sites est leur facilité de déploiement, le peu d'usage qu'ils font en terme de ressources et leur robustesse. L'inconvénient est la nécessité de connaître quelques rudiments sur l'usage d'une ligne de comande pour re-générer et re-déployer le site à chaque modification.
## Sources ### Préparation des sources
**Metalsmith** requiert la disponibilité de **NodeJS** et **npm**. **Metalsmith** requiert la disponibilité de **NodeJS** et **npm**. L'installation de ces logiciels dépend du système d'exploitation utilisé.
Pour installer les sources du site sur votre machine : Les sources peuvent être téléchargées depuis le dépôt **Gitea** ou bien copiées localement à l'aide de **git** avec la commande suivante.
git clone https://apps.ti-nuage.fr/gitea/ti-nuage/site.git git clone https://apps.ti-nuage.fr/gitea/ti-nuage/site.git
@@ -24,9 +24,17 @@ Le projet s'appuie sur un certain nombre de dépendances, utiles au developpemen
Le script `package.json` comprend une série de commandes utiles au développement et à la génération du site. Les plus utiles sont : Le script `package.json` comprend une série de commandes utiles au développement et à la génération du site. Les plus utiles sont :
- pour générer le site : `npm run build`, - pour générer le site : `npm run build`,
- pour le servir localement et le tester sur son navigateur : `npm run server`. - pour le servir localement et le tester sur son navigateur : `npm run server`.
La liste des dommandes existantes est disponible avec `npm run`. Il est possible d'obtenir davantage d'informations en cas de problème avec la variable d'environnement `DEBUG`. Par exemple : La liste des dommandes existantes est disponible avec `npm run`. Il est possible d'obtenir davantage d'informations en cas de problème avec la variable d'environnement `DEBUG`. Par exemple :
DEBUG=metalsmith* npm run build DEBUG=metalsmith* npm run build
## Structure
Le site de l'association est essentiellement composé de pages statiques. Les billets d'actualité et les comptes-rendus de réunion sont eux-même générés ou assemblés avec le reste des pages du site.
Cependant, afin d'être réactif, l'affichage des événements du calendrier partagé de l'association est effectué depuis le navigateur. Si le navigateur dispose d'un support Javascript actif, alors :
- les événements à venir sont mixés avec les billets récents du blog dans la rubrique Actualité de la page d'accueil ;
- le menu propose une entrée « Calendrier » à la suite de « Actualité ». Cette nouvelle page présente plus largement les événements récents ou à venir.

View File

@@ -95,6 +95,34 @@ abbr {
border-bottom: 1px dotted #373d3f; border-bottom: 1px dotted #373d3f;
} }
.loader-wrapper {
display: grid;
grid-template-columns: 1fr 4fr;
margin-left: auto;
margin-right: auto;
padding: 1em;
width: 400px;
}
.loader-wrapper div {
animation: spin 2s linear infinite;
display: table-column-group;
height: 30px;
width: 30px;
border: 5px solid #fff0;
border-top: 5px solid #555;
border-radius: 50%;
}
.loader-wrapper p {
padding-top: 0.5em;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.meta-published { .meta-published {
color: #999; color: #999;
margin-bottom: 1em; margin-bottom: 1em;
@@ -189,10 +217,37 @@ nav section ul li {
font-size: 1.5em; font-size: 1.5em;
} }
.news-entry h3 {
position: relative;
padding-left: 2em;
padding-top: 0.2em;
}
.news-entry h3::before {
content: "";
position: absolute;
left: 0;
top: 0;
height: 48px;
width: 48px;
}
.blog-entry h3::before {
background: transparent url(/images/uicons/comment.svg) center center no-repeat;
}
.event-entry h3::before {
background: transparent url(/images/uicons/alarm-clock.svg) center center no-repeat;
}
.event-elapsed h3 {
color: grey;
text-decoration: line-through;
}
.services { .services {
border: lightblue 4px solid; border: lightblue 4px solid;
padding: 0.6em; padding: 0.6em;
} }
.services-title { .services-title {
@@ -201,7 +256,7 @@ nav section ul li {
line-height: 48px; line-height: 48px;
} }
.services-title:before { .services-title::before {
content: ""; content: "";
position: absolute; position: absolute;
left: 0; left: 0;

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="300mm"
height="300mm"
viewBox="0 0 300 300"
version="1.1"
id="SVGRoot"
sodipodi:docname="ti-nuage-square.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
<defs
id="defs644" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.7"
inkscape:cx="568.33327"
inkscape:cy="560"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1096"
inkscape:window-x="0"
inkscape:window-y="-16"
inkscape:window-maximized="0" />
<metadata
id="metadata647">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<path
d="m 15.183249,155.10712 v 25.20168 c 0,1.00263 0.300894,1.80546 0.902785,2.40726 0.601789,0.60219 1.780403,0.90279 3.53574,0.90279 h 3.535759 v 5.0403 c -1.655021,0.15072 -3.084377,0.22569 -4.288036,0.22569 -3.009148,0 -5.391379,-0.57724 -7.146715,-1.73036 -1.705195,-1.20368 -2.557776,-3.30995 -2.557776,-6.31909 V 155.10704 H 2.7705559 v -5.26592 H 9.165006 v -10.9833 h 6.018274 v 10.9833 h 7.974253 v 5.26592 z"
fill="#373d3f"
id="path404"
style="stroke-width:2.75187" />
<path
d="m 29.292047,136.60117 h 6.394522 v 7.37215 h -6.394522 z m 0.225696,13.24006 h 6.018337 v 38.81802 h -6.018337 z"
fill="#373d3f"
id="path406"
style="stroke-width:2.75187" />
<path
d="m 24.060226,202.27229 c 4.363285,0 7.723364,1.17861 10.080613,3.53584 2.407338,2.30689 3.610999,5.61714 3.610999,9.93012 v 26.25476 H 31.73351 v -25.65296 c 0,-3.00916 -0.777456,-5.19081 -2.331933,-6.54478 -1.55483,-1.40421 -3.736462,-2.10648 -6.54499,-2.10648 -1.253833,0 -2.532709,0.25065 -3.836684,0.75198 -1.253803,0.45139 -2.382233,1.17862 -3.38526,2.18167 -1.003049,1.00262 -1.830577,2.28193 -2.482568,3.83673 -0.652024,1.5545 -0.977985,3.41031 -0.977985,5.5667 v 21.96682 H 6.155784 v -38.81783 h 5.717392 v 5.7926 c 1.303975,-1.95597 2.984072,-3.5609 5.040323,-4.81461 2.056251,-1.25402 4.438482,-1.88086 7.146705,-1.88086 z"
fill="#373d3f"
id="path408"
style="stroke-width:2.75187" />
<path
d="m 76.908989,203.17518 v 38.81783 h -5.717554 v -5.7924 c -1.303832,1.95576 -2.983846,3.5606 -5.040323,4.8146 -2.056186,1.25382 -4.438485,1.88067 -7.146597,1.88067 -4.363363,0 -7.748435,-1.15364 -10.155763,-3.46053 -2.35725,-2.35723 -3.535875,-5.69245 -3.535875,-10.00543 v -26.25474 h 6.018325 v 25.65285 c 0,3.00914 0.77746,5.21588 2.332212,6.62019 1.554533,1.35418 3.736219,2.03117 6.544768,2.03117 1.253744,0 2.507509,-0.22571 3.761524,-0.67711 1.303843,-0.5013 2.457422,-1.254 3.460486,-2.25706 1.003043,-1.00263 1.830538,-2.28182 2.482444,-3.83662 0.651927,-1.55462 0.977989,-3.41041 0.977989,-5.5668 v -21.96672 z"
fill="#373d3f"
id="path410"
style="stroke-width:2.75187" />
<path
d="m 119.86756,237.10318 v 4.88983 c -0.95292,0.15089 -2.05646,0.22568 -3.31022,0.22568 -2.15663,0 -3.76124,-0.45139 -4.81465,-1.35386 -1.00306,-0.95272 -1.57986,-2.35723 -1.73012,-4.21282 -2.70838,4.16249 -6.92121,6.24399 -12.638497,6.24399 -4.262914,0 -7.623213,-0.97769 -10.080624,-2.93405 -2.407343,-1.95576 -3.61101,-4.7643 -3.61101,-8.42567 0,-1.50447 0.250659,-2.93373 0.752384,-4.28802 0.501413,-1.35418 1.303833,-2.55755 2.407323,-3.61095 1.153589,-1.05317 2.63299,-1.93068 4.438495,-2.63304 1.855593,-0.70205 4.112392,-1.20357 6.770422,-1.50437 l 5.567027,-0.60221 c 1.95602,-0.25065 3.46046,-0.75197 4.51361,-1.50468 1.05342,-0.80293 1.57984,-2.08128 1.57984,-3.83664 0,-2.05621 -0.62685,-3.58575 -1.88062,-4.5888 -1.25376,-1.00262 -3.53587,-1.50468 -6.84582,-1.50468 -3.310227,0 -5.717277,0.55228 -7.221993,1.65498 -1.504456,1.10351 -2.357266,3.10961 -2.557858,6.01829 h -6.018062 c 0.09982,-1.90564 0.451282,-3.636 1.053142,-5.19082 0.601786,-1.5545 1.504434,-2.90866 2.708101,-4.06232 1.253754,-1.15324 2.858636,-2.03117 4.814677,-2.63295 1.956016,-0.65214 4.363366,-0.97769 7.221993,-0.97769 2.70811,0 4.99025,0.27564 6.84582,0.82792 1.85559,0.55124 3.36031,1.3291 4.51361,2.33216 1.15357,1.00263 1.98109,2.20672 2.48274,3.61103 0.50141,1.35418 0.75208,2.85866 0.75208,4.51362 v 21.66593 c 0,1.25382 0.70225,1.88067 2.10654,1.88067 z m -22.192707,0.6771 c 1.45465,0 2.883967,-0.22571 4.288247,-0.6771 1.45437,-0.45139 2.75819,-1.12828 3.91178,-2.03116 1.15357,-0.90278 2.08123,-2.05623 2.78351,-3.46044 0.70195,-1.40431 1.05314,-3.05927 1.05314,-4.96521 v -4.58903 c -1.15358,0.90278 -2.85864,1.45466 -5.11544,1.65529 l -5.642147,0.67708 c -3.51085,0.40146 -5.892861,1.20368 -7.146887,2.40736 -1.253755,1.15365 -1.880637,2.68308 -1.880637,4.58903 0,1.90572 0.601807,3.46044 1.805505,4.66412 1.253754,1.15364 3.234829,1.73017 5.942929,1.73017 z"
fill="#373d3f"
id="path412"
style="stroke-width:2.75187" />
<path
d="m 159.64224,203.17518 v 37.31335 c 0,2.50753 -0.37621,4.81472 -1.12855,6.92098 -0.70204,2.10658 -1.80549,3.91204 -3.30994,5.41649 -1.50444,1.50438 -3.38535,2.68309 -5.64215,3.53586 -2.25681,0.85285 -4.91483,1.27877 -7.97436,1.27877 -5.01502,0 -8.92706,-1.15355 -11.73563,-3.46044 -2.80855,-2.25685 -4.41343,-5.26601 -4.81465,-9.02754 h 6.01833 c 0.40127,2.45749 1.47968,4.28803 3.23482,5.49159 1.80549,1.25381 4.23787,1.88099 7.29713,1.88099 3.91206,0 6.89617,-1.00265 8.95237,-3.00914 2.05619,-1.95608 3.08429,-4.96523 3.08429,-9.02756 v -3.76154 c -1.45436,1.95608 -3.2849,3.48581 -5.49161,4.58902 -2.20675,1.05319 -4.71424,1.57987 -7.5228,1.57987 -2.70838,0 -5.19084,-0.47635 -7.44766,-1.42936 -2.20671,-0.95272 -4.11265,-2.3071 -5.71755,-4.06223 -1.55452,-1.75544 -2.7582,-3.88707 -3.61099,-6.3946 -0.85256,-2.50773 -1.27881,-5.31635 -1.27881,-8.42565 0,-3.10931 0.42623,-5.91782 1.27881,-8.42566 0.85275,-2.50752 2.05647,-4.63905 3.61099,-6.39429 1.6049,-1.75533 3.51084,-3.10951 5.71755,-4.06252 2.25682,-0.95271 4.73928,-1.42928 7.44766,-1.42928 2.90873,0 5.49163,0.57724 7.74844,1.73037 2.25681,1.10322 4.11238,2.70805 5.56702,4.81463 v -5.64211 z m -18.28065,34.30421 c 3.76152,0 6.7707,-1.30417 9.0275,-3.91184 2.30715,-2.60789 3.46046,-6.26927 3.46046,-10.98351 0,-4.71425 -1.15331,-8.37532 -3.46046,-10.98321 -2.2568,-2.60798 -5.26598,-3.91204 -9.0275,-3.91204 -3.76126,0 -6.79547,1.35419 -9.10264,4.06223 -2.25679,2.65833 -3.38535,6.26905 -3.38535,10.83302 0,4.56396 1.12856,8.20006 3.38535,10.9081 2.30717,2.65803 5.34138,3.98725 9.10264,3.98725 z"
fill="#373d3f"
id="path414"
style="stroke-width:2.75187" />
<path
d="m 202.51441,224.6906 h -29.48957 c 0.30078,4.41316 1.50445,7.64799 3.61072,9.70443 2.15665,2.0061 4.94017,3.00926 8.35056,3.00926 2.7582,0 4.99023,-0.65214 6.69529,-1.95607 1.70506,-1.30415 2.90871,-3.05927 3.611,-5.266 h 6.24399 c -0.4513,1.80557 -1.15359,3.48549 -2.10656,5.04031 -0.90258,1.55459 -2.05619,2.90877 -3.46048,4.06232 -1.40427,1.10321 -3.0342,1.98103 -4.88979,2.63295 -1.85558,0.65214 -3.93709,0.97769 -6.24399,0.97769 -2.65803,0 -5.09041,-0.4514 -7.29712,-1.35418 -2.20672,-0.95272 -4.11267,-2.3069 -5.71728,-4.06235 -1.60489,-1.75544 -2.85892,-3.88676 -3.76152,-6.39449 -0.85257,-2.50751 -1.2788,-5.34109 -1.2788,-8.50085 0,-3.05927 0.42622,-5.81767 1.2788,-8.27515 0.85254,-2.50751 2.05619,-4.63906 3.611,-6.3945 1.60488,-1.80546 3.4855,-3.18438 5.64215,-4.13741 2.20671,-1.00265 4.66412,-1.50469 7.37225,-1.50469 2.90901,0 5.46659,0.55125 7.6733,1.65518 2.20673,1.05318 4.06257,2.50751 5.56704,4.36312 1.50444,1.80547 2.63297,3.9371 3.38533,6.3945 0.80243,2.45738 1.20368,5.09044 1.20368,7.89895 z m -17.82935,-16.92651 c -3.05925,0 -5.61711,0.95272 -7.67331,2.85866 -2.0562,1.90572 -3.33499,4.78933 -3.83664,8.65135 h 22.86966 c -0.15052,-1.60484 -0.50164,-3.10962 -1.05342,-4.51394 -0.55145,-1.40419 -1.30383,-2.60787 -2.2568,-3.61092 -0.95271,-1.05318 -2.10628,-1.88067 -3.46048,-2.48246 -1.30384,-0.6022 -2.83361,-0.90278 -4.58901,-0.90278 z"
fill="#373d3f"
id="path416"
style="stroke-width:2.75187" />
<path
d="m 65.948861,144.41671 c 1.927951,-7.5214 5.646,-14.46497 10.836848,-20.23915 5.190848,-5.7743 11.700935,-10.20783 18.975234,-12.92283 7.274557,-2.71492 15.097287,-3.63102 22.802237,-2.66978 7.70468,0.96103 15.0629,3.77078 21.44723,8.18976 2.60245,-13.84954 9.09493,-26.671909 18.71793,-36.966592 9.62274,-10.294872 21.97834,-17.636459 35.62126,-21.166058 13.64268,-3.529609 28.00854,-3.101305 41.41672,1.234886 13.40849,4.336285 25.30453,12.40103 34.29708,23.251 8.99147,10.849761 14.70983,24.035954 16.48205,38.015934 1.77218,13.98027 -0.47335,28.17606 -6.4724,40.92655 -6.00073,12.75083 -15.50788,23.52933 -27.40971,31.07467 -11.90182,7.54512 -25.7063,11.54526 -39.79834,11.53206"
stroke="#373d3f"
stroke-width="10.4008"
stroke-linecap="round"
stroke-linejoin="round"
id="path418"
style="opacity:1;stroke-width:10.401;stroke-miterlimit:4;stroke-dasharray:none;stroke-linecap:round;stroke:#373d3f;stroke-opacity:0.80000001;fill:none" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24" width="24" height="24"><path d="M13,2.051V1a1,1,0,0,0-2,0V2.051A10.98,10.98,0,0,0,3.681,20.176,3.024,3.024,0,0,0,2,23a1,1,0,0,0,2,0,1.2,1.2,0,0,1,1.11-1.229.932.932,0,0,0,.2-.062,10.924,10.924,0,0,0,13.39,0,1.033,1.033,0,0,0,.182.064A1.2,1.2,0,0,1,20,23a1,1,0,0,0,2,0,3.024,3.024,0,0,0-1.681-2.824A10.98,10.98,0,0,0,13,2.051ZM3,13a9,9,0,1,1,9,9A9.011,9.011,0,0,1,3,13Z"/><path d="M19.215,0a1,1,0,0,0,0,2A2.59,2.59,0,0,1,22,4.5a1,1,0,0,0,2,0A4.6,4.6,0,0,0,19.215,0Z"/><path d="M2,4.5A2.59,2.59,0,0,1,4.785,2a1,1,0,0,0,0-2A4.6,4.6,0,0,0,0,4.5a1,1,0,0,0,2,0Z"/><path d="M13,11.586V7a1,1,0,0,0-2,0v5a1,1,0,0,0,.293.707l3,3a1,1,0,0,0,1.414-1.414Z"/></svg>

After

Width:  |  Height:  |  Size: 783 B

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><g id="_01_align_center" data-name="01 align center"><path d="M24,24H12.018A12,12,0,1,1,24,11.246l0,.063ZM12.018,2a10,10,0,1,0,0,20H22V11.341A10.018,10.018,0,0,0,12.018,2Z"/><rect x="7" y="7" width="6" height="2"/><rect x="7" y="11" width="10" height="2"/><rect x="7" y="15" width="10" height="2"/></g></svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,45 @@
# [File]
file.class = organization
file.protocol = ChatonsInfos-0.5
file.datetime =
file.generator =
# [Organisation]
organization.name = Ti Nuage
organization.country.code = FR
organization.country.name = France
organization.description = Hébergeur associatif, transparent, éthique, militant et solidaire dans le Trégor.
organization.type = ASSOCIATION
organization.website = https://www.ti-nuage.fr
organization.chatrooms.xmpp = ti-nuage@muc.ti-nuage.fr
organization.contact.url = https://www.ti-nuage.fr/mentions_legales.html
organization.contact.email = contact@ti-nuage.fr
organization.funding.helloasso = https://www.helloasso.com/associations/ti-nuage/adhesions/adhesion-1
organization.geolocation.address = Mairie, 11 places aux Chevaux, 22420 Le Vieux Marché
organization.geolocation.latitude = 48.60736
organization.geolocation.longitude = -3.45054
organization.guide.technical = https://wiki.ti-nuage.fr/fr/Administration/Infrastructure
organization.guide.user = https://wiki.ti-nuage.fr/
organization.legal.url = https://www.ti-nuage.fr/mentions_legales.html
organization.logo = https://www.ti-nuage.fr/images/logo-tinuage-square.svg
organization.memberof.chatons.startdate = 21/12/2022
organization.memberof.chatons.status.level = ACTIVE
organization.memberof.chatons.status.description =
organization.socialnetworks.mastodon = https://hostux.social/@tinuage
organization.socialnetworks.peertube = https://video.tedomum.net/a/tinunu/video-channels
organization.startdate = 15/10/2021
organization.status.description =
organization.status.level = ACTIVE
# [Subs]
subs.foo1 =
# [Metrics]

View File

@@ -0,0 +1,3 @@
Contact: mailto:contact@ti-nuage.fr
Expires: Sun, 31 Dec 2023 23:59 +0200
Preferred-Languages: fr, en

5
content/calendrier.html Normal file
View File

@@ -0,0 +1,5 @@
---
layout: calendar.njk
permalink: false
title: Événements à venir
---

View File

@@ -0,0 +1,8 @@
<?php
const ICS_URL = 'https://cloud.ti-nuage.fr/remote.php/dav/public-calendars/gqm9WNa9eoXGEiNc?export';
$ics = file_get_contents(ICS_URL);
echo $ics;
?>

153
content/js/calendar.js Normal file
View File

@@ -0,0 +1,153 @@
const http = require('http')
const ical = require('ical')
const MONTH = 30 * 24 * 60 * 60 * 1000
const Accumulator = {
init: function (pastLimit, futureLimit) {
this.entries = {}
this.futureLimit = futureLimit
this.pastLimit = pastLimit
},
add: function (time, element) {
if (!this.entries.hasOwnProperty(time)) {
this.entries[time] = []
}
this.entries[time].push(element)
},
offer: function (time, element) {
if (time >= this.pastLimit && time < this.futureLimit) {
this.add(time, element)
}
},
offerEvent: function (event) {
const content = document.createElement('p')
const element = document.createElement('div')
const formatDate = Intl.DateTimeFormat('fr', { dateStyle: 'long', timeStyle: 'short' }).format
const header = document.createElement('h3')
content.innerHTML = event.location
header.textContent = formatDate(event.date) + ' | ' + event.summary
element.className = 'event-entry news-entry'
element.setAttribute('entry-time', event.date.getTime())
element.appendChild(header)
element.appendChild(content)
this.offer(event.date.getTime(), element)
},
iterate: function (cb) {
Object
.keys(this.entries)
.sort(function (a, b) { return a < b })
.forEach(time => { cb(time, this.entries[time]) })
}
}
function createLoader (target) {
const message = document.createElement('p')
const spinner = document.createElement('div')
const wrapper = document.createElement('div')
wrapper.className = 'loader-wrapper'
message.innerHTML = 'Chargement du calendrier...'
wrapper.appendChild(spinner)
wrapper.appendChild(message)
target.insertBefore(wrapper, target.firstChild)
}
function loadEvents (cb) {
const events = []
http.get({
host: 'www.ti-nuage.fr',
path: '/donnees/calendrier.php'
}, function (response) {
let str = ''
response.on('data', (chunk) => {
str += chunk
})
response.on('end', () => {
/* Extract the events. */
Object.values(ical.parseICS(str)).forEach(event => {
if (event.type === 'VEVENT') {
events.push({
date: event.start,
location: event.location,
summary: event.summary
})
}
})
cb(events)
})
response.on('error', (e) => {
console.error(`Error on calendar response: ${e.message}`)
cb(events)
})
}).on('error', (e) => {
console.error(`Could not fetch calendar: ${e.message}`)
cb(events)
})
}
module.exports = {
fill: function (target) {
const now = Date.now()
const accumulator = Object.create(Accumulator)
accumulator.init(now - 1 * MONTH, now + 3 * MONTH)
createLoader(target)
loadEvents(events => {
events.forEach(event => { accumulator.offerEvent(event) })
target.innerHTML = ''
accumulator.iterate((time, elements) => {
elements.forEach(e => {
if (time < now) {
e.className = e.className + ' event-elapsed'
}
target.appendChild(e)
})
})
})
},
mergeWithNews: function (target) {
const now = Date.now()
const accumulator = Object.create(Accumulator)
const blogElements = target.getElementsByClassName('blog-entry')
accumulator.init(now, now + 1 * MONTH)
createLoader(target)
for (let i = 0; i < blogElements.length; ++i) {
const item = blogElements.item(i)
accumulator.add(item.getAttribute('entry-time'), item)
}
loadEvents(events => {
events.forEach(event => { accumulator.offerEvent(event) })
target.innerHTML = ''
accumulator.iterate((time, elements) => {
elements.forEach(e => { target.appendChild(e) })
})
})
}
}

View File

@@ -4,7 +4,7 @@
<h1 class="mt128">{{ title }}</h1> <h1 class="mt128">{{ title }}</h1>
{% if date %} {% if date %}
<div class="meta-published"> <div class="meta-published">
Publié le : {{ moment(date).format('LL') }} Publié le : {{ date_format(date) }}
</div> </div>
{% endif %} {% endif %}

View File

@@ -17,7 +17,7 @@
<nav class="Container three-cols"> <nav class="Container three-cols">
<section> <section>
<h2>Association</h2> <h2>Association</h2>
<ul> <ul id="menu-association">
<li><a href="/association.html">Découvrir l'association</a></li> <li><a href="/association.html">Découvrir l'association</a></li>
<li><a href="/blog">Actualités</a></li> <li><a href="/blog">Actualités</a></li>
</ul> </ul>
@@ -45,7 +45,7 @@
<footer class="footer mt128"> <footer class="footer mt128">
<div class="Container"> <div class="Container">
<p class="tad">Page générée le {{ moment(article.date).format('LL') }}.</p> <p class="tad">Page générée le {{ date_format(article.date) }}.</p>
<hr> <hr>
<p class="tac"> <p class="tac">
<a href="/"><img class="logo-small" src="/images/logo.svg" alt=""></a> <a href="/"><img class="logo-small" src="/images/logo.svg" alt=""></a>
@@ -55,5 +55,16 @@
</p> </p>
</div> </div>
</footer> </footer>
<script type='text/javascript'>
const menu = document.getElementById('menu-association')
const entry = document.createElement('li')
const link = document.createElement('a')
link.href = '/calendrier.html'
link.textContent = 'Calendrier'
entry.appendChild(link)
menu.appendChild(entry)
</script>
</body> </body>
</html> </html>

View File

@@ -5,7 +5,7 @@
{% block header %}{{ header | safe }}{% endblock %} {% block header %}{{ header | safe }}{% endblock %}
<article> <article>
{% for article in collections["archives"] %} {% for article in collections["archives"] %}
<h2><a href="/{{ article.path }}">{{ moment(article.date).format('LL') }} | {{ article.title }}</a></h2> <h2><a href="/{{ article.path }}">{{ date_format(article.date) }} | {{ article.title }}</a></h2>
{{ article.excerpt | safe }} {{ article.excerpt | safe }}
<p><a href="/{{ article.path }}">Lire la suite...</a></p> <p><a href="/{{ article.path }}">Lire la suite...</a></p>
{% else %} {% else %}

19
layouts/calendar.njk Normal file
View File

@@ -0,0 +1,19 @@
{% extends "base.njk" %}
{% block head %}
<script type='text/javascript' src="/js/calendar.js"></script>
{% endblock %}
{% block article %}
<h1 class="mt128">Calendrier</h1>
<div id="calendar" />
<noscript>
Le téléchargement des données du calendrier requiert JavaScript.
</noscript>
<script type='text/javascript'>
window.onload = function(event) {
Calendar.fill(document.getElementById('calendar'))
}
</script>
{% endblock %}

View File

@@ -1,5 +1,9 @@
{% extends "base.njk" %} {% extends "base.njk" %}
{% block head %}
<script type='text/javascript' src="/js/calendar.js"></script>
{% endblock %}
{% block article %} {% block article %}
{% block header %}{{ header | safe }}{% endblock %} {% block header %}{{ header | safe }}{% endblock %}
<article> <article>
@@ -7,15 +11,26 @@
<section class="Services"> <section class="Services">
<h2 class="tac mt128 mb64">Actualités</h2> <h2 class="tac mt128 mb64">Actualités</h2>
{% for article in collections["news"] %} <div id="calendar">
<h3><a href="{{ article.path }}">{{ moment(article.date).format('LL') }} | {{article.title }}</a></h3> {% for article in collections["news"] %}
{{ article.excerpt | safe }} <div class="blog-entry news-entry" entry-time="{{article.date.getTime()}}">
<p><a href="{{ article.path }}">Lire la suite...</a></p> <h3><a href="{{ article.path }}">{{ date_format(article.date) }} | {{article.title }}</a></h3>
{% else %} {{ article.excerpt | safe }}
{% endfor %} <p><a href="{{ article.path }}">Lire la suite...</a></p>
</div>
{% else %}
{% endfor %}
</div>
<div class="tac mt32"> <div class="tac mt32">
<a class="button" href="/blog">Voir les archives</a> <a class="button" href="/blog">Voir les archives</a>
</div> </div>
</section> </section>
</article> </article>
<script type='text/javascript'>
window.onload = function(event) {
Calendar.mergeWithNews(document.getElementById('calendar'))
}
</script>
{% endblock %} {% endblock %}

2961
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,14 +11,16 @@
"clean": "rimraf dist", "clean": "rimraf dist",
"dev": "npm run build && nodemon scripts/run.js serve", "dev": "npm run build && nodemon scripts/run.js serve",
"server": "npm run build && http-server dist", "server": "npm run build && http-server dist",
"deploy": "npm run build:prod && cd dist && rsync -v -rlptz --relative -e \"ssh -p $SERVER_PORT\" * $SERVER_PATH", "deploy": "npm run build:prod && cd dist && rsync -v -rlptz --relative -e \"ssh -p $SERVER_PORT\" ./ $SERVER_PATH",
"lint": "npm run lint:js && npm run lint:css", "lint": "npm run lint:js && npm run lint:css",
"lint:js": "eslint scripts", "lint:js": "eslint scripts",
"lint:css": "stylelint assets/**/*.css", "lint:css": "stylelint assets/**/*.css",
"test": "npm run lint && npm run build" "test": "npm run lint && npm run build"
}, },
"author": "Ti Nuage <contact@ti-nuage.fr>", "author": "Ti Nuage <contact@ti-nuage.fr>",
"dependencies": {}, "dependencies": {
"ical": "^0.8.0"
},
"devDependencies": { "devDependencies": {
"@metalsmith/collections": "^1.2.1", "@metalsmith/collections": "^1.2.1",
"@metalsmith/excerpts": "^1.5.1", "@metalsmith/excerpts": "^1.5.1",
@@ -41,11 +43,11 @@
"jstransformer-nunjucks": "^1.0.0", "jstransformer-nunjucks": "^1.0.0",
"metalsmith": "^2.4.0", "metalsmith": "^2.4.0",
"metalsmith-assets": "^0.1.0", "metalsmith-assets": "^0.1.0",
"metalsmith-browserify": "^1.1.0",
"metalsmith-clean-css": "^6.1.3", "metalsmith-clean-css": "^6.1.3",
"metalsmith-groff": "^0.4.0", "metalsmith-groff": "^0.4.0",
"metalsmith-rename": "^1.0.0", "metalsmith-rename": "^1.0.0",
"metalsmith-sitemap": "^1.2.2", "metalsmith-sitemap": "^1.2.2",
"moment": "^2.29.1",
"nodemon": "^2.0.12", "nodemon": "^2.0.12",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"slug": "^5.1.0", "slug": "^5.1.0",

View File

@@ -0,0 +1,111 @@
// const { Buffer } = require('node:buffer')
const debug = require('debug')('metalsmith-tinuage-chatons')
const config = require('./config.js')
const fs = require('node:fs')
const path = require('path')
module.exports = plugin
/**
* Un plugin pour Metalsmith qui traite des fichiers de propriétés
* pour StatoolInfos.
*
* @return {Function}
*/
function plugin () {
const Filler = {
init: function (properties) {
this.handlers = {
'file.datetime': this.datetime,
'file.generator': this.generator
}
this.properties = properties
},
complete: function () {
Object.entries(this.properties).forEach(([key, value]) => {
if (value.length === 0) {
if (Object.keys(this.handlers).includes(key)) {
this.properties[key] = this.handlers[key]()
}
}
})
},
export: function () {
return Object
.entries(this.properties)
.filter(([key, value]) => !key.startsWith('meta'))
.map(([key, value]) => key + ' = ' + value)
.reduce((acc, value) => acc + value + '\n', '')
},
datetime: function () {
/* Statools n'attend pas de l'ISO8-601,
* mais le strict format YYYY-MM-DDThh:mm:ss
*/
const d = new Date()
const pad = function (value) { return value.toString().padStart(2, '0') }
const date = [d.getFullYear(), pad(d.getMonth() + 1), pad(d.getDate())]
const time = [d.getHours(), d.getMinutes(), d.getSeconds()]
return date.join('-') + 'T' + time.join(':')
},
generator: function () {
return 'Metalsmith'
}
}
function loadProperties (root, filenames) {
const promises = filenames
.filter(f => { return f.indexOf('.properties') !== -1 })
.map(function (f) {
debug('Loading ' + f)
return fs.promises
.readFile(path.join(root, f), 'utf8')
.then(data => {
const properties = Object.fromEntries(data
.toString()
.split(/(?:\r\n|\r|\n)/g)
.filter(line => line.length > 1 && line[0] !== '#')
.map(line => line.split(/\s*=\s*/, 2)))
properties['meta.filename'] = f
return properties
})
})
return Promise.all(promises)
}
return function (files, metalsmith, done) {
const directory = path.join('.well-known', 'chatonsinfos')
const root = path.join(config.paths.projectRoot, config.paths.metalsmithSource, directory)
debug('Looking for properties files in ' + root)
fs.readdir(root, (err, filenames) => {
if (err) {
console.error('%s: %s', root, err)
} else {
loadProperties(root, filenames)
.then(function (sets) {
sets.forEach(properties => {
const data = files[path.join(directory, properties['meta.filename'])]
const filler = Object.create(Filler)
filler.init(properties)
filler.complete()
data.contents = Buffer.from(filler.export())
})
done()
})
.catch(function (err) {
console.log(err)
})
}
})
}
}

View File

@@ -1,6 +1,8 @@
/* This is the actual metalsmith configuration script. */ /* This is the actual metalsmith configuration script. */
const assets = require('metalsmith-assets') const assets = require('metalsmith-assets')
const browserify = require('metalsmith-browserify')
const cleanCSS = require('metalsmith-clean-css') const cleanCSS = require('metalsmith-clean-css')
const chatons = require('./metalsmith-tinuage-chatons')
const config = require('./config.js') const config = require('./config.js')
const collections = require('@metalsmith/collections') const collections = require('@metalsmith/collections')
const excerpts = require('@metalsmith/excerpts') const excerpts = require('@metalsmith/excerpts')
@@ -9,7 +11,6 @@ const layouts = require('@metalsmith/layouts')
const Metalsmith = require('metalsmith') const Metalsmith = require('metalsmith')
const markdown = require('@metalsmith/markdown') const markdown = require('@metalsmith/markdown')
const marked = require('marked') const marked = require('marked')
const moment = require('moment')
const permalinks = require('@metalsmith/permalinks') const permalinks = require('@metalsmith/permalinks')
const rename = require('metalsmith-rename') const rename = require('metalsmith-rename')
const services = require('./metalsmith-tinuage-services') const services = require('./metalsmith-tinuage-services')
@@ -35,12 +36,16 @@ markdownRenderer.link = function (href, title, text) {
return out return out
} }
moment.locale('fr')
module.exports = new Metalsmith(config.paths.projectRoot) module.exports = new Metalsmith(config.paths.projectRoot)
.clean(__PROD__) .clean(__PROD__)
.metadata({ .metadata({
moment: moment, date_format: function (date, withHours) {
const options = { dateStyle: 'long' }
if (withHours) {
options.push('timeStyle', 'short')
}
return Intl.DateTimeFormat('fr', options).format(date)
},
production: __PROD__, production: __PROD__,
remove_diatrics: function (e) { return slug(e, { mode: 'rfc3986' }) }, remove_diatrics: function (e) { return slug(e, { mode: 'rfc3986' }) },
site: { site: {
@@ -55,6 +60,14 @@ module.exports = new Metalsmith(config.paths.projectRoot)
rebase: true rebase: true
} }
})) }))
.use(browserify({
browserifyOptions: {
standalone: 'Calendar'
},
entries: [
'js/calendar.js'
]
}))
.use(assets({ .use(assets({
source: './assets/' + (process.env.NODE_ENV || 'dev'), source: './assets/' + (process.env.NODE_ENV || 'dev'),
destination: './' destination: './'
@@ -71,6 +84,7 @@ module.exports = new Metalsmith(config.paths.projectRoot)
smartypants: true, smartypants: true,
xhtml: true xhtml: true
})) }))
.use(chatons())
.use(services()) .use(services())
.use(excerpts()) .use(excerpts())
.use(permalinks({ .use(permalinks({