mirror of
https://github.com/esphome/esphome-docs.git
synced 2025-11-18 06:34:33 +01:00
Add base files
This commit is contained in:
parent
77ab90f719
commit
349c57f9ce
31
.gitignore
vendored
31
.gitignore
vendored
@ -1,18 +1,13 @@
|
||||
_build
|
||||
.DS_Store
|
||||
|
||||
.python-version
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
venv
|
||||
|
||||
.vscode
|
||||
*.DS_Store
|
||||
/.idea/
|
||||
|
||||
_pagefind/
|
||||
|
||||
# Vim
|
||||
*.swp
|
||||
public/
|
||||
esphome-docs
|
||||
.hugo_build.lock
|
||||
.idea
|
||||
/node_modules/
|
||||
/package.json
|
||||
/package-lock.json
|
||||
/pagefind/
|
||||
/data/repo.yaml
|
||||
/data/anchors.json
|
||||
/data/automations
|
||||
my-secrets
|
||||
resources/_gen
|
||||
|
||||
91
Makefile
91
Makefile
@ -1,66 +1,45 @@
|
||||
ESPHOME_PATH = ../esphome
|
||||
ESPHOME_REF = 2025.5.2
|
||||
PAGEFIND_VERSION=1.1.1
|
||||
PAGEFIND=pagefind
|
||||
NET_PAGEFIND=../pagefindbin/pagefind
|
||||
.PHONY: html clean live-html automations check-links anchors production convert-from-rst directories
|
||||
|
||||
.PHONY: pagefind build-html html html-strict cleanhtml deploy help live-html live-pagefind Makefile netlify netlify-dependencies svg2png copy-svg2png minify
|
||||
export HUGO_PARAMS_COMMIT_HASH=$(shell git rev-parse --short HEAD)
|
||||
export HUGO_PARAMS_COMMIT_TITLE=$(shell git log -1 --pretty=%s)
|
||||
export HUGO_PARAMS_COMMIT_DATE=$(shell git log -1 --date=format-local:'%Y-%m-%d %H:%M:%S UTC' --pretty=%cd)
|
||||
export HUGO_PARAMS_BRANCH=$(shell git branch --show-current)
|
||||
|
||||
html: pagefind
|
||||
sphinx-build -M html . _build -j auto -n $(O) -Dhtml_extra_path=_redirects,_pagefind
|
||||
production: repo-data anchors
|
||||
hugo --minify
|
||||
npx pagefind
|
||||
hugo --minify
|
||||
|
||||
pagefind:
|
||||
sphinx-build -M html . _build -j auto -n $(O)
|
||||
mkdir -p _pagefind/pagefind
|
||||
${PAGEFIND}
|
||||
directories:
|
||||
mkdir -p data public pagefind content static
|
||||
npx pagefind -s pagefind-bootstrap
|
||||
|
||||
live-html: pagefind
|
||||
sphinx-autobuild . _build -j auto -n $(O) --host 0.0.0.0 -Dhtml_extra_path=_redirects,_pagefind
|
||||
check-links: repo-data anchors
|
||||
hugo --environment production
|
||||
|
||||
html-strict:
|
||||
sphinx-build -M html . _build -W -j auto -n $(O)
|
||||
anchors: directories
|
||||
hugo --environment anchors
|
||||
python3 tools/md_anchors.py
|
||||
|
||||
minify:
|
||||
minify _static/webserver-v1.js > _static/webserver-v1.min.js
|
||||
minify _static/webserver-v1.css > _static/webserver-v1.min.css
|
||||
repo-data: directories
|
||||
mkdir -p data/automations
|
||||
echo "url: `git config --get remote.origin.url`" > data/repo.yaml
|
||||
echo "branch: `git branch --show-current`" >> data/repo.yaml
|
||||
curl -s -S https://data.esphome.io/release/automations.json | tools/collate_automations.sh > data/automations/current.json
|
||||
curl -s -S https://data.esphome.io/beta/automations.json | tools/collate_automations.sh > data/automations/beta.json
|
||||
curl -s -S https://data.esphome.io/dev/automations.json | tools/collate_automations.sh > data/automations/next.json
|
||||
|
||||
cleanhtml:
|
||||
rm -rf "_build/html/*"
|
||||
|
||||
svg2png:
|
||||
python3 svg2png.py
|
||||
|
||||
help:
|
||||
sphinx-build -M help . _build $(O)
|
||||
|
||||
net-html:
|
||||
sed -i 's@{{API_DOCS_URL}}@'"${API_DOCS_URL}"'@' _redirects
|
||||
sphinx-build -M html . _build -j auto -n $(O)
|
||||
mkdir -p _pagefind/pagefind
|
||||
${NET_PAGEFIND}
|
||||
sphinx-build -M html . _build -j auto -n $(O) -Dhtml_extra_path=_redirects,_pagefind
|
||||
|
||||
pagefind-binary:
|
||||
mkdir -p ../pagefindbin
|
||||
curl -o pagefind.tar.gz https://github.com/CloudCannon/pagefind/releases/download/v$(PAGEFIND_VERSION)/pagefind-v$(PAGEFIND_VERSION)-x86_64-unknown-linux-musl.tar.gz -L
|
||||
tar xzf pagefind.tar.gz
|
||||
rm pagefind.tar.gz
|
||||
mv pagefind ${NET_PAGEFIND}
|
||||
|
||||
|
||||
copy-svg2png:
|
||||
cp svg2png/*.png _build/html/_images/
|
||||
|
||||
netlify: pagefind-binary net-html copy-svg2png
|
||||
|
||||
lint: html-strict
|
||||
python3 lint.py
|
||||
live-html: repo-data anchors
|
||||
npx pagefind
|
||||
env | grep HUGO
|
||||
hugo server --bind 0.0.0.0
|
||||
|
||||
clean:
|
||||
rm -rf _pagefind/
|
||||
sphinx-build -M clean . _build $(O)
|
||||
rm -rf "public/*"
|
||||
rm -rf "pagefind/*"
|
||||
rm -rf data/automations/
|
||||
rm -rf data/repo.yaml
|
||||
hugo mod clean
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
sphinx-build -M $@ . _build $(O)
|
||||
convert-from-rst:
|
||||
python3 tools/convert_rst_to_md.py ./esphome-docs .
|
||||
|
||||
15
pagefind-bootstrap/components/index.html
Normal file
15
pagefind-bootstrap/components/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-container">
|
||||
<main data-pagefind-body>
|
||||
<h1>Dummy page</h1>
|
||||
<p>
|
||||
This file exists just to feed Pagefind before the site is built, in order to populate the pagefind directory
|
||||
</p>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,10 +1,8 @@
|
||||
site: _build/html
|
||||
output_path: _pagefind/pagefind
|
||||
site: public
|
||||
output_path: pagefind
|
||||
exclude_selectors:
|
||||
- "a.headerlink"
|
||||
- ".toctree-wrapper"
|
||||
- ".sphinxsidebar"
|
||||
- ".breadcrumbs"
|
||||
- "pre"
|
||||
glob: "{components,cookbook,guides,projects,web-api}/**/*.html"
|
||||
root_selector: div[role=main]
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
sphinx==7.1.2
|
||||
sphinx-autobuild==2021.3.14
|
||||
sphinx-tabs==3.4.7
|
||||
sphinx-toolbox==3.8.0
|
||||
sphinx-copybutton==0.5.2
|
||||
605
themes/esphome-theme/assets/css/chroma.css
Normal file
605
themes/esphome-theme/assets/css/chroma.css
Normal file
@ -0,0 +1,605 @@
|
||||
/* Background */ .bg { color:#272822;background-color:#fafafa; }
|
||||
/* PreWrapper */ .chroma { color:#272822;background-color:#fafafa; }
|
||||
/* Other */ .chroma .x { }
|
||||
/* Error */ .chroma .err { color:#960050;background-color:#1e0010 }
|
||||
/* CodeLine */ .chroma .cl { }
|
||||
/* LineLink */ .chroma .lnlinks { outline:none;text-decoration:none;color:inherit }
|
||||
/* LineTableTD */ .chroma .lntd { vertical-align:top;padding:0;margin:0;border:0; }
|
||||
/* LineTable */ .chroma .lntable { border-spacing:0;padding:0;margin:0;border:0; }
|
||||
/* LineHighlight */ .chroma .hl { background-color:#e1e1e1 }
|
||||
/* LineNumbersTable */ .chroma .lnt { white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f }
|
||||
/* LineNumbers */ .chroma .ln { white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f }
|
||||
/* Line */ .chroma .line { display:flex; }
|
||||
/* Keyword */ .chroma .k { color:#00a8c8 }
|
||||
/* KeywordConstant */ .chroma .kc { color:#00a8c8 }
|
||||
/* KeywordDeclaration */ .chroma .kd { color:#00a8c8 }
|
||||
/* KeywordNamespace */ .chroma .kn { color:#f92672 }
|
||||
/* KeywordPseudo */ .chroma .kp { color:#00a8c8 }
|
||||
/* KeywordReserved */ .chroma .kr { color:#00a8c8 }
|
||||
/* KeywordType */ .chroma .kt { color:#00a8c8 }
|
||||
/* Name */ .chroma .n { color:#111 }
|
||||
/* NameAttribute */ .chroma .na { color:#75af00 }
|
||||
/* NameBuiltin */ .chroma .nb { color:#111 }
|
||||
/* NameBuiltinPseudo */ .chroma .bp { color:#111 }
|
||||
/* NameClass */ .chroma .nc { color:#75af00 }
|
||||
/* NameConstant */ .chroma .no { color:#00a8c8 }
|
||||
/* NameDecorator */ .chroma .nd { color:#75af00 }
|
||||
/* NameEntity */ .chroma .ni { color:#111 }
|
||||
/* NameException */ .chroma .ne { color:#75af00 }
|
||||
/* NameFunction */ .chroma .nf { color:#75af00 }
|
||||
/* NameFunctionMagic */ .chroma .fm { color:#111 }
|
||||
/* NameLabel */ .chroma .nl { color:#111 }
|
||||
/* NameNamespace */ .chroma .nn { color:#111 }
|
||||
/* NameOther */ .chroma .nx { color:#75af00 }
|
||||
/* NameProperty */ .chroma .py { color:#111 }
|
||||
/* NameTag */ .chroma .nt { color:#f92672 }
|
||||
/* NameVariable */ .chroma .nv { color:#111 }
|
||||
/* NameVariableClass */ .chroma .vc { color:#111 }
|
||||
/* NameVariableGlobal */ .chroma .vg { color:#111 }
|
||||
/* NameVariableInstance */ .chroma .vi { color:#111 }
|
||||
/* NameVariableMagic */ .chroma .vm { color:#111 }
|
||||
/* Literal */ .chroma .l { color:#ae81ff }
|
||||
/* LiteralDate */ .chroma .ld { color:#d88200 }
|
||||
/* LiteralString */ .chroma .s { color:#d88200 }
|
||||
/* LiteralStringAffix */ .chroma .sa { color:#d88200 }
|
||||
/* LiteralStringBacktick */ .chroma .sb { color:#d88200 }
|
||||
/* LiteralStringChar */ .chroma .sc { color:#d88200 }
|
||||
/* LiteralStringDelimiter */ .chroma .dl { color:#d88200 }
|
||||
/* LiteralStringDoc */ .chroma .sd { color:#d88200 }
|
||||
/* LiteralStringDouble */ .chroma .s2 { color:#d88200 }
|
||||
/* LiteralStringEscape */ .chroma .se { color:#8045ff }
|
||||
/* LiteralStringHeredoc */ .chroma .sh { color:#d88200 }
|
||||
/* LiteralStringInterpol */ .chroma .si { color:#d88200 }
|
||||
/* LiteralStringOther */ .chroma .sx { color:#d88200 }
|
||||
/* LiteralStringRegex */ .chroma .sr { color:#d88200 }
|
||||
/* LiteralStringSingle */ .chroma .s1 { color:#d88200 }
|
||||
/* LiteralStringSymbol */ .chroma .ss { color:#d88200 }
|
||||
/* LiteralNumber */ .chroma .m { color:#ae81ff }
|
||||
/* LiteralNumberBin */ .chroma .mb { color:#ae81ff }
|
||||
/* LiteralNumberFloat */ .chroma .mf { color:#ae81ff }
|
||||
/* LiteralNumberHex */ .chroma .mh { color:#ae81ff }
|
||||
/* LiteralNumberInteger */ .chroma .mi { color:#ae81ff }
|
||||
/* LiteralNumberIntegerLong */ .chroma .il { color:#ae81ff }
|
||||
/* LiteralNumberOct */ .chroma .mo { color:#ae81ff }
|
||||
/* Operator */ .chroma .o { color:#f92672 }
|
||||
/* OperatorWord */ .chroma .ow { color:#f92672 }
|
||||
/* Punctuation */ .chroma .p { color:#111 }
|
||||
/* Comment */ .chroma .c { color:#75715e }
|
||||
/* CommentHashbang */ .chroma .ch { color:#75715e }
|
||||
/* CommentMultiline */ .chroma .cm { color:#75715e }
|
||||
/* CommentSingle */ .chroma .c1 { color:#75715e }
|
||||
/* CommentSpecial */ .chroma .cs { color:#75715e }
|
||||
/* CommentPreproc */ .chroma .cp { color:#75715e }
|
||||
/* CommentPreprocFile */ .chroma .cpf { color:#75715e }
|
||||
/* Generic */ .chroma .g { }
|
||||
/* GenericDeleted */ .chroma .gd { }
|
||||
/* GenericEmph */ .chroma .ge { font-style:italic }
|
||||
/* GenericError */ .chroma .gr { }
|
||||
/* GenericHeading */ .chroma .gh { }
|
||||
/* GenericInserted */ .chroma .gi { }
|
||||
/* GenericOutput */ .chroma .go { }
|
||||
/* GenericPrompt */ .chroma .gp { }
|
||||
/* GenericStrong */ .chroma .gs { font-weight:bold }
|
||||
/* GenericSubheading */ .chroma .gu { }
|
||||
/* GenericTraceback */ .chroma .gt { }
|
||||
/* GenericUnderline */ .chroma .gl { }
|
||||
/* TextWhitespace */ .chroma .w { }
|
||||
|
||||
|
||||
|
||||
[data-theme="dark"] {
|
||||
/* Background */
|
||||
|
||||
.bg {
|
||||
color: #f8f8f2;
|
||||
background-color: #272822;
|
||||
}
|
||||
|
||||
/* PreWrapper */
|
||||
|
||||
.chroma {
|
||||
color: #f8f8f2;
|
||||
background-color: #272822;
|
||||
}
|
||||
|
||||
/* Other */
|
||||
|
||||
.chroma .x {
|
||||
}
|
||||
|
||||
/* Error */
|
||||
|
||||
.chroma .err {
|
||||
color: #960050;
|
||||
background-color: #1e0010
|
||||
}
|
||||
|
||||
/* CodeLine */
|
||||
|
||||
.chroma .cl {
|
||||
}
|
||||
|
||||
/* LineLink */
|
||||
|
||||
.chroma .lnlinks {
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
color: inherit
|
||||
}
|
||||
|
||||
/* LineTableTD */
|
||||
|
||||
.chroma .lntd {
|
||||
vertical-align: top;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* LineTable */
|
||||
|
||||
.chroma .lntable {
|
||||
border-spacing: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* LineHighlight */
|
||||
|
||||
.chroma .hl {
|
||||
background-color: #3c3d38
|
||||
}
|
||||
|
||||
/* LineNumbersTable */
|
||||
|
||||
.chroma .lnt {
|
||||
white-space: pre;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
margin-right: 0.4em;
|
||||
padding: 0 0.4em 0 0.4em;
|
||||
color: #7f7f7f
|
||||
}
|
||||
|
||||
/* LineNumbers */
|
||||
|
||||
.chroma .ln {
|
||||
white-space: pre;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
margin-right: 0.4em;
|
||||
padding: 0 0.4em 0 0.4em;
|
||||
color: #7f7f7f
|
||||
}
|
||||
|
||||
/* Line */
|
||||
|
||||
.chroma .line {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Keyword */
|
||||
|
||||
.chroma .k {
|
||||
color: #66d9ef
|
||||
}
|
||||
|
||||
/* KeywordConstant */
|
||||
|
||||
.chroma .kc {
|
||||
color: #66d9ef
|
||||
}
|
||||
|
||||
/* KeywordDeclaration */
|
||||
|
||||
.chroma .kd {
|
||||
color: #66d9ef
|
||||
}
|
||||
|
||||
/* KeywordNamespace */
|
||||
|
||||
.chroma .kn {
|
||||
color: #f92672
|
||||
}
|
||||
|
||||
/* KeywordPseudo */
|
||||
|
||||
.chroma .kp {
|
||||
color: #66d9ef
|
||||
}
|
||||
|
||||
/* KeywordReserved */
|
||||
|
||||
.chroma .kr {
|
||||
color: #66d9ef
|
||||
}
|
||||
|
||||
/* KeywordType */
|
||||
|
||||
.chroma .kt {
|
||||
color: #66d9ef
|
||||
}
|
||||
|
||||
/* Name */
|
||||
|
||||
.chroma .n {
|
||||
}
|
||||
|
||||
/* NameAttribute */
|
||||
|
||||
.chroma .na {
|
||||
color: #a6e22e
|
||||
}
|
||||
|
||||
/* NameBuiltin */
|
||||
|
||||
.chroma .nb {
|
||||
}
|
||||
|
||||
/* NameBuiltinPseudo */
|
||||
|
||||
.chroma .bp {
|
||||
}
|
||||
|
||||
/* NameClass */
|
||||
|
||||
.chroma .nc {
|
||||
color: #a6e22e
|
||||
}
|
||||
|
||||
/* NameConstant */
|
||||
|
||||
.chroma .no {
|
||||
color: #66d9ef
|
||||
}
|
||||
|
||||
/* NameDecorator */
|
||||
|
||||
.chroma .nd {
|
||||
color: #a6e22e
|
||||
}
|
||||
|
||||
/* NameEntity */
|
||||
|
||||
.chroma .ni {
|
||||
}
|
||||
|
||||
/* NameException */
|
||||
|
||||
.chroma .ne {
|
||||
color: #a6e22e
|
||||
}
|
||||
|
||||
/* NameFunction */
|
||||
|
||||
.chroma .nf {
|
||||
color: #a6e22e
|
||||
}
|
||||
|
||||
/* NameFunctionMagic */
|
||||
|
||||
.chroma .fm {
|
||||
}
|
||||
|
||||
/* NameLabel */
|
||||
|
||||
.chroma .nl {
|
||||
}
|
||||
|
||||
/* NameNamespace */
|
||||
|
||||
.chroma .nn {
|
||||
}
|
||||
|
||||
/* NameOther */
|
||||
|
||||
.chroma .nx {
|
||||
color: #a6e22e
|
||||
}
|
||||
|
||||
/* NameProperty */
|
||||
|
||||
.chroma .py {
|
||||
}
|
||||
|
||||
/* NameTag */
|
||||
|
||||
.chroma .nt {
|
||||
color: #f92672
|
||||
}
|
||||
|
||||
/* NameVariable */
|
||||
|
||||
.chroma .nv {
|
||||
}
|
||||
|
||||
/* NameVariableClass */
|
||||
|
||||
.chroma .vc {
|
||||
}
|
||||
|
||||
/* NameVariableGlobal */
|
||||
|
||||
.chroma .vg {
|
||||
}
|
||||
|
||||
/* NameVariableInstance */
|
||||
|
||||
.chroma .vi {
|
||||
}
|
||||
|
||||
/* NameVariableMagic */
|
||||
|
||||
.chroma .vm {
|
||||
}
|
||||
|
||||
/* Literal */
|
||||
|
||||
.chroma .l {
|
||||
color: #ae81ff
|
||||
}
|
||||
|
||||
/* LiteralDate */
|
||||
|
||||
.chroma .ld {
|
||||
color: #e6db74
|
||||
}
|
||||
|
||||
/* LiteralString */
|
||||
|
||||
.chroma .s {
|
||||
color: #e6db74
|
||||
}
|
||||
|
||||
/* LiteralStringAffix */
|
||||
|
||||
.chroma .sa {
|
||||
color: #e6db74
|
||||
}
|
||||
|
||||
/* LiteralStringBacktick */
|
||||
|
||||
.chroma .sb {
|
||||
color: #e6db74
|
||||
}
|
||||
|
||||
/* LiteralStringChar */
|
||||
|
||||
.chroma .sc {
|
||||
color: #e6db74
|
||||
}
|
||||
|
||||
/* LiteralStringDelimiter */
|
||||
|
||||
.chroma .dl {
|
||||
color: #e6db74
|
||||
}
|
||||
|
||||
/* LiteralStringDoc */
|
||||
|
||||
.chroma .sd {
|
||||
color: #e6db74
|
||||
}
|
||||
|
||||
/* LiteralStringDouble */
|
||||
|
||||
.chroma .s2 {
|
||||
color: #e6db74
|
||||
}
|
||||
|
||||
/* LiteralStringEscape */
|
||||
|
||||
.chroma .se {
|
||||
color: #ae81ff
|
||||
}
|
||||
|
||||
/* LiteralStringHeredoc */
|
||||
|
||||
.chroma .sh {
|
||||
color: #e6db74
|
||||
}
|
||||
|
||||
/* LiteralStringInterpol */
|
||||
|
||||
.chroma .si {
|
||||
color: #e6db74
|
||||
}
|
||||
|
||||
/* LiteralStringOther */
|
||||
|
||||
.chroma .sx {
|
||||
color: #e6db74
|
||||
}
|
||||
|
||||
/* LiteralStringRegex */
|
||||
|
||||
.chroma .sr {
|
||||
color: #e6db74
|
||||
}
|
||||
|
||||
/* LiteralStringSingle */
|
||||
|
||||
.chroma .s1 {
|
||||
color: #e6db74
|
||||
}
|
||||
|
||||
/* LiteralStringSymbol */
|
||||
|
||||
.chroma .ss {
|
||||
color: #e6db74
|
||||
}
|
||||
|
||||
/* LiteralNumber */
|
||||
|
||||
.chroma .m {
|
||||
color: #ae81ff
|
||||
}
|
||||
|
||||
/* LiteralNumberBin */
|
||||
|
||||
.chroma .mb {
|
||||
color: #ae81ff
|
||||
}
|
||||
|
||||
/* LiteralNumberFloat */
|
||||
|
||||
.chroma .mf {
|
||||
color: #ae81ff
|
||||
}
|
||||
|
||||
/* LiteralNumberHex */
|
||||
|
||||
.chroma .mh {
|
||||
color: #ae81ff
|
||||
}
|
||||
|
||||
/* LiteralNumberInteger */
|
||||
|
||||
.chroma .mi {
|
||||
color: #ae81ff
|
||||
}
|
||||
|
||||
/* LiteralNumberIntegerLong */
|
||||
|
||||
.chroma .il {
|
||||
color: #ae81ff
|
||||
}
|
||||
|
||||
/* LiteralNumberOct */
|
||||
|
||||
.chroma .mo {
|
||||
color: #ae81ff
|
||||
}
|
||||
|
||||
/* Operator */
|
||||
|
||||
.chroma .o {
|
||||
color: #f92672
|
||||
}
|
||||
|
||||
/* OperatorWord */
|
||||
|
||||
.chroma .ow {
|
||||
color: #f92672
|
||||
}
|
||||
|
||||
/* Punctuation */
|
||||
|
||||
.chroma .p {
|
||||
color: #f8f8f2
|
||||
}
|
||||
|
||||
/* Comment */
|
||||
|
||||
.chroma .c {
|
||||
color: #75715e
|
||||
}
|
||||
|
||||
/* CommentHashbang */
|
||||
|
||||
.chroma .ch {
|
||||
color: #75715e
|
||||
}
|
||||
|
||||
/* CommentMultiline */
|
||||
|
||||
.chroma .cm {
|
||||
color: #75715e
|
||||
}
|
||||
|
||||
/* CommentSingle */
|
||||
|
||||
.chroma .c1 {
|
||||
color: #75715e
|
||||
}
|
||||
|
||||
/* CommentSpecial */
|
||||
|
||||
.chroma .cs {
|
||||
color: #75715e
|
||||
}
|
||||
|
||||
/* CommentPreproc */
|
||||
|
||||
.chroma .cp {
|
||||
color: #75715e
|
||||
}
|
||||
|
||||
/* CommentPreprocFile */
|
||||
|
||||
.chroma .cpf {
|
||||
color: #75715e
|
||||
}
|
||||
|
||||
/* Generic */
|
||||
|
||||
.chroma .g {
|
||||
}
|
||||
|
||||
/* GenericDeleted */
|
||||
|
||||
.chroma .gd {
|
||||
color: #f92672
|
||||
}
|
||||
|
||||
/* GenericEmph */
|
||||
|
||||
.chroma .ge {
|
||||
font-style: italic
|
||||
}
|
||||
|
||||
/* GenericError */
|
||||
|
||||
.chroma .gr {
|
||||
}
|
||||
|
||||
/* GenericHeading */
|
||||
|
||||
.chroma .gh {
|
||||
}
|
||||
|
||||
/* GenericInserted */
|
||||
|
||||
.chroma .gi {
|
||||
color: #a6e22e
|
||||
}
|
||||
|
||||
/* GenericOutput */
|
||||
|
||||
.chroma .go {
|
||||
}
|
||||
|
||||
/* GenericPrompt */
|
||||
|
||||
.chroma .gp {
|
||||
}
|
||||
|
||||
/* GenericStrong */
|
||||
|
||||
.chroma .gs {
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
/* GenericSubheading */
|
||||
|
||||
.chroma .gu {
|
||||
color: #75715e
|
||||
}
|
||||
|
||||
/* GenericTraceback */
|
||||
|
||||
.chroma .gt {
|
||||
}
|
||||
|
||||
/* GenericUnderline */
|
||||
|
||||
.chroma .gl {
|
||||
}
|
||||
|
||||
/* TextWhitespace */
|
||||
|
||||
.chroma .w {
|
||||
}
|
||||
|
||||
}
|
||||
1506
themes/esphome-theme/assets/css/main.css
Normal file
1506
themes/esphome-theme/assets/css/main.css
Normal file
File diff suppressed because it is too large
Load Diff
104
themes/esphome-theme/assets/css/tables.css
Normal file
104
themes/esphome-theme/assets/css/tables.css
Normal file
@ -0,0 +1,104 @@
|
||||
/* Table color variables */
|
||||
:root {
|
||||
--table-border-color: #e0e0e0;
|
||||
--table-header-bg: #00979d;
|
||||
--table-header-text: #fff;
|
||||
--table-code-bg: rgba(0, 0, 0, 0.05);
|
||||
--table-row-alt-bg: #f8f8f8;
|
||||
--table-hover-bg: rgba(0, 151, 157, 0.05);
|
||||
}
|
||||
[data-theme="dark"] {
|
||||
--table-border-color: #333b3d;
|
||||
--table-header-bg: #1abfc6;
|
||||
--table-header-text: #111;
|
||||
--table-code-bg: rgba(255,255,255,0.08);
|
||||
--table-row-alt-bg: #232728;
|
||||
--table-hover-bg: rgba(26, 191, 198, 0.08);
|
||||
}
|
||||
|
||||
/* Table styling for ESPHome documentation */
|
||||
table {
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
border-collapse: collapse;
|
||||
margin: 1.5em 0;
|
||||
font-size: 0.9em;
|
||||
border: 1px solid var(--table-border-color);
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
|
||||
/* Basic cell styling */
|
||||
table td,
|
||||
table th {
|
||||
padding: 10px 15px;
|
||||
border: 1px solid var(--table-border-color);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* All table headers should have this styling */
|
||||
table th {
|
||||
background-color: var(--table-header-bg);
|
||||
color: var(--table-header-text);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* For Markdown tables where the first row contains th elements */
|
||||
table tr:first-child th {
|
||||
background-color: var(--table-header-bg);
|
||||
color: var(--table-header-text);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Override code styling within table headers */
|
||||
table th code,
|
||||
table tr:first-child th code {
|
||||
background-color: transparent;
|
||||
color: var(--table-header-text);
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* Style for code in table cells */
|
||||
table td code {
|
||||
background-color: var(--table-code-bg);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Alternating row colors for better readability */
|
||||
table tr:nth-child(even) {
|
||||
background-color: var(--table-row-alt-bg);
|
||||
}
|
||||
|
||||
/* But header rows should always have the header background */
|
||||
table tr:nth-child(even) th {
|
||||
background-color: var(--table-header-bg);
|
||||
}
|
||||
|
||||
/* Hover effect */
|
||||
table tr:hover td {
|
||||
background-color: var(--table-hover-bg);
|
||||
}
|
||||
|
||||
/* Style for centered tables */
|
||||
table th[align="center"],
|
||||
table td[align="center"] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Style for right-aligned tables */
|
||||
table th[align="right"],
|
||||
table td[align="right"] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Responsive tables for smaller screens */
|
||||
@media (max-width: 600px) {
|
||||
table {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
3
themes/esphome-theme/assets/js/lazysizes.min.js
vendored
Normal file
3
themes/esphome-theme/assets/js/lazysizes.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
217
themes/esphome-theme/assets/js/main.js
Normal file
217
themes/esphome-theme/assets/js/main.js
Normal file
@ -0,0 +1,217 @@
|
||||
function trapScroll(el) {
|
||||
el.addEventListener('wheel', (e) => {
|
||||
const scrollTop = el.scrollTop;
|
||||
const scrollHeight = el.scrollHeight;
|
||||
const offsetHeight = el.offsetHeight;
|
||||
const delta = e.deltaY;
|
||||
|
||||
const atTop = scrollTop === 0;
|
||||
const atBottom = scrollTop + offsetHeight >= scrollHeight;
|
||||
|
||||
if ((atTop && delta < 0) || (atBottom && delta > 0)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}, {passive: false});
|
||||
}
|
||||
|
||||
function trapTouchScroll(el) {
|
||||
let startY = 0;
|
||||
|
||||
el.addEventListener('touchstart', (e) => {
|
||||
startY = e.touches[0].clientY;
|
||||
});
|
||||
|
||||
el.addEventListener('touchmove', (e) => {
|
||||
const scrollTop = el.scrollTop;
|
||||
const scrollHeight = el.scrollHeight;
|
||||
const offsetHeight = el.offsetHeight;
|
||||
const currentY = e.touches[0].clientY;
|
||||
const deltaY = currentY - startY;
|
||||
|
||||
const atTop = scrollTop === 0;
|
||||
const atBottom = scrollTop + offsetHeight >= scrollHeight;
|
||||
|
||||
if ((atTop && deltaY > 0) || (atBottom && deltaY < 0)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}, {passive: false});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
const scrollers = document.querySelectorAll('.scroll-trap');
|
||||
|
||||
for (let i = 0; i !== scrollers.length; i++) {
|
||||
trapScroll(scrollers[i]);
|
||||
trapTouchScroll(scrollers[i]);
|
||||
}
|
||||
|
||||
document.querySelectorAll('.copy-link').forEach(button => {
|
||||
button.addEventListener('click', async () => {
|
||||
const anchor = button.getAttribute('data-anchor');
|
||||
const url = `${window.location.origin}${window.location.pathname}#${anchor}`;
|
||||
await navigator.clipboard.writeText(url)
|
||||
// Remove the class if it’s already there (to restart the animation)
|
||||
button.classList.remove('spin-once');
|
||||
|
||||
// Trigger reflow to "restart" the animation
|
||||
void button.offsetWidth;
|
||||
|
||||
// Add the class to trigger the spin
|
||||
button.classList.add('spin-once');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Copy button functionality
|
||||
const copyButtons = document.querySelectorAll('.copy-button');
|
||||
copyButtons.forEach(button => {
|
||||
button.addEventListener('click', async () => {
|
||||
const codeBlock = button.closest('.code-block');
|
||||
const yamlContent = codeBlock.querySelector('.codeblock-content');
|
||||
const code = yamlContent.textContent;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
const feedback = button.querySelector(".copy-feedback");
|
||||
if (feedback) {
|
||||
feedback.textContent = "Copied!";
|
||||
}
|
||||
button.classList.add('copied');
|
||||
setTimeout(() => {
|
||||
button.classList.remove('copied');
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error('Failed to copy:', err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const scrollThreshold = 5; // Minimum scroll amount before triggering hide/show
|
||||
const navContainer = document.getElementById('nav-container');
|
||||
let scrollDelta = 0; // Track cumulative scroll amount
|
||||
let lastScrollTop = 0;
|
||||
|
||||
|
||||
function scroll_bar(newDelta) {
|
||||
let navHeight = navContainer.offsetHeight;
|
||||
// Remove the transition class when scrolling down for direct tracking
|
||||
navContainer.classList.remove('nav-scrolling-up');
|
||||
|
||||
// Increase the scroll delta by the amount scrolled - start immediately from top
|
||||
scrollDelta += newDelta;
|
||||
|
||||
// Cap the scroll delta at the nav height
|
||||
scrollDelta = Math.min(scrollDelta, navHeight);
|
||||
|
||||
// Apply the transform
|
||||
navContainer.style.transform = `translateY(-${scrollDelta}px)`;
|
||||
|
||||
// If fully hidden, add the nav-hidden class
|
||||
if (scrollDelta >= navHeight) {
|
||||
navContainer.classList.add('nav-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Header scroll behavior
|
||||
let ticking = false; // Flag to prevent multiple rAF calls
|
||||
let tocScroll = false;
|
||||
let scrollEndTimeout = null;
|
||||
|
||||
|
||||
function handleScroll() {
|
||||
if (tocScroll) {
|
||||
clearTimeout(scrollEndTimeout);
|
||||
scrollEndTimeout = setTimeout( () => { tocScroll = false; }, 100);
|
||||
ticking = false;
|
||||
return;
|
||||
}
|
||||
const currentScrollTop = window.scrollY || document.documentElement.scrollTop;
|
||||
|
||||
// Check if we've scrolled more than the threshold
|
||||
if (Math.abs(lastScrollTop - currentScrollTop) <= scrollThreshold) {
|
||||
ticking = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Scrolling down - directly track the scroll position
|
||||
if (currentScrollTop > lastScrollTop) {
|
||||
scroll_bar(currentScrollTop - lastScrollTop);
|
||||
}
|
||||
// Scrolling up - smooth transition back
|
||||
else if (currentScrollTop < lastScrollTop) {
|
||||
// Reset the scroll delta
|
||||
scrollDelta = 0;
|
||||
|
||||
// Add transition class for smooth appearance
|
||||
navContainer.classList.add('nav-scrolling-up');
|
||||
navContainer.classList.remove('nav-hidden');
|
||||
navContainer.style.transform = 'translateY(0)';
|
||||
}
|
||||
|
||||
lastScrollTop = currentScrollTop;
|
||||
ticking = false;
|
||||
}
|
||||
|
||||
// Use requestAnimationFrame for better performance
|
||||
window.addEventListener('scroll', function () {
|
||||
if (!ticking) {
|
||||
requestAnimationFrame(handleScroll);
|
||||
ticking = true;
|
||||
}
|
||||
});
|
||||
|
||||
const buildInfoButton = document.getElementById('build-info-button');
|
||||
const buildInfoPopup = document.getElementById('build-info-popup');
|
||||
const buildInfoClose = document.querySelector('.build-info-close');
|
||||
|
||||
if (buildInfoButton && buildInfoPopup) {
|
||||
buildInfoButton.addEventListener('click', function() {
|
||||
buildInfoPopup.style.display = 'block';
|
||||
});
|
||||
|
||||
buildInfoClose.addEventListener('click', function() {
|
||||
buildInfoPopup.style.display = 'none';
|
||||
});
|
||||
|
||||
window.addEventListener('click', function(event) {
|
||||
if (event.target === buildInfoPopup) {
|
||||
buildInfoPopup.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Table of Contents highlighting
|
||||
const tocLinks = document.querySelectorAll('.toc-entry');
|
||||
if (tocLinks.length > 0) {
|
||||
// Get all headings that correspond to TOC entries
|
||||
const headings = Array.from(tocLinks).map(link => {
|
||||
const id = link.getAttribute('href').substring(1);
|
||||
return document.getElementById(id);
|
||||
}).filter(Boolean);
|
||||
|
||||
// Add smooth scrolling to TOC links
|
||||
tocLinks.forEach(link => {
|
||||
link.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
closeTOC();
|
||||
const targetId = this.getAttribute('href').substring(1);
|
||||
const targetElement = document.getElementById(targetId);
|
||||
|
||||
if (targetElement) {
|
||||
tocScroll = true;
|
||||
window.scrollTo({
|
||||
top: targetElement.offsetTop - 80, // Offset for fixed header
|
||||
behavior: 'smooth'
|
||||
});
|
||||
scroll_bar(navContainer.offsetHeight);
|
||||
|
||||
// Update URL hash without jumping
|
||||
history.pushState(null, null, `#${targetId}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
173
themes/esphome-theme/assets/js/menu.js
Normal file
173
themes/esphome-theme/assets/js/menu.js
Normal file
@ -0,0 +1,173 @@
|
||||
|
||||
const bodystyle = window.getComputedStyle(document.body);
|
||||
const mobileWidthStop = parseInt(bodystyle.getPropertyValue('--mobile-width-stop'));
|
||||
const isMobile = (window.innerWidth <= mobileWidthStop);
|
||||
|
||||
function closeMenu() {
|
||||
const hamburger = document.querySelector('.hamburger-button');
|
||||
const navLinks = document.querySelector('.nav-links');
|
||||
navLinks.classList.remove('active');
|
||||
hamburger.classList.remove('active');
|
||||
hamburger.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
|
||||
function openMenu() {
|
||||
const hamburger = document.querySelector('.hamburger-button');
|
||||
const navLinks = document.querySelector('.nav-links');
|
||||
navLinks.classList.add('active');
|
||||
hamburger.classList.add('active');
|
||||
hamburger.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
function openTOC() {
|
||||
const tocToggle = document.getElementById('toc-toggle');
|
||||
if (!isMobile || !tocToggle) return;
|
||||
const tocPanel = document.getElementsByClassName('sidebar-mobile')[0];
|
||||
const overlay = document.getElementById('overlay');
|
||||
tocToggle.classList.add('open');
|
||||
tocPanel.classList.add('open');
|
||||
overlay.classList.add('show');
|
||||
}
|
||||
|
||||
function closeTOC() {
|
||||
const tocToggle = document.getElementById('toc-toggle');
|
||||
if (!isMobile || !tocToggle) return;
|
||||
const tocPanel = document.getElementsByClassName('sidebar-mobile')[0];
|
||||
const overlay = document.getElementById('overlay');
|
||||
tocToggle.classList.remove('open');
|
||||
tocPanel.classList.remove('open');
|
||||
overlay.classList.remove('show');
|
||||
}
|
||||
|
||||
// Add keyboard support for dropdown menus
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
function setTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
localStorage.setItem('theme', theme);
|
||||
document.querySelector('.theme-toggle').setAttribute('aria-label', `Toggle ${theme === 'dark' ? 'light' : 'dark'} mode`);
|
||||
closeMenu();
|
||||
}
|
||||
|
||||
// Theme toggle functionality
|
||||
const themeToggle = document.querySelector('.theme-toggle');
|
||||
themeToggle.addEventListener('click', () => {
|
||||
const currentTheme = document.documentElement.getAttribute('data-theme');
|
||||
setTheme(currentTheme === 'dark' ? 'light' : 'dark');
|
||||
});
|
||||
|
||||
const tocToggle = document.getElementById('toc-toggle');
|
||||
const overlay = document.getElementById('overlay');
|
||||
if (tocToggle)
|
||||
tocToggle.addEventListener('click', event => {
|
||||
if (tocToggle.classList.contains("open"))
|
||||
closeTOC();
|
||||
else
|
||||
openTOC();
|
||||
});
|
||||
if (overlay)
|
||||
overlay.addEventListener('click', closeTOC);
|
||||
const sidebarMobile = document.querySelectorAll('.sidebar-mobile');
|
||||
sidebarMobile.forEach(sidebar => {
|
||||
sidebar.addEventListener("click", closeTOC);
|
||||
})
|
||||
|
||||
const dropdownButtons = document.querySelectorAll('.dropbtn button');
|
||||
|
||||
dropdownButtons.forEach(button => {
|
||||
// Handle Enter and Space key presses
|
||||
button.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
toggleDropdown(this);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle click events
|
||||
button.addEventListener('click', function(e) {
|
||||
if (!isMobile) return;
|
||||
e.preventDefault();
|
||||
// Close others
|
||||
dropdownButtons.forEach(function(otherBtn) {
|
||||
if (otherBtn !== button) {
|
||||
otherBtn.setAttribute('aria-expanded', 'false');
|
||||
if (otherBtn.nextElementSibling) {
|
||||
otherBtn.nextElementSibling.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
// Toggle this one
|
||||
const expanded = button.getAttribute('aria-expanded') === 'true';
|
||||
button.setAttribute('aria-expanded', expanded ? "false" : "true");
|
||||
if (button.nextElementSibling) {
|
||||
button.nextElementSibling.style.display = expanded ? 'none' : 'block';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Close dropdowns when Escape key is pressed
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape') {
|
||||
closeAllDropdowns();
|
||||
closeTOC();
|
||||
}
|
||||
});
|
||||
|
||||
// Close dropdowns when clicking outside
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!e.target.matches('.dropbtn')) {
|
||||
closeAllDropdowns();
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle dropdown function
|
||||
function toggleDropdown(button) {
|
||||
if (window.innerWidth > mobileWidthStop) return;
|
||||
const isExpanded = button.getAttribute('aria-expanded') === 'true';
|
||||
closeAllDropdowns();
|
||||
|
||||
if (!isExpanded) {
|
||||
button.setAttribute('aria-expanded', 'true');
|
||||
const dropdownContent = button.nextElementSibling;
|
||||
dropdownContent.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// Close all dropdowns
|
||||
function closeAllDropdowns() {
|
||||
if (window.innerWidth > mobileWidthStop) return;
|
||||
dropdownButtons.forEach(btn => {
|
||||
btn.setAttribute('aria-expanded', 'false');
|
||||
const dropdownContent = btn.nextElementSibling;
|
||||
if (dropdownContent)
|
||||
dropdownContent.style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const hamburger = document.querySelector('.hamburger-button');
|
||||
const navLinks = document.querySelector('.nav-links');
|
||||
if (!hamburger || !navLinks) return;
|
||||
|
||||
hamburger.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
hamburger.classList.toggle('active');
|
||||
navLinks.classList.toggle('active');
|
||||
const expanded = hamburger.getAttribute('aria-expanded') === 'true';
|
||||
hamburger.setAttribute('aria-expanded', expanded ? "false" : "true");
|
||||
closeTOC();
|
||||
});
|
||||
// Close menu on outside click (mobile only)
|
||||
document.addEventListener('click', function(e) {
|
||||
if (window.innerWidth > mobileWidthStop) return;
|
||||
if (!e.target.closest('.hamburger-button') && !e.target.closest('.nav-links')) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
// Close menu on resize to desktop
|
||||
window.addEventListener('resize', function() {
|
||||
if (window.innerWidth > mobileWidthStop) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
});
|
||||
203
themes/esphome-theme/assets/js/search.js
Normal file
203
themes/esphome-theme/assets/js/search.js
Normal file
@ -0,0 +1,203 @@
|
||||
// Search functionality
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (typeof PagefindModularUI === 'undefined') {
|
||||
console.error('PagefindModularUI library not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
class El {
|
||||
constructor(tagname) {
|
||||
this.element = document.createElement(tagname);
|
||||
}
|
||||
|
||||
id(s) {
|
||||
this.element.id = s;
|
||||
return this;
|
||||
}
|
||||
|
||||
class(s) {
|
||||
this.element.classList.add(s);
|
||||
return this;
|
||||
}
|
||||
|
||||
attrs(obj) {
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
this.element.setAttribute(k, v);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
text(t) {
|
||||
this.element.innerText = t;
|
||||
return this;
|
||||
}
|
||||
|
||||
html(t) {
|
||||
this.element.innerHTML = t;
|
||||
return this;
|
||||
}
|
||||
|
||||
handle(e, f) {
|
||||
this.element.addEventListener(e, f);
|
||||
return this;
|
||||
}
|
||||
|
||||
addTo(el) {
|
||||
if (el instanceof El) {
|
||||
el.element.appendChild(this.element);
|
||||
} else {
|
||||
el.appendChild(this.element);
|
||||
}
|
||||
return this.element;
|
||||
}
|
||||
}
|
||||
|
||||
function getLink(location, anchors, url) {
|
||||
if (!anchors || !anchors.length)
|
||||
return null;
|
||||
// find the closest anchor at or before the current location
|
||||
const anchor = anchors.sort((a, b) => b.location - a.location).find(a => a.location <= location);
|
||||
if (anchor) {
|
||||
return url + "#" + anchor.id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const resultTemplate = (result) => {
|
||||
const wrapper = new El("li").class("pagefind-modular-list-result");
|
||||
wrapper.handle("click", closeResults);
|
||||
|
||||
const thumb = new El("div").class("pagefind-modular-list-thumb").addTo(wrapper);
|
||||
let image = result?.meta?.image;
|
||||
if (image) {
|
||||
if (image.includes("/_images/"))
|
||||
image = image.substring(image.indexOf("/_images/"));
|
||||
new El("img").class("pagefind-modular-list-image").attrs({
|
||||
src: image,
|
||||
alt: result.meta.image_alt || result.meta.title
|
||||
}).addTo(thumb);
|
||||
}
|
||||
|
||||
const inner = new El("div").class("pagefind-modular-list-inner").addTo(wrapper);
|
||||
const title = new El("p").class("pagefind-modular-list-title").addTo(inner);
|
||||
new El("a").class("pagefind-modular-list-link").text(result.meta?.title).attrs({
|
||||
href: result.meta?.url || result.url
|
||||
}).addTo(title);
|
||||
|
||||
const excerpt = new El("p").class("pagefind-modular-list-excerpt").addTo(inner);
|
||||
const locations = result.weighted_locations.sort((a, b) => b.weight - a.weight);
|
||||
const url = getLink(locations[0]?.location, result.anchors, result.url) || result.meta?.url || result.url;
|
||||
new El("a").class("pagefind-modular-list-link").html(result.excerpt).attrs({
|
||||
href: url
|
||||
}).addTo(excerpt);
|
||||
|
||||
return wrapper.element;
|
||||
}
|
||||
|
||||
// Create search input and container
|
||||
const searchContainer = document.getElementById('nav-search-container');
|
||||
|
||||
// Create search input
|
||||
const searchInput = document.createElement('input');
|
||||
searchInput.type = 'text';
|
||||
searchInput.id = "frontpage-search";
|
||||
searchInput.placeholder = 'Search...';
|
||||
searchInput.className = 'pagefind-ui__search-input';
|
||||
searchContainer.appendChild(searchInput);
|
||||
|
||||
const resultsContainer = document.getElementById('nav-search-results');
|
||||
|
||||
const instance = new PagefindModularUI.Instance({
|
||||
showSubResults: true,
|
||||
showImages: false,
|
||||
resetStyles: true,
|
||||
ranking: {
|
||||
pageLength: 0.0,
|
||||
termSaturation: 1.6,
|
||||
termFrequency: 0.4,
|
||||
termSimilarity: 6.0
|
||||
}
|
||||
});
|
||||
|
||||
// Add input component
|
||||
instance.add(new PagefindModularUI.Input({
|
||||
inputElement: "#frontpage-search"
|
||||
}));
|
||||
|
||||
// Add results component
|
||||
instance.add(new PagefindModularUI.ResultList({
|
||||
containerElement: "#nav-search-results",
|
||||
resultTemplate: resultTemplate
|
||||
}));
|
||||
|
||||
|
||||
let top_hit = null;
|
||||
|
||||
function closeResults() {
|
||||
resultsContainer.style.display = 'none';
|
||||
top_hit = null;
|
||||
}
|
||||
|
||||
// Show/hide results
|
||||
instance.on("results", async (results) => {
|
||||
if (results.results.length) {
|
||||
resultsContainer.style.display = 'block';
|
||||
const data = await results.results[0].data();
|
||||
top_hit = data.url;
|
||||
} else {
|
||||
closeResults();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!e.target.closest('#nav-search-results')) {
|
||||
closeResults();
|
||||
}
|
||||
});
|
||||
// Create clear button
|
||||
const clearButton = document.createElement('button');
|
||||
clearButton.type = 'button';
|
||||
clearButton.className = 'search-clear-button';
|
||||
clearButton.textContent = "X";
|
||||
clearButton.style.display = 'none';
|
||||
searchContainer.appendChild(clearButton);
|
||||
|
||||
// Show/hide clear button based on input content
|
||||
searchInput.addEventListener('input', () => {
|
||||
clearButton.style.display = searchInput.value.length > 0 ? 'flex' : 'none';
|
||||
});
|
||||
|
||||
// Clear search when button is clicked
|
||||
clearButton.addEventListener('click', () => {
|
||||
searchInput.value = '';
|
||||
clearButton.style.display = 'none';
|
||||
instance.triggerSearch('');
|
||||
resultsContainer.style.display = 'none';
|
||||
searchInput.focus(); // Re-focus the search box after clearing
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (!(searchInput === document.activeElement) && event.key === '/') { // Use '/' key as trigger
|
||||
event.preventDefault(); // Prevent the '/' key from being entered in the search box
|
||||
if (isMobile)
|
||||
openMenu();
|
||||
searchInput.focus();
|
||||
}
|
||||
});
|
||||
const navContainer = document.getElementById('nav-container');
|
||||
|
||||
searchInput.addEventListener('focusin', () => {
|
||||
navContainer.style.transform = `translateY(0)`;
|
||||
});
|
||||
searchInput.addEventListener('beforeinput', () => {
|
||||
navContainer.style.transform = `translateY(0)`;
|
||||
});
|
||||
searchInput.addEventListener('keydown', function (event) {
|
||||
if (event.key === "Enter" && !!top_hit) {
|
||||
window.location = top_hit;
|
||||
top_hit = null;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@ -0,0 +1,14 @@
|
||||
<div class="code-block">
|
||||
<button class="copy-button" aria-label="Copy code">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
<span class="copy-feedback"></span>
|
||||
</button>
|
||||
<div class="codeblock-content">
|
||||
{{- $result := transform.HighlightCodeBlock . -}}
|
||||
{{- $result.Wrapped -}}
|
||||
</div>
|
||||
</div>
|
||||
{{ .Page.Store.Set "hasCode" true }}
|
||||
@ -0,0 +1,4 @@
|
||||
<h{{ .Level }} id="{{ .Anchor | safeURL }}">
|
||||
{{ .Text | safeHTML }}
|
||||
<img data-pagefind-ignore="all" alt="Copy link to header" class="copy-link dark-invert" data-anchor="{{ .Anchor }}" title="Copy link" src="/images/link.svg">
|
||||
</h{{ .Level }}>
|
||||
@ -0,0 +1,40 @@
|
||||
{{- $link := .Destination -}}
|
||||
{{- $text := .Text | safeHTML -}}
|
||||
|
||||
{{- if strings.HasPrefix $link "#" -}}
|
||||
{{- $id := strings.TrimPrefix "#" $link -}}
|
||||
{{- $anchors := site.Data.anchors -}}
|
||||
{{- $entries := index $anchors $id -}}
|
||||
{{- $foundLocal := false -}}
|
||||
|
||||
{{- if $entries -}}
|
||||
{{- range $entries -}}
|
||||
{{- if eq .page .Page.File.Path -}}
|
||||
{{- $foundLocal = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if $foundLocal -}}
|
||||
<a href="{{ $link }}"{{ with .Title }} title="{{ . }}"{{ end }}>{{ $text }}</a>
|
||||
{{- else if $entries -}}
|
||||
{{- $firstEntry := index $entries 0 -}}
|
||||
<a href="/{{ $firstEntry.page }}#{{ $id }}"{{ with .Title }} title="{{ . }}"{{ end }}>{{ $text }}</a>
|
||||
{{- else -}}
|
||||
{{- if eq hugo.Environment "development" -}}
|
||||
{{ warnf "Unresolved anchor '%s' in page '%s'" $link .Page.File.Path }}
|
||||
<a href="{{ $link }}" class="unresolved-anchor"{{ with .Title }} title="{{ . }}"{{ end }}>{{ $text }}</a>
|
||||
{{- end -}}
|
||||
{{- if eq hugo.Environment "production" -}}
|
||||
{{ errorf "Unresolved anchor '%s' in page '%s'" $link .Page.File.Path }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
<a href="{{ $link }}"{{ with .Title }} title="{{ . }}"{{ end }}>{{ $text }}
|
||||
{{- $u := urls.Parse .Destination -}}
|
||||
{{- if $u.IsAbs -}}
|
||||
<img data-pagefind-ignore="all" alt="External link" class="external-link dark-invert" title="External link" src="/images/external-link.svg">
|
||||
{{- end -}}
|
||||
</a>
|
||||
{{- end -}}
|
||||
{{- /**/ -}}
|
||||
@ -0,0 +1,18 @@
|
||||
{{/*
|
||||
MATH SHORTCODE
|
||||
Formats equations
|
||||
|
||||
Usage:
|
||||
{{< math >}}
|
||||
c = \\pm\\sqrt{a^2 + b^2}"
|
||||
{{< /math >}}
|
||||
|
||||
Content:
|
||||
The content between the opening and closing shortcode tags will be formatted as maths
|
||||
*/}}
|
||||
{{ $opts := dict "output" "htmlAndMathml" }}
|
||||
{{ if eq .Type "block" }}
|
||||
{{ $opts = merge $opts (dict "displayMode" true) }}
|
||||
{{ end }}
|
||||
{{ transform.ToMath .Inner $opts }}
|
||||
{{ .Page.Store.Set "hasMath" true }}
|
||||
227
themes/esphome-theme/layouts/_default/baseof.html
Normal file
227
themes/esphome-theme/layouts/_default/baseof.html
Normal file
@ -0,0 +1,227 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ .Site.LanguageCode | default "en" }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{{ if .IsHome }}
|
||||
{{ partial "site-verification.html" . }}
|
||||
{{ end }}
|
||||
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} - {{ .Site.Title }}{{ end }}</title>
|
||||
{{ $seo := .Page.Param "seo" }}
|
||||
{{ with $seo }}
|
||||
{{ $src := $seo.image }}
|
||||
|
||||
{{ with $seo.description | default .Page.Title | default .Site.Params.Description }}
|
||||
<meta name="description" content="{{ . }}">
|
||||
<meta name="twitter:description" content="{{ . }}">
|
||||
<meta property="og:description" content="{{ . }}">
|
||||
{{ end }}
|
||||
|
||||
{{ if $src }}
|
||||
{{/* Check if image exists in local images directory first */}}
|
||||
{{- $localPath := printf "images/%s" $src -}}
|
||||
{{- $componentPath := printf "%s/images/%s" (path.Dir .Page.File.Path) $src -}}
|
||||
{{- $globalPath := printf "images/%s" $src -}}
|
||||
{{- $imagePath := "" -}}
|
||||
{{- if fileExists (printf "content/%s" $componentPath) -}}
|
||||
{{- $imagePath = $componentPath -}}
|
||||
{{- else if fileExists (printf "static/%s" $globalPath) -}}
|
||||
{{- $imagePath = $globalPath -}}
|
||||
{{- else -}}
|
||||
{{- $imagePath = $src -}}
|
||||
{{- end -}}
|
||||
{{ with $imagePath }}
|
||||
<meta property="og:image" content="{{ . | absURL }}">
|
||||
<meta name="twitter:image" content="{{ . | absURL }}">
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<meta property="og:title" content="{{ .Page.Title }}">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="{{ .Page.Permalink }}">
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="{{ .Page.Title }}">
|
||||
|
||||
<script data-cfasync="false" type="text/javascript">
|
||||
// cfasync is set false to prevent Cloudflare from modifying the script
|
||||
// Critical theme preferences
|
||||
// Theme handling
|
||||
const savedTheme = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||
document.documentElement.setAttribute('data-theme', savedTheme);
|
||||
</script>
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="{{ "/images/logo.svg" | relURL }}">
|
||||
|
||||
<!-- CSS -->
|
||||
{{ partial "asset.html" (dict "path" "css/main.css" "defer" false) }}
|
||||
{{ partial "asset.html" (dict "path" "css/tables.css" "defer" true) }}
|
||||
{{ partial "asset.html" (dict "path" "pagefind/pagefind-modular-ui.css" "defer" true) }}
|
||||
{{ block "head" . }}{{ end }}
|
||||
|
||||
</head>
|
||||
<body class="body branch-{{ .Site.Params.Branch }}">
|
||||
<div class="page-container">
|
||||
<div class="nav-container" id="nav-container">
|
||||
<nav class="sticky-nav">
|
||||
{{ if and (ne .IsHome true) (.Content) }}
|
||||
<button type="button" class="toc-button" id="toc-toggle" aria-label="Open table of contents"
|
||||
aria-expanded="false">
|
||||
{{ os.ReadFile "static/images/icons/list.svg" | safeHTML }}
|
||||
</button>
|
||||
{{ end }}
|
||||
<div class="nav-logo">
|
||||
<a id="nav-logo-a"
|
||||
{{ if or (eq .Page.Title "ESPHome Docs") .IsHome }}
|
||||
title="ESPHome Landing Page"
|
||||
href="{{ "/" | relURL }}">
|
||||
{{ else }}
|
||||
title="ESPHome Documentation Home"
|
||||
href="{{ "/components" | relURL }}">
|
||||
{{ end }}
|
||||
{{ os.ReadFile "static/images/logo-text.svg" | safeHTML }}
|
||||
</a>
|
||||
</div>
|
||||
<button type="button" class="hamburger-button" aria-label="Open menu" aria-expanded="false">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
<div class="nav-links">
|
||||
<div class="dropbtn">
|
||||
<button class="theme-toggle" aria-label="Toggle theme" title="Toggle dark mode">
|
||||
<svg class="sun-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="5"></circle>
|
||||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||||
</svg>
|
||||
<svg class="moon-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{{ range (index .Site.Menus "main") }}
|
||||
{{ if .HasChildren }}
|
||||
<div class="dropdown">
|
||||
<button type="button" class="dropbtn" aria-haspopup="true"
|
||||
aria-expanded="false">{{ .Name }}</button>
|
||||
<div class="dropdown-content">
|
||||
{{ range .Children }}
|
||||
<a href="{{ .URL }}">{{ .Name }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ else }}
|
||||
<a class="nav-link" href="{{ .URL }}">{{ .Name }}</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<div class="nav-search">
|
||||
<div id="nav-search-container"></div>
|
||||
<div id="nav-search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
<main data-pagefind-body>
|
||||
{{ block "main" . }}{{ end }}
|
||||
<div class="branch-overlay">{{ .Site.Params.Branch | upper }}</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="footer-content">
|
||||
<a class="footer-logo" href="https://www.openhomefoundation.org/">
|
||||
<span>ESPHome is a project from the</span>
|
||||
<img src="{{ "images/ohf-logo-on-dark.svg" | relURL }}" alt="ESPHome Logo" width="220px" height="49px">
|
||||
</a>
|
||||
<div class="footer-links">
|
||||
{{ range (index .Site.Menus "footer") }}
|
||||
<div class="footer-column">
|
||||
<div class="footer-heading">{{ .Name }}</div>
|
||||
<ul>
|
||||
{{ range .Children }}
|
||||
<li><a href="{{ .URL | relURL }}">
|
||||
{{ os.ReadFile (printf "static/images/icons/%s.svg" .Post) | safeHTML }}
|
||||
{{ .Name }}
|
||||
</a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="build-info">
|
||||
<button id="build-info-button" class="build-info-button" title="Show build information">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="12" y1="16" x2="12" y2="12"></line>
|
||||
<line x1="12" y1="8" x2="12" y2="8"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="build-info-popup" class="build-info-popup">
|
||||
<div class="build-info-content">
|
||||
<span class="build-info-close">×</span>
|
||||
<h4>Build Information</h4>
|
||||
<div class="build-info-grid">
|
||||
<div class="build-info-label">Branch:</div>
|
||||
<div class="build-info-value">{{ .Site.Params.branch | default "unknown" }}</div>
|
||||
|
||||
{{ $repo := strings.TrimSuffix ".git" .Site.Data.repo.url }}
|
||||
<div class="build-info-label">Commit hash:</div>
|
||||
<div class="build-info-value"><a href="{{ $repo }}/commit/{{ .Site.Params.commit.hash }}"
|
||||
target="_blank">{{ .Site.Params.commit.hash }}</a></div>
|
||||
|
||||
<div class="build-info-label">Message:</div>
|
||||
<div class="build-info-value">{{ .Site.Params.commit.title }}</div>
|
||||
|
||||
<div class="build-info-label">Build date:</div>
|
||||
<div class="build-info-value">{{ now.Format "2006-01-02" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript -->
|
||||
<!-- if there are equations on the page, use katex to render client-side -->
|
||||
{{ if .Store.Get "hasMath" }}
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css"
|
||||
integrity="sha384-5TcZemv2l/9On385z///+d7MSYlvIEw9FuZTIdZ14vJLqWphw7e7ZPuOiCHJcFCP" crossorigin="anonymous">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js"
|
||||
integrity="sha384-cMkvdD8LoxVzGF/RPUKAcvmm49FQ0oxwDF3BGKtDXcEc+T1b2N+teh/OJfpU0jr6"
|
||||
crossorigin="anonymous"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/contrib/auto-render.min.js"
|
||||
integrity="sha384-hCXGrW6PitJEwbkoStFjeJxv+fSOOQKOPbJxSfM6G5sWZjAyWhXiTIIAmQqnlLlh" crossorigin="anonymous"
|
||||
onload="renderMathInElement(document.body);"></script>
|
||||
{{ end }}
|
||||
|
||||
{{ partial "asset.html" (dict "path" "/js/lazysizes.min.js" "defer" true) }}
|
||||
{{ partial "asset.html" (dict "path" "/pagefind/pagefind-modular-ui.js" "defer" true) }}
|
||||
{{ partial "asset.html" (dict "path" "/js/menu.js" "defer" true) }}
|
||||
{{ partial "asset.html" (dict "path" "/js/search.js" "defer" true) }}
|
||||
{{ partial "asset.html" (dict "path" "/js/main.js" "defer" true) }}
|
||||
|
||||
<!-- Build info popup script -->
|
||||
{{ if .Store.Get "hasCode" }}
|
||||
{{ partial "asset.html" (dict "path" "css/chroma.css" "defer" true) }}
|
||||
{{ end }}
|
||||
|
||||
{{ block "scripts" . }}{{ end }}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
24
themes/esphome-theme/layouts/_default/list.html
Normal file
24
themes/esphome-theme/layouts/_default/list.html
Normal file
@ -0,0 +1,24 @@
|
||||
{{ define "main" }}
|
||||
<div class="content-container">
|
||||
{{ if .Content }}
|
||||
<div class="sidebar sidebar-fixed" id="sidebar">
|
||||
{{ partial "sidebar.html" . }}
|
||||
</div>
|
||||
<div class="content">
|
||||
{{ if not ( eq .Title "ESPHome Docs") }}
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ end }}
|
||||
{{ .Content }}
|
||||
</div>
|
||||
<div class="overlay" id="overlay"></div>
|
||||
<div class="sidebar sidebar-mobile">
|
||||
{{ partial "sidebar.html" . }}
|
||||
</div>
|
||||
{{ else }}
|
||||
<div class="content">
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ partial "sidebar.html" . }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
24
themes/esphome-theme/layouts/_default/single.html
Normal file
24
themes/esphome-theme/layouts/_default/single.html
Normal file
@ -0,0 +1,24 @@
|
||||
{{ define "main" }}
|
||||
<div class="content-container">
|
||||
<div class="sidebar sidebar-fixed">
|
||||
{{ partial "sidebar.html" . }}
|
||||
</div>
|
||||
<div class="content">
|
||||
{{ $firstHeading := "" }}
|
||||
{{ with (index (index .Fragments.Headings 0).Headings 0) }}
|
||||
{{ $firstHeading = .Title }}
|
||||
{{ end }}
|
||||
|
||||
|
||||
{{ if ne .Title $firstHeading }}
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ end }}
|
||||
|
||||
{{ .Content }}
|
||||
</div>
|
||||
<div class="overlay" id="overlay"></div>
|
||||
<div class="sidebar sidebar-mobile">
|
||||
{{ partial "sidebar.html" . }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
23
themes/esphome-theme/layouts/index.html
Normal file
23
themes/esphome-theme/layouts/index.html
Normal file
@ -0,0 +1,23 @@
|
||||
{{ define "main" }}
|
||||
<div class="hero-container">
|
||||
<div class="hero-content">
|
||||
<h1>Smart Home Made Simple</h1>
|
||||
<p>Turn your ESP32, ESP8266, or RP2040 boards into powerful smart home devices with simple YAML configuration</p>
|
||||
</div>
|
||||
<div class="hero-img">
|
||||
{{ partial "image.html" (dict "src" "hero.png" "page" .Page "thumbSize" "300x q25") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cta-container">
|
||||
<div class="cta-content">
|
||||
<div class="cta-buttons">
|
||||
<a href="{{ "guides/getting_started_hassio/" | relURL }}" class="btn btn-primary">Installation Guide</a>
|
||||
<a href="{{ "components/" | relURL }}" class="btn btn-secondary">Browse Components</a>
|
||||
<a href="https://devices.esphome.io/" class="btn btn-secondary">Device Database</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ .Content }}
|
||||
{{ end }}
|
||||
26
themes/esphome-theme/layouts/partials/asset.html
Normal file
26
themes/esphome-theme/layouts/partials/asset.html
Normal file
@ -0,0 +1,26 @@
|
||||
{{- $path := .path -}}
|
||||
{{- $defer := .defer -}}
|
||||
{{- $ext := path.Ext $path | lower -}}
|
||||
|
||||
{{- if eq hugo.Environment "development" -}}
|
||||
{{- $asset := resources.Get $path -}}
|
||||
{{- if eq $ext ".css" -}}
|
||||
<link rel="stylesheet" href="{{ $asset.RelPermalink }}"
|
||||
>
|
||||
{{- else if eq $ext ".js" -}}
|
||||
<script src="{{ $asset.RelPermalink }}"></script>
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- $asset := resources.Get $path | resources.Minify | resources.Fingerprint "sha256" -}}
|
||||
{{- if eq $ext ".css" -}}
|
||||
<link rel="stylesheet" href="{{ $asset.RelPermalink }}" integrity="{{ $asset.Data.Integrity }}"
|
||||
{{ if $defer }}
|
||||
media="(max-width:1px)"
|
||||
onload="this.onload=null;this.removeAttribute('media');"
|
||||
fetchpriority="high"
|
||||
{{ end }}
|
||||
>
|
||||
{{- else if eq $ext ".js" -}}
|
||||
<script src="{{ $asset.RelPermalink }}" integrity="{{ $asset.Data.Integrity }}" defer></script>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
13
themes/esphome-theme/layouts/partials/automations-menu.html
Normal file
13
themes/esphome-theme/layouts/partials/automations-menu.html
Normal file
@ -0,0 +1,13 @@
|
||||
{{ $currentPage := . }}
|
||||
<ul>
|
||||
{{ range where .Site.Pages "Section" "automations" }}
|
||||
{{ if .IsSection }}
|
||||
{{ continue }}
|
||||
{{ end }}
|
||||
<li>
|
||||
<a href="{{ .RelPermalink }}" class="{{ if eq $currentPage.RelPermalink .RelPermalink }}active{{ end }}">
|
||||
{{ .Title }}
|
||||
</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
13
themes/esphome-theme/layouts/partials/changelog-menu.html
Normal file
13
themes/esphome-theme/layouts/partials/changelog-menu.html
Normal file
@ -0,0 +1,13 @@
|
||||
{{ $currentPage := . }}
|
||||
<ul>
|
||||
{{ range where .Site.Pages "Section" "changelog" }}
|
||||
{{ if .IsSection }}
|
||||
{{ continue }}
|
||||
{{ end }}
|
||||
<li>
|
||||
<a href="{{ .RelPermalink }}" class="{{ if eq $currentPage.RelPermalink .RelPermalink }}active{{ end }}">
|
||||
{{ .Title }}
|
||||
</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
13
themes/esphome-theme/layouts/partials/components-menu.html
Normal file
13
themes/esphome-theme/layouts/partials/components-menu.html
Normal file
@ -0,0 +1,13 @@
|
||||
{{ $currentPage := . }}
|
||||
<ul>
|
||||
{{ range where .Site.Pages "Section" "components" }}
|
||||
{{ if .IsSection }}
|
||||
{{ continue }}
|
||||
{{ end }}
|
||||
<li>
|
||||
<a href="{{ .RelPermalink }}" class="{{ if eq $currentPage.RelPermalink .RelPermalink }}active{{ end }}">
|
||||
{{ .Title }}
|
||||
</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
13
themes/esphome-theme/layouts/partials/guides-menu.html
Normal file
13
themes/esphome-theme/layouts/partials/guides-menu.html
Normal file
@ -0,0 +1,13 @@
|
||||
{{ $currentPage := . }}
|
||||
<ul>
|
||||
{{ range where .Site.Pages "Section" "guides" }}
|
||||
{{ if .IsSection }}
|
||||
{{ continue }}
|
||||
{{ end }}
|
||||
<li>
|
||||
<a href="{{ .RelPermalink }}" class="{{ if eq $currentPage.RelPermalink .RelPermalink }}active{{ end }}">
|
||||
{{ .Title }}
|
||||
</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
78
themes/esphome-theme/layouts/partials/image.html
Normal file
78
themes/esphome-theme/layouts/partials/image.html
Normal file
@ -0,0 +1,78 @@
|
||||
{{ $ext := lower (path.Ext .src) }}
|
||||
{{ $page := .page }}
|
||||
{{ $style := .style }}
|
||||
{{ $thumbSize := .thumbSize | default "180x q20" }}
|
||||
{{/* Check if image exists in local images directory first */}}
|
||||
{{ $src := strings.TrimPrefix "/images/" .src }}
|
||||
{{ $src = strings.TrimPrefix "images/" $src }}
|
||||
{{- $componentPath := printf "content/%s/images/%s" (path.Dir $page.File.Path) $src -}}
|
||||
{{- $globalPath := printf "images/%s" $src -}}
|
||||
|
||||
{{ $image := resources.Get $componentPath }}
|
||||
{{ if not $image }}
|
||||
{{ $image = resources.Get $globalPath }}
|
||||
{{ end }}
|
||||
{{ if not $image }}
|
||||
<pre>{{ .src }}; {{ $ext }}; {{ $componentPath }} {{ $globalPath}}</pre>
|
||||
{{ end }}
|
||||
{{ if not $image }}
|
||||
{{ warnf "Unresolved image '%s' in page '%s'" .src .Page.File.Path }}
|
||||
{{ end }}
|
||||
|
||||
{{- if and $image (eq $ext ".svg") -}}
|
||||
<img src="{{ $image.RelPermalink }}" {{ if .width }}width="{{.width}}" {{ end }} alt="{{ .alt }}" {{ $style }}/>
|
||||
{{ end }}
|
||||
{{ if and $image (in (split ".jpeg,.jpg,.png" ",") $ext) }}
|
||||
{{ if isset . "fill" }}
|
||||
{{ $image = $image.Fill .fill }}
|
||||
{{ end }}
|
||||
{{ $placeholder := ($image.Resize $thumbSize) | images.Filter (images.GaussianBlur 1) }}
|
||||
{{ $src_set := slice (print $image.RelPermalink " " $image.Width "w") }}
|
||||
{{ $maxwidth := .maxwidth | default 1500 }}
|
||||
|
||||
<!-- Generate a range of image sizes to allow adaptive loading -->
|
||||
{{ range $wid := slice (div $maxwidth 5) (div $maxwidth 3) (div $maxwidth 2) (div $maxwidth 1.5) $maxwidth }}
|
||||
{{ $iwid := int $wid }}
|
||||
{{ if ge $image.Width $iwid}}
|
||||
{{ $i := $image.Resize (print $iwid "x" ) }}
|
||||
{{ $src_set = $src_set | append (print $i.RelPermalink " " $iwid "w") }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<noscript>
|
||||
<style>
|
||||
figure.lazy {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<figure class="{{ .classes }}">
|
||||
{{ if .href }}
|
||||
<a href='{{ .href }}'>
|
||||
{{ end }}
|
||||
<img src="{{ $image.RelPermalink }}" {{ if .width }}width="{{.width}}" {{ end }} alt="{{ .alt }}"/>
|
||||
{{ if .href }}
|
||||
</a>
|
||||
{{ end }}
|
||||
<figcaption>
|
||||
<em>{{ .Inner }}</em>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</noscript>
|
||||
{{ $srclist := delimit $src_set ", " }}
|
||||
<figure class="{{ .classes }} lazy">
|
||||
{{ if .href }}
|
||||
<a href='{{ .href }}'>
|
||||
{{ end }}
|
||||
<img class="lazyload w-100 h-100" data-sizes="auto" src="{{ $image.RelPermalink }}" {{ if .width }}width="{{.width}}" {{ end }}
|
||||
fetchpriority=high
|
||||
{{ $style }}
|
||||
srcset="data:image/jpeg;base64,{{ $placeholder.Content | base64Encode }}"
|
||||
data-src="{{ $image.RelPermalink }}"
|
||||
data-srcset="{{ $srclist }}" style="max-width: {{ $image.Width }}px; max-height: {{ $image.Height }}px;"
|
||||
width="{{ $image.Width }}" height="{{ $image.Height }}"
|
||||
alt="{{ .alt }}"/>
|
||||
{{ if .href }}
|
||||
</a>
|
||||
{{ end }}
|
||||
</figure>
|
||||
{{ end }}
|
||||
37
themes/esphome-theme/layouts/partials/linkify.html
Normal file
37
themes/esphome-theme/layouts/partials/linkify.html
Normal file
@ -0,0 +1,37 @@
|
||||
{{/*
|
||||
Try to convert a link text to match a known anchor, with soft failure.
|
||||
The link will be searched for in the given domain, and or category
|
||||
*/}}
|
||||
{{- $text := .text -}}
|
||||
{{- $domain := .domain -}}
|
||||
{{- $category := .category -}}
|
||||
{{- $link := .link -}}
|
||||
{{- $links := slice (printf "%s%s" $domain .link) (printf "%s%s-%s" $domain .link $category) .link (printf "%s-%s" .link $category) -}}
|
||||
{{- $anchors := site.Data.anchors -}}
|
||||
{{- $entries := false -}}
|
||||
{{- range $links -}}
|
||||
{{- $entries = index $anchors . -}}
|
||||
{{- if $entries -}}
|
||||
{{- $link = . -}}
|
||||
{{- break -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $foundLocal := false -}}
|
||||
{{- if $entries -}}
|
||||
{{- range $entries -}}
|
||||
{{- if eq .page .Page.File.Path -}}
|
||||
{{- $text = $text | default .text -}}
|
||||
{{- $foundLocal = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if $foundLocal -}}
|
||||
{{- printf "[%s](#%s)" $text $link -}}
|
||||
{{- else if $entries -}}
|
||||
{{- $firstEntry := index $entries 0 -}}
|
||||
{{- $text = $text | default $firstEntry.text -}}
|
||||
{{- printf "[%s](/%s#%s)" $text $firstEntry.page $link -}}
|
||||
{{- else -}}
|
||||
{{- $text -}}
|
||||
{{- end -}}
|
||||
@ -0,0 +1,31 @@
|
||||
{{- $branch := site.Data.repo.branch -}}
|
||||
{{- $category := .Get 0 -}}
|
||||
{{- $data := index site.Data.automations $branch "automations" -}}
|
||||
|
||||
{{- if $data -}}
|
||||
{{- $markdown := "" -}}
|
||||
{{- range $cat, $items := $data -}}
|
||||
{{- if eq $cat $category -}}
|
||||
{{- $singular := substr $category 0 (sub (len $category) 1) -}}
|
||||
{{- range $domain, $entries := $items -}}
|
||||
{{- $entryList := "" -}}
|
||||
{{- $docref := printf "{{< docref %s %s true >}}" $domain $domain -}}
|
||||
{{- if eq $domain "Core" -}}
|
||||
{{- $docref = printf "[Common %s](#common-%s)" $category $category -}}
|
||||
{{- $domain = "" -}}
|
||||
{{- end -}}
|
||||
{{- range $i, $entry := $entries -}}
|
||||
{{- $entryLink := printf "%s" (replace $entry "." "") -}}
|
||||
{{- $entryLink = partial "linkify.html" (dict "domain" $domain "text" $entry "link" $entryLink "category" $singular) -}}
|
||||
{{- if $i -}}{{- $entryList = printf "%s, " $entryList -}}{{- end -}}
|
||||
{{- $entryList = printf "%s%s" $entryList $entryLink -}}
|
||||
{{- end -}}
|
||||
{{- $markdown = printf "%s- **%s**: %s\n" $markdown $docref $entryList -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $markdown | markdownify -}}
|
||||
{{- else -}}
|
||||
<p>⚠️ Data not found for branch: <code>{{- $branch -}}</code></p>
|
||||
{{- end -}}
|
||||
58
themes/esphome-theme/layouts/partials/sidebar.html
Normal file
58
themes/esphome-theme/layouts/partials/sidebar.html
Normal file
@ -0,0 +1,58 @@
|
||||
{{ $currentPage := . }}
|
||||
{{ $title := .Title }}
|
||||
<div class="sidebar-content {{ if .Content }}scroll-trap{{ end }}">
|
||||
<h3>{{ $title }}</h3>
|
||||
<nav class="sidebar-nav">
|
||||
{{ if .Fragments }}
|
||||
{{ if .Fragments.Headings }}
|
||||
{{ $headings := slice }}
|
||||
<ul>
|
||||
{{ $start_level := 9 }}
|
||||
{{ range .Fragments.HeadingsMap }}
|
||||
{{ if gt $start_level .Level }}
|
||||
{{ $start_level = .Level }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ range .Fragments.HeadingsMap }}
|
||||
{{ if eq .Level $start_level }}
|
||||
<li><a class="toc-entry" href="#{{ .ID }}">{{ .Title | safeHTML }}</a>
|
||||
{{ if .Headings }}
|
||||
<ul>
|
||||
{{ range .Headings }}
|
||||
{{ $headings := collections.Append .ID $headings }}
|
||||
<li><a href="#{{ .ID }}">{{ .Title | safeHTML }}</a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if .Pages }}
|
||||
<h3>In this section</h3>
|
||||
<ul class="section-list">
|
||||
{{ range .Pages }}
|
||||
{{ if .Title }}
|
||||
<li class="section-item">
|
||||
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-resource">
|
||||
<hr>
|
||||
{{ $repo := strings.TrimSuffix ".git" .Site.Data.repo.url }}
|
||||
{{ with .File }}
|
||||
{{ $path := printf "%s/blob/current/content/%s" $repo .Path }}
|
||||
<a href="{{ $path }}" class="ghedit">
|
||||
{{ os.ReadFile "static/images/icons/edit.svg" | safeHTML }}
|
||||
Edit this page on GitHub
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
12
themes/esphome-theme/layouts/partials/site-verification.html
Normal file
12
themes/esphome-theme/layouts/partials/site-verification.html
Normal file
@ -0,0 +1,12 @@
|
||||
{{ if .Site.Params.seo.webmaster_verifications.google }}
|
||||
<meta name="google-site-verification" content="{{ .Site.Params.seo.webmaster_verifications.google }}" />
|
||||
{{ end }}
|
||||
{{ if .Site.Params.seo.webmaster_verifications.bing }}
|
||||
<meta name="msvalidate.01" content="{{ .Site.Params.seo.webmaster_verifications.bing }}">
|
||||
{{ end }}
|
||||
{{ if .Site.Params.seo.webmaster_verifications.alexa }}
|
||||
<meta name="alexaVerifyID" content="{{ .Site.Params.seo.webmaster_verifications.alexa }}">
|
||||
{{ end }}
|
||||
{{ if .Site.Params.seo.webmaster_verifications.yandex }}
|
||||
<meta name="yandex-verification" content="{{ .Site.Params.seo.webmaster_verifications.yandex }}">
|
||||
{{ end }}
|
||||
11
themes/esphome-theme/layouts/shortcodes/anchor.html
Normal file
11
themes/esphome-theme/layouts/shortcodes/anchor.html
Normal file
@ -0,0 +1,11 @@
|
||||
{{/*
|
||||
ANCHOR SHORTCODE
|
||||
Creates an HTML anchor point that can be linked to with fragment identifiers.
|
||||
|
||||
Usage:
|
||||
{{< anchor "my-anchor-id" >}}
|
||||
|
||||
Parameters:
|
||||
1. anchor ID (required) - The ID to use for the anchor point
|
||||
*/}}
|
||||
<a class="anchor" id="{{ .Get 0 }}"></a>
|
||||
115
themes/esphome-theme/layouts/shortcodes/api-key-input.html
Normal file
115
themes/esphome-theme/layouts/shortcodes/api-key-input.html
Normal file
@ -0,0 +1,115 @@
|
||||
<style>
|
||||
.api-key-input {
|
||||
--api-input-bg: #fff;
|
||||
--api-input-border: #b0b9be;
|
||||
--api-input-text: #222;
|
||||
--api-input-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
--api-input-bg-focus: #f6fdff;
|
||||
--api-input-border-focus: #00979d;
|
||||
--api-copy-btn-bg: #f7fafd;
|
||||
--api-copy-btn-text: #00979d;
|
||||
--api-copy-btn-border: #b0b9be;
|
||||
--api-copy-btn-bg-hover: #e0f7fa;
|
||||
}
|
||||
[data-theme="dark"] .api-key-input {
|
||||
--api-input-bg: #232728;
|
||||
--api-input-border: #333b3d;
|
||||
--api-input-text: #eee;
|
||||
--api-input-shadow: 0 1px 7px rgba(0,0,0,0.18);
|
||||
--api-input-bg-focus: #1a2326;
|
||||
--api-input-border-focus: #1abfc6;
|
||||
--api-copy-btn-bg: #1a2326;
|
||||
--api-copy-btn-text: #1abfc6;
|
||||
--api-copy-btn-border: #333b3d;
|
||||
--api-copy-btn-bg-hover: #233438;
|
||||
}
|
||||
.api-key-input {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
align-items: center;
|
||||
}
|
||||
.api-key-input input[type="text"] {
|
||||
background: var(--api-input-bg);
|
||||
color: var(--api-input-text);
|
||||
border: 1.5px solid var(--api-input-border);
|
||||
border-radius: 6px;
|
||||
padding: 0.5em 1em;
|
||||
font-size: 1.05em;
|
||||
font-family: inherit;
|
||||
box-shadow: var(--api-input-shadow);
|
||||
transition: border 0.15s, background 0.18s;
|
||||
outline: none;
|
||||
width: 490px;
|
||||
max-width: 98vw;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.api-key-input input[type="text"]:focus {
|
||||
background: var(--api-input-bg-focus);
|
||||
border-color: var(--api-input-border-focus);
|
||||
}
|
||||
.api-key-input input[type="text"]:hover {
|
||||
border-color: var(--api-input-border-focus);
|
||||
}
|
||||
.api-key-input button {
|
||||
background: var(--api-copy-btn-bg);
|
||||
color: var(--api-copy-btn-text);
|
||||
border: 1.5px solid var(--api-copy-btn-border);
|
||||
border-radius: 6px;
|
||||
padding: 0.5em 1em;
|
||||
font-size: 1em;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: background 0.18s, border 0.15s;
|
||||
margin-bottom: 0.5em;
|
||||
outline: none;
|
||||
position: relative;
|
||||
}
|
||||
.api-key-input button:hover, .api-key-input button:focus {
|
||||
background: var(--api-copy-btn-bg-hover);
|
||||
border-color: var(--api-input-border-focus);
|
||||
}
|
||||
.api-key-input .copied {
|
||||
color: var(--api-copy-btn-text);
|
||||
font-size: 0.95em;
|
||||
margin-left: 0.5em;
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
<div class="api-key-input">
|
||||
<input type="text" id="api-key" onclick="this.focus();this.select()" readonly="readonly">
|
||||
<button type="button" id="copy-api-key" aria-label="Copy API key">Copy</button>
|
||||
<span class="copied" id="api-key-copied" style="display:none">Copied!</span>
|
||||
</div>
|
||||
<script>
|
||||
// https://stackoverflow.com/a/62362724
|
||||
function bytesArrToBase64(arr) {
|
||||
const abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; // base64 alphabet
|
||||
const bin = n => n.toString(2).padStart(8,0); // convert num to 8-bit binary string
|
||||
const l = arr.length;
|
||||
let result = '';
|
||||
for(let i=0; i<=(l-1)/3; i++) {
|
||||
let c1 = i*3+1>=l; // case when "=" is on end
|
||||
let c2 = i*3+2>=l; // case when "=" is on end
|
||||
let chunk = bin(arr[3*i]) + bin(c1? 0:arr[3*i+1]) + bin(c2? 0:arr[3*i+2]);
|
||||
let r = chunk.match(/.{1,6}/g).map((x,j)=> j===3&&c2 ? '=' :(j===2&&c1 ? '=':abc[+('0b'+x)]));
|
||||
result += r.join('');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
let array = new Uint8Array(32);
|
||||
window.crypto.getRandomValues(array);
|
||||
document.getElementById("api-key").value = bytesArrToBase64(array);
|
||||
|
||||
document.getElementById("copy-api-key").addEventListener("click", function() {
|
||||
var input = document.getElementById("api-key");
|
||||
input.select();
|
||||
input.setSelectionRange(0, 99999); // For mobile
|
||||
navigator.clipboard.writeText(input.value).then(function() {
|
||||
var copied = document.getElementById("api-key-copied");
|
||||
copied.style.display = "inline";
|
||||
setTimeout(function() { copied.style.display = "none"; }, 1200);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
36
themes/esphome-theme/layouts/shortcodes/apiclass.html
Normal file
36
themes/esphome-theme/layouts/shortcodes/apiclass.html
Normal file
@ -0,0 +1,36 @@
|
||||
{{- /*
|
||||
apiclass shortcode - Creates a link specifically to a C++ class in the API documentation
|
||||
|
||||
Usage:
|
||||
{{< apiclass "DisplayName" "path/to/Class" >}}
|
||||
|
||||
Examples:
|
||||
{{< apiclass "ClimateDevice" "esphome::climate::ClimateDevice" >}}
|
||||
{{< apiclass "WiFiComponent" "esphome::wifi::WiFiComponent" >}}
|
||||
|
||||
How it works:
|
||||
1. Extracts the last path element from the provided path
|
||||
2. Replaces special characters:
|
||||
- Underscores (_) are replaced with double underscores (__)
|
||||
- Dots (.) are replaced with _8
|
||||
- Colons (:) are replaced with _1
|
||||
3. Converts capital letters to lowercase preceded by an underscore
|
||||
4. Prepends the API documentation base URL (configured in Hugo settings)
|
||||
|
||||
When converting from RST, the following directive is automatically converted:
|
||||
:apiclass:`DisplayName <path/to/Class>`
|
||||
:apiclass:`esphome::climate::ClimateDevice` (uses path as display name)
|
||||
*/ -}}
|
||||
{{- $text := .Get 0 -}}
|
||||
{{- $path := .Get 1 -}}
|
||||
{{- $lastPathElement := index (last 1 (split $path "/")) 0 -}}
|
||||
{{- $processedPath := replace (replace (replace $lastPathElement "_" "__") "." "_8") ":" "_1" -}}
|
||||
{{ $match := findRE `([A-Z])` $processedPath }}
|
||||
{{ range $match }}
|
||||
{{ $original := . }}
|
||||
{{ $lower := lower . | printf "_%s" }}
|
||||
{{ $processedPath = replace $processedPath $original $lower }}
|
||||
{{ end }}
|
||||
|
||||
{{- $baseUrl := .Site.Params.api_docs_url -}}
|
||||
<a href="{{ $baseUrl }}/classesphome_1_1{{ $processedPath }}">{{ $text }}</a>
|
||||
39
themes/esphome-theme/layouts/shortcodes/apiref.html
Normal file
39
themes/esphome-theme/layouts/shortcodes/apiref.html
Normal file
@ -0,0 +1,39 @@
|
||||
{{- /*
|
||||
apiref shortcode - Creates a link to a C++ API reference
|
||||
|
||||
Usage:
|
||||
{{< apiref "DisplayName" "path/to/api/element" >}}
|
||||
|
||||
Examples:
|
||||
{{< apiref "Sensor class" "esphome::sensor::Sensor" >}}
|
||||
{{< apiref "Component" "esphome/core/component.h::Component" >}}
|
||||
|
||||
How it works:
|
||||
1. Extracts the last path element from the provided path
|
||||
2. Replaces special characters:
|
||||
- Underscores (_) are replaced with double underscores (__)
|
||||
- Dots (.) are replaced with _8
|
||||
- Colons (:) are replaced with _1
|
||||
3. Converts capital letters to lowercase preceded by an underscore
|
||||
4. Prepends the API documentation base URL (configured in Hugo settings)
|
||||
|
||||
When converting from RST, the following directive is automatically converted:
|
||||
:apiref:`DisplayName <path/to/api/element>`
|
||||
:apiref:`esphome::sensor::Sensor` (uses path as display name)
|
||||
*/ -}}
|
||||
{{- $text := .Get 0 -}}
|
||||
{{- $path := .Get 1 -}}
|
||||
{{- if eq $text $path -}}
|
||||
{{- $text = "API Reference" }}
|
||||
{{- end }}
|
||||
{{- $lastPathElement := index (last 1 (split $path "/")) 0 -}}
|
||||
{{- $processedPath := replace (replace (replace $lastPathElement "_" "__") "." "_8") ":" "_1" -}}
|
||||
{{ $match := findRE `([A-Z])` $processedPath }}
|
||||
{{ range $match }}
|
||||
{{ $original := . }}
|
||||
{{ $lower := lower . | printf "_%s" }}
|
||||
{{ $processedPath = replace $processedPath $original $lower }}
|
||||
{{ end }}
|
||||
|
||||
{{- $baseUrl := .Site.Params.api_docs_url -}}
|
||||
<a href="{{ $baseUrl }}/{{ $processedPath }}">{{ $text }}</a>
|
||||
36
themes/esphome-theme/layouts/shortcodes/apistruct.html
Normal file
36
themes/esphome-theme/layouts/shortcodes/apistruct.html
Normal file
@ -0,0 +1,36 @@
|
||||
{{- /*
|
||||
apistruct shortcode - Creates a link specifically to a C++ struct in the API documentation
|
||||
|
||||
Usage:
|
||||
{{< apistruct "DisplayName" "path/to/Struct" >}}
|
||||
|
||||
Examples:
|
||||
{{< apistruct "SensorStateClass" "esphome::sensor::SensorStateClass" >}}
|
||||
{{< apistruct "GPIOOutputPin" "esphome::output::GPIOOutputPin" >}}
|
||||
|
||||
How it works:
|
||||
1. Extracts the last path element from the provided path
|
||||
2. Replaces special characters:
|
||||
- Underscores (_) are replaced with double underscores (__)
|
||||
- Dots (.) are replaced with _8
|
||||
- Colons (:) are replaced with _1
|
||||
3. Converts capital letters to lowercase preceded by an underscore
|
||||
4. Prepends the API documentation base URL (configured in Hugo settings)
|
||||
|
||||
When converting from RST, the following directive is automatically converted:
|
||||
:apistruct:`DisplayName <path/to/Struct>`
|
||||
:apistruct:`esphome::sensor::SensorStateClass` (uses path as display name)
|
||||
*/ -}}
|
||||
{{- $text := .Get 0 -}}
|
||||
{{- $path := .Get 1 -}}
|
||||
{{- $lastPathElement := index (last 1 (split $path "/")) 0 -}}
|
||||
{{- $processedPath := replace (replace (replace $lastPathElement "_" "__") "." "_8") ":" "_1" -}}
|
||||
{{ $match := findRE `([A-Z])` $processedPath }}
|
||||
{{ range $match }}
|
||||
{{ $original := . }}
|
||||
{{ $lower := lower . | printf "_%s" }}
|
||||
{{ $processedPath = replace $processedPath $original $lower }}
|
||||
{{ end }}
|
||||
|
||||
{{- $baseUrl := .Site.Params.api_docs_url -}}
|
||||
<a href="{{ $baseUrl }}/structesphome_1_1{{ $processedPath }}">{{ $text }}</a>
|
||||
20
themes/esphome-theme/layouts/shortcodes/button.html
Normal file
20
themes/esphome-theme/layouts/shortcodes/button.html
Normal file
@ -0,0 +1,20 @@
|
||||
{{/*
|
||||
BUTTON SHORTCODE
|
||||
Creates a button with an image that links to a URL.
|
||||
|
||||
Usage:
|
||||
{{< button href="https://example.com" img="/images/button.png" alt="Example Button" target="_self" >}}
|
||||
|
||||
Parameters:
|
||||
- href (required) - The URL to link to
|
||||
- img (required) - The path to the button image
|
||||
- alt (optional) - Alt text for the image (default: "Button")
|
||||
- target (optional) - Target attribute for the link (default: "_blank")
|
||||
*/}}
|
||||
{{- $href := .Get "href" -}}
|
||||
{{- $img := .Get "img" -}}
|
||||
{{- $alt := .Get "alt" | default "Button" -}}
|
||||
{{- $target := .Get "target" | default "_blank" -}}
|
||||
<a href="{{ $href }}" target="{{ $target }}">
|
||||
<img src="{{ $img }}" alt="{{ $alt }}" />
|
||||
</a>
|
||||
17
themes/esphome-theme/layouts/shortcodes/caution.html
Normal file
17
themes/esphome-theme/layouts/shortcodes/caution.html
Normal file
@ -0,0 +1,17 @@
|
||||
{{/*
|
||||
CAUTION SHORTCODE
|
||||
Creates a caution admonition box to highlight important cautions or potential issues.
|
||||
|
||||
Usage:
|
||||
{{< caution >}}
|
||||
Incorrect wiring may damage your device. Double-check connections before powering on.
|
||||
You can include **Markdown** formatting within the caution.
|
||||
{{< /caution >}}
|
||||
|
||||
Content:
|
||||
The content between the opening and closing shortcode tags will be displayed inside the caution box.
|
||||
*/}}
|
||||
<div class="admonition caution">
|
||||
<p class="admonition-title">Caution</p>
|
||||
{{ .Inner | .Page.RenderString}}
|
||||
</div>
|
||||
30
themes/esphome-theme/layouts/shortcodes/collapse.html
Normal file
30
themes/esphome-theme/layouts/shortcodes/collapse.html
Normal file
@ -0,0 +1,30 @@
|
||||
{{/*
|
||||
COLLAPSE SHORTCODE
|
||||
Creates a collapsible section with a title that can be clicked to show/hide content.
|
||||
|
||||
Usage:
|
||||
{{< collapse "Optional Configuration" >}}
|
||||
This content will be hidden by default and can be expanded by clicking the header.
|
||||
You can include any Markdown content here, including lists, code blocks, etc.
|
||||
{{< /collapse >}}
|
||||
|
||||
Parameters:
|
||||
1. title (required) - The title text to display in the header
|
||||
|
||||
Content:
|
||||
The content between the opening and closing shortcode tags will be hidden by default
|
||||
and can be toggled by clicking on the header.
|
||||
*/}}
|
||||
{{ $title := .Get 0 }}
|
||||
{{ $is_open := .Get 1 }}
|
||||
<div class="collapse-container {{ if $is_open }}active{{ end }}">
|
||||
<div class="collapse-header" onclick="this.parentElement.classList.toggle('active')">
|
||||
<h4>{{ $title }}</h4>
|
||||
<h5>Collapse</h5>
|
||||
<span class="collapse-icon">+</span>
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
{{ .Inner | markdownify }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
121
themes/esphome-theme/layouts/shortcodes/docref.html
Normal file
121
themes/esphome-theme/layouts/shortcodes/docref.html
Normal file
@ -0,0 +1,121 @@
|
||||
{{/*
|
||||
DOCREF SHORTCODE
|
||||
Creates a link to another page in the documentation with proper handling of anchors.
|
||||
|
||||
Usage:
|
||||
{{< docref "components/sensor/dht" >}} <!-- Uses the target page title as link text -->
|
||||
{{< docref "components/sensor/dht" "DHT Sensor Guide" >}} <!-- Uses custom text for the link -->
|
||||
{{< docref "components/sensor/dht#configuration" >}} <!-- Links to a specific anchor on the page -->
|
||||
|
||||
Parameters:
|
||||
1. path (required) - The path to the target page, can include an anchor with #
|
||||
2. custom text (optional) - Custom text to use for the link (defaults to target page title)
|
||||
3. An optional boolean, which if true will suppress the broken link message
|
||||
|
||||
Notes:
|
||||
- The path should be relative to the content directory
|
||||
- If the path is not absolute (doesn't start with /), it will try to resolve in this order:
|
||||
1. Current page's directory
|
||||
2. /components directory
|
||||
3. Recursively in subfolders of /components
|
||||
- If the target page doesn't exist, it will display a "broken link" message
|
||||
*/}}
|
||||
{{- $path := .Get 0 -}}
|
||||
{{- $customText := .Get 1 -}}
|
||||
{{- $brokenOK := .Get 2 -}}
|
||||
{{- $anchor := "" -}}
|
||||
{{- if findRE "#" $path 1 -}}
|
||||
{{- $parts := split $path "#" -}}
|
||||
{{- $path = index $parts 0 -}}
|
||||
{{- $anchor = index $parts 1 -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Path resolution logic */}}
|
||||
{{- $page := .Page.GetPage $path -}}
|
||||
|
||||
{{/* If page not found and path is not absolute, try current directory */}}
|
||||
{{- if and (not $page) (not (hasPrefix $path "/")) -}}
|
||||
{{- $currentDir := path.Dir .Page.File.Path -}}
|
||||
{{- $newPath := path.Join $currentDir $path -}}
|
||||
{{- $page = .Page.GetPage $newPath -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* If still not found, try in components directory */}}
|
||||
{{- if and (not $page) (not (hasPrefix $path "/")) -}}
|
||||
{{- $componentsPath := path.Join "components" $path -}}
|
||||
{{- $page = .Page.GetPage $componentsPath -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* If still not found, try to find recursively in component subdirectories */}}
|
||||
{{- if and (not $page) (not (hasPrefix $path "/")) -}}
|
||||
{{/* Get all component pages */}}
|
||||
{{- $componentSection := .Site.GetPage "section" "components" -}}
|
||||
{{- if $componentSection -}}
|
||||
{{/* Recursive function to search in component pages */}}
|
||||
{{- $findInSection := false -}}
|
||||
{{- range $componentSection.Sections -}}
|
||||
{{/* Check if the current section has the page */}}
|
||||
{{- $sectionPath := path.Join .File.Dir $path -}}
|
||||
{{- $foundPage := $.Page.GetPage $sectionPath -}}
|
||||
{{- if $foundPage -}}
|
||||
{{- $page = $foundPage -}}
|
||||
{{- $findInSection = true -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* If not found, check in the section's pages */}}
|
||||
{{- if not $findInSection -}}
|
||||
{{- range .Pages -}}
|
||||
{{- if eq (path.Base .File.Path | replaceRE "\\.(md|html)$" "") (path.Base $path | replaceRE "\\.(md|html)$" "") -}}
|
||||
{{- $page = . -}}
|
||||
{{- $findInSection = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* If still not found, check in subsections recursively */}}
|
||||
{{- if not $findInSection -}}
|
||||
{{- range .Sections -}}
|
||||
{{- $sectionPath := path.Join .File.Dir $path -}}
|
||||
{{- $foundPage := $.Page.GetPage $sectionPath -}}
|
||||
{{- if $foundPage -}}
|
||||
{{- $page = $foundPage -}}
|
||||
{{- $findInSection = true -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Check in the subsection's pages */}}
|
||||
{{- if not $findInSection -}}
|
||||
{{- range .Pages -}}
|
||||
{{- if eq (path.Base .File.Path | replaceRE "\\.(md|html)$" "") (path.Base $path | replaceRE "\\.(md|html)$" "") -}}
|
||||
{{- $page = . -}}
|
||||
{{- $findInSection = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if $page -}}
|
||||
{{- $displayText := $customText -}}
|
||||
{{- if not $displayText -}}
|
||||
{{- $displayText = $page.Title -}}
|
||||
{{- end -}}
|
||||
{{- if $anchor -}}
|
||||
<a href="{{- $page.RelPermalink -}}#{{ $anchor }}">{{ $displayText }}</a>
|
||||
{{- else -}}
|
||||
<a href="{{- $page.RelPermalink -}}">{{ $displayText }}</a>
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- if $brokenOK -}}
|
||||
<span>{{- $customText -}}</span>
|
||||
{{- else -}}
|
||||
{{- if eq hugo.Environment "development" -}}
|
||||
{{ warnf "Unresolved anchor '%s' in page '%s'" $path .Page.File.Path }}
|
||||
<a href="{{ $path }}" class="unresolved-anchor"{{ with .Title }} title="{{ . }}"{{ end }}>{{ $customText }}</a>
|
||||
{{- else -}}
|
||||
{{ errorf "Unresolved anchor '%s' in page '%s'" $path .Page.File.Path }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
17
themes/esphome-theme/layouts/shortcodes/feature-grid.html
Normal file
17
themes/esphome-theme/layouts/shortcodes/feature-grid.html
Normal file
@ -0,0 +1,17 @@
|
||||
{{ $features := .Inner | transform.Unmarshal }}
|
||||
|
||||
<div class="feature-grid">
|
||||
{{ range $features }}
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
{{ if hasPrefix .icon "fa-" }}
|
||||
<i aria-hidden="true" class="fas {{ .icon }}"></i>
|
||||
{{ else }}
|
||||
{{ os.ReadFile (printf "static/images/icons/%s.svg" .icon) | safeHTML }}
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="feature-text">{{ .title }}</div>
|
||||
<p>{{ .description | safeHTML }}</p>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
@ -0,0 +1,27 @@
|
||||
{{ $items := .Inner | transform.Unmarshal }}
|
||||
|
||||
<div class="getting-started-grid">
|
||||
{{ range $items }}
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
{{ if hasPrefix .icon "fa-" }}
|
||||
<i aria-hidden="true" class="fas {{ .icon }}"></i>
|
||||
{{ else }}
|
||||
{{ os.ReadFile (printf "static/images/icons/%s.svg" .icon) | safeHTML }}
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="getting-started-heading">{{ .title }}</div>
|
||||
<p>{{ .description | safeHTML }}</p>
|
||||
{{ if .steps }}
|
||||
<ol>
|
||||
{{ range .steps }}
|
||||
<li>{{ . | safeHTML }}</li>
|
||||
{{ end }}
|
||||
</ol>
|
||||
{{ end }}
|
||||
{{ if .url }}
|
||||
<a href="{{ .url }}" class="btn btn-primary">{{ .button_text }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
9
themes/esphome-theme/layouts/shortcodes/ghuser.html
Normal file
9
themes/esphome-theme/layouts/shortcodes/ghuser.html
Normal file
@ -0,0 +1,9 @@
|
||||
{{/*
|
||||
Shortcode: ghuser
|
||||
Usage: {{< ghuser name="octocat" >}}
|
||||
Output: A link to the specified GitHub user profile, displaying the username (or custom text if provided).
|
||||
Optional param: text (if you want to show something different than the username)
|
||||
*/}}
|
||||
{{- $name := .Get "name" -}}
|
||||
{{- $text := .Get "text" | default (printf "@%s" $name) -}}
|
||||
<a href="https://github.com/{{ $name }}" target="_blank" rel="noopener noreferrer">{{ $text }}</a>
|
||||
22
themes/esphome-theme/layouts/shortcodes/html_file.html
Normal file
22
themes/esphome-theme/layouts/shortcodes/html_file.html
Normal file
@ -0,0 +1,22 @@
|
||||
{{/*
|
||||
HTML_FILE SHORTCODE
|
||||
Read a file and insert as html inside a div
|
||||
|
||||
Usage:
|
||||
{{< html_file file="example.html" class="examlple-class" >}}
|
||||
|
||||
Parameters:
|
||||
- file (required) - The file to read and insert as html
|
||||
- class (optional) - CSS class to apply to the enclosing div
|
||||
|
||||
*/}}
|
||||
{{- $file := .Get "file" -}}
|
||||
{{- $class := .Get "class" -}}
|
||||
{{- if $class -}}
|
||||
<div class="{{ $class }}">
|
||||
{{- end -}}
|
||||
{{ $content := os.ReadFile (printf "static/%s" $file) | safeHTML }}
|
||||
{{- $content -}}
|
||||
{{- if $class -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
34
themes/esphome-theme/layouts/shortcodes/img.html
Normal file
34
themes/esphome-theme/layouts/shortcodes/img.html
Normal file
@ -0,0 +1,34 @@
|
||||
{{/*
|
||||
IMG SHORTCODE
|
||||
Displays an image with optional caption, width, height, and CSS class.
|
||||
Automatically searches for images in the local component directory, global images directory, or uses absolute URLs.
|
||||
|
||||
Usage:
|
||||
{{< img src="example.jpg" alt="Example image" caption="This is an example" width="500" class="center" >}}
|
||||
|
||||
Parameters:
|
||||
- src (required) - The image source path or URL
|
||||
- alt (optional) - Alt text for the image (default: "Image")
|
||||
- caption (optional) - Caption text to display below the image
|
||||
- class (optional) - CSS class to apply to the figure element
|
||||
- width (optional) - Width of the image
|
||||
- height (optional) - Height of the image
|
||||
|
||||
Notes:
|
||||
- The shortcode will first look for images in the component's own images directory
|
||||
- If not found there, it will look in the global images directory
|
||||
- If still not found, it will use the src as provided (for external URLs)
|
||||
*/}}
|
||||
{{- $src := .Get "src" -}}
|
||||
{{- $alt := .Get "alt" | default "Image" -}}
|
||||
{{- $caption := .Get "caption" -}}
|
||||
{{- $class := .Get "class" -}}
|
||||
{{- $width := .Get "width" -}}
|
||||
{{- $height := .Get "height" -}}
|
||||
|
||||
{{- $style := "" -}}
|
||||
{{- if $width -}}{{- $style = printf "%swidth:%spx;" $style $width -}}{{- end -}}
|
||||
{{- if $height -}}{{- $style = printf "%sheight:%spx;" $style $height -}}{{- end -}}
|
||||
{{- if $style -}}{{- $style = printf " style=\"%s\"" $style | safeHTMLAttr -}}{{- end -}}
|
||||
{{ partial "image.html" (dict "src" $src "page" .Page "style" $style "width" $width) }}
|
||||
{{- .Page.Store.Set "hasImg" true -}}
|
||||
79
themes/esphome-theme/layouts/shortcodes/imgtable.html
Normal file
79
themes/esphome-theme/layouts/shortcodes/imgtable.html
Normal file
@ -0,0 +1,79 @@
|
||||
{{/*
|
||||
IMGTABLE SHORTCODE
|
||||
Creates a grid of component cards with images, titles, and optional descriptions that link to other pages.
|
||||
|
||||
Usage with block content (preferred):
|
||||
{{< imgtable >}}
|
||||
Title 1, path/to/page1, image1.png
|
||||
Title 2, path/to/page2, image2.png, dark-invert
|
||||
{{< /imgtable >}}
|
||||
|
||||
Legacy usage with positional parameters:
|
||||
{{< imgtable "Title" "/path/to/page" "image.png" "css-class" >}}
|
||||
|
||||
Notes:
|
||||
- Each line in the block content should contain 3-4 comma-separated values
|
||||
- The format is: Title, Link, Image, [Optional CSS class]
|
||||
- Images are searched for in the following order:
|
||||
1. Component's own images directory
|
||||
2. Global images directory
|
||||
3. Used as-is (for external URLs)
|
||||
- For dark mode compatibility, add "dark-invert" as the CSS class to invert the image in dark mode
|
||||
*/}}
|
||||
|
||||
<div class="component-grid">
|
||||
{{ if .Inner }}
|
||||
{{ $opts := dict "targetType" "slice" "delimiter" "," "lazyQuotes" true }}
|
||||
{{ $data := transform.Unmarshal $opts .Inner }}
|
||||
{{ range $data }}
|
||||
{{ $title := trim (index . 0) " " }}
|
||||
{{ $link := trim (index . 1) " " | strings.TrimSuffix "index" }}
|
||||
{{ $image := trim (index . 2) " " }}
|
||||
{{ $class := trim (index . 3) " " }}
|
||||
|
||||
|
||||
{{- $style := "" -}}
|
||||
{{- if $class -}}{{- $style = printf " class=\"%s\"" $class | safeHTMLAttr -}}{{- end -}}
|
||||
|
||||
<div class="component-card">
|
||||
<a href="{{ $link | relURL }}">
|
||||
<div class="component-icon">
|
||||
{{ partial "image.html" (dict "src" $image "page" $.Page "style" $style "alt" $title) }}
|
||||
</div>
|
||||
<div class="component-name">{{ $title }}</div>
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ $title := .Get 0 }}
|
||||
{{ $link := .Get 1 | strings.TrimSuffix "index" }}
|
||||
{{ $image := .Get 2 }}
|
||||
{{ $class := .Get 3 }}
|
||||
|
||||
{{- $style := "" -}}
|
||||
{{- if $class -}}{{- $style = printf " class=\"%s\"" $class | safeHTMLAttr -}}{{- end -}}
|
||||
{{/* Image path handling similar to img shortcode */}}
|
||||
{{ $componentPath := printf "%s/images/%s" (path.Dir .Page.File.Path) $image }}
|
||||
{{ $globalPath := printf "images/%s" $image }}
|
||||
|
||||
{{ $imagePath := "" }}
|
||||
{{ if fileExists $componentPath }}
|
||||
{{ $imagePath = $componentPath }}
|
||||
{{ else if fileExists (printf "static/%s" $globalPath) }}
|
||||
{{ $imagePath = $globalPath }}
|
||||
{{ else }}
|
||||
{{ $imagePath = $image }}
|
||||
{{ end }}
|
||||
|
||||
<div class="component-card">
|
||||
<a href="{{ $link | relURL }}">
|
||||
<div class="component-icon">
|
||||
{{ partial "image.html" (dict "src" $image "page" $.Page "style" $style "alt" $title) }}
|
||||
</div>
|
||||
<div class="component-name">{{ $title }}</div>
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
</style>
|
||||
17
themes/esphome-theme/layouts/shortcodes/important.html
Normal file
17
themes/esphome-theme/layouts/shortcodes/important.html
Normal file
@ -0,0 +1,17 @@
|
||||
{{/*
|
||||
Important SHORTCODE
|
||||
Creates an "important" admonition box to highlight important cautions or potential issues.
|
||||
|
||||
Usage:
|
||||
{{< important >}}
|
||||
Incorrect wiring may damage your device. Double-check connections before powering on.
|
||||
You can include **Markdown** formatting within the block.
|
||||
{{< /important >}}
|
||||
|
||||
Content:
|
||||
The content between the opening and closing shortcode tags will be displayed inside the block.
|
||||
*/}}
|
||||
<div class="admonition important">
|
||||
<p class="admonition-title">Important</p>
|
||||
{{ .Inner | .Page.RenderString}}
|
||||
</div>
|
||||
18
themes/esphome-theme/layouts/shortcodes/math.html
Normal file
18
themes/esphome-theme/layouts/shortcodes/math.html
Normal file
@ -0,0 +1,18 @@
|
||||
{{/*
|
||||
MATH SHORTCODE
|
||||
Formats equations
|
||||
|
||||
Usage:
|
||||
{{< math >}}
|
||||
c = \\pm\\sqrt{a^2 + b^2}"
|
||||
{{< /math >}}
|
||||
|
||||
Content:
|
||||
The content between the opening and closing shortcode tags will be formatted as maths
|
||||
*/}}
|
||||
{{ $nl := printf "\n" }}
|
||||
|
||||
$$
|
||||
{{ .Inner }}
|
||||
$$
|
||||
{{ .Page.Store.Set "hasMath" true }}
|
||||
17
themes/esphome-theme/layouts/shortcodes/note.html
Normal file
17
themes/esphome-theme/layouts/shortcodes/note.html
Normal file
@ -0,0 +1,17 @@
|
||||
{{/*
|
||||
NOTE SHORTCODE
|
||||
Creates a note admonition box to highlight important information.
|
||||
|
||||
Usage:
|
||||
{{< note >}}
|
||||
This is important information that the reader should pay attention to.
|
||||
You can include **Markdown** formatting within the note.
|
||||
{{< /note >}}
|
||||
|
||||
Content:
|
||||
The content between the opening and closing shortcode tags will be displayed inside the note box.
|
||||
*/}}
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
{{ .Inner | .Page.RenderString }}
|
||||
</div>
|
||||
16
themes/esphome-theme/layouts/shortcodes/option.html
Normal file
16
themes/esphome-theme/layouts/shortcodes/option.html
Normal file
@ -0,0 +1,16 @@
|
||||
{{/*
|
||||
OPTION SHORTCODE
|
||||
Creates an option block
|
||||
|
||||
Usage:
|
||||
{{< option "--help|-h" >}}
|
||||
This is the help option.
|
||||
{{< /option >}}
|
||||
|
||||
Content:
|
||||
The content between the opening and closing shortcode tags will be displayed inside the option block.
|
||||
*/}}
|
||||
<div class="option">
|
||||
<div class="option-title"><code>{{ .Get 0 }}</code></div>
|
||||
{{ .Inner | .Page.RenderString }}
|
||||
</div>
|
||||
9
themes/esphome-theme/layouts/shortcodes/pr.html
Normal file
9
themes/esphome-theme/layouts/shortcodes/pr.html
Normal file
@ -0,0 +1,9 @@
|
||||
{{/*
|
||||
Shortcode: pr
|
||||
Usage: {{< pr number="123" repo="esphome" >}}
|
||||
Output: A link to the specified pull request
|
||||
Optional param: repo (default: "esphome")
|
||||
*/}}
|
||||
{{- $number := .Get "number" -}}
|
||||
{{- $repo := .Get "repo" | default "esphome" -}}
|
||||
<a href="https://github.com/esphome/{{ $repo }}/pull/{{ $number }}" target="_blank" rel="noopener noreferrer">{{ $repo }}#{{ $number }}</a>
|
||||
8
themes/esphome-theme/layouts/shortcodes/redirect.html
Normal file
8
themes/esphome-theme/layouts/shortcodes/redirect.html
Normal file
@ -0,0 +1,8 @@
|
||||
{{/*
|
||||
redirect shortcode: usage {{< redirect url="/some/path" >}}
|
||||
Outputs a meta refresh and canonical link for SEO-friendly redirects.
|
||||
*/}}
|
||||
{{ $url := .Get "url" | strings.TrimSuffix "index.html" | strings.TrimSuffix ".html" }}
|
||||
<meta http-equiv="refresh" content="0; url={{ $url }}">
|
||||
<link rel="canonical" href="{{ $url }}">
|
||||
<p>You are being redirected to <a href="{{ $url }}">{{ $url }}</a>...</p>
|
||||
@ -0,0 +1 @@
|
||||
{{ partial "render-automations.html" . }}
|
||||
41
themes/esphome-theme/layouts/shortcodes/seo.html
Normal file
41
themes/esphome-theme/layouts/shortcodes/seo.html
Normal file
@ -0,0 +1,41 @@
|
||||
{{/*
|
||||
SEO SHORTCODE
|
||||
Adds SEO metadata tags to the page for better search engine optimization and social media sharing.
|
||||
This shortcode should be placed in the head section of your page or template.
|
||||
|
||||
Usage:
|
||||
{{< seo description="Detailed guide for setting up the DHT sensor with ESPHome" image="dht-sensor.jpg" >}}
|
||||
|
||||
Parameters:
|
||||
- description (optional) - Custom meta description for the page (falls back to page summary if not provided)
|
||||
- image (optional) - Image to use for social media sharing (will be looked up in the images directory)
|
||||
|
||||
Notes:
|
||||
- This adds Open Graph and Twitter Card metadata for better social media sharing
|
||||
- The page title is automatically used from the page's front matter
|
||||
*/}}
|
||||
{{ $description := .Get "description" }}
|
||||
{{ $image := .Get "image" }}
|
||||
{{ $seo := .Page.Param "seo" }}
|
||||
{{ with $seo }}
|
||||
{{ $description = $seo.description | default $description }}
|
||||
{{ $image = $seo.image | default $image }}
|
||||
{{ end }}
|
||||
|
||||
{{ with $description | default .Page.Title }}
|
||||
<meta name="description" content="{{ . }}">
|
||||
{{ end }}
|
||||
|
||||
{{ with $image }}
|
||||
<meta property="og:image" content="{{ printf "images/%s" . | absURL }}">
|
||||
<meta name="twitter:image" content="{{ printf "images/%s" . | absURL }}">
|
||||
{{ end }}
|
||||
|
||||
<meta property="og:title" content="{{ .Page.Title }}">
|
||||
<meta property="og:description" content="{{ with $description }}{{ . }}{{ else }}{{ .Page.Summary }}{{ end }}">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="{{ .Page.Permalink }}">
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="{{ .Page.Title }}">
|
||||
<meta name="twitter:description" content="{{ with $description }}{{ . }}{{ else }}{{ .Page.Summary }}{{ end }}">
|
||||
17
themes/esphome-theme/layouts/shortcodes/tip.html
Normal file
17
themes/esphome-theme/layouts/shortcodes/tip.html
Normal file
@ -0,0 +1,17 @@
|
||||
{{/*
|
||||
TIP SHORTCODE
|
||||
Creates a tip admonition box to highlight helpful advice or best practices.
|
||||
|
||||
Usage:
|
||||
{{< tip >}}
|
||||
For best results, place the sensor away from heat sources.
|
||||
You can include **Markdown** formatting within the tip.
|
||||
{{< /tip >}}
|
||||
|
||||
Content:
|
||||
The content between the opening and closing shortcode tags will be displayed inside the tip box.
|
||||
*/}}
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Tip</p>
|
||||
{{- .Inner | .Page.RenderString -}}
|
||||
</div>
|
||||
18
themes/esphome-theme/layouts/shortcodes/warning.html
Normal file
18
themes/esphome-theme/layouts/shortcodes/warning.html
Normal file
@ -0,0 +1,18 @@
|
||||
{{/*
|
||||
WARNING SHORTCODE
|
||||
Creates a warning admonition box to highlight important cautions or potential issues.
|
||||
|
||||
Usage:
|
||||
{{< warning >}}
|
||||
Incorrect wiring may damage your device. Double-check connections before powering on.
|
||||
You can include **Markdown** formatting within the warning.
|
||||
{{< /warning >}}
|
||||
|
||||
Content:
|
||||
The content between the opening and closing shortcode tags will be displayed inside the warning box.
|
||||
*/}}
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Warning</p>
|
||||
{{ .Inner | .Page.RenderString}}
|
||||
</div>
|
||||
|
||||
13
themes/esphome-theme/theme.yaml
Normal file
13
themes/esphome-theme/theme.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
name: ESPHome Theme
|
||||
license: MIT
|
||||
licenselink: https://github.com/esphome/esphome-docs/LICENSE.md
|
||||
description: Custom theme for ESPHome documentation
|
||||
homepage: https://esphome.io/
|
||||
tags:
|
||||
- documentation
|
||||
- responsive
|
||||
features:
|
||||
- responsive
|
||||
- search
|
||||
- syntax highlighting
|
||||
min_version: 0.80.0
|
||||
Loading…
Reference in New Issue
Block a user