CCCB Website (https://berlin.ccc.de/)
  • JavaScript 52.7%
  • HTML 29%
  • CSS 12.6%
  • Nix 4.6%
  • Shell 1.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Hauke Mehrtens 6d99a39190 Do not list a moved event occurrence twice
An occurrence of a recurring event that was changed on its own carries a
RECURRENCE-ID and is stored as an additional VEVENT next to the event it
belongs to. `getAllSubcomponents("vevent")` returns those components as well,
and since they have no RRULE of their own they were handled as separate single
events. The occurrence therefore ended up in the list twice: once from
expanding the recurring event, which resolves the modified time through the
exception, and once more from the extra VEVENT.

To make it worse the two rows disagreed, because name and URL were taken from
the recurring event while the time came from the modification, so the first row
showed the new time under the old name.

Skip components that are a recurrence exception, they are already covered by
the event they modify, and take name and URL from the occurrence details, which
point at the modification where there is one and at the event itself otherwise.
Events whose RECURRENCE-ID refers to an event that is not in the file are
dropped by this, which cannot happen in a full calendar export.

With a recurring Plenum whose 25.08. occurrence is moved two hours earlier and
renamed:

  before: 25.08. 18:00  CCCB Plenum
          25.08. 18:00  CCCB Plenum (verschoben)
  after:  25.08. 18:00  CCCB Plenum (verschoben)

Leaning on the resolution ical.js does here needs two more things to be right.

The first is which modifications an event is asked to resolve. Unless it is
told, `ICAL.Event` relates every VEVENT with a RECURRENCE-ID in the file to
every recurring event and keys them by the recurrence id alone; the UID is only
compared with `strictExceptions`, which then throws instead of skipping. A
modification would therefore also override the occurrence another series holds
at the same instant, so the wrong entry is shown and the modified one twice
over. Group the modifications by the UID of the event they belong to and hand
each event its own, which also turns the relating off for events that have
none. Recognise a modification on the component instead of on the event,
because relating exceptions to an exception throws. With two unrelated weekly
series that both meet on Thursday at 19:00, of which only A has its occurrence
of 03.09. moved to 17:00:

  before: 27.08. 19:00 Serie A          after: 27.08. 19:00 Serie A
          27.08. 19:00 Serie B                 27.08. 19:00 Serie B
          03.09. 17:00 Serie A (versch.)       03.09. 17:00 Serie A (versch.)
          03.09. 17:00 Serie A (versch.)       03.09. 19:00 Serie B

The second is how far the expansion has to run. It walks the unmodified
recurrence times and stopped at the end of the window, but the occurrence
handed to `getOccurrenceDetails()` may have been moved somewhere else entirely.
Both directions were wrong: an occurrence pulled forward from beyond the window
was never reached, because the walk had already stopped at its original time,
and one pushed out of the window was still listed, because only its original
time was ever compared against the window. Iterate far enough that the largest
move towards the past can still reach the window, and decide by the time the
occurrence really takes place at. The stop condition keeps using the raw
recurrence time, which stays monotonic, so the iteration still terminates. With
a weekly series whose occurrence of 05.10. is pulled forward to 25.08. and
whose occurrence of 31.08. is pushed to 02.11., seen from 23.08. through the 20
day window:

  before: 24.08. Serie                 after: 24.08. Serie
          31.08. Serie                        25.08. Serie (vorgezogen)
          07.09. Serie                        07.09. Serie

Reading the URL becomes a function of its own while name and URL move to the
occurrence details, and it now looks at what it reads. The calendar is exported
from a CalDAV server, so whoever may write to it decides what ends up in the
href of the link, and `URL:javascript:alert(1)` on an event would run that
script when a visitor clicks the entry. Pass on nothing but http and https; a
value that is not a URL at all no longer reaches the href either.

The published calendar contains no RECURRENCE-ID at all today, so nothing about
the modifications changes for it. It is exported from a CalDAV server though,
and moving a single Club Discordia or Plenum out of the way of a holiday is
exactly what creates such a modification. Checked against
https://berlin.ccc.de/calendars/all.ics: the list and the links of the 30
events that carry a URL are unchanged.

Fixes: c28f04c6e8 ("switch to ics files; make calendars work; fix some minor issues")
Assisted-by: Claude:claude-opus-5
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
2026-08-23 01:07:25 +02:00
.forgejo/workflows Delete .forgejo/workflows/test.yaml 2026-08-22 15:22:52 +02:00
archetypes Move out datengartens into their own section 2018-05-21 18:31:39 +02:00
assets switch to ics files; make calendars work; fix some minor issues 2026-08-12 02:01:58 +02:00
config/_default switch to ics files; make calendars work; fix some minor issues 2026-08-12 02:01:58 +02:00
content switch to ics files; make calendars work; fix some minor issues 2026-08-12 02:01:58 +02:00
i18n fix 404 2025-06-06 18:39:07 +02:00
layouts Behebe Layoutauswahl unter aktuellem Hugo. 2026-07-04 22:22:53 +02:00
static Do not list a moved event occurrence twice 2026-08-23 01:07:25 +02:00
themes add optional nix flake 2025-06-05 21:51:31 +02:00
tools switch to ics files; make calendars work; fix some minor issues 2026-08-12 02:01:58 +02:00
.editorconfig switch to ics files; make calendars work; fix some minor issues 2026-08-12 02:01:58 +02:00
.gitignore README: describe the calendar as it is built today 2026-08-22 21:22:02 +02:00
.gitmodules removed old beautifulhugo theme 2025-03-12 12:50:25 +01:00
.hugo-params improved calendar and fixed url temporarily 2025-03-12 16:06:29 +01:00
build.sh switch to ics files; make calendars work; fix some minor issues 2026-08-12 02:01:58 +02:00
devShells.nix split flake into multiple files 2025-06-07 15:40:38 +02:00
flake.lock switch to ics files; make calendars work; fix some minor issues 2026-08-12 02:01:58 +02:00
flake.nix switch to ics files; make calendars work; fix some minor issues 2026-08-12 02:01:58 +02:00
LICENSE Add LICENSE 2018-08-14 23:11:06 +02:00
old_config.yaml.txt new images, new content, new theme almost finished 2025-02-27 00:21:42 +01:00
packages.nix flake lib.mkWwwContent 2025-06-07 19:22:56 +02:00
README.md README: describe the calendar as it is built today 2026-08-22 21:22:02 +02:00
TODO.md update readme, markdown lint 2025-06-07 15:35:43 +02:00

CCCB Website

This is the website of the CCCB.

CCCB logo

Getting started

  1. Get Hugo: https://gohugo.io/getting-started/installing

  2. Clone this repo (--recursive is needed to check out submodules)

    git clone --recursive https://git.berlin.ccc.de/cccb-website-team/www.git cccb-website
    
  3. Switch directory

    cd cccb-website
    
  4. Run hugo webserver

    hugo serve
    
  5. Point your browser to: http://localhost:1313/

Every change you make on the project will be reflected in your browser as long as hugo serve is running.

The "Nächste Veranstaltungen" table on the home page and the calendar under /verein/calendar/ are rendered in the browser by static/js/upcoming.js and assets/js/calendar.js. Both fetch /calendars/all.ics, which is not generated by Hugo and is not part of this repo — it is published separately on the web server. Without it both tables stay empty. Download the published calendar into static/, which Hugo serves under the same path, and they work locally, under hugo serve as well as in a built site:

mkdir -p static/calendars
curl -o static/calendars/all.ics https://berlin.ccc.de/calendars/all.ics

The file is only there to look at the site, it is not checked in. Download it again whenever you want the events that are currently published.

Hugo does generate an .ics feed per section (for example /veranstaltungen/index.ics) from the dtstart, dtend and rrule front matter of the pages in content/veranstaltungen/, using the .ics templates under layouts/.

To build the site for upload, run:

./build.sh

This deletes public/ and runs hugo with the parameters from .hugo-params. To build with nix instead: nix build '.?submodules=1#production-content'

Making a change

  1. Use your local dev setup (see Getting started) or via the Forgejo editor.
  2. Make your change in staging branch.
  3. Commit (and push) your change.
  4. GitHub Actions is running the release workflow.
  5. Create a merge request to merge changes from staging to production branch. Ask somebody to check merge request or if small change, merge yourself.
  6. GitHub Actions is running the release workflow.
    • If successfull, check Website if change is correct.
  7. Profit!

Nix stuff

  • After entering the shell with nix develop, hugo is available and hugo serve and ./build.sh should work
  • You can build the staging and production builds with nix build .#staging-content and nix build .#production-content
  • Do not update the nixpkgs branch - 25.05 contains a newer hugo version that is incompatible with the theme (last checked June 2025)

Made with ❤️ and Hugo.