mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-05 10:11:12 +02:00
refactor: improve git tree and syntax highlighting
- Remove shallow clone fallback from useGitTree, only use no-blobs fetch Servers without filter capability are skipped instead of downloading blobs - Add light theme support for Shiki syntax highlighting Theme is automatically selected based on current color scheme
This commit is contained in:
@@ -2,7 +2,6 @@ import { useState, useEffect, useCallback } from "react";
|
||||
import {
|
||||
getInfoRefs,
|
||||
getDirectoryTreeAt,
|
||||
shallowCloneRepositoryAt,
|
||||
MissingCapability,
|
||||
} from "@fiatjaf/git-natural-api";
|
||||
import type { DirectoryTree } from "@/lib/git-types";
|
||||
@@ -33,8 +32,8 @@ interface UseGitTreeResult {
|
||||
* Hook to fetch a git repository tree from clone URLs
|
||||
*
|
||||
* Tries each clone URL in sequence until one succeeds.
|
||||
* Uses the lightweight `getDirectoryTreeAt` if the server supports filtering,
|
||||
* otherwise falls back to `shallowCloneRepositoryAt`.
|
||||
* Uses the lightweight `getDirectoryTreeAt` which requires filter capability.
|
||||
* Servers without filter support are skipped.
|
||||
*
|
||||
* @example
|
||||
* const { tree, loading, error } = useGitTree({
|
||||
@@ -69,7 +68,18 @@ export function useGitTree({
|
||||
try {
|
||||
// Get server info to check capabilities and resolve refs
|
||||
const info = await getInfoRefs(url);
|
||||
const hasFilter = info.capabilities.includes("filter");
|
||||
|
||||
// Only use servers that support filter capability (lightweight fetch)
|
||||
// Skip servers that would require downloading all blobs
|
||||
if (!info.capabilities.includes("filter")) {
|
||||
console.warn(
|
||||
`[useGitTree] Server ${url} doesn't support filter capability, skipping`,
|
||||
);
|
||||
errors.push(
|
||||
new MissingCapability("filter", "Server doesn't support filter"),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resolve the ref to a commit hash
|
||||
let resolvedRef = ref;
|
||||
@@ -92,15 +102,8 @@ export function useGitTree({
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the tree
|
||||
let fetchedTree: DirectoryTree;
|
||||
if (hasFilter) {
|
||||
// Server supports filter - use lightweight fetch (tree only, no blobs)
|
||||
fetchedTree = await getDirectoryTreeAt(url, resolvedRef);
|
||||
} else {
|
||||
// No filter support - need to do shallow clone
|
||||
fetchedTree = await shallowCloneRepositoryAt(url, resolvedRef);
|
||||
}
|
||||
// Fetch the tree using lightweight filter (tree only, no blobs)
|
||||
const fetchedTree = await getDirectoryTreeAt(url, resolvedRef);
|
||||
|
||||
setTree(fetchedTree);
|
||||
setServerUrl(url);
|
||||
|
||||
627
src/index.css
627
src/index.css
@@ -45,219 +45,562 @@
|
||||
@keyframes skeleton-pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.4;
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* Colors - Map Tailwind utilities to CSS variables */
|
||||
/* ==========================================================================
|
||||
Color Tokens
|
||||
These map runtime CSS variables (set by ThemeProvider) to Tailwind colors.
|
||||
The runtime variables use HSL values WITHOUT the hsl() wrapper.
|
||||
========================================================================== */
|
||||
|
||||
/* Core Colors */
|
||||
--color-background: hsl(var(--background));
|
||||
--color-foreground: hsl(var(--foreground));
|
||||
|
||||
/* Card */
|
||||
--color-card: hsl(var(--card));
|
||||
--color-card-foreground: hsl(var(--card-foreground));
|
||||
|
||||
/* Popover */
|
||||
--color-popover: hsl(var(--popover));
|
||||
--color-popover-foreground: hsl(var(--popover-foreground));
|
||||
|
||||
/* Primary */
|
||||
--color-primary: hsl(var(--primary));
|
||||
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||
|
||||
/* Secondary */
|
||||
--color-secondary: hsl(var(--secondary));
|
||||
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
||||
--color-muted: hsl(var(--muted));
|
||||
--color-muted-foreground: hsl(var(--muted-foreground));
|
||||
|
||||
/* Accent */
|
||||
--color-accent: hsl(var(--accent));
|
||||
--color-accent-foreground: hsl(var(--accent-foreground));
|
||||
|
||||
/* Muted */
|
||||
--color-muted: hsl(var(--muted));
|
||||
--color-muted-foreground: hsl(var(--muted-foreground));
|
||||
|
||||
/* Destructive */
|
||||
--color-destructive: hsl(var(--destructive));
|
||||
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
||||
|
||||
/* Form Elements */
|
||||
--color-border: hsl(var(--border));
|
||||
--color-input: hsl(var(--input));
|
||||
--color-ring: hsl(var(--ring));
|
||||
--color-sidebar: hsl(var(--sidebar));
|
||||
--color-sidebar-foreground: hsl(var(--sidebar-foreground));
|
||||
--color-sidebar-primary: hsl(var(--sidebar-primary));
|
||||
--color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));
|
||||
--color-sidebar-accent: hsl(var(--sidebar-accent));
|
||||
--color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));
|
||||
--color-sidebar-border: hsl(var(--sidebar-border));
|
||||
--color-sidebar-ring: hsl(var(--sidebar-ring));
|
||||
--color-chart-1: hsl(var(--chart-1));
|
||||
--color-chart-2: hsl(var(--chart-2));
|
||||
--color-chart-3: hsl(var(--chart-3));
|
||||
--color-chart-4: hsl(var(--chart-4));
|
||||
--color-chart-5: hsl(var(--chart-5));
|
||||
|
||||
/* Status Colors */
|
||||
--color-success: hsl(var(--success));
|
||||
--color-warning: hsl(var(--warning));
|
||||
--color-info: hsl(var(--info));
|
||||
|
||||
/* Nostr-specific Colors */
|
||||
--color-zap: hsl(var(--zap));
|
||||
--color-live: hsl(var(--live));
|
||||
--color-highlight: hsl(var(--highlight));
|
||||
|
||||
/* Tooltip */
|
||||
--color-tooltip: hsl(var(--tooltip));
|
||||
--color-tooltip-foreground: hsl(var(--tooltip-foreground));
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Base Styles (@layer base)
|
||||
These are low-specificity foundational styles.
|
||||
Runtime Theme Variables
|
||||
These are HSL values WITHOUT the hsl() wrapper, allowing alpha transparency.
|
||||
Set dynamically by ThemeProvider via applyTheme().
|
||||
========================================================================== */
|
||||
|
||||
:root {
|
||||
/* Core colors - light theme defaults (overridden by ThemeProvider) */
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
--radius: 0.5rem;
|
||||
|
||||
/* Status colors */
|
||||
--success: 142 76% 36%;
|
||||
--warning: 45 93% 47%;
|
||||
--info: 199 89% 48%;
|
||||
|
||||
/* Nostr-specific colors */
|
||||
--zap: 45 93% 40%;
|
||||
--live: 0 72% 45%;
|
||||
|
||||
/* UI highlight (active user, self-references) */
|
||||
--highlight: 25 90% 35%;
|
||||
|
||||
/* Tooltip colors */
|
||||
--tooltip: 222.2 47.4% 11.2%;
|
||||
--tooltip-foreground: 210 40% 98%;
|
||||
|
||||
/* Syntax highlighting */
|
||||
--syntax-comment: 215.4 16.3% 46.9%;
|
||||
--syntax-punctuation: 222.2 84% 30%;
|
||||
--syntax-property: 222.2 47.4% 11.2%;
|
||||
--syntax-string: 142 60% 30%;
|
||||
--syntax-keyword: 270 80% 50%;
|
||||
--syntax-function: 222.2 47.4% 11.2%;
|
||||
--syntax-variable: 222.2 84% 4.9%;
|
||||
--syntax-operator: 222.2 84% 20%;
|
||||
|
||||
/* Diff colors */
|
||||
--diff-inserted: 142 60% 30%;
|
||||
--diff-inserted-bg: 142 60% 50% / 0.15;
|
||||
--diff-deleted: 0 70% 45%;
|
||||
--diff-deleted-bg: 0 70% 50% / 0.15;
|
||||
--diff-meta: 199 80% 40%;
|
||||
--diff-meta-bg: 199 80% 50% / 0.1;
|
||||
|
||||
/* Scrollbar */
|
||||
--scrollbar-thumb: 222.2 84% 4.9% / 0.2;
|
||||
--scrollbar-thumb-hover: 222.2 84% 4.9% / 0.3;
|
||||
--scrollbar-track: 0 0% 0% / 0;
|
||||
|
||||
/* Gradient colors (RGB values) */
|
||||
--gradient-1: 234 179 8;
|
||||
--gradient-2: 249 115 22;
|
||||
--gradient-3: 147 51 234;
|
||||
--gradient-4: 6 182 212;
|
||||
}
|
||||
|
||||
/* Dark theme - applied via .dark class or ThemeProvider */
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 70%;
|
||||
--accent: 270 100% 70%;
|
||||
--accent-foreground: 222.2 84% 4.9%;
|
||||
--destructive: 0 75% 75%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
|
||||
/* Status colors */
|
||||
--success: 142 76% 46%;
|
||||
--warning: 38 92% 60%;
|
||||
--info: 199 89% 58%;
|
||||
|
||||
/* Nostr-specific colors */
|
||||
--zap: 45 93% 58%;
|
||||
--live: 0 72% 51%;
|
||||
|
||||
/* UI highlight (active user, self-references) */
|
||||
--highlight: 27 96% 61%;
|
||||
|
||||
/* Tooltip colors */
|
||||
--tooltip: 217.2 32.6% 30%;
|
||||
--tooltip-foreground: 210 40% 98%;
|
||||
|
||||
/* Syntax highlighting */
|
||||
--syntax-comment: 215 20.2% 70%;
|
||||
--syntax-punctuation: 210 40% 70%;
|
||||
--syntax-property: 210 40% 98%;
|
||||
--syntax-string: 215 20.2% 70%;
|
||||
--syntax-keyword: 210 40% 98%;
|
||||
--syntax-function: 210 40% 98%;
|
||||
--syntax-variable: 210 40% 98%;
|
||||
--syntax-operator: 210 40% 98%;
|
||||
|
||||
/* Diff colors */
|
||||
--diff-inserted: 134 60% 76%;
|
||||
--diff-inserted-bg: 145 63% 42% / 0.1;
|
||||
--diff-deleted: 0 100% 76%;
|
||||
--diff-deleted-bg: 0 100% 60% / 0.1;
|
||||
--diff-meta: 190 77% 70%;
|
||||
--diff-meta-bg: 190 77% 70% / 0.08;
|
||||
|
||||
/* Scrollbar */
|
||||
--scrollbar-thumb: 0 0% 100% / 0.2;
|
||||
--scrollbar-thumb-hover: 0 0% 100% / 0.3;
|
||||
--scrollbar-track: 0 0% 0% / 0;
|
||||
|
||||
/* Gradient colors (RGB values) */
|
||||
--gradient-1: 250 204 21;
|
||||
--gradient-2: 251 146 60;
|
||||
--gradient-3: 168 85 247;
|
||||
--gradient-4: 34 211 238;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Custom Scrollbar Styling
|
||||
========================================================================== */
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: hsl(var(--scrollbar-thumb)) hsl(var(--scrollbar-track));
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background: hsl(var(--scrollbar-track));
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: hsl(var(--scrollbar-thumb));
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background-color: hsl(var(--scrollbar-thumb-hover));
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Base Layer - Global Styles
|
||||
========================================================================== */
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
/* Force hardware acceleration for smooth scrolling */
|
||||
.hardware-accelerated {
|
||||
transform: translateZ(0);
|
||||
will-change: transform;
|
||||
backface-visibility: hidden;
|
||||
@apply bg-background text-foreground font-mono;
|
||||
/* iOS PWA safe area insets for notch support */
|
||||
padding-top: env(safe-area-inset-top, 0);
|
||||
padding-right: env(safe-area-inset-right, 0);
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
padding-left: env(safe-area-inset-left, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Theme Variables
|
||||
Runtime CSS variables that power the design system.
|
||||
These are toggled by adding/removing the .dark class on <html>.
|
||||
Custom Utilities (v4 @utility syntax)
|
||||
========================================================================== */
|
||||
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 0% 3.9%;
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
--sidebar: 0 0% 98%;
|
||||
--sidebar-foreground: 0 0% 9%;
|
||||
--sidebar-primary: 0 0% 9%;
|
||||
--sidebar-primary-foreground: 0 0% 98%;
|
||||
--sidebar-accent: 0 0% 94%;
|
||||
--sidebar-accent-foreground: 0 0% 9%;
|
||||
--sidebar-border: 0 0% 89%;
|
||||
--sidebar-ring: 0 0% 3.9%;
|
||||
@utility no-scrollbar {
|
||||
-ms-overflow-style: none !important;
|
||||
scrollbar-width: none !important;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 0 0% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 0 0% 83.1%;
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
--sidebar: 0 0% 5%;
|
||||
--sidebar-foreground: 0 0% 98%;
|
||||
--sidebar-primary: 0 0% 98%;
|
||||
--sidebar-primary-foreground: 0 0% 9%;
|
||||
--sidebar-accent: 0 0% 12%;
|
||||
--sidebar-accent-foreground: 0 0% 98%;
|
||||
--sidebar-border: 0 0% 14.9%;
|
||||
--sidebar-ring: 0 0% 83.1%;
|
||||
@utility hide-scrollbar {
|
||||
-ms-overflow-style: none !important;
|
||||
scrollbar-width: none !important;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@utility text-grimoire-gradient {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgb(var(--gradient-1)),
|
||||
rgb(var(--gradient-2)),
|
||||
rgb(var(--gradient-3)),
|
||||
rgb(var(--gradient-4))
|
||||
);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Component Styles
|
||||
Third-Party Component Overrides
|
||||
========================================================================== */
|
||||
|
||||
/* Custom scrollbar for dark theme */
|
||||
.dark ::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
/* react-medium-image-zoom theme customization */
|
||||
[data-rmiz-modal-overlay] {
|
||||
background-color: hsl(var(--background) / 0.92) !important;
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-track {
|
||||
[data-rmiz-modal-content] {
|
||||
box-shadow: 0 0 40px hsl(var(--foreground) / 0.2);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
React Mosaic Theme Customization
|
||||
========================================================================== */
|
||||
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme {
|
||||
background: hsl(var(--background));
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb {
|
||||
/* Smooth animations for window resizing and repositioning */
|
||||
/* Only animate during preset application, not manual resize/drag */
|
||||
body.animating-layout
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme
|
||||
.mosaic-tile {
|
||||
transition:
|
||||
width 150ms cubic-bezier(0.25, 0.1, 0.25, 1),
|
||||
height 150ms cubic-bezier(0.25, 0.1, 0.25, 1),
|
||||
top 150ms cubic-bezier(0.25, 0.1, 0.25, 1),
|
||||
left 150ms cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||
contain: layout; /* Isolate layout calculations for better performance */
|
||||
}
|
||||
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme
|
||||
.mosaic-window
|
||||
.mosaic-window-toolbar {
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
border-radius: 0;
|
||||
color: hsl(var(--foreground));
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb:hover {
|
||||
background: hsl(var(--muted-foreground) / 0.5);
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme
|
||||
.mosaic-window
|
||||
.mosaic-window-title {
|
||||
color: hsl(var(--foreground));
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
/* Mosaic component overrides */
|
||||
.mosaic-root {
|
||||
background: transparent !important;
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-window {
|
||||
background: hsl(var(--background));
|
||||
outline: none;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.mosaic-tile {
|
||||
margin: 2px !important;
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-window * {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.mosaic-window {
|
||||
border-radius: var(--radius) !important;
|
||||
overflow: hidden;
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-window::before,
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-window::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mosaic-window-toolbar {
|
||||
display: none !important;
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme.bp4-dark .mosaic-window,
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme.bp4-dark .mosaic-preview {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.mosaic-window-body {
|
||||
background: transparent !important;
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme
|
||||
.mosaic-window
|
||||
.mosaic-window-body {
|
||||
background: hsl(var(--background));
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.mosaic-split {
|
||||
background: transparent !important;
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-window-controls {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.mosaic-split:hover {
|
||||
background: hsl(var(--primary) / 0.2) !important;
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme
|
||||
.mosaic-window-controls:hover {
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.mosaic-split.-row {
|
||||
margin: 0 -2px !important;
|
||||
width: 6px !important;
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme
|
||||
.mosaic-window-toolbar
|
||||
.separator {
|
||||
border-left: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.mosaic-split.-column {
|
||||
margin: -2px 0 !important;
|
||||
height: 6px !important;
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme
|
||||
.mosaic-window-body-overlay {
|
||||
background: hsl(var(--background));
|
||||
}
|
||||
|
||||
/* Active split highlight */
|
||||
.mosaic-split:active {
|
||||
background: hsl(var(--primary) / 0.4) !important;
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-preview {
|
||||
background: hsl(var(--accent) / 0.3);
|
||||
border: 2px solid hsl(var(--primary));
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-drop-target {
|
||||
border: 2px solid var(--border);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-split:hover {
|
||||
background: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-split.-row {
|
||||
width: 4px;
|
||||
margin: 0 -2px;
|
||||
}
|
||||
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-split.-column {
|
||||
height: 4px;
|
||||
margin: -2px 0;
|
||||
}
|
||||
|
||||
/* Mobile: Wider split dividers for touch dragging */
|
||||
@media (max-width: 767px) {
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-split.-row {
|
||||
width: 12px;
|
||||
margin: 0 -6px;
|
||||
}
|
||||
|
||||
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-split.-column {
|
||||
height: 12px;
|
||||
margin: -6px 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Accessibility: Focus Indicators
|
||||
========================================================================== */
|
||||
|
||||
@layer base {
|
||||
/* Focus-visible for buttons and interactive elements */
|
||||
button:focus-visible,
|
||||
a:focus-visible,
|
||||
[role="button"]:focus-visible {
|
||||
outline: 2px solid hsl(var(--ring));
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Focus-visible for input elements */
|
||||
input:focus-visible,
|
||||
textarea:focus-visible,
|
||||
select:focus-visible {
|
||||
outline: 2px solid hsl(var(--ring));
|
||||
outline-offset: 0;
|
||||
}
|
||||
|
||||
/* Focus-visible for command launcher items */
|
||||
[cmdk-item]:focus-visible {
|
||||
outline: 2px solid hsl(var(--ring));
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* Focus-visible for tab buttons */
|
||||
.tabbar-button:focus-visible {
|
||||
outline: 2px solid hsl(var(--ring));
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
TipTap Editor Styles
|
||||
========================================================================== */
|
||||
|
||||
.ProseMirror {
|
||||
min-height: 1.25rem;
|
||||
line-height: 1.25rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ProseMirror:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ProseMirror p {
|
||||
margin: 0;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.ProseMirror p.is-editor-empty:first-child::before {
|
||||
content: attr(data-placeholder);
|
||||
color: hsl(var(--muted-foreground));
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
/* Disable link navigation in editor - allow editing instead of clicking */
|
||||
.ProseMirror a {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Mention styles */
|
||||
.ProseMirror .mention {
|
||||
color: hsl(var(--primary));
|
||||
background-color: hsl(var(--primary) / 0.1);
|
||||
padding: 0.125rem 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ProseMirror .mention:hover {
|
||||
background-color: hsl(var(--primary) / 0.2);
|
||||
}
|
||||
|
||||
/* Emoji styles */
|
||||
.ProseMirror .emoji-node {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ProseMirror .emoji-image {
|
||||
height: 1.2em;
|
||||
width: auto;
|
||||
vertical-align: middle;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.ProseMirror .emoji-unicode {
|
||||
font-size: 1.1em;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Nostr event preview styles */
|
||||
.ProseMirror .nostr-event-preview {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.125rem 0.375rem;
|
||||
background-color: hsl(var(--primary) / 0.1);
|
||||
border: 1px solid hsl(var(--primary) / 0.3);
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
vertical-align: middle;
|
||||
cursor: default;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.ProseMirror .nostr-event-preview:hover {
|
||||
background-color: hsl(var(--primary) / 0.15);
|
||||
}
|
||||
|
||||
/* Hide scrollbar in RichEditor */
|
||||
.rich-editor .ProseMirror {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE/Edge */
|
||||
}
|
||||
|
||||
.rich-editor .ProseMirror::-webkit-scrollbar {
|
||||
display: none; /* Chrome/Safari/Opera */
|
||||
}
|
||||
|
||||
150
src/lib/shiki.ts
150
src/lib/shiki.ts
@@ -14,7 +14,7 @@ const loadedLanguages = new Set<string>();
|
||||
* Grimoire dark theme - minimalistic grayscale with semantic colors
|
||||
* Uses muted grays for syntax with color only for diff semantics
|
||||
*/
|
||||
const grimoireTheme: ThemeRegistration = {
|
||||
const grimoireDarkTheme: ThemeRegistration = {
|
||||
name: "grimoire-dark",
|
||||
type: "dark",
|
||||
colors: {
|
||||
@@ -143,6 +143,138 @@ const grimoireTheme: ThemeRegistration = {
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Grimoire light theme - minimalistic grayscale for light backgrounds
|
||||
*/
|
||||
const grimoireLightTheme: ThemeRegistration = {
|
||||
name: "grimoire-light",
|
||||
type: "light",
|
||||
colors: {
|
||||
"editor.background": "#ffffff",
|
||||
"editor.foreground": "#1a1a1a",
|
||||
},
|
||||
tokenColors: [
|
||||
// Comments - muted
|
||||
{
|
||||
scope: ["comment", "punctuation.definition.comment"],
|
||||
settings: { foreground: "#6b7280" },
|
||||
},
|
||||
// Strings - muted but slightly emphasized
|
||||
{
|
||||
scope: ["string", "string.quoted"],
|
||||
settings: { foreground: "#4b5563" },
|
||||
},
|
||||
// Keywords, operators - emphasized dark gray
|
||||
{
|
||||
scope: [
|
||||
"keyword",
|
||||
"storage",
|
||||
"storage.type",
|
||||
"storage.modifier",
|
||||
"keyword.operator",
|
||||
"keyword.control",
|
||||
],
|
||||
settings: { foreground: "#374151" },
|
||||
},
|
||||
// Functions, methods - foreground bold
|
||||
{
|
||||
scope: ["entity.name.function", "support.function", "meta.function-call"],
|
||||
settings: { foreground: "#1a1a1a", fontStyle: "bold" },
|
||||
},
|
||||
// Classes, types - foreground bold
|
||||
{
|
||||
scope: [
|
||||
"entity.name.class",
|
||||
"entity.name.type",
|
||||
"support.class",
|
||||
"support.type",
|
||||
],
|
||||
settings: { foreground: "#1a1a1a", fontStyle: "bold" },
|
||||
},
|
||||
// Numbers, constants - emphasized dark gray
|
||||
{
|
||||
scope: [
|
||||
"constant",
|
||||
"constant.numeric",
|
||||
"constant.language",
|
||||
"constant.character",
|
||||
],
|
||||
settings: { foreground: "#374151" },
|
||||
},
|
||||
// Variables, parameters - foreground
|
||||
{
|
||||
scope: ["variable", "variable.parameter", "variable.other"],
|
||||
settings: { foreground: "#1a1a1a" },
|
||||
},
|
||||
// Punctuation - slightly muted
|
||||
{
|
||||
scope: ["punctuation", "meta.brace"],
|
||||
settings: { foreground: "#4b5563" },
|
||||
},
|
||||
// Properties, attributes
|
||||
{
|
||||
scope: [
|
||||
"variable.other.property",
|
||||
"entity.other.attribute-name",
|
||||
"support.type.property-name",
|
||||
],
|
||||
settings: { foreground: "#374151" },
|
||||
},
|
||||
// Tags (HTML/JSX)
|
||||
{
|
||||
scope: ["entity.name.tag", "support.class.component"],
|
||||
settings: { foreground: "#374151" },
|
||||
},
|
||||
// JSON keys
|
||||
{
|
||||
scope: ["support.type.property-name.json"],
|
||||
settings: { foreground: "#374151" },
|
||||
},
|
||||
// Diff - deleted (red)
|
||||
{
|
||||
scope: [
|
||||
"markup.deleted",
|
||||
"punctuation.definition.deleted",
|
||||
"meta.diff.header.from-file",
|
||||
],
|
||||
settings: { foreground: "#dc2626" },
|
||||
},
|
||||
// Diff - inserted (green)
|
||||
{
|
||||
scope: [
|
||||
"markup.inserted",
|
||||
"punctuation.definition.inserted",
|
||||
"meta.diff.header.to-file",
|
||||
],
|
||||
settings: { foreground: "#16a34a" },
|
||||
},
|
||||
// Diff - changed/range
|
||||
{
|
||||
scope: ["markup.changed", "meta.diff.range", "meta.diff.header"],
|
||||
settings: { foreground: "#0891b2" },
|
||||
},
|
||||
// Markdown headings
|
||||
{
|
||||
scope: ["markup.heading", "entity.name.section"],
|
||||
settings: { foreground: "#1a1a1a", fontStyle: "bold" },
|
||||
},
|
||||
// Markdown bold/italic
|
||||
{
|
||||
scope: ["markup.bold"],
|
||||
settings: { fontStyle: "bold" },
|
||||
},
|
||||
{
|
||||
scope: ["markup.italic"],
|
||||
settings: { fontStyle: "italic" },
|
||||
},
|
||||
// Markdown links
|
||||
{
|
||||
scope: ["markup.underline.link"],
|
||||
settings: { foreground: "#2563eb" },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Language alias mapping (file extensions and common names to Shiki IDs)
|
||||
*/
|
||||
@@ -298,7 +430,7 @@ export async function getHighlighter(): Promise<HighlighterCore> {
|
||||
|
||||
if (!highlighterPromise) {
|
||||
highlighterPromise = createHighlighterCore({
|
||||
themes: [grimoireTheme],
|
||||
themes: [grimoireDarkTheme, grimoireLightTheme],
|
||||
langs: [
|
||||
import("shiki/langs/javascript.mjs"),
|
||||
import("shiki/langs/typescript.mjs"),
|
||||
@@ -342,9 +474,18 @@ async function loadLanguage(lang: string): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if dark mode is currently active
|
||||
*/
|
||||
function isDarkMode(): boolean {
|
||||
if (typeof document === "undefined") return true;
|
||||
return document.documentElement.classList.contains("dark");
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight code with lazy language loading
|
||||
* Returns HTML string
|
||||
* Automatically uses the appropriate theme based on current color scheme
|
||||
*/
|
||||
export async function highlightCode(
|
||||
code: string,
|
||||
@@ -357,9 +498,12 @@ export async function highlightCode(
|
||||
const loaded = await loadLanguage(lang);
|
||||
const effectiveLang = loaded ? lang : "text";
|
||||
|
||||
// Select theme based on current color scheme
|
||||
const theme = isDarkMode() ? "grimoire-dark" : "grimoire-light";
|
||||
|
||||
return hl.codeToHtml(code, {
|
||||
lang: effectiveLang,
|
||||
theme: "grimoire-dark",
|
||||
theme,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user