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)
========================================================================== */