Merged features tied to Royal Gold funding goal

This commit is contained in:
squidfunk 2022-07-20 17:25:55 +02:00
parent 5a9c295822
commit de438d5d9d
55 changed files with 1205 additions and 120 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -34,7 +34,7 @@
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block styles %} {% block styles %}
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.1dff34a1.min.css' | url }}"> <link rel="stylesheet" href="{{ 'assets/stylesheets/main.476df7d4.min.css' | url }}">
{% if config.theme.palette %} {% if config.theme.palette %}
{% set palette = config.theme.palette %} {% set palette = config.theme.palette %}
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.cbb835fc.min.css' | url }}"> <link rel="stylesheet" href="{{ 'assets/stylesheets/palette.cbb835fc.min.css' | url }}">
@ -102,21 +102,29 @@
{% if self.announce() %} {% if self.announce() %}
<aside class="md-banner"> <aside class="md-banner">
<div class="md-banner__inner md-grid md-typeset"> <div class="md-banner__inner md-grid md-typeset">
{% if "announce.dismiss" in features %}
<button class="md-banner__button md-icon" aria-label="{{ lang.t('announce.dismiss') }}">
{% include ".icons/material/close.svg" %}
</button>
{% endif %}
{% block announce %}{% endblock %} {% block announce %}{% endblock %}
</div> </div>
{% if "announce.dismiss" in features %}
{% include "partials/javascripts/announce.html" %}
{% endif %}
</aside> </aside>
{% endif %} {% endif %}
</div> </div>
{% if config.extra.version %} {% if config.extra.version %}
<div data-md-component="outdated" hidden> <div data-md-component="outdated" hidden>
<aside class="md-banner md-banner--warning"> {% if self.outdated() %}
{% if self.outdated() %} <aside class="md-banner md-banner--warning">
<div class="md-banner__inner md-grid md-typeset"> <div class="md-banner__inner md-grid md-typeset">
{% block outdated %}{% endblock %} {% block outdated %}{% endblock %}
</div> </div>
{% include "partials/javascripts/outdated.html" %} {% include "partials/javascripts/outdated.html" %}
{% endif %} </aside>
</aside> {% endif %}
</div> </div>
{% endif %} {% endif %}
{% block header %} {% block header %}
@ -182,6 +190,17 @@
<div class="md-dialog" data-md-component="dialog"> <div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div> <div class="md-dialog__inner md-typeset"></div>
</div> </div>
{% if config.extra.consent %}
<div class="md-consent" data-md-component="consent" id="__consent" hidden>
<div class="md-consent__overlay"></div>
<aside class="md-consent__inner">
<form class="md-consent__form md-grid md-typeset" name="consent">
{% include "partials/consent.html" %}
</form>
</aside>
</div>
{% include "partials/javascripts/consent.html" %}
{% endif %}
{% block config %} {% block config %}
{%- set app = { {%- set app = {
"base": base_url, "base": base_url,
@ -216,7 +235,7 @@
</script> </script>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script src="{{ 'assets/javascripts/bundle.c99f48ec.min.js' | url }}"></script> <script src="{{ 'assets/javascripts/bundle.0bc13b87.min.js' | url }}"></script>
{% for path in config["extra_javascript"] %} {% for path in config["extra_javascript"] %}
<script src="{{ path | url }}"></script> <script src="{{ path | url }}"></script>
{% endfor %} {% endfor %}

View File

@ -19,5 +19,5 @@
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
{{ super() }} {{ super() }}
<script src="{{ 'overrides/assets/javascripts/bundle.39654835.min.js' | url }}"></script> <script src="{{ 'overrides/assets/javascripts/bundle.a47bf9ec.min.js' | url }}"></script>
{% endblock %} {% endblock %}

View File

@ -24,3 +24,4 @@
) %} ) %}
{% include "partials/source-file.html" %} {% include "partials/source-file.html" %}
{% endif %} {% endif %}
{% include "partials/feedback.html" %}

View File

@ -0,0 +1,56 @@
{#-
This file was automatically generated - do not edit
-#}
{% import "partials/language.html" as lang with context %}
{% set cookies = config.extra.consent.cookies %}
{% if config.extra.analytics and not cookies %}
{% set cookies = { "analytics": "Google Analytics" } %}
{% endif %}
{% set actions = config.extra.consent.actions %}
{% if not actions %}
{% set actions = ["accept", "manage"] %}
{% endif %}
<h4>{{ config.extra.consent.title }}</h4>
<p>{{ config.extra.consent.description }}</p>
<input type="checkbox" class="md-toggle" id="__settings">
<div class="md-consent__settings">
<ul class="task-list">
{% for type in cookies %}
{% if cookies[type] is string %}
{% set name = cookies[type] %}
{% set checked = "checked" %}
{% else %}
{% set name = cookies[type].name %}
{% if cookies[type].checked %}
{% set checked = "checked" %}
{% endif %}
{% endif %}
<li class="task-list-item">
<label class="task-list-control">
<input type="checkbox" name="{{ type }}" {{ checked }}>
<span class="task-list-indicator"></span>
{{ name }}
<label>
</li>
{% endfor %}
</ul>
</div>
<div class="md-consent__controls">
{% for action in actions %}
{% if action == "accept" %}
<button class="md-button md-button--primary">
{{- lang.t("consent.accept") -}}
</button>
{% endif %}
{% if action == "reject" %}
<button type="reset" class="md-button md-button--primary">
{{- lang.t("consent.reject") -}}
</button>
{% endif %}
{% if action == "manage" %}
<label class="md-button" for="__settings">
{{- lang.t("consent.manage") -}}
</label>
{% endif %}
{% endfor %}
</div>

View File

@ -19,3 +19,4 @@
) %} ) %}
{% include "partials/source-file.html" %} {% include "partials/source-file.html" %}
{% endif %} {% endif %}
{% include "partials/feedback.html" %}

View File

