UNPKG

@underpostnet/underpost

Version:

Underpost Platform — end-to-end CI/CD and application-delivery toolchain CLI. Covers bare metal, Kubernetes, K3s, kubeadm, LXD, container/image orchestration, secrets, databases, cron jobs, monitoring, SSH, runners, PWA + Workbox delivery, and release orc

479 lines (473 loc) 17.6 kB
import { Css, darkTheme, simpleIconsRender, ThemeEvents, Themes } from './Css.js'; import { Modal, renderViewTitle } from './Modal.js'; import { listenQueryPathInstance, setQueryPath, closeModalRouteChangeEvent, getProxyPath } from './Router.js'; import { s, sIframe } from './VanillaJs.js'; // https://mintlify.com/docs/quickstart class Docs { static async RenderModal(type) { const docData = Docs.Data.find((d) => d.type === type); const ModalId = `modal-docs-${docData.type}`; const { barConfig } = await Themes[Css.currentTheme](); const parentBarMode = Modal.Data['modal-docs'] && Modal.Data['modal-docs'].options.barMode ? Modal.Data['modal-docs'].options.barMode : undefined; await Modal.instance({ barConfig, title: renderViewTitle(docData), id: ModalId, html: async () => { if (docData.renderHtml) return await docData.renderHtml(); return html` <style> .iframe-${ModalId} { width: 100%; border: none; background: white; display: block; } </style> <iframe class="in iframe-${ModalId}" src="${docData.url()}" sandbox="allow-same-origin allow-scripts allow-popups allow-forms allow-popups-to-escape-sandbox allow-top-navigation" > </iframe> `; }, maximize: true, mode: 'view', route: 'docs', slideMenu: 'modal-menu', observer: true, barMode: parentBarMode, query: true, RouterInstance: Modal.Data['modal-docs'].options.RouterInstance, }); const iframeEl = s(`.iframe-${ModalId}`); let swaggerThemeEventKey = null; let unbindIframeLayoutSync = null; if (iframeEl) { const scheduleViewLayoutSync = () => { const sync = () => Modal.syncViewLayout(); sync(); setTimeout(sync, 0); setTimeout(sync, 120); setTimeout(sync, 400); }; iframeEl.addEventListener('load', () => { try { const iframeWin = iframeEl.contentWindow; if (iframeWin) { Object.defineProperty(iframeWin, 'parent', { get: () => iframeWin, configurable: false }); Object.defineProperty(iframeWin, 'top', { get: () => iframeWin, configurable: false }); } } catch (e) { // cross-origin or security restriction — safe to ignore } // Keep the parent window scroll untouched. // These modals are fixed-position; scrolling the parent on iframe navigation // shifts Chrome layout calculations and leaves view modals offset by 50px. // Bind Shift+K inside the iframe to focus the parent SearchBox (mirrors app-wide shortcut) try { const iframeWin = iframeEl.contentWindow; const iframeDoc = iframeEl.contentDocument || iframeEl.contentWindow?.document; if (iframeDoc) { if (unbindIframeLayoutSync) unbindIframeLayoutSync(); const onIframeAnchorClick = (e) => { if (e.target && e.target.closest && e.target.closest('a')) { scheduleViewLayoutSync(); } }; const onIframeHashChange = () => scheduleViewLayoutSync(); const onIframePopState = () => scheduleViewLayoutSync(); iframeDoc.addEventListener('click', onIframeAnchorClick, true); if (iframeWin) { iframeWin.addEventListener('hashchange', onIframeHashChange); iframeWin.addEventListener('popstate', onIframePopState); } unbindIframeLayoutSync = () => { iframeDoc.removeEventListener('click', onIframeAnchorClick, true); if (iframeWin) { iframeWin.removeEventListener('hashchange', onIframeHashChange); iframeWin.removeEventListener('popstate', onIframePopState); } }; iframeDoc.addEventListener('keydown', (e) => { if (e.shiftKey && e.key.toLowerCase() === 'k') { e.preventDefault(); e.stopPropagation(); if (s(`.top-bar-search-box`)) { if (s(`.main-body-btn-ui-close`) && s(`.main-body-btn-ui-close`).classList.contains('hide')) { s(`.main-body-btn-ui-open`).click(); } s(`.top-bar-search-box`).blur(); s(`.top-bar-search-box`).focus(); s(`.top-bar-search-box`).select(); } } }); } } catch (e) { // cross-origin or security restriction — safe to ignore } scheduleViewLayoutSync(); }); if (type === 'src') { swaggerThemeEventKey = `jsdocs-iframe-${ModalId}`; const applyJsDocsTheme = (isDark) => { try { const iframeDoc = iframeEl.contentDocument || iframeEl.contentWindow?.document; if (!iframeDoc || !iframeDoc.documentElement) return; // TypeDoc built-in theme: data-theme on <html>, stored as 'tsd-theme' in localStorage iframeDoc.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light'); iframeEl.contentWindow?.localStorage?.setItem('tsd-theme', isDark ? 'dark' : 'light'); } catch (e) { // cross-origin or security restriction — safe to ignore } }; // Apply current theme as soon as the iframe content is ready iframeEl.addEventListener('load', () => applyJsDocsTheme(darkTheme)); // Keep in sync whenever the parent page theme changes ThemeEvents[swaggerThemeEventKey] = () => { if (s(`.iframe-${ModalId}`)) applyJsDocsTheme(darkTheme); }; } if (type === 'api') { swaggerThemeEventKey = `swagger-iframe-${ModalId}`; const applySwaggerTheme = (isDark) => { try { const iframeDoc = iframeEl.contentDocument || iframeEl.contentWindow?.document; if (!iframeDoc || !iframeDoc.body) return; if (isDark) { iframeDoc.body.classList.add('swagger-dark'); } else { iframeDoc.body.classList.remove('swagger-dark'); } iframeEl.contentWindow?.localStorage?.setItem('swagger-theme', isDark ? 'dark' : 'light'); const toggleBtn = sIframe(iframeEl, '#swagger-theme-toggle'); if (toggleBtn) toggleBtn.textContent = isDark ? '\u2600\uFE0F Light Mode' : '\uD83C\uDF19 Dark Mode'; } catch (e) { // cross-origin or security restriction — safe to ignore } }; // Apply current theme as soon as the iframe content is ready iframeEl.addEventListener('load', () => applySwaggerTheme(darkTheme)); // Keep in sync whenever the parent page theme changes ThemeEvents[swaggerThemeEventKey] = () => { if (s(`.iframe-${ModalId}`)) applySwaggerTheme(darkTheme); }; } } Modal.Data[ModalId].onObserverListener[ModalId] = () => { if (s(`.iframe-${ModalId}`)) { const barEl = s(`.bar-default-modal-${ModalId}`); const barHeight = barEl ? barEl.offsetHeight : Modal.headerTitleHeight; s(`.iframe-${ModalId}`).style.height = `${s(`.${ModalId}`).offsetHeight - barHeight}px`; } if (type.match('coverage')) { simpleIconsRender(`.doc-icon-coverage`); simpleIconsRender(`.doc-icon-coverage-link`); } }; Modal.Data[ModalId].onObserverListener[ModalId](); Modal.Data[ModalId].onCloseListener[ModalId] = () => { if (unbindIframeLayoutSync) unbindIframeLayoutSync(); if (swaggerThemeEventKey) delete ThemeEvents[swaggerThemeEventKey]; closeModalRouteChangeEvent({ closedId: ModalId }); }; } static Data = [ { type: 'repo', icon: html`<i class="fab fa-github"></i>`, text: `Last Release`, url: function () { const tokenOpts = Docs.Tokens['modal-docs']; if (tokenOpts && tokenOpts.lastReleaseUrl) return tokenOpts.lastReleaseUrl(); return `https://github.com/underpostnet/pwa-microservices-template-ghpkg/`; }, }, { type: 'demo', icon: html`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 32 32"> <path fill="currentColor" d="M20 2v12l10-6z" /> <path fill="currentColor" d="M28 14v8H4V6h10V4H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h8v4H8v2h16v-2h-4v-4h8a2 2 0 0 0 2-2v-8zM18 28h-4v-4h4z" /> </svg>`, text: html`Demo`, url: function () { const tokenOpts = Docs.Tokens['modal-docs']; if (tokenOpts && tokenOpts.demoUrl) return tokenOpts.demoUrl(); return `https://underpostnet.github.io/pwa-microservices-template-ghpkg/`; }, }, { type: 'src', icon: html`<i class="fa-brands fa-osi"></i>`, text: 'Source Docs', url: function () { return `${getProxyPath()}docs/engine/${window.renderPayload.version.replace('v', '')}`; }, }, { type: 'api', icon: html`<i class="fa-solid fa-arrows-turn-to-dots"></i>`, text: `Api Docs`, url: function () { return `${getProxyPath()}api-docs`; }, }, { type: 'coverage', icon: html`<img height="20" width="20" class="doc-icon-coverage" />`, text: `Coverage report`, url: function () { const tokenOpts = Docs.Tokens['modal-docs']; if (tokenOpts && tokenOpts.coverageUrl) return tokenOpts.coverageUrl(); return `${getProxyPath()}docs/coverage`; }, themeEvent: () => { if (s(`.doc-icon-coverage`)) setTimeout(() => simpleIconsRender(`.doc-icon-coverage`)); }, }, { type: 'coverage-link', icon: html`<img height="20" width="20" class="doc-icon-coverage-link" />`, text: `Coverage`, url: function () { return `https://coveralls.io/github/underpostnet/engine`; }, themeEvent: () => { if (s(`.doc-icon-coverage-link`)) setTimeout(() => simpleIconsRender(`.doc-icon-coverage-link`)); }, }, ]; static Tokens = {}; static async instance(options = {}) { const { idModal } = options; Docs.Tokens[idModal] = options; setTimeout(() => { s(`.btn-docs-src`).onclick = async () => { setQueryPath({ path: 'docs', queryPath: 'src' }); await Docs.RenderModal('src'); }; s(`.btn-docs-api`).onclick = async () => { setQueryPath({ path: 'docs', queryPath: 'api' }); await Docs.RenderModal('api'); }; s(`.btn-docs-coverage`).onclick = async () => { setQueryPath({ path: 'docs', queryPath: 'coverage' }); await Docs.RenderModal('coverage'); }; s(`.btn-docs-coverage-link`).onclick = () => { const docData = Docs.Data.find((d) => d.type === 'coverage-link'); location.href = docData.url(); }; s(`.btn-docs-repo`).onclick = () => { const docData = Docs.Data.find((d) => d.type === 'repo'); location.href = docData.url(); }; s(`.btn-docs-demo`).onclick = () => { const docData = Docs.Data.find((d) => d.type === 'demo'); location.href = docData.url(); }; listenQueryPathInstance({ id: options.idModal, routeId: 'docs', event: (path) => { if (s(`.btn-docs-${path}`)) s(`.btn-docs-${path}`).click(); if (Modal.mobileModal()) { setTimeout(() => { s(`.btn-close-modal-menu`).click(); }); } }, }); }); // Register theme events for items that have them (Docs-specific concern) for (const docData of Docs.Data) { if (docData.themeEvent) { ThemeEvents[`doc-icon-${docData.type}`] = docData.themeEvent; setTimeout(ThemeEvents[`doc-icon-${docData.type}`]); } } // Build submenu items and populate — submenu system is owned by Modal Modal.subMenuPopulate('docs', await Modal.buildSubMenuItemsHtml('docs', Docs.Data, options)); { const docsData = [ { id: 'getting-started', icon: 'rocket', title: 'Getting Started', description: 'Learn the basics and get started with our platform', }, { id: 'api-docs', icon: 'code', title: 'API Reference', description: 'Detailed documentation of our API endpoints', }, { id: 'guides', icon: 'book', title: 'Guides', description: 'Step-by-step tutorials and how-to guides', }, { id: 'demo', icon: 'laptop-code', title: 'Demo', description: 'Practical examples and code snippets', }, { id: 'faq', icon: 'question-circle', title: 'FAQ', description: 'Frequently asked questions', }, { id: 'community', icon: 'users', title: 'Community', description: 'Join our developer community', }, ]; return html` <style> .docs-landing { padding: 2rem; max-width: 1200px; margin: 0 auto; height: 100%; box-sizing: border-box; } .docs-header { text-align: center; margin-bottom: 3rem; opacity: 0; animation: fadeInUp 0.6s ease-out forwards; } .docs-header h1 { font-size: 2.5rem; margin: 0 0 1rem; line-height: 1.2; } .docs-header p { font-size: 1.2rem; max-width: 700px; margin: 0 auto 2rem; line-height: 1.6; } .docs-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1.5rem; margin: 0; padding: 0; list-style: none; } .docs-card-container { cursor: pointer; opacity: 0; margin-bottom: 3rem; animation: fadeInUp 0.6s ease-out forwards; } .docs-card { border-radius: 8px; padding: 1.5rem; display: flex; flex-direction: column; height: 100%; position: relative; transition: all 0.3s ease-in-out; } .card-icon { font-size: 1.75rem; width: 56px; height: 56px; border-radius: 12px; display: flex; align-items: center; justify-content: center; margin: 0 0 1.25rem; transition: transform 0.2s ease; } .card-content { flex: 1; } .card-content h3 { margin: 0 0 0.5rem; font-size: 1.25rem; font-weight: 600; } .card-content p { margin: 0; font-size: 0.95rem; transition: color 0.3s ease; } </style> <style> ${docsData .map( (_, index) => css` .docs-card-container:nth-child(${index + 1}) { animation-delay: ${0.1 * (index + 1)}s; } `, ) .join('')} </style> <div class="docs-landing"> <div class="docs-header"> <h1>Documentation</h1> <!-- <div class="search-bar"> <i class="fas fa-search"></i> <input type="text" placeholder="Search documentation..." id="docs-search"> </div> --> </div> <ul class="docs-grid"> ${docsData .map((item) => { setTimeout(() => { if (s(`.docs-card-container-${item.id}`)) { s(`.docs-card-container-${item.id}`).onclick = () => { if (item.id.match('demo')) { const demoData = Docs.Data.find((d) => d.type === 'demo'); location.href = demoData ? demoData.url() : 'https://underpostnet.github.io/pwa-microservices-template-ghpkg/'; } else if (item.id.match('api')) { if (s(`.btn-docs-api`)) s(`.btn-docs-api`).click(); } else { if (s(`.btn-docs-src`)) s(`.btn-docs-src`).click(); } }; } }); return html` <div class="in docs-card-container docs-card-container-${item.id}"> <li class="docs-card"> <div class="card-icon"> <i class="fas fa-${item.icon}"></i> </div> <div class="card-content"> <h3>${item.title}</h3> <p>${item.description}</p> </div> </li> </div> `; }) .join('')} </ul> </div> `; } } } export { Docs };