$config['domain'], 'devMode' => $config['is_dev'], 'cookieHost' => $config['cookie_host'], ]); $body_class = []; if ($opts['full_width']) $body_class[] = 'full-width'; else if ($opts['wide']) $body_class[] = 'wide'; return << {$title} {$ctx->renderMeta($meta)} {$ctx->renderStatic($static, $theme)} if_true($body_class, ' class="'.implode(' ', $body_class).'"')}> {$ctx->renderHeader($theme)}
{$unsafe_body}
{$ctx->renderScript($js, $unsafe_lang)} HTML; } function renderScript($ctx, $unsafe_js, $unsafe_lang) { global $config; $styles = json_encode($ctx->styleNames); if ($config['is_dev']) $versions = '{}'; else { $versions = []; foreach ($config['static'] as $name => $v) { list($type, $bname) = getStaticNameParts($name); $versions[$type][$bname] = $v; } $versions = json_encode($versions); } return << StaticManager.init({$styles}, {$versions}); {$ctx->if_true($unsafe_js, '(function(){'.$unsafe_js.'})();')} {$ctx->if_true($unsafe_lang, 'extend(__lang, '.$unsafe_lang.');')} ThemeSwitcher.init(); HTML; } function renderMeta($ctx, $meta) { if (empty($meta)) return ''; return implode('', array_map(function(array $item): string { $s = ' $v) $s .= ' '.htmlescape($k).'="'.htmlescape($v).'"'; $s .= '>'; return $s; }, $meta)); } function renderStatic($ctx, $static, $theme) { global $config; $html = []; $dark = $theme == 'dark'; $ctx->styleNames = []; foreach ($static as $name) { // javascript if (str_starts_with($name, 'js/')) $html[] = jsLink($name); // css else if (str_starts_with($name, 'css/')) { $html[] = cssLink($name, 'light', $style_name); $ctx->styleNames[] = $style_name; if ($dark) $html[] = cssLink($name, 'dark', $style_name); else if (!$config['is_dev']) $html[] = cssPrefetchLink($style_name.'_dark'); } else logError(__FUNCTION__.': unexpected static entry: '.$name); } return implode("\n", $html); } function jsLink(string $name): string { global $config; list (, $bname) = getStaticNameParts($name); if ($config['is_dev']) { $href = '/js.php?name='.urlencode($bname).'&v='.time(); } else { $href = '/dist-js/'.$bname.'.js?'.getStaticVersion($name); } return ''; } function cssLink(string $name, string $theme, &$bname = null): string { global $config; list(, $bname) = getStaticNameParts($name); if ($config['is_dev']) { $href = '/sass.php?name='.urlencode($bname).'&theme='.$theme.'&v='.time(); } else { $version = getStaticVersion('css/'.$bname.($theme == 'dark' ? '_dark' : '').'.css'); $href = '/dist-css/'.$bname.($theme == 'dark' ? '_dark' : '').'.css?'.$version; } $id = 'style_'.$bname; if ($theme == 'dark') $id .= '_dark'; return ''; } function cssPrefetchLink(string $name): string { $url = '/dist-css/'.$name.'.css?'.getStaticVersion('css/'.$name.'.css'); return << HTML; } function getStaticNameParts(string $name): array { $dname = dirname($name); $bname = basename($name); if (($pos = strrpos($bname, '.'))) { $ext = substr($bname, $pos+1); $bname = substr($bname, 0, $pos); } else { $ext = ''; } return [$dname, $bname, $ext]; } function getStaticVersion(string $name): string { global $config; if ($config['is_dev']) return time(); if (str_starts_with($name, '/')) { logWarning(__FUNCTION__.': '.$name.' starts with /'); $name = substr($name, 1); } return $config['static'][$name] ?? 'notfound'; } function renderHeader(SkinContext $ctx, string $theme): string { $items = [ //['url' => '/articles/', 'label' => 'articles'], ['url' => 'https://files.4in1.ws', 'label' => 'materials'], ['url' => '/about/', 'label' => 'about'] ]; if (\admin::isAdmin()) $items[] = ['url' => '/admin/', 'label' => 'admin']; $items[] = ['url' => 'javascript:void(0)', 'label' => $theme, 'label_id' => 'theme-switcher-label', 'theme_switcher' => true]; // here, items are rendered using for_each, so that there are no gaps (whitespaces) between tags return <<
{$ctx->for_each($items, fn($item) => $ctx->renderHeaderItem($item['url'], $item['label'], $item['label_id'] ?? null, $item['theme_switcher'] ?? false))}
HTML; } function renderHeaderItem(SkinContext $ctx, string $url, string $label, ?Stringable $label_id, bool $is_theme_switcher): string { return <<if_true($is_theme_switcher, ' onclick="return ThemeSwitcher.next(event)"')}> {$ctx->if_true($is_theme_switcher, ''.$ctx->renderMoonIcon().'')} if_true($label_id, ' id="'.$label_id.'"')}>{$label} HTML; } function renderMoonIcon(SkinContext $ctx): string { return << SVG; }