diff --git a/docs/source/_static/scripts/custom.js b/docs/source/_static/scripts/custom.js index f9c4d17167..576d79ce13 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 = `${text}` } + 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 5ae1804496..5091dead23 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 01004dd02d..1c65a41649 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