/ opencodehub / src / layouts / BaseLayout.astro
Swadhin Biswas Feat/federation (#82) · 8/19/2026
c0437ed
160 lines · 6.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
---
import "@/styles/globals.css";
import Header from "@/components/layout/Header.astro";
import { Toaster } from "@/components/ui/sonner";
import { getCsrfToken } from "@/middleware/csrf";

interface Props {
  title?: string;
  description?: string;
}

const {
  title = "OpenCodeHub - Self-hosted Git Platform",
  description = "A powerful, self-hosted Git Platform with CI/CD, code review, and collaboration features",
} = Astro.props;

const { token, cookie } = getCsrfToken(Astro.request);
Astro.response.headers.set("Set-Cookie", cookie);
---

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content={description} />
    <meta name="generator" content={Astro.generator} />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <title>{title}</title>
    <meta name="csrf-token" content={token} />
    <!-- Global CSRF Fetch Interceptor -->
    <script is:inline>
      (function() {
        const originalFetch = window.fetch;
        window.fetch = async function () {
          let [resource, config] = arguments;
          if (!config) config = {};
          
          const method = (config.method || 'GET').toUpperCase();
          if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
            const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
            if (token) {
              // Handle Headers object or plain object
              if (config.headers instanceof Headers) {
                config.headers.set('x-csrf-token', token);
              } else {
                config.headers = {
                  ...config.headers,
                  'x-csrf-token': token
                };
              }
            }
          }
          return originalFetch(resource, config);
        };
      })();
    </script>
    <!-- Theme: apply before paint to prevent FOUC -->
    <script is:inline>
      (function() {
        var t = localStorage.getItem('theme') || 'system';
        var isSystemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
        var theme = t === 'system' ? (isSystemDark ? 'dark' : 'light') : t;
        
        if (theme === 'light') {
           document.documentElement.classList.remove('dark');
           document.documentElement.removeAttribute('data-theme');
        } else {
           document.documentElement.classList.add('dark');
           if (theme !== 'dark' && theme !== 'system') {
               document.documentElement.setAttribute('data-theme', theme);
           } else {
               document.documentElement.removeAttribute('data-theme');
           }
        }
      })();
    </script>
    <!-- Preload fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
      rel="stylesheet"
    />
  </head>
  <body class="min-h-screen bg-background font-sans antialiased">
    <!-- Skip to main content link (WCAG 2.4.1) -->
    <a
      href="#main-content"
      class="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-[100] focus:rounded-md focus:bg-primary focus:px-4 focus:py-2 focus:text-primary-foreground focus:shadow-lg focus:outline-none focus:ring-2 focus:ring-ring"
    >
      Skip to main content
    </a>

    <!-- Sleek Monochrome Background -->
    <div class="fixed inset-0 z-[-1] h-full w-full bg-background">
      <div class="absolute bottom-0 left-0 right-0 top-0 bg-[linear-gradient(to_right,#ffffff08_1px,transparent_1px),linear-gradient(to_bottom,#ffffff08_1px,transparent_1px)] bg-[size:24px_24px] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_0%,#000_70%,transparent_100%)]"></div>
    </div>

    <div class="relative flex min-h-screen flex-col">
      <Header />
      <main id="main-content" class="flex-1" role="main" tabindex="-1">
        <slot />
      </main>
      <footer class="border-t py-6 md:py-0" role="contentinfo" aria-label="Site footer">
        <div
          class="container flex flex-col items-center justify-between gap-4 md:h-16 md:flex-row"
        >
          <p
            class="text-center text-sm leading-loose text-muted-foreground md:text-left"
          >
            Built with ❤️ using OpenCodeHub. Self-hosted and open source.
          </p>
          <nav class="flex items-center gap-4 text-sm text-muted-foreground" aria-label="Footer navigation">
            <a href="https://docs.opencodehub.space/" target="_blank" rel="noopener noreferrer" class="hover:text-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-primary/20 focus:rounded-sm">Documentation</a>
            <a href="/api" class="hover:text-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-primary/20 focus:rounded-sm">API</a>
            <a
              href="https://github.com/swadhinbiswas/OpencodeHub"
              class="hover:text-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-primary/20 focus:rounded-sm"
              target="_blank"
              rel="noopener noreferrer"
              aria-label="GitHub repository (opens in new tab)">GitHub</a
            >
          </nav>
        </div>
      </footer>
    </div>
    <Toaster client:idle />
    <script is:inline>
      document.addEventListener('keydown', (e) => {
        if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT' || e.target.isContentEditable) return;
        if (e.key === '.') {
          const parts = window.location.pathname.split('/').filter(Boolean);
          if (parts.length >= 2) {
            const owner = parts[0];
            const repo = parts[1];
            const excluded = ['docs', 'api', 'admin', 'login', 'register', 'settings'];
            if (!excluded.includes(owner)) {
              e.preventDefault();
              let url = `/${owner}/${repo}/ide`;
              const type = parts[2];
              if (type === 'tree' || type === 'blob' || type === 'edit') {
                const branch = parts[3];
                if (branch) {
                  url += `?branch=${encodeURIComponent(branch)}`;
                  const fileParts = parts.slice(4);
                  if (fileParts.length > 0) {
                    url += `&file=${encodeURIComponent(fileParts.join('/'))}`;
                  }
                }
              }
              window.location.href = url;
            }
          }
        }
      });
    </script>
  </body>
</html>