@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
JavaScript
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 };