Support du calendrier sur la page d'accueil.
This commit is contained in:
18
README.md
18
README.md
@@ -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.
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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 :
|
||||
|
||||
- pour générer le site : `npm run build`,
|
||||
|
||||
- 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 :
|
||||
|
||||
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.
|
||||
|
||||
@@ -95,6 +95,34 @@ abbr {
|
||||
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 {
|
||||
color: #999;
|
||||
margin-bottom: 1em;
|
||||
|
||||
5
content/calendrier.html
Normal file
5
content/calendrier.html
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
layout: calendar.njk
|
||||
permalink: false
|
||||
title: Événements à venir
|
||||
---
|
||||
8
content/donnees/calendrier.php
Normal file
8
content/donnees/calendrier.php
Normal 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;
|
||||
|
||||
?>
|
||||
122
content/js/calendrier.js
Normal file
122
content/js/calendrier.js
Normal file
@@ -0,0 +1,122 @@
|
||||
|
||||
const http = require('http')
|
||||
const ical = require('ical')
|
||||
|
||||
function createEventEntry (event) {
|
||||
const content = document.createElement('p')
|
||||
const entry = 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
|
||||
|
||||
entry.className = 'event-entry'
|
||||
entry.setAttribute('entry-time', event.date.getTime())
|
||||
entry.appendChild(header)
|
||||
entry.appendChild(content)
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
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 sortEvents (events) {
|
||||
const sorted = []
|
||||
|
||||
/* First extract the events. */
|
||||
Object.values(events).forEach(event => {
|
||||
if (event.type === 'VEVENT') {
|
||||
sorted.push({
|
||||
date: event.start,
|
||||
location: event.location,
|
||||
summary: event.summary
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/* Then sort them out. */
|
||||
// TODO: Useless here. Should be done on callbacks below
|
||||
return sorted.sort((a, b) => { return a.date < b.date })
|
||||
}
|
||||
|
||||
function loadEvents (cb) {
|
||||
http.request({
|
||||
host: 'www.ti-nuage.fr',
|
||||
path: '/donnees/calendrier.php'
|
||||
}, function (response) {
|
||||
let str = ''
|
||||
|
||||
response.on('data', function (chunk) {
|
||||
str += chunk
|
||||
})
|
||||
|
||||
response.on('end', function () {
|
||||
const events = sortEvents(ical.parseICS(str))
|
||||
cb(events)
|
||||
})
|
||||
}).end()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
fill: function (target) {
|
||||
loadEvents(events => {
|
||||
events.forEach(event => {
|
||||
target.appendChild(createEventEntry(event))
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
mergeWithNews: function (target) {
|
||||
const blogElements = target.getElementsByClassName('blog-entry')
|
||||
const entries = {}
|
||||
|
||||
const accumulate = function (index, item) {
|
||||
if (!entries.hasOwnProperty(index)) {
|
||||
entries[index] = []
|
||||
}
|
||||
entries[index].push(item)
|
||||
}
|
||||
|
||||
for (let i = 0; i < blogElements.length; ++i) {
|
||||
const item = blogElements.item(i)
|
||||
accumulate(item.getAttribute('entry-time'), item)
|
||||
}
|
||||
|
||||
createLoader(target)
|
||||
|
||||
loadEvents(events => {
|
||||
const now = Date.now()
|
||||
const limitTime = now + (30 * 24 * 60 * 60 * 1000)
|
||||
|
||||
target.innerHTML = ''
|
||||
|
||||
/* Build calendar entries, keeping only the ones for the next 30 days */
|
||||
events.forEach(event => {
|
||||
const eventTime = event.date.getTime()
|
||||
|
||||
console.log(event)
|
||||
if (eventTime >= now && eventTime < limitTime) {
|
||||
accumulate(eventTime, createEventEntry(event))
|
||||
}
|
||||
})
|
||||
|
||||
Object.keys(entries).sort(function (a, b) { return a < b }).forEach(time => {
|
||||
entries[time].forEach(entry => { target.appendChild(entry) })
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<h1 class="mt128">{{ title }}</h1>
|
||||
{% if date %}
|
||||
<div class="meta-published">
|
||||
Publié le : {{ moment(date).format('LL') }}
|
||||
Publié le : {{ date_format(date) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
<footer class="footer mt128">
|
||||
<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>
|
||||
<p class="tac">
|
||||
<a href="/"><img class="logo-small" src="/images/logo.svg" alt=""></a>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{% block header %}{{ header | safe }}{% endblock %}
|
||||
<article>
|
||||
{% 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 }}
|
||||
<p><a href="/{{ article.path }}">Lire la suite...</a></p>
|
||||
{% else %}
|
||||
|
||||
17
layouts/calendar.njk
Normal file
17
layouts/calendar.njk
Normal file
@@ -0,0 +1,17 @@
|
||||
{% extends "base.njk" %}
|
||||
|
||||
{% block head %}
|
||||
<script type='text/javascript' src="/js/calendrier.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block article %}
|
||||
<h1 class="mt128">Calendrier</h1>
|
||||
<div id="calendar" />
|
||||
|
||||
<script type='text/javascript'>
|
||||
window.onload = function(event) {
|
||||
Calendar.fill(document.getElementById('calendar'))
|
||||
}
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,5 +1,9 @@
|
||||
{% extends "base.njk" %}
|
||||
|
||||
{% block head %}
|
||||
<script type='text/javascript' src="/js/calendrier.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block article %}
|
||||
{% block header %}{{ header | safe }}{% endblock %}
|
||||
<article>
|
||||
@@ -7,15 +11,26 @@
|
||||
|
||||
<section class="Services">
|
||||
<h2 class="tac mt128 mb64">Actualités</h2>
|
||||
{% for article in collections["news"] %}
|
||||
<h3><a href="{{ article.path }}">{{ moment(article.date).format('LL') }} | {{article.title }}</a></h3>
|
||||
{{ article.excerpt | safe }}
|
||||
<p><a href="{{ article.path }}">Lire la suite...</a></p>
|
||||
{% else %}
|
||||
{% endfor %}
|
||||
<div id="calendar">
|
||||
{% for article in collections["news"] %}
|
||||
<div class="blog-entry" entry-time="{{article.date.getTime()}}">
|
||||
<h3><a href="{{ article.path }}">{{ date_format(article.date) }} | {{article.title }}</a></h3>
|
||||
{{ article.excerpt | safe }}
|
||||
<p><a href="{{ article.path }}">Lire la suite...</a></p>
|
||||
</div>
|
||||
{% else %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="tac mt32">
|
||||
<a class="button" href="/blog">Voir les archives</a>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
<script type='text/javascript'>
|
||||
window.onload = function(event) {
|
||||
Calendar.mergeWithNews(document.getElementById('calendar'))
|
||||
}
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
2961
package-lock.json
generated
2961
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,9 @@
|
||||
"test": "npm run lint && npm run build"
|
||||
},
|
||||
"author": "Ti Nuage <contact@ti-nuage.fr>",
|
||||
"dependencies": {},
|
||||
"dependencies": {
|
||||
"ical": "^0.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@metalsmith/collections": "^1.2.1",
|
||||
"@metalsmith/excerpts": "^1.5.1",
|
||||
@@ -41,11 +43,11 @@
|
||||
"jstransformer-nunjucks": "^1.0.0",
|
||||
"metalsmith": "^2.4.0",
|
||||
"metalsmith-assets": "^0.1.0",
|
||||
"metalsmith-browserify": "^1.1.0",
|
||||
"metalsmith-clean-css": "^6.1.3",
|
||||
"metalsmith-groff": "^0.4.0",
|
||||
"metalsmith-rename": "^1.0.0",
|
||||
"metalsmith-sitemap": "^1.2.2",
|
||||
"moment": "^2.29.1",
|
||||
"nodemon": "^2.0.12",
|
||||
"rimraf": "^3.0.2",
|
||||
"slug": "^5.1.0",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* This is the actual metalsmith configuration script. */
|
||||
const assets = require('metalsmith-assets')
|
||||
const browserify = require('metalsmith-browserify')
|
||||
const cleanCSS = require('metalsmith-clean-css')
|
||||
const config = require('./config.js')
|
||||
const collections = require('@metalsmith/collections')
|
||||
@@ -9,7 +10,6 @@ const layouts = require('@metalsmith/layouts')
|
||||
const Metalsmith = require('metalsmith')
|
||||
const markdown = require('@metalsmith/markdown')
|
||||
const marked = require('marked')
|
||||
const moment = require('moment')
|
||||
const permalinks = require('@metalsmith/permalinks')
|
||||
const rename = require('metalsmith-rename')
|
||||
const services = require('./metalsmith-tinuage-services')
|
||||
@@ -35,12 +35,16 @@ markdownRenderer.link = function (href, title, text) {
|
||||
return out
|
||||
}
|
||||
|
||||
moment.locale('fr')
|
||||
|
||||
module.exports = new Metalsmith(config.paths.projectRoot)
|
||||
.clean(__PROD__)
|
||||
.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__,
|
||||
remove_diatrics: function (e) { return slug(e, { mode: 'rfc3986' }) },
|
||||
site: {
|
||||
@@ -55,6 +59,14 @@ module.exports = new Metalsmith(config.paths.projectRoot)
|
||||
rebase: true
|
||||
}
|
||||
}))
|
||||
.use(browserify({
|
||||
browserifyOptions: {
|
||||
standalone: 'Calendar'
|
||||
},
|
||||
entries: [
|
||||
'js/calendrier.js'
|
||||
]
|
||||
}))
|
||||
.use(assets({
|
||||
source: './assets/' + (process.env.NODE_ENV || 'dev'),
|
||||
destination: './'
|
||||
|
||||
Reference in New Issue
Block a user