mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2024-11-30 18:24:35 +01:00
Merged features tied to Royal Gold funding goal
This commit is contained in:
parent
5a9c295822
commit
de438d5d9d
29
material/assets/javascripts/bundle.0bc13b87.min.js
vendored
Normal file
29
material/assets/javascripts/bundle.0bc13b87.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
8
material/assets/javascripts/bundle.0bc13b87.min.js.map
Normal file
8
material/assets/javascripts/bundle.0bc13b87.min.js.map
Normal file
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
1
material/assets/stylesheets/main.476df7d4.min.css
vendored
Normal file
1
material/assets/stylesheets/main.476df7d4.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
material/assets/stylesheets/main.476df7d4.min.css.map
Normal file
1
material/assets/stylesheets/main.476df7d4.min.css.map
Normal file
File diff suppressed because one or more lines are too long
@ -34,7 +34,7 @@
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% 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 %}
|
||||
{% set palette = config.theme.palette %}
|
||||
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.cbb835fc.min.css' | url }}">
|
||||
@ -102,21 +102,29 @@
|
||||
{% if self.announce() %}
|
||||
<aside class="md-banner">
|
||||
<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 %}
|
||||
</div>
|
||||
{% if "announce.dismiss" in features %}
|
||||
{% include "partials/javascripts/announce.html" %}
|
||||
{% endif %}
|
||||
</aside>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if config.extra.version %}
|
||||
<div data-md-component="outdated" hidden>
|
||||
<aside class="md-banner md-banner--warning">
|
||||
{% if self.outdated() %}
|
||||
<aside class="md-banner md-banner--warning">
|
||||
<div class="md-banner__inner md-grid md-typeset">
|
||||
{% block outdated %}{% endblock %}
|
||||
</div>
|
||||
{% include "partials/javascripts/outdated.html" %}
|
||||
{% endif %}
|
||||
</aside>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% block header %}
|
||||
@ -182,6 +190,17 @@
|
||||
<div class="md-dialog" data-md-component="dialog">
|
||||
<div class="md-dialog__inner md-typeset"></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 %}
|
||||
{%- set app = {
|
||||
"base": base_url,
|
||||
@ -216,7 +235,7 @@
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% 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"] %}
|
||||
<script src="{{ path | url }}"></script>
|
||||
{% endfor %}
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -19,5 +19,5 @@
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script src="{{ 'overrides/assets/javascripts/bundle.39654835.min.js' | url }}"></script>
|
||||
<script src="{{ 'overrides/assets/javascripts/bundle.a47bf9ec.min.js' | url }}"></script>
|
||||
{% endblock %}
|
||||
|
@ -24,3 +24,4 @@
|
||||
) %}
|
||||
{% include "partials/source-file.html" %}
|
||||
{% endif %}
|
||||
{% include "partials/feedback.html" %}
|
||||
|
56
material/partials/consent.html
Normal file
56
material/partials/consent.html
Normal 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>
|
@ -19,3 +19,4 @@
|
||||
) %}
|
||||
{% include "partials/source-file.html" %}
|
||||
{% endif %}
|
||||
{% include "partials/feedback.html" %}
|
||||
|
46
material/partials/feedback.html
Normal file
46
material/partials/feedback.html
Normal 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 %}
|
@ -6,4 +6,9 @@
|
||||
{% endif %}
|
||||
{% if provider %}
|
||||
{% 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 %}
|
||||
|
@ -5,9 +5,7 @@
|
||||
{% set property = config.extra.analytics.property | d("", true) %}
|
||||
{% endif %}
|
||||
{% 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&>ag("event","search",{search_term:this.value})}),"undefined"!=typeof location$&&location$.subscribe(function(e){gtag("config","{{ property }}",{page_path:e.pathname})})})</script>
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id={{ property }}"></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>
|
||||
{% 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 async src="https://www.google-analytics.com/analytics.js"></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>
|
||||
{% endif %}
|
||||
|
4
material/partials/javascripts/announce.html
Normal file
4
material/partials/javascripts/announce.html
Normal 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>
|
@ -1,4 +1,4 @@
|
||||
{#-
|
||||
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>
|
||||
|
4
material/partials/javascripts/consent.html
Normal file
4
material/partials/javascripts/consent.html
Normal 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>
|
@ -3,8 +3,12 @@
|
||||
-#}
|
||||
{% macro t(key) %}{{ {
|
||||
"language": "de",
|
||||
"announce.dismiss": "Nicht mehr anzeigen",
|
||||
"clipboard.copy": "In Zwischenablage kopieren",
|
||||
"clipboard.copied": "In Zwischenablage kopiert",
|
||||
"consent.accept": "Akzeptieren",
|
||||
"consent.manage": "Einstellungen",
|
||||
"consent.reject": "Ablehnen",
|
||||
"edit.link.title": "Seite editieren",
|
||||
"footer.previous": "Zurück",
|
||||
"footer.next": "Weiter",
|
||||
|
@ -4,8 +4,12 @@
|
||||
{% macro t(key) %}{{ {
|
||||
"language": "en",
|
||||
"direction": "ltr",
|
||||
"announce.dismiss": "Don't show this again",
|
||||
"clipboard.copy": "Copy to clipboard",
|
||||
"clipboard.copied": "Copied to clipboard",
|
||||
"consent.accept": "Accept",
|
||||
"consent.manage": "Manage settings",
|
||||
"consent.reject": "Reject",
|
||||
"edit.link.title": "Edit this page",
|
||||
"footer.previous": "Previous",
|
||||
"footer.next": "Next",
|
||||
|
@ -5,6 +5,8 @@
|
||||
"language": "es",
|
||||
"clipboard.copy": "Copiar al portapapeles",
|
||||
"clipboard.copied": "Copiado al portapapeles",
|
||||
"consent.accept": "Aceptar",
|
||||
"consent.manage": "Gestionar cookies",
|
||||
"edit.link.title": "Editar esta página",
|
||||
"footer.previous": "Anterior",
|
||||
"footer.next": "Siguiente",
|
||||
|
@ -5,6 +5,9 @@
|
||||
"language": "fr",
|
||||
"clipboard.copy": "Copier 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",
|
||||
"footer.previous": "Précédent",
|
||||
"footer.next": "Suivant",
|
||||
|
@ -5,6 +5,8 @@
|
||||
"language": "sv",
|
||||
"clipboard.copy": "Kopiera till urklipp",
|
||||
"clipboard.copied": "Kopierat till urklipp",
|
||||
"consent.accept": "Acceptera",
|
||||
"consent.manage": "Hantera inställningar",
|
||||
"edit.link.title": "Redigera sidan",
|
||||
"footer.previous": "Föregående",
|
||||
"footer.next": "Nästa",
|
||||
|
@ -3,8 +3,12 @@
|
||||
-#}
|
||||
{% macro t(key) %}{{ {
|
||||
"language": "zh-Hant",
|
||||
"announce.dismiss": "不再顯示此訊息",
|
||||
"clipboard.copy": "複製",
|
||||
"clipboard.copied": "已複製",
|
||||
"consent.accept": "同意",
|
||||
"consent.manage": "管理設定",
|
||||
"consent.reject": "拒絕",
|
||||
"edit.link.title": "編輯此頁",
|
||||
"footer.previous": "上一頁",
|
||||
"footer.next": "下一頁",
|
||||
|
24
mkdocs.yml
24
mkdocs.yml
@ -49,6 +49,7 @@ theme:
|
||||
# Default values, taken from mkdocs_theme.yml
|
||||
language: en
|
||||
features:
|
||||
# - announce.dismiss
|
||||
- content.code.annotate
|
||||
# - content.tabs.link
|
||||
- content.tooltips
|
||||
@ -104,9 +105,30 @@ plugins:
|
||||
|
||||
# Customization
|
||||
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:
|
||||
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:
|
||||
- icon: fontawesome/brands/github
|
||||
link: https://github.com/squidfunk
|
||||
|
@ -30,6 +30,7 @@ import { getElement, getLocation } from "~/browser"
|
||||
* Feature flag
|
||||
*/
|
||||
export type Flag =
|
||||
| "announce.dismiss" /* Dismissable announcement bar */
|
||||
| "content.code.annotate" /* Code annotations */
|
||||
| "content.tabs.link" /* Link content tabs */
|
||||
| "header.autohide" /* Hide header */
|
||||
|
@ -56,7 +56,9 @@ import {
|
||||
import {
|
||||
getComponentElement,
|
||||
getComponentElements,
|
||||
mountAnnounce,
|
||||
mountBackToTop,
|
||||
mountConsent,
|
||||
mountContent,
|
||||
mountDialog,
|
||||
mountHeader,
|
||||
@ -177,6 +179,10 @@ const main$ = document$
|
||||
/* Set up control component observables */
|
||||
const control$ = merge(
|
||||
|
||||
/* Consent */
|
||||
...getComponentElements("consent")
|
||||
.map(el => mountConsent(el, { target$ })),
|
||||
|
||||
/* Dialog */
|
||||
...getComponentElements("dialog")
|
||||
.map(el => mountDialog(el, { alert$ })),
|
||||
@ -201,6 +207,10 @@ const control$ = merge(
|
||||
/* Set up content component observables */
|
||||
const content$ = defer(() => merge(
|
||||
|
||||
/* Announcement bar */
|
||||
...getComponentElements("announce")
|
||||
.map(el => mountAnnounce(el)),
|
||||
|
||||
/* Content */
|
||||
...getComponentElements("content")
|
||||
.map(el => mountContent(el, { target$, print$ })),
|
||||
|
@ -32,6 +32,7 @@ import { getElement, getElements } from "~/browser"
|
||||
export type ComponentType =
|
||||
| "announce" /* Announcement bar */
|
||||
| "container" /* Container */
|
||||
| "consent" /* Consent */
|
||||
| "content" /* Content */
|
||||
| "dialog" /* Dialog */
|
||||
| "header" /* Header */
|
||||
@ -76,6 +77,7 @@ export type Component<
|
||||
interface ComponentTypeMap {
|
||||
"announce": HTMLElement /* Announcement bar */
|
||||
"container": HTMLElement /* Container */
|
||||
"consent": HTMLElement /* Consent */
|
||||
"content": HTMLElement /* Content */
|
||||
"dialog": HTMLElement /* Dialog */
|
||||
"header": HTMLElement /* Header */
|
||||
|
110
src/assets/javascripts/components/announce/index.ts
Normal file
110
src/assets/javascripts/components/announce/index.ts
Normal 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 }))
|
||||
)
|
||||
})
|
||||
}
|
108
src/assets/javascripts/components/consent/index.ts
Normal file
108
src/assets/javascripts/components/consent/index.ts
Normal 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 }))
|
||||
)
|
||||
}
|
@ -21,6 +21,8 @@
|
||||
*/
|
||||
|
||||
export * from "./_"
|
||||
export * from "./announce"
|
||||
export * from "./consent"
|
||||
export * from "./content"
|
||||
export * from "./dialog"
|
||||
export * from "./header"
|
||||
|
@ -44,8 +44,10 @@
|
||||
@import "main/layout/banner";
|
||||
@import "main/layout/base";
|
||||
@import "main/layout/clipboard";
|
||||
@import "main/layout/consent";
|
||||
@import "main/layout/content";
|
||||
@import "main/layout/dialog";
|
||||
@import "main/layout/feedback";
|
||||
@import "main/layout/footer";
|
||||
@import "main/layout/form";
|
||||
@import "main/layout/header";
|
||||
|
@ -47,4 +47,17 @@
|
||||
padding: 0 px2rem(16px);
|
||||
font-size: px2rem(14px);
|
||||
}
|
||||
|
||||
// Banner button
|
||||
&__button {
|
||||
float: right;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
transition: opacity 250ms;
|
||||
|
||||
// Button on hover
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
127
src/assets/stylesheets/main/layout/_consent.scss
Normal file
127
src/assets/stylesheets/main/layout/_consent.scss
Normal 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;
|
||||
}
|
||||
}
|
110
src/assets/stylesheets/main/layout/_feedback.scss
Normal file
110
src/assets/stylesheets/main/layout/_feedback.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -204,8 +204,23 @@
|
||||
{% if self.announce() %}
|
||||
<aside class="md-banner">
|
||||
<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 %}
|
||||
</div>
|
||||
{% if "announce.dismiss" in features %}
|
||||
{% include "partials/javascripts/announce.html" %}
|
||||
{% endif %}
|
||||
</aside>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -213,14 +228,14 @@
|
||||
<!-- Version warning -->
|
||||
{% if config.extra.version %}
|
||||
<div data-md-component="outdated" hidden>
|
||||
<aside class="md-banner md-banner--warning">
|
||||
{% if self.outdated() %}
|
||||
<aside class="md-banner md-banner--warning">
|
||||
<div class="md-banner__inner md-grid md-typeset">
|
||||
{% block outdated %}{% endblock %}
|
||||
</div>
|
||||
{% include "partials/javascripts/outdated.html" %}
|
||||
{% endif %}
|
||||
</aside>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@ -328,6 +343,21 @@
|
||||
<div class="md-dialog__inner md-typeset"></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 -->
|
||||
{% block config %}
|
||||
{%- set app = {
|
||||
|
@ -46,9 +46,8 @@
|
||||
{% endif %}
|
||||
|
||||
<!--
|
||||
Hack: check whether the content contains a h1 headline. If it
|
||||
doesn't, the page title (or respectively site name) is used
|
||||
as the main headline.
|
||||
Hack: check whether the content contains a h1 headline. If it doesn't, the
|
||||
page title (or respectively site name) is used as the main headline.
|
||||
-->
|
||||
{% if not "\x3ch1" in page.content %}
|
||||
<h1>{{ page.title | d(config.site_name, true)}}</h1>
|
||||
@ -64,3 +63,6 @@
|
||||
) %}
|
||||
{% include "partials/source-file.html" %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Was this page helpful? -->
|
||||
{% include "partials/feedback.html" %}
|
||||
|
91
src/partials/consent.html
Normal file
91
src/partials/consent.html
Normal 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>
|
@ -37,9 +37,8 @@
|
||||
{% endif %}
|
||||
|
||||
<!--
|
||||
Hack: check whether the content contains a h1 headline. If it
|
||||
doesn't, the page title (or respectively site name) is used
|
||||
as the main headline.
|
||||
Hack: check whether the content contains a h1 headline. If it doesn't, the
|
||||
page title (or respectively site name) is used as the main headline.
|
||||
-->
|
||||
{% if not "\x3ch1" in page.content %}
|
||||
<h1>{{ page.title | d(config.site_name, true)}}</h1>
|
||||
@ -55,3 +54,6 @@
|
||||
) %}
|
||||
{% include "partials/source-file.html" %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Was this page helpful? -->
|
||||
{% include "partials/feedback.html" %}
|
||||
|
85
src/partials/feedback.html
Normal file
85
src/partials/feedback.html
Normal 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 %}
|
@ -28,4 +28,22 @@
|
||||
<!-- Set up analytics provider -->
|
||||
{% if provider %}
|
||||
{% 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 %}
|
||||
|
@ -27,7 +27,8 @@
|
||||
|
||||
<!-- Google Analytics 4 (G-XXXXXXXXXX) -->
|
||||
{% if property.startswith("G-") %}
|
||||
<script>
|
||||
<script id="__analytics">
|
||||
function __md_analytics() {
|
||||
window.dataLayer = window.dataLayer || []
|
||||
function gtag() { dataLayer.push(arguments) }
|
||||
|
||||
@ -37,6 +38,8 @@
|
||||
|
||||
/* 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() {
|
||||
@ -45,6 +48,32 @@
|
||||
})
|
||||
}
|
||||
|
||||
/* 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")
|
||||
gtag("event", "feedback", { 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) {
|
||||
@ -53,13 +82,22 @@
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id={{ property }}">
|
||||
|
||||
/* 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>
|
||||
|
||||
<!-- Google Analytics (UA-XXXXXXXX-X) -->
|
||||
<!-- Universal Analytics (UA-XXXXXXXX-X) -->
|
||||
{% elif property.startswith("UA-") %}
|
||||
<script>
|
||||
<script id="__analytics">
|
||||
function __md_analytics() {
|
||||
window.ga = window.ga || function() {
|
||||
(ga.q = ga.q || []).push(arguments)
|
||||
}
|
||||
@ -72,24 +110,59 @@
|
||||
|
||||
/* 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 path = document.location.pathname;
|
||||
ga("send", "pageview", path + "?q=" + 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 async src="https://www.google-analytics.com/analytics.js"></script>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
31
src/partials/javascripts/announce.html
Normal file
31
src/partials/javascripts/announce.html
Normal 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>
|
@ -29,6 +29,9 @@
|
||||
/* Compute base path once to integrate with instant loading */
|
||||
__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 */
|
||||
__md_get = (key, storage = localStorage, scope = __md_scope) => (
|
||||
JSON.parse(storage.getItem(scope.pathname + "." + key))
|
||||
|
62
src/partials/javascripts/consent.html
Normal file
62
src/partials/javascripts/consent.html
Normal 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>
|
@ -23,8 +23,12 @@
|
||||
<!-- Translations: German -->
|
||||
{% macro t(key) %}{{ {
|
||||
"language": "de",
|
||||
"announce.dismiss": "Nicht mehr anzeigen",
|
||||
"clipboard.copy": "In Zwischenablage kopieren",
|
||||
"clipboard.copied": "In Zwischenablage kopiert",
|
||||
"consent.accept": "Akzeptieren",
|
||||
"consent.manage": "Einstellungen",
|
||||
"consent.reject": "Ablehnen",
|
||||
"edit.link.title": "Seite editieren",
|
||||
"footer.previous": "Zurück",
|
||||
"footer.next": "Weiter",
|
||||
|
@ -24,8 +24,12 @@
|
||||
{% macro t(key) %}{{ {
|
||||
"language": "en",
|
||||
"direction": "ltr",
|
||||
"announce.dismiss": "Don't show this again",
|
||||
"clipboard.copy": "Copy to clipboard",
|
||||
"clipboard.copied": "Copied to clipboard",
|
||||
"consent.accept": "Accept",
|
||||
"consent.manage": "Manage settings",
|
||||
"consent.reject": "Reject",
|
||||
"edit.link.title": "Edit this page",
|
||||
"footer.previous": "Previous",
|
||||
"footer.next": "Next",
|
||||
|
@ -25,6 +25,8 @@
|
||||
"language": "es",
|
||||
"clipboard.copy": "Copiar al portapapeles",
|
||||
"clipboard.copied": "Copiado al portapapeles",
|
||||
"consent.accept": "Aceptar",
|
||||
"consent.manage": "Gestionar cookies",
|
||||
"edit.link.title": "Editar esta página",
|
||||
"footer.previous": "Anterior",
|
||||
"footer.next": "Siguiente",
|
||||
|
@ -25,6 +25,9 @@
|
||||
"language": "fr",
|
||||
"clipboard.copy": "Copier 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",
|
||||
"footer.previous": "Précédent",
|
||||
"footer.next": "Suivant",
|
||||
|
@ -25,6 +25,8 @@
|
||||
"language": "sv",
|
||||
"clipboard.copy": "Kopiera till urklipp",
|
||||
"clipboard.copied": "Kopierat till urklipp",
|
||||
"consent.accept": "Acceptera",
|
||||
"consent.manage": "Hantera inställningar",
|
||||
"edit.link.title": "Redigera sidan",
|
||||
"footer.previous": "Föregående",
|
||||
"footer.next": "Nästa",
|
||||
|
@ -23,8 +23,12 @@
|
||||
<!-- Translations: Chinese (Taiwanese) -->
|
||||
{% macro t(key) %}{{ {
|
||||
"language": "zh-Hant",
|
||||
"announce.dismiss": "不再顯示此訊息",
|
||||
"clipboard.copy": "複製",
|
||||
"clipboard.copied": "已複製",
|
||||
"consent.accept": "同意",
|
||||
"consent.manage": "管理設定",
|
||||
"consent.reject": "拒絕",
|
||||
"edit.link.title": "編輯此頁",
|
||||
"footer.previous": "上一頁",
|
||||
"footer.next": "下一頁",
|
||||
|
9
typings/_/index.d.ts
vendored
9
typings/_/index.d.ts
vendored
@ -51,6 +51,15 @@ declare global {
|
||||
*/
|
||||
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
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user