feat: add support for giscus & utterances comments

main
welpo 2 years ago
parent 93c8b577b8
commit 19d120019b
No known key found for this signature in database
GPG Key ID: A2F978CF4EC1F5A6

@ -179,41 +179,28 @@ allowed_domains = [
custom_subset = true
# giscus support for comments. https://giscus.app
# TODO: make sure giscus / utterances are added to the Custom security headers when enabled
# TODO: Mention this fact in the docs
[extra.giscus]
enabled_for_all_posts = false # Enables utterances on all posts. You can also do it on a post-per-post basis by setting `utterances = true` in the [extra] section of the post's front matter.
repo = "yourname/yourrepository"
enable_reactions = true
enabled_for_all_posts = false # Enables giscus on all posts. It can be enabled on individual posts by setting `giscus = true` in the [extra] section of the post's front matter.
repo = "welpo/test-utterances"
repo_id = "R_kgDOJ59Urw"
category = "Announcements"
category_id = "DIC_kwDOJ59Ur84CX0QG"
mapping = "slug" # Available: pathname; url; title; slug. "slug" will use the post's filename (slug); this is the only way to share comments between languages.
strict_title_matching = 1 # 1 to enable, 0 to disable. https://github.com/giscus/giscus/blob/main/ADVANCED-USAGE.md#data-strict
enable_reactions = 1 # 1 to enable, 0 to disable.
comment_box_above_comments = true
lazy_loading = true
light_theme = "noborder_light"
dark_theme = "noborder_dark"
# TODO: Set data-lang to page's language
# <script src="https://giscus.app/client.js"
# data-repo-id="[REPOSITORI ID]"
# data-category="[NOM CATEGORIA]"
# data-category-id="[ID CATEGORIA]"
# data-mapping="pathname"
# data-strict="0"
# data-lang="ca"
# crossorigin="anonymous"
# async>
# </script>
lang = "" # Leave blank to set it based on the page's language.
lazy_loading = true
automatic_loading = true # If set to false, a "Show comments" button will be shown.
# Utterances support for comments. https://utteranc.es
# utterances support for comments. https://utteranc.es
[extra.utterances]
enabled_for_all_posts = false # Enables utterances on all posts. You can also do it on a post-per-post basis by setting `utterances = true` in the [extra] section of the post's front matter.
repo = "welpo/test-utterances" # https://utteranc.es/#heading-repository
issue_term = "pathname" # Available: pathname; url; title. More info: https://utteranc.es/#heading-mapping
enabled_for_all_posts = false # Enables utterances on all posts. It can be enabled on individual posts by setting `utterances = true` in the [extra] section of the post's front matter.
repo = "yourgithubuser/yourrepo" # https://utteranc.es/#heading-repository
issue_term = "slug" # Available: pathname; url; title; slug. "slug" will use the post's filename (slug); this is the only way to share comments between languages. https://utteranc.es/#heading-mapping
label = "💬" # https://utteranc.es/#heading-issue-label
theme = "preferred-color-scheme" # https://utteranc.es/#heading-theme. If you use the theme switcher, it's probably best to use "preferred-color-scheme"
light_theme = "github-light" # https://utteranc.es/#heading-theme
dark_theme = "github-dark" # https://utteranc.es/#heading-theme
lazy_loading = true

@ -1,7 +1,7 @@
+++
title = "Secure by default"
date = 2023-02-22
updated = 2023-05-22
updated = 2023-07-12
description = "tabi has an easily customizable Content Security Policy (CSP) with safe defaults. Get peace of mind and an A+ on Mozilla Observatory."
[taxonomies]
@ -27,4 +27,6 @@ The `allowed_domains` list specifies the URLs that the website should be able to
This feature allows you to easily customize the website's security headers to allow for specific use cases, such as embedding YouTube videos, loading scripts or remote fonts ([not recommended](https://www.albertovarela.net/blog/2022/11/stop-using-google-fonts/)).
**Note**: enabling comments automatically allows scripts and frames from either utterances/giscus, as well as unsafe-inline styles when using utterances (required so that the widget can adjust its size based on the number of comments).
[^1]: Requires proper webserver configuration (e.g. redirecting HTTP traffic to HTTPS).

@ -1,10 +1,12 @@
.utterances-frame {
min-height: 18rem;
width: 100%;
}
.comments {
padding-top: 5vmin;
border-top: var(--divider-color) solid 0.5px;
margin-top: 2rem;
padding-top: 2rem;
padding-bottom: 2rem;
iframe {
border: none;

@ -0,0 +1,81 @@
function setGiscusTheme(newTheme) {
// Get the giscus iframe.
const frame = document.querySelector('iframe.giscus-frame');
if (frame) {
// If the iframe exists, send a message to set the theme.
frame.contentWindow.postMessage(
{ giscus: { setConfig: { theme: newTheme } } },
'https://giscus.app'
);
}
}
// Function to initialize Giscus. This function is run when the window loads.
function initGiscus() {
// Get the div that will contain the comments.
const commentsDiv = document.querySelector('.comments');
if (commentsDiv) {
// Get the various settings from data attributes on the div.
const repo = commentsDiv.getAttribute('data-repo');
const repoId = commentsDiv.getAttribute('data-repo-id');
const category = commentsDiv.getAttribute('data-category');
const categoryId = commentsDiv.getAttribute('data-category-id');
const strictTitleMatching = commentsDiv.getAttribute('data-strict');
const term = commentsDiv.getAttribute('data-term');
const reactionsEnabled = commentsDiv.getAttribute('data-reactions-enabled');
const inputPosition = commentsDiv.getAttribute('data-input-position');
const lightTheme = commentsDiv.getAttribute('data-light-theme');
const darkTheme = commentsDiv.getAttribute('data-dark-theme');
const lang = commentsDiv.getAttribute('data-lang');
const lazyLoading = commentsDiv.getAttribute('data-lazy-loading');
// Create a new script tag that will load the Giscus script.
const script = document.createElement('script');
script.src = 'https://giscus.app/client.js';
script.async = true;
// Set the various settings as data attributes on the script tag.
script.setAttribute('data-repo', repo);
script.setAttribute('data-repo-id', repoId);
script.setAttribute('data-category', category);
script.setAttribute('data-category-id', categoryId);
script.setAttribute('data-term', term);
script.setAttribute('data-strict', strictTitleMatching);
script.setAttribute('data-reactions-enabled', reactionsEnabled);
script.setAttribute('data-emit-metadata', '0');
script.setAttribute('data-input-position', inputPosition);
script.setAttribute('data-lang', lang);
script.setAttribute('crossorigin', 'anonymous');
// Set the mapping if it is provided.
const mapping = commentsDiv.getAttribute('data-mapping');
if (mapping) {
script.setAttribute('data-mapping', mapping);
}
// Choose the correct theme based on the current theme of the document.
const currentTheme = document.documentElement.getAttribute('data-theme') || 'light';
const selectedTheme = currentTheme === 'dark' ? darkTheme : lightTheme;
script.setAttribute('data-theme', selectedTheme);
// Set the loading attribute if lazy loading is enabled.
if (lazyLoading === 'true') {
script.setAttribute('data-loading', 'lazy');
}
// Add the script tag to the div.
commentsDiv.appendChild(script);
// Listen for theme changes and update the Giscus theme when they occur.
window.addEventListener('themeChanged', (event) => {
const selectedTheme = event.detail.theme === 'dark' ? darkTheme : lightTheme;
setGiscusTheme(selectedTheme);
});
}
}
// Initialize Giscus when the window loads.
window.addEventListener('load', () => {
initGiscus();
});

@ -0,0 +1 @@
function setGiscusTheme(t){var e=document.querySelector("iframe.giscus-frame");e&&e.contentWindow.postMessage({giscus:{setConfig:{theme:t}}},"https://giscus.app")}function initGiscus(){var t=document.querySelector(".comments");if(t){var e=t.getAttribute("data-repo"),a=t.getAttribute("data-repo-id"),i=t.getAttribute("data-category"),r=t.getAttribute("data-category-id"),d=t.getAttribute("data-strict"),s=t.getAttribute("data-term"),u=t.getAttribute("data-reactions-enabled"),n=t.getAttribute("data-input-position");const b=t.getAttribute("data-light-theme"),A=t.getAttribute("data-dark-theme");var o=t.getAttribute("data-lang"),c=t.getAttribute("data-lazy-loading"),g=document.createElement("script"),e=(g.src="https://giscus.app/client.js",g.async=!0,g.setAttribute("data-repo",e),g.setAttribute("data-repo-id",a),g.setAttribute("data-category",i),g.setAttribute("data-category-id",r),g.setAttribute("data-term",s),g.setAttribute("data-strict",d),g.setAttribute("data-reactions-enabled",u),g.setAttribute("data-emit-metadata","0"),g.setAttribute("data-input-position",n),g.setAttribute("data-lang",o),g.setAttribute("crossorigin","anonymous"),t.getAttribute("data-mapping")),a=(e&&g.setAttribute("data-mapping",e),document.documentElement.getAttribute("data-theme")||"light"),i="dark"===a?A:b;g.setAttribute("data-theme",i),"true"===c&&g.setAttribute("data-loading","lazy"),t.appendChild(g),window.addEventListener("themeChanged",t=>{setGiscusTheme("dark"===t.detail.theme?A:b)})}}window.addEventListener("load",()=>{initGiscus()});

@ -1,37 +0,0 @@
// Get the theme switcher button element.
const themeSwitcher = document.querySelector(".theme-switcher");
// Retrieve theme from localStorage.
let currentTheme = localStorage.getItem("theme");
// Function to switch between dark and light themes.
function switchTheme() {
// Set the new theme based on the current theme.
currentTheme = currentTheme === "dark" ? "light" : "dark";
document.documentElement.setAttribute("data-theme", currentTheme);
localStorage.setItem("theme", currentTheme);
// Send a message to the Utterances iframe to change its theme.
document.querySelectorAll(".utterances-frame").forEach((frame) => {
frame.contentWindow.postMessage(
{ type: 'set-theme', theme: `github-${currentTheme}` },
'*'
);
});
}
// Initialize the theme switcher button.
themeSwitcher.addEventListener("click", switchTheme, false);
// Update the theme based on system preference if the user hasn't manually changed the theme.
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", e => {
if (!localStorage.getItem("theme")) {
currentTheme = e.matches ? "dark" : "light";
document.documentElement.setAttribute("data-theme", currentTheme);
}
});
// Fix the theme switcher button not working on the first click by updating currentTheme.
if (!currentTheme) {
currentTheme = document.documentElement.getAttribute("data-theme");
}

@ -1 +0,0 @@
const themeSwitcher=document.querySelector(".theme-switcher");let currentTheme=localStorage.getItem("theme");function switchTheme(){currentTheme="dark"===currentTheme?"light":"dark",document.documentElement.setAttribute("data-theme",currentTheme),localStorage.setItem("theme",currentTheme),document.querySelectorAll(".utterances-frame").forEach(e=>{e.contentWindow.postMessage({type:"set-theme",theme:`github-${currentTheme}`},"*")})}themeSwitcher.addEventListener("click",switchTheme,!1),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",e=>{localStorage.getItem("theme")||(currentTheme=e.matches?"dark":"light",document.documentElement.setAttribute("data-theme",currentTheme))}),currentTheme||(currentTheme=document.documentElement.getAttribute("data-theme"));

@ -0,0 +1,33 @@
// Get the theme switcher button element.
const themeSwitcher = document.querySelector(".theme-switcher");
// Retrieve theme from localStorage.
let currentTheme = localStorage.getItem("theme");
// Function to switch between dark and light themes.
function setTheme(theme) {
document.documentElement.setAttribute("data-theme", theme);
localStorage.setItem("theme", theme);
// Dispatch a custom event for utterances and giscus.
const event = new CustomEvent("themeChanged", {
detail: { theme: theme }
});
window.dispatchEvent(event);
}
function switchTheme() {
// Set the new theme based on the current theme.
const newTheme = currentTheme === "dark" ? "light" : "dark";
setTheme(newTheme);
}
themeSwitcher.addEventListener("click", switchTheme, false);
// Update the theme based on system preference if the user hasn't manually changed the theme.
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", e => {
if (!localStorage.getItem("theme")) {
const newTheme = e.matches ? "dark" : "light";
setTheme(newTheme);
}
});

