pages/_includes/distill_scripts.liquid
Copilot 14136f6592
Add GDPR-compliant cookie consent with analytics blocking (#3492)
Implements cookie consent using vanilla-cookieconsent v3 to comply with
GDPR requirements. Analytics scripts are blocked until user consent is
obtained.

## Changes

**Library Integration**
- Added `vanilla-cookieconsent@3.1.0` to `_config.yml` third-party
libraries with SRI hashes
- Created `_scripts/cookie-consent-setup.js` with consent modal and
settings configuration
- Added CSS/JS includes in `_includes/head.liquid` and
`_includes/scripts.liquid`

**Analytics Blocking**
- Modified analytics scripts in `scripts.liquid` and
`distill_scripts.liquid` to use conditional `type="text/plain"
data-category="analytics"` when consent is disabled
- Blocks Google Analytics, Cronitor, Pirsch, and OpenPanel until consent
granted
- Library automatically converts blocked scripts to executable on user
acceptance

**Configuration**
- Added `enable_cookie_consent` flag (default: `false`)
- Cookie categories: `necessary` (always on), `analytics` (optional)
- 182-day cookie expiration, auto-clear on rejection

## Implementation

```liquid
{% if site.enable_cookie_consent %}
  <script type="text/plain" data-category="analytics" async src="...gtag.js"></script>
{% else %}
  <script async src="...gtag.js"></script>
{% endif %}
```

Enable in `_config.yml`:
```yaml
enable_cookie_consent: true
```

## Screenshots

**Consent Modal**

![Cookie consent
modal](https://github.com/user-attachments/assets/9edcddad-6a54-49ca-a164-083157d98370)

**Settings Modal**

![Cookie
preferences](https://github.com/user-attachments/assets/cf2a58ea-68c0-4699-b401-38377b98b718)

> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `cdn.jsdelivr.net`
>   - Triggering command: `/usr/bin/curl curl -s REDACTED` (dns block)
> - `cookieconsent.orestbida.com`
> - Triggering command:
`/home/REDACTED/work/_temp/ghcca-node/node/bin/node
/home/REDACTED/work/_temp/ghcca-node/node/bin/node --enable-source-maps
/home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js`
(dns block)
> - `medium.com`
> - Triggering command: `/usr/local/bin/ruby ruby
/usr/local/bundle/bin/bundle exec jekyll serve --watch --port=8080
--host=0.0.0.0 --livereload --verbose --trace --force_polling s
6_64-linux /etc/locale.gen by/backward -binutils_2.44-3grep .deb eb
/systemd-sysctl` (dns block)
> - `publish.twitter.com`
> - Triggering command: `/usr/local/bin/ruby ruby
/usr/local/bundle/bin/bundle exec jekyll serve --watch --port=8080
--host=0.0.0.0 --livereload --verbose --trace --force_polling lude
lude/ruby-4.0.0//run/containerd/io.containerd.runtime.v2.task/moby/a4760212ce4eda97e32b6dfa4d3ea/usr/sbin/iptables
r/runc-log.json f8_range lude r-strong E r/wc7in709a54d7hshow` (dns
block)
>
> If you need me to access, download, or install something from one of
these locations, you can either:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/alshedivat/al-folio/settings/copilot/coding_agent)
(admins only)
>
> </details>

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>GDPR compliant cookie consent dialog</issue_title>
> <issue_description>Hi,
> 
> since I am from Europe and want my site to be accessed by EU citizens
I need to ask users if they are ok with the use of cookies. Adding a
dialogue by default that that asks the user before any cookies /
localStorage are used would be nice.
> 
> I have integrated the dialog from
[https://www.cookieconsent.com/](https://www.cookieconsent.com/) on [my
website](http://hschwane.github.io) and am happy to file a PR if you are
interested. However It is not shown directly at the top, but below the
menu bar for some reason. Not sure how to fix that.
> </issue_description>
> 
> <agent_instructions>Implement this using cookie consent dialog from
https://github.com/orestbida/cookieconsent. The documentation is
available at
https://cookieconsent.orestbida.com/essential/getting-started.html.</agent_instructions>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> <comment_new><author>@alshedivat</author><body>
> @hschwane, thanks for pointing this out. It's would be great if you
could send a PR.
> 
> To answer your question about positioning, the menu bar has the
absolute position at the top of the page and keeps moving with
scrolling. You can change that behavior in `_config.yml` by setting
`navbar_fixed: false` (see [line
27](https://github.com/alshedivat/al-folio/blob/master/_config.yml#L27)).
If you send a PR with basic functionality, I can help with adjusting the
styles a bit. Thanks!</body></comment_new>
> <comment_new><author>@alshedivat</author><body>
> Just catching up on this discussion.
> 
> @hschwane, thanks for providing an implementation based on
`ihavecookies`! I looked through alshedivat/al-folio#223. It seems like
to be able to correctly (un)block different types of cookies (all of
which are third-party cookies) requires adding quite a bit of extra
JavaScript (including adding it directly to posts or pages in some
cases, e.g., when posts embed youtube videos or other external content).
This approach will put some extra burden on contributors and maintainers
of al-folio down the line.
> 
> By the way, the dialog from https://www.cookieconsent.com/ is similar
in that sense as it requires to manually add ALL scripts that use
cookies. So, if someone contributes a new feature to al-folio that uses
cookies, the contributor would have to go and update the cookie consent
dialog as well (or edit quite a bit of JS if `ihavecookies` is used).
> 
> Thinking about this more, I currently see only three options:
> 1. If we want to have cookie consent as a core part of al-folio (which
can be easily enabled or disabled), it needs to be a service that
automatically identifies and blocks cookies, without putting an extra
burden on the contributors or maintainers. The hosted version of [Cookie
Consent by Osano](https://www.osano.com/features/consent-management) is
an example.
> 2. Another option is to provide a very minimalistic cookie consent
dialog which simply says that the website uses cookies, without an
option to opt-in or opt-out (e.g., see the open-source edition of
[Osano](https://www.osano.com/cookieconsent/download/)).
> 3. Finally, not have cookie consent as part of al-folio, and make the
user responsible for figuring out whether they need a consent dialog and
which service to use.
> 
> Let me know what you think.</body></comment_new>
> <comment_new><author>@alshedivat</author><body>
> the best way to add GDPR compliant cookie consent is using
https://github.com/orestbida/cookieconsent</body></comment_new>
> </comments>
> 


</details>


> **Custom agent used: customization_agent**
> Expert customization assistant for the al-folio Jekyll academic
website template



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes alshedivat/al-folio#199

<!-- START COPILOT CODING AGENT TIPS -->
---

 Let Copilot coding agent [set things up for
you](https://github.com/alshedivat/al-folio/issues/new?title=+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.

---------

Signed-off-by: George Araújo <george.gcac@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: george-gca <31376482+george-gca@users.noreply.github.com>
Co-authored-by: George Araújo <george.gcac@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-04 10:16:34 -03:00

341 lines
11 KiB
Plaintext

<!-- jQuery -->
<script
src="{{ site.third_party_libraries.jquery.url.js }}"
integrity="{{ site.third_party_libraries.jquery.integrity.js }}"
crossorigin="anonymous"
></script>
<!-- Bootsrap & MDB scripts -->
<script src="{{ '/assets/js/bootstrap.bundle.min.js' | relative_url }}"></script>
<script
src="{{ site.third_party_libraries.mdb.url.js }}"
integrity="{{ site.third_party_libraries.mdb.integrity.js }}"
crossorigin="anonymous"
></script>
<!-- Custom overrides -->
<script src="{{ '/assets/js/distillpub/overrides.js' | relative_url }}"></script>
{% if page.mermaid and page.mermaid.enabled %}
<!-- Mermaid and D3 -->
<script
defer
src="{{ site.third_party_libraries.mermaid.url.js }}"
integrity="{{ site.third_party_libraries.mermaid.integrity.js }}"
crossorigin="anonymous"
></script>
{% if page.mermaid.zoomable %}
<script
defer
src="{{ site.third_party_libraries.d3.url.js }}"
integrity="{{ site.third_party_libraries.d3.integrity.js }}"
crossorigin="anonymous"
></script>
{% endif %}
<script defer src="{{ '/assets/js/mermaid-setup.js' | relative_url | bust_file_cache }}" type="text/javascript"></script>
{% endif %}
{% if page.code_diff %}
<!-- Diff2HTML -->
<!-- diff2html doesn't go well with Bootstrap Table -->
<script
src="{{ site.third_party_libraries.diff2html.url.js }}"
integrity="{{ site.third_party_libraries.diff2html.integrity.js }}"
crossorigin="anonymous"
></script>
<script defer src="{{ '/assets/js/diff2html-setup.js' | relative_url | bust_file_cache }}" type="text/javascript"></script>
{% endif %}
{% if page.map %}
<!-- Leaflet -->
<script
src="{{ site.third_party_libraries.leaflet.url.js }}"
integrity="{{ site.third_party_libraries.leaflet.integrity.js }}"
crossorigin="anonymous"
></script>
<script defer src="{{ '/assets/js/leaflet-setup.js' | relative_url | bust_file_cache }}" type="text/javascript"></script>
{% endif %}
{% if page.chart and page.chart.chartjs %}
<!-- Chart.js -->
<script
defer
src="{{ site.third_party_libraries.chartjs.url.js }}"
integrity="{{ site.third_party_libraries.chartjs.integrity.js }}"
crossorigin="anonymous"
></script>
<script defer src="{{ '/assets/js/chartjs-setup.js' | relative_url | bust_file_cache }}" type="text/javascript"></script>
{% endif %}
{% if page.chart and page.chart.echarts %}
<!-- ECharts -->
<script
src="{{ site.third_party_libraries.echarts.url.js.library }}"
integrity="{{ site.third_party_libraries.echarts.integrity.js.library }}"
crossorigin="anonymous"
></script>
{% if site.enable_darkmode %}
<script
src="{{ site.third_party_libraries.echarts.url.js.dark_theme }}"
integrity="{{ site.third_party_libraries.echarts.integrity.js.dark_theme }}"
crossorigin="anonymous"
></script>
{% endif %}
<script defer src="{{ '/assets/js/echarts-setup.js' | relative_url | bust_file_cache }}" type="text/javascript"></script>
{% endif %}
{% if page.chart and page.chart.plotly %}
<!-- Plotly -->
<script
defer
src="{{ site.third_party_libraries.plotly.url.js }}"
integrity="{{ site.third_party_libraries.plotly.integrity.js }}"
crossorigin="anonymous"
></script>
<script defer src="{{ '/assets/js/plotly-setup.js' | relative_url | bust_file_cache }}" type="text/javascript"></script>
{% endif %}
{% if page.chart and page.chart.vega_lite %}
<!-- Vega -->
<script
defer
src="{{ site.third_party_libraries.vega.url.js }}"
integrity="{{ site.third_party_libraries.vega.integrity.js }}"
crossorigin="anonymous"
></script>
<script
defer
src="{{ site.third_party_libraries.vega-lite.url.js }}"
integrity="{{ site.third_party_libraries.vega-lite.integrity.js }}"
crossorigin="anonymous"
></script>
<script
defer
src="{{ site.third_party_libraries.vega-embed.url.js }}"
integrity="{{ site.third_party_libraries.vega-embed.integrity.js }}"
crossorigin="anonymous"
></script>
<script defer src="{{ '/assets/js/vega-setup.js' | relative_url | bust_file_cache }}" type="text/javascript"></script>
{% endif %}
{% if page.tikzjax %}
<!-- Tikzjax -->
<script defer src="{{ '/assets/js/tikzjax.min.js' | relative_url | bust_file_cache }}" type="text/javascript"></script>
{% endif %}
{% if page.typograms %}
<!-- Typograms -->
<script src="{{ '/assets/js/typograms.js' | relative_url | bust_file_cache }}"></script>
{% endif %}
{% if site.enable_tooltips %}
<!-- Tooltips -->
<script src="{{ '/assets/js/tooltips-setup.js' | relative_url | bust_file_cache }}"></script>
{% endif %}
{% if site.enable_medium_zoom %}
<!-- Medium Zoom JS -->
<script
defer
src="{{ site.third_party_libraries.medium_zoom.url.js }}"
integrity="{{ site.third_party_libraries.medium_zoom.integrity.js }}"
crossorigin="anonymous"
></script>
<script defer src="{{ '/assets/js/zoom.js' | relative_url | bust_file_cache }}"></script>
{% endif %}
{% if page.toc and page.toc.sidebar %}
<!-- Sidebar Table of Contents -->
<script defer src="{{ '/assets/js/bootstrap-toc.min.js' | relative_url | bust_file_cache }}"></script>
{% endif %}
{% if page.pretty_table %}
<!-- Bootstrap Table -->
<!-- Bootstrap Table doesn't go well with diff2html -->
<script
defer
src="{{ site.third_party_libraries.bootstrap-table.url.js }}"
integrity="{{ site.third_party_libraries.bootstrap-table.integrity.js }}"
crossorigin="anonymous"
></script>
{% endif %}
<!-- Load Common JS -->
<script src="{{ '/assets/js/no_defer.js' | relative_url | bust_file_cache }}"></script>
<script defer src="{{ '/assets/js/common.js' | relative_url | bust_file_cache }}"></script>
<script defer src="{{ '/assets/js/copy_code.js' | relative_url | bust_file_cache }}" type="text/javascript"></script>
<!-- Jupyter Open External Links New Tab -->
<script defer src="{{ '/assets/js/jupyter_new_tab.js' | relative_url | bust_file_cache }}"></script>
<!-- Removed Badges -->
{% if site.enable_math %}
<!-- MathJax -->
<script
defer
type="text/javascript"
id="MathJax-script"
src="{{ site.third_party_libraries.mathjax.url.js }}"
integrity="{{ site.third_party_libraries.mathjax.integrity.js }}"
crossorigin="anonymous"
></script>
<script src="{{ '/assets/js/mathjax-setup.js' | relative_url | bust_file_cache }}"></script>
<script
defer
src="{{ site.third_party_libraries.polyfill.url.js }}"
crossorigin="anonymous"
></script>
<!-- Removed Pseudocode -->
{% endif %}
{% if site.enable_cookie_consent %}
<!-- Cookie Consent -->
<script
defer
src="{{ site.third_party_libraries.vanilla-cookieconsent.url.js }}"
integrity="{{ site.third_party_libraries.vanilla-cookieconsent.integrity.js }}"
crossorigin="anonymous"
></script>
<script defer src="{{ '/assets/js/cookie-consent-setup.js' | relative_url }}"></script>
{% endif %}
{% if site.enable_google_analytics %}
<!-- Analytics -->
<!-- Global site tag (gtag.js) - Google Analytics -->
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
async
src="https://www.googletagmanager.com/gtag/js?id={{ site.google_analytics }}"
></script>
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
defer
src="{{ '/assets/js/google-analytics-setup.js' | relative_url }}"
></script>
{% endif %}
{% if site.enable_cronitor_analytics %}
<!-- Cronitor RUM -->
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
async
src="https://rum.cronitor.io/script.js"
></script>
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
defer
src="{{ '/assets/js/cronitor-analytics-setup.js' | relative_url }}"
></script>
{% endif %}
{% if site.enable_pirsch_analytics %}
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
defer
src="https://api.pirsch.io/pa.js"
id="pianjs"
data-code="{{ site.pirsch_analytics }}"
></script>
{% endif %}
{% if site.enable_openpanel_analytics %}
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
defer
src="{{ '/assets/js/open-panel-analytics-setup.js' | relative_url }}"
></script>
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
async
defer
src="https://openpanel.dev/op1.js"
></script>
{% endif %}
{% if site.enable_progressbar %}
<!-- Scrolling Progress Bar -->
<script defer src="{{ '/assets/js/progress-bar.js' | relative_url | bust_file_cache }}" type="text/javascript"></script>
{% endif %}
{% if page.images %}
<!-- Image Layouts -->
{% if page.images.compare %}
<script
defer
src="{{ site.third_party_libraries.img-comparison-slider.url.js }}"
integrity="{{ site.third_party_libraries.img-comparison-slider.integrity.js }}"
crossorigin="anonymous"
></script>
{% endif %}
{% if page.images.lightbox2 %}
<script
defer
src="{{ site.third_party_libraries.lightbox2.url.js }}"
integrity="{{ site.third_party_libraries.lightbox2.integrity.js }}"
crossorigin="anonymous"
></script>
{% endif %}
{% if page.images.photoswipe %}
<script defer src="{{ '/assets/js/photoswipe-setup.js' | relative_url }}" type="module"></script>
{% endif %}
{% if page.images.slider %}
<script
defer
src="{{ site.third_party_libraries.swiper.url.js }}"
integrity="{{ site.third_party_libraries.swiper.integrity.js }}"
crossorigin="anonymous"
></script>
{% endif %}
{% if page.images.spotlight %}
<script
defer
src="{{ site.third_party_libraries.spotlight.url.js }}"
crossorigin="anonymous"
></script>
{% endif %}
{% if page.images.venobox %}
<script
defer
src="{{ site.third_party_libraries.venobox.url.js }}"
integrity="{{ site.third_party_libraries.venobox.integrity.js }}"
crossorigin="anonymous"
></script>
<script defer src="{{ '/assets/js/venobox-setup.js' | relative_url | bust_file_cache }}" type="text/javascript"></script>
{% endif %}
{% endif %}
{% if page.tabs %}
<!-- Jekyll Tabs -->
<script src="{{ '/assets/js/tabs.min.js' | relative_url | bust_file_cache }}"></script>
{% endif %}
{% if site.back_to_top %}
<!-- Back to Top -->
<script src="{{ '/assets/js/vanilla-back-to-top.min.js' | relative_url | bust_file_cache }}"></script>
<script>
addBackToTop();
</script>
{% endif %}
{% if site.search_enabled %}
<!-- Search -->
<script type="module" src="{{ '/assets/js/search/ninja-keys.min.js' | relative_url | bust_file_cache }}"></script>
<ninja-keys hideBreadcrumbs noAutoLoadMdIcons placeholder="Type to start searching"></ninja-keys>
<script src="{{ '/assets/js/search-setup.js' | relative_url | bust_file_cache }}"></script>
<script src="{{ '/assets/js/search-data.js' | relative_url }}"></script>
<script src="{{ '/assets/js/shortcut-key.js' | relative_url | bust_file_cache }}"></script>
{% endif %}