/* App shell for the mockups, mirroring internal/view/layout/layout.templ. * *
repo pages (code pull_requests issues milestones checks * discussions projects wiki releases settings) *
signed-in account pages (overview repositories gists * pulls issues attention activity settings) *
no subnav *
signed-out header * data-owner="acme-corp" data-repo="x" workspace and repo shown in the switchers; the signed-in * user stays mkappworks * data-admin-mode (or ?admin_mode=1 in the URL) the "Acting as instance admin" banner under the header * optional; renders the layout footer * * All markup is a fixed build-time template; nothing here takes runtime or user input. */ (function () { // An instance serves no docs of its own, so layout.templ links to the upstream repo; so do the mocks. var REPO_URL = 'https://github.com/mkappworks-dev/cloudzilla-app/'; var DOCS_URL = REPO_URL + 'tree/main/docs'; var API_URL = REPO_URL + 'blob/main/docs/api-reference.md'; var CHANGELOG_URL = REPO_URL + 'blob/main/CHANGELOG.md'; var ICON = { code: '', pull_requests: '', issues: '', milestones: '', checks: '', discussions: '', projects: '', wiki: '', releases: '', settings: '', user: '', star: '', lock: '', shield: '', book: '', clock: '', logout: '', plus: '', org: '', }; function svg(name, size, extra) { return ''; } var SEP = ''; var CHEVRON = ''; var ITEM = 'flex items-center gap-2 px-2 py-1.5 rounded-xs hover:bg-accent hover:text-accent-foreground ' + 'focus:bg-accent focus:text-accent-foreground focus:outline-hidden whitespace-nowrap'; var PANEL = 'hidden absolute top-full mt-1.5 z-50 min-w-[12rem] rounded-md border border-border bg-popover ' + 'text-popover-foreground shadow-md p-1 text-sm'; function chip(letter) { return ''; } function label(text) { return '
' + text + '
'; } var DIVIDER = ''; function link(href, inner, cls) { var ext = /^https?:/.test(href) ? ' target="_blank" rel="noopener"' : ''; return '' + inner + ''; } function menu(trigger, triggerCls, aria, align, body) { return '
' + '' + '
'; } var mount = document.getElementById('cz-header'); var ds = mount ? mount.dataset : {}; var anon = mount && mount.hasAttribute('data-anon'); var adminMode = !anon && ((mount && mount.hasAttribute('data-admin-mode')) || /[?&]admin_mode=1(&|$)/.test(location.search)); var repoTab = ds.repoTab || ''; var accountTab = ds.accountTab || ''; var me = 'mkappworks'; var owner = ds.owner || me; var repo = ds.repo || 'cloudzilla'; var ORGS = { 'acme-corp': 1, 'stitch-labs': 1 }; var ownerPage = ORGS[owner] ? 'org_profile.html' : 'profile.html'; var workspaceBlock = ''; if (!anon) { workspaceBlock = SEP + menu( chip(owner.charAt(0).toUpperCase()) + '' + owner + '' + CHEVRON, 'flex items-center gap-2 -ml-1 px-2 h-7 rounded-md hover:bg-accent text-sm font-medium', 'Switch account or organization, current: ' + owner, 'start', label('Account / orgs') + link('profile.html', chip('M') + '' + me + '' + 'Personal') + link('org_profile.html', chip('A') + 'acme-corp' + 'Member') + link('org_profile.html', chip('S') + 'stitch-labs' + 'Owner') + DIVIDER + link('new_organization.html', svg('plus', 13, ' class="text-muted-foreground"').replace('stroke-width="1.75"', 'stroke-width="2"') + 'New organization') + link('organizations.html', svg('org', 13, ' class="text-muted-foreground"') + 'Your organizations')); } var repoBlock = ''; if (!anon && repoTab) { repoBlock = SEP + menu( '' + repo + '' + '', 'flex items-center gap-2 -ml-1 px-2 h-7 rounded-md hover:bg-accent text-sm', 'Switch repository, current: ' + owner + '/' + repo, 'start', label(owner + ' repositories') + link('repo.html', 'cloudzilla') + link('repo.html', 'benchlabs-site') + link('repo.html', 'go-service-starter') + DIVIDER + link(ownerPage + '?tab=repositories', 'All repositories')) + 'Public'; } var search = ''; var bell = '' + '' + '99+'; var themeToggle = ''; var userMenu = '
' + menu( '', 'grid rounded-full [&:hover>span]:border-foreground/30', 'User menu for ' + me, 'end', '

Signed in as

' + '

' + me + '

' + DIVIDER + link('profile.html', svg('user', 13, ' class="text-muted-foreground"') + 'Your profile') + link('profile.html?tab=stars', svg('star', 13, ' class="text-muted-foreground"') + 'Your stars') + DIVIDER + link('account_settings.html', svg('settings', 13, ' class="text-muted-foreground"') + 'Settings') + (adminMode ? '' : link('admin_mode.html', svg('lock', 13, ' class="text-muted-foreground"') + 'Enter admin mode')) + link('admin_settings.html', svg('shield', 13, ' class="text-muted-foreground"') + 'Admin') + link(DOCS_URL, svg('book', 13, ' class="text-muted-foreground"') + 'Docs') + link(CHANGELOG_URL, svg('clock', 13, ' class="text-muted-foreground"') + 'Changelog') + DIVIDER + link('login.html', svg('logout', 13, ' class="text-destructive"') + 'Sign out')) + '
'; var anonLinks = 'Sign in' + 'Register'; function tab(key, text, href, active, extra) { var base = 'px-3 py-1.5 rounded-md inline-flex items-center gap-1.5 ' + (extra || ''); return active ? '' + text + '' : '' + text + ''; } var SUBNAV_CLS = 'max-w-[1400px] mx-auto px-6 py-1.5 flex items-center gap-1 text-sm overflow-x-auto ' + '[scrollbar-width:none] [&::-webkit-scrollbar]:hidden'; function count(n, text) { return n ? ' ' + n + '' : ''; } var REPO_TABS = [ ['code', 'Code', 'repo.html', 0], ['pull_requests', 'Pull requests', 'pulls.html', 12], ['issues', 'Issues', 'issues.html', 23], ['milestones', 'Milestones', 'milestones.html', 0], ['checks', 'Checks', 'actions.html', 0], ['discussions', 'Discussions', 'discussions.html', 5], ['projects', 'Projects', 'project.html', 0], ['wiki', 'Wiki', 'wiki.html', 0], ['releases', 'Releases', 'releases.html', 3], ['settings', 'Settings', 'settings.html', 0, 'ml-auto'], ]; var ACCOUNT_TABS = [ ['overview', 'Overview', 'home.html'], ['repositories', 'Repositories', 'my_repositories.html', 14], ['gists', 'Gists', 'gists.html', 3], ['pulls', 'Pull requests', 'my_pulls.html', 4], ['issues', 'Issues', 'my_issues.html', 7], ['attention', 'Attention', 'attention.html', 5], ['activity', 'Activity', 'activity.html'], ['settings', 'Settings', 'account_settings.html'], ]; var subnav = ''; if (repoTab) { subnav = ''; } else if (accountTab) { subnav = ''; } var header = ''; var footer = ''; function parse(html) { return new DOMParser().parseFromString(html, 'text/html').body.firstChild; } // layout.templ renders the banner between and
; mirrored here as the header's next sibling. var adminBanner = '
' + '
' + 'Acting as instance admin' + '· expires ·' + '
'; if (mount) { var headerNode = parse(header); mount.replaceWith(headerNode); if (adminMode) headerNode.insertAdjacentHTML('afterend', adminBanner); } function mountFooter() { var m = document.getElementById('cz-footer'); if (m) m.replaceWith(parse(footer)); } // Most pages load this script above their footer mount, which doesn't exist yet. if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', mountFooter); else mountFooter(); // Search suggestions, mirroring fragments/search_suggestions.templ: repos, users and orgs whose name contains the // typed text (two characters minimum), then a "Search for" row. The real fragment has no issue or code rows. var SUGGEST = { repo: [ ['mkappworks/cloudzilla', 'Self-hosted git forge', 'repo.html', false], ['mkappworks/benchlabs-site', 'Marketing site for Bench Labs', 'repo.html', false], ['mkappworks/go-service-starter', 'Opinionated Go service template', 'repo.html', true], ['acme-corp/oauth-proxy', 'Reverse proxy that fronts apps with OAuth 2.0', 'repo.html', false], ['stitch-labs/htmx-kit', 'Tiny HTMX component toolkit', 'repo.html', false], ['openforge/runner', 'Sandboxed CI runner', 'repo.html', false], ], user: [['malith', '', 'profile.html'], ['daisy', '', 'profile.html'], ['kiyo', '', 'profile.html'], ['rhea', '', 'profile.html'], ['oauth-bot', '', 'profile.html']], org: [['acme-corp', 'Acme Corp', 'org_profile.html'], ['stitch-labs', 'Stitch Labs', 'org_profile.html'], ['openforge', 'Open Forge', 'org_profile.html']], }; var SUGGEST_LABEL = { repo: 'Repositories', user: 'Users', org: 'Organizations' }; var BOOK = ''; var SUGGEST_ROW = 'flex items-center gap-2 px-3 py-1.5 text-[13px] hover:bg-accent aria-selected:bg-accent'; function escHtml(t) { var d = document.createElement('div'); d.textContent = t; return d.innerHTML; } // components.initials: first letters of the first and last words, so one letter for a username. function initialsOf(n) { var w = n.trim().split(/\s+/); return w[0] ? (w.length > 1 ? w[0][0] + w[w.length - 1][0] : w[0][0]).toUpperCase() : '?'; } // Pages with their own search box (search.html) attach the same behaviour: czAttachSuggest(input, panel). function attachSuggest(input, panel) { var active = -1; function isOpen() { return !panel.classList.contains('hidden'); } function setOpen(open) { panel.classList.toggle('hidden', !open); input.setAttribute('aria-expanded', open ? 'true' : 'false'); if (!open) { active = -1; input.removeAttribute('aria-activedescendant'); } } function options() { return panel.querySelectorAll('[data-suggest-item]'); } function highlight(i) { active = i; options().forEach(function (a, n) { a.setAttribute('aria-selected', n === i ? 'true' : 'false'); }); if (i >= 0) input.setAttribute('aria-activedescendant', panel.id + '-' + i); else input.removeAttribute('aria-activedescendant'); } function render() { var q = input.value.trim(); if (q.length < 2) { panel.innerHTML = ''; setOpen(false); return; } var needle = q.toLowerCase(); var rows = []; ['repo', 'user', 'org'].forEach(function (kind) { SUGGEST[kind].forEach(function (r) { if (r[0].toLowerCase().indexOf(needle) < 0) return; rows.push({ kind: kind, title: r[0], detail: r[1], href: r[2], priv: kind === 'repo' && r[3] }); }); }); var html = ''; panel.innerHTML = html; active = -1; setOpen(true); document.dispatchEvent(new CustomEvent('cz-suggest-opened', { detail: panel })); } function move(delta) { var n = options().length; if (!n) return; setOpen(true); highlight(active < 0 ? (delta > 0 ? 0 : n - 1) : (active + delta + n) % n); options()[active].scrollIntoView({ block: 'nearest' }); } input.addEventListener('input', render); input.addEventListener('focus', function () { if (options().length) setOpen(true); }); input.addEventListener('keydown', function (e) { if (e.key === 'ArrowDown') { e.preventDefault(); move(1); } else if (e.key === 'ArrowUp') { e.preventDefault(); move(-1); } else if (e.key === 'Enter') { // Nothing highlighted: Enter submits the form. if (isOpen() && active >= 0) { e.preventDefault(); options()[active].click(); } } else if (e.key === 'Escape' && isOpen()) { e.preventDefault(); setOpen(false); } }); panel.addEventListener('mouseover', function (e) { var a = e.target.closest && e.target.closest('[data-suggest-item]'); if (a) highlight(Array.prototype.indexOf.call(options(), a)); }); document.addEventListener('click', function (e) { if (isOpen() && !input.contains(e.target) && !panel.contains(e.target)) setOpen(false); }); // The topnav and the results page each have a box; only one list shows at a time. document.addEventListener('cz-suggest-opened', function (e) { if (e.detail !== panel) setOpen(false); }); return { close: function () { panel.innerHTML = ''; setOpen(false); } }; } window.czAttachSuggest = attachSuggest; var searchForm = document.querySelector('form[data-cz-search]'); if (searchForm) { var searchInput = searchForm.querySelector('input[name=q]'); var clearBtn = searchForm.querySelector('[data-cz-clear]'); var kbd = searchForm.querySelector('[data-cz-kbd]'); var topSuggest = attachSuggest(searchInput, document.getElementById('topnav-suggest')); var syncClear = function () { var has = searchInput.value !== ''; clearBtn.classList.toggle('hidden', !has); kbd.classList.toggle('hidden', has); }; searchInput.addEventListener('input', syncClear); clearBtn.addEventListener('click', function () { searchInput.value = ''; topSuggest.close(); syncClear(); searchInput.focus(); }); // The generic submit handler below would only toast; this form really navigates. searchForm.addEventListener('submit', function (e) { e.preventDefault(); var q = searchInput.value.trim(); location.href = 'search.html' + (q ? '?q=' + encodeURIComponent(q) : ''); }); document.addEventListener('keydown', function (e) { if (e.key !== '/' || e.metaKey || e.ctrlKey || e.altKey) return; var t = e.target; if (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName)) return; if (searchInput.offsetParent === null) return; e.preventDefault(); searchInput.focus(); searchInput.select(); }); } // Theme: same storage key and class polarity as the layout's bootstrap (dark by default, `light` opts out). var root = document.documentElement; // highlight.DefaultLight and DefaultDark: code-themes.css keys its .hl colours off these attributes. if (!root.dataset.codeLight) root.dataset.codeLight = 'github'; if (!root.dataset.codeDark) root.dataset.codeDark = 'github-dark'; var toggle = document.getElementById('theme-toggle'); try { var saved = localStorage.getItem('cz-theme'); if (saved === 'light' || (!saved && window.matchMedia('(prefers-color-scheme: light)').matches)) { root.classList.remove('dark'); root.classList.add('light'); } } catch (e) { /* storage blocked */ } function syncToggle() { if (!toggle) return; var light = root.classList.contains('light'); toggle.setAttribute('aria-pressed', light ? 'true' : 'false'); toggle.setAttribute('aria-label', light ? 'Switch to dark theme' : 'Switch to light theme'); toggle.querySelector('.dark-only').classList.toggle('hidden', light); toggle.querySelector('.light-only').classList.toggle('hidden', !light); } document.addEventListener('keydown', function (e) { if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === 'l' && toggle) { e.preventDefault(); toggle.click(); } }); if (toggle) { toggle.addEventListener('click', function () { var light = !root.classList.contains('light'); root.classList.toggle('light', light); root.classList.toggle('dark', !light); try { localStorage.setItem('cz-theme', light ? 'light' : 'dark'); } catch (e) { /* storage blocked */ } syncToggle(); }); syncToggle(); } // Dropdowns: `