diff --git a/.gitignore b/.gitignore index 2984493b57..1d7ee958d6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ build build-pico-sdk-docs documentation/html documentation/asciidoc/pico-sdk +.venv diff --git a/Gemfile b/Gemfile index a54d14c209..d342d0b9a9 100644 --- a/Gemfile +++ b/Gemfile @@ -21,6 +21,8 @@ gem "minima", "~> 2.0" group :jekyll_plugins do gem "jekyll-feed", "~> 0.17" gem 'jekyll-asciidoc' + gem 'asciidoctor' + gem 'asciidoctor-tabs', ">= 1.0.0.beta.6" end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem @@ -37,4 +39,4 @@ gem "nokogiri", "~> 1.16" # So we can add custom element templates gem 'slim', '~> 5.2.1' -gem 'thread_safe', '~> 0.3.5' \ No newline at end of file +gem 'thread_safe', '~> 0.3.5' diff --git a/Gemfile.lock b/Gemfile.lock index a8d10ab265..5808c168c6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,6 +4,8 @@ GEM addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) asciidoctor (2.0.20) + asciidoctor-tabs (1.0.0.beta.6) + asciidoctor (>= 2.0.0, < 3.0.0) colorator (1.1.0) concurrent-ruby (1.2.3) em-websocket (0.5.3) @@ -94,6 +96,8 @@ PLATFORMS ruby DEPENDENCIES + asciidoctor + asciidoctor-tabs (>= 1.0.0.beta.6) jekyll (~> 4.3.3) jekyll-asciidoc jekyll-feed (~> 0.17) diff --git a/_config.yml b/_config.yml index acfa472d3d..66a8c67ba3 100644 --- a/_config.yml +++ b/_config.yml @@ -25,6 +25,7 @@ githubbranch_edit: develop # Build settings theme: minima plugins: + - asciidoctor-tabs - jekyll-asciidoc - jekyll-feed diff --git a/documentation/asciidoc/computers/remote-access/ssh.adoc b/documentation/asciidoc/computers/remote-access/ssh.adoc index 0d3e385132..acdec020dc 100644 --- a/documentation/asciidoc/computers/remote-access/ssh.adoc +++ b/documentation/asciidoc/computers/remote-access/ssh.adoc @@ -7,24 +7,26 @@ You can access the terminal of a Raspberry Pi remotely from another computer on By default, Raspberry Pi OS disables the SSH server. Enable SSH in one of the following ways: -==== On the desktop - +[tabs] +====== +On the desktop:: ++ . From the *Preferences* menu, launch *Raspberry Pi Configuration*. . Navigate to the *Interfaces* tab. . Select *Enabled* next to *SSH*. . Click *OK*. -==== While flashing a fresh OS image - +While flashing a fresh OS image:: ++ To configure SSH on a completely new installation of Raspberry Pi OS: - ++ . Follow the instructions in the xref:../computers/getting-started.adoc#raspberry-pi-imager[Install with Imager] guide. . During the **OS Customisation** step, navigate to the **Services** tab. . Tick the checkbox to **Enable SSH**. . Select **password authentication** to log in using the same username and password you use while physically using your Raspberry Pi. Select **Allow public-key authentication only** to xref:remote-access.adoc#configure-ssh-without-a-password[configure an SSH key] for passwordless login. -==== From the terminal - +From the terminal:: ++ . Enter `sudo raspi-config` in a terminal window. . Select `Interfacing Options`. . Navigate to and select `SSH`. @@ -32,8 +34,8 @@ To configure SSH on a completely new installation of Raspberry Pi OS: . Select `Ok`. . Choose `Finish`. -==== Manually - +Manually:: ++ . Create an empty file named `ssh` in the boot partition: + [source,console] @@ -46,6 +48,7 @@ $ sudo touch /boot/firmware/ssh ---- $ sudo reboot ---- +====== === Connect to an SSH server diff --git a/jekyll-assets/_includes/head.html b/jekyll-assets/_includes/head.html index 369ae86463..4780553b3c 100644 --- a/jekyll-assets/_includes/head.html +++ b/jekyll-assets/_includes/head.html @@ -18,6 +18,7 @@ + + + + diff --git a/jekyll-assets/css/asciidoctor-tabs.css b/jekyll-assets/css/asciidoctor-tabs.css new file mode 100644 index 0000000000..1afbd9185a --- /dev/null +++ b/jekyll-assets/css/asciidoctor-tabs.css @@ -0,0 +1,116 @@ + +/*! Asciidoctor Tabs | Copyright (c) 2018-present Dan Allen | MIT License */ +.tabs { + margin-bottom: 1.25em; +} + +.tablist > ul { + display: flex; + flex-wrap: nowrap; + overflow-x: scroll; + overflow-y: hidden; + list-style: none; + margin: 0; + padding: 0; +} + +.tablist > ul li { + align-items: center; + background-color: #eee; + cursor: pointer; + display: flex; + font-weight: bold; + line-height: 1.5; + padding: 0.25em 1em; + position: relative; +} + +.tablist > ul li p { + margin-bottom: 0px !important; +} + +.tablist > ul li:focus-visible { + outline: none; +} + +.tablist.ulist, +.tablist.ulist > ul li { + margin: 0; +} + +.tablist.ulist > ul li + li { + margin-left: 0.25em; +} + +.tabs .tablist li::after { + content: ""; + display: block; + height: 1px; + position: absolute; + bottom: -1px; + left: 0; + right: 0; +} + +.tabs.is-loading .tablist li:not(:first-child), +.tabs:not(.is-loading) .tablist li:not(.is-selected) { + background-color: initial; + border-left: 1px solid #eee; + border-right: 1px solid #eee; + border-top: 1px solid #eee; +} + +.tabs.is-loading .tablist li:first-child::after, +.tabs:not(.is-loading) .tablist li.is-selected::after { + background-color: #eee; + border-top: 2px solid var(--red); +} + +/* +.tabs:not(.is-loading) .tablist li, +.tabs:not(.is-loading) .tablist li::after { + transition: background-color 200ms ease-in-out; +} +*/ + +.tablist > ul p { + line-height: inherit; + margin: 0; +} + +.tabpanel { + background-color: #eee; + padding: 1.25em; +} + +.tablist > ul li { + border-bottom: 0; +} + +.tabs.is-loading .tabpanel + .tabpanel, +.tabs:not(.is-loading) .tabpanel.is-hidden { + display: none; +} + +.tabpanel > :first-child { + margin-top: 0; +} + +/* #content is a signature of the Asciidoctor standalone HTML output */ +#content .tabpanel > :last-child, +#content .tabpanel > :last-child > :last-child, +#content .tabpanel > :last-child > :last-child > li:last-child > :last-child { + margin-bottom: 0; +} + +.tablecontainer { + overflow-x: auto; +} + +#content .tablecontainer { + margin-bottom: 1.25em; +} + +#content .tablecontainer > table.tableblock { + margin-bottom: 0; +} diff --git a/jekyll-assets/css/tabs.css b/jekyll-assets/css/tabs.css index 04d927ed0f..b1880c02a6 100644 --- a/jekyll-assets/css/tabs.css +++ b/jekyll-assets/css/tabs.css @@ -79,4 +79,4 @@ p#release + #container { display: block; width: 100%; } -} +} \ No newline at end of file diff --git a/jekyll-assets/scripts/asciidoctor-tabs.js b/jekyll-assets/scripts/asciidoctor-tabs.js new file mode 100644 index 0000000000..f8966c0247 --- /dev/null +++ b/jekyll-assets/scripts/asciidoctor-tabs.js @@ -0,0 +1,127 @@ +;(function () { /*! Asciidoctor Tabs | Copyright (c) 2018-present Dan Allen | MIT License */ + 'use strict' + + var config = (document.currentScript || {}).dataset || {} + var forEach = Array.prototype.forEach + + init(document.querySelectorAll('.tabs')) + + function init (tabsBlocks) { + if (!tabsBlocks.length) return + forEach.call(tabsBlocks, function (tabs) { + var syncIds = tabs.classList.contains('is-sync') ? {} : undefined + var tablist = tabs.querySelector('.tablist ul') + tablist.setAttribute('role', 'tablist') + var start + forEach.call(tablist.querySelectorAll('li'), function (tab, idx) { + tab.tabIndex = -1 + tab.setAttribute('role', tab.classList.add('tab') || 'tab') + var id, anchor, syncId + if (!(id = tab.id) && (anchor = tab.querySelector('a[id]'))) { + id = tab.id = anchor.parentNode.removeChild(anchor).id + } + var panel = id && tabs.querySelector('.tabpanel[aria-labelledby~="' + id + '"]') + if (!panel) return idx ? undefined : toggleSelected(tab, true) // invalid state + syncIds && (((syncId = tab.textContent.trim()) in syncIds) ? (syncId = undefined) : true) && + (syncIds[(tab.dataset.syncId = syncId)] = tab) + idx || (syncIds && (start = { tab: tab, panel: panel })) ? toggleHidden(panel, true) : toggleSelected(tab, true) + tab.setAttribute('aria-controls', panel.id) + panel.setAttribute('role', 'tabpanel') + var onClick = syncId === undefined ? activateTab : activateTabSync + tab.addEventListener('click', onClick.bind({ tabs: tabs, tab: tab, panel: panel })) + }) + if (!tabs.closest('.tabpanel')) { + forEach.call(tabs.querySelectorAll('.tabpanel table.tableblock'), function (table) { + var container = Object.assign(document.createElement('div'), { className: 'tablecontainer' }) + table.parentNode.insertBefore(container, table).appendChild(table) + }) + } + if (start) { + var syncGroupId + for (var i = 0, lst = tabs.classList, len = lst.length, className; i !== len; i++) { + if (!(className = lst.item(i)).startsWith('data-sync-group-id=')) continue + tabs.dataset.syncGroupId = syncGroupId = lst.remove(className) || className.slice(19).replace(/\u00a0/g, ' ') + break + } + if (syncGroupId === undefined) tabs.dataset.syncGroupId = syncGroupId = Object.keys(syncIds).sort().join('|') + var preferredSyncId = 'syncStorageKey' in config && + window[(config.syncStorageScope || 'local') + 'Storage'].getItem(config.syncStorageKey + '-' + syncGroupId) + var tab = preferredSyncId && syncIds[preferredSyncId] + tab && Object.assign(start, { tab: tab, panel: document.getElementById(tab.getAttribute('aria-controls')) }) + toggleSelected(start.tab, true) || toggleHidden(start.panel, false) + } + }) + onHashChange() + toggleClassOnEach(tabsBlocks, 'is-loading', 'remove') + window.setTimeout(toggleClassOnEach.bind(null, tabsBlocks, 'is-loaded', 'add'), 0) + window.addEventListener('hashchange', onHashChange) + } + + function activateTab (e) { + var tab = this.tab + var tabs = this.tabs || (this.tabs = tab.closest('.tabs')) + var panel = this.panel || (this.panel = document.getElementById(tab.getAttribute('aria-controls'))) + querySelectorWithSiblings(tabs, '.tablist .tab', 'tab').forEach(function (el) { + toggleSelected(el, el === tab) + }) + querySelectorWithSiblings(tabs, '.tabpanel', 'tabpanel').forEach(function (el) { + toggleHidden(el, el !== panel) + }) + if (!this.isSync && 'syncStorageKey' in config && 'syncGroupId' in tabs.dataset) { + var storageKey = config.syncStorageKey + '-' + tabs.dataset.syncGroupId + window[(config.syncStorageScope || 'local') + 'Storage'].setItem(storageKey, tab.dataset.syncId) + } + if (!e) return + var loc = window.location + var hashIdx = loc.hash ? loc.href.indexOf('#') : -1 + if (~hashIdx) window.history.replaceState(null, '', loc.href.slice(0, hashIdx)) + e.preventDefault() + } + + function activateTabSync (e) { + activateTab.call(this, e) + var thisTabs = this.tabs + var thisTab = this.tab + var initialY = thisTabs.getBoundingClientRect().y + forEach.call(document.querySelectorAll('.tabs'), function (tabs) { + if (tabs === thisTabs || tabs.dataset.syncGroupId !== thisTabs.dataset.syncGroupId) return + querySelectorWithSiblings(tabs, '.tablist .tab', 'tab').forEach(function (tab) { + if (tab.dataset.syncId === thisTab.dataset.syncId) activateTab.call({ tabs: tabs, tab: tab, isSync: true }) + }) + }) + var shiftedBy = thisTabs.getBoundingClientRect().y - initialY + if (shiftedBy && (shiftedBy = Math.round(shiftedBy))) window.scrollBy({ top: shiftedBy, behavior: 'instant' }) + } + + function querySelectorWithSiblings (scope, selector, siblingClass) { + var el = scope.querySelector(selector) + if (!el) return [] + var result = [el] + while ((el = el.nextElementSibling) && el.classList.contains(siblingClass)) result.push(el) + return result + } + + function toggleClassOnEach (elements, className, method) { + forEach.call(elements, function (el) { + el.classList[method](className) + }) + } + + function toggleHidden (el, state) { + el.classList[(el.hidden = state) ? 'add' : 'remove']('is-hidden') + } + + function toggleSelected (el, state) { + el.setAttribute('aria-selected', '' + state) + el.classList[state ? 'add' : 'remove']('is-selected') + el.tabIndex = state ? 0 : -1 + } + + function onHashChange () { + var id = window.location.hash.slice(1) + if (!id) return + var tab = document.getElementById(~id.indexOf('%') ? decodeURIComponent(id) : id) + if (!(tab && tab.classList.contains('tab'))) return + 'syncId' in tab.dataset ? activateTabSync.call({ tab: tab }) : activateTab.call({ tab: tab }) + } +})()