render compile
This commit is contained in:
@@ -0,0 +1 @@
|
||||
d5973a4a5020ae5c83a7342071519052
|
||||
@@ -2,13 +2,12 @@
|
||||
\providecommand\zref@newlabel[2]{}
|
||||
\providecommand\hyper@newdestlabel[2]{}
|
||||
\providecommand*\HyPL@Entry[1]{}
|
||||
\providecommand\babel@aux[2]{}
|
||||
\providecommand \babel@aux [2]{\global \let \babel@toc \@gobbletwo }
|
||||
\@nameuse{bbl@beforestart}
|
||||
\bibstyle{elsarticle-harv}
|
||||
\emailauthor{mingsu@rcees.ac.cn}{Ming Su}
|
||||
\emailauthor{yangmin@rcees.ac.cn}{Min Yang\corref {cor1}}
|
||||
\Newlabel{cor1}{1}
|
||||
\bibdata{../BB/Ref.bib,../BB/localRef.bib}
|
||||
\HyPL@Entry{0<</S/D>>}
|
||||
\babel@aux{english}{}
|
||||
\Newlabel{1}{a}
|
||||
@@ -18,16 +17,17 @@
|
||||
\newlabel{r:r1q0101}{{Bla blaBla blaBla blaBla blaBla bla Bla bla.}{}}
|
||||
\newlabel{r1q0101}{{1}{1}{Introduction}{section.1}{}}
|
||||
\newlabel{r1q0101end}{{1}{1}{Introduction}{section.1}{}}
|
||||
\newlabel{line:r1q0101}{{6}{1}{}{}{}}
|
||||
\newlabel{line:r1q0101end}{{6}{1}{}{}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {2}Materials and methods}{1}{section.2}\protected@file@percent }
|
||||
\newlabel{materials-and-methods}{{2}{1}{Materials and methods}{section.2}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {3}Results}{1}{section.3}\protected@file@percent }
|
||||
\newlabel{results}{{3}{1}{Results}{section.3}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {4}Discussion}{1}{section.4}\protected@file@percent }
|
||||
\newlabel{discussion}{{4}{1}{Discussion}{section.4}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {5}Conclusions}{1}{section.5}\protected@file@percent }
|
||||
\newlabel{conclusions}{{5}{1}{Conclusions}{section.5}{}}
|
||||
\newlabel{acknowledgements}{{5}{1}{Acknowledgements}{section*.1}{}}
|
||||
\@writefile{toc}{\contentsline {section}{Acknowledgements}{1}{section*.1}\protected@file@percent }
|
||||
\gdef \@abspage@last{1}
|
||||
\newlabel{line:r1q0101}{{10}{1}{}{}{}}
|
||||
\newlabel{line:r1q0101end}{{10}{1}{}{}{}}
|
||||
\bibdata{../BB/Ref.bib,../BB/localRef.bib}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {2}Materials and methods}{2}{section.2}\protected@file@percent }
|
||||
\newlabel{materials-and-methods}{{2}{2}{Materials and methods}{section.2}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {3}Results}{2}{section.3}\protected@file@percent }
|
||||
\newlabel{results}{{3}{2}{Results}{section.3}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {4}Discussion}{2}{section.4}\protected@file@percent }
|
||||
\newlabel{discussion}{{4}{2}{Discussion}{section.4}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {5}Conclusions}{2}{section.5}\protected@file@percent }
|
||||
\newlabel{conclusions}{{5}{2}{Conclusions}{section.5}{}}
|
||||
\newlabel{acknowledgements}{{5}{2}{Acknowledgements}{section*.1}{}}
|
||||
\@writefile{toc}{\contentsline {section}{Acknowledgements}{2}{section*.1}\protected@file@percent }
|
||||
\gdef \@abspage@last{2}
|
||||
|
||||
+35
-32
@@ -14,41 +14,38 @@ format:
|
||||
elsevier-html:
|
||||
css: _extensions/inst/css/style.css
|
||||
keep-md: true
|
||||
dplyr::filters:
|
||||
- author-info-blocks
|
||||
epub: default
|
||||
docx:
|
||||
reference-doc: _extensions/inst/word/MS.docx
|
||||
dplyr::filters:
|
||||
- author-info-blocks
|
||||
# elsevier-pdf:
|
||||
# keep-tex: false
|
||||
# keep-md: false
|
||||
# include-in-header: "./_extensions/quarto-journals/elsevier/partials/revision.tex"
|
||||
# latex-clean: false
|
||||
# header-includes:
|
||||
# # - \usepackage{threeparttable}
|
||||
# # - \usepackage{fontspec}
|
||||
# # - \usepackage{endfloat}
|
||||
# - \setmainfont{Calibri}
|
||||
# - \usepackage[UTF8, scheme=plain]{ctex}
|
||||
# - \usepackage{float}
|
||||
# - \usepackage[normalem]{ulem}
|
||||
# - \usepackage[doublespacing]{setspace}
|
||||
# - \usepackage{lineno}
|
||||
# - \linenumbers
|
||||
# journal:
|
||||
# formatting: preprint # preprint | review | doublebind
|
||||
# model: 1p
|
||||
# cite-style: authoryear
|
||||
# # graphical-abstract: ""
|
||||
# # highlights:
|
||||
# # - A comprehensive model was contructed to evaluate the risk of odor occurrences
|
||||
# # - Turbidity distribution determine underwater light and MIB-producing *Planktothrix*
|
||||
# # - Increasing flowrate increases turbidity that can control *Planktothrix* and MIB
|
||||
# # - Odor control based on flow regulation in source water is "green" and fundamental
|
||||
# filters:
|
||||
# - latex-environment
|
||||
elsevier-pdf:
|
||||
keep-tex: false
|
||||
keep-md: false
|
||||
include-in-header: "./_extensions/quarto-journals/elsevier/partials/revision.tex"
|
||||
latex-clean: false
|
||||
header-includes:
|
||||
# - \usepackage{threeparttable}
|
||||
# - \usepackage{fontspec}
|
||||
# - \usepackage{endfloat}
|
||||
- \setmainfont{Calibri}
|
||||
- \usepackage[UTF8, scheme=plain]{ctex}
|
||||
- \usepackage{float}
|
||||
- \usepackage[normalem]{ulem}
|
||||
- \usepackage[doublespacing]{setspace}
|
||||
- \usepackage{lineno}
|
||||
- \linenumbers
|
||||
journal:
|
||||
formatting: preprint # preprint | review | doublebind
|
||||
model: 1p
|
||||
cite-style: authoryear
|
||||
# graphical-abstract: ""
|
||||
# highlights:
|
||||
# - A comprehensive model was contructed to evaluate the risk of odor occurrences
|
||||
# - Turbidity distribution determine underwater light and MIB-producing *Planktothrix*
|
||||
# - Increasing flowrate increases turbidity that can control *Planktothrix* and MIB
|
||||
# - Odor control based on flow regulation in source water is "green" and fundamental
|
||||
filters:
|
||||
- latex-environment
|
||||
- authors-block
|
||||
commands: [clab]
|
||||
---
|
||||
|
||||
@@ -61,6 +58,8 @@ commands: [clab]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
::: {.content-visible when-format="html"}
|
||||
# Color tip {.unnumbered}
|
||||
|
||||
@@ -87,6 +86,10 @@ commands: [clab]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Introduction
|
||||
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
---
|
||||
msname: MS
|
||||
smname: SM
|
||||
abstract: ABSTRACT
|
||||
keywords:
|
||||
- KEYWORD1
|
||||
- KEYWORD2
|
||||
- KEYWORD3
|
||||
# date: last-modified
|
||||
bibliography: [../BB/Ref.bib, ../BB/localRef.bib]
|
||||
# csl: https://www.zotero.org/styles/elsevier-harvard
|
||||
csl: _extensions/inst/tex/elsevier-harvard.csl
|
||||
format:
|
||||
elsevier-html:
|
||||
css: _extensions/inst/css/style.css
|
||||
keep-md: true
|
||||
filters:
|
||||
- author-info-blocks
|
||||
epub: default
|
||||
docx:
|
||||
reference-doc: _extensions/inst/word/MS.docx
|
||||
filters:
|
||||
- author-info-blocks
|
||||
elsevier-pdf:
|
||||
keep-tex: true
|
||||
keep-md: true
|
||||
include-in-header: "./_extensions/quarto-journals/elsevier/partials/revision.tex"
|
||||
latex-clean: false
|
||||
header-includes:
|
||||
# - \usepackage{threeparttable}
|
||||
# - \usepackage{fontspec}
|
||||
# - \usepackage{endfloat}
|
||||
- \setmainfont{Calibri}
|
||||
# - \usepackage[UTF8, scheme=plain]{ctex}
|
||||
# - \usepackage{float}
|
||||
- \usepackage[normalem]{ulem}
|
||||
- \usepackage[doublespacing]{setspace}
|
||||
- \usepackage{lineno}
|
||||
- \linenumbers
|
||||
journal:
|
||||
formatting: preprint # preprint | review | doublebind
|
||||
model: 1p
|
||||
cite-style: authoryear
|
||||
# graphical-abstract: ""
|
||||
# highlights:
|
||||
# - A comprehensive model was contructed to evaluate the risk of odor occurrences
|
||||
# - Turbidity distribution determine underwater light and MIB-producing *Planktothrix*
|
||||
# - Increasing flowrate increases turbidity that can control *Planktothrix* and MIB
|
||||
# - Odor control based on flow regulation in source water is "green" and fundamental
|
||||
filters:
|
||||
- latex-environment
|
||||
commands: [clab]
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
::: {.content-visible when-format="html"}
|
||||
# Color tip {.unnumbered}
|
||||
|
||||
- [Something should be deleted]{.del}
|
||||
- [Something bad]{.bad}
|
||||
- [Something good]{.good}
|
||||
- [Something todo]{.todo}
|
||||
- [Something should be added]{.add}
|
||||
- [I am a comment]{.com}
|
||||
:::
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Introduction
|
||||
|
||||
Blabla. [Bla blaBla blaBla blaBla blaBla bla Bla bla.]{.clab options=r1q0101}
|
||||
|
||||
# Materials and methods
|
||||
|
||||
# Results
|
||||
|
||||
# Discussion
|
||||
|
||||
# Conclusions
|
||||
|
||||
# Acknowledgements {.unnumbered}
|
||||
|
||||
This work was financially supported by the National Natural Science Foundation of China (52030002, 51878649), and Youth Innovation Promotion Association CAS.
|
||||
|
||||
# References {.unnumbered}
|
||||
|
||||
@@ -46,6 +46,7 @@ format:
|
||||
filters:
|
||||
- latex-environment
|
||||
- authors-block
|
||||
ightbox: auto
|
||||
commands: [clab]
|
||||
---
|
||||
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
% Options for packages loaded elsewhere
|
||||
\PassOptionsToPackage{unicode}{hyperref}
|
||||
\PassOptionsToPackage{hyphens}{url}
|
||||
\PassOptionsToPackage{dvipsnames,svgnames,x11names}{xcolor}
|
||||
%
|
||||
\documentclass[
|
||||
authoryear,
|
||||
preprint,
|
||||
1p]{elsarticle}
|
||||
|
||||
\usepackage{amsmath,amssymb}
|
||||
\usepackage{iftex}
|
||||
\ifPDFTeX
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage{textcomp} % provide euro and other symbols
|
||||
\else % if luatex or xetex
|
||||
\usepackage{unicode-math}
|
||||
\defaultfontfeatures{Scale=MatchLowercase}
|
||||
\defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1}
|
||||
\fi
|
||||
\usepackage{lmodern}
|
||||
\ifPDFTeX\else
|
||||
% xetex/luatex font selection
|
||||
\fi
|
||||
% Use upquote if available, for straight quotes in verbatim environments
|
||||
\IfFileExists{upquote.sty}{\usepackage{upquote}}{}
|
||||
\IfFileExists{microtype.sty}{% use microtype if available
|
||||
\usepackage[]{microtype}
|
||||
\UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts
|
||||
}{}
|
||||
\makeatletter
|
||||
\@ifundefined{KOMAClassName}{% if non-KOMA class
|
||||
\IfFileExists{parskip.sty}{%
|
||||
\usepackage{parskip}
|
||||
}{% else
|
||||
\setlength{\parindent}{0pt}
|
||||
\setlength{\parskip}{6pt plus 2pt minus 1pt}}
|
||||
}{% if KOMA class
|
||||
\KOMAoptions{parskip=half}}
|
||||
\makeatother
|
||||
\usepackage{xcolor}
|
||||
\setlength{\emergencystretch}{3em} % prevent overfull lines
|
||||
\setcounter{secnumdepth}{5}
|
||||
% Make \paragraph and \subparagraph free-standing
|
||||
\ifx\paragraph\undefined\else
|
||||
\let\oldparagraph\paragraph
|
||||
\renewcommand{\paragraph}[1]{\oldparagraph{#1}\mbox{}}
|
||||
\fi
|
||||
\ifx\subparagraph\undefined\else
|
||||
\let\oldsubparagraph\subparagraph
|
||||
\renewcommand{\subparagraph}[1]{\oldsubparagraph{#1}\mbox{}}
|
||||
\fi
|
||||
|
||||
|
||||
\providecommand{\tightlist}{%
|
||||
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}\usepackage{longtable,booktabs,array}
|
||||
\usepackage{calc} % for calculating minipage widths
|
||||
% Correct order of tables after \paragraph or \subparagraph
|
||||
\usepackage{etoolbox}
|
||||
\makeatletter
|
||||
\patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{}
|
||||
\makeatother
|
||||
% Allow footnotes in longtable head/foot
|
||||
\IfFileExists{footnotehyper.sty}{\usepackage{footnotehyper}}{\usepackage{footnote}}
|
||||
\makesavenoteenv{longtable}
|
||||
\usepackage{graphicx}
|
||||
\makeatletter
|
||||
\def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi}
|
||||
\def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi}
|
||||
\makeatother
|
||||
% Scale images if necessary, so that they will not overflow the page
|
||||
% margins by default, and it is still possible to overwrite the defaults
|
||||
% using explicit options in \includegraphics[width, height, ...]{}
|
||||
\setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio}
|
||||
% Set default figure placement to htbp
|
||||
\makeatletter
|
||||
\def\fps@figure{htbp}
|
||||
\makeatother
|
||||
|
||||
\usepackage{enumitem}
|
||||
\usepackage{fontspec}
|
||||
\usepackage{float}
|
||||
\usepackage[normalem]{ulem}
|
||||
\usepackage{hyperref}
|
||||
\usepackage{nameref} %needed by zref-xr
|
||||
\usepackage{zref-xr,zref-hyperref,zref-user}
|
||||
\usepackage{xr-hyper}
|
||||
|
||||
|
||||
\usepackage{adjustbox}
|
||||
% \usepackage{xcolor}
|
||||
\usepackage{mdframed}
|
||||
\newmdenv[topline=false,
|
||||
leftline=true,
|
||||
bottomline=false,
|
||||
rightline=false,
|
||||
linewidth=3pt,
|
||||
%backgroundcolor=lightgray,
|
||||
linecolor=gray!30,
|
||||
backgroundcolor=gray!5,
|
||||
skipabove=\topsep,
|
||||
skipbelow=\topsep,
|
||||
leftmargin=0pt,
|
||||
rightmargin=0pt,
|
||||
innertopmargin=0pt,
|
||||
innerbottommargin=0pt
|
||||
]{refquote}
|
||||
\newmdenv[topline=false,
|
||||
leftline=true,
|
||||
bottomline=false,
|
||||
rightline=false,
|
||||
linewidth=3pt,
|
||||
%backgroundcolor=lightgray,
|
||||
backgroundcolor=red!3!white,
|
||||
linecolor=red!30!white,
|
||||
skipabove=\topsep,
|
||||
skipbelow=\topsep,
|
||||
leftmargin=0pt,
|
||||
rightmargin=0pt,
|
||||
innertopmargin=10pt,
|
||||
innerbottommargin=10pt
|
||||
]{question}
|
||||
% setup hyperlink for page and line number
|
||||
\def\msname{MS}
|
||||
\def\smname{SM}
|
||||
\setenumerate{labelsep=*, leftmargin=1.0pc}
|
||||
%\zexternaldocument*{ManuscriptNew\veraa}
|
||||
\zexternaldocument*{\msname}
|
||||
\zexternaldocument*{\smname}
|
||||
\newcommand{\hlabel}[1]{\label{#1}\hypertarget{#1}{
|
||||
\linelabel{line:#1}}}
|
||||
%\externaldocument[si-]{\smname\veraa}[\smname\veraa.pdf]
|
||||
\makeatletter
|
||||
\newcommand{\clab}[2][]{
|
||||
\protected@write\@auxout{\let\clab\@secondoftwo}{
|
||||
\string\newlabel{r:#1}{{#2}{}}}%
|
||||
\hlabel{#1}#2\hlabel{#1end}}
|
||||
\makeatother
|
||||
\newcommand{\zhypera}[1]{\href[pdfnewwindow]{\msname.pdf\##1}{\\[1ex]\textbf{Page \zpageref{#1}, Line
|
||||
\zref{line:#1}--\zref{line:#1end}:}\\}}
|
||||
\newcommand{\cref}[1]{\begin{refquote}\zhypera{#1}{\zref{r:#1}}\\[-0.5ex]\end{refquote}}
|
||||
% \newenvironment{ra}[1][\unskip]{\par \noindent \\[-1ex] \textbf{Response/Action:}\par\bf}{\ \\}
|
||||
\renewenvironment{quote}{\begin{question}}{\end{question}}
|
||||
% \renewenvironment{verbatim}{\begin{ra}}{\end{ra}}
|
||||
|
||||
\setmainfont{Calibri}
|
||||
\usepackage[normalem]{ulem}
|
||||
\usepackage[doublespacing]{setspace}
|
||||
\usepackage{lineno}
|
||||
\linenumbers
|
||||
\makeatletter
|
||||
\@ifpackageloaded{caption}{}{\usepackage{caption}}
|
||||
\AtBeginDocument{%
|
||||
\ifdefined\contentsname
|
||||
\renewcommand*\contentsname{Table of contents}
|
||||
\else
|
||||
\newcommand\contentsname{Table of contents}
|
||||
\fi
|
||||
\ifdefined\listfigurename
|
||||
\renewcommand*\listfigurename{List of Figures}
|
||||
\else
|
||||
\newcommand\listfigurename{List of Figures}
|
||||
\fi
|
||||
\ifdefined\listtablename
|
||||
\renewcommand*\listtablename{List of Tables}
|
||||
\else
|
||||
\newcommand\listtablename{List of Tables}
|
||||
\fi
|
||||
\ifdefined\figurename
|
||||
\renewcommand*\figurename{Fig.}
|
||||
\else
|
||||
\newcommand\figurename{Fig.}
|
||||
\fi
|
||||
\ifdefined\tablename
|
||||
\renewcommand*\tablename{Table}
|
||||
\else
|
||||
\newcommand\tablename{Table}
|
||||
\fi
|
||||
}
|
||||
\@ifpackageloaded{float}{}{\usepackage{float}}
|
||||
\floatstyle{ruled}
|
||||
\@ifundefined{c@chapter}{\newfloat{codelisting}{h}{lop}}{\newfloat{codelisting}{h}{lop}[chapter]}
|
||||
\floatname{codelisting}{Listing}
|
||||
\newcommand*\listoflistings{\listof{codelisting}{List of Listings}}
|
||||
\makeatother
|
||||
\makeatletter
|
||||
\makeatother
|
||||
\makeatletter
|
||||
\@ifpackageloaded{caption}{}{\usepackage{caption}}
|
||||
\@ifpackageloaded{subcaption}{}{\usepackage{subcaption}}
|
||||
\makeatother
|
||||
\ifLuaTeX
|
||||
\usepackage[bidi=basic]{babel}
|
||||
\else
|
||||
\usepackage[bidi=default]{babel}
|
||||
\fi
|
||||
\babelprovide[main,import]{english}
|
||||
% get rid of language-specific shorthands (see #6817):
|
||||
\let\LanguageShortHands\languageshorthands
|
||||
\def\languageshorthands#1{}
|
||||
\ifLuaTeX
|
||||
\usepackage{selnolig} % disable illegal ligatures
|
||||
\fi
|
||||
\usepackage[]{natbib}
|
||||
\bibliographystyle{elsarticle-harv}
|
||||
\IfFileExists{bookmark.sty}{\usepackage{bookmark}}{\usepackage{hyperref}}
|
||||
\IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available
|
||||
\urlstyle{same} % disable monospaced font for URLs
|
||||
\hypersetup{
|
||||
pdftitle={MANUSCRIPT TITLE},
|
||||
pdfauthor={Ming Su; Min Yang},
|
||||
pdflang={en},
|
||||
pdfkeywords={KEYWORD1, KEYWORD2, KEYWORD3},
|
||||
colorlinks=true,
|
||||
linkcolor={blue},
|
||||
filecolor={Maroon},
|
||||
citecolor={Blue},
|
||||
urlcolor={Blue},
|
||||
pdfcreator={LaTeX via pandoc}}
|
||||
|
||||
\setlength{\parindent}{6pt}
|
||||
\begin{document}
|
||||
\begin{frontmatter}
|
||||
\title{MANUSCRIPT TITLE }
|
||||
|
||||
\author[1,2]{Ming Su%
|
||||
%
|
||||
}
|
||||
\ead{mingsu@rcees.ac.cn}
|
||||
\author[1,2]{Min Yang%
|
||||
\corref{cor1}%
|
||||
}
|
||||
\ead{yangmin@rcees.ac.cn}
|
||||
|
||||
\affiliation[1]{organization={State Key Laboratory of Environmental
|
||||
Aquatic Chemistry, Research Center for Eco-Environmental Sciences,
|
||||
Chinese Academy of Sciences},addressline={P.O. Box
|
||||
2871},city={Beijing},postcode={100085}}
|
||||
\affiliation[2]{organization={University of Chinese Academy of
|
||||
Sciences},city={Beijing},postcode={100049}}
|
||||
|
||||
\cortext[cor1]{Corresponding author}
|
||||
|
||||
|
||||
|
||||
\begin{abstract}
|
||||
ABSTRACT
|
||||
\end{abstract}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
\begin{keyword}
|
||||
KEYWORD1 \sep KEYWORD2 \sep
|
||||
KEYWORD3
|
||||
\end{keyword}
|
||||
\end{frontmatter}
|
||||
\section{Introduction}\label{introduction}
|
||||
|
||||
Blabla. \clab[r1q0101]{Bla blaBla blaBla blaBla blaBla bla Bla bla.}
|
||||
|
||||
\section{Materials and methods}\label{materials-and-methods}
|
||||
|
||||
\section{Results}\label{results}
|
||||
|
||||
\section{Discussion}\label{discussion}
|
||||
|
||||
\section{Conclusions}\label{conclusions}
|
||||
|
||||
\section*{Acknowledgements}\label{acknowledgements}
|
||||
\addcontentsline{toc}{section}{Acknowledgements}
|
||||
|
||||
This work was financially supported by the National Natural Science
|
||||
Foundation of China (52030002, 51878649), and Youth Innovation Promotion
|
||||
Association CAS.
|
||||
|
||||
|
||||
\renewcommand\refname{References}
|
||||
\bibliography{../BB/Ref.bib,../BB/localRef.bib}
|
||||
|
||||
|
||||
\end{document}
|
||||
@@ -2,7 +2,7 @@
|
||||
\providecommand\zref@newlabel[2]{}
|
||||
\providecommand\hyper@newdestlabel[2]{}
|
||||
\providecommand*\HyPL@Entry[1]{}
|
||||
\providecommand\babel@aux[2]{}
|
||||
\providecommand \babel@aux [2]{\global \let \babel@toc \@gobbletwo }
|
||||
\@nameuse{bbl@beforestart}
|
||||
\bibstyle{elsarticle-num}
|
||||
\emailauthor{mingsu@rcees.ac.cn}{Ming Su}
|
||||
|
||||
@@ -32,6 +32,7 @@ format:
|
||||
filters:
|
||||
- latex-environment
|
||||
- authors-block
|
||||
- lightbox
|
||||
commands: [clab]
|
||||
---
|
||||
|
||||
|
||||
@@ -294,7 +294,7 @@
|
||||
\urlstyle{same} % disable monospaced font for URLs
|
||||
\hypersetup{
|
||||
pdftitle={MANUSCRIPT TITLE},
|
||||
pdfauthor={Ming Su; Min Yang},
|
||||
pdfauthor={, and },
|
||||
pdflang={en},
|
||||
colorlinks=true,
|
||||
linkcolor={blue},
|
||||
@@ -337,6 +337,15 @@ Sciences},city={Beijing},postcode={100049}}
|
||||
\end{frontmatter}
|
||||
|
||||
|
||||
\textsuperscript{1} State Key Laboratory of Environmental Aquatic
|
||||
Chemistry, Research Center for Eco-Environmental Sciences, Chinese
|
||||
Academy of Sciences\\
|
||||
\textsuperscript{2} University of Chinese Academy of Sciences
|
||||
|
||||
\textsuperscript{*} Correspondence:
|
||||
\href{mailto:yangmin@rcees.ac.cn}{Min Yang
|
||||
\textless{}yangmin@rcees.ac.cn\textgreater{}}
|
||||
|
||||
\section*{Supplementary Material}\label{supplementary-material}
|
||||
\addcontentsline{toc}{section}{Supplementary Material}
|
||||
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
# Makefile for Quarto Project Automation
|
||||
|
||||
# Detect OS
|
||||
OS := $(shell uname | tr A-Z a-z)
|
||||
ifeq ($(OS), darwin)
|
||||
OS := OSX
|
||||
else ifeq ($(OS), linux)
|
||||
OS := linux
|
||||
else
|
||||
$(error Unknown operating system)
|
||||
endif
|
||||
|
||||
# Fetch Git branch and project details
|
||||
branchname := $(shell git branch --show-current)
|
||||
reponame := $(shell basename $(shell git rev-parse --show-toplevel))
|
||||
projtype := $(shell basename $(shell dirname $(shell git rev-parse --show-toplevel)))
|
||||
pubtype := $(if $(findstring PUB,$(branchname)),public,protected)
|
||||
remotedir := drwater@serev:/home/www/drwater/$(projtype)/$(pubtype)/$(reponame)/@$(branchname)
|
||||
outputdir := $(shell awk -F': *' '/^ *output-dir:/ {print $$2 "/" }' ./_quarto.yml)
|
||||
siteurl := https://drwater.rcees.ac.cn/$(projtype)/$(pubtype)/$(reponame)/@$(branchname)
|
||||
|
||||
|
||||
# Variables for colors and port
|
||||
bcolor := grey
|
||||
port := 4199
|
||||
|
||||
# Set background color based on branch name
|
||||
ifeq ($(findstring R1,$(branchname)),R1)
|
||||
bcolor := orange
|
||||
else ifeq ($(findstring R2,$(branchname)),R2)
|
||||
bcolor := lightblue
|
||||
else ifeq ($(findstring R3,$(branchname)),R3)
|
||||
bcolor := lightgreen
|
||||
else ifeq ($(findstring PUB,$(branchname)),PUB)
|
||||
bcolor := light
|
||||
endif
|
||||
|
||||
# Set port based on branch name
|
||||
branch_ports := main:4200 SM:4201 TX:4202 FJ:4203 YF:4204 ZY:4205 WW:4206 JB:4207 YY:4208
|
||||
port := $(shell echo $(branch_ports) | tr " " "\n" | grep -E "^$(branchname):" | sed -E 's/^$(branchname):([0-9]+)/\1/')
|
||||
port := $(if $(port),$(port),4199)
|
||||
|
||||
# Define the state file
|
||||
STATE_FILE := .source_state
|
||||
|
||||
# checkfile
|
||||
GREPSTR := " \|^submit\|^analysis\|_cache\|_freeze\|^site_libs\|^www"
|
||||
|
||||
# Default target
|
||||
.PHONY: all preview readme rsync local clean upload fix_links force
|
||||
|
||||
all: render upload clean
|
||||
|
||||
# Render target with hash checking
|
||||
render:
|
||||
@current_hash=$$(find $(shell git ls-files "*.qmd" "_*.yml" "*.pdf" "*.svg" "*.png" | grep -v $(GREPSTR)) -exec cat {} + | md5sum | awk '{print $$1}'); \
|
||||
if [ ! -f $(STATE_FILE) ] || [ "$$current_hash" != "$$(cat $(STATE_FILE))" ]; then \
|
||||
echo "Changes detected, rendering..."; \
|
||||
quarto render; \
|
||||
echo "$$current_hash" > $(STATE_FILE); \
|
||||
git add . && git commit -m "render compile"; \
|
||||
else \
|
||||
echo "No changes detected, skipping render."; \
|
||||
fi
|
||||
|
||||
# Force render target
|
||||
force:
|
||||
@echo "Forcing render..."
|
||||
@quarto render
|
||||
@current_hash=$$(find $(shell git ls-files "*.qmd" "_*.yml" "*.pdf" "*.svg" "*.png" | grep -v $(GREPSTR)) -exec cat {} + | md5sum | awk '{print $$1}'); \
|
||||
echo "$$current_hash" > $(STATE_FILE); \
|
||||
git add . && git commit -m "render compile"; \
|
||||
|
||||
# Preview the site on a specific port
|
||||
preview:
|
||||
@quarto preview --port $(port)
|
||||
|
||||
# Generate README.md
|
||||
readme:
|
||||
@quarto render index.qmd -t markdown -o README.md
|
||||
@sed -e '/^---/,/^---/d' "$(outputdir)/README.md" > README.md
|
||||
@rm "$(outputdir)/README.md"
|
||||
|
||||
# Sync files with remote server
|
||||
rsync: render
|
||||
@rsync -azvu --progress --delete -r "$(outputdir)" "$(remotedir)"
|
||||
|
||||
# Open local site
|
||||
local: render
|
||||
@if [ "$(OS)" = "OSX" ]; then open "$(outputdir)/index.html"; fi
|
||||
|
||||
# Clean unnecessary files
|
||||
clean:
|
||||
@rm -f ./*.spl ./*.aux ./*.bbl ./*.blg ./*.log ./*.tex ./*.bcf ./*.tex.sedbak ./*.fdb_latexmk
|
||||
|
||||
# Upload files to server and fix links
|
||||
upload: render
|
||||
@chmod -R 2775 "$(outputdir)"
|
||||
@$(MAKE) fix_links
|
||||
@if rsync -azvu --progress --delete -r "$(outputdir)" "$(remotedir)"; then \
|
||||
if [ "$(OS)" = "OSX" ]; then \
|
||||
open "$(siteurl)" 2>/dev/null; \
|
||||
fi; \
|
||||
else \
|
||||
echo "Rsync failed. Attempting alternative upload method..."; \
|
||||
mkdir -p "$(reponame)"; \
|
||||
rsync -azvu --progress --delete -r "$(reponame)" "$(dir $(remotedir))"; \
|
||||
rm -rf "$(reponame)"; \
|
||||
rsync -azvu --progress --delete -r "$(outputdir)" "$(remotedir)"; \
|
||||
if [ "$(OS)" = "OSX" ]; then \
|
||||
open "$(siteurl)" 2>/dev/null; \
|
||||
fi; \
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
# Fix links in www directory
|
||||
fix_links:
|
||||
@find ./www -type f -name "*.html" -exec sed -i.bak \
|
||||
-e "s/{{< var branch >}}/$(branchname)/g" \
|
||||
-e "s/{{< var pubtype >}}/$(pubtype)/g" \
|
||||
-e "s/{{< var projtype >}}/$(projtype)/g" \
|
||||
-e "s/{{< var reponame >}}/$(reponame)/g" \
|
||||
-e "s/$(reponame)\/blob/$(reponame)\/raw\/branch/g" \
|
||||
-e "s/$(reponame)\/edit/$(reponame)\/_edit/g" {} +
|
||||
@find ./www -type f -name "*.bak" -exec rm {} +
|
||||
@@ -0,0 +1,6 @@
|
||||
title: Qrcode
|
||||
author: Jannik Buhr
|
||||
version: 0.0.1
|
||||
contributes:
|
||||
shortcodes:
|
||||
- qrcode.lua
|
||||
@@ -0,0 +1,614 @@
|
||||
/**
|
||||
* @fileoverview
|
||||
* - Using the 'QRCode for Javascript library'
|
||||
* - Fixed dataset of 'QRCode for Javascript library' for support full-spec.
|
||||
* - this library has no dependencies.
|
||||
*
|
||||
* @author davidshimjs
|
||||
* @see <a href="http://www.d-project.com/" target="_blank">http://www.d-project.com/</a>
|
||||
* @see <a href="http://jeromeetienne.github.com/jquery-qrcode/" target="_blank">http://jeromeetienne.github.com/jquery-qrcode/</a>
|
||||
*/
|
||||
var QRCode;
|
||||
|
||||
(function () {
|
||||
//---------------------------------------------------------------------
|
||||
// QRCode for JavaScript
|
||||
//
|
||||
// Copyright (c) 2009 Kazuhiko Arase
|
||||
//
|
||||
// URL: http://www.d-project.com/
|
||||
//
|
||||
// Licensed under the MIT license:
|
||||
// http://www.opensource.org/licenses/mit-license.php
|
||||
//
|
||||
// The word "QR Code" is registered trademark of
|
||||
// DENSO WAVE INCORPORATED
|
||||
// http://www.denso-wave.com/qrcode/faqpatent-e.html
|
||||
//
|
||||
//---------------------------------------------------------------------
|
||||
function QR8bitByte(data) {
|
||||
this.mode = QRMode.MODE_8BIT_BYTE;
|
||||
this.data = data;
|
||||
this.parsedData = [];
|
||||
|
||||
// Added to support UTF-8 Characters
|
||||
for (var i = 0, l = this.data.length; i < l; i++) {
|
||||
var byteArray = [];
|
||||
var code = this.data.charCodeAt(i);
|
||||
|
||||
if (code > 0x10000) {
|
||||
byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18);
|
||||
byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12);
|
||||
byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6);
|
||||
byteArray[3] = 0x80 | (code & 0x3F);
|
||||
} else if (code > 0x800) {
|
||||
byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12);
|
||||
byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6);
|
||||
byteArray[2] = 0x80 | (code & 0x3F);
|
||||
} else if (code > 0x80) {
|
||||
byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6);
|
||||
byteArray[1] = 0x80 | (code & 0x3F);
|
||||
} else {
|
||||
byteArray[0] = code;
|
||||
}
|
||||
|
||||
this.parsedData.push(byteArray);
|
||||
}
|
||||
|
||||
this.parsedData = Array.prototype.concat.apply([], this.parsedData);
|
||||
|
||||
if (this.parsedData.length != this.data.length) {
|
||||
this.parsedData.unshift(191);
|
||||
this.parsedData.unshift(187);
|
||||
this.parsedData.unshift(239);
|
||||
}
|
||||
}
|
||||
|
||||
QR8bitByte.prototype = {
|
||||
getLength: function (buffer) {
|
||||
return this.parsedData.length;
|
||||
},
|
||||
write: function (buffer) {
|
||||
for (var i = 0, l = this.parsedData.length; i < l; i++) {
|
||||
buffer.put(this.parsedData[i], 8);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function QRCodeModel(typeNumber, errorCorrectLevel) {
|
||||
this.typeNumber = typeNumber;
|
||||
this.errorCorrectLevel = errorCorrectLevel;
|
||||
this.modules = null;
|
||||
this.moduleCount = 0;
|
||||
this.dataCache = null;
|
||||
this.dataList = [];
|
||||
}
|
||||
|
||||
QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row<0||this.moduleCount<=row||col<0||this.moduleCount<=col){throw new Error(row+","+col);}
|
||||
return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}
|
||||
this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=7){this.setupTypeNumber(test);}
|
||||
if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);}
|
||||
this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}}
|
||||
return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row<this.modules.length;row++){var y=row*cs;for(var col=0;col<this.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}
|
||||
return qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}
|
||||
this.modules[r][6]=(r%2==0);}
|
||||
for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}
|
||||
this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}
|
||||
for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;}
|
||||
for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}}
|
||||
for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}}
|
||||
this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex<data.length){dark=(((data[byteIndex]>>>bitIndex)&1)==1);}
|
||||
var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}
|
||||
this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}
|
||||
row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}
|
||||
var totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}
|
||||
if(buffer.getLengthInBits()>totalDataCount*8){throw new Error("code length overflow. ("
|
||||
+buffer.getLengthInBits()
|
||||
+">"
|
||||
+totalDataCount*8
|
||||
+")");}
|
||||
if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);}
|
||||
while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}
|
||||
while(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;}
|
||||
buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;}
|
||||
buffer.put(QRCodeModel.PAD1,8);}
|
||||
return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}
|
||||
offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}
|
||||
var totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}
|
||||
var data=new Array(totalCodeCount);var index=0;for(var i=0;i<maxDcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}
|
||||
for(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}
|
||||
return data;};var QRMode={MODE_NUMBER:1<<0,MODE_ALPHA_NUM:1<<1,MODE_8BIT_BYTE:1<<2,MODE_KANJI:1<<3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));}
|
||||
return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));}
|
||||
return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;}
|
||||
return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return(i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error("bad maskPattern:"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}
|
||||
return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error("mode:"+mode);}}else if(type<27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error("mode:"+mode);}}else if(type<41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error("mode:"+mode);}}else{throw new Error("type:"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r){continue;}
|
||||
for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}
|
||||
if(r==0&&c==0){continue;}
|
||||
if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}
|
||||
if(sameCount>5){lostPoint+=(3+sameCount-5);}}}
|
||||
for(var row=0;row<moduleCount-1;row++){for(var col=0;col<moduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}
|
||||
for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}
|
||||
for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}
|
||||
var darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}
|
||||
var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n<1){throw new Error("glog("+n+")");}
|
||||
return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}
|
||||
while(n>=256){n-=255;}
|
||||
return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<<i;}
|
||||
for(var i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}
|
||||
for(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}
|
||||
function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+"/"+shift);}
|
||||
var offset=0;while(offset<num.length&&num[offset]==0){offset++;}
|
||||
this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}
|
||||
QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}
|
||||
return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}
|
||||
var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}
|
||||
for(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}
|
||||
return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}
|
||||
QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error("bad rs block @ typeNumber:"+typeNumber+"/errorCorrectLevel:"+errorCorrectLevel);}
|
||||
var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}
|
||||
return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}
|
||||
QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);}
|
||||
if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));}
|
||||
this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];
|
||||
|
||||
function _isSupportCanvas() {
|
||||
return typeof CanvasRenderingContext2D != "undefined";
|
||||
}
|
||||
|
||||
// android 2.x doesn't support Data-URI spec
|
||||
function _getAndroid() {
|
||||
var android = false;
|
||||
var sAgent = navigator.userAgent;
|
||||
|
||||
if (/android/i.test(sAgent)) { // android
|
||||
android = true;
|
||||
var aMat = sAgent.toString().match(/android ([0-9]\.[0-9])/i);
|
||||
|
||||
if (aMat && aMat[1]) {
|
||||
android = parseFloat(aMat[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return android;
|
||||
}
|
||||
|
||||
var svgDrawer = (function() {
|
||||
|
||||
var Drawing = function (el, htOption) {
|
||||
this._el = el;
|
||||
this._htOption = htOption;
|
||||
};
|
||||
|
||||
Drawing.prototype.draw = function (oQRCode) {
|
||||
var _htOption = this._htOption;
|
||||
var _el = this._el;
|
||||
var nCount = oQRCode.getModuleCount();
|
||||
var nWidth = Math.floor(_htOption.width / nCount);
|
||||
var nHeight = Math.floor(_htOption.height / nCount);
|
||||
|
||||
this.clear();
|
||||
|
||||
function makeSVG(tag, attrs) {
|
||||
var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
|
||||
for (var k in attrs)
|
||||
if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]);
|
||||
return el;
|
||||
}
|
||||
|
||||
var svg = makeSVG("svg" , {'viewBox': '0 0 ' + String(nCount) + " " + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight});
|
||||
svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
|
||||
_el.appendChild(svg);
|
||||
|
||||
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorLight, "width": "100%", "height": "100%"}));
|
||||
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorDark, "width": "1", "height": "1", "id": "template"}));
|
||||
|
||||
for (var row = 0; row < nCount; row++) {
|
||||
for (var col = 0; col < nCount; col++) {
|
||||
if (oQRCode.isDark(row, col)) {
|
||||
var child = makeSVG("use", {"x": String(col), "y": String(row)});
|
||||
child.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#template")
|
||||
svg.appendChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Drawing.prototype.clear = function () {
|
||||
while (this._el.hasChildNodes())
|
||||
this._el.removeChild(this._el.lastChild);
|
||||
};
|
||||
return Drawing;
|
||||
})();
|
||||
|
||||
var useSVG = document.documentElement.tagName.toLowerCase() === "svg";
|
||||
|
||||
// Drawing in DOM by using Table tag
|
||||
var Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () {
|
||||
var Drawing = function (el, htOption) {
|
||||
this._el = el;
|
||||
this._htOption = htOption;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draw the QRCode
|
||||
*
|
||||
* @param {QRCode} oQRCode
|
||||
*/
|
||||
Drawing.prototype.draw = function (oQRCode) {
|
||||
var _htOption = this._htOption;
|
||||
var _el = this._el;
|
||||
var nCount = oQRCode.getModuleCount();
|
||||
var nWidth = Math.floor(_htOption.width / nCount);
|
||||
var nHeight = Math.floor(_htOption.height / nCount);
|
||||
var aHTML = ['<table style="border:0;border-collapse:collapse;">'];
|
||||
|
||||
for (var row = 0; row < nCount; row++) {
|
||||
aHTML.push('<tr>');
|
||||
|
||||
for (var col = 0; col < nCount; col++) {
|
||||
aHTML.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:' + nWidth + 'px;height:' + nHeight + 'px;background-color:' + (oQRCode.isDark(row, col) ? _htOption.colorDark : _htOption.colorLight) + ';"></td>');
|
||||
}
|
||||
|
||||
aHTML.push('</tr>');
|
||||
}
|
||||
|
||||
aHTML.push('</table>');
|
||||
_el.innerHTML = aHTML.join('');
|
||||
|
||||
// Fix the margin values as real size.
|
||||
var elTable = _el.childNodes[0];
|
||||
var nLeftMarginTable = (_htOption.width - elTable.offsetWidth) / 2;
|
||||
var nTopMarginTable = (_htOption.height - elTable.offsetHeight) / 2;
|
||||
|
||||
if (nLeftMarginTable > 0 && nTopMarginTable > 0) {
|
||||
elTable.style.margin = nTopMarginTable + "px " + nLeftMarginTable + "px";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the QRCode
|
||||
*/
|
||||
Drawing.prototype.clear = function () {
|
||||
this._el.innerHTML = '';
|
||||
};
|
||||
|
||||
return Drawing;
|
||||
})() : (function () { // Drawing in Canvas
|
||||
function _onMakeImage() {
|
||||
this._elImage.src = this._elCanvas.toDataURL("image/png");
|
||||
this._elImage.style.display = "block";
|
||||
this._elCanvas.style.display = "none";
|
||||
}
|
||||
|
||||
// Android 2.1 bug workaround
|
||||
// http://code.google.com/p/android/issues/detail?id=5141
|
||||
if (this._android && this._android <= 2.1) {
|
||||
var factor = 1 / window.devicePixelRatio;
|
||||
var drawImage = CanvasRenderingContext2D.prototype.drawImage;
|
||||
CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
|
||||
if (("nodeName" in image) && /img/i.test(image.nodeName)) {
|
||||
for (var i = arguments.length - 1; i >= 1; i--) {
|
||||
arguments[i] = arguments[i] * factor;
|
||||
}
|
||||
} else if (typeof dw == "undefined") {
|
||||
arguments[1] *= factor;
|
||||
arguments[2] *= factor;
|
||||
arguments[3] *= factor;
|
||||
arguments[4] *= factor;
|
||||
}
|
||||
|
||||
drawImage.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the user's browser supports Data URI or not
|
||||
*
|
||||
* @private
|
||||
* @param {Function} fSuccess Occurs if it supports Data URI
|
||||
* @param {Function} fFail Occurs if it doesn't support Data URI
|
||||
*/
|
||||
function _safeSetDataURI(fSuccess, fFail) {
|
||||
var self = this;
|
||||
self._fFail = fFail;
|
||||
self._fSuccess = fSuccess;
|
||||
|
||||
// Check it just once
|
||||
if (self._bSupportDataURI === null) {
|
||||
var el = document.createElement("img");
|
||||
var fOnError = function() {
|
||||
self._bSupportDataURI = false;
|
||||
|
||||
if (self._fFail) {
|
||||
self._fFail.call(self);
|
||||
}
|
||||
};
|
||||
var fOnSuccess = function() {
|
||||
self._bSupportDataURI = true;
|
||||
|
||||
if (self._fSuccess) {
|
||||
self._fSuccess.call(self);
|
||||
}
|
||||
};
|
||||
|
||||
el.onabort = fOnError;
|
||||
el.onerror = fOnError;
|
||||
el.onload = fOnSuccess;
|
||||
el.src = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; // the Image contains 1px data.
|
||||
return;
|
||||
} else if (self._bSupportDataURI === true && self._fSuccess) {
|
||||
self._fSuccess.call(self);
|
||||
} else if (self._bSupportDataURI === false && self._fFail) {
|
||||
self._fFail.call(self);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Drawing QRCode by using canvas
|
||||
*
|
||||
* @constructor
|
||||
* @param {HTMLElement} el
|
||||
* @param {Object} htOption QRCode Options
|
||||
*/
|
||||
var Drawing = function (el, htOption) {
|
||||
this._bIsPainted = false;
|
||||
this._android = _getAndroid();
|
||||
|
||||
this._htOption = htOption;
|
||||
this._elCanvas = document.createElement("canvas");
|
||||
this._elCanvas.width = htOption.width;
|
||||
this._elCanvas.height = htOption.height;
|
||||
el.appendChild(this._elCanvas);
|
||||
this._el = el;
|
||||
this._oContext = this._elCanvas.getContext("2d");
|
||||
this._bIsPainted = false;
|
||||
this._elImage = document.createElement("img");
|
||||
this._elImage.alt = "Scan me!";
|
||||
this._elImage.style.display = "none";
|
||||
this._el.appendChild(this._elImage);
|
||||
this._bSupportDataURI = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draw the QRCode
|
||||
*
|
||||
* @param {QRCode} oQRCode
|
||||
*/
|
||||
Drawing.prototype.draw = function (oQRCode) {
|
||||
var _elImage = this._elImage;
|
||||
var _oContext = this._oContext;
|
||||
var _htOption = this._htOption;
|
||||
|
||||
var nCount = oQRCode.getModuleCount();
|
||||
var nWidth = _htOption.width / nCount;
|
||||
var nHeight = _htOption.height / nCount;
|
||||
var nRoundedWidth = Math.round(nWidth);
|
||||
var nRoundedHeight = Math.round(nHeight);
|
||||
|
||||
_elImage.style.display = "none";
|
||||
this.clear();
|
||||
|
||||
for (var row = 0; row < nCount; row++) {
|
||||
for (var col = 0; col < nCount; col++) {
|
||||
var bIsDark = oQRCode.isDark(row, col);
|
||||
var nLeft = col * nWidth;
|
||||
var nTop = row * nHeight;
|
||||
_oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
|
||||
_oContext.lineWidth = 1;
|
||||
_oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
|
||||
_oContext.fillRect(nLeft, nTop, nWidth, nHeight);
|
||||
|
||||
// 안티 앨리어싱 방지 처리
|
||||
_oContext.strokeRect(
|
||||
Math.floor(nLeft) + 0.5,
|
||||
Math.floor(nTop) + 0.5,
|
||||
nRoundedWidth,
|
||||
nRoundedHeight
|
||||
);
|
||||
|
||||
_oContext.strokeRect(
|
||||
Math.ceil(nLeft) - 0.5,
|
||||
Math.ceil(nTop) - 0.5,
|
||||
nRoundedWidth,
|
||||
nRoundedHeight
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this._bIsPainted = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make the image from Canvas if the browser supports Data URI.
|
||||
*/
|
||||
Drawing.prototype.makeImage = function () {
|
||||
if (this._bIsPainted) {
|
||||
_safeSetDataURI.call(this, _onMakeImage);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return whether the QRCode is painted or not
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Drawing.prototype.isPainted = function () {
|
||||
return this._bIsPainted;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the QRCode
|
||||
*/
|
||||
Drawing.prototype.clear = function () {
|
||||
this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height);
|
||||
this._bIsPainted = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Number} nNumber
|
||||
*/
|
||||
Drawing.prototype.round = function (nNumber) {
|
||||
if (!nNumber) {
|
||||
return nNumber;
|
||||
}
|
||||
|
||||
return Math.floor(nNumber * 1000) / 1000;
|
||||
};
|
||||
|
||||
return Drawing;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Get the type by string length
|
||||
*
|
||||
* @private
|
||||
* @param {String} sText
|
||||
* @param {Number} nCorrectLevel
|
||||
* @return {Number} type
|
||||
*/
|
||||
function _getTypeNumber(sText, nCorrectLevel) {
|
||||
var nType = 1;
|
||||
var length = _getUTF8Length(sText);
|
||||
|
||||
for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) {
|
||||
var nLimit = 0;
|
||||
|
||||
switch (nCorrectLevel) {
|
||||
case QRErrorCorrectLevel.L :
|
||||
nLimit = QRCodeLimitLength[i][0];
|
||||
break;
|
||||
case QRErrorCorrectLevel.M :
|
||||
nLimit = QRCodeLimitLength[i][1];
|
||||
break;
|
||||
case QRErrorCorrectLevel.Q :
|
||||
nLimit = QRCodeLimitLength[i][2];
|
||||
break;
|
||||
case QRErrorCorrectLevel.H :
|
||||
nLimit = QRCodeLimitLength[i][3];
|
||||
break;
|
||||
}
|
||||
|
||||
if (length <= nLimit) {
|
||||
break;
|
||||
} else {
|
||||
nType++;
|
||||
}
|
||||
}
|
||||
|
||||
if (nType > QRCodeLimitLength.length) {
|
||||
throw new Error("Too long data");
|
||||
}
|
||||
|
||||
return nType;
|
||||
}
|
||||
|
||||
function _getUTF8Length(sText) {
|
||||
var replacedText = encodeURI(sText).toString().replace(/\%[0-9a-fA-F]{2}/g, 'a');
|
||||
return replacedText.length + (replacedText.length != sText ? 3 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @class QRCode
|
||||
* @constructor
|
||||
* @example
|
||||
* new QRCode(document.getElementById("test"), "http://jindo.dev.naver.com/collie");
|
||||
*
|
||||
* @example
|
||||
* var oQRCode = new QRCode("test", {
|
||||
* text : "http://naver.com",
|
||||
* width : 128,
|
||||
* height : 128
|
||||
* });
|
||||
*
|
||||
* oQRCode.clear(); // Clear the QRCode.
|
||||
* oQRCode.makeCode("http://map.naver.com"); // Re-create the QRCode.
|
||||
*
|
||||
* @param {HTMLElement|String} el target element or 'id' attribute of element.
|
||||
* @param {Object|String} vOption
|
||||
* @param {String} vOption.text QRCode link data
|
||||
* @param {Number} [vOption.width=256]
|
||||
* @param {Number} [vOption.height=256]
|
||||
* @param {String} [vOption.colorDark="#000000"]
|
||||
* @param {String} [vOption.colorLight="#ffffff"]
|
||||
* @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H]
|
||||
*/
|
||||
QRCode = function (el, vOption) {
|
||||
this._htOption = {
|
||||
width : 256,
|
||||
height : 256,
|
||||
typeNumber : 4,
|
||||
colorDark : "#000000",
|
||||
colorLight : "#ffffff",
|
||||
correctLevel : QRErrorCorrectLevel.H
|
||||
};
|
||||
|
||||
if (typeof vOption === 'string') {
|
||||
vOption = {
|
||||
text : vOption
|
||||
};
|
||||
}
|
||||
|
||||
// Overwrites options
|
||||
if (vOption) {
|
||||
for (var i in vOption) {
|
||||
this._htOption[i] = vOption[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof el == "string") {
|
||||
el = document.getElementById(el);
|
||||
}
|
||||
|
||||
if (this._htOption.useSVG) {
|
||||
Drawing = svgDrawer;
|
||||
}
|
||||
|
||||
this._android = _getAndroid();
|
||||
this._el = el;
|
||||
this._oQRCode = null;
|
||||
this._oDrawing = new Drawing(this._el, this._htOption);
|
||||
|
||||
if (this._htOption.text) {
|
||||
this.makeCode(this._htOption.text);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Make the QRCode
|
||||
*
|
||||
* @param {String} sText link data
|
||||
*/
|
||||
QRCode.prototype.makeCode = function (sText) {
|
||||
this._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel);
|
||||
this._oQRCode.addData(sText);
|
||||
this._oQRCode.make();
|
||||
this._el.title = sText;
|
||||
this._oDrawing.draw(this._oQRCode);
|
||||
this.makeImage();
|
||||
};
|
||||
|
||||
/**
|
||||
* Make the Image from Canvas element
|
||||
* - It occurs automatically
|
||||
* - Android below 3 doesn't support Data-URI spec.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
QRCode.prototype.makeImage = function () {
|
||||
if (typeof this._oDrawing.makeImage == "function" && (!this._android || this._android >= 3)) {
|
||||
this._oDrawing.makeImage();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the QRCode
|
||||
*/
|
||||
QRCode.prototype.clear = function () {
|
||||
this._oDrawing.clear();
|
||||
};
|
||||
|
||||
/**
|
||||
* @name QRCode.CorrectLevel
|
||||
*/
|
||||
QRCode.CorrectLevel = QRErrorCorrectLevel;
|
||||
})();
|
||||
@@ -0,0 +1,107 @@
|
||||
-- for development:
|
||||
local p = quarto.log.warning
|
||||
|
||||
---Format string like in bash or python,
|
||||
---e.g. f('Hello ${one}', {one = 'world'})
|
||||
---@param s string The string to format
|
||||
---@param kwargs {[string]: string} A table with key-value replacemen pairs
|
||||
---@return string
|
||||
local function f(s, kwargs)
|
||||
return (s:gsub('($%b{})', function(w) return kwargs[w:sub(3, -2)] or w end))
|
||||
end
|
||||
|
||||
|
||||
---Merge user provided options with defaults
|
||||
---@param userOptions table
|
||||
---@return string JSON string to pass to molstar
|
||||
local function mergeOptions(url, userOptions)
|
||||
local defaultOptions = {
|
||||
text = url,
|
||||
width = 128,
|
||||
height = 128,
|
||||
colorDark = "#000000",
|
||||
colorLight = "#ffffff",
|
||||
}
|
||||
if userOptions == nil then
|
||||
return quarto.json.encode(defaultOptions)
|
||||
end
|
||||
|
||||
for k, v in pairs(userOptions) do
|
||||
local value = pandoc.utils.stringify(v)
|
||||
if value == 'true' then value = true end
|
||||
if value == 'false' then value = false end
|
||||
defaultOptions[k] = value
|
||||
end
|
||||
|
||||
return quarto.json.encode(defaultOptions)
|
||||
end
|
||||
|
||||
|
||||
---@return string
|
||||
local function wrapInlineDiv(options)
|
||||
return [[
|
||||
<div id="${id}" class="qrcode"></div>
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var script = document.currentScript;
|
||||
var qrcode = script.previousElementSibling;
|
||||
qrcode.qrcode = new QRCode(qrcode, ]] .. options .. [[);
|
||||
script.remove();
|
||||
})();
|
||||
</script>
|
||||
]]
|
||||
end
|
||||
|
||||
---@return string
|
||||
local function wrapInlineTex(url, opts)
|
||||
return [[
|
||||
\qrcode[]] .. opts .. [[]{]] .. url .. [[}
|
||||
]]
|
||||
end
|
||||
|
||||
return {
|
||||
['qrcode'] = function(args, kwargs, _)
|
||||
if quarto.doc.is_format("html:js") then
|
||||
quarto.doc.add_html_dependency {
|
||||
name = 'qrcodejs',
|
||||
version = 'v1.0.0',
|
||||
scripts = { './assets/qrcode.js' },
|
||||
}
|
||||
local url = pandoc.utils.stringify(args[1])
|
||||
local id = ""
|
||||
if args[2] ~= nil then
|
||||
id = f('id="${id}" ', { id = pandoc.utils.stringify(id) })
|
||||
end
|
||||
local options = mergeOptions(url, kwargs)
|
||||
local text = wrapInlineDiv(options)
|
||||
return pandoc.RawBlock(
|
||||
'html',
|
||||
f(text, { id = id })
|
||||
)
|
||||
elseif quarto.doc.is_format("pdf") then
|
||||
quarto.doc.use_latex_package("qrcode")
|
||||
local url = pandoc.utils.stringify(args[1])
|
||||
local opts = ""
|
||||
for k, v in pairs(kwargs) do
|
||||
if string.match(k, "^pdf") then
|
||||
k = string.sub(k, 4)
|
||||
opts = opts .. k .. "=" .. v .. ", "
|
||||
end
|
||||
end
|
||||
for _, v in ipairs(args) do
|
||||
if string.match(v, "^pdf") then
|
||||
v = string.sub(v, 4)
|
||||
opts = opts .. v .. ", "
|
||||
end
|
||||
end
|
||||
if string.len(opts) then
|
||||
opts = string.sub(opts, 1, string.len(opts) - 2)
|
||||
end
|
||||
local text = wrapInlineTex(url, opts)
|
||||
return pandoc.RawBlock(
|
||||
'tex',
|
||||
text
|
||||
)
|
||||
end
|
||||
end,
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Mickaël Canouil
|
||||
|
||||
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 NONINFRINGEMENT. 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.
|
||||
@@ -0,0 +1,7 @@
|
||||
title: Highlight Text
|
||||
author: Mickaël Canouil
|
||||
version: 0.4.0
|
||||
quarto-required: ">=1.4.550"
|
||||
contributes:
|
||||
filters:
|
||||
- highlight-text.lua
|
||||
@@ -0,0 +1,164 @@
|
||||
--[[
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2024 Mickaël Canouil
|
||||
#
|
||||
# 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 NONINFRINGEMENT. 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.
|
||||
]]
|
||||
|
||||
local function highlight_html(span, colour, bg_colour)
|
||||
if span.attributes['style'] == nil then
|
||||
span.attributes['style'] = ''
|
||||
elseif span.attributes['style']:sub(-1) ~= ";" then
|
||||
span.attributes['style'] = span.attributes['style'] .. ";"
|
||||
end
|
||||
|
||||
if colour ~= nil then
|
||||
span.attributes['colour'] = nil
|
||||
span.attributes['color'] = nil
|
||||
span.attributes['style'] = span.attributes['style'] .. 'color: ' .. colour .. ';'
|
||||
end
|
||||
|
||||
if bg_colour ~= nil then
|
||||
span.attributes['bg-colour'] = nil
|
||||
span.attributes['bg-color'] = nil
|
||||
span.attributes['style'] = span.attributes['style'] .. 'border-radius: 0.2rem; padding: 0 0.2rem 0 0.2rem;' .. 'background-color: ' .. bg_colour .. ';'
|
||||
end
|
||||
|
||||
return span
|
||||
end
|
||||
|
||||
local function highlight_latex(span, colour, bg_colour)
|
||||
if colour == nil then
|
||||
colour_open = ''
|
||||
colour_close = ''
|
||||
else
|
||||
colour_open = '\\textcolor[HTML]{' .. colour:gsub("^#", "") .. '}{'
|
||||
colour_close = '}'
|
||||
end
|
||||
if bg_colour == nil then
|
||||
bg_colour_open = ''
|
||||
bg_colour_close = ''
|
||||
else
|
||||
bg_colour_open = '\\colorbox[HTML]{' .. bg_colour:gsub("^#", "") .. '}{'
|
||||
bg_colour_close = '}'
|
||||
end
|
||||
|
||||
table.insert(
|
||||
span.content, 1,
|
||||
pandoc.RawInline('latex', colour_open .. bg_colour_open)
|
||||
)
|
||||
table.insert(span.content, pandoc.RawInline('latex', bg_colour_close .. colour_close))
|
||||
|
||||
return span.content
|
||||
end
|
||||
|
||||
local function highlight_openxml_docx(span, colour, bg_colour)
|
||||
local spec = '<w:r><w:rPr>'
|
||||
if bg_colour ~= nil then
|
||||
spec = spec .. '<w:shd w:val="clear" w:fill="' .. bg_colour:gsub("^#", "") .. '"/>'
|
||||
end
|
||||
if colour ~= nil then
|
||||
spec = spec .. '<w:color w:val="' .. colour:gsub("^#", "") .. '"/>'
|
||||
end
|
||||
spec = spec .. '</w:rPr><w:t>'
|
||||
|
||||
table.insert(span.content, 1, pandoc.RawInline('openxml', spec))
|
||||
table.insert(span.content, pandoc.RawInline('openxml', '</w:t></w:r>'))
|
||||
|
||||
return span.content
|
||||
end
|
||||
|
||||
local function highlight_openxml_pptx(span, colour, bg_colour)
|
||||
local spec = '<a:r><a:rPr dirty="0">'
|
||||
if bg_colour ~= nil then
|
||||
spec = spec .. '<a:highlight><a:srgbClr val="' .. bg_colour:gsub("^#", "") .. '" /></a:highlight>'
|
||||
end
|
||||
if colour ~= nil then
|
||||
spec = spec .. '<a:solidFill><a:srgbClr val="' .. colour:gsub("^#", "") .. '" /></a:solidFill>'
|
||||
end
|
||||
spec = spec .. '</a:rPr><a:t>'
|
||||
|
||||
-- table.insert(span.content, 1, pandoc.RawInline('openxml', spec))
|
||||
-- table.insert(span.content, pandoc.RawInline('openxml', '</a:t></a:r>'))
|
||||
|
||||
local span_content_string = ""
|
||||
for i, inline in ipairs(span.content) do
|
||||
span_content_string = span_content_string .. pandoc.utils.stringify(inline)
|
||||
end
|
||||
|
||||
return pandoc.RawInline('openxml', spec .. span_content_string .. '</a:t></a:r>')
|
||||
end
|
||||
|
||||
local function highlight_typst(span, colour, bg_colour)
|
||||
if colour == nil then
|
||||
colour_open = ''
|
||||
colour_close = ''
|
||||
else
|
||||
colour_open = '#text(rgb("' .. colour .. '"))['
|
||||
colour_close = ']'
|
||||
end
|
||||
|
||||
if bg_colour == nil then
|
||||
bg_colour_open = ''
|
||||
bg_colour_close = ''
|
||||
else
|
||||
bg_colour_open = '#highlight(fill: rgb("' .. bg_colour .. '"))['
|
||||
bg_colour_close = ']'
|
||||
end
|
||||
|
||||
table.insert(
|
||||
span.content, 1,
|
||||
pandoc.RawInline('typst', colour_open .. bg_colour_open)
|
||||
)
|
||||
table.insert(
|
||||
span.content,
|
||||
pandoc.RawInline('typst', bg_colour_close .. colour_close)
|
||||
)
|
||||
|
||||
return span.content
|
||||
end
|
||||
|
||||
function Span(span)
|
||||
local colour = span.attributes['colour']
|
||||
if colour == nil then
|
||||
colour = span.attributes['color']
|
||||
end
|
||||
|
||||
local bg_colour = span.attributes['bg-colour']
|
||||
if bg_colour == nil then
|
||||
bg_colour = span.attributes['bg-color']
|
||||
end
|
||||
|
||||
if colour == nil and bg_colour == nil then return span end
|
||||
|
||||
if FORMAT:match 'html' or FORMAT:match 'revealjs' then
|
||||
return highlight_html(span, colour, bg_colour)
|
||||
elseif FORMAT:match 'latex' or FORMAT:match 'beamer' then
|
||||
return highlight_latex(span, colour, bg_colour)
|
||||
elseif FORMAT:match 'docx' then
|
||||
return highlight_openxml_docx(span, colour, bg_colour)
|
||||
elseif FORMAT:match 'pptx' then
|
||||
return highlight_openxml_pptx(span, colour, bg_colour)
|
||||
elseif FORMAT:match 'typst' then
|
||||
return highlight_typst(span, colour, bg_colour)
|
||||
else
|
||||
return span
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
title: Elsevier Journal Format
|
||||
author: Charles Teague
|
||||
version: 0.2.0
|
||||
version: 0.4.4
|
||||
quarto-required: ">=1.2.198"
|
||||
contributes:
|
||||
formats:
|
||||
|
||||
@@ -1,166 +1,178 @@
|
||||
local kClassOpt = "classoption"
|
||||
|
||||
-- cite style constants
|
||||
local kBibStyleDefault = 'number'
|
||||
local kBibStyles = { 'number', 'numbername', 'authoryear' }
|
||||
local kBibStyleAuthYr = 'elsarticle-harv'
|
||||
local kBibStyleNumber = 'elsarticle-num'
|
||||
local kBibStyleNumberName = 'elsarticle-num-names'
|
||||
local kBibStyleDefault = "number"
|
||||
local kBibStyles = { "number", "numbername", "authoryear" }
|
||||
local kBibStyleAuthYr = "elsarticle-harv"
|
||||
local kBibStyleNumber = "elsarticle-num"
|
||||
local kBibStyleNumberName = "elsarticle-num-names"
|
||||
local kBibStyleUnknown = kBibStyleNumberName
|
||||
|
||||
-- layout and style
|
||||
local kFormatting = pandoc.List({ 'preprint', 'review', 'doubleblind' })
|
||||
local kModels = pandoc.List({ '1p', '3p', '5p' })
|
||||
local kLayouts = pandoc.List({ 'onecolumn', 'twocolumn' })
|
||||
|
||||
local kFormatting = pandoc.List({ "preprint", "review", "doubleblind" })
|
||||
local kModels = pandoc.List({ "1p", "3p", "5p" })
|
||||
local kLayouts = pandoc.List({ "onecolumn", "twocolumn" })
|
||||
|
||||
local function setBibStyle(meta, style)
|
||||
meta['biblio-style'] = style
|
||||
quarto.doc.add_format_resource('bib/' .. style .. '.bst')
|
||||
-- .bst files are only supported with natbib
|
||||
if quarto.doc.cite_method() ~= "natbib" then
|
||||
return
|
||||
end
|
||||
if meta["biblio-style"] == nil then
|
||||
meta["biblio-style"] = style
|
||||
quarto.doc.add_format_resource("bib/" .. style .. ".bst")
|
||||
end
|
||||
end
|
||||
|
||||
local function hasClassOption(meta, option)
|
||||
if meta['classoption'] == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
for i, v in ipairs(meta['classoption']) do
|
||||
if v[1].text == option then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
if meta[kClassOpt] then
|
||||
for i, v in ipairs(meta[kClassOpt]) do
|
||||
if pandoc.utils.stringify(v) == option then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function addClassOption(meta, option)
|
||||
if meta['classoption'] == nil then
|
||||
meta['classoption'] = pandoc.List({})
|
||||
end
|
||||
|
||||
if not hasClassOption(meta, option) then
|
||||
meta['classoption']:insert({ pandoc.Str(option) })
|
||||
end
|
||||
if meta[kClassOpt] == nil then
|
||||
meta[kClassOpt] = pandoc.List({})
|
||||
elseif pandoc.utils.type(meta[kClassOpt]) == "Inlines" then
|
||||
-- handle classoption: <value> as a string
|
||||
meta[kClassOpt] = pandoc.List({ meta[kClassOpt] })
|
||||
end
|
||||
if not hasClassOption(meta, option) then
|
||||
meta[kClassOpt]:insert({ pandoc.Str(option) })
|
||||
end
|
||||
end
|
||||
|
||||
local function printList(list)
|
||||
local result = ''
|
||||
local sep = ''
|
||||
for i, v in ipairs(list) do
|
||||
result = result .. sep .. v
|
||||
sep = ', '
|
||||
end
|
||||
return result
|
||||
local result = ""
|
||||
local sep = ""
|
||||
for i, v in ipairs(list) do
|
||||
result = result .. sep .. v
|
||||
sep = ", "
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local bibstyle = kBibStyleDefault
|
||||
local bibstyle
|
||||
|
||||
return {
|
||||
{
|
||||
Meta = function(meta)
|
||||
-- If citeproc is being used, switch to the proper
|
||||
-- CSL file
|
||||
if quarto.doc.cite_method() == 'citeproc' and meta['csl'] == nil then
|
||||
meta['csl'] = quarto.utils.resolve_path('bib/elsevier-harvard.csl')
|
||||
end
|
||||
{
|
||||
Meta = function(meta)
|
||||
-- If citeproc is being used, switch to the proper
|
||||
-- CSL file
|
||||
if quarto.doc.cite_method() == "citeproc" and meta["csl"] == nil then
|
||||
meta["csl"] = quarto.utils.resolve_path("bib/elsevier-harvard.csl")
|
||||
end
|
||||
|
||||
if quarto.doc.is_format("pdf") then
|
||||
if quarto.doc.is_format("pdf") then
|
||||
-- read the journal settings
|
||||
local journal = meta["journal"]
|
||||
local citestyle = nil
|
||||
local formatting = nil
|
||||
local model = nil
|
||||
local layout = nil
|
||||
local name = nil
|
||||
|
||||
-- read the journal settings
|
||||
local journal = meta['journal']
|
||||
local citestyle = nil
|
||||
local formatting = nil
|
||||
local model = nil
|
||||
local layout = nil
|
||||
local name = nil
|
||||
if journal ~= nil then
|
||||
citestyle = journal["cite-style"]
|
||||
formatting = journal["formatting"]
|
||||
model = journal["model"]
|
||||
layout = journal["layout"]
|
||||
name = journal["name"]
|
||||
end
|
||||
|
||||
if journal ~= nil then
|
||||
citestyle = journal['cite-style']
|
||||
formatting = journal['formatting']
|
||||
model = journal['model']
|
||||
layout = journal['layout']
|
||||
name = journal['name']
|
||||
end
|
||||
-- process the site style
|
||||
if citestyle ~= nil then
|
||||
citestyle = pandoc.utils.stringify(citestyle)
|
||||
else
|
||||
citestyle = kBibStyleDefault
|
||||
end
|
||||
-- capture the bibstyle
|
||||
bibstyle = citestyle
|
||||
if citestyle == "numbername" then
|
||||
setBibStyle(meta, kBibStyleNumberName)
|
||||
addClassOption(meta, "number")
|
||||
elseif citestyle == "authoryear" then
|
||||
setBibStyle(meta, kBibStyleAuthYr)
|
||||
addClassOption(meta, "authoryear")
|
||||
elseif citestyle == "number" then
|
||||
setBibStyle(meta, kBibStyleNumber)
|
||||
addClassOption(meta, "number")
|
||||
elseif citestyle == "super" then
|
||||
addClassOption(meta, "super")
|
||||
setBibStyle(meta, kBibStyleNumber)
|
||||
else
|
||||
error("Unknown journal cite-style " .. citestyle .. "\nPlease use one of " .. printList(kBibStyles))
|
||||
setBibStyle(meta, kBibStyleUnknown)
|
||||
end
|
||||
|
||||
-- process the site style
|
||||
if citestyle ~= nil then
|
||||
citestyle = pandoc.utils.stringify(citestyle)
|
||||
else
|
||||
citestyle = kBibStyleDefault
|
||||
end
|
||||
-- process the layout
|
||||
if formatting ~= nil then
|
||||
formatting = pandoc.utils.stringify(formatting)
|
||||
if kFormatting:includes(formatting) then
|
||||
addClassOption(meta, formatting)
|
||||
else
|
||||
error(
|
||||
"Unknown journal formatting "
|
||||
.. formatting
|
||||
.. "\nPlease use one of "
|
||||
.. printList(kFormatting)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
-- capture the bibstyle
|
||||
bibstyle = citestyle
|
||||
if citestyle == 'numbername' then
|
||||
setBibStyle(meta, kBibStyleNumberName)
|
||||
addClassOption(meta, 'number')
|
||||
elseif citestyle == 'authoryear' then
|
||||
setBibStyle(meta, kBibStyleAuthYr)
|
||||
addClassOption(meta, 'authoryear')
|
||||
elseif citestyle == 'number' then
|
||||
setBibStyle(meta, kBibStyleNumber)
|
||||
addClassOption(meta, 'number')
|
||||
else
|
||||
error("Unknown journal cite-style " .. citestyle .. "\nPlease use one of " .. printList(kBibStyles))
|
||||
setBibStyle(meta, kBibStyleUnknown)
|
||||
end
|
||||
-- process the type
|
||||
if model ~= nil then
|
||||
model = pandoc.utils.stringify(model)
|
||||
if kModels:includes(model) then
|
||||
addClassOption(meta, model)
|
||||
else
|
||||
error("Unknown journal model " .. model .. "\nPlease use one of " .. printList(kModels))
|
||||
end
|
||||
end
|
||||
|
||||
-- process the layout
|
||||
if formatting ~= nil then
|
||||
formatting = pandoc.utils.stringify(formatting)
|
||||
if kFormatting:includes(formatting) then
|
||||
addClassOption(meta, formatting)
|
||||
else
|
||||
error("Unknown journal formatting " .. formatting .. "\nPlease use one of " .. printList(kFormatting))
|
||||
end
|
||||
end
|
||||
-- 5p models should be two column always
|
||||
if model == "5p" and layout == nil then
|
||||
layout = "twocolumn"
|
||||
end
|
||||
|
||||
-- process the type
|
||||
if model ~= nil then
|
||||
model = pandoc.utils.stringify(model)
|
||||
if kModels:includes(model) then
|
||||
addClassOption(meta, model)
|
||||
else
|
||||
error("Unknown journal model " .. model .. "\nPlease use one of " .. printList(kModels))
|
||||
end
|
||||
end
|
||||
-- process the type
|
||||
if layout ~= nil then
|
||||
layout = pandoc.utils.stringify(layout)
|
||||
if kLayouts:includes(layout) then
|
||||
addClassOption(meta, layout)
|
||||
if layout == "twocolumn" then
|
||||
quarto.doc.include_file("in-header", "partials/_two-column-longtable.tex")
|
||||
end
|
||||
else
|
||||
error("Unknown journal layout " .. layout .. "\nPlease use one of " .. printList(kLayouts))
|
||||
end
|
||||
end
|
||||
|
||||
-- 5p models should be two column always
|
||||
if model == '5p' and layout == nil then
|
||||
layout = 'twocolumn'
|
||||
end
|
||||
-- process the name
|
||||
if name ~= nil then
|
||||
name = pandoc.utils.stringify(name)
|
||||
quarto.doc.include_text("in-header", "\\journal{" .. name .. "}")
|
||||
end
|
||||
end
|
||||
|
||||
-- process the type
|
||||
if layout ~= nil then
|
||||
layout = pandoc.utils.stringify(layout)
|
||||
if kLayouts:includes(layout) then
|
||||
addClassOption(meta, layout)
|
||||
if layout == 'twocolumn' then
|
||||
quarto.doc.include_file('in-header', 'partials/_two-column-longtable.tex')
|
||||
end
|
||||
else
|
||||
error("Unknown journal layout " .. layout .. "\nPlease use one of " .. printList(kLayouts))
|
||||
end
|
||||
end
|
||||
|
||||
-- process the name
|
||||
if name ~= nil then
|
||||
name = pandoc.utils.stringify(name)
|
||||
quarto.doc.include_text('in-header', '\\journal{' .. name .. '}')
|
||||
end
|
||||
end
|
||||
|
||||
return meta
|
||||
end
|
||||
},
|
||||
{
|
||||
Cite = function(cite)
|
||||
if bibstyle == 'number' then
|
||||
-- If we are numbered, force citations into normal mode
|
||||
-- as the author styles don't make sense
|
||||
for i, v in ipairs(cite.citations) do
|
||||
v.mode = 'NormalCitation'
|
||||
end
|
||||
return cite
|
||||
end
|
||||
end,
|
||||
|
||||
}
|
||||
return meta
|
||||
end,
|
||||
},
|
||||
{
|
||||
Cite = function(cite)
|
||||
if bibstyle == "number" or bibstyle == "super" then
|
||||
-- If we are numbered, force citations into normal mode
|
||||
-- as the author styles don't make sense
|
||||
for i, v in ipairs(cite.citations) do
|
||||
v.mode = "NormalCitation"
|
||||
end
|
||||
return cite
|
||||
end
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
title: Name Your References
|
||||
author: Shafayet Khan Shafee
|
||||
version: 1.1.0
|
||||
quarto-required: ">=1.2.0"
|
||||
contributes:
|
||||
filters:
|
||||
- nameref.lua
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
local str = pandoc.utils.stringify
|
||||
local p = quarto.log.output
|
||||
|
||||
function get_header_data(data, numbered)
|
||||
local get_headers = {
|
||||
-- get the Header text and header id from headers and
|
||||
-- insert them into table `data` passed into `get_header_data`
|
||||
Header = function(el)
|
||||
local id = el.identifier
|
||||
local number = numbered=="true" and el.attributes['number'] or ""
|
||||
local text = str(el.content):gsub("^[%d.]+ ", "")
|
||||
text = number .. " " .. text
|
||||
table.insert(data, {id = id, text = text})
|
||||
end,
|
||||
|
||||
-- get the id and title attribute from pandoc div, so the
|
||||
-- generated output has this id and can be referred with.
|
||||
Div = function(el)
|
||||
if el.attributes["link-id"] then
|
||||
local id = el.attributes["link-id"]
|
||||
local text = el.attributes["link-title"]
|
||||
table.insert(data, {id = id, text = text})
|
||||
end
|
||||
end
|
||||
}
|
||||
return get_headers
|
||||
end
|
||||
|
||||
|
||||
function change_ref(data)
|
||||
local change_rawinline = {
|
||||
-- change the nameref instance with pandoc.Link
|
||||
RawInline = function(el)
|
||||
for key, value in pairs(data) do
|
||||
if el.text:match("\\nameref{(.*)}") == value.id then
|
||||
local target = "#" .. value.id
|
||||
local link = pandoc.Link(value.text, target)
|
||||
return link
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
return change_rawinline
|
||||
end
|
||||
|
||||
|
||||
local function add_div_id(div)
|
||||
return {
|
||||
-- add id to div with `cell-output-stdout` or
|
||||
-- `cell-output-display`
|
||||
Div = function(elem)
|
||||
if elem.classes:includes("cell-output-display")
|
||||
or elem.classes:includes("cell-output-stdout") then
|
||||
elem.identifier = div.attributes["link-id"]
|
||||
return elem
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
function Div(el)
|
||||
-- target those div with attribute `link-id`
|
||||
if el.attributes["link-id"] then
|
||||
-- for markdown syntax (image or table) wrapped withing
|
||||
-- div with `link`
|
||||
if el.classes:includes('link') then
|
||||
el.identifier = el.attributes["link-id"]
|
||||
end
|
||||
return el:walk(add_div_id(el))
|
||||
end
|
||||
end
|
||||
|
||||
function Pandoc(doc)
|
||||
local meta = doc.meta
|
||||
local numbered
|
||||
local numbered = meta.nameref and str(meta.nameref['section-number'])
|
||||
local header_data = {}
|
||||
-- populate the header_data table
|
||||
doc:walk(get_header_data(header_data, numbered))
|
||||
-- generate named link inplaces with `\nameref`
|
||||
return doc:walk(change_ref(header_data))
|
||||
end
|
||||
|
||||
-2078
File diff suppressed because it is too large
Load Diff
Binary file not shown.
-12
File diff suppressed because one or more lines are too long
Vendored
-7
File diff suppressed because one or more lines are too long
Vendored
-7
File diff suppressed because one or more lines are too long
Vendored
-9
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
.tippy-box[data-theme~=light-border]{background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,8,16,.15);color:#333;box-shadow:0 4px 14px -2px rgba(0,8,16,.08)}.tippy-box[data-theme~=light-border]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light-border]>.tippy-arrow:after,.tippy-box[data-theme~=light-border]>.tippy-svg-arrow:after{content:"";position:absolute;z-index:-1}.tippy-box[data-theme~=light-border]>.tippy-arrow:after{border-color:transparent;border-style:solid}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-arrow:after{border-top-color:rgba(0,8,16,.2);border-width:7px 7px 0;top:17px;left:1px}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-svg-arrow>svg{top:16px}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-svg-arrow:after{top:17px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff;bottom:16px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-arrow:after{border-bottom-color:rgba(0,8,16,.2);border-width:0 7px 7px;bottom:17px;left:1px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:16px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-svg-arrow:after{bottom:17px}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-arrow:after{border-left-color:rgba(0,8,16,.2);border-width:7px 0 7px 7px;left:17px;top:1px}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-svg-arrow>svg{left:11px}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-svg-arrow:after{left:12px}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff;right:16px}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-arrow:after{border-width:7px 7px 7px 0;right:17px;top:1px;border-right-color:rgba(0,8,16,.2)}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-svg-arrow>svg{right:11px}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-svg-arrow:after{right:12px}.tippy-box[data-theme~=light-border]>.tippy-svg-arrow{fill:#fff}.tippy-box[data-theme~=light-border]>.tippy-svg-arrow:after{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiLz48L3N2Zz4=);background-size:16px 6px;width:16px;height:6px}
|
||||
Vendored
-6
File diff suppressed because one or more lines are too long
-1
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
/* quarto syntax highlight colors */
|
||||
:root {
|
||||
--quarto-hl-ot-color: #003B4F;
|
||||
--quarto-hl-at-color: #657422;
|
||||
--quarto-hl-ss-color: #20794D;
|
||||
--quarto-hl-an-color: #5E5E5E;
|
||||
--quarto-hl-fu-color: #4758AB;
|
||||
--quarto-hl-st-color: #20794D;
|
||||
--quarto-hl-cf-color: #003B4F;
|
||||
--quarto-hl-op-color: #5E5E5E;
|
||||
--quarto-hl-er-color: #AD0000;
|
||||
--quarto-hl-bn-color: #AD0000;
|
||||
--quarto-hl-al-color: #AD0000;
|
||||
--quarto-hl-va-color: #111111;
|
||||
--quarto-hl-bu-color: inherit;
|
||||
--quarto-hl-ex-color: inherit;
|
||||
--quarto-hl-pp-color: #AD0000;
|
||||
--quarto-hl-in-color: #5E5E5E;
|
||||
--quarto-hl-vs-color: #20794D;
|
||||
--quarto-hl-wa-color: #5E5E5E;
|
||||
--quarto-hl-do-color: #5E5E5E;
|
||||
--quarto-hl-im-color: #00769E;
|
||||
--quarto-hl-ch-color: #20794D;
|
||||
--quarto-hl-dt-color: #AD0000;
|
||||
--quarto-hl-fl-color: #AD0000;
|
||||
--quarto-hl-co-color: #5E5E5E;
|
||||
--quarto-hl-cv-color: #5E5E5E;
|
||||
--quarto-hl-cn-color: #8f5902;
|
||||
--quarto-hl-sc-color: #5E5E5E;
|
||||
--quarto-hl-dv-color: #AD0000;
|
||||
--quarto-hl-kw-color: #003B4F;
|
||||
}
|
||||
|
||||
/* other quarto variables */
|
||||
pre > code.sourceCode > span {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
code span {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
code.sourceCode > span {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
div.sourceCode,
|
||||
div.sourceCode pre.sourceCode {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
code span.ot {
|
||||
color: #003B4F;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.at {
|
||||
color: #657422;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.ss {
|
||||
color: #20794D;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.an {
|
||||
color: #5E5E5E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.fu {
|
||||
color: #4758AB;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.st {
|
||||
color: #20794D;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.cf {
|
||||
color: #003B4F;
|
||||
font-weight: bold;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.op {
|
||||
color: #5E5E5E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.er {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.bn {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.al {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.va {
|
||||
color: #111111;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.bu {
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.ex {
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.pp {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.in {
|
||||
color: #5E5E5E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.vs {
|
||||
color: #20794D;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.wa {
|
||||
color: #5E5E5E;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
code span.do {
|
||||
color: #5E5E5E;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
code span.im {
|
||||
color: #00769E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.ch {
|
||||
color: #20794D;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.dt {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.fl {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.co {
|
||||
color: #5E5E5E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.cv {
|
||||
color: #5E5E5E;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
code span.cn {
|
||||
color: #8f5902;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.sc {
|
||||
color: #5E5E5E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.dv {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
code span.kw {
|
||||
color: #003B4F;
|
||||
font-weight: bold;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
.prevent-inlining {
|
||||
content: "</";
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=debc5d5d77c3f9108843748ff7464032.css.map */
|
||||
@@ -1,908 +0,0 @@
|
||||
const sectionChanged = new CustomEvent("quarto-sectionChanged", {
|
||||
detail: {},
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
composed: false,
|
||||
});
|
||||
|
||||
const layoutMarginEls = () => {
|
||||
// Find any conflicting margin elements and add margins to the
|
||||
// top to prevent overlap
|
||||
const marginChildren = window.document.querySelectorAll(
|
||||
".column-margin.column-container > *, .margin-caption, .aside"
|
||||
);
|
||||
|
||||
let lastBottom = 0;
|
||||
for (const marginChild of marginChildren) {
|
||||
if (marginChild.offsetParent !== null) {
|
||||
// clear the top margin so we recompute it
|
||||
marginChild.style.marginTop = null;
|
||||
const top = marginChild.getBoundingClientRect().top + window.scrollY;
|
||||
if (top < lastBottom) {
|
||||
const marginChildStyle = window.getComputedStyle(marginChild);
|
||||
const marginBottom = parseFloat(marginChildStyle["marginBottom"]);
|
||||
const margin = lastBottom - top + marginBottom;
|
||||
marginChild.style.marginTop = `${margin}px`;
|
||||
}
|
||||
const styles = window.getComputedStyle(marginChild);
|
||||
const marginTop = parseFloat(styles["marginTop"]);
|
||||
lastBottom = top + marginChild.getBoundingClientRect().height + marginTop;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.document.addEventListener("DOMContentLoaded", function (_event) {
|
||||
// Recompute the position of margin elements anytime the body size changes
|
||||
if (window.ResizeObserver) {
|
||||
const resizeObserver = new window.ResizeObserver(
|
||||
throttle(() => {
|
||||
layoutMarginEls();
|
||||
if (
|
||||
window.document.body.getBoundingClientRect().width < 990 &&
|
||||
isReaderMode()
|
||||
) {
|
||||
quartoToggleReader();
|
||||
}
|
||||
}, 50)
|
||||
);
|
||||
resizeObserver.observe(window.document.body);
|
||||
}
|
||||
|
||||
const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]');
|
||||
const sidebarEl = window.document.getElementById("quarto-sidebar");
|
||||
const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left");
|
||||
const marginSidebarEl = window.document.getElementById(
|
||||
"quarto-margin-sidebar"
|
||||
);
|
||||
// function to determine whether the element has a previous sibling that is active
|
||||
const prevSiblingIsActiveLink = (el) => {
|
||||
const sibling = el.previousElementSibling;
|
||||
if (sibling && sibling.tagName === "A") {
|
||||
return sibling.classList.contains("active");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior)
|
||||
function fireSlideEnter(e) {
|
||||
const event = window.document.createEvent("Event");
|
||||
event.initEvent("slideenter", true, true);
|
||||
window.document.dispatchEvent(event);
|
||||
}
|
||||
const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]');
|
||||
tabs.forEach((tab) => {
|
||||
tab.addEventListener("shown.bs.tab", fireSlideEnter);
|
||||
});
|
||||
|
||||
// fire slideEnter for tabby tab activations (for htmlwidget resize behavior)
|
||||
document.addEventListener("tabby", fireSlideEnter, false);
|
||||
|
||||
// Track scrolling and mark TOC links as active
|
||||
// get table of contents and sidebar (bail if we don't have at least one)
|
||||
const tocLinks = tocEl
|
||||
? [...tocEl.querySelectorAll("a[data-scroll-target]")]
|
||||
: [];
|
||||
const makeActive = (link) => tocLinks[link].classList.add("active");
|
||||
const removeActive = (link) => tocLinks[link].classList.remove("active");
|
||||
const removeAllActive = () =>
|
||||
[...Array(tocLinks.length).keys()].forEach((link) => removeActive(link));
|
||||
|
||||
// activate the anchor for a section associated with this TOC entry
|
||||
tocLinks.forEach((link) => {
|
||||
link.addEventListener("click", () => {
|
||||
if (link.href.indexOf("#") !== -1) {
|
||||
const anchor = link.href.split("#")[1];
|
||||
const heading = window.document.querySelector(
|
||||
`[data-anchor-id="${anchor}"]`
|
||||
);
|
||||
if (heading) {
|
||||
// Add the class
|
||||
heading.classList.add("reveal-anchorjs-link");
|
||||
|
||||
// function to show the anchor
|
||||
const handleMouseout = () => {
|
||||
heading.classList.remove("reveal-anchorjs-link");
|
||||
heading.removeEventListener("mouseout", handleMouseout);
|
||||
};
|
||||
|
||||
// add a function to clear the anchor when the user mouses out of it
|
||||
heading.addEventListener("mouseout", handleMouseout);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const sections = tocLinks.map((link) => {
|
||||
const target = link.getAttribute("data-scroll-target");
|
||||
if (target.startsWith("#")) {
|
||||
return window.document.getElementById(decodeURI(`${target.slice(1)}`));
|
||||
} else {
|
||||
return window.document.querySelector(decodeURI(`${target}`));
|
||||
}
|
||||
});
|
||||
|
||||
const sectionMargin = 200;
|
||||
let currentActive = 0;
|
||||
// track whether we've initialized state the first time
|
||||
let init = false;
|
||||
|
||||
const updateActiveLink = () => {
|
||||
// The index from bottom to top (e.g. reversed list)
|
||||
let sectionIndex = -1;
|
||||
if (
|
||||
window.innerHeight + window.pageYOffset >=
|
||||
window.document.body.offsetHeight
|
||||
) {
|
||||
// This is the no-scroll case where last section should be the active one
|
||||
sectionIndex = 0;
|
||||
} else {
|
||||
// This finds the last section visible on screen that should be made active
|
||||
sectionIndex = [...sections].reverse().findIndex((section) => {
|
||||
if (section) {
|
||||
return window.pageYOffset >= section.offsetTop - sectionMargin;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (sectionIndex > -1) {
|
||||
const current = sections.length - sectionIndex - 1;
|
||||
if (current !== currentActive) {
|
||||
removeAllActive();
|
||||
currentActive = current;
|
||||
makeActive(current);
|
||||
if (init) {
|
||||
window.dispatchEvent(sectionChanged);
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const inHiddenRegion = (top, bottom, hiddenRegions) => {
|
||||
for (const region of hiddenRegions) {
|
||||
if (top <= region.bottom && bottom >= region.top) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const categorySelector = "header.quarto-title-block .quarto-category";
|
||||
const activateCategories = (href) => {
|
||||
// Find any categories
|
||||
// Surround them with a link pointing back to:
|
||||
// #category=Authoring
|
||||
try {
|
||||
const categoryEls = window.document.querySelectorAll(categorySelector);
|
||||
for (const categoryEl of categoryEls) {
|
||||
const categoryText = categoryEl.textContent;
|
||||
if (categoryText) {
|
||||
const link = `${href}#category=${encodeURIComponent(categoryText)}`;
|
||||
const linkEl = window.document.createElement("a");
|
||||
linkEl.setAttribute("href", link);
|
||||
for (const child of categoryEl.childNodes) {
|
||||
linkEl.append(child);
|
||||
}
|
||||
categoryEl.appendChild(linkEl);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
};
|
||||
function hasTitleCategories() {
|
||||
return window.document.querySelector(categorySelector) !== null;
|
||||
}
|
||||
|
||||
function offsetRelativeUrl(url) {
|
||||
const offset = getMeta("quarto:offset");
|
||||
return offset ? offset + url : url;
|
||||
}
|
||||
|
||||
function offsetAbsoluteUrl(url) {
|
||||
const offset = getMeta("quarto:offset");
|
||||
const baseUrl = new URL(offset, window.location);
|
||||
|
||||
const projRelativeUrl = url.replace(baseUrl, "");
|
||||
if (projRelativeUrl.startsWith("/")) {
|
||||
return projRelativeUrl;
|
||||
} else {
|
||||
return "/" + projRelativeUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// read a meta tag value
|
||||
function getMeta(metaName) {
|
||||
const metas = window.document.getElementsByTagName("meta");
|
||||
for (let i = 0; i < metas.length; i++) {
|
||||
if (metas[i].getAttribute("name") === metaName) {
|
||||
return metas[i].getAttribute("content");
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
async function findAndActivateCategories() {
|
||||
const currentPagePath = offsetAbsoluteUrl(window.location.href);
|
||||
const response = await fetch(offsetRelativeUrl("listings.json"));
|
||||
if (response.status == 200) {
|
||||
return response.json().then(function (listingPaths) {
|
||||
const listingHrefs = [];
|
||||
for (const listingPath of listingPaths) {
|
||||
const pathWithoutLeadingSlash = listingPath.listing.substring(1);
|
||||
for (const item of listingPath.items) {
|
||||
if (
|
||||
item === currentPagePath ||
|
||||
item === currentPagePath + "index.html"
|
||||
) {
|
||||
// Resolve this path against the offset to be sure
|
||||
// we already are using the correct path to the listing
|
||||
// (this adjusts the listing urls to be rooted against
|
||||
// whatever root the page is actually running against)
|
||||
const relative = offsetRelativeUrl(pathWithoutLeadingSlash);
|
||||
const baseUrl = window.location;
|
||||
const resolvedPath = new URL(relative, baseUrl);
|
||||
listingHrefs.push(resolvedPath.pathname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look up the tree for a nearby linting and use that if we find one
|
||||
const nearestListing = findNearestParentListing(
|
||||
offsetAbsoluteUrl(window.location.pathname),
|
||||
listingHrefs
|
||||
);
|
||||
if (nearestListing) {
|
||||
activateCategories(nearestListing);
|
||||
} else {
|
||||
// See if the referrer is a listing page for this item
|
||||
const referredRelativePath = offsetAbsoluteUrl(document.referrer);
|
||||
const referrerListing = listingHrefs.find((listingHref) => {
|
||||
const isListingReferrer =
|
||||
listingHref === referredRelativePath ||
|
||||
listingHref === referredRelativePath + "index.html";
|
||||
return isListingReferrer;
|
||||
});
|
||||
|
||||
if (referrerListing) {
|
||||
// Try to use the referrer if possible
|
||||
activateCategories(referrerListing);
|
||||
} else if (listingHrefs.length > 0) {
|
||||
// Otherwise, just fall back to the first listing
|
||||
activateCategories(listingHrefs[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (hasTitleCategories()) {
|
||||
findAndActivateCategories();
|
||||
}
|
||||
|
||||
const findNearestParentListing = (href, listingHrefs) => {
|
||||
if (!href || !listingHrefs) {
|
||||
return undefined;
|
||||
}
|
||||
// Look up the tree for a nearby linting and use that if we find one
|
||||
const relativeParts = href.substring(1).split("/");
|
||||
while (relativeParts.length > 0) {
|
||||
const path = relativeParts.join("/");
|
||||
for (const listingHref of listingHrefs) {
|
||||
if (listingHref.startsWith(path)) {
|
||||
return listingHref;
|
||||
}
|
||||
}
|
||||
relativeParts.pop();
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const manageSidebarVisiblity = (el, placeholderDescriptor) => {
|
||||
let isVisible = true;
|
||||
let elRect;
|
||||
|
||||
return (hiddenRegions) => {
|
||||
if (el === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the last element of the TOC
|
||||
const lastChildEl = el.lastElementChild;
|
||||
|
||||
if (lastChildEl) {
|
||||
// Converts the sidebar to a menu
|
||||
const convertToMenu = () => {
|
||||
for (const child of el.children) {
|
||||
child.style.opacity = 0;
|
||||
child.style.overflow = "hidden";
|
||||
child.style.pointerEvents = "none";
|
||||
}
|
||||
|
||||
nexttick(() => {
|
||||
const toggleContainer = window.document.createElement("div");
|
||||
toggleContainer.style.width = "100%";
|
||||
toggleContainer.classList.add("zindex-over-content");
|
||||
toggleContainer.classList.add("quarto-sidebar-toggle");
|
||||
toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom
|
||||
toggleContainer.id = placeholderDescriptor.id;
|
||||
toggleContainer.style.position = "fixed";
|
||||
|
||||
const toggleIcon = window.document.createElement("i");
|
||||
toggleIcon.classList.add("quarto-sidebar-toggle-icon");
|
||||
toggleIcon.classList.add("bi");
|
||||
toggleIcon.classList.add("bi-caret-down-fill");
|
||||
|
||||
const toggleTitle = window.document.createElement("div");
|
||||
const titleEl = window.document.body.querySelector(
|
||||
placeholderDescriptor.titleSelector
|
||||
);
|
||||
if (titleEl) {
|
||||
toggleTitle.append(
|
||||
titleEl.textContent || titleEl.innerText,
|
||||
toggleIcon
|
||||
);
|
||||
}
|
||||
toggleTitle.classList.add("zindex-over-content");
|
||||
toggleTitle.classList.add("quarto-sidebar-toggle-title");
|
||||
toggleContainer.append(toggleTitle);
|
||||
|
||||
const toggleContents = window.document.createElement("div");
|
||||
toggleContents.classList = el.classList;
|
||||
toggleContents.classList.add("zindex-over-content");
|
||||
toggleContents.classList.add("quarto-sidebar-toggle-contents");
|
||||
for (const child of el.children) {
|
||||
if (child.id === "toc-title") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const clone = child.cloneNode(true);
|
||||
clone.style.opacity = 1;
|
||||
clone.style.pointerEvents = null;
|
||||
clone.style.display = null;
|
||||
toggleContents.append(clone);
|
||||
}
|
||||
toggleContents.style.height = "0px";
|
||||
const positionToggle = () => {
|
||||
// position the element (top left of parent, same width as parent)
|
||||
if (!elRect) {
|
||||
elRect = el.getBoundingClientRect();
|
||||
}
|
||||
toggleContainer.style.left = `${elRect.left}px`;
|
||||
toggleContainer.style.top = `${elRect.top}px`;
|
||||
toggleContainer.style.width = `${elRect.width}px`;
|
||||
};
|
||||
positionToggle();
|
||||
|
||||
toggleContainer.append(toggleContents);
|
||||
el.parentElement.prepend(toggleContainer);
|
||||
|
||||
// Process clicks
|
||||
let tocShowing = false;
|
||||
// Allow the caller to control whether this is dismissed
|
||||
// when it is clicked (e.g. sidebar navigation supports
|
||||
// opening and closing the nav tree, so don't dismiss on click)
|
||||
const clickEl = placeholderDescriptor.dismissOnClick
|
||||
? toggleContainer
|
||||
: toggleTitle;
|
||||
|
||||
const closeToggle = () => {
|
||||
if (tocShowing) {
|
||||
toggleContainer.classList.remove("expanded");
|
||||
toggleContents.style.height = "0px";
|
||||
tocShowing = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Get rid of any expanded toggle if the user scrolls
|
||||
window.document.addEventListener(
|
||||
"scroll",
|
||||
throttle(() => {
|
||||
closeToggle();
|
||||
}, 50)
|
||||
);
|
||||
|
||||
// Handle positioning of the toggle
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
throttle(() => {
|
||||
elRect = undefined;
|
||||
positionToggle();
|
||||
}, 50)
|
||||
);
|
||||
|
||||
window.addEventListener("quarto-hrChanged", () => {
|
||||
elRect = undefined;
|
||||
});
|
||||
|
||||
// Process the click
|
||||
clickEl.onclick = () => {
|
||||
if (!tocShowing) {
|
||||
toggleContainer.classList.add("expanded");
|
||||
toggleContents.style.height = null;
|
||||
tocShowing = true;
|
||||
} else {
|
||||
closeToggle();
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Converts a sidebar from a menu back to a sidebar
|
||||
const convertToSidebar = () => {
|
||||
for (const child of el.children) {
|
||||
child.style.opacity = 1;
|
||||
child.style.overflow = null;
|
||||
child.style.pointerEvents = null;
|
||||
}
|
||||
|
||||
const placeholderEl = window.document.getElementById(
|
||||
placeholderDescriptor.id
|
||||
);
|
||||
if (placeholderEl) {
|
||||
placeholderEl.remove();
|
||||
}
|
||||
|
||||
el.classList.remove("rollup");
|
||||
};
|
||||
|
||||
if (isReaderMode()) {
|
||||
convertToMenu();
|
||||
isVisible = false;
|
||||
} else {
|
||||
// Find the top and bottom o the element that is being managed
|
||||
const elTop = el.offsetTop;
|
||||
const elBottom =
|
||||
elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight;
|
||||
|
||||
if (!isVisible) {
|
||||
// If the element is current not visible reveal if there are
|
||||
// no conflicts with overlay regions
|
||||
if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) {
|
||||
convertToSidebar();
|
||||
isVisible = true;
|
||||
}
|
||||
} else {
|
||||
// If the element is visible, hide it if it conflicts with overlay regions
|
||||
// and insert a placeholder toggle (or if we're in reader mode)
|
||||
if (inHiddenRegion(elTop, elBottom, hiddenRegions)) {
|
||||
convertToMenu();
|
||||
isVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]');
|
||||
for (const tabEl of tabEls) {
|
||||
const id = tabEl.getAttribute("data-bs-target");
|
||||
if (id) {
|
||||
const columnEl = document.querySelector(
|
||||
`${id} .column-margin, .tabset-margin-content`
|
||||
);
|
||||
if (columnEl)
|
||||
tabEl.addEventListener("shown.bs.tab", function (event) {
|
||||
const el = event.srcElement;
|
||||
if (el) {
|
||||
const visibleCls = `${el.id}-margin-content`;
|
||||
// walk up until we find a parent tabset
|
||||
let panelTabsetEl = el.parentElement;
|
||||
while (panelTabsetEl) {
|
||||
if (panelTabsetEl.classList.contains("panel-tabset")) {
|
||||
break;
|
||||
}
|
||||
panelTabsetEl = panelTabsetEl.parentElement;
|
||||
}
|
||||
|
||||
if (panelTabsetEl) {
|
||||
const prevSib = panelTabsetEl.previousElementSibling;
|
||||
if (
|
||||
prevSib &&
|
||||
prevSib.classList.contains("tabset-margin-container")
|
||||
) {
|
||||
const childNodes = prevSib.querySelectorAll(
|
||||
".tabset-margin-content"
|
||||
);
|
||||
for (const childEl of childNodes) {
|
||||
if (childEl.classList.contains(visibleCls)) {
|
||||
childEl.classList.remove("collapse");
|
||||
} else {
|
||||
childEl.classList.add("collapse");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layoutMarginEls();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Manage the visibility of the toc and the sidebar
|
||||
const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, {
|
||||
id: "quarto-toc-toggle",
|
||||
titleSelector: "#toc-title",
|
||||
dismissOnClick: true,
|
||||
});
|
||||
const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, {
|
||||
id: "quarto-sidebarnav-toggle",
|
||||
titleSelector: ".title",
|
||||
dismissOnClick: false,
|
||||
});
|
||||
let tocLeftScrollVisibility;
|
||||
if (leftTocEl) {
|
||||
tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, {
|
||||
id: "quarto-lefttoc-toggle",
|
||||
titleSelector: "#toc-title",
|
||||
dismissOnClick: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Find the first element that uses formatting in special columns
|
||||
const conflictingEls = window.document.body.querySelectorAll(
|
||||
'[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]'
|
||||
);
|
||||
|
||||
// Filter all the possibly conflicting elements into ones
|
||||
// the do conflict on the left or ride side
|
||||
const arrConflictingEls = Array.from(conflictingEls);
|
||||
const leftSideConflictEls = arrConflictingEls.filter((el) => {
|
||||
if (el.tagName === "ASIDE") {
|
||||
return false;
|
||||
}
|
||||
return Array.from(el.classList).find((className) => {
|
||||
return (
|
||||
className !== "column-body" &&
|
||||
className.startsWith("column-") &&
|
||||
!className.endsWith("right") &&
|
||||
!className.endsWith("container") &&
|
||||
className !== "column-margin"
|
||||
);
|
||||
});
|
||||
});
|
||||
const rightSideConflictEls = arrConflictingEls.filter((el) => {
|
||||
if (el.tagName === "ASIDE") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hasMarginCaption = Array.from(el.classList).find((className) => {
|
||||
return className == "margin-caption";
|
||||
});
|
||||
if (hasMarginCaption) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Array.from(el.classList).find((className) => {
|
||||
return (
|
||||
className !== "column-body" &&
|
||||
!className.endsWith("container") &&
|
||||
className.startsWith("column-") &&
|
||||
!className.endsWith("left")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const kOverlapPaddingSize = 10;
|
||||
function toRegions(els) {
|
||||
return els.map((el) => {
|
||||
const boundRect = el.getBoundingClientRect();
|
||||
const top =
|
||||
boundRect.top +
|
||||
document.documentElement.scrollTop -
|
||||
kOverlapPaddingSize;
|
||||
return {
|
||||
top,
|
||||
bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
let hasObserved = false;
|
||||
const visibleItemObserver = (els) => {
|
||||
let visibleElements = [...els];
|
||||
const intersectionObserver = new IntersectionObserver(
|
||||
(entries, _observer) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
if (visibleElements.indexOf(entry.target) === -1) {
|
||||
visibleElements.push(entry.target);
|
||||
}
|
||||
} else {
|
||||
visibleElements = visibleElements.filter((visibleEntry) => {
|
||||
return visibleEntry !== entry;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasObserved) {
|
||||
hideOverlappedSidebars();
|
||||
}
|
||||
hasObserved = true;
|
||||
},
|
||||
{}
|
||||
);
|
||||
els.forEach((el) => {
|
||||
intersectionObserver.observe(el);
|
||||
});
|
||||
|
||||
return {
|
||||
getVisibleEntries: () => {
|
||||
return visibleElements;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const rightElementObserver = visibleItemObserver(rightSideConflictEls);
|
||||
const leftElementObserver = visibleItemObserver(leftSideConflictEls);
|
||||
|
||||
const hideOverlappedSidebars = () => {
|
||||
marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries()));
|
||||
sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries()));
|
||||
if (tocLeftScrollVisibility) {
|
||||
tocLeftScrollVisibility(
|
||||
toRegions(leftElementObserver.getVisibleEntries())
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
window.quartoToggleReader = () => {
|
||||
// Applies a slow class (or removes it)
|
||||
// to update the transition speed
|
||||
const slowTransition = (slow) => {
|
||||
const manageTransition = (id, slow) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
if (slow) {
|
||||
el.classList.add("slow");
|
||||
} else {
|
||||
el.classList.remove("slow");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
manageTransition("TOC", slow);
|
||||
manageTransition("quarto-sidebar", slow);
|
||||
};
|
||||
const readerMode = !isReaderMode();
|
||||
setReaderModeValue(readerMode);
|
||||
|
||||
// If we're entering reader mode, slow the transition
|
||||
if (readerMode) {
|
||||
slowTransition(readerMode);
|
||||
}
|
||||
highlightReaderToggle(readerMode);
|
||||
hideOverlappedSidebars();
|
||||
|
||||
// If we're exiting reader mode, restore the non-slow transition
|
||||
if (!readerMode) {
|
||||
slowTransition(!readerMode);
|
||||
}
|
||||
};
|
||||
|
||||
const highlightReaderToggle = (readerMode) => {
|
||||
const els = document.querySelectorAll(".quarto-reader-toggle");
|
||||
if (els) {
|
||||
els.forEach((el) => {
|
||||
if (readerMode) {
|
||||
el.classList.add("reader");
|
||||
} else {
|
||||
el.classList.remove("reader");
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const setReaderModeValue = (val) => {
|
||||
if (window.location.protocol !== "file:") {
|
||||
window.localStorage.setItem("quarto-reader-mode", val);
|
||||
} else {
|
||||
localReaderMode = val;
|
||||
}
|
||||
};
|
||||
|
||||
const isReaderMode = () => {
|
||||
if (window.location.protocol !== "file:") {
|
||||
return window.localStorage.getItem("quarto-reader-mode") === "true";
|
||||
} else {
|
||||
return localReaderMode;
|
||||
}
|
||||
};
|
||||
let localReaderMode = null;
|
||||
|
||||
const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded");
|
||||
const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1;
|
||||
|
||||
// Walk the TOC and collapse/expand nodes
|
||||
// Nodes are expanded if:
|
||||
// - they are top level
|
||||
// - they have children that are 'active' links
|
||||
// - they are directly below an link that is 'active'
|
||||
const walk = (el, depth) => {
|
||||
// Tick depth when we enter a UL
|
||||
if (el.tagName === "UL") {
|
||||
depth = depth + 1;
|
||||
}
|
||||
|
||||
// It this is active link
|
||||
let isActiveNode = false;
|
||||
if (el.tagName === "A" && el.classList.contains("active")) {
|
||||
isActiveNode = true;
|
||||
}
|
||||
|
||||
// See if there is an active child to this element
|
||||
let hasActiveChild = false;
|
||||
for (child of el.children) {
|
||||
hasActiveChild = walk(child, depth) || hasActiveChild;
|
||||
}
|
||||
|
||||
// Process the collapse state if this is an UL
|
||||
if (el.tagName === "UL") {
|
||||
if (tocOpenDepth === -1 && depth > 1) {
|
||||
// toc-expand: false
|
||||
el.classList.add("collapse");
|
||||
} else if (
|
||||
depth <= tocOpenDepth ||
|
||||
hasActiveChild ||
|
||||
prevSiblingIsActiveLink(el)
|
||||
) {
|
||||
el.classList.remove("collapse");
|
||||
} else {
|
||||
el.classList.add("collapse");
|
||||
}
|
||||
|
||||
// untick depth when we leave a UL
|
||||
depth = depth - 1;
|
||||
}
|
||||
return hasActiveChild || isActiveNode;
|
||||
};
|
||||
|
||||
// walk the TOC and expand / collapse any items that should be shown
|
||||
if (tocEl) {
|
||||
updateActiveLink();
|
||||
walk(tocEl, 0);
|
||||
}
|
||||
|
||||
// Throttle the scroll event and walk peridiocally
|
||||
window.document.addEventListener(
|
||||
"scroll",
|
||||
throttle(() => {
|
||||
if (tocEl) {
|
||||
updateActiveLink();
|
||||
walk(tocEl, 0);
|
||||
}
|
||||
if (!isReaderMode()) {
|
||||
hideOverlappedSidebars();
|
||||
}
|
||||
}, 5)
|
||||
);
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
throttle(() => {
|
||||
if (tocEl) {
|
||||
updateActiveLink();
|
||||
walk(tocEl, 0);
|
||||
}
|
||||
if (!isReaderMode()) {
|
||||
hideOverlappedSidebars();
|
||||
}
|
||||
}, 10)
|
||||
);
|
||||
hideOverlappedSidebars();
|
||||
highlightReaderToggle(isReaderMode());
|
||||
});
|
||||
|
||||
// grouped tabsets
|
||||
window.addEventListener("pageshow", (_event) => {
|
||||
function getTabSettings() {
|
||||
const data = localStorage.getItem("quarto-persistent-tabsets-data");
|
||||
if (!data) {
|
||||
localStorage.setItem("quarto-persistent-tabsets-data", "{}");
|
||||
return {};
|
||||
}
|
||||
if (data) {
|
||||
return JSON.parse(data);
|
||||
}
|
||||
}
|
||||
|
||||
function setTabSettings(data) {
|
||||
localStorage.setItem(
|
||||
"quarto-persistent-tabsets-data",
|
||||
JSON.stringify(data)
|
||||
);
|
||||
}
|
||||
|
||||
function setTabState(groupName, groupValue) {
|
||||
const data = getTabSettings();
|
||||
data[groupName] = groupValue;
|
||||
setTabSettings(data);
|
||||
}
|
||||
|
||||
function toggleTab(tab, active) {
|
||||
const tabPanelId = tab.getAttribute("aria-controls");
|
||||
const tabPanel = document.getElementById(tabPanelId);
|
||||
if (active) {
|
||||
tab.classList.add("active");
|
||||
tabPanel.classList.add("active");
|
||||
} else {
|
||||
tab.classList.remove("active");
|
||||
tabPanel.classList.remove("active");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAll(selectedGroup, selectorsToSync) {
|
||||
for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) {
|
||||
const active = selectedGroup === thisGroup;
|
||||
for (const tab of tabs) {
|
||||
toggleTab(tab, active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findSelectorsToSyncByLanguage() {
|
||||
const result = {};
|
||||
const tabs = Array.from(
|
||||
document.querySelectorAll(`div[data-group] a[id^='tabset-']`)
|
||||
);
|
||||
for (const item of tabs) {
|
||||
const div = item.parentElement.parentElement.parentElement;
|
||||
const group = div.getAttribute("data-group");
|
||||
if (!result[group]) {
|
||||
result[group] = {};
|
||||
}
|
||||
const selectorsToSync = result[group];
|
||||
const value = item.innerHTML;
|
||||
if (!selectorsToSync[value]) {
|
||||
selectorsToSync[value] = [];
|
||||
}
|
||||
selectorsToSync[value].push(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function setupSelectorSync() {
|
||||
const selectorsToSync = findSelectorsToSyncByLanguage();
|
||||
Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => {
|
||||
Object.entries(tabSetsByValue).forEach(([value, items]) => {
|
||||
items.forEach((item) => {
|
||||
item.addEventListener("click", (_event) => {
|
||||
setTabState(group, value);
|
||||
toggleAll(value, selectorsToSync[group]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return selectorsToSync;
|
||||
}
|
||||
|
||||
const selectorsToSync = setupSelectorSync();
|
||||
for (const [group, selectedName] of Object.entries(getTabSettings())) {
|
||||
const selectors = selectorsToSync[group];
|
||||
// it's possible that stale state gives us empty selections, so we explicitly check here.
|
||||
if (selectors) {
|
||||
toggleAll(selectedName, selectors);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function throttle(func, wait) {
|
||||
let waiting = false;
|
||||
return function () {
|
||||
if (!waiting) {
|
||||
func.apply(this, arguments);
|
||||
waiting = true;
|
||||
setTimeout(function () {
|
||||
waiting = false;
|
||||
}, wait);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function nexttick(func) {
|
||||
return setTimeout(func, 0);
|
||||
}
|
||||
Vendored
-418
@@ -1,418 +0,0 @@
|
||||
(function (root, factory) {
|
||||
if (typeof define === "function" && define.amd) {
|
||||
define([], function () {
|
||||
return factory(root);
|
||||
});
|
||||
} else if (typeof exports === "object") {
|
||||
module.exports = factory(root);
|
||||
} else {
|
||||
root.Tabby = factory(root);
|
||||
}
|
||||
})(
|
||||
typeof global !== "undefined"
|
||||
? global
|
||||
: typeof window !== "undefined"
|
||||
? window
|
||||
: this,
|
||||
function (window) {
|
||||
"use strict";
|
||||
|
||||
//
|
||||
// Variables
|
||||
//
|
||||
|
||||
var defaults = {
|
||||
idPrefix: "tabby-toggle_",
|
||||
default: "[data-tabby-default]",
|
||||
};
|
||||
|
||||
//
|
||||
// Methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Merge two or more objects together.
|
||||
* @param {Object} objects The objects to merge together
|
||||
* @returns {Object} Merged values of defaults and options
|
||||
*/
|
||||
var extend = function () {
|
||||
var merged = {};
|
||||
Array.prototype.forEach.call(arguments, function (obj) {
|
||||
for (var key in obj) {
|
||||
if (!obj.hasOwnProperty(key)) return;
|
||||
merged[key] = obj[key];
|
||||
}
|
||||
});
|
||||
return merged;
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit a custom event
|
||||
* @param {String} type The event type
|
||||
* @param {Node} tab The tab to attach the event to
|
||||
* @param {Node} details Details about the event
|
||||
*/
|
||||
var emitEvent = function (tab, details) {
|
||||
// Create a new event
|
||||
var event;
|
||||
if (typeof window.CustomEvent === "function") {
|
||||
event = new CustomEvent("tabby", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
detail: details,
|
||||
});
|
||||
} else {
|
||||
event = document.createEvent("CustomEvent");
|
||||
event.initCustomEvent("tabby", true, true, details);
|
||||
}
|
||||
|
||||
// Dispatch the event
|
||||
tab.dispatchEvent(event);
|
||||
};
|
||||
|
||||
var focusHandler = function (event) {
|
||||
toggle(event.target);
|
||||
};
|
||||
|
||||
var getKeyboardFocusableElements = function (element) {
|
||||
return [
|
||||
...element.querySelectorAll(
|
||||
'a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])'
|
||||
),
|
||||
].filter(
|
||||
(el) => !el.hasAttribute("disabled") && !el.getAttribute("aria-hidden")
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove roles and attributes from a tab and its content
|
||||
* @param {Node} tab The tab
|
||||
* @param {Node} content The tab content
|
||||
* @param {Object} settings User settings and options
|
||||
*/
|
||||
var destroyTab = function (tab, content, settings) {
|
||||
// Remove the generated ID
|
||||
if (tab.id.slice(0, settings.idPrefix.length) === settings.idPrefix) {
|
||||
tab.id = "";
|
||||
}
|
||||
|
||||
// remove event listener
|
||||
tab.removeEventListener("focus", focusHandler, true);
|
||||
|
||||
// Remove roles
|
||||
tab.removeAttribute("role");
|
||||
tab.removeAttribute("aria-controls");
|
||||
tab.removeAttribute("aria-selected");
|
||||
tab.removeAttribute("tabindex");
|
||||
tab.closest("li").removeAttribute("role");
|
||||
content.removeAttribute("role");
|
||||
content.removeAttribute("aria-labelledby");
|
||||
content.removeAttribute("hidden");
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the required roles and attributes to a tab and its content
|
||||
* @param {Node} tab The tab
|
||||
* @param {Node} content The tab content
|
||||
* @param {Object} settings User settings and options
|
||||
*/
|
||||
var setupTab = function (tab, content, settings) {
|
||||
// Give tab an ID if it doesn't already have one
|
||||
if (!tab.id) {
|
||||
tab.id = settings.idPrefix + content.id;
|
||||
}
|
||||
|
||||
// Add roles
|
||||
tab.setAttribute("role", "tab");
|
||||
tab.setAttribute("aria-controls", content.id);
|
||||
tab.closest("li").setAttribute("role", "presentation");
|
||||
content.setAttribute("role", "tabpanel");
|
||||
content.setAttribute("aria-labelledby", tab.id);
|
||||
|
||||
// Add selected state
|
||||
if (tab.matches(settings.default)) {
|
||||
tab.setAttribute("aria-selected", "true");
|
||||
} else {
|
||||
tab.setAttribute("aria-selected", "false");
|
||||
content.setAttribute("hidden", "hidden");
|
||||
}
|
||||
|
||||
// add focus event listender
|
||||
tab.addEventListener("focus", focusHandler);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide a tab and its content
|
||||
* @param {Node} newTab The new tab that's replacing it
|
||||
*/
|
||||
var hide = function (newTab) {
|
||||
// Variables
|
||||
var tabGroup = newTab.closest('[role="tablist"]');
|
||||
if (!tabGroup) return {};
|
||||
var tab = tabGroup.querySelector('[role="tab"][aria-selected="true"]');
|
||||
if (!tab) return {};
|
||||
var content = document.querySelector(tab.hash);
|
||||
|
||||
// Hide the tab
|
||||
tab.setAttribute("aria-selected", "false");
|
||||
|
||||
// Hide the content
|
||||
if (!content) return { previousTab: tab };
|
||||
content.setAttribute("hidden", "hidden");
|
||||
|
||||
// Return the hidden tab and content
|
||||
return {
|
||||
previousTab: tab,
|
||||
previousContent: content,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Show a tab and its content
|
||||
* @param {Node} tab The tab
|
||||
* @param {Node} content The tab content
|
||||
*/
|
||||
var show = function (tab, content) {
|
||||
tab.setAttribute("aria-selected", "true");
|
||||
content.removeAttribute("hidden");
|
||||
tab.focus();
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle a new tab
|
||||
* @param {Node} tab The tab to show
|
||||
*/
|
||||
var toggle = function (tab) {
|
||||
// Make sure there's a tab to toggle and it's not already active
|
||||
if (!tab || tab.getAttribute("aria-selected") == "true") return;
|
||||
|
||||
// Variables
|
||||
var content = document.querySelector(tab.hash);
|
||||
if (!content) return;
|
||||
|
||||
// Hide active tab and content
|
||||
var details = hide(tab);
|
||||
|
||||
// Show new tab and content
|
||||
show(tab, content);
|
||||
|
||||
// Add event details
|
||||
details.tab = tab;
|
||||
details.content = content;
|
||||
|
||||
// Emit a custom event
|
||||
emitEvent(tab, details);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all of the tabs in a tablist
|
||||
* @param {Node} tab A tab from the list
|
||||
* @return {Object} The tabs and the index of the currently active one
|
||||
*/
|
||||
var getTabsMap = function (tab) {
|
||||
var tabGroup = tab.closest('[role="tablist"]');
|
||||
var tabs = tabGroup ? tabGroup.querySelectorAll('[role="tab"]') : null;
|
||||
if (!tabs) return;
|
||||
return {
|
||||
tabs: tabs,
|
||||
index: Array.prototype.indexOf.call(tabs, tab),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Switch the active tab based on keyboard activity
|
||||
* @param {Node} tab The currently active tab
|
||||
* @param {Key} key The key that was pressed
|
||||
*/
|
||||
var switchTabs = function (tab, key) {
|
||||
// Get a map of tabs
|
||||
var map = getTabsMap(tab);
|
||||
if (!map) return;
|
||||
var length = map.tabs.length - 1;
|
||||
var index;
|
||||
|
||||
// Go to previous tab
|
||||
if (["ArrowUp", "ArrowLeft", "Up", "Left"].indexOf(key) > -1) {
|
||||
index = map.index < 1 ? length : map.index - 1;
|
||||
}
|
||||
|
||||
// Go to next tab
|
||||
else if (["ArrowDown", "ArrowRight", "Down", "Right"].indexOf(key) > -1) {
|
||||
index = map.index === length ? 0 : map.index + 1;
|
||||
}
|
||||
|
||||
// Go to home
|
||||
else if (key === "Home") {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// Go to end
|
||||
else if (key === "End") {
|
||||
index = length;
|
||||
}
|
||||
|
||||
// Toggle the tab
|
||||
toggle(map.tabs[index]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the Constructor object
|
||||
*/
|
||||
var Constructor = function (selector, options) {
|
||||
//
|
||||
// Variables
|
||||
//
|
||||
|
||||
var publicAPIs = {};
|
||||
var settings, tabWrapper;
|
||||
|
||||
//
|
||||
// Methods
|
||||
//
|
||||
|
||||
publicAPIs.destroy = function () {
|
||||
// Get all tabs
|
||||
var tabs = tabWrapper.querySelectorAll("a");
|
||||
|
||||
// Add roles to tabs
|
||||
Array.prototype.forEach.call(tabs, function (tab) {
|
||||
// Get the tab content
|
||||
var content = document.querySelector(tab.hash);
|
||||
if (!content) return;
|
||||
|
||||
// Setup the tab
|
||||
destroyTab(tab, content, settings);
|
||||
});
|
||||
|
||||
// Remove role from wrapper
|
||||
tabWrapper.removeAttribute("role");
|
||||
|
||||
// Remove event listeners
|
||||
document.documentElement.removeEventListener(
|
||||
"click",
|
||||
clickHandler,
|
||||
true
|
||||
);
|
||||
tabWrapper.removeEventListener("keydown", keyHandler, true);
|
||||
|
||||
// Reset variables
|
||||
settings = null;
|
||||
tabWrapper = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup the DOM with the proper attributes
|
||||
*/
|
||||
publicAPIs.setup = function () {
|
||||
// Variables
|
||||
tabWrapper = document.querySelector(selector);
|
||||
if (!tabWrapper) return;
|
||||
var tabs = tabWrapper.querySelectorAll("a");
|
||||
|
||||
// Add role to wrapper
|
||||
tabWrapper.setAttribute("role", "tablist");
|
||||
|
||||
// Add roles to tabs. provide dynanmic tab indexes if we are within reveal
|
||||
var contentTabindexes =
|
||||
window.document.body.classList.contains("reveal-viewport");
|
||||
var nextTabindex = 1;
|
||||
Array.prototype.forEach.call(tabs, function (tab) {
|
||||
if (contentTabindexes) {
|
||||
tab.setAttribute("tabindex", "" + nextTabindex++);
|
||||
} else {
|
||||
tab.setAttribute("tabindex", "0");
|
||||
}
|
||||
|
||||
// Get the tab content
|
||||
var content = document.querySelector(tab.hash);
|
||||
if (!content) return;
|
||||
|
||||
// set tab indexes for content
|
||||
if (contentTabindexes) {
|
||||
getKeyboardFocusableElements(content).forEach(function (el) {
|
||||
el.setAttribute("tabindex", "" + nextTabindex++);
|
||||
});
|
||||
}
|
||||
|
||||
// Setup the tab
|
||||
setupTab(tab, content, settings);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle a tab based on an ID
|
||||
* @param {String|Node} id The tab to toggle
|
||||
*/
|
||||
publicAPIs.toggle = function (id) {
|
||||
// Get the tab
|
||||
var tab = id;
|
||||
if (typeof id === "string") {
|
||||
tab = document.querySelector(
|
||||
selector + ' [role="tab"][href*="' + id + '"]'
|
||||
);
|
||||
}
|
||||
|
||||
// Toggle the tab
|
||||
toggle(tab);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle click events
|
||||
*/
|
||||
var clickHandler = function (event) {
|
||||
// Only run on toggles
|
||||
var tab = event.target.closest(selector + ' [role="tab"]');
|
||||
if (!tab) return;
|
||||
|
||||
// Prevent link behavior
|
||||
event.preventDefault();
|
||||
|
||||
// Toggle the tab
|
||||
toggle(tab);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle keydown events
|
||||
*/
|
||||
var keyHandler = function (event) {
|
||||
// Only run if a tab is in focus
|
||||
var tab = document.activeElement;
|
||||
if (!tab.matches(selector + ' [role="tab"]')) return;
|
||||
|
||||
// Only run for specific keys
|
||||
if (["Home", "End"].indexOf(event.key) < 0) return;
|
||||
|
||||
// Switch tabs
|
||||
switchTabs(tab, event.key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the instance
|
||||
*/
|
||||
var init = function () {
|
||||
// Merge user options with defaults
|
||||
settings = extend(defaults, options || {});
|
||||
|
||||
// Setup the DOM
|
||||
publicAPIs.setup();
|
||||
|
||||
// Add event listeners
|
||||
document.documentElement.addEventListener("click", clickHandler, true);
|
||||
tabWrapper.addEventListener("keydown", keyHandler, true);
|
||||
};
|
||||
|
||||
//
|
||||
// Initialize and return the Public APIs
|
||||
//
|
||||
|
||||
init();
|
||||
return publicAPIs;
|
||||
};
|
||||
|
||||
//
|
||||
// Return the Constructor
|
||||
//
|
||||
|
||||
return Constructor;
|
||||
}
|
||||
);
|
||||
@@ -1 +0,0 @@
|
||||
.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}
|
||||
-2
File diff suppressed because one or more lines are too long
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
/*!
|
||||
* headroom.js v0.12.0 - Give your page some headroom. Hide your header until you need it
|
||||
* Copyright (c) 2020 Nick Williams - http://wicky.nillia.ms/headroom.js
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t=t||self).Headroom=n()}(this,function(){"use strict";function t(){return"undefined"!=typeof window}function d(t){return function(t){return t&&t.document&&function(t){return 9===t.nodeType}(t.document)}(t)?function(t){var n=t.document,o=n.body,s=n.documentElement;return{scrollHeight:function(){return Math.max(o.scrollHeight,s.scrollHeight,o.offsetHeight,s.offsetHeight,o.clientHeight,s.clientHeight)},height:function(){return t.innerHeight||s.clientHeight||o.clientHeight},scrollY:function(){return void 0!==t.pageYOffset?t.pageYOffset:(s||o.parentNode||o).scrollTop}}}(t):function(t){return{scrollHeight:function(){return Math.max(t.scrollHeight,t.offsetHeight,t.clientHeight)},height:function(){return Math.max(t.offsetHeight,t.clientHeight)},scrollY:function(){return t.scrollTop}}}(t)}function n(t,s,e){var n,o=function(){var n=!1;try{var t={get passive(){n=!0}};window.addEventListener("test",t,t),window.removeEventListener("test",t,t)}catch(t){n=!1}return n}(),i=!1,r=d(t),l=r.scrollY(),a={};function c(){var t=Math.round(r.scrollY()),n=r.height(),o=r.scrollHeight();a.scrollY=t,a.lastScrollY=l,a.direction=l<t?"down":"up",a.distance=Math.abs(t-l),a.isOutOfBounds=t<0||o<t+n,a.top=t<=s.offset[a.direction],a.bottom=o<=t+n,a.toleranceExceeded=a.distance>s.tolerance[a.direction],e(a),l=t,i=!1}function h(){i||(i=!0,n=requestAnimationFrame(c))}var u=!!o&&{passive:!0,capture:!1};return t.addEventListener("scroll",h,u),c(),{destroy:function(){cancelAnimationFrame(n),t.removeEventListener("scroll",h,u)}}}function o(t){return t===Object(t)?t:{down:t,up:t}}function s(t,n){n=n||{},Object.assign(this,s.options,n),this.classes=Object.assign({},s.options.classes,n.classes),this.elem=t,this.tolerance=o(this.tolerance),this.offset=o(this.offset),this.initialised=!1,this.frozen=!1}return s.prototype={constructor:s,init:function(){return s.cutsTheMustard&&!this.initialised&&(this.addClass("initial"),this.initialised=!0,setTimeout(function(t){t.scrollTracker=n(t.scroller,{offset:t.offset,tolerance:t.tolerance},t.update.bind(t))},100,this)),this},destroy:function(){this.initialised=!1,Object.keys(this.classes).forEach(this.removeClass,this),this.scrollTracker.destroy()},unpin:function(){!this.hasClass("pinned")&&this.hasClass("unpinned")||(this.addClass("unpinned"),this.removeClass("pinned"),this.onUnpin&&this.onUnpin.call(this))},pin:function(){this.hasClass("unpinned")&&(this.addClass("pinned"),this.removeClass("unpinned"),this.onPin&&this.onPin.call(this))},freeze:function(){this.frozen=!0,this.addClass("frozen")},unfreeze:function(){this.frozen=!1,this.removeClass("frozen")},top:function(){this.hasClass("top")||(this.addClass("top"),this.removeClass("notTop"),this.onTop&&this.onTop.call(this))},notTop:function(){this.hasClass("notTop")||(this.addClass("notTop"),this.removeClass("top"),this.onNotTop&&this.onNotTop.call(this))},bottom:function(){this.hasClass("bottom")||(this.addClass("bottom"),this.removeClass("notBottom"),this.onBottom&&this.onBottom.call(this))},notBottom:function(){this.hasClass("notBottom")||(this.addClass("notBottom"),this.removeClass("bottom"),this.onNotBottom&&this.onNotBottom.call(this))},shouldUnpin:function(t){return"down"===t.direction&&!t.top&&t.toleranceExceeded},shouldPin:function(t){return"up"===t.direction&&t.toleranceExceeded||t.top},addClass:function(t){this.elem.classList.add.apply(this.elem.classList,this.classes[t].split(" "))},removeClass:function(t){this.elem.classList.remove.apply(this.elem.classList,this.classes[t].split(" "))},hasClass:function(t){return this.classes[t].split(" ").every(function(t){return this.classList.contains(t)},this.elem)},update:function(t){t.isOutOfBounds||!0!==this.frozen&&(t.top?this.top():this.notTop(),t.bottom?this.bottom():this.notBottom(),this.shouldUnpin(t)?this.unpin():this.shouldPin(t)&&this.pin())}},s.options={tolerance:{up:0,down:0},offset:0,scroller:t()?window:null,classes:{frozen:"headroom--frozen",pinned:"headroom--pinned",unpinned:"headroom--unpinned",top:"headroom--top",notTop:"headroom--not-top",bottom:"headroom--bottom",notBottom:"headroom--not-bottom",initial:"headroom"}},s.cutsTheMustard=!!(t()&&function(){}.bind&&"classList"in document.documentElement&&Object.assign&&Object.keys&&requestAnimationFrame),s});
|
||||
@@ -1,325 +0,0 @@
|
||||
const headroomChanged = new CustomEvent("quarto-hrChanged", {
|
||||
detail: {},
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
composed: false,
|
||||
});
|
||||
|
||||
const announceDismiss = () => {
|
||||
const annEl = window.document.getElementById("quarto-announcement");
|
||||
if (annEl) {
|
||||
annEl.remove();
|
||||
|
||||
const annId = annEl.getAttribute("data-announcement-id");
|
||||
window.localStorage.setItem(`quarto-announce-${annId}`, "true");
|
||||
}
|
||||
};
|
||||
|
||||
const announceRegister = () => {
|
||||
const annEl = window.document.getElementById("quarto-announcement");
|
||||
if (annEl) {
|
||||
const annId = annEl.getAttribute("data-announcement-id");
|
||||
const isDismissed =
|
||||
window.localStorage.getItem(`quarto-announce-${annId}`) || false;
|
||||
if (isDismissed) {
|
||||
announceDismiss();
|
||||
return;
|
||||
} else {
|
||||
annEl.classList.remove("hidden");
|
||||
}
|
||||
|
||||
const actionEl = annEl.querySelector(".quarto-announcement-action");
|
||||
if (actionEl) {
|
||||
actionEl.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
// Hide the bar immediately
|
||||
announceDismiss();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.document.addEventListener("DOMContentLoaded", function () {
|
||||
let init = false;
|
||||
|
||||
announceRegister();
|
||||
|
||||
// Manage the back to top button, if one is present.
|
||||
let lastScrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||
const scrollDownBuffer = 5;
|
||||
const scrollUpBuffer = 35;
|
||||
const btn = document.getElementById("quarto-back-to-top");
|
||||
const hideBackToTop = () => {
|
||||
btn.style.display = "none";
|
||||
};
|
||||
const showBackToTop = () => {
|
||||
btn.style.display = "inline-block";
|
||||
};
|
||||
if (btn) {
|
||||
window.document.addEventListener(
|
||||
"scroll",
|
||||
function () {
|
||||
const currentScrollTop =
|
||||
window.pageYOffset || document.documentElement.scrollTop;
|
||||
|
||||
// Shows and hides the button 'intelligently' as the user scrolls
|
||||
if (currentScrollTop - scrollDownBuffer > lastScrollTop) {
|
||||
hideBackToTop();
|
||||
lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop;
|
||||
} else if (currentScrollTop < lastScrollTop - scrollUpBuffer) {
|
||||
showBackToTop();
|
||||
lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop;
|
||||
}
|
||||
|
||||
// Show the button at the bottom, hides it at the top
|
||||
if (currentScrollTop <= 0) {
|
||||
hideBackToTop();
|
||||
} else if (
|
||||
window.innerHeight + currentScrollTop >=
|
||||
document.body.offsetHeight
|
||||
) {
|
||||
showBackToTop();
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
function throttle(func, wait) {
|
||||
var timeout;
|
||||
return function () {
|
||||
const context = this;
|
||||
const args = arguments;
|
||||
const later = function () {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
func.apply(context, args);
|
||||
};
|
||||
|
||||
if (!timeout) {
|
||||
timeout = setTimeout(later, wait);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function headerOffset() {
|
||||
// Set an offset if there is are fixed top navbar
|
||||
const headerEl = window.document.querySelector("header.fixed-top");
|
||||
if (headerEl) {
|
||||
return headerEl.clientHeight;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function footerOffset() {
|
||||
const footerEl = window.document.querySelector("footer.footer");
|
||||
if (footerEl) {
|
||||
return footerEl.clientHeight;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function dashboardOffset() {
|
||||
const dashboardNavEl = window.document.getElementById(
|
||||
"quarto-dashboard-header"
|
||||
);
|
||||
if (dashboardNavEl !== null) {
|
||||
return dashboardNavEl.clientHeight;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function updateDocumentOffsetWithoutAnimation() {
|
||||
updateDocumentOffset(false);
|
||||
}
|
||||
|
||||
function updateDocumentOffset(animated) {
|
||||
// set body offset
|
||||
const topOffset = headerOffset();
|
||||
const bodyOffset = topOffset + footerOffset() + dashboardOffset();
|
||||
const bodyEl = window.document.body;
|
||||
bodyEl.setAttribute("data-bs-offset", topOffset);
|
||||
bodyEl.style.paddingTop = topOffset + "px";
|
||||
|
||||
// deal with sidebar offsets
|
||||
const sidebars = window.document.querySelectorAll(
|
||||
".sidebar, .headroom-target"
|
||||
);
|
||||
sidebars.forEach((sidebar) => {
|
||||
if (!animated) {
|
||||
sidebar.classList.add("notransition");
|
||||
// Remove the no transition class after the animation has time to complete
|
||||
setTimeout(function () {
|
||||
sidebar.classList.remove("notransition");
|
||||
}, 201);
|
||||
}
|
||||
|
||||
if (window.Headroom && sidebar.classList.contains("sidebar-unpinned")) {
|
||||
sidebar.style.top = "0";
|
||||
sidebar.style.maxHeight = "100vh";
|
||||
} else {
|
||||
sidebar.style.top = topOffset + "px";
|
||||
sidebar.style.maxHeight = "calc(100vh - " + topOffset + "px)";
|
||||
}
|
||||
});
|
||||
|
||||
// allow space for footer
|
||||
const mainContainer = window.document.querySelector(".quarto-container");
|
||||
if (mainContainer) {
|
||||
mainContainer.style.minHeight = "calc(100vh - " + bodyOffset + "px)";
|
||||
}
|
||||
|
||||
// link offset
|
||||
let linkStyle = window.document.querySelector("#quarto-target-style");
|
||||
if (!linkStyle) {
|
||||
linkStyle = window.document.createElement("style");
|
||||
linkStyle.setAttribute("id", "quarto-target-style");
|
||||
window.document.head.appendChild(linkStyle);
|
||||
}
|
||||
while (linkStyle.firstChild) {
|
||||
linkStyle.removeChild(linkStyle.firstChild);
|
||||
}
|
||||
if (topOffset > 0) {
|
||||
linkStyle.appendChild(
|
||||
window.document.createTextNode(`
|
||||
section:target::before {
|
||||
content: "";
|
||||
display: block;
|
||||
height: ${topOffset}px;
|
||||
margin: -${topOffset}px 0 0;
|
||||
}`)
|
||||
);
|
||||
}
|
||||
if (init) {
|
||||
window.dispatchEvent(headroomChanged);
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
|
||||
// initialize headroom
|
||||
var header = window.document.querySelector("#quarto-header");
|
||||
if (header && window.Headroom) {
|
||||
const headroom = new window.Headroom(header, {
|
||||
tolerance: 5,
|
||||
onPin: function () {
|
||||
const sidebars = window.document.querySelectorAll(
|
||||
".sidebar, .headroom-target"
|
||||
);
|
||||
sidebars.forEach((sidebar) => {
|
||||
sidebar.classList.remove("sidebar-unpinned");
|
||||
});
|
||||
updateDocumentOffset();
|
||||
},
|
||||
onUnpin: function () {
|
||||
const sidebars = window.document.querySelectorAll(
|
||||
".sidebar, .headroom-target"
|
||||
);
|
||||
sidebars.forEach((sidebar) => {
|
||||
sidebar.classList.add("sidebar-unpinned");
|
||||
});
|
||||
updateDocumentOffset();
|
||||
},
|
||||
});
|
||||
headroom.init();
|
||||
|
||||
let frozen = false;
|
||||
window.quartoToggleHeadroom = function () {
|
||||
if (frozen) {
|
||||
headroom.unfreeze();
|
||||
frozen = false;
|
||||
} else {
|
||||
headroom.freeze();
|
||||
frozen = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
window.addEventListener(
|
||||
"hashchange",
|
||||
function (e) {
|
||||
if (
|
||||
getComputedStyle(document.documentElement).scrollBehavior !== "smooth"
|
||||
) {
|
||||
window.scrollTo(0, window.pageYOffset - headerOffset());
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
// Observe size changed for the header
|
||||
const headerEl = window.document.querySelector("header.fixed-top");
|
||||
if (headerEl && window.ResizeObserver) {
|
||||
const observer = new window.ResizeObserver(() => {
|
||||
setTimeout(updateDocumentOffsetWithoutAnimation, 0);
|
||||
});
|
||||
observer.observe(headerEl, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
characterData: true,
|
||||
});
|
||||
} else {
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
throttle(updateDocumentOffsetWithoutAnimation, 50)
|
||||
);
|
||||
}
|
||||
setTimeout(updateDocumentOffsetWithoutAnimation, 250);
|
||||
|
||||
// fixup index.html links if we aren't on the filesystem
|
||||
if (window.location.protocol !== "file:") {
|
||||
const links = window.document.querySelectorAll("a");
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
if (links[i].href) {
|
||||
links[i].dataset.originalHref = links[i].href;
|
||||
links[i].href = links[i].href.replace(/\/index\.html/, "/");
|
||||
}
|
||||
}
|
||||
|
||||
// Fixup any sharing links that require urls
|
||||
// Append url to any sharing urls
|
||||
const sharingLinks = window.document.querySelectorAll(
|
||||
"a.sidebar-tools-main-item, a.quarto-navigation-tool, a.quarto-navbar-tools, a.quarto-navbar-tools-item"
|
||||
);
|
||||
for (let i = 0; i < sharingLinks.length; i++) {
|
||||
const sharingLink = sharingLinks[i];
|
||||
const href = sharingLink.getAttribute("href");
|
||||
if (href) {
|
||||
sharingLink.setAttribute(
|
||||
"href",
|
||||
href.replace("|url|", window.location.href)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll the active navigation item into view, if necessary
|
||||
const navSidebar = window.document.querySelector("nav#quarto-sidebar");
|
||||
if (navSidebar) {
|
||||
// Find the active item
|
||||
const activeItem = navSidebar.querySelector("li.sidebar-item a.active");
|
||||
if (activeItem) {
|
||||
// Wait for the scroll height and height to resolve by observing size changes on the
|
||||
// nav element that is scrollable
|
||||
const resizeObserver = new ResizeObserver((_entries) => {
|
||||
// The bottom of the element
|
||||
const elBottom = activeItem.offsetTop;
|
||||
const viewBottom = navSidebar.scrollTop + navSidebar.clientHeight;
|
||||
|
||||
// The element height and scroll height are the same, then we are still loading
|
||||
if (viewBottom !== navSidebar.scrollHeight) {
|
||||
// Determine if the item isn't visible and scroll to it
|
||||
if (elBottom >= viewBottom) {
|
||||
navSidebar.scrollTop = elBottom;
|
||||
}
|
||||
|
||||
// stop observing now since we've completed the scroll
|
||||
resizeObserver.unobserve(navSidebar);
|
||||
}
|
||||
});
|
||||
resizeObserver.observe(navSidebar);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
Vendored
-9
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Vendored
-30
@@ -1,30 +0,0 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v4.0 | 20180602
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
main, menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, main, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
Vendored
-8
File diff suppressed because one or more lines are too long
-9
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
Vendored
-9
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
||||
SIL Open Font License (OFL)
|
||||
http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL
|
||||
@@ -1,10 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'League Gothic';
|
||||
src: url('./league-gothic.eot');
|
||||
src: url('./league-gothic.eot?#iefix') format('embedded-opentype'),
|
||||
url('./league-gothic.woff') format('woff'),
|
||||
url('./league-gothic.ttf') format('truetype');
|
||||
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,45 +0,0 @@
|
||||
SIL Open Font License
|
||||
|
||||
Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name ‘Source’. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
|
||||
|
||||
—————————————————————————————-
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
—————————————————————————————-
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
“Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
|
||||
|
||||
“Reserved Font Name” refers to any names specified as such after the copyright statement(s).
|
||||
|
||||
“Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s).
|
||||
|
||||
“Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
|
||||
|
||||
“Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -1,39 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
src: url('./source-sans-pro-regular.eot');
|
||||
src: url('./source-sans-pro-regular.eot?#iefix') format('embedded-opentype'),
|
||||
url('./source-sans-pro-regular.woff') format('woff'),
|
||||
url('./source-sans-pro-regular.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
src: url('./source-sans-pro-italic.eot');
|
||||
src: url('./source-sans-pro-italic.eot?#iefix') format('embedded-opentype'),
|
||||
url('./source-sans-pro-italic.woff') format('woff'),
|
||||
url('./source-sans-pro-italic.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
src: url('./source-sans-pro-semibold.eot');
|
||||
src: url('./source-sans-pro-semibold.eot?#iefix') format('embedded-opentype'),
|
||||
url('./source-sans-pro-semibold.woff') format('woff'),
|
||||
url('./source-sans-pro-semibold.ttf') format('truetype');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
src: url('./source-sans-pro-semibolditalic.eot');
|
||||
src: url('./source-sans-pro-semibolditalic.eot?#iefix') format('embedded-opentype'),
|
||||
url('./source-sans-pro-semibolditalic.woff') format('woff'),
|
||||
url('./source-sans-pro-semibolditalic.ttf') format('truetype');
|
||||
font-weight: 600;
|
||||
font-style: italic;
|
||||
}
|
||||
-5
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,71 +0,0 @@
|
||||
/*
|
||||
Monokai style - ported by Luigi Maselli - http://grigio.org
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
background: #272822;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.hljs-tag,
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-literal,
|
||||
.hljs-strong,
|
||||
.hljs-name {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
.hljs-code {
|
||||
color: #66d9ef;
|
||||
}
|
||||
|
||||
.hljs-class .hljs-title {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hljs-attribute,
|
||||
.hljs-symbol,
|
||||
.hljs-regexp,
|
||||
.hljs-link {
|
||||
color: #bf79db;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-bullet,
|
||||
.hljs-subst,
|
||||
.hljs-title,
|
||||
.hljs-section,
|
||||
.hljs-emphasis,
|
||||
.hljs-type,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-addition,
|
||||
.hljs-variable,
|
||||
.hljs-template-tag,
|
||||
.hljs-template-variable {
|
||||
color: #a6e22e;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-quote,
|
||||
.hljs-deletion,
|
||||
.hljs-meta {
|
||||
color: #75715e;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-literal,
|
||||
.hljs-doctag,
|
||||
.hljs-title,
|
||||
.hljs-section,
|
||||
.hljs-type,
|
||||
.hljs-selector-id {
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -1,439 +0,0 @@
|
||||
import hljs from 'highlight.js';
|
||||
|
||||
/* highlightjs-line-numbers.js 2.8.0 | (C) 2018 Yauheni Pakala | MIT License | github.com/wcoder/highlightjs-line-numbers.js */
|
||||
!function(r,o){"use strict";var e,i="hljs-ln",l="hljs-ln-line",h="hljs-ln-code",s="hljs-ln-numbers",c="hljs-ln-n",m="data-line-number",a=/\r\n|\r|\n/g;function u(e){for(var n=e.toString(),t=e.anchorNode;"TD"!==t.nodeName;)t=t.parentNode;for(var r=e.focusNode;"TD"!==r.nodeName;)r=r.parentNode;var o=parseInt(t.dataset.lineNumber),a=parseInt(r.dataset.lineNumber);if(o==a)return n;var i,l=t.textContent,s=r.textContent;for(a<o&&(i=o,o=a,a=i,i=l,l=s,s=i);0!==n.indexOf(l);)l=l.slice(1);for(;-1===n.lastIndexOf(s);)s=s.slice(0,-1);for(var c=l,u=function(e){for(var n=e;"TABLE"!==n.nodeName;)n=n.parentNode;return n}(t),d=o+1;d<a;++d){var f=p('.{0}[{1}="{2}"]',[h,m,d]);c+="\n"+u.querySelector(f).textContent}return c+="\n"+s}function n(e){try{var n=o.querySelectorAll("code.hljs,code.nohighlight");for(var t in n)n.hasOwnProperty(t)&&(n[t].classList.contains("nohljsln")||d(n[t],e))}catch(e){r.console.error("LineNumbers error: ",e)}}function d(e,n){if("object"==typeof e)e.innerHTML=f(e,n)}function f(e,n){var t,r,o=(t=e,{singleLine:function(e){return!!e.singleLine&&e.singleLine}(r=(r=n)||{}),startFrom:function(e,n){var t=1;isFinite(n.startFrom)&&(t=n.startFrom);var r=function(e,n){return e.hasAttribute(n)?e.getAttribute(n):null}(e,"data-ln-start-from");return null!==r&&(t=function(e,n){if(!e)return n;var t=Number(e);return isFinite(t)?t:n}(r,1)),t}(t,r)});return function e(n){var t=n.childNodes;for(var r in t){var o;t.hasOwnProperty(r)&&(o=t[r],0<(o.textContent.trim().match(a)||[]).length&&(0<o.childNodes.length?e(o):v(o.parentNode)))}}(e),function(e,n){var t=g(e);""===t[t.length-1].trim()&&t.pop();if(1<t.length||n.singleLine){for(var r="",o=0,a=t.length;o<a;o++)r+=p('<tr><td class="{0} {1}" {3}="{5}"><div class="{2}" {3}="{5}"></div></td><td class="{0} {4}" {3}="{5}">{6}</td></tr>',[l,s,c,m,h,o+n.startFrom,0<t[o].length?t[o]:" "]);return p('<table class="{0}">{1}</table>',[i,r])}return e}(e.innerHTML,o)}function v(e){var n=e.className;if(/hljs-/.test(n)){for(var t=g(e.innerHTML),r=0,o="";r<t.length;r++){o+=p('<span class="{0}">{1}</span>\n',[n,0<t[r].length?t[r]:" "])}e.innerHTML=o.trim()}}function g(e){return 0===e.length?[]:e.split(a)}function p(e,t){return e.replace(/\{(\d+)\}/g,function(e,n){return void 0!==t[n]?t[n]:e})}hljs?(hljs.initLineNumbersOnLoad=function(e){"interactive"===o.readyState||"complete"===o.readyState?n(e):r.addEventListener("DOMContentLoaded",function(){n(e)})},hljs.lineNumbersBlock=d,hljs.lineNumbersValue=function(e,n){if("string"!=typeof e)return;var t=document.createElement("code");return t.innerHTML=e,f(t,n)},(e=o.createElement("style")).type="text/css",e.innerHTML=p(".{0}{border-collapse:collapse}.{0} td{padding:0}.{1}:before{content:attr({2})}",[i,c,m]),o.getElementsByTagName("head")[0].appendChild(e)):r.console.error("highlight.js not detected!"),document.addEventListener("copy",function(e){var n,t=window.getSelection();!function(e){for(var n=e;n;){if(n.className&&-1!==n.className.indexOf("hljs-ln-code"))return 1;n=n.parentNode}}(t.anchorNode)||(n=-1!==window.navigator.userAgent.indexOf("Edge")?u(t):t.toString(),e.clipboardData.setData("text/plain",n),e.preventDefault())})}(window,document);
|
||||
|
||||
|
||||
/*!
|
||||
* reveal.js plugin that adds syntax highlight support.
|
||||
*/
|
||||
|
||||
const Plugin = {
|
||||
|
||||
id: 'highlight',
|
||||
|
||||
HIGHLIGHT_STEP_DELIMITER: '|',
|
||||
HIGHLIGHT_LINE_DELIMITER: ',',
|
||||
HIGHLIGHT_LINE_RANGE_DELIMITER: '-',
|
||||
|
||||
hljs,
|
||||
|
||||
/**
|
||||
* Highlights code blocks withing the given deck.
|
||||
*
|
||||
* Note that this can be called multiple times if
|
||||
* there are multiple presentations on one page.
|
||||
*
|
||||
* @param {Reveal} reveal the reveal.js instance
|
||||
*/
|
||||
init: function( reveal ) {
|
||||
|
||||
// Read the plugin config options and provide fallbacks
|
||||
let config = reveal.getConfig().highlight || {};
|
||||
|
||||
config.highlightOnLoad = typeof config.highlightOnLoad === 'boolean' ? config.highlightOnLoad : true;
|
||||
config.escapeHTML = typeof config.escapeHTML === 'boolean' ? config.escapeHTML : true;
|
||||
|
||||
Array.from( reveal.getRevealElement().querySelectorAll( 'pre code' ) ).forEach( block => {
|
||||
|
||||
block.parentNode.classList.add('code-wrapper');
|
||||
|
||||
// Code can optionally be wrapped in script template to avoid
|
||||
// HTML being parsed by the browser (i.e. when you need to
|
||||
// include <, > or & in your code).
|
||||
let substitute = block.querySelector( 'script[type="text/template"]' );
|
||||
if( substitute ) {
|
||||
// textContent handles the HTML entity escapes for us
|
||||
block.textContent = substitute.innerHTML;
|
||||
}
|
||||
|
||||
// Trim whitespace if the "data-trim" attribute is present
|
||||
if( block.hasAttribute( 'data-trim' ) && typeof block.innerHTML.trim === 'function' ) {
|
||||
block.innerHTML = betterTrim( block );
|
||||
}
|
||||
|
||||
// Escape HTML tags unless the "data-noescape" attrbute is present
|
||||
if( config.escapeHTML && !block.hasAttribute( 'data-noescape' )) {
|
||||
block.innerHTML = block.innerHTML.replace( /</g,"<").replace(/>/g, '>' );
|
||||
}
|
||||
|
||||
// Re-highlight when focus is lost (for contenteditable code)
|
||||
block.addEventListener( 'focusout', function( event ) {
|
||||
hljs.highlightElement( event.currentTarget );
|
||||
}, false );
|
||||
|
||||
} );
|
||||
|
||||
// Triggers a callback function before we trigger highlighting
|
||||
if( typeof config.beforeHighlight === 'function' ) {
|
||||
config.beforeHighlight( hljs );
|
||||
}
|
||||
|
||||
// Run initial highlighting for all code
|
||||
if( config.highlightOnLoad ) {
|
||||
Array.from( reveal.getRevealElement().querySelectorAll( 'pre code' ) ).forEach( block => {
|
||||
Plugin.highlightBlock( block );
|
||||
} );
|
||||
}
|
||||
|
||||
// If we're printing to PDF, scroll the code highlights of
|
||||
// all blocks in the deck into view at once
|
||||
reveal.on( 'pdf-ready', function() {
|
||||
[].slice.call( reveal.getRevealElement().querySelectorAll( 'pre code[data-line-numbers].current-fragment' ) ).forEach( function( block ) {
|
||||
Plugin.scrollHighlightedLineIntoView( block, {}, true );
|
||||
} );
|
||||
} );
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlights a code block. If the <code> node has the
|
||||
* 'data-line-numbers' attribute we also generate slide
|
||||
* numbers.
|
||||
*
|
||||
* If the block contains multiple line highlight steps,
|
||||
* we clone the block and create a fragment for each step.
|
||||
*/
|
||||
highlightBlock: function( block ) {
|
||||
|
||||
hljs.highlightElement( block );
|
||||
|
||||
// Don't generate line numbers for empty code blocks
|
||||
if( block.innerHTML.trim().length === 0 ) return;
|
||||
|
||||
if( block.hasAttribute( 'data-line-numbers' ) ) {
|
||||
hljs.lineNumbersBlock( block, { singleLine: true } );
|
||||
|
||||
var scrollState = { currentBlock: block };
|
||||
|
||||
// If there is more than one highlight step, generate
|
||||
// fragments
|
||||
var highlightSteps = Plugin.deserializeHighlightSteps( block.getAttribute( 'data-line-numbers' ) );
|
||||
if( highlightSteps.length > 1 ) {
|
||||
|
||||
// If the original code block has a fragment-index,
|
||||
// each clone should follow in an incremental sequence
|
||||
var fragmentIndex = parseInt( block.getAttribute( 'data-fragment-index' ), 10 );
|
||||
|
||||
if( typeof fragmentIndex !== 'number' || isNaN( fragmentIndex ) ) {
|
||||
fragmentIndex = null;
|
||||
}
|
||||
|
||||
// Generate fragments for all steps except the original block
|
||||
highlightSteps.slice(1).forEach( function( highlight ) {
|
||||
|
||||
var fragmentBlock = block.cloneNode( true );
|
||||
fragmentBlock.setAttribute( 'data-line-numbers', Plugin.serializeHighlightSteps( [ highlight ] ) );
|
||||
fragmentBlock.classList.add( 'fragment' );
|
||||
block.parentNode.appendChild( fragmentBlock );
|
||||
Plugin.highlightLines( fragmentBlock );
|
||||
|
||||
if( typeof fragmentIndex === 'number' ) {
|
||||
fragmentBlock.setAttribute( 'data-fragment-index', fragmentIndex );
|
||||
fragmentIndex += 1;
|
||||
}
|
||||
else {
|
||||
fragmentBlock.removeAttribute( 'data-fragment-index' );
|
||||
}
|
||||
|
||||
// Scroll highlights into view as we step through them
|
||||
fragmentBlock.addEventListener( 'visible', Plugin.scrollHighlightedLineIntoView.bind( Plugin, fragmentBlock, scrollState ) );
|
||||
fragmentBlock.addEventListener( 'hidden', Plugin.scrollHighlightedLineIntoView.bind( Plugin, fragmentBlock.previousSibling, scrollState ) );
|
||||
|
||||
} );
|
||||
|
||||
block.removeAttribute( 'data-fragment-index' );
|
||||
block.setAttribute( 'data-line-numbers', Plugin.serializeHighlightSteps( [ highlightSteps[0] ] ) );
|
||||
|
||||
}
|
||||
|
||||
// Scroll the first highlight into view when the slide
|
||||
// becomes visible. Note supported in IE11 since it lacks
|
||||
// support for Element.closest.
|
||||
var slide = typeof block.closest === 'function' ? block.closest( 'section:not(.stack)' ) : null;
|
||||
if( slide ) {
|
||||
var scrollFirstHighlightIntoView = function() {
|
||||
Plugin.scrollHighlightedLineIntoView( block, scrollState, true );
|
||||
slide.removeEventListener( 'visible', scrollFirstHighlightIntoView );
|
||||
}
|
||||
slide.addEventListener( 'visible', scrollFirstHighlightIntoView );
|
||||
}
|
||||
|
||||
Plugin.highlightLines( block );
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Animates scrolling to the first highlighted line
|
||||
* in the given code block.
|
||||
*/
|
||||
scrollHighlightedLineIntoView: function( block, scrollState, skipAnimation ) {
|
||||
|
||||
cancelAnimationFrame( scrollState.animationFrameID );
|
||||
|
||||
// Match the scroll position of the currently visible
|
||||
// code block
|
||||
if( scrollState.currentBlock ) {
|
||||
block.scrollTop = scrollState.currentBlock.scrollTop;
|
||||
}
|
||||
|
||||
// Remember the current code block so that we can match
|
||||
// its scroll position when showing/hiding fragments
|
||||
scrollState.currentBlock = block;
|
||||
|
||||
var highlightBounds = this.getHighlightedLineBounds( block )
|
||||
var viewportHeight = block.offsetHeight;
|
||||
|
||||
// Subtract padding from the viewport height
|
||||
var blockStyles = getComputedStyle( block );
|
||||
viewportHeight -= parseInt( blockStyles.paddingTop ) + parseInt( blockStyles.paddingBottom );
|
||||
|
||||
// Scroll position which centers all highlights
|
||||
var startTop = block.scrollTop;
|
||||
var targetTop = highlightBounds.top + ( Math.min( highlightBounds.bottom - highlightBounds.top, viewportHeight ) - viewportHeight ) / 2;
|
||||
|
||||
// Account for offsets in position applied to the
|
||||
// <table> that holds our lines of code
|
||||
var lineTable = block.querySelector( '.hljs-ln' );
|
||||
if( lineTable ) targetTop += lineTable.offsetTop - parseInt( blockStyles.paddingTop );
|
||||
|
||||
// Make sure the scroll target is within bounds
|
||||
targetTop = Math.max( Math.min( targetTop, block.scrollHeight - viewportHeight ), 0 );
|
||||
|
||||
if( skipAnimation === true || startTop === targetTop ) {
|
||||
block.scrollTop = targetTop;
|
||||
}
|
||||
else {
|
||||
|
||||
// Don't attempt to scroll if there is no overflow
|
||||
if( block.scrollHeight <= viewportHeight ) return;
|
||||
|
||||
var time = 0;
|
||||
var animate = function() {
|
||||
time = Math.min( time + 0.02, 1 );
|
||||
|
||||
// Update our eased scroll position
|
||||
block.scrollTop = startTop + ( targetTop - startTop ) * Plugin.easeInOutQuart( time );
|
||||
|
||||
// Keep animating unless we've reached the end
|
||||
if( time < 1 ) {
|
||||
scrollState.animationFrameID = requestAnimationFrame( animate );
|
||||
}
|
||||
};
|
||||
|
||||
animate();
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* The easing function used when scrolling.
|
||||
*/
|
||||
easeInOutQuart: function( t ) {
|
||||
|
||||
// easeInOutQuart
|
||||
return t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t;
|
||||
|
||||
},
|
||||
|
||||
getHighlightedLineBounds: function( block ) {
|
||||
|
||||
var highlightedLines = block.querySelectorAll( '.highlight-line' );
|
||||
if( highlightedLines.length === 0 ) {
|
||||
return { top: 0, bottom: 0 };
|
||||
}
|
||||
else {
|
||||
var firstHighlight = highlightedLines[0];
|
||||
var lastHighlight = highlightedLines[ highlightedLines.length -1 ];
|
||||
|
||||
return {
|
||||
top: firstHighlight.offsetTop,
|
||||
bottom: lastHighlight.offsetTop + lastHighlight.offsetHeight
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Visually emphasize specific lines within a code block.
|
||||
* This only works on blocks with line numbering turned on.
|
||||
*
|
||||
* @param {HTMLElement} block a <code> block
|
||||
* @param {String} [linesToHighlight] The lines that should be
|
||||
* highlighted in this format:
|
||||
* "1" = highlights line 1
|
||||
* "2,5" = highlights lines 2 & 5
|
||||
* "2,5-7" = highlights lines 2, 5, 6 & 7
|
||||
*/
|
||||
highlightLines: function( block, linesToHighlight ) {
|
||||
|
||||
var highlightSteps = Plugin.deserializeHighlightSteps( linesToHighlight || block.getAttribute( 'data-line-numbers' ) );
|
||||
|
||||
if( highlightSteps.length ) {
|
||||
|
||||
highlightSteps[0].forEach( function( highlight ) {
|
||||
|
||||
var elementsToHighlight = [];
|
||||
|
||||
// Highlight a range
|
||||
if( typeof highlight.end === 'number' ) {
|
||||
elementsToHighlight = [].slice.call( block.querySelectorAll( 'table tr:nth-child(n+'+highlight.start+'):nth-child(-n+'+highlight.end+')' ) );
|
||||
}
|
||||
// Highlight a single line
|
||||
else if( typeof highlight.start === 'number' ) {
|
||||
elementsToHighlight = [].slice.call( block.querySelectorAll( 'table tr:nth-child('+highlight.start+')' ) );
|
||||
}
|
||||
|
||||
if( elementsToHighlight.length ) {
|
||||
elementsToHighlight.forEach( function( lineElement ) {
|
||||
lineElement.classList.add( 'highlight-line' );
|
||||
} );
|
||||
|
||||
block.classList.add( 'has-highlights' );
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Parses and formats a user-defined string of line
|
||||
* numbers to highlight.
|
||||
*
|
||||
* @example
|
||||
* Plugin.deserializeHighlightSteps( '1,2|3,5-10' )
|
||||
* // [
|
||||
* // [ { start: 1 }, { start: 2 } ],
|
||||
* // [ { start: 3 }, { start: 5, end: 10 } ]
|
||||
* // ]
|
||||
*/
|
||||
deserializeHighlightSteps: function( highlightSteps ) {
|
||||
|
||||
// Remove whitespace
|
||||
highlightSteps = highlightSteps.replace( /\s/g, '' );
|
||||
|
||||
// Divide up our line number groups
|
||||
highlightSteps = highlightSteps.split( Plugin.HIGHLIGHT_STEP_DELIMITER );
|
||||
|
||||
return highlightSteps.map( function( highlights ) {
|
||||
|
||||
return highlights.split( Plugin.HIGHLIGHT_LINE_DELIMITER ).map( function( highlight ) {
|
||||
|
||||
// Parse valid line numbers
|
||||
if( /^[\d-]+$/.test( highlight ) ) {
|
||||
|
||||
highlight = highlight.split( Plugin.HIGHLIGHT_LINE_RANGE_DELIMITER );
|
||||
|
||||
var lineStart = parseInt( highlight[0], 10 ),
|
||||
lineEnd = parseInt( highlight[1], 10 );
|
||||
|
||||
if( isNaN( lineEnd ) ) {
|
||||
return {
|
||||
start: lineStart
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
start: lineStart,
|
||||
end: lineEnd
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
// If no line numbers are provided, no code will be highlighted
|
||||
else {
|
||||
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
} );
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Serializes parsed line number data into a string so
|
||||
* that we can store it in the DOM.
|
||||
*/
|
||||
serializeHighlightSteps: function( highlightSteps ) {
|
||||
|
||||
return highlightSteps.map( function( highlights ) {
|
||||
|
||||
return highlights.map( function( highlight ) {
|
||||
|
||||
// Line range
|
||||
if( typeof highlight.end === 'number' ) {
|
||||
return highlight.start + Plugin.HIGHLIGHT_LINE_RANGE_DELIMITER + highlight.end;
|
||||
}
|
||||
// Single line
|
||||
else if( typeof highlight.start === 'number' ) {
|
||||
return highlight.start;
|
||||
}
|
||||
// All lines
|
||||
else {
|
||||
return '';
|
||||
}
|
||||
|
||||
} ).join( Plugin.HIGHLIGHT_LINE_DELIMITER );
|
||||
|
||||
} ).join( Plugin.HIGHLIGHT_STEP_DELIMITER );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Function to perform a better "data-trim" on code snippets
|
||||
// Will slice an indentation amount on each line of the snippet (amount based on the line having the lowest indentation length)
|
||||
function betterTrim(snippetEl) {
|
||||
// Helper functions
|
||||
function trimLeft(val) {
|
||||
// Adapted from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill
|
||||
return val.replace(/^[\s\uFEFF\xA0]+/g, '');
|
||||
}
|
||||
function trimLineBreaks(input) {
|
||||
var lines = input.split('\n');
|
||||
|
||||
// Trim line-breaks from the beginning
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
if (lines[i].trim() === '') {
|
||||
lines.splice(i--, 1);
|
||||
} else break;
|
||||
}
|
||||
|
||||
// Trim line-breaks from the end
|
||||
for (var i = lines.length-1; i >= 0; i--) {
|
||||
if (lines[i].trim() === '') {
|
||||
lines.splice(i, 1);
|
||||
} else break;
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// Main function for betterTrim()
|
||||
return (function(snippetEl) {
|
||||
var content = trimLineBreaks(snippetEl.innerHTML);
|
||||
var lines = content.split('\n');
|
||||
// Calculate the minimum amount to remove on each line start of the snippet (can be 0)
|
||||
var pad = lines.reduce(function(acc, line) {
|
||||
if (line.length > 0 && trimLeft(line).length > 0 && acc > line.length - trimLeft(line).length) {
|
||||
return line.length - trimLeft(line).length;
|
||||
}
|
||||
return acc;
|
||||
}, Number.POSITIVE_INFINITY);
|
||||
// Slice each line with this amount
|
||||
return lines.map(function(line, index) {
|
||||
return line.slice(pad);
|
||||
})
|
||||
.join('\n');
|
||||
})(snippetEl);
|
||||
}
|
||||
|
||||
export default () => Plugin;
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
|
||||
Zenburn style from voldmar.ru (c) Vladimir Epifanov <voldmar@voldmar.ru>
|
||||
based on dark.css by Ivan Sagalaev
|
||||
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
background: #3f3f3f;
|
||||
color: #dcdcdc;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-tag {
|
||||
color: #e3ceab;
|
||||
}
|
||||
|
||||
.hljs-template-tag {
|
||||
color: #dcdcdc;
|
||||
}
|
||||
|
||||
.hljs-number {
|
||||
color: #8cd0d3;
|
||||
}
|
||||
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-attribute {
|
||||
color: #efdcbc;
|
||||
}
|
||||
|
||||
.hljs-literal {
|
||||
color: #efefaf;
|
||||
}
|
||||
|
||||
.hljs-subst {
|
||||
color: #8f8f8f;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-section,
|
||||
.hljs-type {
|
||||
color: #efef8f;
|
||||
}
|
||||
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-link {
|
||||
color: #dca3a3;
|
||||
}
|
||||
|
||||
.hljs-deletion,
|
||||
.hljs-string,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name {
|
||||
color: #cc9393;
|
||||
}
|
||||
|
||||
.hljs-addition,
|
||||
.hljs-comment,
|
||||
.hljs-quote,
|
||||
.hljs-meta {
|
||||
color: #7f9f7f;
|
||||
}
|
||||
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,475 +0,0 @@
|
||||
/*!
|
||||
* The reveal.js markdown plugin. Handles parsing of
|
||||
* markdown inside of presentations as well as loading
|
||||
* of external markdown documents.
|
||||
*/
|
||||
|
||||
import { marked } from 'marked';
|
||||
|
||||
const DEFAULT_SLIDE_SEPARATOR = '\r?\n---\r?\n',
|
||||
DEFAULT_NOTES_SEPARATOR = 'notes?:',
|
||||
DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$',
|
||||
DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
|
||||
|
||||
const SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__';
|
||||
|
||||
const CODE_LINE_NUMBER_REGEX = /\[([\s\d,|-]*)\]/;
|
||||
|
||||
const HTML_ESCAPE_MAP = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
|
||||
const Plugin = () => {
|
||||
|
||||
// The reveal.js instance this plugin is attached to
|
||||
let deck;
|
||||
|
||||
/**
|
||||
* Retrieves the markdown contents of a slide section
|
||||
* element. Normalizes leading tabs/whitespace.
|
||||
*/
|
||||
function getMarkdownFromSlide( section ) {
|
||||
|
||||
// look for a <script> or <textarea data-template> wrapper
|
||||
var template = section.querySelector( '[data-template]' ) || section.querySelector( 'script' );
|
||||
|
||||
// strip leading whitespace so it isn't evaluated as code
|
||||
var text = ( template || section ).textContent;
|
||||
|
||||
// restore script end tags
|
||||
text = text.replace( new RegExp( SCRIPT_END_PLACEHOLDER, 'g' ), '</script>' );
|
||||
|
||||
var leadingWs = text.match( /^\n?(\s*)/ )[1].length,
|
||||
leadingTabs = text.match( /^\n?(\t*)/ )[1].length;
|
||||
|
||||
if( leadingTabs > 0 ) {
|
||||
text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}','g'), '\n' );
|
||||
}
|
||||
else if( leadingWs > 1 ) {
|
||||
text = text.replace( new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n' );
|
||||
}
|
||||
|
||||
return text;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a markdown slide section element, this will
|
||||
* return all arguments that aren't related to markdown
|
||||
* parsing. Used to forward any other user-defined arguments
|
||||
* to the output markdown slide.
|
||||
*/
|
||||
function getForwardedAttributes( section ) {
|
||||
|
||||
var attributes = section.attributes;
|
||||
var result = [];
|
||||
|
||||
for( var i = 0, len = attributes.length; i < len; i++ ) {
|
||||
var name = attributes[i].name,
|
||||
value = attributes[i].value;
|
||||
|
||||
// disregard attributes that are used for markdown loading/parsing
|
||||
if( /data\-(markdown|separator|vertical|notes)/gi.test( name ) ) continue;
|
||||
|
||||
if( value ) {
|
||||
result.push( name + '="' + value + '"' );
|
||||
}
|
||||
else {
|
||||
result.push( name );
|
||||
}
|
||||
}
|
||||
|
||||
return result.join( ' ' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the given options and fills out default
|
||||
* values for what's not defined.
|
||||
*/
|
||||
function getSlidifyOptions( options ) {
|
||||
|
||||
options = options || {};
|
||||
options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR;
|
||||
options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR;
|
||||
options.attributes = options.attributes || '';
|
||||
|
||||
return options;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for constructing a markdown slide.
|
||||
*/
|
||||
function createMarkdownSlide( content, options ) {
|
||||
|
||||
options = getSlidifyOptions( options );
|
||||
|
||||
var notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) );
|
||||
|
||||
if( notesMatch.length === 2 ) {
|
||||
content = notesMatch[0] + '<aside class="notes">' + marked(notesMatch[1].trim()) + '</aside>';
|
||||
}
|
||||
|
||||
// prevent script end tags in the content from interfering
|
||||
// with parsing
|
||||
content = content.replace( /<\/script>/g, SCRIPT_END_PLACEHOLDER );
|
||||
|
||||
return '<script type="text/template">' + content + '</script>';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a data string into multiple slides based
|
||||
* on the passed in separator arguments.
|
||||
*/
|
||||
function slidify( markdown, options ) {
|
||||
|
||||
options = getSlidifyOptions( options );
|
||||
|
||||
var separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ),
|
||||
horizontalSeparatorRegex = new RegExp( options.separator );
|
||||
|
||||
var matches,
|
||||
lastIndex = 0,
|
||||
isHorizontal,
|
||||
wasHorizontal = true,
|
||||
content,
|
||||
sectionStack = [];
|
||||
|
||||
// iterate until all blocks between separators are stacked up
|
||||
while( matches = separatorRegex.exec( markdown ) ) {
|
||||
var notes = null;
|
||||
|
||||
// determine direction (horizontal by default)
|
||||
isHorizontal = horizontalSeparatorRegex.test( matches[0] );
|
||||
|
||||
if( !isHorizontal && wasHorizontal ) {
|
||||
// create vertical stack
|
||||
sectionStack.push( [] );
|
||||
}
|
||||
|
||||
// pluck slide content from markdown input
|
||||
content = markdown.substring( lastIndex, matches.index );
|
||||
|
||||
if( isHorizontal && wasHorizontal ) {
|
||||
// add to horizontal stack
|
||||
sectionStack.push( content );
|
||||
}
|
||||
else {
|
||||
// add to vertical stack
|
||||
sectionStack[sectionStack.length-1].push( content );
|
||||
}
|
||||
|
||||
lastIndex = separatorRegex.lastIndex;
|
||||
wasHorizontal = isHorizontal;
|
||||
}
|
||||
|
||||
// add the remaining slide
|
||||
( wasHorizontal ? sectionStack : sectionStack[sectionStack.length-1] ).push( markdown.substring( lastIndex ) );
|
||||
|
||||
var markdownSections = '';
|
||||
|
||||
// flatten the hierarchical stack, and insert <section data-markdown> tags
|
||||
for( var i = 0, len = sectionStack.length; i < len; i++ ) {
|
||||
// vertical
|
||||
if( sectionStack[i] instanceof Array ) {
|
||||
markdownSections += '<section '+ options.attributes +'>';
|
||||
|
||||
sectionStack[i].forEach( function( child ) {
|
||||
markdownSections += '<section data-markdown>' + createMarkdownSlide( child, options ) + '</section>';
|
||||
} );
|
||||
|
||||
markdownSections += '</section>';
|
||||
}
|
||||
else {
|
||||
markdownSections += '<section '+ options.attributes +' data-markdown>' + createMarkdownSlide( sectionStack[i], options ) + '</section>';
|
||||
}
|
||||
}
|
||||
|
||||
return markdownSections;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses any current data-markdown slides, splits
|
||||
* multi-slide markdown into separate sections and
|
||||
* handles loading of external markdown.
|
||||
*/
|
||||
function processSlides( scope ) {
|
||||
|
||||
return new Promise( function( resolve ) {
|
||||
|
||||
var externalPromises = [];
|
||||
|
||||
[].slice.call( scope.querySelectorAll( 'section[data-markdown]:not([data-markdown-parsed])') ).forEach( function( section, i ) {
|
||||
|
||||
if( section.getAttribute( 'data-markdown' ).length ) {
|
||||
|
||||
externalPromises.push( loadExternalMarkdown( section ).then(
|
||||
|
||||
// Finished loading external file
|
||||
function( xhr, url ) {
|
||||
section.outerHTML = slidify( xhr.responseText, {
|
||||
separator: section.getAttribute( 'data-separator' ),
|
||||
verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
|
||||
notesSeparator: section.getAttribute( 'data-separator-notes' ),
|
||||
attributes: getForwardedAttributes( section )
|
||||
});
|
||||
},
|
||||
|
||||
// Failed to load markdown
|
||||
function( xhr, url ) {
|
||||
section.outerHTML = '<section data-state="alert">' +
|
||||
'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' +
|
||||
'Check your browser\'s JavaScript console for more details.' +
|
||||
'<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>' +
|
||||
'</section>';
|
||||
}
|
||||
|
||||
) );
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
section.outerHTML = slidify( getMarkdownFromSlide( section ), {
|
||||
separator: section.getAttribute( 'data-separator' ),
|
||||
verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
|
||||
notesSeparator: section.getAttribute( 'data-separator-notes' ),
|
||||
attributes: getForwardedAttributes( section )
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Promise.all( externalPromises ).then( resolve );
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
function loadExternalMarkdown( section ) {
|
||||
|
||||
return new Promise( function( resolve, reject ) {
|
||||
|
||||
var xhr = new XMLHttpRequest(),
|
||||
url = section.getAttribute( 'data-markdown' );
|
||||
|
||||
var datacharset = section.getAttribute( 'data-charset' );
|
||||
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
|
||||
if( datacharset != null && datacharset != '' ) {
|
||||
xhr.overrideMimeType( 'text/html; charset=' + datacharset );
|
||||
}
|
||||
|
||||
xhr.onreadystatechange = function( section, xhr ) {
|
||||
if( xhr.readyState === 4 ) {
|
||||
// file protocol yields status code 0 (useful for local debug, mobile applications etc.)
|
||||
if ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 0 ) {
|
||||
|
||||
resolve( xhr, url );
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
reject( xhr, url );
|
||||
|
||||
}
|
||||
}
|
||||
}.bind( this, section, xhr );
|
||||
|
||||
xhr.open( 'GET', url, true );
|
||||
|
||||
try {
|
||||
xhr.send();
|
||||
}
|
||||
catch ( e ) {
|
||||
console.warn( 'Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e );
|
||||
resolve( xhr, url );
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a node value has the attributes pattern.
|
||||
* If yes, extract it and add that value as one or several attributes
|
||||
* to the target element.
|
||||
*
|
||||
* You need Cache Killer on Chrome to see the effect on any FOM transformation
|
||||
* directly on refresh (F5)
|
||||
* http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277
|
||||
*/
|
||||
function addAttributeInElement( node, elementTarget, separator ) {
|
||||
|
||||
var mardownClassesInElementsRegex = new RegExp( separator, 'mg' );
|
||||
var mardownClassRegex = new RegExp( "([^\"= ]+?)=\"([^\"]+?)\"|(data-[^\"= ]+?)(?=[\" ])", 'mg' );
|
||||
var nodeValue = node.nodeValue;
|
||||
var matches,
|
||||
matchesClass;
|
||||
if( matches = mardownClassesInElementsRegex.exec( nodeValue ) ) {
|
||||
|
||||
var classes = matches[1];
|
||||
nodeValue = nodeValue.substring( 0, matches.index ) + nodeValue.substring( mardownClassesInElementsRegex.lastIndex );
|
||||
node.nodeValue = nodeValue;
|
||||
while( matchesClass = mardownClassRegex.exec( classes ) ) {
|
||||
if( matchesClass[2] ) {
|
||||
elementTarget.setAttribute( matchesClass[1], matchesClass[2] );
|
||||
} else {
|
||||
elementTarget.setAttribute( matchesClass[3], "" );
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add attributes to the parent element of a text node,
|
||||
* or the element of an attribute node.
|
||||
*/
|
||||
function addAttributes( section, element, previousElement, separatorElementAttributes, separatorSectionAttributes ) {
|
||||
|
||||
if ( element != null && element.childNodes != undefined && element.childNodes.length > 0 ) {
|
||||
var previousParentElement = element;
|
||||
for( var i = 0; i < element.childNodes.length; i++ ) {
|
||||
var childElement = element.childNodes[i];
|
||||
if ( i > 0 ) {
|
||||
var j = i - 1;
|
||||
while ( j >= 0 ) {
|
||||
var aPreviousChildElement = element.childNodes[j];
|
||||
if ( typeof aPreviousChildElement.setAttribute == 'function' && aPreviousChildElement.tagName != "BR" ) {
|
||||
previousParentElement = aPreviousChildElement;
|
||||
break;
|
||||
}
|
||||
j = j - 1;
|
||||
}
|
||||
}
|
||||
var parentSection = section;
|
||||
if( childElement.nodeName == "section" ) {
|
||||
parentSection = childElement ;
|
||||
previousParentElement = childElement ;
|
||||
}
|
||||
if ( typeof childElement.setAttribute == 'function' || childElement.nodeType == Node.COMMENT_NODE ) {
|
||||
addAttributes( parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( element.nodeType == Node.COMMENT_NODE ) {
|
||||
if ( addAttributeInElement( element, previousElement, separatorElementAttributes ) == false ) {
|
||||
addAttributeInElement( element, section, separatorSectionAttributes );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts any current data-markdown slides in the
|
||||
* DOM to HTML.
|
||||
*/
|
||||
function convertSlides() {
|
||||
|
||||
var sections = deck.getRevealElement().querySelectorAll( '[data-markdown]:not([data-markdown-parsed])');
|
||||
|
||||
[].slice.call( sections ).forEach( function( section ) {
|
||||
|
||||
section.setAttribute( 'data-markdown-parsed', true )
|
||||
|
||||
var notes = section.querySelector( 'aside.notes' );
|
||||
var markdown = getMarkdownFromSlide( section );
|
||||
|
||||
section.innerHTML = marked( markdown );
|
||||
addAttributes( section, section, null, section.getAttribute( 'data-element-attributes' ) ||
|
||||
section.parentNode.getAttribute( 'data-element-attributes' ) ||
|
||||
DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
|
||||
section.getAttribute( 'data-attributes' ) ||
|
||||
section.parentNode.getAttribute( 'data-attributes' ) ||
|
||||
DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
|
||||
|
||||
// If there were notes, we need to re-add them after
|
||||
// having overwritten the section's HTML
|
||||
if( notes ) {
|
||||
section.appendChild( notes );
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
return Promise.resolve();
|
||||
|
||||
}
|
||||
|
||||
function escapeForHTML( input ) {
|
||||
|
||||
return input.replace( /([&<>'"])/g, char => HTML_ESCAPE_MAP[char] );
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
id: 'markdown',
|
||||
|
||||
/**
|
||||
* Starts processing and converting Markdown within the
|
||||
* current reveal.js deck.
|
||||
*/
|
||||
init: function( reveal ) {
|
||||
|
||||
deck = reveal;
|
||||
|
||||
let { renderer, animateLists, ...markedOptions } = deck.getConfig().markdown || {};
|
||||
|
||||
if( !renderer ) {
|
||||
renderer = new marked.Renderer();
|
||||
|
||||
renderer.code = ( code, language ) => {
|
||||
|
||||
// Off by default
|
||||
let lineNumbers = '';
|
||||
|
||||
// Users can opt in to show line numbers and highlight
|
||||
// specific lines.
|
||||
// ```javascript [] show line numbers
|
||||
// ```javascript [1,4-8] highlights lines 1 and 4-8
|
||||
if( CODE_LINE_NUMBER_REGEX.test( language ) ) {
|
||||
lineNumbers = language.match( CODE_LINE_NUMBER_REGEX )[1].trim();
|
||||
lineNumbers = `data-line-numbers="${lineNumbers}"`;
|
||||
language = language.replace( CODE_LINE_NUMBER_REGEX, '' ).trim();
|
||||
}
|
||||
|
||||
// Escape before this gets injected into the DOM to
|
||||
// avoid having the HTML parser alter our code before
|
||||
// highlight.js is able to read it
|
||||
code = escapeForHTML( code );
|
||||
|
||||
return `<pre><code ${lineNumbers} class="${language}">${code}</code></pre>`;
|
||||
};
|
||||
}
|
||||
|
||||
if( animateLists === true ) {
|
||||
renderer.listitem = text => `<li class="fragment">${text}</li>`;
|
||||
}
|
||||
|
||||
marked.setOptions( {
|
||||
renderer,
|
||||
...markedOptions
|
||||
} );
|
||||
|
||||
return processSlides( deck.getRevealElement() ).then( convertSlides );
|
||||
|
||||
},
|
||||
|
||||
// TODO: Do these belong in the API?
|
||||
processSlides: processSlides,
|
||||
convertSlides: convertSlides,
|
||||
slidify: slidify,
|
||||
marked: marked
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export default Plugin;
|
||||
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* A plugin which enables rendering of math equations inside
|
||||
* of reveal.js slides. Essentially a thin wrapper for KaTeX.
|
||||
*
|
||||
* @author Hakim El Hattab
|
||||
* @author Gerhard Burger
|
||||
*/
|
||||
export const KaTeX = () => {
|
||||
let deck;
|
||||
|
||||
let defaultOptions = {
|
||||
version: 'latest',
|
||||
delimiters: [
|
||||
{left: '$$', right: '$$', display: true}, // Note: $$ has to come before $
|
||||
{left: '$', right: '$', display: false},
|
||||
{left: '\\(', right: '\\)', display: false},
|
||||
{left: '\\[', right: '\\]', display: true}
|
||||
],
|
||||
ignoredTags: ['script', 'noscript', 'style', 'textarea', 'pre']
|
||||
}
|
||||
|
||||
const loadCss = src => {
|
||||
let link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = src;
|
||||
document.head.appendChild(link);
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads a JavaScript file and returns a Promise for when it is loaded
|
||||
* Credits: https://aaronsmith.online/easily-load-an-external-script-using-javascript/
|
||||
*/
|
||||
const loadScript = src => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script')
|
||||
script.type = 'text/javascript'
|
||||
script.onload = resolve
|
||||
script.onerror = reject
|
||||
script.src = src
|
||||
document.head.append(script)
|
||||
})
|
||||
};
|
||||
|
||||
async function loadScripts(urls) {
|
||||
for(const url of urls) {
|
||||
await loadScript(url);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: 'katex',
|
||||
|
||||
init: function (reveal) {
|
||||
|
||||
deck = reveal;
|
||||
|
||||
let revealOptions = deck.getConfig().katex || {};
|
||||
|
||||
let options = {...defaultOptions, ...revealOptions};
|
||||
const {local, version, extensions, ...katexOptions} = options;
|
||||
|
||||
let baseUrl = options.local || 'https://cdn.jsdelivr.net/npm/katex';
|
||||
let versionString = options.local ? '' : '@' + options.version;
|
||||
|
||||
let cssUrl = baseUrl + versionString + '/dist/katex.min.css';
|
||||
let katexUrl = baseUrl + versionString + '/dist/katex.min.js';
|
||||
let mhchemUrl = baseUrl + versionString + '/dist/contrib/mhchem.min.js'
|
||||
let karUrl = baseUrl + versionString + '/dist/contrib/auto-render.min.js';
|
||||
|
||||
let katexScripts = [katexUrl];
|
||||
if(options.extensions && options.extensions.includes("mhchem")) {
|
||||
katexScripts.push(mhchemUrl);
|
||||
}
|
||||
katexScripts.push(karUrl);
|
||||
|
||||
const renderMath = () => {
|
||||
renderMathInElement(reveal.getSlidesElement(), katexOptions);
|
||||
deck.layout();
|
||||
}
|
||||
|
||||
loadCss(cssUrl);
|
||||
|
||||
// For some reason dynamically loading with defer attribute doesn't result in the expected behavior, the below code does
|
||||
loadScripts(katexScripts).then(() => {
|
||||
if( deck.isReady() ) {
|
||||
renderMath();
|
||||
}
|
||||
else {
|
||||
deck.on( 'ready', renderMath.bind( this ) );
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,89 +0,0 @@
|
||||
/**
|
||||
* A plugin which enables rendering of math equations inside
|
||||
* of reveal.js slides. Essentially a thin wrapper for MathJax.
|
||||
*
|
||||
* @author Hakim El Hattab
|
||||
*/
|
||||
export const MathJax2 = () => {
|
||||
|
||||
// The reveal.js instance this plugin is attached to
|
||||
let deck;
|
||||
|
||||
let defaultOptions = {
|
||||
messageStyle: 'none',
|
||||
tex2jax: {
|
||||
inlineMath: [ [ '$', '$' ], [ '\\(', '\\)' ] ],
|
||||
skipTags: [ 'script', 'noscript', 'style', 'textarea', 'pre' ]
|
||||
},
|
||||
skipStartupTypeset: true
|
||||
};
|
||||
|
||||
function loadScript( url, callback ) {
|
||||
|
||||
let head = document.querySelector( 'head' );
|
||||
let script = document.createElement( 'script' );
|
||||
script.type = 'text/javascript';
|
||||
script.src = url;
|
||||
|
||||
// Wrapper for callback to make sure it only fires once
|
||||
let finish = () => {
|
||||
if( typeof callback === 'function' ) {
|
||||
callback.call();
|
||||
callback = null;
|
||||
}
|
||||
}
|
||||
|
||||
script.onload = finish;
|
||||
|
||||
// IE
|
||||
script.onreadystatechange = () => {
|
||||
if ( this.readyState === 'loaded' ) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
// Normal browsers
|
||||
head.appendChild( script );
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
id: 'mathjax2',
|
||||
|
||||
init: function( reveal ) {
|
||||
|
||||
deck = reveal;
|
||||
|
||||
let revealOptions = deck.getConfig().mathjax2 || deck.getConfig().math || {};
|
||||
|
||||
let options = { ...defaultOptions, ...revealOptions };
|
||||
let mathjax = options.mathjax || 'https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js';
|
||||
let config = options.config || 'TeX-AMS_HTML-full';
|
||||
let url = mathjax + '?config=' + config;
|
||||
|
||||
options.tex2jax = { ...defaultOptions.tex2jax, ...revealOptions.tex2jax };
|
||||
|
||||
options.mathjax = options.config = null;
|
||||
|
||||
loadScript( url, function() {
|
||||
|
||||
MathJax.Hub.Config( options );
|
||||
|
||||
// Typeset followed by an immediate reveal.js layout since
|
||||
// the typesetting process could affect slide height
|
||||
MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, deck.getRevealElement() ] );
|
||||
MathJax.Hub.Queue( deck.layout );
|
||||
|
||||
// Reprocess equations in slides when they turn visible
|
||||
deck.on( 'slidechanged', function( event ) {
|
||||
|
||||
MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, event.currentSlide ] );
|
||||
|
||||
} );
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,77 +0,0 @@
|
||||
/**
|
||||
* A plugin which enables rendering of math equations inside
|
||||
* of reveal.js slides. Essentially a thin wrapper for MathJax 3
|
||||
*
|
||||
* @author Hakim El Hattab
|
||||
* @author Gerhard Burger
|
||||
*/
|
||||
export const MathJax3 = () => {
|
||||
|
||||
// The reveal.js instance this plugin is attached to
|
||||
let deck;
|
||||
|
||||
let defaultOptions = {
|
||||
tex: {
|
||||
inlineMath: [ [ '$', '$' ], [ '\\(', '\\)' ] ]
|
||||
},
|
||||
options: {
|
||||
skipHtmlTags: [ 'script', 'noscript', 'style', 'textarea', 'pre' ]
|
||||
},
|
||||
startup: {
|
||||
ready: () => {
|
||||
MathJax.startup.defaultReady();
|
||||
MathJax.startup.promise.then(() => {
|
||||
Reveal.layout();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function loadScript( url, callback ) {
|
||||
|
||||
let script = document.createElement( 'script' );
|
||||
script.type = "text/javascript"
|
||||
script.id = "MathJax-script"
|
||||
script.src = url;
|
||||
script.async = true
|
||||
|
||||
// Wrapper for callback to make sure it only fires once
|
||||
script.onload = () => {
|
||||
if (typeof callback === 'function') {
|
||||
callback.call();
|
||||
callback = null;
|
||||
}
|
||||
};
|
||||
|
||||
document.head.appendChild( script );
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
id: 'mathjax3',
|
||||
init: function(reveal) {
|
||||
|
||||
deck = reveal;
|
||||
|
||||
let revealOptions = deck.getConfig().mathjax3 || {};
|
||||
let options = {...defaultOptions, ...revealOptions};
|
||||
options.tex = {...defaultOptions.tex, ...revealOptions.tex}
|
||||
options.options = {...defaultOptions.options, ...revealOptions.options}
|
||||
options.startup = {...defaultOptions.startup, ...revealOptions.startup}
|
||||
|
||||
let url = options.mathjax || 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js';
|
||||
options.mathjax = null;
|
||||
|
||||
window.MathJax = options;
|
||||
|
||||
loadScript( url, function() {
|
||||
// Reprocess equations in slides when they turn visible
|
||||
Reveal.addEventListener( 'slidechanged', function( event ) {
|
||||
MathJax.typeset();
|
||||
} );
|
||||
} );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
import {KaTeX} from "./katex";
|
||||
import {MathJax2} from "./mathjax2";
|
||||
import {MathJax3} from "./mathjax3";
|
||||
|
||||
const defaultTypesetter = MathJax2;
|
||||
|
||||
/*!
|
||||
* This plugin is a wrapper for the MathJax2,
|
||||
* MathJax3 and KaTeX typesetter plugins.
|
||||
*/
|
||||
export default Plugin = Object.assign( defaultTypesetter(), {
|
||||
KaTeX,
|
||||
MathJax2,
|
||||
MathJax3
|
||||
} );
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,236 +0,0 @@
|
||||
import speakerViewHTML from './speaker-view.html';
|
||||
|
||||
import { marked } from 'marked';
|
||||
|
||||
/**
|
||||
* Handles opening of and synchronization with the reveal.js
|
||||
* notes window.
|
||||
*
|
||||
* Handshake process:
|
||||
* 1. This window posts 'connect' to notes window
|
||||
* - Includes URL of presentation to show
|
||||
* 2. Notes window responds with 'connected' when it is available
|
||||
* 3. This window proceeds to send the current presentation state
|
||||
* to the notes window
|
||||
*/
|
||||
const Plugin = () => {
|
||||
|
||||
let connectInterval;
|
||||
let speakerWindow = null;
|
||||
let deck;
|
||||
|
||||
/**
|
||||
* Opens a new speaker view window.
|
||||
*/
|
||||
function openSpeakerWindow() {
|
||||
|
||||
// If a window is already open, focus it
|
||||
if( speakerWindow && !speakerWindow.closed ) {
|
||||
speakerWindow.focus();
|
||||
}
|
||||
else {
|
||||
speakerWindow = window.open( 'about:blank', 'reveal.js - Notes', 'width=1100,height=700' );
|
||||
speakerWindow.marked = marked;
|
||||
speakerWindow.document.write( speakerViewHTML );
|
||||
|
||||
if( !speakerWindow ) {
|
||||
alert( 'Speaker view popup failed to open. Please make sure popups are allowed and reopen the speaker view.' );
|
||||
return;
|
||||
}
|
||||
|
||||
connect();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect with an existing speaker view window.
|
||||
*/
|
||||
function reconnectSpeakerWindow( reconnectWindow ) {
|
||||
|
||||
if( speakerWindow && !speakerWindow.closed ) {
|
||||
speakerWindow.focus();
|
||||
}
|
||||
else {
|
||||
speakerWindow = reconnectWindow;
|
||||
window.addEventListener( 'message', onPostMessage );
|
||||
onConnected();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the notes window through a postmessage handshake.
|
||||
* Using postmessage enables us to work in situations where the
|
||||
* origins differ, such as a presentation being opened from the
|
||||
* file system.
|
||||
*/
|
||||
function connect() {
|
||||
|
||||
const presentationURL = deck.getConfig().url;
|
||||
|
||||
const url = typeof presentationURL === 'string' ? presentationURL :
|
||||
window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search;
|
||||
|
||||
// Keep trying to connect until we get a 'connected' message back
|
||||
connectInterval = setInterval( function() {
|
||||
speakerWindow.postMessage( JSON.stringify( {
|
||||
namespace: 'reveal-notes',
|
||||
type: 'connect',
|
||||
state: deck.getState(),
|
||||
url
|
||||
} ), '*' );
|
||||
}, 500 );
|
||||
|
||||
window.addEventListener( 'message', onPostMessage );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the specified Reveal.js method with the provided argument
|
||||
* and then pushes the result to the notes frame.
|
||||
*/
|
||||
function callRevealApi( methodName, methodArguments, callId ) {
|
||||
|
||||
let result = deck[methodName].apply( deck, methodArguments );
|
||||
speakerWindow.postMessage( JSON.stringify( {
|
||||
namespace: 'reveal-notes',
|
||||
type: 'return',
|
||||
result,
|
||||
callId
|
||||
} ), '*' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts the current slide data to the notes window.
|
||||
*/
|
||||
function post( event ) {
|
||||
|
||||
let slideElement = deck.getCurrentSlide(),
|
||||
notesElement = slideElement.querySelector( 'aside.notes' ),
|
||||
fragmentElement = slideElement.querySelector( '.current-fragment' );
|
||||
|
||||
let messageData = {
|
||||
namespace: 'reveal-notes',
|
||||
type: 'state',
|
||||
notes: '',
|
||||
markdown: false,
|
||||
whitespace: 'normal',
|
||||
state: deck.getState()
|
||||
};
|
||||
|
||||
// Look for notes defined in a slide attribute
|
||||
if( slideElement.hasAttribute( 'data-notes' ) ) {
|
||||
messageData.notes = slideElement.getAttribute( 'data-notes' );
|
||||
messageData.whitespace = 'pre-wrap';
|
||||
}
|
||||
|
||||
// Look for notes defined in a fragment
|
||||
if( fragmentElement ) {
|
||||
let fragmentNotes = fragmentElement.querySelector( 'aside.notes' );
|
||||
if( fragmentNotes ) {
|
||||
notesElement = fragmentNotes;
|
||||
}
|
||||
else if( fragmentElement.hasAttribute( 'data-notes' ) ) {
|
||||
messageData.notes = fragmentElement.getAttribute( 'data-notes' );
|
||||
messageData.whitespace = 'pre-wrap';
|
||||
|
||||
// In case there are slide notes
|
||||
notesElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for notes defined in an aside element
|
||||
if( notesElement ) {
|
||||
messageData.notes = notesElement.innerHTML;
|
||||
messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string';
|
||||
}
|
||||
|
||||
speakerWindow.postMessage( JSON.stringify( messageData ), '*' );
|
||||
|
||||
}
|
||||
|
||||
function onPostMessage( event ) {
|
||||
|
||||
let data = JSON.parse( event.data );
|
||||
if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) {
|
||||
clearInterval( connectInterval );
|
||||
onConnected();
|
||||
}
|
||||
else if( data && data.namespace === 'reveal-notes' && data.type === 'call' ) {
|
||||
callRevealApi( data.methodName, data.arguments, data.callId );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once we have established a connection to the notes
|
||||
* window.
|
||||
*/
|
||||
function onConnected() {
|
||||
|
||||
// Monitor events that trigger a change in state
|
||||
deck.on( 'slidechanged', post );
|
||||
deck.on( 'fragmentshown', post );
|
||||
deck.on( 'fragmenthidden', post );
|
||||
deck.on( 'overviewhidden', post );
|
||||
deck.on( 'overviewshown', post );
|
||||
deck.on( 'paused', post );
|
||||
deck.on( 'resumed', post );
|
||||
|
||||
// Post the initial state
|
||||
post();
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
id: 'notes',
|
||||
|
||||
init: function( reveal ) {
|
||||
|
||||
deck = reveal;
|
||||
|
||||
if( !/receiver/i.test( window.location.search ) ) {
|
||||
|
||||
// If the there's a 'notes' query set, open directly
|
||||
if( window.location.search.match( /(\?|\&)notes/gi ) !== null ) {
|
||||
openSpeakerWindow();
|
||||
}
|
||||
else {
|
||||
// Keep listening for speaker view hearbeats. If we receive a
|
||||
// heartbeat from an orphaned window, reconnect it. This ensures
|
||||
// that we remain connected to the notes even if the presentation
|
||||
// is reloaded.
|
||||
window.addEventListener( 'message', event => {
|
||||
|
||||
if( !speakerWindow && typeof event.data === 'string' ) {
|
||||
let data;
|
||||
|
||||
try {
|
||||
data = JSON.parse( event.data );
|
||||
}
|
||||
catch( error ) {}
|
||||
|
||||
if( data && data.namespace === 'reveal-notes' && data.type === 'heartbeat' ) {
|
||||
reconnectSpeakerWindow( event.source );
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Open the notes when the 's' key is hit
|
||||
deck.addKeyBinding({keyCode: 83, key: 'S', description: 'Speaker notes view'}, function() {
|
||||
openSpeakerWindow();
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
open: openSpeakerWindow
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
export default Plugin;
|
||||
@@ -1,884 +0,0 @@
|
||||
<!--
|
||||
NOTE: You need to build the notes plugin after making changes to this file.
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>reveal.js - Speaker View</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: Helvetica;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#current-slide,
|
||||
#upcoming-slide,
|
||||
#speaker-controls {
|
||||
padding: 6px;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
}
|
||||
|
||||
#current-slide iframe,
|
||||
#upcoming-slide iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
#current-slide .label,
|
||||
#upcoming-slide .label {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#connection-status {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 20;
|
||||
padding: 30% 20% 20% 20%;
|
||||
font-size: 18px;
|
||||
color: #222;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.overlay-element {
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
padding: 0 10px;
|
||||
text-shadow: none;
|
||||
background: rgba( 220, 220, 220, 0.8 );
|
||||
color: #222;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.overlay-element.interactive:hover {
|
||||
background: rgba( 220, 220, 220, 1 );
|
||||
}
|
||||
|
||||
#current-slide {
|
||||
position: absolute;
|
||||
width: 60%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
#upcoming-slide {
|
||||
position: absolute;
|
||||
width: 40%;
|
||||
height: 40%;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
/* Speaker controls */
|
||||
#speaker-controls {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
right: 0;
|
||||
width: 40%;
|
||||
height: 60%;
|
||||
overflow: auto;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.speaker-controls-time.hidden,
|
||||
.speaker-controls-notes.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.speaker-controls-time .label,
|
||||
.speaker-controls-pace .label,
|
||||
.speaker-controls-notes .label {
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
font-size: 0.66em;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.speaker-controls-time, .speaker-controls-pace {
|
||||
border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
|
||||
margin-bottom: 10px;
|
||||
padding: 10px 16px;
|
||||
padding-bottom: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.speaker-controls-time .reset-button {
|
||||
opacity: 0;
|
||||
float: right;
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
}
|
||||
.speaker-controls-time:hover .reset-button {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.speaker-controls-time .timer,
|
||||
.speaker-controls-time .clock {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.speaker-controls-time .timer,
|
||||
.speaker-controls-time .clock,
|
||||
.speaker-controls-time .pacing .hours-value,
|
||||
.speaker-controls-time .pacing .minutes-value,
|
||||
.speaker-controls-time .pacing .seconds-value {
|
||||
font-size: 1.9em;
|
||||
}
|
||||
|
||||
.speaker-controls-time .timer {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.speaker-controls-time .clock {
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.speaker-controls-time span.mute {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.speaker-controls-time .pacing-title {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.speaker-controls-time .pacing.ahead {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.speaker-controls-time .pacing.on-track {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.speaker-controls-time .pacing.behind {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.speaker-controls-notes {
|
||||
padding: 10px 16px;
|
||||
}
|
||||
|
||||
.speaker-controls-notes .value {
|
||||
margin-top: 5px;
|
||||
line-height: 1.4;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
/* Layout selector */
|
||||
#speaker-layout {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
color: #222;
|
||||
z-index: 10;
|
||||
}
|
||||
#speaker-layout select {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border: 0;
|
||||
box-shadow: 0;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
|
||||
font-size: 1em;
|
||||
background-color: transparent;
|
||||
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
#speaker-layout select:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* Speaker layout: Wide */
|
||||
body[data-speaker-layout="wide"] #current-slide,
|
||||
body[data-speaker-layout="wide"] #upcoming-slide {
|
||||
width: 50%;
|
||||
height: 45%;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
body[data-speaker-layout="wide"] #current-slide {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
body[data-speaker-layout="wide"] #upcoming-slide {
|
||||
top: 0;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
body[data-speaker-layout="wide"] #speaker-controls {
|
||||
top: 45%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
/* Speaker layout: Tall */
|
||||
body[data-speaker-layout="tall"] #current-slide,
|
||||
body[data-speaker-layout="tall"] #upcoming-slide {
|
||||
width: 45%;
|
||||
height: 50%;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
body[data-speaker-layout="tall"] #current-slide {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
body[data-speaker-layout="tall"] #upcoming-slide {
|
||||
top: 50%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
body[data-speaker-layout="tall"] #speaker-controls {
|
||||
padding-top: 40px;
|
||||
top: 0;
|
||||
left: 45%;
|
||||
width: 55%;
|
||||
height: 100%;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
/* Speaker layout: Notes only */
|
||||
body[data-speaker-layout="notes-only"] #current-slide,
|
||||
body[data-speaker-layout="notes-only"] #upcoming-slide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body[data-speaker-layout="notes-only"] #speaker-controls {
|
||||
padding-top: 40px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1080px) {
|
||||
body[data-speaker-layout="default"] #speaker-controls {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
body[data-speaker-layout="default"] #speaker-controls {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
body[data-speaker-layout="default"] #speaker-controls {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="connection-status">Loading speaker view...</div>
|
||||
|
||||
<div id="current-slide"></div>
|
||||
<div id="upcoming-slide"><span class="overlay-element label">Upcoming</span></div>
|
||||
<div id="speaker-controls">
|
||||
<div class="speaker-controls-time">
|
||||
<h4 class="label">Time <span class="reset-button">Click to Reset</span></h4>
|
||||
<div class="clock">
|
||||
<span class="clock-value">0:00 AM</span>
|
||||
</div>
|
||||
<div class="timer">
|
||||
<span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
|
||||
<h4 class="label pacing-title" style="display: none">Pacing – Time to finish current slide</h4>
|
||||
<div class="pacing" style="display: none">
|
||||
<span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="speaker-controls-notes hidden">
|
||||
<h4 class="label">Notes</h4>
|
||||
<div class="value"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="speaker-layout" class="overlay-element interactive">
|
||||
<span class="speaker-layout-label"></span>
|
||||
<select class="speaker-layout-dropdown"></select>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
(function() {
|
||||
|
||||
var notes,
|
||||
notesValue,
|
||||
currentState,
|
||||
currentSlide,
|
||||
upcomingSlide,
|
||||
layoutLabel,
|
||||
layoutDropdown,
|
||||
pendingCalls = {},
|
||||
lastRevealApiCallId = 0,
|
||||
connected = false,
|
||||
whitelistedWindows = [window.opener];
|
||||
|
||||
var SPEAKER_LAYOUTS = {
|
||||
'default': 'Default',
|
||||
'wide': 'Wide',
|
||||
'tall': 'Tall',
|
||||
'notes-only': 'Notes only'
|
||||
};
|
||||
|
||||
setupLayout();
|
||||
|
||||
var connectionStatus = document.querySelector( '#connection-status' );
|
||||
var connectionTimeout = setTimeout( function() {
|
||||
connectionStatus.innerHTML = 'Error connecting to main window.<br>Please try closing and reopening the speaker view.';
|
||||
}, 5000 );
|
||||
;
|
||||
window.addEventListener( 'message', function( event ) {
|
||||
|
||||
// Validate the origin of this message to prevent XSS
|
||||
if( window.location.origin !== event.origin && whitelistedWindows.indexOf( event.source ) === -1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout( connectionTimeout );
|
||||
connectionStatus.style.display = 'none';
|
||||
|
||||
var data = JSON.parse( event.data );
|
||||
|
||||
// The overview mode is only useful to the reveal.js instance
|
||||
// where navigation occurs so we don't sync it
|
||||
if( data.state ) delete data.state.overview;
|
||||
|
||||
// Messages sent by the notes plugin inside of the main window
|
||||
if( data && data.namespace === 'reveal-notes' ) {
|
||||
if( data.type === 'connect' ) {
|
||||
handleConnectMessage( data );
|
||||
}
|
||||
else if( data.type === 'state' ) {
|
||||
handleStateMessage( data );
|
||||
}
|
||||
else if( data.type === 'return' ) {
|
||||
pendingCalls[data.callId](data.result);
|
||||
delete pendingCalls[data.callId];
|
||||
}
|
||||
}
|
||||
// Messages sent by the reveal.js inside of the current slide preview
|
||||
else if( data && data.namespace === 'reveal' ) {
|
||||
if( /ready/.test( data.eventName ) ) {
|
||||
// Send a message back to notify that the handshake is complete
|
||||
window.opener.postMessage( JSON.stringify({ namespace: 'reveal-notes', type: 'connected'} ), '*' );
|
||||
}
|
||||
else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) {
|
||||
|
||||
dispatchStateToMainWindow( data.state );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
/**
|
||||
* Updates the presentation in the main window to match the state
|
||||
* of the presentation in the notes window.
|
||||
*/
|
||||
const dispatchStateToMainWindow = debounce(( state ) => {
|
||||
window.opener.postMessage( JSON.stringify({ method: 'setState', args: [ state ]} ), '*' );
|
||||
}, 500);
|
||||
|
||||
/**
|
||||
* Asynchronously calls the Reveal.js API of the main frame.
|
||||
*/
|
||||
function callRevealApi( methodName, methodArguments, callback ) {
|
||||
|
||||
var callId = ++lastRevealApiCallId;
|
||||
pendingCalls[callId] = callback;
|
||||
window.opener.postMessage( JSON.stringify( {
|
||||
namespace: 'reveal-notes',
|
||||
type: 'call',
|
||||
callId: callId,
|
||||
methodName: methodName,
|
||||
arguments: methodArguments
|
||||
} ), '*' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the main window is trying to establish a
|
||||
* connection.
|
||||
*/
|
||||
function handleConnectMessage( data ) {
|
||||
|
||||
if( connected === false ) {
|
||||
connected = true;
|
||||
|
||||
setupIframes( data );
|
||||
setupKeyboard();
|
||||
setupNotes();
|
||||
setupTimer();
|
||||
setupHeartbeat();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the main window sends an updated state.
|
||||
*/
|
||||
function handleStateMessage( data ) {
|
||||
|
||||
// Store the most recently set state to avoid circular loops
|
||||
// applying the same state
|
||||
currentState = JSON.stringify( data.state );
|
||||
|
||||
// No need for updating the notes in case of fragment changes
|
||||
if ( data.notes ) {
|
||||
notes.classList.remove( 'hidden' );
|
||||
notesValue.style.whiteSpace = data.whitespace;
|
||||
if( data.markdown ) {
|
||||
notesValue.innerHTML = marked( data.notes );
|
||||
}
|
||||
else {
|
||||
notesValue.innerHTML = data.notes;
|
||||
}
|
||||
}
|
||||
else {
|
||||
notes.classList.add( 'hidden' );
|
||||
}
|
||||
|
||||
// Update the note slides
|
||||
currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
|
||||
upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
|
||||
upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'next' }), '*' );
|
||||
|
||||
}
|
||||
|
||||
// Limit to max one state update per X ms
|
||||
handleStateMessage = debounce( handleStateMessage, 200 );
|
||||
|
||||
/**
|
||||
* Forward keyboard events to the current slide window.
|
||||
* This enables keyboard events to work even if focus
|
||||
* isn't set on the current slide iframe.
|
||||
*
|
||||
* Block F5 default handling, it reloads and disconnects
|
||||
* the speaker notes window.
|
||||
*/
|
||||
function setupKeyboard() {
|
||||
|
||||
document.addEventListener( 'keydown', function( event ) {
|
||||
if( event.keyCode === 116 || ( event.metaKey && event.keyCode === 82 ) ) {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' );
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the preview iframes.
|
||||
*/
|
||||
function setupIframes( data ) {
|
||||
|
||||
var params = [
|
||||
'receiver',
|
||||
'progress=false',
|
||||
'history=false',
|
||||
'transition=none',
|
||||
'autoSlide=0',
|
||||
'backgroundTransition=none'
|
||||
].join( '&' );
|
||||
|
||||
var urlSeparator = /\?/.test(data.url) ? '&' : '?';
|
||||
var hash = '#/' + data.state.indexh + '/' + data.state.indexv;
|
||||
var currentURL = data.url + urlSeparator + params + '&postMessageEvents=true' + hash;
|
||||
var upcomingURL = data.url + urlSeparator + params + '&controls=false' + hash;
|
||||
|
||||
currentSlide = document.createElement( 'iframe' );
|
||||
currentSlide.setAttribute( 'width', 1280 );
|
||||
currentSlide.setAttribute( 'height', 1024 );
|
||||
currentSlide.setAttribute( 'src', currentURL );
|
||||
document.querySelector( '#current-slide' ).appendChild( currentSlide );
|
||||
|
||||
upcomingSlide = document.createElement( 'iframe' );
|
||||
upcomingSlide.setAttribute( 'width', 640 );
|
||||
upcomingSlide.setAttribute( 'height', 512 );
|
||||
upcomingSlide.setAttribute( 'src', upcomingURL );
|
||||
document.querySelector( '#upcoming-slide' ).appendChild( upcomingSlide );
|
||||
|
||||
whitelistedWindows.push( currentSlide.contentWindow, upcomingSlide.contentWindow );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the notes UI.
|
||||
*/
|
||||
function setupNotes() {
|
||||
|
||||
notes = document.querySelector( '.speaker-controls-notes' );
|
||||
notesValue = document.querySelector( '.speaker-controls-notes .value' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* We send out a heartbeat at all times to ensure we can
|
||||
* reconnect with the main presentation window after reloads.
|
||||
*/
|
||||
function setupHeartbeat() {
|
||||
|
||||
setInterval( () => {
|
||||
window.opener.postMessage( JSON.stringify({ namespace: 'reveal-notes', type: 'heartbeat'} ), '*' );
|
||||
}, 1000 );
|
||||
|
||||
}
|
||||
|
||||
function getTimings( callback ) {
|
||||
|
||||
callRevealApi( 'getSlidesAttributes', [], function ( slideAttributes ) {
|
||||
callRevealApi( 'getConfig', [], function ( config ) {
|
||||
var totalTime = config.totalTime;
|
||||
var minTimePerSlide = config.minimumTimePerSlide || 0;
|
||||
var defaultTiming = config.defaultTiming;
|
||||
if ((defaultTiming == null) && (totalTime == null)) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
// Setting totalTime overrides defaultTiming
|
||||
if (totalTime) {
|
||||
defaultTiming = 0;
|
||||
}
|
||||
var timings = [];
|
||||
for ( var i in slideAttributes ) {
|
||||
var slide = slideAttributes[ i ];
|
||||
var timing = defaultTiming;
|
||||
if( slide.hasOwnProperty( 'data-timing' )) {
|
||||
var t = slide[ 'data-timing' ];
|
||||
timing = parseInt(t);
|
||||
if( isNaN(timing) ) {
|
||||
console.warn("Could not parse timing '" + t + "' of slide " + i + "; using default of " + defaultTiming);
|
||||
timing = defaultTiming;
|
||||
}
|
||||
}
|
||||
timings.push(timing);
|
||||
}
|
||||
if ( totalTime ) {
|
||||
// After we've allocated time to individual slides, we summarize it and
|
||||
// subtract it from the total time
|
||||
var remainingTime = totalTime - timings.reduce( function(a, b) { return a + b; }, 0 );
|
||||
// The remaining time is divided by the number of slides that have 0 seconds
|
||||
// allocated at the moment, giving the average time-per-slide on the remaining slides
|
||||
var remainingSlides = (timings.filter( function(x) { return x == 0 }) ).length
|
||||
var timePerSlide = Math.round( remainingTime / remainingSlides, 0 )
|
||||
// And now we replace every zero-value timing with that average
|
||||
timings = timings.map( function(x) { return (x==0 ? timePerSlide : x) } );
|
||||
}
|
||||
var slidesUnderMinimum = timings.filter( function(x) { return (x < minTimePerSlide) } ).length
|
||||
if ( slidesUnderMinimum ) {
|
||||
message = "The pacing time for " + slidesUnderMinimum + " slide(s) is under the configured minimum of " + minTimePerSlide + " seconds. Check the data-timing attribute on individual slides, or consider increasing the totalTime or minimumTimePerSlide configuration options (or removing some slides).";
|
||||
alert(message);
|
||||
}
|
||||
callback( timings );
|
||||
} );
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of seconds allocated for presenting
|
||||
* all slides up to and including this one.
|
||||
*/
|
||||
function getTimeAllocated( timings, callback ) {
|
||||
|
||||
callRevealApi( 'getSlidePastCount', [], function ( currentSlide ) {
|
||||
var allocated = 0;
|
||||
for (var i in timings.slice(0, currentSlide + 1)) {
|
||||
allocated += timings[i];
|
||||
}
|
||||
callback( allocated );
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the timer and clock and start updating them
|
||||
* at an interval.
|
||||
*/
|
||||
function setupTimer() {
|
||||
|
||||
var start = new Date(),
|
||||
timeEl = document.querySelector( '.speaker-controls-time' ),
|
||||
clockEl = timeEl.querySelector( '.clock-value' ),
|
||||
hoursEl = timeEl.querySelector( '.hours-value' ),
|
||||
minutesEl = timeEl.querySelector( '.minutes-value' ),
|
||||
secondsEl = timeEl.querySelector( '.seconds-value' ),
|
||||
pacingTitleEl = timeEl.querySelector( '.pacing-title' ),
|
||||
pacingEl = timeEl.querySelector( '.pacing' ),
|
||||
pacingHoursEl = pacingEl.querySelector( '.hours-value' ),
|
||||
pacingMinutesEl = pacingEl.querySelector( '.minutes-value' ),
|
||||
pacingSecondsEl = pacingEl.querySelector( '.seconds-value' );
|
||||
|
||||
var timings = null;
|
||||
getTimings( function ( _timings ) {
|
||||
|
||||
timings = _timings;
|
||||
if (_timings !== null) {
|
||||
pacingTitleEl.style.removeProperty('display');
|
||||
pacingEl.style.removeProperty('display');
|
||||
}
|
||||
|
||||
// Update once directly
|
||||
_updateTimer();
|
||||
|
||||
// Then update every second
|
||||
setInterval( _updateTimer, 1000 );
|
||||
|
||||
} );
|
||||
|
||||
|
||||
function _resetTimer() {
|
||||
|
||||
if (timings == null) {
|
||||
start = new Date();
|
||||
_updateTimer();
|
||||
}
|
||||
else {
|
||||
// Reset timer to beginning of current slide
|
||||
getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
|
||||
var slideEndTiming = slideEndTimingSeconds * 1000;
|
||||
callRevealApi( 'getSlidePastCount', [], function ( currentSlide ) {
|
||||
var currentSlideTiming = timings[currentSlide] * 1000;
|
||||
var previousSlidesTiming = slideEndTiming - currentSlideTiming;
|
||||
var now = new Date();
|
||||
start = new Date(now.getTime() - previousSlidesTiming);
|
||||
_updateTimer();
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
timeEl.addEventListener( 'click', function() {
|
||||
_resetTimer();
|
||||
return false;
|
||||
} );
|
||||
|
||||
function _displayTime( hrEl, minEl, secEl, time) {
|
||||
|
||||
var sign = Math.sign(time) == -1 ? "-" : "";
|
||||
time = Math.abs(Math.round(time / 1000));
|
||||
var seconds = time % 60;
|
||||
var minutes = Math.floor( time / 60 ) % 60 ;
|
||||
var hours = Math.floor( time / ( 60 * 60 )) ;
|
||||
hrEl.innerHTML = sign + zeroPadInteger( hours );
|
||||
if (hours == 0) {
|
||||
hrEl.classList.add( 'mute' );
|
||||
}
|
||||
else {
|
||||
hrEl.classList.remove( 'mute' );
|
||||
}
|
||||
minEl.innerHTML = ':' + zeroPadInteger( minutes );
|
||||
if (hours == 0 && minutes == 0) {
|
||||
minEl.classList.add( 'mute' );
|
||||
}
|
||||
else {
|
||||
minEl.classList.remove( 'mute' );
|
||||
}
|
||||
secEl.innerHTML = ':' + zeroPadInteger( seconds );
|
||||
}
|
||||
|
||||
function _updateTimer() {
|
||||
|
||||
var diff, hours, minutes, seconds,
|
||||
now = new Date();
|
||||
|
||||
diff = now.getTime() - start.getTime();
|
||||
|
||||
clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );
|
||||
_displayTime( hoursEl, minutesEl, secondsEl, diff );
|
||||
if (timings !== null) {
|
||||
_updatePacing(diff);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function _updatePacing(diff) {
|
||||
|
||||
getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
|
||||
var slideEndTiming = slideEndTimingSeconds * 1000;
|
||||
|
||||
callRevealApi( 'getSlidePastCount', [], function ( currentSlide ) {
|
||||
var currentSlideTiming = timings[currentSlide] * 1000;
|
||||
var timeLeftCurrentSlide = slideEndTiming - diff;
|
||||
if (timeLeftCurrentSlide < 0) {
|
||||
pacingEl.className = 'pacing behind';
|
||||
}
|
||||
else if (timeLeftCurrentSlide < currentSlideTiming) {
|
||||
pacingEl.className = 'pacing on-track';
|
||||
}
|
||||
else {
|
||||
pacingEl.className = 'pacing ahead';
|
||||
}
|
||||
_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the speaker view layout and layout selector.
|
||||
*/
|
||||
function setupLayout() {
|
||||
|
||||
layoutDropdown = document.querySelector( '.speaker-layout-dropdown' );
|
||||
layoutLabel = document.querySelector( '.speaker-layout-label' );
|
||||
|
||||
// Render the list of available layouts
|
||||
for( var id in SPEAKER_LAYOUTS ) {
|
||||
var option = document.createElement( 'option' );
|
||||
option.setAttribute( 'value', id );
|
||||
option.textContent = SPEAKER_LAYOUTS[ id ];
|
||||
layoutDropdown.appendChild( option );
|
||||
}
|
||||
|
||||
// Monitor the dropdown for changes
|
||||
layoutDropdown.addEventListener( 'change', function( event ) {
|
||||
|
||||
setLayout( layoutDropdown.value );
|
||||
|
||||
}, false );
|
||||
|
||||
// Restore any currently persisted layout
|
||||
setLayout( getLayout() );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new speaker view layout. The layout is persisted
|
||||
* in local storage.
|
||||
*/
|
||||
function setLayout( value ) {
|
||||
|
||||
var title = SPEAKER_LAYOUTS[ value ];
|
||||
|
||||
layoutLabel.innerHTML = 'Layout' + ( title ? ( ': ' + title ) : '' );
|
||||
layoutDropdown.value = value;
|
||||
|
||||
document.body.setAttribute( 'data-speaker-layout', value );
|
||||
|
||||
// Persist locally
|
||||
if( supportsLocalStorage() ) {
|
||||
window.localStorage.setItem( 'reveal-speaker-layout', value );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the most recently set speaker layout
|
||||
* or our default layout if none has been set.
|
||||
*/
|
||||
function getLayout() {
|
||||
|
||||
if( supportsLocalStorage() ) {
|
||||
var layout = window.localStorage.getItem( 'reveal-speaker-layout' );
|
||||
if( layout ) {
|
||||
return layout;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to the first record in the layouts hash
|
||||
for( var id in SPEAKER_LAYOUTS ) {
|
||||
return id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function supportsLocalStorage() {
|
||||
|
||||
try {
|
||||
localStorage.setItem('test', 'test');
|
||||
localStorage.removeItem('test');
|
||||
return true;
|
||||
}
|
||||
catch( e ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function zeroPadInteger( num ) {
|
||||
|
||||
var str = '00' + parseInt( num );
|
||||
return str.substring( str.length - 2 );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Limits the frequency at which a function can be called.
|
||||
*/
|
||||
function debounce( fn, ms ) {
|
||||
|
||||
var lastTime = 0,
|
||||
timeout;
|
||||
|
||||
return function() {
|
||||
|
||||
var args = arguments;
|
||||
var context = this;
|
||||
|
||||
clearTimeout( timeout );
|
||||
|
||||
var timeSinceLastCall = Date.now() - lastTime;
|
||||
if( timeSinceLastCall > ms ) {
|
||||
fn.apply( context, args );
|
||||
lastTime = Date.now();
|
||||
}
|
||||
else {
|
||||
timeout = setTimeout( function() {
|
||||
fn.apply( context, args );
|
||||
lastTime = Date.now();
|
||||
}, ms - timeSinceLastCall );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,108 +0,0 @@
|
||||
var PdfExport = ( function( _Reveal ){
|
||||
|
||||
var Reveal = _Reveal;
|
||||
var setStylesheet = null;
|
||||
var installAltKeyBindings = null;
|
||||
|
||||
function getRevealJsPath(){
|
||||
var regex = /\b[^/]+\/reveal.css$/i;
|
||||
var script = Array.from( document.querySelectorAll( 'link' ) ).find( function( e ){
|
||||
return e.attributes.href && e.attributes.href.value.search( regex ) >= 0;
|
||||
});
|
||||
if( !script ){
|
||||
console.error( 'reveal.css could not be found in included <link> elements. Did you rename this file?' );
|
||||
return '';
|
||||
}
|
||||
return script.attributes.href.value.replace( regex, '' );
|
||||
}
|
||||
|
||||
function setStylesheet3( pdfExport ){
|
||||
var link = document.querySelector( '#print' );
|
||||
if( !link ){
|
||||
link = document.createElement( 'link' );
|
||||
link.rel = 'stylesheet';
|
||||
link.id = 'print';
|
||||
document.querySelector( 'head' ).appendChild( link );
|
||||
}
|
||||
var style = 'paper';
|
||||
if( pdfExport ){
|
||||
style = 'pdf';
|
||||
}
|
||||
link.href = getRevealJsPath() + 'css/print/' + style + '.css';
|
||||
}
|
||||
|
||||
function setStylesheet4( pdfExport ){
|
||||
}
|
||||
|
||||
function installAltKeyBindings3(){
|
||||
}
|
||||
|
||||
function installAltKeyBindings4(){
|
||||
if( isPrintingPDF() ){
|
||||
var config = Reveal.getConfig();
|
||||
var shortcut = config.pdfExportShortcut || 'E';
|
||||
window.addEventListener( 'keydown', function( e ){
|
||||
if( e.target.nodeName.toUpperCase() == 'BODY'
|
||||
&& ( e.key.toUpperCase() == shortcut.toUpperCase() || e.keyCode == shortcut.toUpperCase().charCodeAt( 0 ) ) ){
|
||||
e.preventDefault();
|
||||
togglePdfExport();
|
||||
return false;
|
||||
}
|
||||
}, true );
|
||||
}
|
||||
}
|
||||
|
||||
function isPrintingPDF(){
|
||||
return ( /print-pdf/gi ).test( window.location.search );
|
||||
}
|
||||
|
||||
function togglePdfExport(){
|
||||
var url_doc = new URL( document.URL );
|
||||
var query_doc = new URLSearchParams( url_doc.searchParams );
|
||||
if( isPrintingPDF() ){
|
||||
query_doc.delete( 'print-pdf' );
|
||||
}else{
|
||||
query_doc.set( 'print-pdf', '' );
|
||||
}
|
||||
url_doc.search = ( query_doc.toString() ? '?' + query_doc.toString() : '' );
|
||||
window.location.href = url_doc.toString();
|
||||
}
|
||||
|
||||
function installKeyBindings(){
|
||||
var config = Reveal.getConfig();
|
||||
var shortcut = config.pdfExportShortcut || 'E';
|
||||
Reveal.addKeyBinding({
|
||||
keyCode: shortcut.toUpperCase().charCodeAt( 0 ),
|
||||
key: shortcut.toUpperCase(),
|
||||
description: 'PDF export mode'
|
||||
}, togglePdfExport );
|
||||
installAltKeyBindings();
|
||||
}
|
||||
|
||||
function install(){
|
||||
installKeyBindings();
|
||||
setStylesheet( isPrintingPDF() );
|
||||
}
|
||||
|
||||
var Plugin = {
|
||||
}
|
||||
|
||||
if( Reveal && Reveal.VERSION && Reveal.VERSION.length && Reveal.VERSION[ 0 ] == '3' ){
|
||||
// reveal 3.x
|
||||
setStylesheet = setStylesheet3;
|
||||
installAltKeyBindings = installAltKeyBindings3;
|
||||
install();
|
||||
}else{
|
||||
// must be reveal 4.x
|
||||
setStylesheet = setStylesheet4;
|
||||
installAltKeyBindings = installAltKeyBindings4;
|
||||
Plugin.id = 'pdf-export';
|
||||
Plugin.init = function( _Reveal ){
|
||||
Reveal = _Reveal;
|
||||
install();
|
||||
};
|
||||
}
|
||||
|
||||
return Plugin;
|
||||
|
||||
})( Reveal );
|
||||
@@ -1,2 +0,0 @@
|
||||
name: PdfExport
|
||||
script: pdfexport.js
|
||||
@@ -1,31 +0,0 @@
|
||||
.reveal
|
||||
div.sourceCode
|
||||
pre
|
||||
code.has-line-highlights
|
||||
> span:not(.highlight-line) {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.reveal pre.numberSource {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.reveal pre.numberSource code > span {
|
||||
left: -2.1em;
|
||||
}
|
||||
|
||||
pre.numberSource code > span > a:first-child::before {
|
||||
left: -0.7em;
|
||||
}
|
||||
|
||||
.reveal pre > code:not(:first-child).fragment {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.reveal div.sourceCode pre code {
|
||||
min-height: 100%;
|
||||
}
|
||||
@@ -1,351 +0,0 @@
|
||||
window.QuartoLineHighlight = function () {
|
||||
function isPrintView() {
|
||||
return /print-pdf/gi.test(window.location.search);
|
||||
}
|
||||
|
||||
const delimiters = {
|
||||
step: "|",
|
||||
line: ",",
|
||||
lineRange: "-",
|
||||
};
|
||||
|
||||
const regex = new RegExp(
|
||||
"^[\\d" + Object.values(delimiters).join("") + "]+$"
|
||||
);
|
||||
|
||||
function handleLinesSelector(deck, attr) {
|
||||
// if we are in printview with pdfSeparateFragments: false
|
||||
// then we'll also want to supress
|
||||
if (regex.test(attr)) {
|
||||
if (isPrintView() && deck.getConfig().pdfSeparateFragments !== true) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const kCodeLineNumbersAttr = "data-code-line-numbers";
|
||||
const kFragmentIndex = "data-fragment-index";
|
||||
|
||||
function initQuartoLineHighlight(deck) {
|
||||
const divSourceCode = deck
|
||||
.getRevealElement()
|
||||
.querySelectorAll("div.sourceCode");
|
||||
// Process each div created by Pandoc highlighting - numbered line are already included.
|
||||
divSourceCode.forEach((el) => {
|
||||
if (el.hasAttribute(kCodeLineNumbersAttr)) {
|
||||
const codeLineAttr = el.getAttribute(kCodeLineNumbersAttr);
|
||||
el.removeAttribute(kCodeLineNumbersAttr);
|
||||
if (handleLinesSelector(deck, codeLineAttr)) {
|
||||
// Only process if attr is a string to select lines to highlights
|
||||
// e.g "1|3,6|8-11"
|
||||
const codeBlock = el.querySelectorAll("pre code");
|
||||
codeBlock.forEach((code) => {
|
||||
// move attributes on code block
|
||||
code.setAttribute(kCodeLineNumbersAttr, codeLineAttr);
|
||||
|
||||
const scrollState = { currentBlock: code };
|
||||
|
||||
// Check if there are steps and duplicate code block accordingly
|
||||
const highlightSteps = splitLineNumbers(codeLineAttr);
|
||||
if (highlightSteps.length > 1) {
|
||||
// If the original code block has a fragment-index,
|
||||
// each clone should follow in an incremental sequence
|
||||
let fragmentIndex = parseInt(
|
||||
code.getAttribute(kFragmentIndex),
|
||||
10
|
||||
);
|
||||
fragmentIndex =
|
||||
typeof fragmentIndex !== "number" || isNaN(fragmentIndex)
|
||||
? null
|
||||
: fragmentIndex;
|
||||
|
||||
let stepN = 1;
|
||||
highlightSteps.slice(1).forEach(
|
||||
// Generate fragments for all steps except the original block
|
||||
(step) => {
|
||||
var fragmentBlock = code.cloneNode(true);
|
||||
fragmentBlock.setAttribute(
|
||||
"data-code-line-numbers",
|
||||
joinLineNumbers([step])
|
||||
);
|
||||
fragmentBlock.classList.add("fragment");
|
||||
|
||||
// Pandoc sets id on spans we need to keep unique
|
||||
fragmentBlock
|
||||
.querySelectorAll(":scope > span")
|
||||
.forEach((span) => {
|
||||
if (span.hasAttribute("id")) {
|
||||
span.setAttribute(
|
||||
"id",
|
||||
span.getAttribute("id").concat("-" + stepN)
|
||||
);
|
||||
}
|
||||
});
|
||||
stepN = ++stepN;
|
||||
|
||||
// Add duplicated <code> element after existing one
|
||||
code.parentNode.appendChild(fragmentBlock);
|
||||
|
||||
// Each new <code> element is highlighted based on the new attributes value
|
||||
highlightCodeBlock(fragmentBlock);
|
||||
|
||||
if (typeof fragmentIndex === "number") {
|
||||
fragmentBlock.setAttribute(kFragmentIndex, fragmentIndex);
|
||||
fragmentIndex += 1;
|
||||
} else {
|
||||
fragmentBlock.removeAttribute(kFragmentIndex);
|
||||
}
|
||||
|
||||
// Scroll highlights into view as we step through them
|
||||
fragmentBlock.addEventListener(
|
||||
"visible",
|
||||
scrollHighlightedLineIntoView.bind(
|
||||
this,
|
||||
fragmentBlock,
|
||||
scrollState
|
||||
)
|
||||
);
|
||||
fragmentBlock.addEventListener(
|
||||
"hidden",
|
||||
scrollHighlightedLineIntoView.bind(
|
||||
this,
|
||||
fragmentBlock.previousSibling,
|
||||
scrollState
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
code.removeAttribute(kFragmentIndex);
|
||||
code.setAttribute(
|
||||
kCodeLineNumbersAttr,
|
||||
joinLineNumbers([highlightSteps[0]])
|
||||
);
|
||||
}
|
||||
|
||||
// Scroll the first highlight into view when the slide becomes visible.
|
||||
const slide =
|
||||
typeof code.closest === "function"
|
||||
? code.closest("section:not(.stack)")
|
||||
: null;
|
||||
if (slide) {
|
||||
const scrollFirstHighlightIntoView = function () {
|
||||
scrollHighlightedLineIntoView(code, scrollState, true);
|
||||
slide.removeEventListener(
|
||||
"visible",
|
||||
scrollFirstHighlightIntoView
|
||||
);
|
||||
};
|
||||
slide.addEventListener("visible", scrollFirstHighlightIntoView);
|
||||
}
|
||||
|
||||
highlightCodeBlock(code);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function highlightCodeBlock(codeBlock) {
|
||||
const highlightSteps = splitLineNumbers(
|
||||
codeBlock.getAttribute(kCodeLineNumbersAttr)
|
||||
);
|
||||
|
||||
if (highlightSteps.length) {
|
||||
// If we have at least one step, we generate fragments
|
||||
highlightSteps[0].forEach((highlight) => {
|
||||
// Add expected class on <pre> for reveal CSS
|
||||
codeBlock.parentNode.classList.add("code-wrapper");
|
||||
|
||||
// Select lines to highlight
|
||||
spanToHighlight = [];
|
||||
if (typeof highlight.last === "number") {
|
||||
spanToHighlight = [].slice.call(
|
||||
codeBlock.querySelectorAll(
|
||||
":scope > span:nth-of-type(n+" +
|
||||
highlight.first +
|
||||
"):nth-of-type(-n+" +
|
||||
highlight.last +
|
||||
")"
|
||||
)
|
||||
);
|
||||
} else if (typeof highlight.first === "number") {
|
||||
spanToHighlight = [].slice.call(
|
||||
codeBlock.querySelectorAll(
|
||||
":scope > span:nth-of-type(" + highlight.first + ")"
|
||||
)
|
||||
);
|
||||
}
|
||||
if (spanToHighlight.length) {
|
||||
// Add a class on <code> and <span> to select line to highlight
|
||||
spanToHighlight.forEach((span) =>
|
||||
span.classList.add("highlight-line")
|
||||
);
|
||||
codeBlock.classList.add("has-line-highlights");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates scrolling to the first highlighted line
|
||||
* in the given code block.
|
||||
*/
|
||||
function scrollHighlightedLineIntoView(block, scrollState, skipAnimation) {
|
||||
window.cancelAnimationFrame(scrollState.animationFrameID);
|
||||
|
||||
// Match the scroll position of the currently visible
|
||||
// code block
|
||||
if (scrollState.currentBlock) {
|
||||
block.scrollTop = scrollState.currentBlock.scrollTop;
|
||||
}
|
||||
|
||||
// Remember the current code block so that we can match
|
||||
// its scroll position when showing/hiding fragments
|
||||
scrollState.currentBlock = block;
|
||||
|
||||
const highlightBounds = getHighlightedLineBounds(block);
|
||||
let viewportHeight = block.offsetHeight;
|
||||
|
||||
// Subtract padding from the viewport height
|
||||
const blockStyles = window.getComputedStyle(block);
|
||||
viewportHeight -=
|
||||
parseInt(blockStyles.paddingTop) + parseInt(blockStyles.paddingBottom);
|
||||
|
||||
// Scroll position which centers all highlights
|
||||
const startTop = block.scrollTop;
|
||||
let targetTop =
|
||||
highlightBounds.top +
|
||||
(Math.min(highlightBounds.bottom - highlightBounds.top, viewportHeight) -
|
||||
viewportHeight) /
|
||||
2;
|
||||
|
||||
// Make sure the scroll target is within bounds
|
||||
targetTop = Math.max(
|
||||
Math.min(targetTop, block.scrollHeight - viewportHeight),
|
||||
0
|
||||
);
|
||||
|
||||
if (skipAnimation === true || startTop === targetTop) {
|
||||
block.scrollTop = targetTop;
|
||||
} else {
|
||||
// Don't attempt to scroll if there is no overflow
|
||||
if (block.scrollHeight <= viewportHeight) return;
|
||||
|
||||
let time = 0;
|
||||
|
||||
const animate = function () {
|
||||
time = Math.min(time + 0.02, 1);
|
||||
|
||||
// Update our eased scroll position
|
||||
block.scrollTop =
|
||||
startTop + (targetTop - startTop) * easeInOutQuart(time);
|
||||
|
||||
// Keep animating unless we've reached the end
|
||||
if (time < 1) {
|
||||
scrollState.animationFrameID = requestAnimationFrame(animate);
|
||||
}
|
||||
};
|
||||
|
||||
animate();
|
||||
}
|
||||
}
|
||||
|
||||
function getHighlightedLineBounds(block) {
|
||||
const highlightedLines = block.querySelectorAll(".highlight-line");
|
||||
if (highlightedLines.length === 0) {
|
||||
return { top: 0, bottom: 0 };
|
||||
} else {
|
||||
const firstHighlight = highlightedLines[0];
|
||||
const lastHighlight = highlightedLines[highlightedLines.length - 1];
|
||||
|
||||
return {
|
||||
top: firstHighlight.offsetTop,
|
||||
bottom: lastHighlight.offsetTop + lastHighlight.offsetHeight,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The easing function used when scrolling.
|
||||
*/
|
||||
function easeInOutQuart(t) {
|
||||
// easeInOutQuart
|
||||
return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t;
|
||||
}
|
||||
|
||||
function splitLineNumbers(lineNumbersAttr) {
|
||||
// remove space
|
||||
lineNumbersAttr = lineNumbersAttr.replace("/s/g", "");
|
||||
// seperate steps (for fragment)
|
||||
lineNumbersAttr = lineNumbersAttr.split(delimiters.step);
|
||||
|
||||
// for each step, calculate first and last line, if any
|
||||
return lineNumbersAttr.map((highlights) => {
|
||||
// detect lines
|
||||
const lines = highlights.split(delimiters.line);
|
||||
return lines.map((range) => {
|
||||
if (/^[\d-]+$/.test(range)) {
|
||||
range = range.split(delimiters.lineRange);
|
||||
const firstLine = parseInt(range[0], 10);
|
||||
const lastLine = range[1] ? parseInt(range[1], 10) : undefined;
|
||||
return {
|
||||
first: firstLine,
|
||||
last: lastLine,
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function joinLineNumbers(splittedLineNumbers) {
|
||||
return splittedLineNumbers
|
||||
.map(function (highlights) {
|
||||
return highlights
|
||||
.map(function (highlight) {
|
||||
// Line range
|
||||
if (typeof highlight.last === "number") {
|
||||
return highlight.first + delimiters.lineRange + highlight.last;
|
||||
}
|
||||
// Single line
|
||||
else if (typeof highlight.first === "number") {
|
||||
return highlight.first;
|
||||
}
|
||||
// All lines
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
})
|
||||
.join(delimiters.line);
|
||||
})
|
||||
.join(delimiters.step);
|
||||
}
|
||||
|
||||
return {
|
||||
id: "quarto-line-highlight",
|
||||
init: function (deck) {
|
||||
initQuartoLineHighlight(deck);
|
||||
|
||||
// If we're printing to PDF, scroll the code highlights of
|
||||
// all blocks in the deck into view at once
|
||||
deck.on("pdf-ready", function () {
|
||||
[].slice
|
||||
.call(
|
||||
deck
|
||||
.getRevealElement()
|
||||
.querySelectorAll(
|
||||
"pre code[data-code-line-numbers].current-fragment"
|
||||
)
|
||||
)
|
||||
.forEach(function (block) {
|
||||
scrollHighlightedLineIntoView(block, {}, true);
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
# adapted from https://github.com/hakimel/reveal.js/tree/master/plugin/highlight
|
||||
name: QuartoLineHighlight
|
||||
script: line-highlight.js
|
||||
stylesheet: line-highlight.css
|
||||
@@ -1,110 +0,0 @@
|
||||
.reveal .slide-logo {
|
||||
display: block;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 12px;
|
||||
max-height: 2.2rem;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.reveal .footer {
|
||||
display: block;
|
||||
position: fixed;
|
||||
bottom: 18px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.reveal .footer > * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.reveal .slide .footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.reveal .slide-number {
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
font-size: 16px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.reveal.has-logo .slide-number {
|
||||
bottom: initial;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.reveal .slide-number .slide-number-delimiter {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.reveal .slide-menu-button {
|
||||
left: 8px;
|
||||
bottom: 8px;
|
||||
}
|
||||
|
||||
.reveal .slide-chalkboard-buttons {
|
||||
position: fixed;
|
||||
left: 12px;
|
||||
bottom: 8px;
|
||||
z-index: 30;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.reveal .slide-chalkboard-buttons.slide-menu-offset {
|
||||
left: 54px;
|
||||
}
|
||||
|
||||
.reveal .slide-chalkboard-buttons > span {
|
||||
margin-right: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.reveal .slide-logo {
|
||||
max-height: 1.1rem;
|
||||
bottom: -2px;
|
||||
right: 10px;
|
||||
}
|
||||
.reveal .footer {
|
||||
font-size: 14px;
|
||||
bottom: 12px;
|
||||
}
|
||||
.reveal .slide-number {
|
||||
font-size: 12px;
|
||||
bottom: 7px;
|
||||
}
|
||||
.reveal .slide-menu-button .fas::before {
|
||||
height: 1.3rem;
|
||||
width: 1.3rem;
|
||||
vertical-align: -0.125em;
|
||||
background-size: 1.3rem 1.3rem;
|
||||
}
|
||||
|
||||
.reveal .slide-chalkboard-buttons .fas::before {
|
||||
height: 0.95rem;
|
||||
width: 0.95rem;
|
||||
background-size: 0.95rem 0.95rem;
|
||||
vertical-align: -0em;
|
||||
}
|
||||
|
||||
.reveal .slide-chalkboard-buttons.slide-menu-offset {
|
||||
left: 36px;
|
||||
}
|
||||
.reveal .slide-chalkboard-buttons > span {
|
||||
margin-right: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
html.print-pdf .reveal .slide-menu-button,
|
||||
html.print-pdf .reveal .slide-chalkboard-buttons {
|
||||
display: none;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
name: QuartoSupport
|
||||
script: support.js
|
||||
stylesheet: footer.css
|
||||
config:
|
||||
smaller: false
|
||||
@@ -1,290 +0,0 @@
|
||||
// catch all plugin for various quarto features
|
||||
window.QuartoSupport = function () {
|
||||
function isPrintView() {
|
||||
return /print-pdf/gi.test(window.location.search);
|
||||
}
|
||||
|
||||
// implement controlsAudo
|
||||
function controlsAuto(deck) {
|
||||
const config = deck.getConfig();
|
||||
if (config.controlsAuto === true) {
|
||||
const iframe = window.location !== window.parent.location;
|
||||
const localhost =
|
||||
window.location.hostname === "localhost" ||
|
||||
window.location.hostname === "127.0.0.1";
|
||||
deck.configure({
|
||||
controls:
|
||||
(iframe && !localhost) ||
|
||||
(deck.hasVerticalSlides() && config.navigationMode !== "linear"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// helper to provide event handlers for all links in a container
|
||||
function handleLinkClickEvents(deck, container) {
|
||||
Array.from(container.querySelectorAll("a")).forEach((el) => {
|
||||
const url = el.getAttribute("href");
|
||||
if (/^(http|www)/gi.test(url)) {
|
||||
el.addEventListener(
|
||||
"click",
|
||||
(ev) => {
|
||||
const fullscreen = !!window.document.fullscreen;
|
||||
const dataPreviewLink = el.getAttribute("data-preview-link");
|
||||
|
||||
// if there is a local specifcation then use that
|
||||
if (dataPreviewLink) {
|
||||
if (
|
||||
dataPreviewLink === "true" ||
|
||||
(dataPreviewLink === "auto" && fullscreen)
|
||||
) {
|
||||
ev.preventDefault();
|
||||
deck.showPreview(url);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const previewLinks = !!deck.getConfig().previewLinks;
|
||||
const previewLinksAuto =
|
||||
deck.getConfig().previewLinksAuto === true;
|
||||
if (previewLinks == true || (previewLinksAuto && fullscreen)) {
|
||||
ev.preventDefault();
|
||||
deck.showPreview(url);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// if the deck is in an iframe we want to open it externally
|
||||
// (don't do this when in vscode though as it has its own
|
||||
// handler for opening links externally that will be play)
|
||||
const iframe = window.location !== window.parent.location;
|
||||
if (
|
||||
iframe &&
|
||||
!window.location.search.includes("quartoPreviewReqId=")
|
||||
) {
|
||||
ev.preventDefault();
|
||||
ev.stopImmediatePropagation();
|
||||
window.open(url, "_blank");
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the user has set data-preview-link to "auto" we need to handle the event
|
||||
// (because reveal will interpret "auto" as true)
|
||||
if (dataPreviewLink === "auto") {
|
||||
ev.preventDefault();
|
||||
ev.stopImmediatePropagation();
|
||||
const target =
|
||||
el.getAttribute("target") ||
|
||||
(ev.ctrlKey || ev.metaKey ? "_blank" : "");
|
||||
if (target) {
|
||||
window.open(url, target);
|
||||
} else {
|
||||
window.location.href = url;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// implement previewLinksAuto
|
||||
function previewLinksAuto(deck) {
|
||||
handleLinkClickEvents(deck, deck.getRevealElement());
|
||||
}
|
||||
|
||||
// apply styles
|
||||
function applyGlobalStyles(deck) {
|
||||
if (deck.getConfig()["smaller"] === true) {
|
||||
const revealParent = deck.getRevealElement();
|
||||
revealParent.classList.add("smaller");
|
||||
}
|
||||
}
|
||||
|
||||
// add logo image
|
||||
function addLogoImage(deck) {
|
||||
const revealParent = deck.getRevealElement();
|
||||
const logoImg = document.querySelector(".slide-logo");
|
||||
if (logoImg) {
|
||||
revealParent.appendChild(logoImg);
|
||||
revealParent.classList.add("has-logo");
|
||||
}
|
||||
}
|
||||
|
||||
// add footer text
|
||||
function addFooter(deck) {
|
||||
const revealParent = deck.getRevealElement();
|
||||
const defaultFooterDiv = document.querySelector(".footer-default");
|
||||
if (defaultFooterDiv) {
|
||||
revealParent.appendChild(defaultFooterDiv);
|
||||
handleLinkClickEvents(deck, defaultFooterDiv);
|
||||
if (!isPrintView()) {
|
||||
deck.on("slidechanged", function (ev) {
|
||||
const prevSlideFooter = document.querySelector(
|
||||
".reveal > .footer:not(.footer-default)"
|
||||
);
|
||||
if (prevSlideFooter) {
|
||||
prevSlideFooter.remove();
|
||||
}
|
||||
const currentSlideFooter = ev.currentSlide.querySelector(".footer");
|
||||
if (currentSlideFooter) {
|
||||
defaultFooterDiv.style.display = "none";
|
||||
const slideFooter = currentSlideFooter.cloneNode(true);
|
||||
handleLinkClickEvents(deck, slideFooter);
|
||||
deck.getRevealElement().appendChild(slideFooter);
|
||||
} else {
|
||||
defaultFooterDiv.style.display = "block";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add chalkboard buttons
|
||||
function addChalkboardButtons(deck) {
|
||||
const chalkboard = deck.getPlugin("RevealChalkboard");
|
||||
if (chalkboard && !isPrintView()) {
|
||||
const revealParent = deck.getRevealElement();
|
||||
const chalkboardDiv = document.createElement("div");
|
||||
chalkboardDiv.classList.add("slide-chalkboard-buttons");
|
||||
if (document.querySelector(".slide-menu-button")) {
|
||||
chalkboardDiv.classList.add("slide-menu-offset");
|
||||
}
|
||||
// add buttons
|
||||
const buttons = [
|
||||
{
|
||||
icon: "easel2",
|
||||
title: "Toggle Chalkboard (b)",
|
||||
onclick: chalkboard.toggleChalkboard,
|
||||
},
|
||||
{
|
||||
icon: "brush",
|
||||
title: "Toggle Notes Canvas (c)",
|
||||
onclick: chalkboard.toggleNotesCanvas,
|
||||
},
|
||||
];
|
||||
buttons.forEach(function (button) {
|
||||
const span = document.createElement("span");
|
||||
span.title = button.title;
|
||||
const icon = document.createElement("i");
|
||||
icon.classList.add("fas");
|
||||
icon.classList.add("fa-" + button.icon);
|
||||
span.appendChild(icon);
|
||||
span.onclick = function (event) {
|
||||
event.preventDefault();
|
||||
button.onclick();
|
||||
};
|
||||
chalkboardDiv.appendChild(span);
|
||||
});
|
||||
revealParent.appendChild(chalkboardDiv);
|
||||
const config = deck.getConfig();
|
||||
if (!config.chalkboard.buttons) {
|
||||
chalkboardDiv.classList.add("hidden");
|
||||
}
|
||||
|
||||
// show and hide chalkboard buttons on slidechange
|
||||
deck.on("slidechanged", function (ev) {
|
||||
const config = deck.getConfig();
|
||||
let buttons = !!config.chalkboard.buttons;
|
||||
const slideButtons = ev.currentSlide.getAttribute(
|
||||
"data-chalkboard-buttons"
|
||||
);
|
||||
if (slideButtons) {
|
||||
if (slideButtons === "true" || slideButtons === "1") {
|
||||
buttons = true;
|
||||
} else if (slideButtons === "false" || slideButtons === "0") {
|
||||
buttons = false;
|
||||
}
|
||||
}
|
||||
if (buttons) {
|
||||
chalkboardDiv.classList.remove("hidden");
|
||||
} else {
|
||||
chalkboardDiv.classList.add("hidden");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleTabbyClicks() {
|
||||
const tabs = document.querySelectorAll(".panel-tabset-tabby > li > a");
|
||||
for (let i = 0; i < tabs.length; i++) {
|
||||
const tab = tabs[i];
|
||||
tab.onclick = function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function fixupForPrint(deck) {
|
||||
if (isPrintView()) {
|
||||
const slides = deck.getSlides();
|
||||
slides.forEach(function (slide) {
|
||||
slide.removeAttribute("data-auto-animate");
|
||||
});
|
||||
window.document.querySelectorAll(".hljs").forEach(function (el) {
|
||||
el.classList.remove("hljs");
|
||||
});
|
||||
window.document.querySelectorAll(".hljs-ln-code").forEach(function (el) {
|
||||
el.classList.remove("hljs-ln-code");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleSlideChanges(deck) {
|
||||
// dispatch for htmlwidgets
|
||||
const fireSlideEnter = () => {
|
||||
const event = window.document.createEvent("Event");
|
||||
event.initEvent("slideenter", true, true);
|
||||
window.document.dispatchEvent(event);
|
||||
};
|
||||
|
||||
const fireSlideChanged = (previousSlide, currentSlide) => {
|
||||
fireSlideEnter();
|
||||
|
||||
// dispatch for shiny
|
||||
if (window.jQuery) {
|
||||
if (previousSlide) {
|
||||
window.jQuery(previousSlide).trigger("hidden");
|
||||
}
|
||||
if (currentSlide) {
|
||||
window.jQuery(currentSlide).trigger("shown");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// fire slideEnter for tabby tab activations (for htmlwidget resize behavior)
|
||||
document.addEventListener("tabby", fireSlideEnter, false);
|
||||
|
||||
deck.on("slidechanged", function (event) {
|
||||
fireSlideChanged(event.previousSlide, event.currentSlide);
|
||||
});
|
||||
}
|
||||
|
||||
function workaroundMermaidDistance(deck) {
|
||||
if (window.document.querySelector("pre.mermaid-js")) {
|
||||
const slideCount = deck.getTotalSlides();
|
||||
deck.configure({
|
||||
mobileViewDistance: slideCount,
|
||||
viewDistance: slideCount,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: "quarto-support",
|
||||
init: function (deck) {
|
||||
controlsAuto(deck);
|
||||
previewLinksAuto(deck);
|
||||
fixupForPrint(deck);
|
||||
applyGlobalStyles(deck);
|
||||
addLogoImage(deck);
|
||||
addFooter(deck);
|
||||
addChalkboardButtons(deck);
|
||||
handleTabbyClicks();
|
||||
handleSlideChanges(deck);
|
||||
workaroundMermaidDistance(deck);
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,346 +0,0 @@
|
||||
.slide-menu-wrapper {
|
||||
font-family: 'Source Sans Pro', Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu {
|
||||
background-color: #333;
|
||||
z-index: 200;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
/*overflow-y: scroll;*/
|
||||
transition: transform 0.3s;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu.slide-menu--wide {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu.slide-menu--third {
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu.slide-menu--half {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu.slide-menu--full {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
/*
|
||||
* Slides menu
|
||||
*/
|
||||
|
||||
.slide-menu-wrapper .slide-menu-items {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
border-bottom: solid 1px #555;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-item,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical {
|
||||
display: block;
|
||||
text-align: left;
|
||||
padding: 10px 18px;
|
||||
color: #aaa;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-item-vertical {
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu--wide .slide-menu-item-vertical,
|
||||
.slide-menu-wrapper .slide-menu--third .slide-menu-item-vertical,
|
||||
.slide-menu-wrapper .slide-menu--half .slide-menu-item-vertical,
|
||||
.slide-menu-wrapper .slide-menu--full .slide-menu-item-vertical,
|
||||
.slide-menu-wrapper .slide-menu--custom .slide-menu-item-vertical {
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-item {
|
||||
border-top: solid 1px #555;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .active-menu-panel li.selected {
|
||||
background-color: #222;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .active-menu-panel li.active {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-item.no-title .slide-menu-item-title,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical.no-title .slide-menu-item-title {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-item-number {
|
||||
color: #999;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-item i.far,
|
||||
.slide-menu-wrapper .slide-menu-item i.fas,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical i.far,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical i.fas,
|
||||
.slide-menu-wrapper .slide-menu-item svg.svg-inline--fa,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical svg.svg-inline--fa {
|
||||
padding-right: 12px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-item.past i.fas.past,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical.past i.fas.past,
|
||||
.slide-menu-wrapper .slide-menu-item.active i.fas.active,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical.active i.fas.active,
|
||||
.slide-menu-wrapper .slide-menu-item.future i.far.future,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical.future i.far.future,
|
||||
.slide-menu-wrapper .slide-menu-item.past svg.svg-inline--fa.past,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical.past svg.svg-inline--fa.past,
|
||||
.slide-menu-wrapper .slide-menu-item.active svg.svg-inline--fa.active,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical.active svg.svg-inline--fa.active,
|
||||
.slide-menu-wrapper .slide-menu-item.future svg.svg-inline--fa.future,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical.future svg.svg-inline--fa.future {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-item.past i.fas.past,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical.past i.fas.past,
|
||||
.slide-menu-wrapper .slide-menu-item.future i.far.future,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical.future i.far.future,
|
||||
.slide-menu-wrapper .slide-menu-item.past svg.svg-inline--fa.past,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical.past svg.svg-inline--fa.past,
|
||||
.slide-menu-wrapper .slide-menu-item.future svg.svg-inline--fa.future,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical.future svg.svg-inline--fa.future {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-item.active i.fas.active,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical.active i.fas.active,
|
||||
.slide-menu-wrapper .slide-menu-item.active svg.svg-inline--fa.active,
|
||||
.slide-menu-wrapper .slide-menu-item-vertical.active svg.svg-inline--fa.active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu--left {
|
||||
left: 0;
|
||||
-webkit-transform: translateX(-100%);
|
||||
-ms-transform: translateX(-100%);
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu--left.active {
|
||||
-webkit-transform: translateX(0);
|
||||
-ms-transform: translateX(0);
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu--right {
|
||||
right: 0;
|
||||
-webkit-transform: translateX(100%);
|
||||
-ms-transform: translateX(100%);
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu--right.active {
|
||||
-webkit-transform: translateX(0);
|
||||
-ms-transform: translateX(0);
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.slide-menu-wrapper {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
/*
|
||||
* Toolbar
|
||||
*/
|
||||
.slide-menu-wrapper .slide-menu-toolbar {
|
||||
height: 60px;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
display: table;
|
||||
table-layout: fixed; /* ensures equal width */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-bottom: solid 2px #666;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-toolbar > li {
|
||||
display: table-cell;
|
||||
line-height: 150%;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
color: #aaa;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-toolbar > li.toolbar-panel-button i,
|
||||
.slide-menu-wrapper
|
||||
.slide-menu-toolbar
|
||||
> li.toolbar-panel-button
|
||||
svg.svg-inline--fa {
|
||||
font-size: 1.7em;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-toolbar > li.active-toolbar-button {
|
||||
color: white;
|
||||
text-shadow: 0 1px black;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.slide-menu-toolbar > li.toolbar-panel-button:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.slide-menu-toolbar
|
||||
> li.toolbar-panel-button:hover
|
||||
span.slide-menu-toolbar-label,
|
||||
.slide-menu-wrapper
|
||||
.slide-menu-toolbar
|
||||
> li.active-toolbar-button
|
||||
span.slide-menu-toolbar-label {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/*
|
||||
* Panels
|
||||
*/
|
||||
.slide-menu-wrapper .slide-menu-panel {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
visibility: hidden;
|
||||
height: calc(100% - 60px);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-panel.active-menu-panel {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-panel h1,
|
||||
.slide-menu-wrapper .slide-menu-panel h2,
|
||||
.slide-menu-wrapper .slide-menu-panel h3,
|
||||
.slide-menu-wrapper .slide-menu-panel h4,
|
||||
.slide-menu-wrapper .slide-menu-panel h5,
|
||||
.slide-menu-wrapper .slide-menu-panel h6 {
|
||||
margin: 20px 0 10px 0;
|
||||
color: #fff;
|
||||
line-height: 1.2;
|
||||
letter-spacing: normal;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-panel h1 {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
.slide-menu-wrapper .slide-menu-panel h2 {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
.slide-menu-wrapper .slide-menu-panel h3 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
.slide-menu-wrapper .slide-menu-panel h4 {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
.slide-menu-wrapper .slide-menu-panel h5 {
|
||||
font-size: 1em;
|
||||
}
|
||||
.slide-menu-wrapper .slide-menu-panel h6 {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-panel p {
|
||||
margin: 10px 0 5px 0;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-panel a {
|
||||
color: #ccc;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-panel a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-item a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-custom-panel {
|
||||
width: calc(100% - 20px);
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-custom-panel .slide-menu-items {
|
||||
width: calc(100% + 20px);
|
||||
margin-left: -10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Theme and Transitions buttons
|
||||
*/
|
||||
|
||||
.slide-menu-wrapper div[data-panel='Themes'] li,
|
||||
.slide-menu-wrapper div[data-panel='Transitions'] li {
|
||||
display: block;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
color: #848484;
|
||||
}
|
||||
|
||||
/*
|
||||
* Menu controls
|
||||
*/
|
||||
.reveal .slide-menu-button {
|
||||
position: fixed;
|
||||
left: 30px;
|
||||
bottom: 30px;
|
||||
z-index: 30;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Menu overlay
|
||||
*/
|
||||
|
||||
.slide-menu-wrapper .slide-menu-overlay {
|
||||
position: fixed;
|
||||
z-index: 199;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
width: 0;
|
||||
height: 0;
|
||||
background-color: #000;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s, width 0s 0.3s, height 0s 0.3s;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-overlay.active {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hide menu for pdf printing
|
||||
*/
|
||||
body.print-pdf .slide-menu-wrapper .slide-menu,
|
||||
body.print-pdf .reveal .slide-menu-button,
|
||||
body.print-pdf .slide-menu-wrapper .slide-menu-overlay {
|
||||
display: none;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,9 +0,0 @@
|
||||
name: RevealMenu
|
||||
script: [menu.js, quarto-menu.js]
|
||||
stylesheet: [menu.css, quarto-menu.css]
|
||||
config:
|
||||
menu:
|
||||
side: "left"
|
||||
useTextContentForMissingTitles: true
|
||||
markers: false
|
||||
loadIcons: false
|
||||
@@ -1,68 +0,0 @@
|
||||
.slide-menu-wrapper .slide-tool-item {
|
||||
display: block;
|
||||
text-align: left;
|
||||
padding: 10px 18px;
|
||||
color: #aaa;
|
||||
cursor: pointer;
|
||||
border-top: solid 1px #555;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-tool-item a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-tool-item kbd {
|
||||
font-family: monospace;
|
||||
margin-right: 10px;
|
||||
padding: 3px 8px;
|
||||
color: inherit;
|
||||
border: 1px solid;
|
||||
border-radius: 5px;
|
||||
border-color: #555;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-toolbar > li.active-toolbar-button {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.reveal .slide-menu-button {
|
||||
left: 8px;
|
||||
bottom: 8px;
|
||||
}
|
||||
|
||||
.reveal .slide-menu-button .fas::before,
|
||||
.reveal .slide-chalkboard-buttons .fas::before,
|
||||
.slide-menu-wrapper .slide-menu-toolbar .fas::before {
|
||||
display: inline-block;
|
||||
height: 2.2rem;
|
||||
width: 2.2rem;
|
||||
content: "";
|
||||
vertical-align: -0.125em;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 2.2rem 2.2rem;
|
||||
}
|
||||
|
||||
.reveal .slide-chalkboard-buttons .fas::before {
|
||||
height: 1.45rem;
|
||||
width: 1.45rem;
|
||||
background-size: 1.45rem 1.45rem;
|
||||
vertical-align: 0.1em;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-toolbar .fas::before {
|
||||
height: 1.8rem;
|
||||
width: 1.8rem;
|
||||
background-size: 1.8rem 1.8rem;
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-toolbar .fa-images::before {
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-images" viewBox="0 0 16 16"><path d="M4.502 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/><path d="M14.002 13a2 2 0 0 1-2 2h-10a2 2 0 0 1-2-2V5A2 2 0 0 1 2 3a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v8a2 2 0 0 1-1.998 2zM14 2H4a1 1 0 0 0-1 1h9.002a2 2 0 0 1 2 2v7A1 1 0 0 0 15 11V3a1 1 0 0 0-1-1zM2.002 4a1 1 0 0 0-1 1v8l2.646-2.354a.5.5 0 0 1 .63-.062l2.66 1.773 3.71-3.71a.5.5 0 0 1 .577-.094l1.777 1.947V5a1 1 0 0 0-1-1h-10z"/></svg>');
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-toolbar .fa-gear::before {
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-gear" viewBox="0 0 16 16"><path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492zM5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0z"/><path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52l-.094-.319zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115l.094-.319z"/></svg>');
|
||||
}
|
||||
|
||||
.slide-menu-wrapper .slide-menu-toolbar .fa-times::before {
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-x" viewBox="0 0 16 16"><path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/></svg>');
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
window.revealMenuToolHandler = function (handler) {
|
||||
return function (event) {
|
||||
event.preventDefault();
|
||||
handler();
|
||||
Reveal.getPlugin("menu").closeMenu();
|
||||
};
|
||||
};
|
||||
|
||||
window.RevealMenuToolHandlers = {
|
||||
fullscreen: revealMenuToolHandler(function () {
|
||||
const element = document.documentElement;
|
||||
const requestMethod =
|
||||
element.requestFullscreen ||
|
||||
element.webkitRequestFullscreen ||
|
||||
element.webkitRequestFullScreen ||
|
||||
element.mozRequestFullScreen ||
|
||||
element.msRequestFullscreen;
|
||||
if (requestMethod) {
|
||||
requestMethod.apply(element);
|
||||
}
|
||||
}),
|
||||
speakerMode: revealMenuToolHandler(function () {
|
||||
Reveal.getPlugin("notes").open();
|
||||
}),
|
||||
keyboardHelp: revealMenuToolHandler(function () {
|
||||
Reveal.toggleHelp(true);
|
||||
}),
|
||||
overview: revealMenuToolHandler(function () {
|
||||
Reveal.toggleOverview(true);
|
||||
}),
|
||||
toggleChalkboard: revealMenuToolHandler(function () {
|
||||
RevealChalkboard.toggleChalkboard();
|
||||
}),
|
||||
toggleNotesCanvas: revealMenuToolHandler(function () {
|
||||
RevealChalkboard.toggleNotesCanvas();
|
||||
}),
|
||||
downloadDrawings: revealMenuToolHandler(function () {
|
||||
RevealChalkboard.download();
|
||||
}),
|
||||
togglePdfExport: revealMenuToolHandler(function () {
|
||||
PdfExport.togglePdfExport();
|
||||
}),
|
||||
};
|
||||
@@ -1,243 +0,0 @@
|
||||
/*!
|
||||
* Handles finding a text string anywhere in the slides and showing the next occurrence to the user
|
||||
* by navigatating to that slide and highlighting it.
|
||||
*
|
||||
* @author Jon Snyder <snyder.jon@gmail.com>, February 2013
|
||||
*/
|
||||
|
||||
const Plugin = () => {
|
||||
|
||||
// The reveal.js instance this plugin is attached to
|
||||
let deck;
|
||||
|
||||
let searchElement;
|
||||
let searchButton;
|
||||
let searchInput;
|
||||
|
||||
let matchedSlides;
|
||||
let currentMatchedIndex;
|
||||
let searchboxDirty;
|
||||
let hilitor;
|
||||
|
||||
function render() {
|
||||
|
||||
searchElement = document.createElement( 'div' );
|
||||
searchElement.classList.add( 'searchbox' );
|
||||
searchElement.style.position = 'absolute';
|
||||
searchElement.style.top = '10px';
|
||||
searchElement.style.right = '10px';
|
||||
searchElement.style.zIndex = 10;
|
||||
|
||||
//embedded base64 search icon Designed by Sketchdock - http://www.sketchdock.com/:
|
||||
searchElement.innerHTML = `<input type="search" class="searchinput" placeholder="Search..." style="vertical-align: top;"/>
|
||||
</span>`;
|
||||
|
||||
searchInput = searchElement.querySelector( '.searchinput' );
|
||||
searchInput.style.width = '240px';
|
||||
searchInput.style.fontSize = '14px';
|
||||
searchInput.style.padding = '4px 6px';
|
||||
searchInput.style.color = '#000';
|
||||
searchInput.style.background = '#fff';
|
||||
searchInput.style.borderRadius = '2px';
|
||||
searchInput.style.border = '0';
|
||||
searchInput.style.outline = '0';
|
||||
searchInput.style.boxShadow = '0 2px 18px rgba(0, 0, 0, 0.2)';
|
||||
searchInput.style['-webkit-appearance'] = 'none';
|
||||
|
||||
deck.getRevealElement().appendChild( searchElement );
|
||||
|
||||
// searchButton.addEventListener( 'click', function(event) {
|
||||
// doSearch();
|
||||
// }, false );
|
||||
|
||||
searchInput.addEventListener( 'keyup', function( event ) {
|
||||
switch (event.keyCode) {
|
||||
case 13:
|
||||
event.preventDefault();
|
||||
doSearch();
|
||||
searchboxDirty = false;
|
||||
break;
|
||||
default:
|
||||
searchboxDirty = true;
|
||||
}
|
||||
}, false );
|
||||
|
||||
closeSearch();
|
||||
|
||||
}
|
||||
|
||||
function openSearch() {
|
||||
if( !searchElement ) render();
|
||||
|
||||
searchElement.style.display = 'inline';
|
||||
searchInput.focus();
|
||||
searchInput.select();
|
||||
}
|
||||
|
||||
function closeSearch() {
|
||||
if( !searchElement ) render();
|
||||
|
||||
searchElement.style.display = 'none';
|
||||
if(hilitor) hilitor.remove();
|
||||
}
|
||||
|
||||
function toggleSearch() {
|
||||
if( !searchElement ) render();
|
||||
|
||||
if (searchElement.style.display !== 'inline') {
|
||||
openSearch();
|
||||
}
|
||||
else {
|
||||
closeSearch();
|
||||
}
|
||||
}
|
||||
|
||||
function doSearch() {
|
||||
//if there's been a change in the search term, perform a new search:
|
||||
if (searchboxDirty) {
|
||||
var searchstring = searchInput.value;
|
||||
|
||||
if (searchstring === '') {
|
||||
if(hilitor) hilitor.remove();
|
||||
matchedSlides = null;
|
||||
}
|
||||
else {
|
||||
//find the keyword amongst the slides
|
||||
hilitor = new Hilitor("slidecontent");
|
||||
matchedSlides = hilitor.apply(searchstring);
|
||||
currentMatchedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedSlides) {
|
||||
//navigate to the next slide that has the keyword, wrapping to the first if necessary
|
||||
if (matchedSlides.length && (matchedSlides.length <= currentMatchedIndex)) {
|
||||
currentMatchedIndex = 0;
|
||||
}
|
||||
if (matchedSlides.length > currentMatchedIndex) {
|
||||
deck.slide(matchedSlides[currentMatchedIndex].h, matchedSlides[currentMatchedIndex].v);
|
||||
currentMatchedIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Original JavaScript code by Chirp Internet: www.chirp.com.au
|
||||
// Please acknowledge use of this code by including this header.
|
||||
// 2/2013 jon: modified regex to display any match, not restricted to word boundaries.
|
||||
function Hilitor(id, tag) {
|
||||
|
||||
var targetNode = document.getElementById(id) || document.body;
|
||||
var hiliteTag = tag || "EM";
|
||||
var skipTags = new RegExp("^(?:" + hiliteTag + "|SCRIPT|FORM)$");
|
||||
var colors = ["#ff6", "#a0ffff", "#9f9", "#f99", "#f6f"];
|
||||
var wordColor = [];
|
||||
var colorIdx = 0;
|
||||
var matchRegex = "";
|
||||
var matchingSlides = [];
|
||||
|
||||
this.setRegex = function(input)
|
||||
{
|
||||
input = input.replace(/^[^\w]+|[^\w]+$/g, "").replace(/[^\w'-]+/g, "|");
|
||||
matchRegex = new RegExp("(" + input + ")","i");
|
||||
}
|
||||
|
||||
this.getRegex = function()
|
||||
{
|
||||
return matchRegex.toString().replace(/^\/\\b\(|\)\\b\/i$/g, "").replace(/\|/g, " ");
|
||||
}
|
||||
|
||||
// recursively apply word highlighting
|
||||
this.hiliteWords = function(node)
|
||||
{
|
||||
if(node == undefined || !node) return;
|
||||
if(!matchRegex) return;
|
||||
if(skipTags.test(node.nodeName)) return;
|
||||
|
||||
if(node.hasChildNodes()) {
|
||||
for(var i=0; i < node.childNodes.length; i++)
|
||||
this.hiliteWords(node.childNodes[i]);
|
||||
}
|
||||
if(node.nodeType == 3) { // NODE_TEXT
|
||||
var nv, regs;
|
||||
if((nv = node.nodeValue) && (regs = matchRegex.exec(nv))) {
|
||||
//find the slide's section element and save it in our list of matching slides
|
||||
var secnode = node;
|
||||
while (secnode != null && secnode.nodeName != 'SECTION') {
|
||||
secnode = secnode.parentNode;
|
||||
}
|
||||
|
||||
var slideIndex = deck.getIndices(secnode);
|
||||
var slidelen = matchingSlides.length;
|
||||
var alreadyAdded = false;
|
||||
for (var i=0; i < slidelen; i++) {
|
||||
if ( (matchingSlides[i].h === slideIndex.h) && (matchingSlides[i].v === slideIndex.v) ) {
|
||||
alreadyAdded = true;
|
||||
}
|
||||
}
|
||||
if (! alreadyAdded) {
|
||||
matchingSlides.push(slideIndex);
|
||||
}
|
||||
|
||||
if(!wordColor[regs[0].toLowerCase()]) {
|
||||
wordColor[regs[0].toLowerCase()] = colors[colorIdx++ % colors.length];
|
||||
}
|
||||
|
||||
var match = document.createElement(hiliteTag);
|
||||
match.appendChild(document.createTextNode(regs[0]));
|
||||
match.style.backgroundColor = wordColor[regs[0].toLowerCase()];
|
||||
match.style.fontStyle = "inherit";
|
||||
match.style.color = "#000";
|
||||
|
||||
var after = node.splitText(regs.index);
|
||||
after.nodeValue = after.nodeValue.substring(regs[0].length);
|
||||
node.parentNode.insertBefore(match, after);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// remove highlighting
|
||||
this.remove = function()
|
||||
{
|
||||
var arr = document.getElementsByTagName(hiliteTag);
|
||||
var el;
|
||||
while(arr.length && (el = arr[0])) {
|
||||
el.parentNode.replaceChild(el.firstChild, el);
|
||||
}
|
||||
};
|
||||
|
||||
// start highlighting at target node
|
||||
this.apply = function(input)
|
||||
{
|
||||
if(input == undefined || !input) return;
|
||||
this.remove();
|
||||
this.setRegex(input);
|
||||
this.hiliteWords(targetNode);
|
||||
return matchingSlides;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
id: 'search',
|
||||
|
||||
init: reveal => {
|
||||
|
||||
deck = reveal;
|
||||
deck.registerKeyboardShortcut( 'CTRL + Shift + F', 'Search' );
|
||||
|
||||
document.addEventListener( 'keydown', function( event ) {
|
||||
if( event.key == "F" && (event.ctrlKey || event.metaKey) ) { //Control+Shift+f
|
||||
event.preventDefault();
|
||||
toggleSearch();
|
||||
}
|
||||
}, false );
|
||||
|
||||
},
|
||||
|
||||
open: openSearch
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
export default Plugin;
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,264 +0,0 @@
|
||||
/*!
|
||||
* reveal.js Zoom plugin
|
||||
*/
|
||||
const Plugin = {
|
||||
|
||||
id: 'zoom',
|
||||
|
||||
init: function( reveal ) {
|
||||
|
||||
reveal.getRevealElement().addEventListener( 'mousedown', function( event ) {
|
||||
var defaultModifier = /Linux/.test( window.navigator.platform ) ? 'ctrl' : 'alt';
|
||||
|
||||
var modifier = ( reveal.getConfig().zoomKey ? reveal.getConfig().zoomKey : defaultModifier ) + 'Key';
|
||||
var zoomLevel = ( reveal.getConfig().zoomLevel ? reveal.getConfig().zoomLevel : 2 );
|
||||
|
||||
if( event[ modifier ] && !reveal.isOverview() ) {
|
||||
event.preventDefault();
|
||||
|
||||
zoom.to({
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
scale: zoomLevel,
|
||||
pan: false
|
||||
});
|
||||
}
|
||||
} );
|
||||
|
||||
},
|
||||
|
||||
destroy: () => {
|
||||
|
||||
zoom.reset();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export default () => Plugin;
|
||||
|
||||
/*!
|
||||
* zoom.js 0.3 (modified for use with reveal.js)
|
||||
* http://lab.hakim.se/zoom-js
|
||||
* MIT licensed
|
||||
*
|
||||
* Copyright (C) 2011-2014 Hakim El Hattab, http://hakim.se
|
||||
*/
|
||||
var zoom = (function(){
|
||||
|
||||
// The current zoom level (scale)
|
||||
var level = 1;
|
||||
|
||||
// The current mouse position, used for panning
|
||||
var mouseX = 0,
|
||||
mouseY = 0;
|
||||
|
||||
// Timeout before pan is activated
|
||||
var panEngageTimeout = -1,
|
||||
panUpdateInterval = -1;
|
||||
|
||||
// Check for transform support so that we can fallback otherwise
|
||||
var supportsTransforms = 'transform' in document.body.style;
|
||||
|
||||
if( supportsTransforms ) {
|
||||
// The easing that will be applied when we zoom in/out
|
||||
document.body.style.transition = 'transform 0.8s ease';
|
||||
}
|
||||
|
||||
// Zoom out if the user hits escape
|
||||
document.addEventListener( 'keyup', function( event ) {
|
||||
if( level !== 1 && event.keyCode === 27 ) {
|
||||
zoom.out();
|
||||
}
|
||||
} );
|
||||
|
||||
// Monitor mouse movement for panning
|
||||
document.addEventListener( 'mousemove', function( event ) {
|
||||
if( level !== 1 ) {
|
||||
mouseX = event.clientX;
|
||||
mouseY = event.clientY;
|
||||
}
|
||||
} );
|
||||
|
||||
/**
|
||||
* Applies the CSS required to zoom in, prefers the use of CSS3
|
||||
* transforms but falls back on zoom for IE.
|
||||
*
|
||||
* @param {Object} rect
|
||||
* @param {Number} scale
|
||||
*/
|
||||
function magnify( rect, scale ) {
|
||||
|
||||
var scrollOffset = getScrollOffset();
|
||||
|
||||
// Ensure a width/height is set
|
||||
rect.width = rect.width || 1;
|
||||
rect.height = rect.height || 1;
|
||||
|
||||
// Center the rect within the zoomed viewport
|
||||
rect.x -= ( window.innerWidth - ( rect.width * scale ) ) / 2;
|
||||
rect.y -= ( window.innerHeight - ( rect.height * scale ) ) / 2;
|
||||
|
||||
if( supportsTransforms ) {
|
||||
// Reset
|
||||
if( scale === 1 ) {
|
||||
document.body.style.transform = '';
|
||||
}
|
||||
// Scale
|
||||
else {
|
||||
var origin = scrollOffset.x +'px '+ scrollOffset.y +'px',
|
||||
transform = 'translate('+ -rect.x +'px,'+ -rect.y +'px) scale('+ scale +')';
|
||||
|
||||
document.body.style.transformOrigin = origin;
|
||||
document.body.style.transform = transform;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Reset
|
||||
if( scale === 1 ) {
|
||||
document.body.style.position = '';
|
||||
document.body.style.left = '';
|
||||
document.body.style.top = '';
|
||||
document.body.style.width = '';
|
||||
document.body.style.height = '';
|
||||
document.body.style.zoom = '';
|
||||
}
|
||||
// Scale
|
||||
else {
|
||||
document.body.style.position = 'relative';
|
||||
document.body.style.left = ( - ( scrollOffset.x + rect.x ) / scale ) + 'px';
|
||||
document.body.style.top = ( - ( scrollOffset.y + rect.y ) / scale ) + 'px';
|
||||
document.body.style.width = ( scale * 100 ) + '%';
|
||||
document.body.style.height = ( scale * 100 ) + '%';
|
||||
document.body.style.zoom = scale;
|
||||
}
|
||||
}
|
||||
|
||||
level = scale;
|
||||
|
||||
if( document.documentElement.classList ) {
|
||||
if( level !== 1 ) {
|
||||
document.documentElement.classList.add( 'zoomed' );
|
||||
}
|
||||
else {
|
||||
document.documentElement.classList.remove( 'zoomed' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pan the document when the mosue cursor approaches the edges
|
||||
* of the window.
|
||||
*/
|
||||
function pan() {
|
||||
var range = 0.12,
|
||||
rangeX = window.innerWidth * range,
|
||||
rangeY = window.innerHeight * range,
|
||||
scrollOffset = getScrollOffset();
|
||||
|
||||
// Up
|
||||
if( mouseY < rangeY ) {
|
||||
window.scroll( scrollOffset.x, scrollOffset.y - ( 1 - ( mouseY / rangeY ) ) * ( 14 / level ) );
|
||||
}
|
||||
// Down
|
||||
else if( mouseY > window.innerHeight - rangeY ) {
|
||||
window.scroll( scrollOffset.x, scrollOffset.y + ( 1 - ( window.innerHeight - mouseY ) / rangeY ) * ( 14 / level ) );
|
||||
}
|
||||
|
||||
// Left
|
||||
if( mouseX < rangeX ) {
|
||||
window.scroll( scrollOffset.x - ( 1 - ( mouseX / rangeX ) ) * ( 14 / level ), scrollOffset.y );
|
||||
}
|
||||
// Right
|
||||
else if( mouseX > window.innerWidth - rangeX ) {
|
||||
window.scroll( scrollOffset.x + ( 1 - ( window.innerWidth - mouseX ) / rangeX ) * ( 14 / level ), scrollOffset.y );
|
||||
}
|
||||
}
|
||||
|
||||
function getScrollOffset() {
|
||||
return {
|
||||
x: window.scrollX !== undefined ? window.scrollX : window.pageXOffset,
|
||||
y: window.scrollY !== undefined ? window.scrollY : window.pageYOffset
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Zooms in on either a rectangle or HTML element.
|
||||
*
|
||||
* @param {Object} options
|
||||
* - element: HTML element to zoom in on
|
||||
* OR
|
||||
* - x/y: coordinates in non-transformed space to zoom in on
|
||||
* - width/height: the portion of the screen to zoom in on
|
||||
* - scale: can be used instead of width/height to explicitly set scale
|
||||
*/
|
||||
to: function( options ) {
|
||||
|
||||
// Due to an implementation limitation we can't zoom in
|
||||
// to another element without zooming out first
|
||||
if( level !== 1 ) {
|
||||
zoom.out();
|
||||
}
|
||||
else {
|
||||
options.x = options.x || 0;
|
||||
options.y = options.y || 0;
|
||||
|
||||
// If an element is set, that takes precedence
|
||||
if( !!options.element ) {
|
||||
// Space around the zoomed in element to leave on screen
|
||||
var padding = 20;
|
||||
var bounds = options.element.getBoundingClientRect();
|
||||
|
||||
options.x = bounds.left - padding;
|
||||
options.y = bounds.top - padding;
|
||||
options.width = bounds.width + ( padding * 2 );
|
||||
options.height = bounds.height + ( padding * 2 );
|
||||
}
|
||||
|
||||
// If width/height values are set, calculate scale from those values
|
||||
if( options.width !== undefined && options.height !== undefined ) {
|
||||
options.scale = Math.max( Math.min( window.innerWidth / options.width, window.innerHeight / options.height ), 1 );
|
||||
}
|
||||
|
||||
if( options.scale > 1 ) {
|
||||
options.x *= options.scale;
|
||||
options.y *= options.scale;
|
||||
|
||||
magnify( options, options.scale );
|
||||
|
||||
if( options.pan !== false ) {
|
||||
|
||||
// Wait with engaging panning as it may conflict with the
|
||||
// zoom transition
|
||||
panEngageTimeout = setTimeout( function() {
|
||||
panUpdateInterval = setInterval( pan, 1000 / 60 );
|
||||
}, 800 );
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets the document zoom state to its default.
|
||||
*/
|
||||
out: function() {
|
||||
clearTimeout( panEngageTimeout );
|
||||
clearInterval( panUpdateInterval );
|
||||
|
||||
magnify( { x: 0, y: 0 }, 1 );
|
||||
|
||||
level = 1;
|
||||
},
|
||||
|
||||
// Alias
|
||||
magnify: function( options ) { this.to( options ) },
|
||||
reset: function() { this.out() },
|
||||
|
||||
zoomLevel: function() {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user