diff --git a/src/components/nostr/kinds/RepositoryFilesSection.tsx b/src/components/nostr/kinds/RepositoryFilesSection.tsx index 8acfef7..6f1a58e 100644 --- a/src/components/nostr/kinds/RepositoryFilesSection.tsx +++ b/src/components/nostr/kinds/RepositoryFilesSection.tsx @@ -1,7 +1,15 @@ import { useState } from "react"; -import { FolderGit2, AlertCircle, FileQuestion, Binary } from "lucide-react"; +import { + FolderGit2, + AlertCircle, + FileQuestion, + Binary, + Copy, + Check, +} from "lucide-react"; import { useGitTree } from "@/hooks/useGitTree"; import { useGitBlob } from "@/hooks/useGitBlob"; +import { useCopy } from "@/hooks/useCopy"; import { FileTreeView } from "@/components/ui/FileTreeView"; import { SyntaxHighlight } from "@/components/SyntaxHighlight"; import { Skeleton } from "@/components/ui/skeleton/Skeleton"; @@ -71,6 +79,9 @@ export function RepositoryFilesSection({ ? getExtension(selectedFile.name) || null : null; + // Copy functionality for file content + const { copy, copied } = useCopy(); + const handleFileSelect = (file: SelectedFile) => { setSelectedFile(file); }; @@ -170,11 +181,25 @@ export function RepositoryFilesSection({ ) : fileContent ? (
- - {selectedFile.path} - +
+ + {selectedFile.path} + + +
{rawContent && ( - + {formatSize(rawContent.length)} )} diff --git a/src/lib/shiki.ts b/src/lib/shiki.ts index 71d4969..7adbd1a 100644 --- a/src/lib/shiki.ts +++ b/src/lib/shiki.ts @@ -1,7 +1,7 @@ import { createHighlighterCore, + createCssVariablesTheme, type HighlighterCore, - type ShikiTransformer, } from "shiki/core"; import { createOnigurumaEngine } from "shiki/engine/oniguruma"; @@ -12,146 +12,16 @@ const loadedLanguages = new Set(); const failedLanguages = new Set(); /** - * Transformer that cleans up Shiki output for our styling - * Keeps inline colors from the theme but removes backgrounds + * CSS Variables theme for Shiki + * This outputs CSS custom properties that we map to our theme variables. + * The actual colors are defined in shiki-theme.css using our theme system. */ -const cleanupTransformer: ShikiTransformer = { - name: "cleanup-transformer", - pre(node) { - // Remove background color from pre, let CSS handle it - if (node.properties?.style) { - const style = node.properties.style as string; - node.properties.style = style.replace(/background-color:[^;]+;?/g, ""); - } - }, - code(node) { - // Remove background from code element, keep color - if (node.properties?.style) { - const style = node.properties.style as string; - node.properties.style = style.replace(/background-color:[^;]+;?/g, ""); - } - }, -}; - -/** - * Minimal theme - we'll override colors via CSS - * Using high-contrast colors as fallback if CSS fails - */ -const minimalTheme = { - name: "grimoire", - type: "dark" as const, - colors: { - "editor.background": "transparent", - "editor.foreground": "#e6edf3", - }, - tokenColors: [ - { - scope: ["comment", "punctuation.definition.comment"], - settings: { foreground: "#8b949e" }, - }, - { - scope: ["string", "string.quoted"], - settings: { foreground: "#a5d6ff" }, - }, - { - scope: [ - "keyword", - "storage", - "storage.type", - "storage.modifier", - "keyword.operator", - "keyword.control", - ], - settings: { foreground: "#f0f0f0" }, - }, - { - scope: ["entity.name.function", "support.function", "meta.function-call"], - settings: { foreground: "#e6edf3" }, - }, - { - scope: [ - "entity.name.class", - "entity.name.type", - "support.class", - "support.type", - ], - settings: { foreground: "#f0f0f0" }, - }, - { - scope: [ - "constant", - "constant.numeric", - "constant.language", - "constant.character", - ], - settings: { foreground: "#79c0ff" }, - }, - { - scope: ["variable", "variable.parameter", "variable.other"], - settings: { foreground: "#e6edf3" }, - }, - { - scope: ["punctuation", "meta.brace"], - settings: { foreground: "#c9d1d9" }, - }, - { - scope: [ - "variable.other.property", - "entity.other.attribute-name", - "support.type.property-name", - ], - settings: { foreground: "#e6edf3" }, - }, - { - scope: ["entity.name.tag", "support.class.component"], - settings: { foreground: "#7ee787" }, - }, - { - scope: ["support.type.property-name.json"], - settings: { foreground: "#a5d6ff" }, - }, - { - scope: [ - "markup.deleted", - "punctuation.definition.deleted", - "meta.diff.header.from-file", - ], - settings: { foreground: "#ffa198" }, - }, - { - scope: [ - "markup.inserted", - "punctuation.definition.inserted", - "meta.diff.header.to-file", - ], - settings: { foreground: "#7ee787" }, - }, - { - scope: ["markup.changed", "meta.diff.range", "meta.diff.header"], - settings: { foreground: "#a5d6ff" }, - }, - { - scope: ["markup.heading", "entity.name.section"], - settings: { foreground: "#f0f0f0" }, - }, - { - scope: ["markup.bold"], - settings: { fontStyle: "bold" }, - }, - { - scope: ["markup.italic"], - settings: { fontStyle: "italic" }, - }, - { - scope: ["markup.underline.link"], - settings: { foreground: "#a5d6ff" }, - }, - { - scope: ["markup.inline.raw", "markup.raw"], - settings: { foreground: "#a5d6ff" }, - }, - ], -}; +const cssVarsTheme = createCssVariablesTheme({ + name: "css-variables", + variablePrefix: "--shiki-", + variableDefaults: {}, + fontStyle: true, +}); /** * Language alias mapping (file extensions and common names to Shiki IDs) @@ -308,7 +178,7 @@ export async function getHighlighter(): Promise { if (!highlighterPromise) { highlighterPromise = createHighlighterCore({ - themes: [minimalTheme], + themes: [cssVarsTheme], langs: [ import("shiki/langs/javascript.mjs"), import("shiki/langs/typescript.mjs"), @@ -371,8 +241,7 @@ export async function highlightCode( return hl.codeToHtml(code, { lang: effectiveLang, - theme: "grimoire", - transformers: [cleanupTransformer], + theme: "css-variables", }); } diff --git a/src/styles/shiki-theme.css b/src/styles/shiki-theme.css index bf67fe7..af13480 100644 --- a/src/styles/shiki-theme.css +++ b/src/styles/shiki-theme.css @@ -1,11 +1,40 @@ /* ========================================================================== Shiki Syntax Highlighting - CSS Variable Based Theming - Token colors are defined using CSS variables from the theme system, + Maps Shiki's CSS variables to our theme system variables, allowing automatic adaptation to light/dark mode. ========================================================================== */ -/* Base container styling */ +/* ========================================================================== + Shiki CSS Variables -> Theme Variables Mapping + These are set by Shiki's css-variables theme and we map them to our theme + ========================================================================== */ + +:root { + /* Default/fallback - uses foreground */ + --shiki-color-text: hsl(var(--foreground)); + --shiki-color-background: transparent; + + /* Token colors - mapped to our syntax theme variables */ + --shiki-token-constant: hsl(var(--syntax-constant)); + --shiki-token-string: hsl(var(--syntax-string)); + --shiki-token-comment: hsl(var(--syntax-comment)); + --shiki-token-keyword: hsl(var(--syntax-keyword)); + --shiki-token-parameter: hsl(var(--syntax-variable)); + --shiki-token-function: hsl(var(--syntax-function)); + --shiki-token-string-expression: hsl(var(--syntax-string)); + --shiki-token-punctuation: hsl(var(--syntax-punctuation)); + --shiki-token-link: hsl(var(--syntax-string)); + + /* Diff colors */ + --shiki-token-deleted: hsl(var(--diff-deleted)); + --shiki-token-inserted: hsl(var(--diff-inserted)); +} + +/* ========================================================================== + Base container styling + ========================================================================== */ + .shiki-container { color: hsl(var(--foreground)); } @@ -36,55 +65,6 @@ background: transparent !important; } -/* ========================================================================== - Token Classes - Styled with CSS Variables - ========================================================================== */ - -/* Base token - inherits foreground color */ -.shiki-token { - color: hsl(var(--syntax-variable)); -} - -/* Comments */ -.shiki-comment { - color: hsl(var(--syntax-comment)); -} - -/* Strings */ -.shiki-string { - color: hsl(var(--syntax-string)); -} - -/* Keywords (if, else, return, function, etc.) */ -.shiki-keyword { - color: hsl(var(--syntax-keyword)); -} - -/* Constants and numbers */ -.shiki-constant { - color: hsl(var(--syntax-constant)); -} - -/* Punctuation */ -.shiki-punctuation { - color: hsl(var(--syntax-punctuation)); -} - -/* Tags (HTML/JSX) */ -.shiki-tag { - color: hsl(var(--syntax-tag)); -} - -/* Diff - deleted lines */ -.shiki-deleted { - color: hsl(var(--diff-deleted)); -} - -/* Diff - inserted lines */ -.shiki-inserted { - color: hsl(var(--diff-inserted)); -} - /* ========================================================================== Loading State ========================================================================== */ @@ -108,29 +88,20 @@ ========================================================================== */ /* Diff-specific styling - block-level backgrounds for inserted/deleted */ -.shiki-container .line:has(.shiki-deleted) { +.shiki-container .line:has([style*="--shiki-token-deleted"]) { background: hsl(var(--diff-deleted-bg)); display: block; margin: 0 -1rem; padding: 0 1rem; } -.shiki-container .line:has(.shiki-inserted) { +.shiki-container .line:has([style*="--shiki-token-inserted"]) { background: hsl(var(--diff-inserted-bg)); display: block; margin: 0 -1rem; padding: 0 1rem; } -/* Hunk headers (@@ lines) */ -.shiki-container .line:has([class*="meta"]) { - background: hsl(var(--diff-meta-bg)); - display: block; - margin: 0 -1rem; - padding: 0 1rem; - font-weight: 600; -} - /* ========================================================================== Line Numbers (optional) ========================================================================== */