From f888541fa68e09c0f3abb4c636237de06b3385cb Mon Sep 17 00:00:00 2001 From: Francis Chartrand Date: Thu, 21 Nov 2024 13:09:25 -0500 Subject: [PATCH 1/6] feat(docusaurus): init provisioning plugin for supported devices --- .gitignore | 1 + website/.gitignore | 2 + website/docusaurus.config.ts | 2 + website/src/plugins/provisioning/External.tsx | 16 ++ .../src/plugins/provisioning/builder/index.ts | 87 +++++++++ .../plugins/provisioning/builder/slugify.js | 8 + .../src/plugins/provisioning/builder/utils.ts | 89 +++++++++ .../provisioning/components/Vendors.js | 43 ++++ .../src/plugins/provisioning/provisioning.css | 183 ++++++++++++++++++ 9 files changed, 431 insertions(+) create mode 100644 website/src/plugins/provisioning/External.tsx create mode 100644 website/src/plugins/provisioning/builder/index.ts create mode 100644 website/src/plugins/provisioning/builder/slugify.js create mode 100644 website/src/plugins/provisioning/builder/utils.ts create mode 100644 website/src/plugins/provisioning/components/Vendors.js create mode 100644 website/src/plugins/provisioning/provisioning.css diff --git a/.gitignore b/.gitignore index 4827b8ce..2438bc0c 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,4 @@ yarn-error.log .yarn-integrity tests/node_modules/ +static/provisioning diff --git a/website/.gitignore b/website/.gitignore index 9985a122..efff6ddb 100644 --- a/website/.gitignore +++ b/website/.gitignore @@ -30,6 +30,8 @@ yarn-error.log* !.yarn/sdks !.yarn/versions +static/provisioning + ## temporary --- remove once static folder has been moved inside website/ static static/ diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index a8b33736..ca10a8fa 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -2,6 +2,7 @@ import type * as Preset from '@docusaurus/preset-classic'; import type { Config } from '@docusaurus/types'; import { themes as prismThemes } from 'prism-react-renderer'; import REDIRECTS from './redirects'; +import pluginsProvisioningInit from './src/plugins/provisioning/builder/index'; const config: Config = { title: 'Wazo Platform', @@ -172,6 +173,7 @@ const config: Config = { redirects: REDIRECTS, }, ], + pluginsProvisioningInit ], }; diff --git a/website/src/plugins/provisioning/External.tsx b/website/src/plugins/provisioning/External.tsx new file mode 100644 index 00000000..d49dd1fa --- /dev/null +++ b/website/src/plugins/provisioning/External.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import Vendors from './components/Vendors'; +import './provisioning.css'; + +const Page = ({ route }) => { + const { plugins, images } = route?.customData || {}; + + return ( +
+ +
+ ); +} + +export default Page diff --git a/website/src/plugins/provisioning/builder/index.ts b/website/src/plugins/provisioning/builder/index.ts new file mode 100644 index 00000000..867c4b5e --- /dev/null +++ b/website/src/plugins/provisioning/builder/index.ts @@ -0,0 +1,87 @@ +const fs = require('fs'); +const { exec } = require('child_process'); +const path = require('path'); + +const { getProvisioningPlugins, walk } = require('./utils'); +const slugify = require('./slugify'); + +const generatePages = async ({ plugins, actions }) => { + // Retrieve images + const destPath = './static/provisioning/'; + exec(`mkdir -p ${destPath}`); + const imagesPaths = walk('../content/provisioning/img', /.png$/, 'binary'); + + const images = {}; + + // Move only needed images to plulic folder + Object.keys(imagesPaths).forEach(basePath => { + const vendorName = basePath.split('/').pop(); + const isVendor = vendorName === 'vendors'; + + Object.keys(imagesPaths[basePath]).forEach(fileName => { + const filePath = `${destPath}/${isVendor ? '' : `${vendorName}-`}${fileName}`; + + if (!images[vendorName]) images[vendorName] = []; + images[vendorName].push(fileName); + + fs.writeFileSync(filePath, imagesPaths[basePath][fileName], { encoding: 'binary' }); + }); + }); + + // Create vendors page + // await newPage('/uc-doc/ecosystem/supported_devices', 'provisioning/vendors', { + // plugins, + // images: imgs, + // }); + + // Create external page + // await newPage('/provisioning/external', 'provisioning/external', { plugins, images: imgs }); + await actions.addRoute({ + path: '/provisioning/external/', + component: "@site/src/plugins/provisioning/External.tsx", + exact: true, + customData: { plugins, images } + }); + + + // Create vendor pages + // await Promise.all( + // Object.keys(plugins).map(vendor => + // newPage(`/provisioning/${slugify(vendor)}`, 'provisioning/vendor', { + // name: vendor, + // vendor_plugins: plugins[vendor], + // vendor_images: imgs[slugify(vendor)], + // }) + // ) + // ); + + // Create phone pages + // const phonePagesPromises = []; + // Object.keys(plugins).forEach(vendor => { + // Object.keys(plugins[vendor]).forEach(name => { + // phonePagesPromises.push( + // newPage(`/provisioning/${slugify(vendor)}/${slugify(name)}`, 'provisioning/phone', { + // name, + // vendor, + // phone: plugins[vendor][name], + // vendor_images: imgs[slugify(vendor)], + // }) + // ); + // }); + // }); + // await Promise.all[phonePagesPromises]; +}; + + +export default async (context, options) => ({ + name: "provisioning-pages-build", + + async loadContent() { + const plugins = getProvisioningPlugins(); + return { plugins } ; + }, + + async contentLoaded({ content, actions }) { + await generatePages({ plugins: content.plugins, actions }); + }, +}) diff --git a/website/src/plugins/provisioning/builder/slugify.js b/website/src/plugins/provisioning/builder/slugify.js new file mode 100644 index 00000000..6393055d --- /dev/null +++ b/website/src/plugins/provisioning/builder/slugify.js @@ -0,0 +1,8 @@ +// @see: gist.github.com/mathewbyrne/1280286 +module.exports = (text) => + text.toString().toLowerCase() + .replace(/\s+/g, '-') + .replace(/[^\w-]+/g, '') + .replace(/--+/g, '-') + .replace(/^-+/, '') + .replace(/-+$/, ''); diff --git a/website/src/plugins/provisioning/builder/utils.ts b/website/src/plugins/provisioning/builder/utils.ts new file mode 100644 index 00000000..4a53f148 --- /dev/null +++ b/website/src/plugins/provisioning/builder/utils.ts @@ -0,0 +1,89 @@ +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +let cachedPlugins = null; + +export const walk = (basePath, regexp, encoding = 'utf8', custom = false) => { + const files = fs.readdirSync(basePath); + const dirname = basePath.split('/').pop(); + let results = { [dirname]: {} }; + + files.forEach(file => { + const filePath = `${basePath}/${file}`; + const stat = fs.lstatSync(filePath); + + // Do not follow symlinks to avoid max recursion on OSX with filename in different case + // eg: Ringlist.xml symlink to RINGLIST.XML + if (stat.isSymbolicLink()) { + return; + } + + if (stat.isDirectory()) { + results = { ...results, ...walk(filePath, regexp, encoding, custom) }; + } else if (file.match(regexp)) { + // HEADS UP: hard code + const wazo_plugin = `${basePath.split('/')[4]}-${basePath.split('/')[5]}`; + const content = fs.readFileSync(filePath, encoding); + results[dirname][file] = custom ? { + path: path.dirname(filePath), + file: content, + wazo_plugin, + } : content; + } + }); + + return results; +}; + +export const getProvisioningPlugins = () => { + if (cachedPlugins) { + return cachedPlugins; + } + + // Clone the repo + const repoPath = '/tmp/wazo-provd-plugins'; + execSync(` + rm -rf ${repoPath}; + git clone https://github.com/wazo-platform/wazo-provd-plugins.git ${repoPath}; + `); + + const pluginInfoFiles = walk(repoPath, /plugin-info/, null, true); + cachedPlugins = {}; + + // Parse plugin-info files + Object.keys(pluginInfoFiles).forEach(basePath => { + Object.keys(pluginInfoFiles[basePath]).forEach(fileName => { + try { + const { wazo_plugin, file, path: localPath } = pluginInfoFiles[basePath][fileName]; + const content = JSON.parse(file); + + Object.keys(content.capabilities).forEach(capabilityName => { + const [vendor, phone, firmware] = capabilityName.split(', '); + if (!(vendor in cachedPlugins)) { + cachedPlugins[vendor] = {}; + } + if (!(phone in cachedPlugins[vendor])) { + cachedPlugins[vendor][phone] = {}; + } + + const installPath = `${localPath}/install.md`; + const limitationsPath = `${localPath}/limitations.md`; + const install = fs.existsSync(installPath) ? fs.readFileSync(installPath, { encoding:'utf8', flag:'r' }) : null; + const limitations = fs.existsSync(limitationsPath) ? fs.readFileSync(limitationsPath, { encoding:'utf8', flag:'r' }) : null; + + cachedPlugins[vendor][phone][firmware] = content.capabilities[capabilityName]; + cachedPlugins[vendor][phone][firmware].wazo_plugin = `${wazo_plugin} (v${content.version})`; + cachedPlugins[vendor][phone][firmware].install = install; + cachedPlugins[vendor][phone][firmware].limitations = limitations; + }); + } catch (error) { + console.log('json error in ', basePath, fileName, error); + } + }); + }); + + delete cachedPlugins['*']; + + return cachedPlugins; +}; diff --git a/website/src/plugins/provisioning/components/Vendors.js b/website/src/plugins/provisioning/components/Vendors.js new file mode 100644 index 00000000..4d657eaa --- /dev/null +++ b/website/src/plugins/provisioning/components/Vendors.js @@ -0,0 +1,43 @@ +import React, { useState } from 'react'; + +import { buildTable } from '../helpers'; +import slugify from '../builder/slugify' + +const Page = ({ plugins, images }) => { + const [currentVendor, setCurrentVendor] = useState(null); + const [currentDevice, setCurrentDevice] = useState(null); + + const onDeviceClick = event => { + event.preventDefault(); + setCurrentDevice(currentDevice === event.target.id ? null : event.target.id); + } + + const onVendorClick = event => { + event.preventDefault(); + setCurrentVendor(currentVendor === event.target.id ? null : event.target.id); + } + + return Object.keys(plugins).map(vendor => ( +
+ {vendor} + {vendor === currentVendor && +
+ {Object.keys(plugins[vendor]).sort().map(name => { + const id = `${slugify(vendor)}-${slugify(name)}`; + return
+ {name} + {id === currentDevice &&
+
+
{name}
+ { images?.[slugify(vendor)]?.indexOf(`${slugify(name)}.png`) !== -1 ? {`${slugify(vendor)}-${name}`}/ : {`${slugify(vendor)}-${name}`}} +
+
{buildTable(plugins[vendor][name])}
+
} +
; + })} +
} +
+ )); +}; + +export default Page diff --git a/website/src/plugins/provisioning/provisioning.css b/website/src/plugins/provisioning/provisioning.css new file mode 100644 index 00000000..56760446 --- /dev/null +++ b/website/src/plugins/provisioning/provisioning.css @@ -0,0 +1,183 @@ + +.provisioning-phone, +.provisioning-vendors, +.provisioning-vendor { + .card { + margin-bottom: 20px; + a.card-header { + color: #777; + } + .body { + display: flex; + align-items: center; + justify-content: center; + min-height: 25vw; + padding: 20px; + a { + display: block; + text-align: center; + width: 75%; + } + img { + width: 100%; + height: auto; + } + } + } +} + +.provisioning-vendors { + &.platform { + width: 66.66%; + } + .vendor { + a.title { + text-align: center; + margin-top: 2px; + text-transform: uppercase; + display: block; + padding: 8px; + color: #444; + font-size: 1.2em; + background: #e8e8e8; + } + .phones { + width: 100%; + display: flex; + flex-wrap: wrap; + align-items: flex-start; + justify-content: flex-start; + div{ + flex: 0 20%; + &.selected { + flex: 0 100%; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + > a{ + display: none; + margin-bottom: 0; + color: #444; + font-weight: bold; + font-size: 1.2em; + letter-spacing: 0.2em; + border-right: none; + background-color: #fff; + + } + } + + > a { + color: #444; + border-right: 1px solid #ccc; + display: block; + text-align: center; + padding: 15px; + } + + .slide { + display: flex; + align-items: stretch; + flex: 0 100%; + background-color: #fff; + .image { + flex: 0 30%; + padding: 30px 20px; + text-align: center; + .name { + font-weight: bold; + margin-bottom: 20px; + font-size: 1.2em; + } + img{ + max-width: 100%; + height: auto; + } + } + .content { + table.table { + width: 100%; + + thead{ + th { + border-top: 0 none; + } + } + + tr.marked-button { + transition: all .3s ease-out; + cursor: pointer; + color: #17baef; + + &:hover { + color: #085b77; + } + + & .status { + text-align: right; + padding-right: 15px; + + &::after { + content: '+'; + } + } + + &.selected { + background-color: #eee; + & .status { + &::after { + content: '-'; + } + } + } + } + + tr.marked-content { + background-color: #eee; + transition: all .3s ease-out; + height: auto; + + td { + border-top: none; + padding-left: 25px; + padding-right: 25px; + } + + &.hidden { + display: none; + } + } + + .key { + text-transform: capitalize; + } + .value { + text-align: right; + padding-right: 30px; + } + } + flex: 0 100%; + padding: 20px; + } + } + } + } + } +} + +.provisioning-phone { + .col-card { + display: flex; + align-items: stretch; + .card{ + margin-bottom: 0; + width: 100%; + } + } + pre { + white-space: pre; + margin-bottom: 0; + } + .row { + align-items: stretch; + } +} From 51bfd50b6a0a1139c1ca9d956214b115e5d9713e Mon Sep 17 00:00:00 2001 From: Francis Chartrand Date: Thu, 21 Nov 2024 14:43:54 -0500 Subject: [PATCH 2/6] feat(docusaurus): add vendors and phone pages --- website/src/plugins/provisioning/External.tsx | 6 +- website/src/plugins/provisioning/Phone.tsx | 44 ++++++++++++ website/src/plugins/provisioning/Vendor.tsx | 50 ++++++++++++++ .../src/plugins/provisioning/builder/index.ts | 58 +++++++++------- website/src/plugins/provisioning/helpers.js | 69 +++++++++++++++++++ .../src/plugins/provisioning/provisioning.css | 26 ++++++- 6 files changed, 224 insertions(+), 29 deletions(-) create mode 100644 website/src/plugins/provisioning/Phone.tsx create mode 100644 website/src/plugins/provisioning/Vendor.tsx create mode 100644 website/src/plugins/provisioning/helpers.js diff --git a/website/src/plugins/provisioning/External.tsx b/website/src/plugins/provisioning/External.tsx index d49dd1fa..f6105bc0 100644 --- a/website/src/plugins/provisioning/External.tsx +++ b/website/src/plugins/provisioning/External.tsx @@ -3,14 +3,14 @@ import React from 'react'; import Vendors from './components/Vendors'; import './provisioning.css'; -const Page = ({ route }) => { +const ExternalPage = ({ route }) => { const { plugins, images } = route?.customData || {}; return ( -
+
); } -export default Page +export default ExternalPage diff --git a/website/src/plugins/provisioning/Phone.tsx b/website/src/plugins/provisioning/Phone.tsx new file mode 100644 index 00000000..5d5fa6f6 --- /dev/null +++ b/website/src/plugins/provisioning/Phone.tsx @@ -0,0 +1,44 @@ +import React from 'react'; + +import Layout from "@theme/Layout"; + +import slugify from './builder/slugify'; +import { buildTable } from './helpers'; +import './provisioning.css'; +import Head from '@docusaurus/Head'; + +const VENDORS_URL = '/uc-doc/ecosystem/supported_devices'; + +const PhonePage = ({ route }) => { + const { name, vendor, phone, vendor_images } = route?.customData || {}; + + const breadcrumbs = [ + { url: VENDORS_URL, label: 'Provd plugins' }, + { url: `/provisioning/${slugify(vendor)}`, label: vendor }, + { label: name }, + ]; + + return ( + + + { breadcrumbs.map(({ label }) => label).join(' - ') } + +
+
+
+
+
+
+ {vendor_images && vendor_images.indexOf(`${slugify(name)}.png`) !== -1 ? {`${slugify(vendor)}-${name}`}/ : {`${slugify(vendor)}-${name}`}} +
+
+
+
{buildTable(phone)}
+
+
+
+
+ ); +} + +export default PhonePage; diff --git a/website/src/plugins/provisioning/Vendor.tsx b/website/src/plugins/provisioning/Vendor.tsx new file mode 100644 index 00000000..c6c0773a --- /dev/null +++ b/website/src/plugins/provisioning/Vendor.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import Layout from "@theme/Layout"; + +import './provisioning.css'; +import slugify from './builder/slugify'; +import Head from '@docusaurus/Head'; + +const vendorsUrl = '/uc-doc/ecosystem/supported_devices'; + + +const VendorPage = ({ route }) => { + const { name, vendor_plugins, vendor_images } = route?.customData || {}; + + const breadcrumbs = [ + { url: vendorsUrl, label: 'Provd plugins' }, + { url: `/provisioning/${slugify(name)}`, label: name }, + ]; + + return ( + + + { breadcrumbs.map(({ label }) => label).join(' - ') } + +
+
+
+
+ {Object.keys(vendor_plugins).map(phoneName => ( + + ))} +
+
+
+
+
+ ); +} + +export default VendorPage; diff --git a/website/src/plugins/provisioning/builder/index.ts b/website/src/plugins/provisioning/builder/index.ts index 867c4b5e..9fbb22fb 100644 --- a/website/src/plugins/provisioning/builder/index.ts +++ b/website/src/plugins/provisioning/builder/index.ts @@ -1,6 +1,5 @@ const fs = require('fs'); const { exec } = require('child_process'); -const path = require('path'); const { getProvisioningPlugins, walk } = require('./utils'); const slugify = require('./slugify'); @@ -35,7 +34,6 @@ const generatePages = async ({ plugins, actions }) => { // }); // Create external page - // await newPage('/provisioning/external', 'provisioning/external', { plugins, images: imgs }); await actions.addRoute({ path: '/provisioning/external/', component: "@site/src/plugins/provisioning/External.tsx", @@ -45,31 +43,41 @@ const generatePages = async ({ plugins, actions }) => { // Create vendor pages - // await Promise.all( - // Object.keys(plugins).map(vendor => - // newPage(`/provisioning/${slugify(vendor)}`, 'provisioning/vendor', { - // name: vendor, - // vendor_plugins: plugins[vendor], - // vendor_images: imgs[slugify(vendor)], - // }) - // ) - // ); + await Promise.all( + Object.keys(plugins).map(vendor => + actions.addRoute({ + path: `/provisioning/${slugify(vendor)}`, + component: "@site/src/plugins/provisioning/Vendor.tsx", + exact: true, + customData: { + name: vendor, + vendor_plugins: plugins[vendor], + vendor_images: images[slugify(vendor)], + } + }) + ) + ); // Create phone pages - // const phonePagesPromises = []; - // Object.keys(plugins).forEach(vendor => { - // Object.keys(plugins[vendor]).forEach(name => { - // phonePagesPromises.push( - // newPage(`/provisioning/${slugify(vendor)}/${slugify(name)}`, 'provisioning/phone', { - // name, - // vendor, - // phone: plugins[vendor][name], - // vendor_images: imgs[slugify(vendor)], - // }) - // ); - // }); - // }); - // await Promise.all[phonePagesPromises]; + const phonePagesPromises = []; + Object.keys(plugins).forEach(vendor => { + Object.keys(plugins[vendor]).forEach(phone => { + phonePagesPromises.push( + actions.addRoute({ + path: `/provisioning/${slugify(vendor)}/${slugify(phone)}`, + component: "@site/src/plugins/provisioning/Phone.tsx", + exact: true, + customData: { + name: phone, + vendor, + phone: plugins[vendor][phone], + vendor_images: images[slugify(vendor)], + } + }) + ); + }); + }); + await Promise.all[phonePagesPromises]; }; diff --git a/website/src/plugins/provisioning/helpers.js b/website/src/plugins/provisioning/helpers.js new file mode 100644 index 00000000..f48b0902 --- /dev/null +++ b/website/src/plugins/provisioning/helpers.js @@ -0,0 +1,69 @@ +import React from 'react'; +import ReactMarkdown from 'react-markdown'; + +const MarkedDown = ({ label, content }) => { + if (!content) { + return null; + } + + const toggleContent = event => { + event.currentTarget.classList.toggle('selected') + event.currentTarget.nextSibling.classList.toggle('hidden') + }; + + // @FIXME: this is quite a lazy assumption -- that the first line is the title + const editedContent = content.split("\n").slice(1).join("\n") + + return ( + <> + + {label} + +
+ + + + +
+ + + + ); +} + +export const buildTable = data => { + return Object.keys(data).map(version => { + const allCapabilities = data[version]; + const { install, limitations, wazo_plugin: wazoPlugin, ...capabilities } = allCapabilities; + + return + + + + + + + + + + {Object.keys(capabilities).map(key => { + let value = capabilities[key]; + if (value === true) { + value = 'yes'; + } + if (value === false) { + value = 'no'; + } + return + + + ; + })} + +
Firmware
+ Wazo Plugin
+ {version}
+ {wazoPlugin || <> } +
{key.replace('_', ' ')}{value}
; + }) + } diff --git a/website/src/plugins/provisioning/provisioning.css b/website/src/plugins/provisioning/provisioning.css index 56760446..97bd3e34 100644 --- a/website/src/plugins/provisioning/provisioning.css +++ b/website/src/plugins/provisioning/provisioning.css @@ -2,11 +2,27 @@ .provisioning-phone, .provisioning-vendors, .provisioning-vendor { + padding: 40px 0; + + &.provisioning-vendors-external { padding: 0; } + .card { margin-bottom: 20px; - a.card-header { + &:hover, &:active { + a.card__header { + background-color: rgba(0,0,0,1); + color: #FFF; + } + } + + a.card__header { + transition: 200ms color ease, 200ms background ease; color: #777; + background-color: rgba(0,0,0,.03); + padding: 10px 16px; + text-decoration: none; } + .body { display: flex; align-items: center; @@ -173,10 +189,18 @@ width: 100%; } } + + table.table { + display: table; + width: 100%; + margin-bottom: 0; + } + pre { white-space: pre; margin-bottom: 0; } + .row { align-items: stretch; } From 0ff4bc16ebf51f7beab40bb953855ac7710ef68f Mon Sep 17 00:00:00 2001 From: Francis Chartrand Date: Thu, 21 Nov 2024 14:53:13 -0500 Subject: [PATCH 3/6] feat(docusaurus): add support devices inside the uc-doc --- biome.json | 8 ++++++- .../src/plugins/provisioning/builder/index.ts | 22 +++++++------------ website/uc-doc/ecosystem/index.md | 2 +- website/uc-doc/ecosystem/supported_devices.md | 5 +++++ 4 files changed, 21 insertions(+), 16 deletions(-) create mode 100644 website/uc-doc/ecosystem/supported_devices.md diff --git a/biome.json b/biome.json index 0fabfff7..a355e8bf 100644 --- a/biome.json +++ b/biome.json @@ -12,7 +12,13 @@ "linter": { "enabled": true, "rules": { - "recommended": true + "recommended": true, + "complexity": { + "noForEach": "off" + }, + "style": { + "useImportType": "off" + } } }, "javascript": { diff --git a/website/src/plugins/provisioning/builder/index.ts b/website/src/plugins/provisioning/builder/index.ts index 9fbb22fb..413396ad 100644 --- a/website/src/plugins/provisioning/builder/index.ts +++ b/website/src/plugins/provisioning/builder/index.ts @@ -27,12 +27,6 @@ const generatePages = async ({ plugins, actions }) => { }); }); - // Create vendors page - // await newPage('/uc-doc/ecosystem/supported_devices', 'provisioning/vendors', { - // plugins, - // images: imgs, - // }); - // Create external page await actions.addRoute({ path: '/provisioning/external/', @@ -82,14 +76,14 @@ const generatePages = async ({ plugins, actions }) => { export default async (context, options) => ({ - name: "provisioning-pages-build", + name: "provisioning-pages-build", - async loadContent() { - const plugins = getProvisioningPlugins(); - return { plugins } ; - }, + async loadContent() { + const plugins = getProvisioningPlugins(); + return { plugins } ; + }, - async contentLoaded({ content, actions }) { - await generatePages({ plugins: content.plugins, actions }); - }, + async contentLoaded({ content, actions }) { + await generatePages({ plugins: content.plugins, actions }); + }, }) diff --git a/website/uc-doc/ecosystem/index.md b/website/uc-doc/ecosystem/index.md index 3611c857..fb6b3a9f 100644 --- a/website/uc-doc/ecosystem/index.md +++ b/website/uc-doc/ecosystem/index.md @@ -6,6 +6,6 @@ import CardList from '@site/src/components/Card/CardList'; diff --git a/website/uc-doc/ecosystem/supported_devices.md b/website/uc-doc/ecosystem/supported_devices.md new file mode 100644 index 00000000..3cdd68a4 --- /dev/null +++ b/website/uc-doc/ecosystem/supported_devices.md @@ -0,0 +1,5 @@ +--- +title: Supported Devices +--- + +