@ -0,0 +1,37 @@
// Get the theme switcher button element.
const themeSwitcher = document.querySelector(".theme-switcher");
// Retrieve theme from localStorage.
let currentTheme = localStorage.getItem("theme");
// Function to set theme
function setTheme(theme, saveToLocalStorage = false) {
document.documentElement.setAttribute("data-theme", theme);
currentTheme = theme;
if (saveToLocalStorage) {
localStorage.setItem("theme", theme);
}
// Dispatch a custom event for utterances and giscus.
const event = new CustomEvent("themeChanged", {
detail: { theme: theme }
});
window.dispatchEvent(event);
}
// Function to switch between dark and light themes.
function switchTheme() {
// Set the new theme based on the current theme.
const newTheme = currentTheme === "dark" ? "light" : "dark";
setTheme(newTheme, true); // Save the theme to localStorage when the user changes it.
}
// Initialize the theme switcher button.
themeSwitcher.addEventListener("click", switchTheme, false);
// Update the theme based on system preference if the user hasn't manually changed the theme.
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", e => {
const newTheme = e.matches ? "dark" : "light";
setTheme(newTheme);
});

@ -0,0 +1,64 @@
function setUtterancesTheme(newTheme) {
// Get the frame with class "utterances-frame".
const frame = document.querySelector(".utterances-frame");
if (frame) {
// If the iframe exists, send a message to set the theme.
frame.contentWindow.postMessage(
{ type: 'set-theme', theme: newTheme },
'https://utteranc.es'
);
}
}
function initUtterances() {
// Get the comments div.
const commentsDiv = document.querySelector('.comments');
// Check if the comments div exists.
if (commentsDiv) {
// Get all necessary attributes for initializing Utterances.
const repo = commentsDiv.getAttribute('data-repo');
const issueTerm = commentsDiv.getAttribute('data-issue-term');
const label = commentsDiv.getAttribute('data-label');
const lightTheme = commentsDiv.getAttribute('data-light-theme');
const darkTheme = commentsDiv.getAttribute('data-dark-theme');
const lazyLoading = commentsDiv.getAttribute('data-lazy-loading');
// Create a new script element.
const script = document.createElement('script');
script.src = 'https://utteranc.es/client.js';
script.async = true;
script.setAttribute('repo', repo);
script.setAttribute('issue-term', issueTerm);
script.setAttribute('label', label);
// Set the initial theme.
const currentTheme = document.documentElement.getAttribute('data-theme') || 'light';
const selectedTheme = currentTheme === 'dark' ? darkTheme : lightTheme;
script.setAttribute('theme', selectedTheme);
script.setAttribute('crossorigin', 'anonymous');
// Enable lazy loading if specified.
if (lazyLoading === 'true') {
script.setAttribute('data-loading', 'lazy');
}
// Append the script to the comments div.
commentsDiv.appendChild(script);
// Listen for themeChanged event to update the theme.
window.addEventListener('themeChanged', (event) => {
// Determine the new theme based on the event detail.
const selectedTheme = event.detail.theme === 'dark' ? darkTheme : lightTheme;
// Set the new theme.
setUtterancesTheme(selectedTheme);
});
}
}
// Initialize Utterances once the window has loaded.
window.addEventListener('load', () => {
initUtterances();
});

