nhance/app/Views/docs/partials/docs_footer.php
2026-05-18 12:28:19 +05:30

543 lines
16 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Docs Footer Partial
* app/Views/docs/partials/docs_footer.php
*
* Closes the body/html tags and initialises shared client-side helpers.
* Always the last partial on every docs page.
*
* Variables:
* $app_name (string) — defaults to 'Nhance PAM'
*/
$app_name = $app_name ?? 'Nhance PAM';
$year = date('Y');
?>
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
<div id="mermaid-modal" class="mermaid-modal" hidden aria-hidden="true">
<button type="button" class="mermaid-modal__backdrop" aria-label="Close enlarged diagram"></button>
<div class="mermaid-modal__panel" role="dialog" aria-modal="true" aria-labelledby="mermaid-modal-title">
<div class="mermaid-modal__header">
<span id="mermaid-modal-title" class="mermaid-modal__title">Diagram</span>
<div class="mermaid-modal__actions">
<div class="mermaid-modal__zoom" aria-label="Zoom controls">
<button type="button" class="mermaid-modal__zoom-btn" data-zoom="out" aria-label="Zoom out"></button>
<span class="mermaid-modal__zoom-label" aria-live="polite">100%</span>
<button type="button" class="mermaid-modal__zoom-btn" data-zoom="in" aria-label="Zoom in">+</button>
<button type="button" class="mermaid-modal__zoom-btn mermaid-modal__zoom-btn--text" data-zoom="reset" aria-label="Reset zoom and pan">Reset</button>
</div>
<button type="button" class="mermaid-modal__close" aria-label="Close enlarged diagram">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M18 6 6 18M6 6l12 12"/>
</svg>
</button>
</div>
</div>
<p class="mermaid-modal__help">
<strong>Tips:</strong>
Drag to pan · Scroll or double-click to zoom in ·
<kbd>Ctrl</kbd>+double-click to zoom out ·
Use +//Reset or <kbd>+</kbd> <kbd></kbd> <kbd>0</kbd> to reset · <kbd>Esc</kbd> to close
</p>
<div class="mermaid-modal__viewport" aria-label="Diagram viewer">
<div class="mermaid-modal__stage"></div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Syntax highlighting
if (window.hljs) hljs.highlightAll();
// Mermaid diagrams + enlarge controls
if (window.mermaid && document.querySelector('.mermaid')) {
mermaid.initialize({
startOnLoad: false,
theme: 'neutral',
securityLevel: 'loose'
});
mermaid.run({ querySelector: '.mermaid' }).then(initMermaidEnlarge).catch(initMermaidEnlarge);
}
function initMermaidEnlarge() {
const modal = document.getElementById('mermaid-modal');
const modalTitle = document.getElementById('mermaid-modal-title');
const modalViewport = modal && modal.querySelector('.mermaid-modal__viewport');
const modalStage = modal && modal.querySelector('.mermaid-modal__stage');
const zoomLabel = modal && modal.querySelector('.mermaid-modal__zoom-label');
if (!modal || !modalViewport || !modalStage) return;
const MIN_SCALE = 0.25;
const MAX_SCALE = 4;
const ZOOM_FACTOR = 1.15;
const expandIcon = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>';
let view = { scale: 1, x: 0, y: 0 };
let drag = null;
function applyView() {
modalStage.style.transform = 'translate(' + view.x + 'px, ' + view.y + 'px) scale(' + view.scale + ')';
if (zoomLabel) zoomLabel.textContent = Math.round(view.scale * 100) + '%';
}
function resetView() {
view = { scale: 1, x: 0, y: 0 };
applyView();
}
function centerDiagram() {
const svg = modalStage.querySelector('svg');
if (!svg) return;
const vpRect = modalViewport.getBoundingClientRect();
const svgRect = svg.getBoundingClientRect();
view.x = Math.max(24, (vpRect.width - svgRect.width) / 2);
view.y = Math.max(24, (vpRect.height - svgRect.height) / 2);
applyView();
}
function zoomAt(factor, originX, originY) {
const vpRect = modalViewport.getBoundingClientRect();
const ox = (originX !== undefined) ? originX : vpRect.width / 2;
const oy = (originY !== undefined) ? originY : vpRect.height / 2;
const newScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, view.scale * factor));
if (newScale === view.scale) return;
view.x = ox - (ox - view.x) * (newScale / view.scale);
view.y = oy - (oy - view.y) * (newScale / view.scale);
view.scale = newScale;
applyView();
}
document.querySelectorAll('.mermaid-wrapper').forEach(function (wrapper) {
if (wrapper.querySelector('.mermaid-enlarge-btn')) return;
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'mermaid-enlarge-btn';
btn.setAttribute('aria-label', 'View enlarged diagram');
btn.innerHTML = expandIcon;
btn.addEventListener('click', function () {
openMermaidModal(wrapper);
});
wrapper.appendChild(btn);
});
function getDiagramTitle(wrapper) {
if (wrapper.id) {
return wrapper.id.replace(/-/g, ' ').replace(/\b\w/g, function (c) { return c.toUpperCase(); });
}
var el = wrapper.previousElementSibling;
while (el) {
if (el.matches && el.matches('h2, h3')) return el.textContent.trim();
el = el.previousElementSibling;
}
return 'Diagram';
}
function openMermaidModal(wrapper) {
const svg = wrapper.querySelector('.mermaid svg');
if (!svg) return;
modalTitle.textContent = getDiagramTitle(wrapper);
modalStage.innerHTML = '';
modalStage.appendChild(svg.cloneNode(true));
resetView();
modal.hidden = false;
modal.setAttribute('aria-hidden', 'false');
document.body.classList.add('mermaid-modal-open');
requestAnimationFrame(centerDiagram);
modal.querySelector('.mermaid-modal__close').focus();
}
function closeMermaidModal() {
modal.hidden = true;
modal.setAttribute('aria-hidden', 'true');
modalStage.innerHTML = '';
drag = null;
modalViewport.classList.remove('is-dragging');
resetView();
document.body.classList.remove('mermaid-modal-open');
}
if (!modal.dataset.mermaidViewerReady) {
modal.dataset.mermaidViewerReady = '1';
modal.querySelector('.mermaid-modal__backdrop').addEventListener('click', closeMermaidModal);
modal.querySelector('.mermaid-modal__close').addEventListener('click', closeMermaidModal);
modal.addEventListener('click', function (e) {
const zoomBtn = e.target.closest('[data-zoom]');
if (!zoomBtn || modal.hidden) return;
const action = zoomBtn.getAttribute('data-zoom');
if (action === 'in') zoomAt(ZOOM_FACTOR);
else if (action === 'out') zoomAt(1 / ZOOM_FACTOR);
else if (action === 'reset') resetView();
});
modalViewport.addEventListener('wheel', function (e) {
if (modal.hidden) return;
e.preventDefault();
const rect = modalViewport.getBoundingClientRect();
const factor = e.deltaY < 0 ? ZOOM_FACTOR : 1 / ZOOM_FACTOR;
zoomAt(factor, e.clientX - rect.left, e.clientY - rect.top);
}, { passive: false });
modalViewport.addEventListener('dblclick', function (e) {
if (modal.hidden) return;
e.preventDefault();
const rect = modalViewport.getBoundingClientRect();
const factor = e.ctrlKey ? (1 / ZOOM_FACTOR) : ZOOM_FACTOR;
zoomAt(factor, e.clientX - rect.left, e.clientY - rect.top);
});
modalViewport.addEventListener('pointerdown', function (e) {
if (modal.hidden || e.button !== 0) return;
drag = {
pointerId: e.pointerId,
startX: e.clientX,
startY: e.clientY,
origX: view.x,
origY: view.y
};
modalViewport.setPointerCapture(e.pointerId);
modalViewport.classList.add('is-dragging');
});
modalViewport.addEventListener('pointermove', function (e) {
if (!drag || drag.pointerId !== e.pointerId) return;
view.x = drag.origX + (e.clientX - drag.startX);
view.y = drag.origY + (e.clientY - drag.startY);
applyView();
});
function endDrag(e) {
if (!drag || drag.pointerId !== e.pointerId) return;
drag = null;
modalViewport.classList.remove('is-dragging');
try { modalViewport.releasePointerCapture(e.pointerId); } catch (err) { /* ignore */ }
}
modalViewport.addEventListener('pointerup', endDrag);
modalViewport.addEventListener('pointercancel', endDrag);
document.addEventListener('keydown', function (e) {
if (modal.hidden) return;
if (e.key === 'Escape') {
closeMermaidModal();
return;
}
if (e.target.closest('input, textarea, select')) return;
if (e.key === '+' || e.key === '=') {
e.preventDefault();
zoomAt(ZOOM_FACTOR);
} else if (e.key === '-') {
e.preventDefault();
zoomAt(1 / ZOOM_FACTOR);
} else if (e.key === '0') {
e.preventDefault();
resetView();
}
});
}
}
// Active TOC link on scroll
const tocLinks = document.querySelectorAll('.docs-toc a');
const headings = document.querySelectorAll('main h2, main h3');
if (tocLinks.length && headings.length) {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
tocLinks.forEach(l => l.classList.remove('is-active-toc'));
const match = document.querySelector(
`.docs-toc a[href="#${entry.target.id}"]`
);
if (match) match.classList.add('is-active-toc');
}
});
},
{ rootMargin: '0px 0px -70% 0px' }
);
headings.forEach(h => { if (h.id) observer.observe(h); });
}
});
</script>
<style>
.docs-toc a.is-active-toc { color: var(--accent); font-weight: 500; }
.mermaid-wrapper {
position: relative;
margin: 20px 0 24px;
padding: 16px;
padding-top: 44px;
border: 1px solid var(--border);
border-radius: 12px;
background: var(--bg2);
overflow-x: auto;
}
.mermaid-enlarge-btn {
position: absolute;
top: 10px;
right: 10px;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg);
color: var(--muted);
cursor: pointer;
transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.mermaid-enlarge-btn:hover {
color: var(--accent);
border-color: var(--accent);
background: var(--accent-bg);
}
.mermaid {
min-width: 520px;
text-align: center;
}
.mermaid svg {
max-width: 100%;
height: auto;
}
body.mermaid-modal-open {
overflow: hidden;
}
.mermaid-modal {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
}
.mermaid-modal[hidden] {
display: none;
}
.mermaid-modal__backdrop {
position: absolute;
inset: 0;
border: 0;
background: rgba(15, 23, 42, 0.55);
cursor: pointer;
}
.mermaid-modal__panel {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
width: min(96vw, 1400px);
height: min(92vh, 900px);
max-height: 92vh;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 12px;
box-shadow: 0 24px 48px rgba(15, 23, 42, 0.2);
overflow: hidden;
}
.mermaid-modal__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 14px 18px;
border-bottom: 1px solid var(--border);
background: var(--bg2);
flex-shrink: 0;
}
.mermaid-modal__title {
font-size: 14px;
font-weight: 600;
color: var(--text);
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mermaid-modal__actions {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
}
.mermaid-modal__zoom {
display: flex;
align-items: center;
gap: 6px;
padding: 2px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg);
}
.mermaid-modal__zoom-btn {
display: flex;
align-items: center;
justify-content: center;
min-width: 32px;
height: 32px;
padding: 0 8px;
border: 0;
border-radius: 6px;
background: transparent;
color: var(--text);
font-size: 18px;
line-height: 1;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.mermaid-modal__zoom-btn--text {
font-size: 12px;
font-weight: 500;
padding: 0 10px;
}
.mermaid-modal__zoom-btn:hover {
background: var(--accent-bg);
color: var(--accent);
}
.mermaid-modal__zoom-label {
min-width: 44px;
font-family: var(--mono);
font-size: 12px;
color: var(--muted);
text-align: center;
user-select: none;
}
.mermaid-modal__close {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg);
color: var(--muted);
cursor: pointer;
transition: color 0.15s, border-color 0.15s;
}
.mermaid-modal__close:hover {
color: var(--accent);
border-color: var(--accent);
}
.mermaid-modal__help {
flex-shrink: 0;
margin: 0;
padding: 8px 18px;
font-size: 12px;
line-height: 1.55;
color: var(--muted);
background: var(--bg2);
border-bottom: 1px solid var(--border);
}
.mermaid-modal__help strong {
color: var(--text);
font-weight: 600;
}
.mermaid-modal__help kbd {
font-family: var(--mono);
font-size: 11px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 4px;
padding: 1px 5px;
color: var(--text);
}
.mermaid-modal__viewport {
flex: 1;
min-height: 0;
overflow: hidden;
background: var(--bg);
cursor: grab;
touch-action: none;
user-select: none;
}
.mermaid-modal__viewport.is-dragging {
cursor: grabbing;
}
.mermaid-modal__stage {
display: inline-block;
transform-origin: 0 0;
will-change: transform;
padding: 24px;
}
.mermaid-modal__stage svg {
display: block;
max-width: none !important;
width: auto;
height: auto;
pointer-events: none;
}
/* ─── GLOBAL FOOTER BAR ──────────────────── */
.docs-global-footer {
border-top : 1px solid var(--border);
padding : 16px 24px;
font-size : 12px;
color : var(--muted);
display : flex;
justify-content: space-between;
align-items: center;
margin-top : auto;
}
.docs-global-footer a {
color : var(--muted);
text-decoration: none;
}
.docs-global-footer a:hover { color: var(--accent); }
</style>
<footer class="docs-global-footer">
<span>© <?= $year ?> <?= esc($app_name) ?>. Internal developer docs.</span>
<span>
Built with CodeIgniter 4 &nbsp;·&nbsp;
<a href="<?= base_url('docs/changelog') ?>">Changelog</a> &nbsp;·&nbsp;
<a href="<?= base_url('docs/contributing') ?>">Contributing</a>
</span>
</footer>
</body>
</html>