From 10559048d02279fe156a76263e088b0f1b995746 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sat, 14 Feb 2026 10:21:45 +0100 Subject: [PATCH] bug-fixes --- src/app.css | 4 + .../components/content/FileExplorer.svelte | 149 +++++----- .../components/modals/EventJsonModal.svelte | 103 ++++++- src/lib/services/content/git-repo-fetcher.ts | 102 ++++++- .../api/gitea-proxy/[...path]/+server.ts | 257 +++++++++++++++--- src/routes/repos/+page.svelte | 32 ++- src/routes/repos/[naddr]/+page.svelte | 148 +++++++++- static/changelog.yaml | 1 + static/healthz.json | 4 +- 9 files changed, 666 insertions(+), 134 deletions(-) diff --git a/src/app.css b/src/app.css index 521ed23..403e2e1 100644 --- a/src/app.css +++ b/src/app.css @@ -1,3 +1,7 @@ +/* Custom highlight.js theme for code blocks, JSON previews, and markdown */ +/* @import must come before all other statements */ +@import './lib/styles/highlight-theme.css'; + /* stylelint-disable-next-line at-rule-no-unknown */ @tailwind base; /* stylelint-disable-next-line at-rule-no-unknown */ diff --git a/src/lib/components/content/FileExplorer.svelte b/src/lib/components/content/FileExplorer.svelte index 77f04c3..1657991 100644 --- a/src/lib/components/content/FileExplorer.svelte +++ b/src/lib/components/content/FileExplorer.svelte @@ -3,7 +3,6 @@ import { fetchGitHubApi } from '../../services/github-api.js'; // @ts-ignore - highlight.js default export works at runtime import hljs from 'highlight.js'; - import 'highlight.js/styles/vs2015.css'; import Icon from '../ui/Icon.svelte'; interface Props { @@ -34,13 +33,36 @@ for (let i = 0; i < parts.length; i++) { const part = parts[i]; - if (i === parts.length - 1) { - // File - current[part] = file; + const isLast = i === parts.length - 1; + const isDirectory = file.type === 'dir'; + + if (isLast) { + // Last part of path + if (isDirectory) { + // Directory - ensure it's an object (not a file) + if (!current[part] || (current[part].path && !current[part]._isDir)) { + current[part] = {}; + } + // Mark as directory + current[part]._isDir = true; + current[part]._path = file.path; + } else { + // File - store the GitFile object + // Only overwrite if current entry is a directory placeholder without children + if (!current[part] || (current[part]._isDir && Object.keys(current[part]).length <= 2)) { + current[part] = file; + } + } } else { - // Directory - if (!current[part] || current[part].path) { + // Intermediate path segment - must be a directory + if (!current[part]) { current[part] = {}; + } else if (current[part].path && !current[part]._isDir) { + // This was stored as a file, but we need it as a directory + // This shouldn't happen, but convert it + const existingFile = current[part]; + current[part] = {}; + current[part][existingFile.name] = existingFile; } current = current[part]; } @@ -106,7 +128,8 @@ ? 'https://gitlab.com/api/v4' : `${baseHost}/api/v4`; // Use proxy endpoint to avoid CORS issues - apiUrl = `/api/gitea-proxy/projects/${projectPath}/repository/files/${encodeURIComponent(file.path)}/raw?baseUrl=${encodeURIComponent(apiBase)}&ref=${repoInfo.defaultBranch}`; + // Pass file path with actual slashes - proxy will handle encoding correctly + apiUrl = `/api/gitea-proxy/projects/${projectPath}/repository/files/${file.path}/raw?baseUrl=${encodeURIComponent(apiBase)}&ref=${repoInfo.defaultBranch}`; } } else if (url.includes('onedev')) { // OneDev API: similar to Gitea, uses /api/v1/repos/{owner}/{repo}/contents/{path} @@ -147,15 +170,30 @@ throw new Error(`Failed to fetch file: ${response.status} ${response.statusText}`); } - const data = await response.json(); + // Check content type to determine how to parse the response + const contentType = response.headers.get('content-type') || ''; - // GitHub returns base64 encoded content, GitLab/Gitea return raw text - if (data.content) { - // GitHub format - fileContent = atob(data.content.replace(/\s/g, '')); + if (url.includes('github.com')) { + // GitHub API returns JSON with base64 encoded content + const data = await response.json(); + if (data.content) { + fileContent = atob(data.content.replace(/\s/g, '')); + } + } else if (url.includes('gitlab') && apiUrl.includes('/raw')) { + // GitLab raw file endpoint returns plain text + fileContent = await response.text(); + } else if (contentType.includes('application/json')) { + // Other platforms that return JSON (like Gitea contents API) + const data = await response.json(); + if (data.content) { + // Some APIs return base64 content in JSON + fileContent = atob(data.content.replace(/\s/g, '')); + } else if (typeof data === 'string') { + fileContent = data; + } } else { - // GitLab/Gitea format (raw text) - fileContent = typeof data === 'string' ? data : await response.text(); + // Default to text for raw file endpoints + fileContent = await response.text(); } } catch (error) { console.error('Error fetching file content:', error); @@ -247,8 +285,8 @@ 'dockerfile': 'dockerfile', 'makefile': 'makefile', 'cmake': 'cmake', 'gradle': 'gradle', 'maven': 'xml', 'pom': 'xml', 'toml': 'toml', 'ini': 'ini', 'conf': 'ini', 'config': 'ini', - 'properties': 'properties', 'env': 'bash', 'gitignore': 'gitignore', - 'dockerignore': 'gitignore', 'editorconfig': 'ini', 'eslintrc': 'json', 'prettierrc': 'json' + 'properties': 'properties', 'env': 'bash', 'gitignore': 'plaintext', + 'dockerignore': 'plaintext', 'editorconfig': 'ini', 'eslintrc': 'json', 'prettierrc': 'json' }; return languageMap[ext] || 'plaintext'; } @@ -286,8 +324,22 @@ $effect(() => { if (fileContent && selectedFile && isCodeFile(selectedFile) && codeRef) { const language = getLanguageFromExtension(selectedFile); - codeRef.innerHTML = hljs.highlight(fileContent, { language }).value; - codeRef.className = `language-${language}`; + try { + // Check if language is supported, fallback to plaintext if not + if (hljs.getLanguage(language)) { + codeRef.innerHTML = hljs.highlight(fileContent, { language }).value; + codeRef.className = `language-${language}`; + } else { + // Language not supported, use plaintext + codeRef.innerHTML = hljs.highlight(fileContent, { language: 'plaintext' }).value; + codeRef.className = 'language-plaintext'; + } + } catch (error) { + // If highlighting fails, just display plain text + console.warn(`Failed to highlight code with language '${language}':`, error); + codeRef.textContent = fileContent; + codeRef.className = 'language-plaintext'; + } } }); @@ -534,7 +586,8 @@ .file-explorer { display: flex; gap: 1rem; - height: 600px; + height: 1000px; + min-height: 800px; border: 1px solid var(--fog-border, #e5e7eb); border-radius: 0.5rem; overflow: hidden; @@ -767,8 +820,8 @@ .file-content-code { margin: 0; - background: #1e1e1e !important; /* VS Code dark background, same as JSON preview */ - border: 1px solid #3e3e3e; + background: #000000 !important; /* Pure black background */ + border: 1px solid #333333; border-radius: 4px; padding: 1rem; overflow-x: auto; @@ -777,60 +830,15 @@ } :global(.dark) .file-content-code { - background: #1e1e1e !important; - border-color: #3e3e3e; + background: #000000 !important; /* Pure black background */ + border-color: #333333; } .file-content-code code { display: block; overflow-x: auto; padding: 0; - background: transparent !important; - color: #d4d4d4; /* VS Code text color */ - font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Droid Sans Mono', 'Source Code Pro', monospace; - font-size: 0.875rem; - line-height: 1.5; - } - - .file-content-code :global(code.hljs), - .file-content-code :global(code.hljs *), - .file-content-code :global(code.hljs span), - .file-content-code :global(code.hljs .hljs-keyword), - .file-content-code :global(code.hljs .hljs-string), - .file-content-code :global(code.hljs .hljs-comment), - .file-content-code :global(code.hljs .hljs-number), - .file-content-code :global(code.hljs .hljs-function), - .file-content-code :global(code.hljs .hljs-variable), - .file-content-code :global(code.hljs .hljs-class), - .file-content-code :global(code.hljs .hljs-title), - .file-content-code :global(code.hljs .hljs-attr), - .file-content-code :global(code.hljs .hljs-tag), - .file-content-code :global(code.hljs .hljs-name), - .file-content-code :global(code.hljs .hljs-selector-id), - .file-content-code :global(code.hljs .hljs-selector-class), - .file-content-code :global(code.hljs .hljs-attribute), - .file-content-code :global(code.hljs .hljs-built_in), - .file-content-code :global(code.hljs .hljs-literal), - .file-content-code :global(code.hljs .hljs-type), - .file-content-code :global(code.hljs .hljs-property), - .file-content-code :global(code.hljs .hljs-operator), - .file-content-code :global(code.hljs .hljs-punctuation), - .file-content-code :global(code.hljs .hljs-meta), - .file-content-code :global(code.hljs .hljs-doctag), - .file-content-code :global(code.hljs .hljs-section), - .file-content-code :global(code.hljs .hljs-addition), - .file-content-code :global(code.hljs .hljs-deletion), - .file-content-code :global(code.hljs .hljs-emphasis), - .file-content-code :global(code.hljs .hljs-strong) { - text-shadow: none !important; - -webkit-font-smoothing: antialiased !important; - -moz-osx-font-smoothing: grayscale !important; - font-smoothing: antialiased !important; - text-rendering: geometricPrecision !important; - transform: translateZ(0); - backface-visibility: hidden; - filter: none !important; - will-change: auto; + /* Theme colors are defined in highlight-theme.css */ } .file-image-container { @@ -919,7 +927,8 @@ .file-explorer { flex-direction: column; height: auto; - max-height: 800px; + min-height: 800px; + max-height: 1200px; } .file-tree-panel { diff --git a/src/lib/components/modals/EventJsonModal.svelte b/src/lib/components/modals/EventJsonModal.svelte index a648a23..a27e3a9 100644 --- a/src/lib/components/modals/EventJsonModal.svelte +++ b/src/lib/components/modals/EventJsonModal.svelte @@ -3,7 +3,6 @@ import Icon from '../ui/Icon.svelte'; // @ts-ignore - highlight.js default export works at runtime import hljs from 'highlight.js'; - import 'highlight.js/styles/vs2015.css'; interface Props { open?: boolean; @@ -13,6 +12,7 @@ let { open = $bindable(false), event = $bindable(null) }: Props = $props(); let jsonText = $derived(event ? JSON.stringify(event, null, 2) : ''); let copied = $state(false); + let wordWrap = $state(true); // Default to word-wrap enabled let jsonPreviewRef: HTMLElement | null = $state(null); // Highlight JSON when it changes @@ -72,13 +72,25 @@