XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
@using Microsoft.Extensions.Options
@using System.Globalization
@using XFEStudioWebSite.Localization
@using XFEStudioWebSite.Options
@using XFEStudioWebSite.Services
@inject IOptions<SiteSettings> SiteSettings
@inject IOptions<GitSettings> GitSettings
@inject IOptions<NuGetOptions> NuGetSettings
@inject IUserService UserService
@inject IPublicUrlService PublicUrls
@{
    static string ResolveConfiguredHost(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
            return string.Empty;

        var candidate = value.Contains("://", StringComparison.Ordinal)
            ? value
            : $"https://{value}";
        return Uri.TryCreate(candidate, UriKind.Absolute, out var uri)
            ? uri.Host
            : value.Split(':', 2)[0];
    }

    var requestHost = Context.Request.Host.Host;
    var currentController = ViewContext.RouteData.Values["controller"]?.ToString() ?? string.Empty;
    var mainHost = ResolveConfiguredHost(SiteSettings.Value.MainDomain);
    var docsHost = ResolveConfiguredHost(SiteSettings.Value.DocsDomain);
    var gitHost = ResolveConfiguredHost(GitSettings.Value.PublicUrl);
    var nuGetHost = ResolveConfiguredHost(NuGetSettings.Value.PublicUrl);
    var mainPublicUrl = PublicUrls.Main();
    var docsPublicUrl = PublicUrls.Docs();
    var gitPublicUrl = PublicUrls.Git();
    var nuGetPublicUrl = PublicUrls.NuGet();
    var isMainSiteHost = string.IsNullOrWhiteSpace(mainHost)
        || requestHost.Equals(mainHost, StringComparison.OrdinalIgnoreCase);
    var isGitAppHost = !string.IsNullOrWhiteSpace(gitHost)
        && requestHost.Equals(gitHost, StringComparison.OrdinalIgnoreCase);
    var currentPublicUrl = $"{Context.Request.Scheme}://{Context.Request.Host}{Context.Request.PathBase}{Context.Request.Path}{Context.Request.QueryString}";
    var loginUrl = mainPublicUrl + "/Account/Login" + QueryString.Create("returnUrl", currentPublicUrl);
    var currentCulture = CultureInfo.CurrentUICulture.Name;
    var textDirection = SiteLanguageCatalog.IsRightToLeft(currentCulture) ? "rtl" : "ltr";
    var edgeCountry = SiteLanguageCatalog.NormalizeCountryCode(
        Context.Request.Headers[SiteLanguageCatalog.EdgeOneCountryHeader].ToString());

    var isNuGetSection = currentController.StartsWith("NuGet", StringComparison.OrdinalIgnoreCase)
        || requestHost.Equals(nuGetHost, StringComparison.OrdinalIgnoreCase);
    var isGitSection = !isNuGetSection && (currentController.StartsWith("Git", StringComparison.OrdinalIgnoreCase)
        || requestHost.Equals(gitHost, StringComparison.OrdinalIgnoreCase));
    var hasDedicatedDocsHost = !string.IsNullOrEmpty(docsHost)
        && !docsHost.Equals(mainHost, StringComparison.OrdinalIgnoreCase);
    var isDocsSection = !isNuGetSection && !isGitSection
        && (currentController.Equals("Docs", StringComparison.OrdinalIgnoreCase)
            || (hasDedicatedDocsHost && requestHost.Equals(docsHost, StringComparison.OrdinalIgnoreCase)));
    var isDownloadsSection = !isNuGetSection && !isGitSection && !isDocsSection
        && currentController.Equals("Downloads", StringComparison.OrdinalIgnoreCase);
    var isDiscussionSection = !isNuGetSection && !isGitSection && !isDocsSection
        && currentController.Equals("Discussion", StringComparison.OrdinalIgnoreCase);
    var isHomeSection = !isNuGetSection && !isGitSection && !isDocsSection
        && !isDownloadsSection && !isDiscussionSection
        && currentController.Equals("Home", StringComparison.OrdinalIgnoreCase);
}
<!DOCTYPE html>
<html lang="@currentCulture" dir="@textDirection">
<head>
    <meta charset="utf-8" />
    <title>寰宇朽力 - @ViewData["Title"]</title>
    <link rel="icon" type="image/png" href="/favicon.png" />
    @if (isGitAppHost)
    {
        <link rel="manifest" href="/manifest.webmanifest" />
        <link rel="apple-touch-icon" href="/img/pwa/icon-192.png" />
        <meta name="application-name" content="XFE Studio Git" />
        <meta name="theme-color" content="#9786d3" />
        <script src="/js/pwa-install.js" defer></script>
    }
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script>
        (() => {
            const cookie = document.cookie
                .split(';')
                .map(item => item.trim())
                .find(item => item.startsWith('xfe-theme='));
            let theme = null;
            try {
                theme = cookie ? decodeURIComponent(cookie.slice('xfe-theme='.length)) : null;
            } catch { }
            if (theme !== 'light' && theme !== 'dark') {
                try { theme = localStorage.getItem('theme'); } catch { }
            }
            if (theme !== 'light' && theme !== 'dark') theme = 'dark';
            document.documentElement.classList.toggle('dark', theme === 'dark');
            document.documentElement.setAttribute('data-bs-theme', theme);
        })();
    </script>
    <link rel="stylesheet" href="~/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/theme-variables.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/css/site-language.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/css/notifications.css" asp-append-version="true" />
    <link rel="stylesheet" href="https://lib.baomitu.com/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css" />
    <style>
        body.no-transition {
            transition: none; /* 页面加载时禁用过渡 */
        }

        body {
            background-color: var(--bg-color);
            color: var(--text-color);
            font-family: sans-serif;
            transition: background-color 0.3s, color 0.3s; /* 只在切换时生效 */
            scrollbar-color: color-mix(in srgb, var(--main-color) 50%, transparent) transparent;
        }

            body.no-transition {
                transition: none; /* 页面加载时禁用过渡 */
            }

        a {
            color: var(--link-color);
        }

            a:hover {
                color: var(--main-deep-color);
                text-decoration: underline;
            }

        .navbar .navbar-nav .nav-link {
            border-radius: .55rem;
            transition: background-color .18s ease, color .18s ease;
        }

            .navbar .navbar-nav .nav-link.active {
                background: rgba(255, 255, 255, .08);
                color: #fff;
            }

            .navbar .navbar-nav .nav-link.active:hover,
            .navbar .navbar-nav .nav-link.active:focus-visible {
                background: rgba(255, 255, 255, .13);
                color: #fff;
                text-decoration: none;
            }

        .site-footer {
            background: var(--footer-bg);
            border-top: 1px solid var(--footer-border);
            color: var(--text-color);
            width: 100%;
        }

            .site-footer a {
                color: var(--link-color);
                text-decoration: none;
                font-weight: 500;
            }

                .site-footer a:hover {
                    text-decoration: underline;
                }

        /* Breadcrumb Adaptation */
        .breadcrumb-item.active {
            color: var(--text-color) !important;
        }

        .breadcrumb-item + .breadcrumb-item::before {
            color: var(--text-secondary-color) !important;
        }

        .radio-control {
            color: var(--on-main-color);
            background: var(--main-color);
        }

        .user-avatar {
            width: 48px;
            height: 48px;
            background-color: var(--main-color);
            color: var(--on-main-color);
            border-radius: 999px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: bold;
            font-size: 1.5rem;
            flex-shrink: 0;
            border: 2px solid var(--bg-color);
            box-shadow: 0 0 0 1px var(--main-color);
        }

        /* Dropdown Theme Adaptation */
        .dropdown-menu {
            background-color: var(--bg-color);
            color: var(--text-color);
            border-color: var(--footer-border);
        }

        .dropdown-item {
            color: var(--text-color);
        }

            .dropdown-item:hover, .dropdown-item:focus {
                background-color: color-mix(in srgb, var(--text-color) 5%, transparent);
                color: var(--text-color);
            }

        .dropdown-header {
            color: var(--text-secondary-color);
        }

        .dropdown-divider {
            border-top-color: var(--footer-border);
        }

        .text-muted {
            color: var(--text-secondary-color) !important;
        }

        .table {
            color: var(--text-color);
            --bs-table-color: var(--text-color);
            --bs-table-bg: transparent;
            --bs-table-border-color: var(--profile-border-color);
            --bs-table-hover-color: var(--text-color);
            --bs-table-hover-bg: color-mix(in srgb, var(--text-color) 5%, transparent);
        }

            .table th {
                border-color: var(--profile-border-color);
                color: var(--text-secondary-color);
                font-weight: 600;
                text-transform: uppercase;
                font-size: 0.85rem;
            }

            .table td {
                border-color: var(--profile-border-color);
                vertical-align: middle;
            }

        .form-text {
            color: var(--text-secondary-color);
        }

        .list-group-item {
            background-color: var(--bg-color);
            border-color: var(--footer-border);
            color: var(--text-color);
            transition: background-color 0.2s;
        }

        .list-group-item-action:hover, .list-group-item-action:focus {
            background-color: color-mix(in srgb, var(--text-color) 4%, var(--bg-color));
            color: var(--text-color);
        }

        /* Utility Class Overrides */
        .bg-white {
            background-color: var(--bg-color) !important;
            color: var(--text-color) !important;
        }

        .text-dark {
            color: var(--text-color) !important;
        }

        .text-muted {
            color: var(--text-secondary-color) !important;
        }

        .bg-light {
            background-color: color-mix(in srgb, var(--text-color) 5%, transparent) !important;
            color: var(--text-color) !important;
        }

        /* Badge Theme Overrides */
        .badge {
            font-weight: 500;
        }

            .badge.bg-light {
                background-color: color-mix(in srgb, var(--text-color) 10%, transparent) !important;
                color: var(--text-color) !important;
            }

        /* Card & Layout Adaptation */
        .card {
            background-color: var(--bg-color);
            border-color: var(--footer-border);
            color: var(--text-color);
        }

        .card-body {
            background: var(--bg-color);
            /*Add radios corner*/
            border-radius: 0.375rem;
            /*Add main color shadow*/
            /* box-shadow: 0px 0px 2px var(--main-color); */
        }

        .card-header {
            background-color: var(--bg-color) !important;
            border-bottom-color: var(--footer-border);
            color: var(--text-color);
        }

        ::-webkit-scrollbar {
            width: 5px;
        }

        ::-webkit-scrollbar-thumb {
            background: color-mix(in srgb, var(--main-color) 50%, transparent);
            border-radius: 999px;
        }

        ::-webkit-scrollbar-track {
            background: transparent;
        }

        /* Tabs */
        .nav-tabs .nav-link.active {
            background-color: var(--bg-color);
            border-color: var(--footer-border) var(--footer-border) var(--bg-color);
            color: var(--main-color);
        }

        .nav-tabs .nav-link {
            color: var(--text-secondary-color);
        }

        .nav-tabs {
            border-bottom-color: var(--footer-border);
        }

        .tab-content {
            border-color: var(--footer-border) !important;
        }

        .shadow-sm {
            box-shadow: 0 .125rem .25rem var(--focus-main-color) !important;
        }

        .btn-close {
            filter: var(--text-color, 0);
        }

        .btn-close-white {
            filter: var(--text-color, 0);
        }

        /* Button Theme Overrides */
        .btn-primary {
            background-color: var(--main-color);
            border-color: var(--main-color);
            color: var(--on-main-color);
        }

            .btn-primary:hover, .btn-primary:focus, .btn-primary:active {
                background-color: var(--main-deep-color);
                border-color: var(--main-deep-color);
                color: var(--on-main-color);
            }

        .btn-outline-primary {
            color: var(--main-color);
            border-color: var(--main-color);
        }

            .btn-outline-primary:hover, .btn-outline-primary:focus, .btn-outline-primary:active {
                background-color: var(--main-color);
                border-color: var(--main-color);
                color: var(--on-main-color);
            }

        .btn-secondary {
            border-color: var(--main-color);
            background-color: var(--bg-color);
            color: var(--main-color);
        }

            .btn-secondary:hover {
                border-color: var(--main-deep-color);
                background-color: var(--bg-color);
                color: var(--main-deep-color);
            }

        .btn-outline-secondary {
            color: var(--text-secondary-color);
            border-color: var(--footer-border);
        }

            .btn-outline-secondary:hover {
                background-color: var(--text-color);
                color: var(--bg-color);
            }

        .btn-link {
            color: var(--link-color);
        }

            .btn-link:hover {
                color: var(--main-deep-color);
            }

        /* Form Theme Overrides */
        .form-control, .form-select {
            background-color: var(--bg-color);
            border-color: var(--footer-border);
            color: var(--text-color);
        }

            .form-control::placeholder {
                color: var(--text-secondary-color);
                opacity: 0.8;
            }

        .form-label {
            color: var(--text-secondary-color);
            font-weight: 500;
        }

        .form-control:focus, .form-select:focus {
            border-color: var(--main-color);
            box-shadow: 0 0 0 0.25rem var(--focus-main-color);
            background-color: var(--bg-color);
            color: var(--text-color);
        }

        .form-control:disabled, .form-control[readonly] {
            background-color: color-mix(in srgb, var(--bg-color) 95%, #ffffff 5%);
            border-color: var(--footer-border);
            color: var(--text-secondary-color);
            opacity: 1;
        }

        .input-group-text {
            background-color: color-mix(in srgb, var(--bg-color) 96%, #000000 4%);
            border-color: var(--footer-border);
            color: var(--text-color);
        }

        .form-check-input:checked {
            background-color: var(--main-color);
            border-color: var(--main-color);
        }

        .form-check-input:focus {
            border-color: var(--main-color);
            box-shadow: 0 0 0 0.25rem var(--focus-main-color);
        }

        /* Navbar Hover Dropdown */
        @@media (min-width: 992px) {
            .navbar .dropdown:hover .dropdown-menu {
                display: block;
                margin-top: 0;
            }

            .navbar .dropdown:hover > .user-menu-dropdown {
                right: 0;
                left: auto;
            }
        }
    </style>
    <link rel="stylesheet" href="~/css/dialogs.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/css/dropdowns.css" asp-append-version="true" />
    @RenderSection("Styles", required: false)
</head>

<body class="d-flex flex-column min-vh-100 @(ViewData["BodyClass"] as string)"
      data-site-culture="@currentCulture" data-edgeone-country="@edgeCountry"
      data-main-origin="@mainPublicUrl">
    <partial name="_Notifications" />
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
        <div class="container-fluid">
            <a class="navbar-brand" href="@mainPublicUrl" data-no-localize>XFE-寰宇朽力</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarCollapse">
                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                    <li class="nav-item">
                        <a class="nav-link @(isHomeSection ? "active" : null)" href="@mainPublicUrl"
                           aria-current="@(isHomeSection ? "page" : null)">首页</a>
                    </li>
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle @(isDocsSection ? "active" : null)" href="#" id="docsHeaderDropdown" role="button"
                           data-bs-toggle="dropdown" aria-expanded="false" aria-current="@(isDocsSection ? "page" : null)">
                            文档
                        </a>
                        <ul class="dropdown-menu" aria-labelledby="docsHeaderDropdown">
                            <li><a class="dropdown-item" href="@docsPublicUrl">文档中心</a></li>
                            <li><a class="dropdown-item" href="@docsPublicUrl/API">API 文档</a></li>
                            <li><a class="dropdown-item" href="@docsPublicUrl/Errors">错误文档</a></li>
                        </ul>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link @(isDownloadsSection ? "active" : null)" href="@mainPublicUrl/Downloads"
                           aria-current="@(isDownloadsSection ? "page" : null)">下载</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link @(isDiscussionSection ? "active" : null)" href="@mainPublicUrl/Discussion"
                           aria-current="@(isDiscussionSection ? "page" : null)">讨论</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link site-git-app-link @(isGitSection ? "active" : null)" href="@gitPublicUrl"
                           target="_blank" rel="noopener noreferrer"
                           title="在新标签页打开 XFE Git"
                           aria-label="Git(在新标签页打开)"
                           aria-current="@(isGitSection ? "page" : null)">
                            <i class="bi bi-git me-1" aria-hidden="true"></i>Git
                            <i class="bi bi-box-arrow-up-right ms-1 small" aria-hidden="true"></i>
                        </a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link @(isNuGetSection ? "active" : null)" href="@nuGetPublicUrl"
                           aria-current="@(isNuGetSection ? "page" : null)">
                            <i class="bi bi-box-seam me-1"></i>NuGet
                        </a>
                    </li>
                </ul>

                <div class="d-flex align-items-center gap-2">
                    <partial name="_LanguagePicker" />
                    @if (User.Identity?.IsAuthenticated == true)
                    {
                        var userAvatar = await UserService.GetAvatarUrlByUserNameAsync(User.Identity.Name!);
                        var userDisplayName = await UserService.GetDisplayNameByUserNameAsync(User.Identity.Name!);
                        @await Component.InvokeAsync("HeaderNotifications")
                        <div class="dropdown">
                            <button class="btn btn-outline-light btn-sm dropdown-toggle d-flex align-items-center gap-1" type="button" id="userMenuDropdown" data-bs-toggle="dropdown" aria-expanded="false">
                                <div class="user-avatar me-2" data-current-user-avatar style="width: 24px; height: 24px; font-size: 0.8rem; @(!string.IsNullOrEmpty(userAvatar) ? $"background-image: url('{userAvatar}'); background-size: cover; background-position: center; color: transparent; background-color: transparent;" : "")">
                                    <i class="bi bi-person-fill" aria-hidden="true"></i>
                                </div>
                                <span>@userDisplayName</span>
                            </button>
                            <ul class="dropdown-menu dropdown-menu-end user-menu-dropdown shadow" aria-labelledby="userMenuDropdown">
                                <li><h6 class="dropdown-header">欢迎, @userDisplayName</h6></li>
                                <li>
                                    <a class="dropdown-item d-flex align-items-center gap-2"
                                       href="@PublicUrls.Git(Uri.EscapeDataString(User.Identity.Name!))">
                                        <i class="bi bi-person-circle"></i> 个人主页
                                    </a>
                                </li>
                                <li>
                                    <a class="dropdown-item d-flex align-items-center gap-2" href="@mainPublicUrl/Account/Profile#v-pills-profile">
                                        <i class="bi bi-gear"></i> 账户设置
                                    </a>
                                </li>
                                @if (Context.User.IsInRole(XFEStudioWebSite.Core.AppRoles.Admin) || Context.User.IsInRole(XFEStudioWebSite.Core.AppRoles.SuperAdmin))
                                {
                                    <li>
                                        <a class="dropdown-item d-flex align-items-center gap-2" href="@mainPublicUrl/Admin">
                                            <i class="bi bi-speedometer2"></i> 管理后台
                                        </a>
                                    </li>
                                }
                                <li><hr class="dropdown-divider"></li>
                                <li>
                                    <form action="@mainPublicUrl/Account/Logout" method="post" class="d-inline">
                                        @Html.AntiForgeryToken()
                                        <button type="submit" class="dropdown-item d-flex align-items-center gap-2 text-danger">
                                            <i class="bi bi-box-arrow-right"></i> 退出登录
                                        </button>
                                    </form>
                                </li>
                            </ul>
                        </div>
                    }
                    else
                    {
                        <a class="btn btn-outline-light btn-sm" href="@loginUrl">
                            <i class="bi bi-box-arrow-in-right me-1"></i> 登录
                        </a>
                    }
                </div>
            </div>
        </div>
    </nav>

    <script>
        // 页面刚开始加载,先禁止动画
        document.body.classList.add('no-transition');

        // 主题同时写入顶级站点 Cookie,使 www/docs/git 子域保持一致。
        const themeCookieName = 'xfe-theme';
        const hostParts = location.hostname.toLowerCase().split('.').filter(Boolean);
        const configuredRootDomain = hostParts.length >= 2 ? hostParts.slice(-2).join('.') : '';
        const readThemeCookie = () => {
            const prefix = `${themeCookieName}=`;
            const value = document.cookie
                .split(';')
                .map(item => item.trim())
                .find(item => item.startsWith(prefix));
            try {
                return value ? decodeURIComponent(value.slice(prefix.length)) : null;
            } catch {
                return null;
            }
        };
        const persistTheme = (theme) => {
            localStorage.setItem('theme', theme);
            const domain = configuredRootDomain &&
                (location.hostname === configuredRootDomain || location.hostname.endsWith(`.${configuredRootDomain}`))
                ? `; Domain=.${configuredRootDomain}`
                : '';
            const secure = location.protocol === 'https:' ? '; Secure' : '';
            document.cookie = `${themeCookieName}=${encodeURIComponent(theme)}; Path=/; Max-Age=31536000; SameSite=Lax${domain}${secure}`;
        };

        // ---- 优先使用共享 Cookie 与本地存储;新用户默认深色 ----
        let savedTheme = readThemeCookie();
        if (savedTheme !== 'light' && savedTheme !== 'dark') {
            try { savedTheme = localStorage.getItem('theme'); } catch { }
        }
        let currentTheme = savedTheme === 'light' || savedTheme === 'dark' ? savedTheme : 'dark';
        persistTheme(currentTheme);

        if (currentTheme === 'dark') {
            document.documentElement.classList.add('dark');
        } else {
            document.documentElement.classList.remove('dark');
        }
        document.documentElement.setAttribute('data-bs-theme', currentTheme);

        // 页面加载完成后再启用动画
        window.addEventListener('load', () => {
            document.body.classList.remove('no-transition');
        });

        // ---- 切换主题 ----
        const setTheme = (theme) => {
            if (theme !== 'light' && theme !== 'dark') return;
            if (theme === 'dark') {
                document.documentElement.classList.add('dark');
            } else {
                document.documentElement.classList.remove('dark');
            }
            document.documentElement.setAttribute('data-bs-theme', theme);
            persistTheme(theme);
            updateThemeUI(theme);
        };
        window.xfeSetTheme = setTheme;

        const updateThemeUI = (theme) => {
            document.querySelectorAll('[data-theme]').forEach(el => {
                if (el.getAttribute('data-theme') === theme) {
                    el.classList.add('active');
                } else {
                    el.classList.remove('active');
                }
            });
        };

        // 等待 DOM 加载完成(确保 footer 中的元素已存在)
        document.addEventListener('DOMContentLoaded', () => {
            document.querySelectorAll('[data-theme]').forEach(el => {
                el.addEventListener('click', (e) => {
                    e.preventDefault();
                    setTheme(el.getAttribute('data-theme'));
                });
            });
            // 初始化 UI 选中状态
            updateThemeUI(currentTheme);
        });
    </script>

    <div class="container flex-grow-1 mb-5">
        @RenderBody()
    </div>

    <footer class="site-footer mt-auto">
        <div class="container-fluid px-4 px-lg-5 py-4">
            <div class="row g-3 align-items-center">
                <div class="col-lg text-center text-lg-start">
                    <p class="mb-1 small" data-no-localize>鄂ICP备2023005524号</p>
                    <p class="mb-0 small"><span>© @DateTime.UtcNow.Year</span> <span>寰宇朽力网络科技有限公司 · 版权所有</span></p>
                </div>
                <div class="col-lg-auto d-flex flex-wrap justify-content-center justify-content-lg-end align-items-center gap-3">
                    <ul class="list-inline mb-0 small">
                        <li class="list-inline-item me-3">
                            <a href="@mainPublicUrl">首页</a>
                        </li>
                        <li class="list-inline-item me-3">
                            <a href="@docsPublicUrl">文档</a>
                        </li>
                        <li class="list-inline-item me-3">
                            <a href="@mainPublicUrl/Downloads">下载</a>
                        </li>
                        <li class="list-inline-item me-3">
                            <a href="@mainPublicUrl/Discussion">讨论</a>
                        </li>
                        <li class="list-inline-item me-3">
                            <a href="@gitPublicUrl">Git</a>
                        </li>
                        <li class="list-inline-item me-3">
                            <a href="@nuGetPublicUrl">NuGet</a>
                        </li>
                        <li class="list-inline-item me-3">
                            <a href="https://work.weixin.qq.com/kfid/kfc03513925fb38fc24">支持与帮助</a>
                        </li>
                    </ul>
                    <div class="dropdown dropup">
                        <button class="btn btn-sm btn-outline-secondary dropdown-toggle d-flex align-items-center gap-1" type="button" id="themeDropdown" data-bs-toggle="dropdown" aria-expanded="false">
                            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-circle-half" viewBox="0 0 16 16">
                                <path d="M8 15A7 7 0 1 0 8 1v14zm0 1A8 8 0 1 1 8 0a8 8 0 0 1 0 16z" />
                            </svg>
                            主题
                        </button>
                        <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="themeDropdown">
                            <li>
                                <button class="dropdown-item d-flex align-items-center gap-2" type="button" data-theme="light">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-sun-fill" viewBox="0 0 16 16">
                                        <path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5-.5h-2a.5.5 0 0 1 0 1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z" />
                                    </svg>
                                    明亮
                                </button>
                            </li>
                            <li>
                                <button class="dropdown-item d-flex align-items-center gap-2" type="button" data-theme="dark">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-moon-stars-fill" viewBox="0 0 16 16">
                                        <path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z" />
                                        <path d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z" />
                                    </svg>
                                    黑暗
                                </button>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>
    </footer>

    <script src="~/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/dialogs.js" asp-append-version="true"></script>
    <script src="~/js/dropdowns.js" asp-append-version="true"></script>
    <script src="~/js/time-localizer.js" asp-append-version="true"></script>
    <script src="~/js/notifications.js" asp-append-version="true"></script>
    <script src="~/js/site-localization.js" asp-append-version="true"></script>
    @if (isMainSiteHost)
    {
        <script>
            // 主站不再作为 PWA:清理由旧版本可能留下的 Service Worker 与应用缓存。
            if ('serviceWorker' in navigator) {
                window.addEventListener('load', () => {
                    void navigator.serviceWorker.getRegistrations()
                        .then(registrations => Promise.all(registrations.map(registration => registration.unregister())))
                        .catch(() => { });
                    if ('caches' in window) {
                        void caches.keys()
                            .then(keys => Promise.all(keys
                                .filter(key => key.startsWith('xfe-studio-shell-'))
                                .map(key => caches.delete(key))))
                            .catch(() => { });
                    }
                });
            }
        </script>
    }
    <script>
        document.addEventListener('click', function (e) {
            const anchor = e.target.closest('a');
            if (!anchor) return;

            // Check if interception should be bypassed
            if (anchor.getAttribute('data-bypass-interception') === 'true') return;

            const href = anchor.getAttribute('href');
            if (!href) return;

            if (href.startsWith('http://') || href.startsWith('https://')) {
                try {
                    const url = new URL(href);
                    const currentHostname = window.location.hostname;
                    const targetHostname = url.hostname;

                    let isInternal = false;

                    if (currentHostname === targetHostname) {
                        isInternal = true;
                    } else {
                        // Check if it is a subdomain of the same root domain
                        // Assuming xfe.studio structure or localhost
                        const currentParts = currentHostname.split('.');
                        let rootDomain = currentHostname;
                        // Basic check for x.y (e.g. xfe.studio) vs localhost
                        if (currentParts.length >= 2) {
                             // Use the last two parts as root domain
                             rootDomain = currentParts.slice(-2).join('.');
                             if (targetHostname.endsWith('.' + rootDomain) || targetHostname === rootDomain) {
                                 isInternal = true;
                             }
                        }
                    }

                    if (!isInternal) {
                        e.preventDefault();
                        window.open('@mainPublicUrl/Redirect?url=' + encodeURIComponent(href), '_blank');
                    }
                } catch (err) {
                    // Invalid URL
                }
            }
        });
    </script>
    @RenderSection("Scripts", required: false)
</body>
</html>