aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorGravatar Inverle <inverle@proton.me> 2025-12-04 20:06:21 +0100
committerGravatar GitHub <noreply@github.com> 2025-12-04 20:06:21 +0100
commit5e9c3617cac1e3eac246e2ae7df6f4b71c33d37c (patch)
tree435618816d2ccc5d29b21fa0c089f814972a2ce7 /docs
parent78e40c6fe3afe7f815ef9d32646610e2d5436ba3 (diff)
Improve layout of documentation page and add search feature (#8247)
* Improve layout of documentation page and add search feature Closes https://github.com/FreshRSS/FreshRSS/issues/7915, https://github.com/FreshRSS/FreshRSS/issues/5325 Also: anchor headings and fix building site locally * Further improvements * Set color of hyperlinks * Consistent styling of close aside button across devices * Mobile layout 600px -> 1200px * Add suffix to docs `<title>` * Note: titles of pages probably need to be improved, since currently they are just derived from the names of the first heading on every page * Add favicon * Improve font * Try to fix favicon not loading correctly on GH pages * Use local font * Attempt to fix GH pages * Final improvements * Copy to clipboard button * Support for nojs search * Dark mode * Load search.json (200KB json) only on search input focus * Keep scroll state of sidebar across navigations * Clickable images and CSP CSP so we avoid hotlinking resources and clickable images are useful for zooming on mobile for example * Fix typos * Disable Dark Reader extension if dark mode CSS is loaded * Support internationalisation (via language dropdown) * Add Gemfile.lock * Make CI build work with the custom plugin * Make menus closable with Esc * Fix typos CI * Suggestions * Use `ruby/setup-ruby` action in workflow for installing and caching gems. * Run build only when there are changes to `docs/` See: https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows?versionId=free-pro-team%40latest&productId=actions#running-your-workflow-only-when-a-push-to-specific-branches-occurs * Change font to `Open Sans` * Increase line height * Fix Liquid syntax error
Diffstat (limited to 'docs')
-rw-r--r--docs/Gemfile17
-rw-r--r--docs/Gemfile.lock150
-rw-r--r--docs/README.md13
-rw-r--r--docs/_config.yml50
-rw-r--r--docs/_includes/anchor_headings.html174
-rw-r--r--docs/_includes/docs.css210
-rw-r--r--docs/_includes/docs_nav.html77
-rw-r--r--docs/_includes/lang_dropdown.html15
-rw-r--r--docs/_layouts/default.html122
-rw-r--r--docs/assets/css/darkmode.css620
-rw-r--r--docs/assets/css/docs.css3
-rw-r--r--docs/assets/css/highlight.css282
-rw-r--r--docs/assets/css/normalize.css350
-rw-r--r--docs/assets/css/style.scss11
-rw-r--r--docs/assets/fonts/OpenSans.woff2bin0 -> 48320 bytes
-rw-r--r--docs/assets/js/docs.js63
-rw-r--r--docs/assets/js/simple-jekyll-search.min.js6
-rw-r--r--docs/en/developers/03_Running_tests.md2
-rw-r--r--docs/en/index.md4
-rw-r--r--docs/favicon.icobin0 -> 18102 bytes
-rw-r--r--docs/fr/index.md3
-rw-r--r--docs/index.md6
-rw-r--r--docs/search.en.json13
-rw-r--r--docs/search.fr.json13
24 files changed, 2145 insertions, 59 deletions
diff --git a/docs/Gemfile b/docs/Gemfile
new file mode 100644
index 000000000..76d06a741
--- /dev/null
+++ b/docs/Gemfile
@@ -0,0 +1,17 @@
+source "https://rubygems.org"
+
+gem "kramdown-parser-gfm"
+
+group :jekyll_plugins do
+ gem 'jekyll-coffeescript'
+ gem 'jekyll-commonmark-ghpages'
+ gem 'jekyll-gist'
+ gem 'jekyll-github-metadata'
+ gem 'jekyll-paginate'
+ gem 'jekyll-relative-links'
+ gem 'jekyll-optional-front-matter'
+ gem 'jekyll-readme-index'
+ gem 'jekyll-default-layout'
+ gem 'jekyll-titles-from-headings'
+ gem 'jekyll-i18n_tags'
+end
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
new file mode 100644
index 000000000..c6fd9379e
--- /dev/null
+++ b/docs/Gemfile.lock
@@ -0,0 +1,150 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ addressable (2.8.8)
+ public_suffix (>= 2.0.2, < 8.0)
+ coffee-script (2.4.1)
+ coffee-script-source
+ execjs
+ coffee-script-source (1.12.2)
+ colorator (1.1.0)
+ commonmarker (0.23.12)
+ concurrent-ruby (1.3.5)
+ csv (3.3.5)
+ em-websocket (0.5.3)
+ eventmachine (>= 0.12.9)
+ http_parser.rb (~> 0)
+ eventmachine (1.2.7)
+ execjs (2.10.0)
+ faraday (2.14.0)
+ faraday-net_http (>= 2.0, < 3.5)
+ json
+ logger
+ faraday-net_http (3.4.2)
+ net-http (~> 0.5)
+ ffi (1.17.2)
+ ffi (1.17.2-aarch64-linux-gnu)
+ ffi (1.17.2-aarch64-linux-musl)
+ ffi (1.17.2-arm-linux-gnu)
+ ffi (1.17.2-arm-linux-musl)
+ ffi (1.17.2-arm64-darwin)
+ ffi (1.17.2-x86-linux-gnu)
+ ffi (1.17.2-x86-linux-musl)
+ ffi (1.17.2-x86_64-darwin)
+ ffi (1.17.2-x86_64-linux-gnu)
+ ffi (1.17.2-x86_64-linux-musl)
+ forwardable-extended (2.6.0)
+ http_parser.rb (0.8.0)
+ i18n (1.14.7)
+ concurrent-ruby (~> 1.0)
+ jekyll (3.10.0)
+ addressable (~> 2.4)
+ colorator (~> 1.0)
+ csv (~> 3.0)
+ em-websocket (~> 0.5)
+ i18n (>= 0.7, < 2)
+ jekyll-sass-converter (~> 1.0)
+ jekyll-watch (~> 2.0)
+ kramdown (>= 1.17, < 3)
+ liquid (~> 4.0)
+ mercenary (~> 0.3.3)
+ pathutil (~> 0.9)
+ rouge (>= 1.7, < 4)
+ safe_yaml (~> 1.0)
+ webrick (>= 1.0)
+ jekyll-coffeescript (2.0.0)
+ coffee-script (~> 2.2)
+ coffee-script-source (~> 1.12)
+ jekyll-commonmark (1.4.0)
+ commonmarker (~> 0.22)
+ jekyll-commonmark-ghpages (0.5.1)
+ commonmarker (>= 0.23.7, < 1.1.0)
+ jekyll (>= 3.9, < 4.0)
+ jekyll-commonmark (~> 1.4.0)
+ rouge (>= 2.0, < 5.0)
+ jekyll-default-layout (0.1.5)
+ jekyll (>= 3.0, < 5.0)
+ jekyll-gist (1.5.0)
+ octokit (~> 4.2)
+ jekyll-github-metadata (2.16.1)
+ jekyll (>= 3.4, < 5.0)
+ octokit (>= 4, < 7, != 4.4.0)
+ jekyll-i18n_tags (1.0.0)
+ jekyll-optional-front-matter (0.3.2)
+ jekyll (>= 3.0, < 5.0)
+ jekyll-paginate (1.1.0)
+ jekyll-readme-index (0.3.0)
+ jekyll (>= 3.0, < 5.0)
+ jekyll-relative-links (0.7.0)
+ jekyll (>= 3.3, < 5.0)
+ jekyll-sass-converter (1.5.2)
+ sass (~> 3.4)
+ jekyll-titles-from-headings (0.5.3)
+ jekyll (>= 3.3, < 5.0)
+ jekyll-watch (2.2.1)
+ listen (~> 3.0)
+ json (2.16.0)
+ kramdown (2.5.1)
+ rexml (>= 3.3.9)
+ kramdown-parser-gfm (1.1.0)
+ kramdown (~> 2.0)
+ liquid (4.0.4)
+ listen (3.9.0)
+ rb-fsevent (~> 0.10, >= 0.10.3)
+ rb-inotify (~> 0.9, >= 0.9.10)
+ logger (1.7.0)
+ mercenary (0.3.6)
+ net-http (0.8.0)
+ uri (>= 0.11.1)
+ octokit (4.25.1)
+ faraday (>= 1, < 3)
+ sawyer (~> 0.9)
+ pathutil (0.16.2)
+ forwardable-extended (~> 2.6)
+ public_suffix (7.0.0)
+ rb-fsevent (0.11.2)
+ rb-inotify (0.11.1)
+ ffi (~> 1.0)
+ rexml (3.4.4)
+ rouge (3.30.0)
+ safe_yaml (1.0.5)
+ sass (3.7.4)
+ sass-listen (~> 4.0.0)
+ sass-listen (4.0.0)
+ rb-fsevent (~> 0.9, >= 0.9.4)
+ rb-inotify (~> 0.9, >= 0.9.7)
+ sawyer (0.9.3)
+ addressable (>= 2.3.5)
+ faraday (>= 0.17.3, < 3)
+ uri (1.1.1)
+ webrick (1.9.2)
+
+PLATFORMS
+ aarch64-linux-gnu
+ aarch64-linux-musl
+ arm-linux-gnu
+ arm-linux-musl
+ arm64-darwin
+ ruby
+ x86-linux-gnu
+ x86-linux-musl
+ x86_64-darwin
+ x86_64-linux-gnu
+ x86_64-linux-musl
+
+DEPENDENCIES
+ jekyll-coffeescript
+ jekyll-commonmark-ghpages
+ jekyll-default-layout
+ jekyll-gist
+ jekyll-github-metadata
+ jekyll-i18n_tags
+ jekyll-optional-front-matter
+ jekyll-paginate
+ jekyll-readme-index
+ jekyll-relative-links
+ jekyll-titles-from-headings
+ kramdown-parser-gfm
+
+BUNDLED WITH
+ 2.7.2
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 000000000..050eefc37
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,13 @@
+This is the documentation deployed by GitHub Pages.
+
+To build and serve locally, first install the necessary packages:
+```sh
+bundle install
+```
+
+Then serve:
+```sh
+bundle exec jekyll serve -H 127.0.0.1 --watch --incremental
+```
+
+The documentation should be reachable at <http://127.0.0.1:4000/FreshRSS/>.
diff --git a/docs/_config.yml b/docs/_config.yml
index 7f8f6132a..e90f6bd2d 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,9 +1,53 @@
-theme: jekyll-theme-cayman
title: FreshRSS
description: Documentation center
+baseurl: /FreshRSS
logo: /img/FreshRSS-logo.png
-show_downloads: true
include: [contributing.md]
-exclude: [CHANGELOG*.md]
+exclude: [CHANGELOG*.md, README.md, vendor]
+
+defaults:
+ -
+ scope:
+ path: "/en/*"
+ values:
+ lang: "en"
+ -
+ scope:
+ path: "/fr/*"
+ values:
+ lang: "fr"
+
+translations:
+ en:
+ back_to_freshrss: Back to freshrss.org
+ search_docs: Search docs…
+ search: Search
+ toggle_aside: Toggle sidebar
+ close: Close
+ choose_language: Choose language
+ copy_to_clipboard: Copy to clipboard
+ fr:
+ back_to_freshrss: Retour à freshrss.org
+ search_docs: Rechercher dans la documentation…
+ search: Rechercher
+ toggle_aside: Afficher/masquer le panneau
+ close: Fermer
+ choose_language: Choisir la langue
+ copy_to_clipboard: Copier dans le presse-papiers
+
+plugins:
+ # gh
+ - jekyll-coffeescript
+ - jekyll-commonmark-ghpages
+ - jekyll-gist
+ - jekyll-github-metadata
+ - jekyll-paginate
+ - jekyll-relative-links
+ - jekyll-optional-front-matter
+ - jekyll-readme-index
+ - jekyll-default-layout
+ - jekyll-titles-from-headings
+ # custom
+ - jekyll-i18n_tags
diff --git a/docs/_includes/anchor_headings.html b/docs/_includes/anchor_headings.html
new file mode 100644
index 000000000..ee5c2a640
--- /dev/null
+++ b/docs/_includes/anchor_headings.html
@@ -0,0 +1,174 @@
+{% capture headingsWorkspace %}
+ {% comment %}
+ Copyright (c) 2018 Vladimir "allejo" Jimenez
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following
+ conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+ {% endcomment %}
+ {% comment %}
+ Version 1.0.13
+ https://github.com/allejo/jekyll-anchor-headings
+
+ "Be the pull request you wish to see in the world." ~Ben Balter
+
+ Usage:
+ {% include anchor_headings.html html=content anchorBody="#" %}
+
+ Parameters:
+ * html (string) - the HTML of compiled markdown generated by kramdown in Jekyll
+
+ Optional Parameters:
+ * beforeHeading (bool) : false - Set to true if the anchor should be placed _before_ the heading's content
+ * headerAttrs (string) : '' - Any custom HTML attributes that will be added to the heading tag; you may NOT use `id`;
+ the `%heading%` and `%html_id%` placeholders are available
+ * anchorAttrs (string) : '' - Any custom HTML attributes that will be added to the `<a>` tag; you may NOT use `href`, `class` or `title`;
+ the `%heading%` and `%html_id%` placeholders are available
+ * anchorBody (string) : '' - The content that will be placed inside the anchor; the `%heading%` placeholder is available
+ * anchorClass (string) : '' - The class(es) that will be used for each anchor. Separate multiple classes with a space
+ * anchorTitle (string) : '' - The `title` attribute that will be used for anchors
+ * h_min (int) : 1 - The minimum header level to build an anchor for; any header lower than this value will be ignored
+ * h_max (int) : 6 - The maximum header level to build an anchor for; any header greater than this value will be ignored
+ * bodyPrefix (string) : '' - Anything that should be inserted inside of the heading tag _before_ its anchor and content
+ * bodySuffix (string) : '' - Anything that should be inserted inside of the heading tag _after_ its anchor and content
+ * generateId (true) : false - Set to true if a header without id should generate an id to use.
+
+ Output:
+ The original HTML with the addition of anchors inside of all of the h1-h6 headings.
+ {% endcomment %}
+
+ {% assign minHeader = include.h_min | default: 1 %}
+ {% assign maxHeader = include.h_max | default: 6 %}
+ {% assign beforeHeading = include.beforeHeading %}
+ {% assign headerAttrs = include.headerAttrs %}
+ {% assign nodes = include.html | split: '<h' %}
+
+ {% capture edited_headings %}{% endcapture %}
+
+ {% for _node in nodes %}
+ {% capture node %}{{ _node | strip }}{% endcapture %}
+
+ {% if node == "" %}
+ {% continue %}
+ {% endif %}
+
+ {% assign nextChar = node | replace: '"', '' | strip | slice: 0, 1 %}
+ {% assign headerLevel = nextChar | times: 1 %}
+
+ <!-- If the level is cast to 0, it means it's not a h1-h6 tag, so let's see if we need to fix it -->
+ {% if headerLevel == 0 %}
+ <!-- Split up the node based on closing angle brackets and get the first one. -->
+ {% assign firstChunk = node | split: '>' | first %}
+
+ <!-- If the first chunk does NOT contain a '<', that means we've broken another HTML tag that starts with 'h' -->
+ {% unless firstChunk contains '<' %}
+ {% capture node %}<h{{ node }}{% endcapture %}
+ {% endunless %}
+
+ {% capture edited_headings %}{{ edited_headings }}{{ node }}{% endcapture %}
+ {% continue %}
+ {% endif %}
+
+ {% capture _closingTag %}</h{{ headerLevel }}>{% endcapture %}
+ {% assign _workspace = node | split: _closingTag %}
+ {% capture _hAttrToStrip %}{{ _workspace[0] | split: '>' | first }}>{% endcapture %}
+ {% assign header = _workspace[0] | replace: _hAttrToStrip, '' %}
+ {% assign escaped_header = header | strip_html | strip %}
+
+ {% assign _classWorkspace = _workspace[0] | split: 'class="' %}
+ {% assign _classWorkspace = _classWorkspace[1] | split: '"' %}
+ {% assign _html_class = _classWorkspace[0] %}
+
+ {% if _html_class contains "no_anchor" %}
+ {% assign skip_anchor = true %}
+ {% else %}
+ {% assign skip_anchor = false %}
+ {% endif %}
+
+ {% assign _idWorkspace = _workspace[0] | split: 'id="' %}
+ {% if _idWorkspace[1] %}
+ {% assign _idWorkspace = _idWorkspace[1] | split: '"' %}
+ {% assign html_id = _idWorkspace[0] %}
+ {% assign h_attrs = headerAttrs %}
+ {% elsif include.generateId %}
+ <!-- If the header did not have an id we create one. -->
+ {% assign html_id = escaped_header | slugify %}
+ {% if html_id == "" %}
+ {% assign html_id = false %}
+ {% endif %}
+ <!-- Append the generated id to other potential header attributes. -->
+ {% capture h_attrs %}{{ headerAttrs }} id="%html_id%"{% endcapture %}
+ {% endif %}
+
+ <!-- Build the anchor to inject for our heading -->
+ {% capture anchor %}{% endcapture %}
+
+ {% if skip_anchor == false and html_id and headerLevel >= minHeader and headerLevel <= maxHeader %}
+ {% if h_attrs %}
+ {% capture _hAttrToStrip %}{{ _hAttrToStrip | split: '>' | first }} {{ h_attrs | strip | replace: '%heading%', escaped_header | replace: '%html_id%', html_id }}>{% endcapture %}
+ {% endif %}
+
+ {% capture anchor %}href="#{{ html_id }}"{% endcapture %}
+
+ {% if include.anchorClass %}
+ {% capture anchor %}{{ anchor }} class="{{ include.anchorClass }}"{% endcapture %}
+ {% endif %}
+
+ {% if include.anchorTitle %}
+ {% capture anchor %}{{ anchor }} title="{{ include.anchorTitle | replace: '%heading%', escaped_header }}"{% endcapture %}
+ {% endif %}
+
+ {% if include.anchorAttrs %}
+ {% capture anchor %}{{ anchor }} {{ include.anchorAttrs | replace: '%heading%', escaped_header | replace: '%html_id%', html_id }}{% endcapture %}
+ {% endif %}
+
+ {% capture anchor %}<a {{ anchor }}>{{ include.anchorBody | replace: '%heading%', escaped_header | default: '' }}</a>{% endcapture %}
+
+ <!-- In order to prevent adding extra space after a heading, we'll let the 'anchor' value contain it -->
+ {% if beforeHeading %}
+ {% capture anchor %}{{ anchor }} {% endcapture %}
+ {% else %}
+ {% capture anchor %} {{ anchor }}{% endcapture %}
+ {% endif %}
+ {% endif %}
+
+ {% capture new_heading %}
+<h{{ _hAttrToStrip }}
+ {{ include.bodyPrefix }}
+ {% if beforeHeading %}
+ {{ anchor }}{{ header }}
+ {% else %}
+ {{ header }}{{ anchor }}
+ {% endif %}
+ {{ include.bodySuffix }}
+</h{{ headerLevel }}>
+ {% endcapture %}
+
+ <!--
+ If we have content after the `</hX>` tag, then we'll want to append that here so we don't lost any content.
+ -->
+ {% assign chunkCount = _workspace | size %}
+ {% if chunkCount > 1 %}
+ {% capture new_heading %}{{ new_heading }}{{ _workspace | last }}{% endcapture %}
+ {% endif %}
+
+ {% capture edited_headings %}{{ edited_headings }}{{ new_heading }}{% endcapture %}
+ {% endfor %}
+{% endcapture %}{% assign headingsWorkspace = '' %}{{ edited_headings | strip }}
diff --git a/docs/_includes/docs.css b/docs/_includes/docs.css
new file mode 100644
index 000000000..b1dbb1b08
--- /dev/null
+++ b/docs/_includes/docs.css
@@ -0,0 +1,210 @@
+:root {
+ --aside-width: 300px;
+}
+
+@font-face {
+ font-family: 'Open Sans';
+ font-style: normal;
+ font-weight: 300 800;
+ font-stretch: 100%;
+ font-display: swap;
+ src: url('{{ "/assets/fonts/OpenSans.woff2" | relative_url }}') format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+/* layout */
+html, body {
+ overflow-x: hidden;
+}
+
+body {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ line-height: 1.5;
+ min-height: 100vh;
+}
+
+aside {
+ padding: 1rem;
+ width: var(--aside-width);
+ height: 100vh;
+ border-right: 1px solid black;
+ position: fixed;
+ top: 0;
+ left: 0;
+ margin-right: 1rem;
+}
+
+aside > nav.docs {
+ overflow-y: scroll;
+ max-height: 90vh;
+}
+
+aside > nav.docs > ul {
+ margin-bottom: 10rem; /* extra scroll space */
+}
+
+aside > nav.docs ul {
+ padding-left: 1rem;
+ margin-right: 1rem;
+}
+
+main {
+ margin-left: calc(var(--aside-width) + 50px);
+ max-width: 70vw;
+}
+
+img {
+ max-width: 100%;
+}
+
+section.search {
+ margin-top: 1rem;
+ border-bottom: 1px solid black;
+}
+
+div.nojs-search {
+ margin-bottom: 1rem;
+}
+
+nav.mobile-nav {
+ display: none;
+}
+
+nav.lang-dropdown {
+ display: none;
+}
+
+div.lang-dropdown:target nav {
+ padding: 0.2rem;
+ display: block;
+ border: 1px solid black;
+ border-radius: 6px;
+ position: absolute;
+ background-color: white;
+ z-index: 100;
+ text-wrap: nowrap;
+}
+
+div#mobile-language:target nav {
+ right: 0.2rem;
+}
+
+div.lang-dropdown:target nav a:hover {
+ background-color: silver;
+}
+
+div.lang-dropdown:target a.close {
+ display: block;
+ cursor: default;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ z-index: 99; /* just below language dropdown */
+}
+
+nav.lang-dropdown ul {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+}
+
+/* mobile layout */
+
+@media (max-width: 1200px) {
+ aside {
+ display: none;
+ }
+
+ aside a.close {
+ display: block;
+ }
+
+ aside a.lang-btn {
+ display: none;
+ }
+
+ html:has(aside:target) {
+ overflow-y: hidden;
+ }
+
+ body:has(aside:target) > a.close {
+ display: block;
+ cursor: default;
+ position: fixed;
+ top: 0;
+ background-color: black;
+ opacity: 0.3;
+ width: 100vw;
+ height: 100vh;
+ z-index: 99; /* just below aside */
+ }
+
+ aside:target {
+ display: block;
+ position: fixed;
+ background-color: white;
+ z-index: 100;
+ }
+
+ main {
+ margin-left: 1rem;
+ max-width: 90vw;
+ }
+
+ nav.mobile-nav {
+ display: block;
+ border-bottom: 1px dashed black;
+ }
+
+ nav.mobile-nav > a.toggle-aside {
+ color: black;
+ -webkit-tap-highlight-color: transparent;
+ }
+
+ nav.mobile-nav > a.toggle-aside svg {
+ width: 2rem;
+ height: 2rem;
+ }
+}
+
+/* general styling */
+a {
+ text-decoration: none;
+ color: #00e;
+}
+
+a.close {
+ display: none;
+}
+
+aside a.close {
+ float: right;
+ color: #f00;
+ font-size: 24px;
+ position: relative;
+ bottom: 4px;
+}
+
+table, th, tr, td {
+ border: 1px solid black;
+ border-collapse: collapse;
+}
+
+th, tr, td {
+ padding: 0.3rem;
+}
+
+button.copy {
+ margin: 0.5rem;
+ float: right;
+}
+
+div.lang-dropdown {
+ float: right;
+}
+
+a.lang-btn {
+ color: black;
+}
diff --git a/docs/_includes/docs_nav.html b/docs/_includes/docs_nav.html
new file mode 100644
index 000000000..3b3083c1e
--- /dev/null
+++ b/docs/_includes/docs_nav.html
@@ -0,0 +1,77 @@
+{% if page.lang == 'en' %}
+ <ul>
+ <li><a href="{{ '/en/' | relative_url }}">Home</a></li>
+ <li>
+ <a href="{{ '/en/users/02_First_steps.html' | relative_url }}">User documentation</a>
+ <ul>
+ {% for page in site.pages %}
+ {% if page.url contains '/en/users/' and page.title != nil and page.url != "/en/users/02_First_steps.html" %}
+ <li><a href="{{ page.url | relative_url }}">{{ page.title | escape }}</a></li>
+ {% endif %}
+ {% endfor %}
+ </ul>
+ </li>
+ <li>
+ <a href="{{ '/en/admins/01_Index.html' | relative_url }}">Administrator documentation</a>
+ <ul>
+ {% for page in site.pages %}
+ {% if page.url contains '/en/admins/' and page.url != "/en/admins/01_Index.html" and page.title != nil %}
+ <li><a href="{{ page.url | relative_url }}">{{ page.title | escape }}</a></li>
+ {% endif %}
+ {% endfor %}
+ </ul>
+ </li>
+ <li>
+ <a href="{{ '/en/developers/01_Index.html' | relative_url }}">Developer documentation</a>
+ <ul>
+ {% for page in site.pages %}
+ {% if page.url contains '/en/developers/' and page.url != "/en/developers/01_Index.html" and page.title != nil %}
+ <li><a href="{{ page.url | relative_url }}">{{ page.title | escape }}</a></li>
+ {% endif %}
+ {% endfor %}
+ </ul>
+ </li>
+ <li>
+ <a href="{{ '/en/contributing.html' | relative_url}}">Contributor guidelines</a>
+ </li>
+ </ul>
+{% elsif page.lang == 'fr' %}
+ <ul>
+ <li><a href="{{ '/fr/' | relative_url }}">Accueil</a></li>
+ <li>
+ <a href="{{ '/fr/users/02_First_steps.html' | relative_url }}">Documentation utilisateur</a>
+ <ul>
+ {% for page in site.pages %}
+ {% if page.url contains '/fr/users/' and page.title != nil and page.url != "/fr/users/02_First_steps.html" %}
+ <li><a href="{{ page.url | relative_url }}">{{ page.title | escape }}</a></li>
+ {% endif %}
+ {% endfor %}
+ </ul>
+ </li>
+ <!-- TODO: French doesn't have admin docs -->
+ <!-- <li> -->
+ <!-- <a href="{{ '/fr/admins/01_Index.html' | relative_url }}">Administrator documentation</a> -->
+ <!-- <ul> -->
+ <!-- {% for page in site.pages %} -->
+ <!-- {% if page.url contains '/fr/admins/' and page.url != "/fr/admins/01_Index.html" and page.title != nil %} -->
+ <!-- <li><a href="{{ page.url | relative_url }}">{{ page.title | escape }}</a></li> -->
+ <!-- {% endif %} -->
+ <!-- {% endfor %} -->
+ <!-- </ul> -->
+ <!-- </li> -->
+ <li>
+ <a href="{{ '/fr/developers/01_First_steps.html' | relative_url }}">Documentation développeur</a>
+ <ul>
+ {% for page in site.pages %}
+ {% if page.url contains '/fr/developers/' and page.url != "/fr/developers/01_First_steps.html" and page.title != nil %}
+ <li><a href="{{ page.url | relative_url }}">{{ page.title | escape }}</a></li>
+ {% endif %}
+ {% endfor %}
+ </ul>
+ </li>
+ <li>
+ <a href="{{ '/fr/contributing.html' | relative_url}}">Directives pour les contributeurs</a>
+ </li>
+ </ul>
+{% endif %}
+
diff --git a/docs/_includes/lang_dropdown.html b/docs/_includes/lang_dropdown.html
new file mode 100644
index 000000000..81dc62508
--- /dev/null
+++ b/docs/_includes/lang_dropdown.html
@@ -0,0 +1,15 @@
+<div class="lang-dropdown" id="{{ include.location }}-language">
+ <a class="lang-btn" href="#{{ include.location }}-language" title="{%t choose_language %}">
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 16 16">
+ <path d="M4.545 6.714 4.11 8H3l1.862-5h1.284L8 8H6.833l-.435-1.286zm1.634-.736L5.5 3.956h-.049l-.679 2.022z"></path>
+ <path d="M0 2a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v3h3a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-3H2a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zm7.138 9.995q.289.451.63.846c-.748.575-1.673 1.001-2.768 1.292.178.217.451.635.555.867 1.125-.359 2.08-.844 2.886-1.494.777.665 1.739 1.165 2.93 1.472.133-.254.414-.673.629-.89-1.125-.253-2.057-.694-2.82-1.284.681-.747 1.222-1.651 1.621-2.757H14V8h-3v1.047h.765c-.318.844-.74 1.546-1.272 2.13a6 6 0 0 1-.415-.492 2 2 0 0 1-.94.31"></path>
+ </svg>
+ </a>
+ <a class="close" href="#close"></a>
+ <nav class="lang-dropdown">
+ <ul>
+ <li><a href="{{ '/en/' | relative_url }}">English (en)</a></li>
+ <li><a href="{{ '/fr/' | relative_url }}">Français (fr)</a></li>
+ </ul>
+ </nav>
+</div>
diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html
index 9e30b6eae..252e1024d 100644
--- a/docs/_layouts/default.html
+++ b/docs/_layouts/default.html
@@ -1,50 +1,84 @@
<!DOCTYPE html>
-<html lang="{{ site.lang | default: "en-US" }}">
+<html lang="{{ page.lang | default: 'en' }}">
<head>
- <meta charset="UTF-8">
- <title>{{ page.title | default: site.title }}</title>
- <meta name="description" content="{{ page.description | default: site.description | default: site.github.project_tagline }}"/>
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <meta name="theme-color" content="#157878">
- <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
- <link rel="stylesheet" href="{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}">
+ <meta charset="UTF-8">
+ <title>{{ page.title | default: site.title }} · FreshRSS</title>
+ <meta name="description" content="{{ page.description | default: site.description | default: site.github.project_tagline }}">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self'; connect-src 'self'">
+
+ <link rel="stylesheet" href="{{ '/assets/css/docs.css?v=' | append: site.github.build_revision | relative_url }}">
+ <link rel="stylesheet" href="{{ '/assets/css/highlight.css?v=' | append: site.github.build_revision | relative_url }}">
+ <link rel="stylesheet" href="{{ '/assets/css/normalize.css?v=' | append: site.github.build_revision | relative_url }}">
+ <link rel="stylesheet" media="(prefers-color-scheme: dark)" href="{{ '/assets/css/darkmode.css?v=' | append: site.github.build_revision | relative_url}}">
+
+ <link rel="icon" href="{{ '/favicon.ico' | relative_url }}">
+
+ <script>
+ var i18n = {
+ "copy_to_clipboard": "{%t copy_to_clipboard %}"
+ };
+ </script>
+ <script src="{{ '/assets/js/docs.js?v=' | append: site.github.build_revision | relative_url }}"></script>
</head>
<body>
- <section class="page-header">
- <h1 class="project-name">
- <a href="{{ site.github.url }}">{{ site.title | default: site.github.repository_name }}</a>
- </h1>
- <h2 class="project-tagline">{{ site.description | default: site.github.project_tagline }}</h2>
- {% if site.github.is_project_page %}
- <a href="{{ site.github.repository_url }}" class="btn">View on GitHub</a>
- {% endif %}
- {% if site.show_downloads %}
- <a href="{{ site.github.zip_url }}" class="btn">Download .zip</a>
- <a href="{{ site.github.tar_url }}" class="btn">Download .tar.gz</a>
- {% endif %}
- </section>
-
- <section class="main-content">
- {{ content }}
-
- <footer class="site-footer">
- {% if site.github.is_project_page %}
- <span class="site-footer-owner"><a href="{{ site.github.repository_url }}">{{ site.github.repository_name }}</a> is maintained by <a href="{{ site.github.owner_url }}">{{ site.github.owner_name }}</a>.</span>
- {% endif %}
- <span class="site-footer-credits">This page was generated by <a href="https://pages.github.com">GitHub Pages</a>.</span>
- </footer>
- </section>
-
- {% if site.google_analytics %}
- <script type="text/javascript">
- (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
- (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
- m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
- })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-
- ga('create', '{{ site.google_analytics }}', 'auto');
- ga('send', 'pageview');
- </script>
- {% endif %}
+ <nav class="mobile-nav">
+ <a class="toggle-aside" href="#aside" title="{%t toggle_aside %}">
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
+ <path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5"/>
+ </svg>
+ </a>
+ {% include lang_dropdown.html location="mobile" %}
+ </nav>
+
+ <a class="close" href="#close"></a>
+
+ <aside id="aside">
+ <a href="https://freshrss.org/">&lt; {%t back_to_freshrss %}</a>
+ {% include lang_dropdown.html location="aside" %}
+ <a class="close" href="#close" title="{%t close %}">&times;</a>
+
+ <section class="search">
+ <noscript>
+ <style>div.js-search { display: none; }</style>
+ <div class="nojs-search">
+ <form action="https://duckduckgo.com/" method="get">
+ <input type="search" name="q" placeholder="{%t search_docs %}">
+ <input type="hidden" name="sites" value="freshrss.github.io">
+ <button type="submit">{%t search %}</button>
+ </form>
+ </div>
+ </noscript>
+
+ <div class="js-search">
+ <input type="text" id="search-input" placeholder="{%t search_docs %}">
+ <ul id="results-container"></ul>
+
+ <script src="{{ '/assets/js/simple-jekyll-search.min.js?v=' | append: site.github.build_revision | relative_url }}"></script>
+ <script>
+ const search = document.querySelector('#search-input');
+
+ function init_search() {
+ search.removeEventListener('focus', init_search);
+ SimpleJekyllSearch({
+ searchInput: search,
+ resultsContainer: document.querySelector('#results-container'),
+ json: '{{ "/search." | append: page.lang | append: ".json?v=" | append: site.github.build_revision | relative_url }}',
+ searchResultTemplate: '<li><a href="{url}">{title}</a></li>'
+ });
+ }
+
+ search.addEventListener('focus', init_search);
+ </script>
+ </div>
+ </section>
+ <nav class="docs">
+ {% include docs_nav.html %}
+ </nav>
+ </aside>
+
+ <main>
+ {% include anchor_headings.html html=content anchorBody="#" %}
+ </main>
</body>
</html>
diff --git a/docs/assets/css/darkmode.css b/docs/assets/css/darkmode.css
new file mode 100644
index 000000000..371ff1a2b
--- /dev/null
+++ b/docs/assets/css/darkmode.css
@@ -0,0 +1,620 @@
+/* stylelint-disable */
+/*! Dark reader generated CSS | Licensed under MIT https://github.com/darkreader/darkreader/blob/main/LICENSE */
+
+/* User-Agent Style */
+@layer {
+html {
+ background-color: var(--darkreader-background-ffffff, #181a1b) !important;
+}
+html {
+ color-scheme: dark !important;
+}
+iframe {
+ color-scheme: dark !important;
+}
+html, body {
+ background-color: var(--darkreader-background-ffffff, #181a1b);
+}
+html, body {
+ border-color: var(--darkreader-border-4c4c4c, #736b5e);
+ color: var(--darkreader-text-000000, #e8e6e3);
+}
+a {
+ color: var(--darkreader-text-0040ff, #3391ff);
+}
+table {
+ border-color: var(--darkreader-border-808080, #545b5e);
+}
+mark {
+ color: var(--darkreader-text-000000, #e8e6e3);
+}
+::placeholder {
+ color: var(--darkreader-text-a9a9a9, #b2aba1);
+}
+input:-webkit-autofill,
+textarea:-webkit-autofill,
+select:-webkit-autofill {
+ background-color: var(--darkreader-background-faffbd, #404400) !important;
+ color: var(--darkreader-text-000000, #e8e6e3) !important;
+}
+::selection {
+ background-color: var(--darkreader-background-0060d4, #004daa) !important;
+ color: var(--darkreader-text-ffffff, #e8e6e3) !important;
+}
+::-moz-selection {
+ background-color: var(--darkreader-background-0060d4, #004daa) !important;
+ color: var(--darkreader-text-ffffff, #e8e6e3) !important;
+}
+}
+
+/* Variables Style */
+:root {
+ --darkreader-neutral-background: var(--darkreader-background-ffffff, #181a1b);
+ --darkreader-neutral-text: var(--darkreader-text-000000, #e8e6e3);
+ --darkreader-selection-background: var(--darkreader-background-0060d4, #004daa);
+ --darkreader-selection-text: var(--darkreader-text-ffffff, #e8e6e3);
+}
+
+/* Modified CSS */
+:root {
+ --aside-width: 300px;
+}
+aside {
+ border-right-color: var(--darkreader-border-000000, #8c8273);
+}
+section.search {
+ border-bottom-color: var(--darkreader-border-000000, #8c8273);
+}
+div.lang-dropdown:target nav {
+ background-color: var(--darkreader-background-ffffff, #181a1b);
+ border-bottom-color: var(--darkreader-border-000000, #8c8273);
+ border-left-color: var(--darkreader-border-000000, #8c8273);
+ border-right-color: var(--darkreader-border-000000, #8c8273);
+ border-top-color: var(--darkreader-border-000000, #8c8273);
+}
+div.lang-dropdown:target nav a:hover {
+ background-color: var(--darkreader-background-c0c0c0, #3c4143);
+}
+@media (max-width: 1200px) {
+ body:has(aside:target) > a.close {
+ background-color: var(--darkreader-background-000000, #000000);
+ }
+ aside:target {
+ background-color: var(--darkreader-background-ffffff, #181a1b);
+ }
+ nav.mobile-nav {
+ border-bottom-color: var(--darkreader-border-000000, #8c8273);
+ }
+ nav.mobile-nav > a.toggle-aside {
+ color: var(--darkreader-text-000000, #e8e6e3);
+ }
+}
+a {
+ color: var(--darkreader-text-0000ee, #3d84ff);
+ text-decoration-color: currentcolor;
+}
+aside a.close {
+ color: var(--darkreader-text-ff0000, #ff1a1a);
+}
+table,
+th,
+tr,
+td {
+ border-bottom-color: var(--darkreader-border-000000, #8c8273);
+ border-left-color: var(--darkreader-border-000000, #8c8273);
+ border-right-color: var(--darkreader-border-000000, #8c8273);
+ border-top-color: var(--darkreader-border-000000, #8c8273);
+}
+a.lang-btn {
+ color: var(--darkreader-text-000000, #e8e6e3);
+}
+.highlight .cm {
+ color: var(--darkreader-text-999988, #a29a8e);
+}
+.highlight .cp {
+ color: var(--darkreader-text-999999, #a8a095);
+}
+.highlight .c1 {
+ color: var(--darkreader-text-999988, #a29a8e);
+}
+.highlight .cs {
+ color: var(--darkreader-text-999999, #a8a095);
+}
+.highlight .c,
+.highlight .ch,
+.highlight .cd,
+.highlight .cpf {
+ color: var(--darkreader-text-999988, #a29a8e);
+}
+.highlight .err {
+ background-color: var(--darkreader-background-e3d2d2, #3a2424);
+ color: var(--darkreader-text-a61717, #e95e5e);
+}
+.highlight .gd {
+ background-color: var(--darkreader-background-ffdddd, #470000);
+ color: var(--darkreader-text-000000, #e8e6e3);
+}
+.highlight .ge {
+ color: var(--darkreader-text-000000, #e8e6e3);
+}
+.highlight .gr {
+ color: var(--darkreader-text-aa0000, #ff5555);
+}
+.highlight .gh {
+ color: var(--darkreader-text-999999, #a8a095);
+}
+.highlight .gi {
+ background-color: var(--darkreader-background-ddffdd, #124700);
+ color: var(--darkreader-text-000000, #e8e6e3);
+}
+.highlight .go {
+ color: var(--darkreader-text-888888, #9d9488);
+}
+.highlight .gp {
+ color: var(--darkreader-text-555555, #b2aca2);
+}
+.highlight .gu {
+ color: var(--darkreader-text-aaaaaa, #b2aca2);
+}
+.highlight .gt {
+ color: var(--darkreader-text-aa0000, #ff5555);
+}
+.highlight .kc {
+ color: var(--darkreader-text-000000, #e8e6e3);
+}
+.highlight .kd {
+ color: var(--darkreader-text-000000, #e8e6e3);
+}
+.highlight .kn {
+ color: var(--darkreader-text-000000, #e8e6e3);
+}
+.highlight .kp {
+ color: var(--darkreader-text-000000, #e8e6e3);
+}
+.highlight .kr {
+ color: var(--darkreader-text-000000, #e8e6e3);
+}
+.highlight .kt {
+ color: var(--darkreader-text-445588, #8ba6c5);
+}
+.highlight .k,
+.highlight .kv {
+ color: var(--darkreader-text-000000, #e8e6e3);
+}
+.highlight .mf {
+ color: var(--darkreader-text-009999, #61ffff);
+}
+.highlight .mh {
+ color: var(--darkreader-text-009999, #61ffff);
+}
+.highlight .il {
+ color: var(--darkreader-text-009999, #61ffff);
+}
+.highlight .mi {
+ color: var(--darkreader-text-009999, #61ffff);
+}
+.highlight .mo {
+ color: var(--darkreader-text-009999, #61ffff);
+}
+.highlight .m,
+.highlight .mb,
+.highlight .mx {
+ color: var(--darkreader-text-009999, #61ffff);
+}
+.highlight .sa {
+ color: var(--darkreader-text-000000, #e8e6e3);
+}
+.highlight .sb {
+ color: var(--darkreader-text-dd1144, #ef3564);
+}
+.highlight .sc {
+ color: var(--darkreader-text-dd1144, #ef3564);
+}
+.highlight .sd {
+ color: var(--darkreader-text-dd1144, #ef3564);
+}
+.highlight .s2 {
+ color: var(--darkreader-text-dd1144, #ef3564);
+}
+.highlight .se {
+ color: var(--darkreader-text-dd1144, #ef3564);
+}
+.highlight .sh {
+ color: var(--darkreader-text-dd1144, #ef3564);
+}
+.highlight .si {
+ color: var(--darkreader-text-dd1144, #ef3564);
+}
+.highlight .sx {
+ color: var(--darkreader-text-dd1144, #ef3564);
+}
+.highlight .sr {
+ color: var(--darkreader-text-009926, #61ff88);
+}
+.highlight .s1 {
+ color: var(--darkreader-text-dd1144, #ef3564);
+}
+.highlight .ss {
+ color: var(--darkreader-text-990073, #ff61d8);
+}
+.highlight .s,
+.highlight .dl {
+ color: var(--darkreader-text-dd1144, #ef3564);
+}
+.highlight .na {
+ color: var(--darkreader-text-008080, #72ffff);
+}
+.highlight .bp {
+ color: var(--darkreader-text-999999, #a8a095);
+}
+.highlight .nb {
+ color: var(--darkreader-text-0086b3, #4fd3ff);
+}
+.highlight .nc {
+ color: var(--darkreader-text-445588, #8ba6c5);
+}
+.highlight .no {
+ color: var(--darkreader-text-008080, #72ffff);
+}
+.highlight .nd {
+ color: var(--darkreader-text-3c5d5d, #b8b2a8);
+}
+.highlight .ni {
+ color: var(--darkreader-text-800080, #ff72ff);
+}
+.highlight .ne {
+ color: var(--darkreader-text-990000, #ff6161);
+}
+.highlight .nf,
+.highlight .fm {
+ color: var(--darkreader-text-990000, #ff6161);
+}
+.highlight .nl {
+ color: var(--darkreader-text-990000, #ff6161);
+}
+.highlight .nn {
+ color: var(--darkreader-text-555555, #b2aca2);
+}
+.highlight .nt {
+ color: var(--darkreader-text-000080, #7faeff);
+}
+.highlight .vc {
+ color: var(--darkreader-text-008080, #72ffff);
+}
+.highlight .vg {
+ color: var(--darkreader-text-008080, #72ffff);
+}
+.highlight .vi {
+ color: var(--darkreader-text-008080, #72ffff);
+}
+.highlight .nv,
+.highlight .vm {
+ color: var(--darkreader-text-008080, #72ffff);
+}
+.highlight .ow {
+ color: var(--darkreader-text-000000, #e8e6e3);
+}
+.highlight .o {
+ color: var(--darkreader-text-000000, #e8e6e3);
+}
+.highlight .w {
+ color: var(--darkreader-text-bbbbbb, #bdb7af);
+}
+.highlight {
+ background-color: var(--darkreader-background-f8f8f8, #1c1e1f);
+}
+a {
+ background-color: transparent;
+}
+abbr[title] {
+ border-bottom-color: currentcolor;
+ text-decoration-color: currentcolor;
+}
+button:focus-visible,
+[type="button"]:focus-visible,
+[type="reset"]:focus-visible,
+[type="submit"]:focus-visible {
+ outline-color: var(--darkreader-border-000000, #8c8273);
+}
+legend {
+ color: inherit;
+}
+@layer {
+ html {
+ background-color: var(--darkreader-bg--darkreader-background-ffffff, var(--darkreader-background-181a1b, #131516)) !important;
+ }
+ html {
+ color-scheme: dark !important;
+ }
+ iframe {
+ color-scheme: dark !important;
+ }
+ html,
+ body {
+ background-color: var(--darkreader-bg--darkreader-background-ffffff, var(--darkreader-background-181a1b, #131516));
+ }
+ html,
+ body {
+ border-color: var(--darkreader-border--darkreader-border-4c4c4c, var(--darkreader-border-736b5e, #6a6257));
+ color: var(--darkreader-text--darkreader-text-000000, var(--darkreader-text-e8e6e3, #d8d4cf));
+ }
+ a {
+ color: var(--darkreader-text--darkreader-text-0040ff, var(--darkreader-text-3391ff, #3da5ff));
+ }
+ table {
+ border-color: var(--darkreader-border--darkreader-border-808080, var(--darkreader-border-545b5e, #6f675b));
+ }
+ mark {
+ color: var(--darkreader-text--darkreader-text-000000, var(--darkreader-text-e8e6e3, #d8d4cf));
+ }
+ ::placeholder {
+ color: var(--darkreader-text--darkreader-text-a9a9a9, var(--darkreader-text-b2aba1, #b2aba1));
+ }
+ input:autofill,
+ textarea:autofill,
+ select:autofill {
+ background-color: var(--darkreader-bg--darkreader-background-faffbd, var(--darkreader-background-404400, #333600)) !important;
+ color: var(--darkreader-text--darkreader-text-000000, var(--darkreader-text-e8e6e3, #d8d4cf)) !important;
+ }
+ ::selection {
+ background-color: var(--darkreader-bg--darkreader-background-0060d4, var(--darkreader-background-004daa, #003e88)) !important;
+ color: var(--darkreader-text--darkreader-text-ffffff, var(--darkreader-text-e8e6e3, #d8d4cf)) !important;
+ }
+ ::selection {
+ background-color: var(--darkreader-bg--darkreader-background-0060d4, var(--darkreader-background-004daa, #003e88)) !important;
+ color: var(--darkreader-text--darkreader-text-ffffff, var(--darkreader-text-e8e6e3, #d8d4cf)) !important;
+ }
+}
+:root {
+ --darkreader-neutral-background: var(--darkreader-background-ffffff, #181a1b);
+ --darkreader-neutral-text: var(--darkreader-text-000000, #e8e6e3);
+ --darkreader-selection-background: var(--darkreader-background-0060d4, #004daa);
+ --darkreader-selection-text: var(--darkreader-text-ffffff, #e8e6e3);
+}
+:root {
+ --aside-width: 300px;
+}
+aside {
+ border-right-color: var(--darkreader-border--darkreader-border-000000, var(--darkreader-border-8c8273, #545b5f));
+}
+section.search {
+ border-bottom-color: var(--darkreader-border--darkreader-border-000000, var(--darkreader-border-8c8273, #545b5f));
+}
+@media (max-width: 1200px) {
+ body:has(aside:target) > a#close {
+ background-color: var(--darkreader-bg--darkreader-background-000000, var(--darkreader-background-000000, #000000));
+ }
+ aside:target {
+ background-color: var(--darkreader-bg--darkreader-background-ffffff, var(--darkreader-background-181a1b, #131516));
+ }
+ nav.mobile-nav {
+ border-bottom-color: var(--darkreader-border--darkreader-border-000000, var(--darkreader-border-8c8273, #545b5f));
+ }
+}
+a {
+ color: var(--darkreader-text--darkreader-text-0000ee, var(--darkreader-text-3d84ff, #44a2ff));
+ text-decoration-color: currentcolor;
+}
+aside a#close {
+ color: var(--darkreader-text--darkreader-text-ff0000, var(--darkreader-text-ff1a1a, #ff2c2c));
+}
+table,
+th,
+tr,
+td {
+ border-bottom-color: var(--darkreader-border--darkreader-border-000000, var(--darkreader-border-8c8273, #545b5f));
+ border-left-color: var(--darkreader-border--darkreader-border-000000, var(--darkreader-border-8c8273, #545b5f));
+ border-right-color: var(--darkreader-border--darkreader-border-000000, var(--darkreader-border-8c8273, #545b5f));
+ border-top-color: var(--darkreader-border--darkreader-border-000000, var(--darkreader-border-8c8273, #545b5f));
+}
+.highlight .cm {
+ color: var(--darkreader-text--darkreader-text-999988, var(--darkreader-text-a29a8e, #a79f94));
+}
+.highlight .cp {
+ color: var(--darkreader-text--darkreader-text-999999, var(--darkreader-text-a8a095, #aba499));
+}
+.highlight .c1 {
+ color: var(--darkreader-text--darkreader-text-999988, var(--darkreader-text-a29a8e, #a79f94));
+}
+.highlight .cs {
+ color: var(--darkreader-text--darkreader-text-999999, var(--darkreader-text-a8a095, #aba499));
+}
+.highlight .c,
+.highlight .ch,
+.highlight .cd,
+.highlight .cpf {
+ color: var(--darkreader-text--darkreader-text-999988, var(--darkreader-text-a29a8e, #a79f94));
+}
+.highlight .err {
+ background-color: var(--darkreader-bg--darkreader-background-e3d2d2, var(--darkreader-background-3a2424, #2e1d1d));
+ color: var(--darkreader-text--darkreader-text-a61717, var(--darkreader-text-e95e5e, #e96161));
+}
+.highlight .gd {
+ background-color: var(--darkreader-bg--darkreader-background-ffdddd, var(--darkreader-background-470000, #390000));
+ color: var(--darkreader-text--darkreader-text-000000, var(--darkreader-text-e8e6e3, #d8d4cf));
+}
+.highlight .ge {
+ color: var(--darkreader-text--darkreader-text-000000, var(--darkreader-text-e8e6e3, #d8d4cf));
+}
+.highlight .gr {
+ color: var(--darkreader-text--darkreader-text-aa0000, var(--darkreader-text-ff5555, #ff5555));
+}
+.highlight .gh {
+ color: var(--darkreader-text--darkreader-text-999999, var(--darkreader-text-a8a095, #aba499));
+}
+.highlight .gi {
+ background-color: var(--darkreader-bg--darkreader-background-ddffdd, var(--darkreader-background-124700, #0e3900));
+ color: var(--darkreader-text--darkreader-text-000000, var(--darkreader-text-e8e6e3, #d8d4cf));
+}
+.highlight .go {
+ color: var(--darkreader-text--darkreader-text-888888, var(--darkreader-text-9d9488, #a39c90));
+}
+.highlight .gp {
+ color: var(--darkreader-text--darkreader-text-555555, var(--darkreader-text-b2aca2, #b2aca2));
+}
+.highlight .gu {
+ color: var(--darkreader-text--darkreader-text-aaaaaa, var(--darkreader-text-b2aca2, #b2aca2));
+}
+.highlight .gt {
+ color: var(--darkreader-text--darkreader-text-aa0000, var(--darkreader-text-ff5555, #ff5555));
+}
+.highlight .kc {
+ color: var(--darkreader-text--darkreader-text-000000, var(--darkreader-text-e8e6e3, #d8d4cf));
+}
+.highlight .kd {
+ color: var(--darkreader-text--darkreader-text-000000, var(--darkreader-text-e8e6e3, #d8d4cf));
+}
+.highlight .kn {
+ color: var(--darkreader-text--darkreader-text-000000, var(--darkreader-text-e8e6e3, #d8d4cf));
+}
+.highlight .kp {
+ color: var(--darkreader-text--darkreader-text-000000, var(--darkreader-text-e8e6e3, #d8d4cf));
+}
+.highlight .kr {
+ color: var(--darkreader-text--darkreader-text-000000, var(--darkreader-text-e8e6e3, #d8d4cf));
+}
+.highlight .kt {
+ color: var(--darkreader-text--darkreader-text-445588, var(--darkreader-text-8ba6c5, #8cabc5));
+}
+.highlight .k,
+.highlight .kv {
+ color: var(--darkreader-text--darkreader-text-000000, var(--darkreader-text-e8e6e3, #d8d4cf));
+}
+.highlight .mf {
+ color: var(--darkreader-text--darkreader-text-009999, var(--darkreader-text-61ffff, #5dffff));
+}
+.highlight .mh {
+ color: var(--darkreader-text--darkreader-text-009999, var(--darkreader-text-61ffff, #5dffff));
+}
+.highlight .il {
+ color: var(--darkreader-text--darkreader-text-009999, var(--darkreader-text-61ffff, #5dffff));
+}
+.highlight .mi {
+ color: var(--darkreader-text--darkreader-text-009999, var(--darkreader-text-61ffff, #5dffff));
+}
+.highlight .mo {
+ color: var(--darkreader-text--darkreader-text-009999, var(--darkreader-text-61ffff, #5dffff));
+}
+.highlight .m,
+.highlight .mb,
+.highlight .mx {
+ color: var(--darkreader-text--darkreader-text-009999, var(--darkreader-text-61ffff, #5dffff));
+}
+.highlight .sa {
+ color: var(--darkreader-text--darkreader-text-000000, var(--darkreader-text-e8e6e3, #d8d4cf));
+}
+.highlight .sb {
+ color: var(--darkreader-text--darkreader-text-dd1144, var(--darkreader-text-ef3564, #f0426e));
+}
+.highlight .sc {
+ color: var(--darkreader-text--darkreader-text-dd1144, var(--darkreader-text-ef3564, #f0426e));
+}
+.highlight .sd {
+ color: var(--darkreader-text--darkreader-text-dd1144, var(--darkreader-text-ef3564, #f0426e));
+}
+.highlight .s2 {
+ color: var(--darkreader-text--darkreader-text-dd1144, var(--darkreader-text-ef3564, #f0426e));
+}
+.highlight .se {
+ color: var(--darkreader-text--darkreader-text-dd1144, var(--darkreader-text-ef3564, #f0426e));
+}
+.highlight .sh {
+ color: var(--darkreader-text--darkreader-text-dd1144, var(--darkreader-text-ef3564, #f0426e));
+}
+.highlight .si {
+ color: var(--darkreader-text--darkreader-text-dd1144, var(--darkreader-text-ef3564, #f0426e));
+}
+.highlight .sx {
+ color: var(--darkreader-text--darkreader-text-dd1144, var(--darkreader-text-ef3564, #f0426e));
+}
+.highlight .sr {
+ color: var(--darkreader-text--darkreader-text-009926, var(--darkreader-text-61ff88, #5dff85));
+}
+.highlight .s1 {
+ color: var(--darkreader-text--darkreader-text-dd1144, var(--darkreader-text-ef3564, #f0426e));
+}
+.highlight .ss {
+ color: var(--darkreader-text--darkreader-text-990073, var(--darkreader-text-ff61d8, #ff5dd7));
+}
+.highlight .s,
+.highlight .dl {
+ color: var(--darkreader-text--darkreader-text-dd1144, var(--darkreader-text-ef3564, #f0426e));
+}
+.highlight .na {
+ color: var(--darkreader-text--darkreader-text-008080, var(--darkreader-text-72ffff, #69ffff));
+}
+.highlight .bp {
+ color: var(--darkreader-text--darkreader-text-999999, var(--darkreader-text-a8a095, #aba499));
+}
+.highlight .nb {
+ color: var(--darkreader-text--darkreader-text-0086b3, var(--darkreader-text-4fd3ff, #51d3ff));
+}
+.highlight .nc {
+ color: var(--darkreader-text--darkreader-text-445588, var(--darkreader-text-8ba6c5, #8cabc5));
+}
+.highlight .no {
+ color: var(--darkreader-text--darkreader-text-008080, var(--darkreader-text-72ffff, #69ffff));
+}
+.highlight .nd {
+ color: var(--darkreader-text--darkreader-text-3c5d5d, var(--darkreader-text-b8b2a8, #b6b0a6));
+}
+.highlight .ni {
+ color: var(--darkreader-text--darkreader-text-800080, var(--darkreader-text-ff72ff, #ff69ff));
+}
+.highlight .ne {
+ color: var(--darkreader-text--darkreader-text-990000, var(--darkreader-text-ff6161, #ff5d5d));
+}
+.highlight .nf,
+.highlight .fm {
+ color: var(--darkreader-text--darkreader-text-990000, var(--darkreader-text-ff6161, #ff5d5d));
+}
+.highlight .nl {
+ color: var(--darkreader-text--darkreader-text-990000, var(--darkreader-text-ff6161, #ff5d5d));
+}
+.highlight .nn {
+ color: var(--darkreader-text--darkreader-text-555555, var(--darkreader-text-b2aca2, #b2aca2));
+}
+.highlight .nt {
+ color: var(--darkreader-text--darkreader-text-000080, var(--darkreader-text-7faeff, #72b9ff));
+}
+.highlight .vc {
+ color: var(--darkreader-text--darkreader-text-008080, var(--darkreader-text-72ffff, #69ffff));
+}
+.highlight .vg {
+ color: var(--darkreader-text--darkreader-text-008080, var(--darkreader-text-72ffff, #69ffff));
+}
+.highlight .vi {
+ color: var(--darkreader-text--darkreader-text-008080, var(--darkreader-text-72ffff, #69ffff));
+}
+.highlight .nv,
+.highlight .vm {
+ color: var(--darkreader-text--darkreader-text-008080, var(--darkreader-text-72ffff, #69ffff));
+}
+.highlight .ow {
+ color: var(--darkreader-text--darkreader-text-000000, var(--darkreader-text-e8e6e3, #d8d4cf));
+}
+.highlight .o {
+ color: var(--darkreader-text--darkreader-text-000000, var(--darkreader-text-e8e6e3, #d8d4cf));
+}
+.highlight .w {
+ color: var(--darkreader-text--darkreader-text-bbbbbb, var(--darkreader-text-bdb7af, #bab4ab));
+}
+.highlight {
+ background-color: var(--darkreader-bg--darkreader-background-f8f8f8, var(--darkreader-background-1c1e1f, #161819));
+}
+a {
+ background-color: transparent;
+}
+abbr[title] {
+ border-bottom-color: currentcolor;
+ text-decoration-color: currentcolor;
+}
+button:focus-visible,
+[type="button"]:focus-visible,
+[type="reset"]:focus-visible,
+[type="submit"]:focus-visible {
+ outline-color: var(--darkreader-border--darkreader-border-000000, var(--darkreader-border-8c8273, #545b5f));
+}
+legend {
+ color: inherit;
+}
diff --git a/docs/assets/css/docs.css b/docs/assets/css/docs.css
new file mode 100644
index 000000000..05a407784
--- /dev/null
+++ b/docs/assets/css/docs.css
@@ -0,0 +1,3 @@
+---
+---
+{% include docs.css %}
diff --git a/docs/assets/css/highlight.css b/docs/assets/css/highlight.css
new file mode 100644
index 000000000..6b5c4b725
--- /dev/null
+++ b/docs/assets/css/highlight.css
@@ -0,0 +1,282 @@
+pre.highlight {
+ padding: 1rem;
+ overflow-x: scroll;
+}
+
+/* generated with `rougify style github` */
+.highlight table td { padding: 5px; }
+
+.highlight table pre { margin: 0; }
+
+.highlight .cm {
+ color: #998;
+ font-style: italic;
+}
+
+.highlight .cp {
+ color: #999;
+ font-weight: bold;
+}
+
+.highlight .c1 {
+ color: #998;
+ font-style: italic;
+}
+
+.highlight .cs {
+ color: #999;
+ font-weight: bold;
+ font-style: italic;
+}
+
+.highlight .c, .highlight .ch, .highlight .cd, .highlight .cpf {
+ color: #998;
+ font-style: italic;
+}
+
+.highlight .err {
+ color: #a61717;
+ background-color: #e3d2d2;
+}
+
+.highlight .gd {
+ color: #000;
+ background-color: #fdd;
+}
+
+.highlight .ge {
+ color: #000;
+ font-style: italic;
+}
+
+.highlight .gr {
+ color: #a00;
+}
+
+.highlight .gh {
+ color: #999;
+}
+
+.highlight .gi {
+ color: #000;
+ background-color: #dfd;
+}
+
+.highlight .go {
+ color: #888;
+}
+
+.highlight .gp {
+ color: #555;
+}
+
+.highlight .gs {
+ font-weight: bold;
+}
+
+.highlight .gu {
+ color: #aaa;
+}
+
+.highlight .gt {
+ color: #a00;
+}
+
+.highlight .kc {
+ color: #000;
+ font-weight: bold;
+}
+
+.highlight .kd {
+ color: #000;
+ font-weight: bold;
+}
+
+.highlight .kn {
+ color: #000;
+ font-weight: bold;
+}
+
+.highlight .kp {
+ color: #000;
+ font-weight: bold;
+}
+
+.highlight .kr {
+ color: #000;
+ font-weight: bold;
+}
+
+.highlight .kt {
+ color: #458;
+ font-weight: bold;
+}
+
+.highlight .k, .highlight .kv {
+ color: #000;
+ font-weight: bold;
+}
+
+.highlight .mf {
+ color: #099;
+}
+
+.highlight .mh {
+ color: #099;
+}
+
+.highlight .il {
+ color: #099;
+}
+
+.highlight .mi {
+ color: #099;
+}
+
+.highlight .mo {
+ color: #099;
+}
+
+.highlight .m, .highlight .mb, .highlight .mx {
+ color: #099;
+}
+
+.highlight .sa {
+ color: #000;
+ font-weight: bold;
+}
+
+.highlight .sb {
+ color: #d14;
+}
+
+.highlight .sc {
+ color: #d14;
+}
+
+.highlight .sd {
+ color: #d14;
+}
+
+.highlight .s2 {
+ color: #d14;
+}
+
+.highlight .se {
+ color: #d14;
+}
+
+.highlight .sh {
+ color: #d14;
+}
+
+.highlight .si {
+ color: #d14;
+}
+
+.highlight .sx {
+ color: #d14;
+}
+
+.highlight .sr {
+ color: #009926;
+}
+
+.highlight .s1 {
+ color: #d14;
+}
+
+.highlight .ss {
+ color: #990073;
+}
+
+.highlight .s, .highlight .dl {
+ color: #d14;
+}
+
+.highlight .na {
+ color: #008080;
+}
+
+.highlight .bp {
+ color: #999;
+}
+
+.highlight .nb {
+ color: #0086b3;
+}
+
+.highlight .nc {
+ color: #458;
+ font-weight: bold;
+}
+
+.highlight .no {
+ color: #008080;
+}
+
+.highlight .nd {
+ color: #3c5d5d;
+ font-weight: bold;
+}
+
+.highlight .ni {
+ color: #800080;
+}
+
+.highlight .ne {
+ color: #900;
+ font-weight: bold;
+}
+
+.highlight .nf, .highlight .fm {
+ color: #900;
+ font-weight: bold;
+}
+
+.highlight .nl {
+ color: #900;
+ font-weight: bold;
+}
+
+.highlight .nn {
+ color: #555;
+}
+
+.highlight .nt {
+ color: #000080;
+}
+
+.highlight .vc {
+ color: #008080;
+}
+
+.highlight .vg {
+ color: #008080;
+}
+
+.highlight .vi {
+ color: #008080;
+}
+
+.highlight .nv, .highlight .vm {
+ color: #008080;
+}
+
+.highlight .ow {
+ color: #000;
+ font-weight: bold;
+}
+
+.highlight .o {
+ color: #000;
+ font-weight: bold;
+}
+
+.highlight .w {
+ color: #bbb;
+}
+
+.highlight {
+ background-color: #f8f8f8;
+}
diff --git a/docs/assets/css/normalize.css b/docs/assets/css/normalize.css
new file mode 100644
index 000000000..bcc16e96b
--- /dev/null
+++ b/docs/assets/css/normalize.css
@@ -0,0 +1,350 @@
+/* stylelint-disable */
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+
+/* Document
+ ========================================================================== */
+
+/**
+ * 1. Correct the line height in all browsers.
+ * 2. Prevent adjustments of font size after orientation changes in iOS.
+ */
+
+html {
+ line-height: 1.15; /* 1 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/* Sections
+ ========================================================================== */
+
+/**
+ * Remove the margin in all browsers.
+ */
+
+body {
+ margin: 0;
+}
+
+/**
+ * Render the `main` element consistently in IE.
+ */
+
+main {
+ display: block;
+}
+
+/**
+ * Correct the font size and margin on `h1` elements within `section` and
+ * `article` contexts in Chrome, Firefox, and Safari.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/* Grouping content
+ ========================================================================== */
+
+/**
+ * 1. Add the correct box sizing in Firefox.
+ * 2. Show the overflow in Edge and IE.
+ */
+
+hr {
+ box-sizing: content-box; /* 1 */
+ height: 0; /* 1 */
+ overflow: visible; /* 2 */
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+pre {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/* Text-level semantics
+ ========================================================================== */
+
+/**
+ * Remove the gray background on active links in IE 10.
+ */
+
+a {
+ background-color: transparent;
+}
+
+/**
+ * 1. Remove the bottom border in Chrome 57-
+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
+ */
+
+abbr[title] {
+ border-bottom: none; /* 1 */
+ text-decoration: underline; /* 2 */
+ text-decoration: underline dotted; /* 2 */
+}
+
+/**
+ * Add the correct font weight in Chrome, Edge, and Safari.
+ */
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+code,
+kbd,
+samp {
+ /* font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/**
+ * Add the correct font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` elements from affecting the line height in
+ * all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/* Embedded content
+ ========================================================================== */
+
+/**
+ * Remove the border on images inside links in IE 10.
+ */
+
+img {
+ border-style: none;
+}
+
+/* Forms
+ ========================================================================== */
+
+/**
+ * 1. Change the font styles in all browsers.
+ * 2. Remove the margin in Firefox and Safari.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit; /* 1 */
+ font-size: 100%; /* 1 */
+ line-height: 1.15; /* 1 */
+ margin: 0; /* 2 */
+}
+
+/**
+ * Show the overflow in IE.
+ * 1. Show the overflow in Edge.
+ */
+
+button,
+input { /* 1 */
+ overflow: visible;
+}
+
+/**
+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
+ * 1. Remove the inheritance of text transform in Firefox.
+ */
+
+button,
+select { /* 1 */
+ text-transform: none;
+}
+
+/**
+ * Correct the inability to style clickable types in iOS and Safari.
+ */
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+ -webkit-appearance: button;
+}
+
+/**
+ * Remove the inner border and padding in Firefox.
+ */
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+ border-style: none;
+ padding: 0;
+}
+
+/**
+ * Restore the focus styles unset by the previous rule.
+ */
+
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+ outline: 1px dotted ButtonText;
+}
+
+/**
+ * Correct the padding in Firefox.
+ */
+
+fieldset {
+ padding: 0.35em 0.75em 0.625em;
+}
+
+/**
+ * 1. Correct the text wrapping in Edge and IE.
+ * 2. Correct the color inheritance from `fieldset` elements in IE.
+ * 3. Remove the padding so developers are not caught out when they zero out
+ * `fieldset` elements in all browsers.
+ */
+
+legend {
+ box-sizing: border-box; /* 1 */
+ color: inherit; /* 2 */
+ display: table; /* 1 */
+ max-width: 100%; /* 1 */
+ padding: 0; /* 3 */
+ white-space: normal; /* 1 */
+}
+
+/**
+ * Add the correct vertical alignment in Chrome, Firefox, and Opera.
+ */
+
+progress {
+ vertical-align: baseline;
+}
+
+/**
+ * Remove the default vertical scrollbar in IE 10+.
+ */
+
+textarea {
+ overflow: auto;
+}
+
+/**
+ * 1. Add the correct box sizing in IE 10.
+ * 2. Remove the padding in IE 10.
+ */
+
+[type="checkbox"],
+[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Correct the cursor style of increment and decrement buttons in Chrome.
+ */
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/**
+ * 1. Correct the odd appearance in Chrome and Safari.
+ * 2. Correct the outline style in Safari.
+ */
+
+[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ outline-offset: -2px; /* 2 */
+}
+
+/**
+ * Remove the inner padding in Chrome and Safari on macOS.
+ */
+
+[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * 1. Correct the inability to style clickable types in iOS and Safari.
+ * 2. Change font properties to `inherit` in Safari.
+ */
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button; /* 1 */
+ font: inherit; /* 2 */
+}
+
+/* Interactive
+ ========================================================================== */
+
+/*
+ * Add the correct display in Edge, IE 10+, and Firefox.
+ */
+
+details {
+ display: block;
+}
+
+/*
+ * Add the correct display in all browsers.
+ */
+
+summary {
+ display: list-item;
+}
+
+/* Misc
+ ========================================================================== */
+
+/**
+ * Add the correct display in IE 10+.
+ */
+
+template {
+ display: none;
+}
+
+/**
+ * Add the correct display in IE 10.
+ */
+
+[hidden] {
+ display: none;
+}
diff --git a/docs/assets/css/style.scss b/docs/assets/css/style.scss
deleted file mode 100644
index fdd12eda4..000000000
--- a/docs/assets/css/style.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-// stylelint-disable-next-line
-@import "{{ site.theme }}";
-
-.page-header .project-name a {
- color: #fff;
-
- &:hover {
- text-decoration: none;
- opacity: .7;
- }
-}
diff --git a/docs/assets/fonts/OpenSans.woff2 b/docs/assets/fonts/OpenSans.woff2
new file mode 100644
index 000000000..b5d54e762
--- /dev/null
+++ b/docs/assets/fonts/OpenSans.woff2
Binary files differ
diff --git a/docs/assets/js/docs.js b/docs/assets/js/docs.js
new file mode 100644
index 000000000..c14fce920
--- /dev/null
+++ b/docs/assets/js/docs.js
@@ -0,0 +1,63 @@
+/* globals i18n */
+
+if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
+ document.head.insertAdjacentHTML('beforeend', `
+ <meta name="darkreader-lock">
+ `);
+}
+
+let asideNav;
+
+window.addEventListener('beforeunload', () => {
+ sessionStorage.setItem('sidebar_scrollTop', asideNav.scrollTop);
+});
+
+window.addEventListener('keydown', (e) => {
+ if (e.key === 'Escape') {
+ location.hash = 'close';
+ }
+});
+
+document.addEventListener('DOMContentLoaded', () => {
+ asideNav = document.querySelector('aside > nav.docs');
+
+ const sidebar_scrollTop = sessionStorage.getItem('sidebar_scrollTop');
+ if (sidebar_scrollTop) {
+ asideNav.scrollTo(0, sidebar_scrollTop);
+ sessionStorage.removeItem('sidebar_scrollTop');
+ }
+
+ for (const el of document.querySelectorAll('div.highlight')) {
+ /* eslint-disable @stylistic/max-len */
+ el.insertAdjacentHTML('afterbegin', `
+ <button class="copy" title="${i18n.copy_to_clipboard}">
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
+ <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1z"/>
+ <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0z"/>
+ </svg>
+ </button>
+ `);
+ /* eslint-enable @stylistic/max-len */
+ const copyBtn = el.querySelector('button.copy');
+ copyBtn.addEventListener('click', () => {
+ const snippet = el.querySelector('code').innerText;
+ if (navigator.clipboard) {
+ navigator.clipboard.writeText(snippet);
+ } else {
+ // Fallback if no HTTPS
+ const input = document.createElement('textarea');
+ input.innerHTML = snippet;
+ document.body.append(input);
+ input.select();
+ document.execCommand('copy');
+ input.remove();
+ }
+ });
+ }
+
+ for (const el of document.querySelectorAll('img')) {
+ if (el.parentNode.tagName !== 'A') {
+ el.outerHTML = `<a href="${el.getAttribute('src')}">${el.outerHTML}</a>`;
+ }
+ }
+});
diff --git a/docs/assets/js/simple-jekyll-search.min.js b/docs/assets/js/simple-jekyll-search.min.js
new file mode 100644
index 000000000..81df5d5a3
--- /dev/null
+++ b/docs/assets/js/simple-jekyll-search.min.js
@@ -0,0 +1,6 @@
+/*!
+ * Simple-Jekyll-Search
+ * Copyright 2015-2020, Christian Fei
+ * Licensed under the MIT License.
+ */
+!function(){"use strict";var f={compile:function(r){return i.template.replace(i.pattern,function(t,e){var n=i.middleware(e,r[e],i.template);return void 0!==n?n:r[e]||t})},setOptions:function(t){i.pattern=t.pattern||i.pattern,i.template=t.template||i.template,"function"==typeof t.middleware&&(i.middleware=t.middleware)}};const i={pattern:/\{(.*?)\}/g,template:"",middleware:function(){}};var n=function(t,e){var n=e.length,r=t.length;if(n<r)return!1;if(r===n)return t===e;t:for(var i=0,o=0;i<r;i++){for(var u=t.charCodeAt(i);o<n;)if(e.charCodeAt(o++)===u)continue t;return!1}return!0},e=new function(){this.matches=function(t,e){return n(e.toLowerCase(),t.toLowerCase())}},r=new function(){this.matches=function(e,t){return!!e&&(e=e.trim().toLowerCase(),(t=t.trim().toLowerCase()).split(" ").filter(function(t){return 0<=e.indexOf(t)}).length===t.split(" ").length)}},d={put:function(t){if(l(t))return a(t);if(function(t){return Boolean(t)&&"[object Array]"===Object.prototype.toString.call(t)}(t))return function(n){const r=[];s();for(let t=0,e=n.length;t<e;t++)l(n[t])&&r.push(a(n[t]));return r}(t);return undefined},clear:s,search:function(t){return t?function(e,n,r,i){const o=[];for(let t=0;t<e.length&&o.length<i.limit;t++){var u=function(t,e,n,r){for(const i in t)if(!function(n,r){for(let t=0,e=r.length;t<e;t++){var i=r[t];if(new RegExp(i).test(n))return!0}return!1}(t[i],r.exclude)&&n.matches(t[i],e))return t}(e[t],n,r,i);u&&o.push(u)}return o}(u,t,c.searchStrategy,c).sort(c.sort):[]},setOptions:function(t){c=t||{},c.fuzzy=t.fuzzy||!1,c.limit=t.limit||10,c.searchStrategy=t.fuzzy?e:r,c.sort=t.sort||o,c.exclude=t.exclude||[]}};function o(){return 0}const u=[];let c={};function s(){return u.length=0,u}function l(t){return Boolean(t)&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return u.push(t),u}c.fuzzy=!1,c.limit=10,c.searchStrategy=c.fuzzy?e:r,c.sort=o,c.exclude=[];var p={load:function(t,e){const n=window.XMLHttpRequest?new window.XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP");n.open("GET",t,!0),n.onreadystatechange=h(n,e),n.send()}};function h(e,n){return function(){if(4===e.readyState&&200===e.status)try{n(null,JSON.parse(e.responseText))}catch(t){n(t,null)}}}var m=function y(t){if(!(e=t)||!("undefined"!=typeof e.required&&e.required instanceof Array))throw new Error("-- OptionsValidator: required options missing");var e;if(!(this instanceof y))return new y(t);const r=t.required;this.getRequiredOptions=function(){return r},this.validate=function(e){const n=[];return r.forEach(function(t){"undefined"==typeof e[t]&&n.push(t)}),n}},w={merge:function(t,e){const n={};for(const r in t)n[r]=t[r],"undefined"!=typeof e[r]&&(n[r]=e[r]);return n},isJSON:function(t){try{return t instanceof Object&&JSON.parse(JSON.stringify(t))?!0:!1}catch(e){return!1}}};!function(t){let i={searchInput:null,resultsContainer:null,json:[],success:Function.prototype,searchResultTemplate:'<li><a href="{url}" title="{desc}">{title}</a></li>',templateMiddleware:Function.prototype,sortMiddleware:function(){return 0},noResultsText:"No results found",limit:10,fuzzy:!1,debounceTime:null,exclude:[]},n;const e=function(t,e){e?(clearTimeout(n),n=setTimeout(t,e)):t.call()};var r=["searchInput","resultsContainer","json"];const o=m({required:r});function u(t){d.put(t),i.searchInput.addEventListener("input",function(t){-1===[13,16,20,37,38,39,40,91].indexOf(t.which)&&(c(),e(function(){l(t.target.value)},i.debounceTime))})}function c(){i.resultsContainer.innerHTML=""}function s(t){i.resultsContainer.innerHTML+=t}function l(t){var e;(e=t)&&0<e.length&&(c(),function(e,n){var r=e.length;if(0===r)return s(i.noResultsText);for(let t=0;t<r;t++)e[t].query=n,s(f.compile(e[t]))}(d.search(t),t))}function a(t){throw new Error("SimpleJekyllSearch --- "+t)}t.SimpleJekyllSearch=function(t){var n;0<o.validate(t).length&&a("You must specify the following required options: "+r),i=w.merge(i,t),f.setOptions({template:i.searchResultTemplate,middleware:i.templateMiddleware}),d.setOptions({fuzzy:i.fuzzy,limit:i.limit,sort:i.sortMiddleware,exclude:i.exclude}),w.isJSON(i.json)?u(i.json):(n=i.json,p.load(n,function(t,e){t&&a("failed to get JSON ("+n+")"),u(e)}));t={search:l};return"function"==typeof i.success&&i.success.call(t),t}}(window)}(); \ No newline at end of file
diff --git a/docs/en/developers/03_Running_tests.md b/docs/en/developers/03_Running_tests.md
index c5f962a6d..1092288d1 100644
--- a/docs/en/developers/03_Running_tests.md
+++ b/docs/en/developers/03_Running_tests.md
@@ -61,7 +61,7 @@ If you do not have one, you need to create one.
},
"response": {
"status": 200,
- "bodyFileName": "{{request.pathSegments.[0]}}",
+ "bodyFileName": "{{ '{{' }}request.pathSegments.[0]}}",
"transformers": ["response-template"],
"headers": {
"Content-Type": "application/rss+xml"
diff --git a/docs/en/index.md b/docs/en/index.md
index 22eb51227..b19656bb6 100644
--- a/docs/en/index.md
+++ b/docs/en/index.md
@@ -1,3 +1,7 @@
+---
+lang: en
+---
+
![FreshRSS logo](img/logo_freshrss.png)
# FreshRSS manual (English)
diff --git a/docs/favicon.ico b/docs/favicon.ico
new file mode 100644
index 000000000..5d571cc82
--- /dev/null
+++ b/docs/favicon.ico
Binary files differ
diff --git a/docs/fr/index.md b/docs/fr/index.md
index a46e4c7b3..8ac0c3f04 100644
--- a/docs/fr/index.md
+++ b/docs/fr/index.md
@@ -1,3 +1,6 @@
+---
+lang: fr
+---
![Logo de FreshRSS](img/logo_freshrss.png)
FreshRSS est un agrégateur et lecteur de flux RSS. Il permet de regrouper
diff --git a/docs/index.md b/docs/index.md
index 65ab7dfc4..ba2502acf 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,6 +1,12 @@
+---
+lang: en
+---
+
# Welcome to the FreshRSS documentation
If you want to contribute, you can [find us on GitHub](https://github.com/FreshRSS/FreshRSS).
- [English documentation](./en/index.md)
- [Documentation française](./fr/index.md)
+
+<meta http-equiv="Refresh" content="0; url=./en/">
diff --git a/docs/search.en.json b/docs/search.en.json
new file mode 100644
index 000000000..213c8d965
--- /dev/null
+++ b/docs/search.en.json
@@ -0,0 +1,13 @@
+---
+layout: none
+---
+[
+{% assign pages = site.pages | where_exp: "p", "p.title != nil" | where: "lang", "en" %}
+{% for page in pages %}
+ {
+ "title": "{{ page.title | escape }}",
+ "url": "{{ site.baseurl }}{{ page.url }}",
+ "content": {{ page.content | markdownify | strip_html | jsonify }}
+ }{% unless forloop.last %},{% endunless %}
+{% endfor %}
+]
diff --git a/docs/search.fr.json b/docs/search.fr.json
new file mode 100644
index 000000000..b32d32150
--- /dev/null
+++ b/docs/search.fr.json
@@ -0,0 +1,13 @@
+---
+layout: none
+---
+[
+{% assign pages = site.pages | where_exp: "p", "p.title != nil" | where: "lang", "fr" %}
+{% for page in pages %}
+ {
+ "title": "{{ page.title | escape }}",
+ "url": "{{ site.baseurl }}{{ page.url }}",
+ "content": {{ page.content | markdownify | strip_html | jsonify }}
+ }{% unless forloop.last %},{% endunless %}
+{% endfor %}
+]