@ -0,0 +1,46 @@
{#-
This file was automatically generated - do not edit
-#}
{% if config.extra.analytics %}
{% set feedback = config.extra.analytics.feedback %}
{% endif %}
{% if page and page.meta and page.meta.hide %}
{% if "feedback" in page.meta.hide %}
{% set feedback = None %}
{% endif %}
{% endif %}
{% if feedback %}
<form class="md-feedback" name="feedback" hidden>
<fieldset>
<legend class="md-feedback__title">
{{ feedback.title }}
</legend>
<div class="md-feedback__inner">
<div class="md-feedback__list">
{% for rating in feedback.ratings %}
<button class="md-feedback__icon md-icon" type="submit" title="{{ rating.name }}" data-md-value="{{ rating.data }}">
{% include ".icons/" ~ rating.icon ~ ".svg" %}
</button>
{% endfor %}
</div>
<div class="md-feedback__note">
{% for rating in feedback.ratings %}
<div data-md-value="{{ rating.data }}" hidden>
{% set url = "/" ~ page.url %}
{% if page and page.meta and page.meta.title %}
{% set title = page.meta.title | urlencode %}
{% else %}
{% set title = page.title | urlencode %}
{% endif %}
{% if "{}" in rating.note %}
{{ rating.note.format(url, title) }}
{% else %}
{{ rating.note.format(url = url, title = title) }}
{% endif %}
</div>
{% endfor %}
</div>
</div>
</fieldset>
</form>
{% endif %}

View File

@ -6,4 +6,9 @@
{% endif %} {% endif %}
{% if provider %} {% if provider %}
{% include "partials/integrations/analytics/" ~ provider ~ ".html" %} {% include "partials/integrations/analytics/" ~ provider ~ ".html" %}
{% if config.extra.consent %}
<script>var consent;"undefined"==typeof __md_analytics||(consent=__md_get("__consent"))&&consent.analytics&&__md_analytics()</script>
{% else %}
<script>"undefined"!=typeof __md_analytics&&__md_analytics()</script>
{% endif %}
{% endif %} {% endif %}

View File

@ -5,9 +5,7 @@
{% set property = config.extra.analytics.property | d("", true) %} {% set property = config.extra.analytics.property | d("", true) %}
{% endif %} {% endif %}
{% if property.startswith("G-") %} {% if property.startswith("G-") %}
<script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","{{ property }}"),document.addEventListener("DOMContentLoaded",function(){document.forms.search&&document.forms.search.query.addEventListener("blur",function(){this.value&&gtag("event","search",{search_term:this.value})}),"undefined"!=typeof location$&&location$.subscribe(function(e){gtag("config","{{ property }}",{page_path:e.pathname})})})</script> <script id="__analytics">function __md_analytics(){function n(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],n("js",new Date),n("config","{{ property }}"),document.addEventListener("DOMContentLoaded",function(){if(document.forms.search&&document.forms.search.query.addEventListener("blur",function(){this.value&&n("event","search",{search_term:this.value})}),document.forms.feedback){var e,a=document.forms.feedback;for(e of a.querySelectorAll("[type=submit]"))e.addEventListener("click",function(e){e.preventDefault();var t=document.location.pathname,e=this.getAttribute("data-md-value");n("event","feedback",{page:t,data:e}),a.firstElementChild.disabled=!0;e=a.querySelector(".md-feedback__note [data-md-value='"+e+"']");e&&(e.hidden=!1)}),a.hidden=!1}"undefined"!=typeof location$&&location$.subscribe(function(e){n("config","{{ property }}",{page_path:e.pathname})})});var e=document.createElement("script");e.async=!0,e.src="https://www.googletagmanager.com/gtag/js?id={{ property }}",document.getElementById("__analytics").insertAdjacentElement("afterEnd",e)}</script>
<script async src="https://www.googletagmanager.com/gtag/js?id={{ property }}"></script>
{% elif property.startswith("UA-") %} {% elif property.startswith("UA-") %}
<script>window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)},ga.l=+new Date,ga("create","{{ property }}","auto"),ga("set","anonymizeIp",!0),ga("send","pageview"),document.addEventListener("DOMContentLoaded",function(){document.forms.search&&document.forms.search.query.addEventListener("blur",function(){var e;this.value&&(e=document.location.pathname,ga("send","pageview",e+"?q="+this.value))}),"undefined"!=typeof location$&&location$.subscribe(function(e){ga("send","pageview",e.pathname)})})</script> <script id="__analytics">function __md_analytics(){window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)},ga.l=+new Date,ga("create","{{ property }}","auto"),ga("set","anonymizeIp",!0),ga("send","pageview"),document.addEventListener("DOMContentLoaded",function(){if(document.forms.search&&document.forms.search.query.addEventListener("blur",function(){var e;this.value&&(e=document.location.pathname,ga("send","pageview",e+"?q="+this.value))}),document.forms.feedback){var e,a=document.forms.feedback;for(e of a.querySelectorAll("[type=submit]"))e.addEventListener("click",function(e){e.preventDefault();var t=document.location.pathname,e=this.getAttribute("data-md-value");ga("send","event","feedback","click",t,e),a.firstElementChild.disabled=!0;e=a.querySelector(".md-feedback__note [data-md-value='"+e+"']");e&&(e.hidden=!1)}),a.hidden=!1}"undefined"!=typeof location$&&location$.subscribe(function(e){ga("send","pageview",e.pathname)})});var e=document.createElement("script");e.async=!0,e.src="https://www.google-analytics.com/analytics.js",document.getElementById("__analytics").insertAdjacentElement("afterEnd",e)}</script>
<script async src="https://www.google-analytics.com/analytics.js"></script>
{% endif %} {% endif %}

View File