@ -0,0 +1 @@
function setUtterancesTheme(t){const e=document.querySelector(".utterances-frame");e&&e.contentWindow.postMessage({type:"set-theme",theme:t},"https://utteranc.es")}function initUtterances(){const t=document.querySelector(".comments");if(t){var e=t.getAttribute("data-repo"),a=t.getAttribute("data-issue-term"),r=t.getAttribute("data-label");const s=t.getAttribute("data-light-theme"),d=t.getAttribute("data-dark-theme");var n=t.getAttribute("data-lazy-loading"),i=document.createElement("script"),e=(i.src="https://utteranc.es/client.js",i.async=!0,i.setAttribute("repo",e),i.setAttribute("issue-term",a),i.setAttribute("label",r),document.documentElement.getAttribute("data-theme")||"light"),a="dark"===e?d:s;i.setAttribute("theme",a),i.setAttribute("crossorigin","anonymous"),"true"===n&&i.setAttribute("data-loading","lazy"),t.appendChild(i),window.addEventListener("themeChanged",t=>{setUtterancesTheme("dark"===t.detail.theme?d:s)})}}window.addEventListener("load",()=>{initUtterances()});

@ -6,6 +6,7 @@
{% import "macros/set_title.html" as macros_set_title %}
{% import "macros/paginate.html" as macros_paginate %}
{% import "macros/format_date.html" as macros_format_date %}
{% import "macros/add_comments.html" as macros_add_comments %}
<!DOCTYPE html>
<html lang="{{ lang }}">

