diff --git a/docs/source/_static/scripts/custom.js b/docs/source/_static/scripts/custom.js
index f9c4d1716..576d79ce1 100644
--- a/docs/source/_static/scripts/custom.js
+++ b/docs/source/_static/scripts/custom.js
@@ -1,25 +1,151 @@
-document.addEventListener("DOMContentLoaded", function() {
- const processList = (list, level, addTitle) => {
- const title = list.parentElement.querySelector('a')
- list.classList.add('grid')
- if (addTitle)
- title.classList.add('grid-title')
+const processList = (list, level, addTitle) => {
+ const title = list.parentElement.querySelector('a')
+ list.classList.add('grid')
+ if (addTitle)
+ title.classList.add('grid-title')
- list.querySelectorAll(`li.toctree-l${level}`).forEach((item) => {
- const link = item.querySelector('a')
- if (link) {
- item.style.cursor = 'pointer'
- item.addEventListener('click', () => link.click())
- }
+ list.querySelectorAll(`li.toctree-l${level}`).forEach((item) => {
+ const link = item.querySelector('a')
+ if (link) {
+ item.style.cursor = 'pointer'
+ item.addEventListener('click', () => link.click())
+ }
- const name = item.querySelector('a').innerText
- const img = document.createElement('img')
- img.src = `https://static.platypush.tech/icons/${name.toLowerCase()}-64.png`
- img.alt = ' '
- item.prepend(img)
- })
+ const name = item.querySelector('a').innerText
+ const img = document.createElement('img')
+ img.src = `https://static.platypush.tech/icons/${name.toLowerCase()}-64.png`
+ img.alt = ' '
+ item.prepend(img)
+ })
+}
+
+const addClipboard = (parent) => {
+ const pre = parent.tagName === 'PRE' ? parent : parent.querySelector('pre')
+ if (!pre)
+ return
+
+ const clipboard = document.createElement('i')
+ const setClipboard = (img, text) => {
+ clipboard.innerHTML = ``
}
+ clipboard.classList.add('clipboard')
+ setClipboard('clipboard-bw', 'Copy')
+ clipboard.onclick = () => {
+ if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
+ setClipboard('ok', 'Copied!')
+ setTimeout(() => setClipboard('clipboard-bw', 'Copy'), 2000)
+ return navigator.clipboard.writeText(pre.innerText.trim())
+ }
+
+ return Promise.reject('The Clipboard API is not available.');
+ }
+
+ pre.style.position = 'relative'
+ pre.appendChild(clipboard)
+}
+
+const Tabs = () => {
+ let selectedTab = null
+ let parent = null
+ let data = {}
+
+ const init = (obj) => {
+ data = obj
+ if (Object.keys(data).length && selectedTab == null)
+ selectedTab = Object.keys(data)[0]
+ }
+
+ const select = (name) => {
+ if (!parent) {
+ console.warn('Cannot select tab: parent not set')
+ return
+ }
+
+ if (!data[name]) {
+ console.warn(`Cannot select tab: invalid name: ${name}`)
+ return
+ }
+
+ const tabsBody = parent.querySelector('.body')
+ selectedTab = name
+ tabsBody.innerHTML = data[selectedTab]
+ parent.querySelectorAll('.tabs li').forEach(
+ (tab) => tab.classList.remove('selected')
+ )
+
+ const tab = [...parent.querySelectorAll('.tabs li')].find(
+ (t) => t.innerText === name
+ )
+
+ if (!tab) {
+ console.warn(`Cannot select tab: invalid name: ${name}`)
+ return
+ }
+
+ addClipboard(tabsBody)
+ tab.classList.add('selected')
+ }
+
+ const mount = (p) => {
+ const tabs = document.createElement('div')
+ tabs.classList.add('tabs')
+ parent = p
+
+ const tabsList = document.createElement('ul')
+ Object.keys(data).forEach((title) => {
+ const tab = document.createElement('li')
+ tab.innerText = title
+ tab.onclick = (event) => {
+ event.stopPropagation()
+ select(title)
+ },
+
+ tabsList.appendChild(tab)
+ })
+
+ const tabsBody = document.createElement('div')
+ tabsBody.classList.add('body')
+
+ tabs.appendChild(tabsList)
+ tabs.appendChild(tabsBody)
+ parent.innerHTML = ''
+ parent.appendChild(tabs)
+ select(selectedTab)
+ }
+
+ return {
+ init,
+ select,
+ mount,
+ }
+}
+
+const depsTabs = Tabs()
+
+const convertDepsToTabs = () => {
+ const depsContainer = document.getElementById('dependencies')
+ if (!depsContainer)
+ return
+
+ const blocks = [...depsContainer.querySelectorAll('.highlight-bash')].map((block) => block.outerHTML)
+ const titles = [...depsContainer.querySelectorAll('p strong')].map((title) => title.innerText)
+
+ if (!(blocks.length && titles.length && blocks.length === titles.length))
+ return
+
+ const title = depsContainer.querySelector('h2')
+ const tabsData = titles.reduce((obj, title, i) => {
+ obj[title] = blocks[i]
+ return obj
+ }, {})
+
+ depsTabs.init(tabsData)
+ depsTabs.mount(depsContainer)
+ depsContainer.prepend(title)
+}
+
+const generateComponentsGrid = () => {
const tocWrappers = document.querySelectorAll('.toctree-wrapper.compound')
if (!tocWrappers.length) {
@@ -45,4 +171,26 @@ document.addEventListener("DOMContentLoaded", function() {
if (list)
processList(list, 1, false)
}
+}
+
+const addClipboardToCodeBlocks = () => {
+ document.querySelectorAll('pre').forEach((pre) => addClipboard(pre))
+}
+
+const renderActionsList = () => {
+ const actionsList = document.getElementById('actions')?.querySelector('ul')
+ if (!actionsList)
+ return
+
+ [...actionsList.querySelectorAll('li')].forEach((li) => {
+ const link = li.querySelector('a')
+ link.innerHTML = `${link.innerText}
`
+ })
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+ generateComponentsGrid()
+ convertDepsToTabs()
+ addClipboardToCodeBlocks()
+ renderActionsList()
})
diff --git a/docs/source/_static/styles/custom.css b/docs/source/_static/styles/custom.css
index 5ae180449..5091dead2 100644
--- a/docs/source/_static/styles/custom.css
+++ b/docs/source/_static/styles/custom.css
@@ -69,3 +69,62 @@ ul.grid li a code {
ul.grid .icon {
width: 32px;
}
+
+/* Clipboard button */
+.clipboard {
+ position: absolute;
+ display: inline-block;
+ width: 32px;
+ top: 0.5em;
+ right: 0.5em;
+ cursor: pointer;
+}
+
+/* Tabs */
+.tabs {
+ margin: 0 0 1em 0;
+ padding: 0;
+ list-style: none;
+}
+
+.tabs ul {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ margin: 0 0 1em 0;
+ padding: 0;
+ list-style: none;
+ border-bottom: 1px solid #ccc;
+}
+
+.tabs ul li {
+ display: inline-flex;
+ max-width: 25%;
+ margin: 0;
+ padding: 0.25em 0.5em;
+ list-style: none;
+ cursor: pointer;
+ flex-grow: 1;
+ justify-content: center;
+ align-items: center;
+ border-radius: 0.75em 0.75em 0 0;
+ border: 1px solid #ddd;
+}
+
+.tabs ul li.selected {
+ background: rgb(200,255,208);
+}
+
+.tabs ul li:hover {
+ background: rgb(190,246,218);
+}
+
+.tabs .body {
+ margin-top: -1em;
+ padding: 1em;
+ border: 1px solid #ccc;
+ border-top: none;
+ border-radius: 0 0 0.75em 0.75em;
+}
+
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 01004dd02..1c65a4164 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -1,30 +1,50 @@
Platypush
#########
-Welcome to the Platypush reference of available plugins, backends and event types.
+Description
+===========
-For more information on Platypush check out:
+This is the main documentation hub for Platypush. It includes both the wiki and
+the complete reference of the available integrations.
-* The `main page`_ of the project
-* The `Gitea page`_ of the project
-* The `Blog articles`_ for inspiration on use-cases possible projects
+Platypush is a general-purpose automation framework that can be used to cover
+all the cases where you'd use a home automation hub, a media center, a smart
+assistant, some IFTTT recipes, and a variety of other products and services.
-.. _main page: https://platypush.tech
-.. _Gitea page: https://git.platypush.tech/platypush/platypush
-.. _Blog articles: https://blog.platypush.tech
+It draws inspiration from the following projects, and it aims to cover all of
+their use-cases:
+
+* `Home Assistant `_
+* `Homebridge `_
+* `OpenHAB `_
+* `IFTTT `_
+* `Tasker `_
+
+Useful links
+============
+
+* The `main page `_ of the project.
+* The `Gitea page `_.
+* The `blog `_, for articles showing how to use
+ Platypush in real-world scenarios.
+
+Wiki
+====
.. toctree::
:maxdepth: 3
- :caption: Wiki:
wiki/index
wiki/Installation
wiki/Configuration
wiki/Installing-extensions
+ wiki/A-configuration-example
+
+Reference
+=========
.. toctree::
:maxdepth: 2
- :caption: Reference:
backends
plugins