@ -0,0 +1,4 @@
{#-
This file was automatically generated - do not edit
-#}
<script>var content,el=document.querySelector("[data-md-component=announce]");el&&(content=el.querySelector(".md-typeset"),__md_hash(content.innerHTML)===__md_get("__announce")&&(el.hidden=!0))</script>

View File

@ -1,4 +1,4 @@
{#- {#-
This file was automatically generated - do not edit This file was automatically generated - do not edit
-#} -#}
<script>__md_scope=new URL("{{ config.extra.scope | d(base_url) }}",location),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script> <script>__md_scope=new URL("{{ config.extra.scope | d(base_url) }}",location),__md_hash=e=>[...e].reduce((e,_)=>(e<<5)-e+_.charCodeAt(0),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>

View File

@ -0,0 +1,4 @@
{#-
This file was automatically generated - do not edit
-#}
<script>var consent=__md_get("__consent");if(consent)for(var input of document.forms.consent.elements)input.name&&(input.checked=consent[input.name]||!1);else"file:"!==location.protocol&&setTimeout(function(){document.querySelector("[data-md-component=consent]").hidden=!1},250);var action,form=document.forms.consent;for(action of["submit","reset"])form.addEventListener(action,function(e){if(e.preventDefault(),"reset"===e.type)for(var n of document.forms.consent.elements)n.name&&(n.checked=!1);console.log(new FormData(form)),__md_set("__consent",Object.fromEntries(Array.from(new FormData(form).keys()).map(function(e){return[e,!0]}))),location.hash="",location.reload()})</script>

View File

@ -3,8 +3,12 @@
-#} -#}
{% macro t(key) %}{{ { {% macro t(key) %}{{ {
"language": "de", "language": "de",
"announce.dismiss": "Nicht mehr anzeigen",
"clipboard.copy": "In Zwischenablage kopieren", "clipboard.copy": "In Zwischenablage kopieren",
"clipboard.copied": "In Zwischenablage kopiert", "clipboard.copied": "In Zwischenablage kopiert",
"consent.accept": "Akzeptieren",
"consent.manage": "Einstellungen",
"consent.reject": "Ablehnen",
"edit.link.title": "Seite editieren", "edit.link.title": "Seite editieren",
"footer.previous": "Zurück", "footer.previous": "Zurück",
"footer.next": "Weiter", "footer.next": "Weiter",

View File

@ -4,8 +4,12 @@
{% macro t(key) %}{{ { {% macro t(key) %}{{ {
"language": "en", "language": "en",
"direction": "ltr", "direction": "ltr",
"announce.dismiss": "Don't show this again",
"clipboard.copy": "Copy to clipboard", "clipboard.copy": "Copy to clipboard",
"clipboard.copied": "Copied to clipboard", "clipboard.copied": "Copied to clipboard",
"consent.accept": "Accept",
"consent.manage": "Manage settings",
"consent.reject": "Reject",
"edit.link.title": "Edit this page", "edit.link.title": "Edit this page",
"footer.previous": "Previous", "footer.previous": "Previous",
"footer.next": "Next", "footer.next": "Next",

View File

@ -5,6 +5,8 @@
"language": "es", "language": "es",
"clipboard.copy": "Copiar al portapapeles", "clipboard.copy": "Copiar al portapapeles",
"clipboard.copied": "Copiado al portapapeles", "clipboard.copied": "Copiado al portapapeles",
"consent.accept": "Aceptar",
"consent.manage": "Gestionar cookies",
"edit.link.title": "Editar esta página", "edit.link.title": "Editar esta página",
"footer.previous": "Anterior", "footer.previous": "Anterior",
"footer.next": "Siguiente", "footer.next": "Siguiente",

View File

@ -5,6 +5,9 @@
"language": "fr", "language": "fr",
"clipboard.copy": "Copier dans le presse-papier", "clipboard.copy": "Copier dans le presse-papier",
"clipboard.copied": "Copié dans le presse-papier", "clipboard.copied": "Copié dans le presse-papier",
"consent.accept": "Accepter",
"consent.manage": "Paramétrer vos choix",
"consent.reject": "Refuser",
"edit.link.title": "Editer cette page", "edit.link.title": "Editer cette page",
"footer.previous": "Précédent", "footer.previous": "Précédent",
"footer.next": "Suivant", "footer.next": "Suivant",

View File

@ -5,6 +5,8 @@
"language": "sv", "language": "sv",
"clipboard.copy": "Kopiera till urklipp", "clipboard.copy": "Kopiera till urklipp",
"clipboard.copied": "Kopierat till urklipp", "clipboard.copied": "Kopierat till urklipp",
"consent.accept": "Acceptera",
"consent.manage": "Hantera inställningar",
"edit.link.title": "Redigera sidan", "edit.link.title": "Redigera sidan",
"footer.previous": "Föregående", "footer.previous": "Föregående",
"footer.next": "Nästa", "footer.next": "Nästa",

View File

@ -3,8 +3,12 @@
-#} -#}
{% macro t(key) %}{{ { {% macro t(key) %}{{ {
"language": "zh-Hant", "language": "zh-Hant",
"announce.dismiss": "不再顯示此訊息",
"clipboard.copy": "複製", "clipboard.copy": "複製",
"clipboard.copied": "已複製", "clipboard.copied": "已複製",
"consent.accept": "同意",
"consent.manage": "管理設定",
"consent.reject": "拒絕",
"edit.link.title": "編輯此頁", "edit.link.title": "編輯此頁",
"footer.previous": "上一頁", "footer.previous": "上一頁",
"footer.next": "下一頁", "footer.next": "下一頁",

View File

@ -49,6 +49,7 @@ theme:
# Default values, taken from mkdocs_theme.yml # Default values, taken from mkdocs_theme.yml
language: en language: en
features: features:
# - announce.dismiss
- content.code.annotate - content.code.annotate
# - content.tabs.link # - content.tabs.link
- content.tooltips - content.tooltips
@ -104,9 +105,30 @@ plugins:
# Customization # Customization
extra: extra:
consent:
title: Cookie consent
description: >-
We use cookies to recognize your repeated visits and preferences, as well
as to measure the effectiveness of our documentation and whether users
find what they're searching for. With your consent, you're helping us to
make our documentation better.
analytics: analytics:
provider: google provider: google
property: !ENV GOOGLE_ANALYTICS_KEY property: UA-12345 #!ENV GOOGLE_ANALYTICS_KEY
feedback:
title: Was this page helpful?
ratings:
- icon: material/emoticon-happy-outline
name: This page was helpful
data: 1
note: >-
Thanks for your feedback!
- icon: material/emoticon-sad-outline
name: This page could be improved
data: 0
note: >- # !
Thanks for your feedback! Help us improve this page by
using our <a href="..." target=_blank>feedback form</a>.
social: social:
- icon: fontawesome/brands/github - icon: fontawesome/brands/github
link: https://github.com/squidfunk link: https://github.com/squidfunk

View File

@ -30,6 +30,7 @@ import { getElement, getLocation } from "~/browser"
* Feature flag * Feature flag
*/ */
export type Flag = export type Flag =
| "announce.dismiss" /* Dismissable announcement bar */
| "content.code.annotate" /* Code annotations */ | "content.code.annotate" /* Code annotations */
| "content.tabs.link" /* Link content tabs */ | "content.tabs.link" /* Link content tabs */
| "header.autohide" /* Hide header */ | "header.autohide" /* Hide header */

View File

@ -56,7 +56,9 @@ import {
import { import {
getComponentElement, getComponentElement,
getComponentElements, getComponentElements,
mountAnnounce,
mountBackToTop, mountBackToTop,
mountConsent,
mountContent, mountContent,
mountDialog, mountDialog,
mountHeader, mountHeader,
@ -177,6 +179,10 @@ const main$ = document$
/* Set up control component observables */ /* Set up control component observables */
const control$ = merge( const control$ = merge(
/* Consent */
...getComponentElements("consent")
.map(el => mountConsent(el, { target$ })),
/* Dialog */ /* Dialog */
...getComponentElements("dialog") ...getComponentElements("dialog")
.map(el => mountDialog(el, { alert$ })), .map(el => mountDialog(el, { alert$ })),
@ -201,6 +207,10 @@ const control$ = merge(
/* Set up content component observables */ /* Set up content component observables */
const content$ = defer(() => merge( const content$ = defer(() => merge(
/* Announcement bar */
...getComponentElements("announce")
.map(el => mountAnnounce(el)),
/* Content */ /* Content */
...getComponentElements("content") ...getComponentElements("content")
.map(el => mountContent(el, { target$, print$ })), .map(el => mountContent(el, { target$, print$ })),

View File

@ -32,6 +32,7 @@ import { getElement, getElements } from "~/browser"
export type ComponentType = export type ComponentType =
| "announce" /* Announcement bar */ | "announce" /* Announcement bar */
| "container" /* Container */ | "container" /* Container */
| "consent" /* Consent */
| "content" /* Content */ | "content" /* Content */
| "dialog" /* Dialog */ | "dialog" /* Dialog */
| "header" /* Header */ | "header" /* Header */
@ -76,6 +77,7 @@ export type Component<
interface ComponentTypeMap { interface ComponentTypeMap {
"announce": HTMLElement /* Announcement bar */ "announce": HTMLElement /* Announcement bar */
"container": HTMLElement /* Container */ "container": HTMLElement /* Container */
"consent": HTMLElement /* Consent */
"content": HTMLElement /* Content */ "content": HTMLElement /* Content */
"dialog": HTMLElement /* Dialog */ "dialog": HTMLElement /* Dialog */
"header": HTMLElement /* Header */ "header": HTMLElement /* Header */

View File

@ -0,0 +1,110 @@
/*
* Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import {
EMPTY,
Observable,
Subject,
defer,
finalize,
fromEvent,
map,
startWith,
tap
} from "rxjs"
import { feature } from "~/_"
import { getElement } from "~/browser"
import { Component } from "../_"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Announcement bar
*/
export interface Announce {
hash: number /* Content hash */
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch announcement bar
*
* @param el - Announcement bar element
*
* @returns Announcement bar observable
*/
export function watchAnnounce(
el: HTMLElement
): Observable<Announce> {
const button = getElement(".md-typeset > :first-child", el)
return fromEvent(button, "click", { once: true })
.pipe(
map(() => getElement(".md-typeset", el)),
map(content => ({ hash: __md_hash(content.innerHTML) }))
)
}
/**
* Mount announcement bar
*
* @param el - Announcement bar element
*
* @returns Announcement bar component observable
*/
export function mountAnnounce(
el: HTMLElement
): Observable<Component<Announce>> {
if (!feature("announce.dismiss") || !el.childElementCount)
return EMPTY
/* Mount component on subscription */
return defer(() => {
const push$ = new Subject<Announce>()
push$
.pipe(
startWith({ hash: __md_get<number>("__announce") })
)
.subscribe(({ hash }) => {
if (hash && hash === (__md_get<number>("__announce") ?? hash)) {
el.hidden = true
/* Persist preference in local storage */
__md_set<number>("__announce", hash)
}
})
/* Create and return component */
return watchAnnounce(el)
.pipe(
tap(state => push$.next(state)),
finalize(() => push$.complete()),
map(state => ({ ref: el, ...state }))
)
})
}

View File

@ -0,0 +1,108 @@
/*
* Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import {
Observable,
Subject,
finalize,
map,
tap
} from "rxjs"
import { Component } from "../_"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Consent
*/
export interface Consent {
hidden: boolean /* Consent is hidden */
}
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
/**
* Watch options
*/
interface WatchOptions {
target$: Observable<HTMLElement> /* Target observable */
}
/**
* Mount options
*/
interface MountOptions {
target$: Observable<HTMLElement> /* Target observable */
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch consent
*
* @param el - Consent element
* @param options - Options
*
* @returns Consent observable
*/
export function watchConsent(
el: HTMLElement, { target$ }: WatchOptions
): Observable<Consent> {
return target$
.pipe(
map(target => ({ hidden: target !== el }))
)
}
/* ------------------------------------------------------------------------- */
/**
* Mount consent
*
* @param el - Consent element
* @param options - Options
*
* @returns Consent component observable
*/
export function mountConsent(
el: HTMLElement, options: MountOptions
): Observable<Component<Consent>> {
const internal$ = new Subject<Consent>()
internal$.subscribe(({ hidden }) => {
el.hidden = hidden
})
/* Create and return component */
return watchConsent(el, options)
.pipe(
tap(state => internal$.next(state)),
finalize(() => internal$.complete()),
map(state => ({ ref: el, ...state }))
)
}

View File

@ -21,6 +21,8 @@
*/ */
export * from "./_" export * from "./_"
export * from "./announce"
export * from "./consent"
export * from "./content" export * from "./content"
export * from "./dialog" export * from "./dialog"
export * from "./header" export * from "./header"

View File

@ -44,8 +44,10 @@
@import "main/layout/banner"; @import "main/layout/banner";
@import "main/layout/base"; @import "main/layout/base";
@import "main/layout/clipboard"; @import "main/layout/clipboard";
@import "main/layout/consent";
@import "main/layout/content"; @import "main/layout/content";
@import "main/layout/dialog"; @import "main/layout/dialog";
@import "main/layout/feedback";
@import "main/layout/footer"; @import "main/layout/footer";
@import "main/layout/form"; @import "main/layout/form";
@import "main/layout/header"; @import "main/layout/header";

View File

@ -47,4 +47,17 @@
padding: 0 px2rem(16px); padding: 0 px2rem(16px);
font-size: px2rem(14px); font-size: px2rem(14px);
} }
// Banner button
&__button {
float: right;
color: inherit;
cursor: pointer;
transition: opacity 250ms;
// Button on hover
&:hover {
opacity: 0.7;
}
}
} }

View File

@ -0,0 +1,127 @@
////
/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
///
/// Permission is hereby granted, free of charge, to any person obtaining a
/// copy of this software and associated documentation files (the "Software"),
/// to deal in the Software without restriction, including without limitation
/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
/// and/or sell copies of the Software, and to permit persons to whom the
/// Software is furnished to do so, subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in
/// all copies or substantial portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
/// DEALINGS
////
// ----------------------------------------------------------------------------
// Keyframes
// ----------------------------------------------------------------------------
// Show consent
@keyframes consent {
0% {
transform: translateY(100%);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
// Show consent overlay
@keyframes overlay {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
// ----------------------------------------------------------------------------
// Rules
// ----------------------------------------------------------------------------
// Consent
.md-consent {
// Consent overlay
&__overlay {
position: fixed;
top: 0;
z-index: 5;
width: 100%;
height: 100%;
background-color: hsla(0, 0%, 0%, 0.54);
opacity: 1;
backdrop-filter: blur(px2rem(2px));
animation: overlay 250ms both;
}
// Consent wrapper
&__inner {
position: fixed;
bottom: 0;
z-index: 5;
width: 100%;
max-height: 100%;
padding: 0;
overflow: auto;
background-color: var(--md-default-bg-color);
border: 0;
border-radius: px2rem(2px);
box-shadow:
0 0 px2rem(4px) rgba(0, 0, 0, 0.1),
0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0.2);
animation: consent 500ms cubic-bezier(0.1, 0.7, 0.1, 1) both;
}
// Consent form
&__form {
padding: px2rem(16px);
}
// Consent settings
&__settings {
display: none;
margin: 1em 0;
// Show settings
input:checked + & {
display: block;
}
}
// Consent controls
&__controls {
margin-bottom: px2rem(16px);
// Consent control button
.md-typeset & .md-button {
display: inline;
// [tablet +]: Align buttons horizontally
@include break-to-device(mobile) {
display: block;
width: 100%;
margin-top: px2rem(8px);
text-align: center;
}
}
}
// Ensure users realize that labels are clickaböe
label {
cursor: pointer;
}
}

View File

@ -0,0 +1,110 @@
////
/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
///
/// Permission is hereby granted, free of charge, to any person obtaining a
/// copy of this software and associated documentation files (the "Software"),
/// to deal in the Software without restriction, including without limitation
/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
/// and/or sell copies of the Software, and to permit persons to whom the
/// Software is furnished to do so, subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in
/// all copies or substantial portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
/// DEALINGS
////
// ----------------------------------------------------------------------------
// Rules
// ----------------------------------------------------------------------------
// Was this page helpful?
.md-feedback {
margin: 2em 0 1em;
text-align: center;
// Feedback fieldset
fieldset {
margin: 0;
padding: 0;
border: none;
}
// Feedback title
&__title {
margin: 1em auto;
font-weight: 700;
}
// Feedback wrapper
&__inner {
position: relative;
}
// Feedback list
&__list {
position: relative;
display: flex;
flex-wrap: wrap;
align-content: baseline;
justify-content: center;
// Feedback icon on hover
&:hover .md-icon:not(:disabled) {
color: var(--md-default-fg-color--lighter);
}
// Adjust height after submission
:disabled & {
min-height: px2rem(36px);
}
}
// Feedback icon
&__icon {
flex-shrink: 0;
margin: 0 px2rem(2px);
color: var(--md-default-fg-color--light);
cursor: pointer;
transition: color 125ms;
// Feedback icon on hover
&:not(:disabled).md-icon:hover {
color: var(--md-accent-fg-color);
}
// Feedback icon after submit
&:disabled {
color: var(--md-default-fg-color--lightest);
pointer-events: none;
}
}
// Feedback note
&__note {
position: relative;
transform: translateY(px2rem(8px));
opacity: 0;
transition:
transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),
opacity 150ms;
// Feedback note value
> * {
max-width: px2rem(320px);
margin: 0 auto;
}
// Show after submission
:disabled & {
transform: translateY(0);
opacity: 1;
}
}
}

View File

@ -204,8 +204,23 @@
{% if self.announce() %} {% if self.announce() %}
<aside class="md-banner"> <aside class="md-banner">
<div class="md-banner__inner md-grid md-typeset"> <div class="md-banner__inner md-grid md-typeset">
<!-- Button to dismiss announcement -->
{% if "announce.dismiss" in features %}
<button
class="md-banner__button md-icon"
aria-label="{{ lang.t('announce.dismiss') }}"
>
{% include ".icons/material/close.svg" %}
</button>
{% endif %}
<!-- Announcement bar content -->
{% block announce %}{% endblock %} {% block announce %}{% endblock %}
</div> </div>
{% if "announce.dismiss" in features %}
{% include "partials/javascripts/announce.html" %}
{% endif %}
</aside> </aside>
{% endif %} {% endif %}
</div> </div>
@ -213,14 +228,14 @@
<!-- Version warning --> <!-- Version warning -->
{% if config.extra.version %} {% if config.extra.version %}
<div data-md-component="outdated" hidden> <div data-md-component="outdated" hidden>
<aside class="md-banner md-banner--warning"> {% if self.outdated() %}
{% if self.outdated() %} <aside class="md-banner md-banner--warning">
<div class="md-banner__inner md-grid md-typeset"> <div class="md-banner__inner md-grid md-typeset">
{% block outdated %}{% endblock %} {% block outdated %}{% endblock %}
</div> </div>
{% include "partials/javascripts/outdated.html" %} {% include "partials/javascripts/outdated.html" %}
{% endif %} </aside>
</aside> {% endif %}
</div> </div>
{% endif %} {% endif %}
@ -328,6 +343,21 @@
<div class="md-dialog__inner md-typeset"></div> <div class="md-dialog__inner md-typeset"></div>
</div> </div>
<!-- Consent -->
{% if config.extra.consent %}
<div class="md-consent" data-md-component="consent" id="__consent" hidden>
<div class="md-consent__overlay"></div>
<aside class="md-consent__inner">
<form class="md-consent__form md-grid md-typeset" name="consent">
{% include "partials/consent.html" %}
</form>
</aside>
</div>
<!-- User preference: consent -->
{% include "partials/javascripts/consent.html" %}
{% endif %}
<!-- Theme-related configuration --> <!-- Theme-related configuration -->
{% block config %} {% block config %}
{%- set app = { {%- set app = {

View File

@ -46,9 +46,8 @@
{% endif %} {% endif %}
<!-- <!--
Hack: check whether the content contains a h1 headline. If it Hack: check whether the content contains a h1 headline. If it doesn't, the
doesn't, the page title (or respectively site name) is used page title (or respectively site name) is used as the main headline.
as the main headline.
--> -->
{% if not "\x3ch1" in page.content %} {% if not "\x3ch1" in page.content %}
<h1>{{ page.title | d(config.site_name, true)}}</h1> <h1>{{ page.title | d(config.site_name, true)}}</h1>
@ -64,3 +63,6 @@
) %} ) %}
{% include "partials/source-file.html" %} {% include "partials/source-file.html" %}
{% endif %} {% endif %}
<!-- Was this page helpful? -->
{% include "partials/feedback.html" %}

91
src/partials/consent.html Normal file
View File

@ -0,0 +1,91 @@
<!--
Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
-->
{% import "partials/language.html" as lang with context %}
<!-- Determine cookies (default to analytics, if present) -->
{% set cookies = config.extra.consent.cookies %}
{% if config.extra.analytics and not cookies %}
{% set cookies = { "analytics": "Google Analytics" } %}
{% endif %}
<!-- Determine actions -->
{% set actions = config.extra.consent.actions %}
{% if not actions %}
{% set actions = ["accept", "manage"] %}
{% endif %}
<!-- Consent title -->
<h4>{{ config.extra.consent.title }}</h4>
<p>{{ config.extra.consent.description }}</p>
<!-- Consent settings -->
<input type="checkbox" class="md-toggle" id="__settings" />
<div class="md-consent__settings">
<ul class="task-list">
{% for type in cookies %}
{% if cookies[type] is string %}
{% set name = cookies[type] %}
{% set checked = "checked" %}
{% else %}
{% set name = cookies[type].name %}
{% if cookies[type].checked %}
{% set checked = "checked" %}
{% endif %}
{% endif %}
<li class="task-list-item">
<label class="task-list-control">
<input type="checkbox" name="{{ type }}" {{ checked }}>
<span class="task-list-indicator"></span>
{{ name }}
<label>
</li>
{% endfor %}
</ul>
</div>
<!-- Consent controls -->
<div class="md-consent__controls">
{% for action in actions %}
<!-- Button to accept cookies -->
{% if action == "accept" %}
<button class="md-button md-button--primary">
{{- lang.t("consent.accept") -}}
</button>
{% endif %}
<!-- Button to reject cookies -->
{% if action == "reject" %}
<button type="reset" class="md-button md-button--primary">
{{- lang.t("consent.reject") -}}
</button>
{% endif %}
<!-- Button to manage settings -->
{% if action == "manage" %}
<label class="md-button" for="__settings">
{{- lang.t("consent.manage") -}}
</label>
{% endif %}
{% endfor %}
</div>

View File

@ -37,9 +37,8 @@
{% endif %} {% endif %}
<!-- <!--
Hack: check whether the content contains a h1 headline. If it Hack: check whether the content contains a h1 headline. If it doesn't, the
doesn't, the page title (or respectively site name) is used page title (or respectively site name) is used as the main headline.
as the main headline.
--> -->
{% if not "\x3ch1" in page.content %} {% if not "\x3ch1" in page.content %}
<h1>{{ page.title | d(config.site_name, true)}}</h1> <h1>{{ page.title | d(config.site_name, true)}}</h1>
@ -55,3 +54,6 @@
) %} ) %}
{% include "partials/source-file.html" %} {% include "partials/source-file.html" %}
{% endif %} {% endif %}
<!-- Was this page helpful? -->
{% include "partials/feedback.html" %}

View File

@ -0,0 +1,85 @@
<!--
Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
-->
<!-- Determine feedback configuration -->
{% if config.extra.analytics %}
{% set feedback = config.extra.analytics.feedback %}
{% endif %}
<!-- Determine whether to show feedback -->
{% if page and page.meta and page.meta.hide %}
{% if "feedback" in page.meta.hide %}
{% set feedback = None %}
{% endif %}
{% endif %}
<!-- Was this page helpful? -->
{% if feedback %}
<form class="md-feedback" name="feedback" hidden>
<fieldset>
<legend class="md-feedback__title">
{{ feedback.title }}
</legend>
<div class="md-feedback__inner">
<!-- Feedback ratings -->
<div class="md-feedback__list">
{% for rating in feedback.ratings %}
<button
class="md-feedback__icon md-icon"
type="submit"
title="{{ rating.name }}"
data-md-value="{{ rating.data }}"
>
{% include ".icons/" ~ rating.icon ~ ".svg" %}
</button>
{% endfor %}
</div>
<!-- Feedback rating notes (shown after submission) -->
<div class="md-feedback__note">
{% for rating in feedback.ratings %}
<div data-md-value="{{ rating.data }}" hidden>
{% set url = "/" ~ page.url %}
<!-- Determine title -->
{% if page and page.meta and page.meta.title %}
{% set title = page.meta.title | urlencode %}
{% else %}
{% set title = page.title | urlencode %}
{% endif %}
<!-- Legacy, deprecated, removed in next major version -->
{% if "{}" in rating.note %}
{{ rating.note.format(url, title) }}
<!-- Replace {url} and {title} placeholders in note -->
{% else %}
{{ rating.note.format(url = url, title = title) }}
{% endif %}
</div>
{% endfor %}
</div>
</div>
</fieldset>
</form>
{% endif %}

View File

@ -28,4 +28,22 @@
<!-- Set up analytics provider --> <!-- Set up analytics provider -->
{% if provider %} {% if provider %}
{% include "partials/integrations/analytics/" ~ provider ~ ".html" %} {% include "partials/integrations/analytics/" ~ provider ~ ".html" %}
<!-- Consent necessary -->
{% if config.extra.consent %}
<script>
if (typeof __md_analytics !== "undefined") {
var consent = __md_get("__consent")
if (consent && consent.analytics)
__md_analytics()
}
</script>
<!-- Consent unnecessary -->
{% else %}
<script>
if (typeof __md_analytics !== "undefined")
__md_analytics()
</script>
{% endif %}
{% endif %} {% endif %}

View File

@ -27,69 +27,142 @@
<!-- Google Analytics 4 (G-XXXXXXXXXX) --> <!-- Google Analytics 4 (G-XXXXXXXXXX) -->
{% if property.startswith("G-") %} {% if property.startswith("G-") %}
<script> <script id="__analytics">
window.dataLayer = window.dataLayer || [] function __md_analytics() {
function gtag() { dataLayer.push(arguments) } window.dataLayer = window.dataLayer || []
function gtag() { dataLayer.push(arguments) }
/* Set up integration and send page view */ /* Set up integration and send page view */
gtag("js", new Date()) gtag("js", new Date())
gtag("config", "{{ property }}") gtag("config", "{{ property }}")
/* Register event handlers after documented loaded */ /* Register event handlers after documented loaded */
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
if (document.forms.search) {
var query = document.forms.search.query
query.addEventListener("blur", function() {
if (this.value)
gtag("event", "search", { search_term: this.value })
})
}
/* Send page view on location change */ /* Set up search tracking */
if (typeof location$ !== "undefined") if (document.forms.search) {
location$.subscribe(function(url) { var query = document.forms.search.query
gtag("config", "{{ property }}", { query.addEventListener("blur", function() {
page_path: url.pathname if (this.value)
gtag("event", "search", { search_term: this.value })
}) })
}) }
})
</script>
<script async src="https://www.googletagmanager.com/gtag/js?id={{ property }}">
</script>
<!-- Google Analytics (UA-XXXXXXXX-X) --> /* Set up feedback, i.e. "Was this page helpful?" */
{% elif property.startswith("UA-") %} if (document.forms.feedback) {
<script> var feedback = document.forms.feedback
window.ga = window.ga || function() { for (var button of feedback.querySelectorAll("[type=submit]")) {
(ga.q = ga.q || []).push(arguments) button.addEventListener("click", function(ev) {
} ev.preventDefault()
ga.l = +new Date()
/* Set up integration and send page view */ /* Retrieve and send data */
ga("create", "{{ property }}", "auto") var page = document.location.pathname
ga("set", "anonymizeIp", true) var data = this.getAttribute("data-md-value")
ga("send", "pageview") gtag("event", "feedback", { page, data })
/* Register event handlers after documented loaded */ /* Disable form and show note, if given */
document.addEventListener("DOMContentLoaded", function() { feedback.firstElementChild.disabled = true
if (document.forms.search) { var note = feedback.querySelector(
var query = document.forms.search.query ".md-feedback__note [data-md-value='" + data + "']"
query.addEventListener("blur", function() { )
if (this.value) { if (note)
var path = document.location.pathname; note.hidden = false
ga("send", "pageview", path + "?q=" + this.value) })
/* Show feedback */
feedback.hidden = false
} }
}) }
}
/* Send page view on location change */ /* Send page view on location change */
if (typeof location$ !== "undefined") if (typeof location$ !== "undefined")
location$.subscribe(function(url) { location$.subscribe(function(url) {
ga("send", "pageview", url.pathname) gtag("config", "{{ property }}", {
}) page_path: url.pathname
}) })
})
})
/* Create script tag */
var script = document.createElement("script")
script.async = true
script.src = "https://www.googletagmanager.com/gtag/js?id={{ property }}"
/* Inject script tag */
var container = document.getElementById("__analytics")
container.insertAdjacentElement("afterEnd", script)
}
</script>
<!-- Universal Analytics (UA-XXXXXXXX-X) -->
{% elif property.startswith("UA-") %}
<script id="__analytics">
function __md_analytics() {
window.ga = window.ga || function() {
(ga.q = ga.q || []).push(arguments)
}
ga.l = +new Date()
/* Set up integration and send page view */
ga("create", "{{ property }}", "auto")
ga("set", "anonymizeIp", true)
ga("send", "pageview")
/* Register event handlers after documented loaded */
document.addEventListener("DOMContentLoaded", function() {
/* Set up search tracking */
if (document.forms.search) {
var query = document.forms.search.query
query.addEventListener("blur", function() {
if (this.value) {
var page = document.location.pathname;
ga("send", "pageview", page + "?q=" + this.value)
}
})
}
/* Set up feedback, i.e. "Was this page helpful?" */
if (document.forms.feedback) {
var feedback = document.forms.feedback
for (var button of feedback.querySelectorAll("[type=submit]")) {
button.addEventListener("click", function(ev) {
ev.preventDefault()
/* Retrieve and send data */
var page = document.location.pathname
var data = this.getAttribute("data-md-value")
ga("send", "event", "feedback", "click", page, data)
/* Disable form and show note, if given */
feedback.firstElementChild.disabled = true
var note = feedback.querySelector(
".md-feedback__note [data-md-value='" + data + "']"
)
if (note)
note.hidden = false
})
/* Show feedback */
feedback.hidden = false
}
}
/* Send page view on location change */
if (typeof location$ !== "undefined")
location$.subscribe(function(url) {
ga("send", "pageview", url.pathname)
})
})
/* Create script tag */
var script = document.createElement("script")
script.async = true
script.src = "https://www.google-analytics.com/analytics.js"
/* Inject script tag */
var container = document.getElementById("__analytics")
container.insertAdjacentElement("afterEnd", script)
}
</script> </script>
<script async src="https://www.google-analytics.com/analytics.js"></script>
{% endif %} {% endif %}

View File

@ -0,0 +1,31 @@
<!--
Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
-->
<!-- Announcement bar -->
<script>
var el = document.querySelector("[data-md-component=announce]")
if (el) {
var content = el.querySelector(".md-typeset")
if (__md_hash(content.innerHTML) === __md_get("__announce"))
el.hidden = true
}
</script>

View File

@ -29,6 +29,9 @@
/* Compute base path once to integrate with instant loading */ /* Compute base path once to integrate with instant loading */
__md_scope = new URL("{{ config.extra.scope | d(base_url) }}", location) __md_scope = new URL("{{ config.extra.scope | d(base_url) }}", location)
/* Compute hash from the given string - see https://bit.ly/3pvPjXG */
__md_hash = v => [...v].reduce((h, c) => (h << 5) - h + c.charCodeAt(0), 0)
/* Fetch the value for a key from the given storage */ /* Fetch the value for a key from the given storage */
__md_get = (key, storage = localStorage, scope = __md_scope) => ( __md_get = (key, storage = localStorage, scope = __md_scope) => (
JSON.parse(storage.getItem(scope.pathname + "." + key)) JSON.parse(storage.getItem(scope.pathname + "." + key))

View File

@ -0,0 +1,62 @@
<!--
Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
-->
<!-- User-preference: consent -->
<script>
var consent = __md_get("__consent")
if (consent) {
for (var input of document.forms.consent.elements)
if (input.name)
input.checked = consent[input.name] || false
/* Show consent with a small delay, but not if browsing locally */
} else if (location.protocol !== "file:") {
setTimeout(function() {
var el = document.querySelector("[data-md-component=consent]")
el.hidden = false
}, 250)
}
/* Intercept submission of consent form */
var form = document.forms.consent
for (var action of ["submit", "reset"])
form.addEventListener(action, function(ev) {
ev.preventDefault()
/* Reject all cookies */
if (ev.type === "reset")
for (var input of document.forms.consent.elements)
if (input.name)
input.checked = false
/* Grab and serialize form data */
console.log(new FormData(form))
__md_set("__consent", Object.fromEntries(
Array.from(new FormData(form).keys())
.map(function(key) { return [key, true] })
))
/* Remove anchor to omit consent from reappearing and reload */
location.hash = '';
location.reload()
})
</script>

View File

@ -23,8 +23,12 @@
<!-- Translations: German --> <!-- Translations: German -->
{% macro t(key) %}{{ { {% macro t(key) %}{{ {
"language": "de", "language": "de",
"announce.dismiss": "Nicht mehr anzeigen",
"clipboard.copy": "In Zwischenablage kopieren", "clipboard.copy": "In Zwischenablage kopieren",
"clipboard.copied": "In Zwischenablage kopiert", "clipboard.copied": "In Zwischenablage kopiert",
"consent.accept": "Akzeptieren",
"consent.manage": "Einstellungen",
"consent.reject": "Ablehnen",
"edit.link.title": "Seite editieren", "edit.link.title": "Seite editieren",
"footer.previous": "Zurück", "footer.previous": "Zurück",
"footer.next": "Weiter", "footer.next": "Weiter",

View File

@ -24,8 +24,12 @@
{% macro t(key) %}{{ { {% macro t(key) %}{{ {
"language": "en", "language": "en",
"direction": "ltr", "direction": "ltr",
"announce.dismiss": "Don't show this again",
"clipboard.copy": "Copy to clipboard", "clipboard.copy": "Copy to clipboard",
"clipboard.copied": "Copied to clipboard", "clipboard.copied": "Copied to clipboard",
"consent.accept": "Accept",
"consent.manage": "Manage settings",
"consent.reject": "Reject",
"edit.link.title": "Edit this page", "edit.link.title": "Edit this page",
"footer.previous": "Previous", "footer.previous": "Previous",
"footer.next": "Next", "footer.next": "Next",

View File

@ -25,6 +25,8 @@
"language": "es", "language": "es",
"clipboard.copy": "Copiar al portapapeles", "clipboard.copy": "Copiar al portapapeles",
"clipboard.copied": "Copiado al portapapeles", "clipboard.copied": "Copiado al portapapeles",
"consent.accept": "Aceptar",
"consent.manage": "Gestionar cookies",
"edit.link.title": "Editar esta página", "edit.link.title": "Editar esta página",
"footer.previous": "Anterior", "footer.previous": "Anterior",
"footer.next": "Siguiente", "footer.next": "Siguiente",

View File

@ -25,6 +25,9 @@
"language": "fr", "language": "fr",
"clipboard.copy": "Copier dans le presse-papier", "clipboard.copy": "Copier dans le presse-papier",
"clipboard.copied": "Copié dans le presse-papier", "clipboard.copied": "Copié dans le presse-papier",
"consent.accept": "Accepter",
"consent.manage": "Paramétrer vos choix",
"consent.reject": "Refuser",
"edit.link.title": "Editer cette page", "edit.link.title": "Editer cette page",
"footer.previous": "Précédent", "footer.previous": "Précédent",
"footer.next": "Suivant", "footer.next": "Suivant",

View File

@ -25,6 +25,8 @@
"language": "sv", "language": "sv",
"clipboard.copy": "Kopiera till urklipp", "clipboard.copy": "Kopiera till urklipp",
"clipboard.copied": "Kopierat till urklipp", "clipboard.copied": "Kopierat till urklipp",
"consent.accept": "Acceptera",
"consent.manage": "Hantera inställningar",
"edit.link.title": "Redigera sidan", "edit.link.title": "Redigera sidan",
"footer.previous": "Föregående", "footer.previous": "Föregående",
"footer.next": "Nästa", "footer.next": "Nästa",

View File

@ -23,8 +23,12 @@
<!-- Translations: Chinese (Taiwanese) --> <!-- Translations: Chinese (Taiwanese) -->
{% macro t(key) %}{{ { {% macro t(key) %}{{ {
"language": "zh-Hant", "language": "zh-Hant",
"announce.dismiss": "不再顯示此訊息",
"clipboard.copy": "複製", "clipboard.copy": "複製",
"clipboard.copied": "已複製", "clipboard.copied": "已複製",
"consent.accept": "同意",
"consent.manage": "管理設定",
"consent.reject": "拒絕",
"edit.link.title": "編輯此頁", "edit.link.title": "編輯此頁",
"footer.previous": "上一頁", "footer.previous": "上一頁",
"footer.next": "下一頁", "footer.next": "下一頁",

View File

@ -51,6 +51,15 @@ declare global {
*/ */
const __search: GlobalSearchConfig | undefined const __search: GlobalSearchConfig | undefined
/**
* Compute hash from the given string
*
* @param value - String value
*
* @returns Hash
*/
function __md_hash(value: string): number
/** /**
* Fetch the value for a key from the given storage * Fetch the value for a key from the given storage
* *