@ -0,0 +1,64 @@
{%- macro add_comments() -%}
{% set giscus_enabled = config.extra.giscus.enabled_for_all_posts or page.extra.giscus %}
{% set utterances_enabled = config.extra.utterances.enabled_for_all_posts or page.extra.utterances %}
{# Ensure only one comment system is enabled #}
{% if giscus_enabled and utterances_enabled %}
{{ throw(message="ERROR: Multiple comment systems have been enabled for the same page. Check your config.toml and individual page settings to ensure only one comment system is activated at a time.") }}
{% elif giscus_enabled %}
{# Create a div to host giscus comments #}
<div class="comments"
{# Store required giscus data attributes in the div #}
data-repo="{{ config.extra.giscus.repo }}"
data-repo-id="{{ config.extra.giscus.repo_id }}"
data-category="{{ config.extra.giscus.category }}"
data-category-id="{{ config.extra.giscus.category_id }}"
{% if config.extra.giscus.mapping == "slug" %}
data-mapping="specific"
data-term="{{ page.slug }}"
{% else %}
data-mapping="{{ config.extra.giscus.mapping }}"
{% endif %}
data-strict="{{ config.extra.giscus.strict_title_matching }}"
data-reactions-enabled="{{ config.extra.giscus.enable_reactions }}"
{% if config.extra.giscus.comment_box_above_comments %}
data-input-position="top"
{% else %}
data-input-position="bottom"
{% endif %}
data-light-theme="{{ config.extra.giscus.light_theme }}"
data-dark-theme="{{ config.extra.giscus.dark_theme }}"
{% if config.extra.giscus.lang %}
data-lang="{{ config.extra.giscus.lang }}"
{% else %}
data-lang="{{ lang }}"
{% endif %}
data-lazy-loading="{{ config.extra.giscus.lazy_loading | default(value=true) }}">
</div>
{# giscus script to load the widget #}
<script src="{{ get_url(path='js/giscus_min.js', trailing_slash=false) | safe }}"/></script>
{% elif utterances_enabled %}
{# Create a div to host utterances comments #}
<div class="comments"
{# Store required utterances data attributes in the div #}
data-repo="{{ config.extra.utterances.repo }}"
{% if config.extra.utterances.issue_term == "slug" %}
data-issue-term="{{ page.slug }}"
{% else %}
data-issue-term="{{ config.extra.utterances.issue_term }}"
{% endif %}
data-label="{{ config.extra.utterances.label }}"
data-light-theme="{{ config.extra.utterances.light_theme }}"
data-dark-theme="{{ config.extra.utterances.dark_theme }}"
data-lazy-loading="{{ config.extra.utterances.lazy_loading | default(value=true) }}">
</div>
{# utterances script to load the widget #}
<script src="{{ get_url(path='js/utterances_min.js', trailing_slash=false) | safe }}"/></script>
{% endif %}
{%- endmacro add_comments -%}

@ -80,26 +80,9 @@
{{ page.content | safe }}
</section>
{# Add comments if they are enabled #}
{{ macros_add_comments::add_comments() }}
</article>
{# giscus / utterances comments #}
{% if config.extra.giscus.enabled_for_all_posts or page.extra.giscus %}
{# TODO: HERE GOES GISCUS SUPPORT #}
{% elif config.extra.utterances.enabled_for_all_posts or page.extra.utterances %}
<div class="comments">
<script src="https://utteranc.es/client.js"
repo="{{ config.extra.utterances.repo }}"
issue-term="{{ config.extra.utterances.issue_term }}"
label="{{ config.extra.utterances.label }}"
theme="{{ config.extra.utterances.theme }}"
{%- if config.extra.utterances.lazy_loading -%}
data-loading="lazy"
{%- endif -%}
crossorigin="anonymous"
async>
</script>
</div>
{% endif %}
</main>
{% endmacro content %}

@ -14,7 +14,7 @@
<link rel=icon href='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg" viewBox="0 0 105 55"><text y=".7em" font-size="90">{{ config.extra.favicon_emoji }}</text></svg>'>
{% endif %}
{# RSS #}
{# Feed #}
<link rel="alternate" type="application/atom+xml" title="{{ config.title }}" href="{{ get_url(path="atom.xml",
trailing_slash=false) }}">
@ -36,7 +36,16 @@
{% endfor %}
{% endif %}
<meta name="description" content="{{ config.description }}">
<meta name="color-scheme" content="light dark">
{%- if page.description %}
<meta name="description" content="{{ page.description }}" />
{%- elif page.summary %}
<meta name="description" content="{{ page.summary }}" />
{%- else %}
<meta name="description" content="{{ config.description }}" />
{%- endif %}
{% if is_404 %}
<meta name="robots" content="noindex, follow">
{% else %}
@ -61,11 +70,18 @@
content="default-src 'self'
{%- if config.extra.allowed_domains -%}
;
{%- set utterances_enabled = config.extra.utterances.enabled_for_all_posts or page.extra.utterances -%}
{%- set giscus_enabled = config.extra.giscus.enabled_for_all_posts or page.extra.giscus -%}
{%- for domain in config.extra.allowed_domains -%}
{{ domain.directive }} {{ domain.domains | join(sep=' ') }}
{{ domain.directive }} {{ domain.domains | join(sep=' ') -}}
{% if utterances_enabled -%}
{%- if domain.directive == "style-src" %} 'unsafe-inline'
{%- endif -%}
{% endif -%}
{# Automatically allow giscus/utterances, if enabled #}
{%- if domain.directive == "script-src" or domain.directive == "frame-src" or domain.directive == "style-src" -%}
{%- if domain.directive == "script-src" or domain.directive == "frame-src" -%}
{%- if config.extra.giscus.enabled_for_all_posts or page.extra.giscus %} giscus.app
{%- elif config.extra.utterances.enabled_for_all_posts or page.extra.utterances%} utteranc.es
{%- endif %}
@ -77,8 +93,8 @@
{%- endif -%}">
{%- if config.extra.theme_switcher and config.extra.theme_switcher == true -%}
<script type="text/javascript" src="{{ get_url(path='js/initialize_theme_min.js') | safe }}"></script>
<script defer src="{{ get_url(path='js/main_min.js', trailing_slash=false) | safe }}"/></script>
<script type="text/javascript" src="{{ get_url(path='js/initializeTheme_min.js') | safe }}"></script>
<script defer src="{{ get_url(path='js/themeSwitcher_min.js', trailing_slash=false) | safe }}"/></script>
{%- endif -%}
</head>

Loading…
Cancel
Save