Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 58 additions & 55 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -644,60 +644,63 @@ body {
}

/* ─── Code-block syntax highlighting (highlight.js tokens, VS Code Dark+) ──── */
.klipcode-md .hljs-comment, .klipcode-md .hljs-quote { color: #6a9955; font-style: italic; }
.klipcode-md .hljs-keyword, .klipcode-md .hljs-selector-tag,
.klipcode-md .hljs-literal, .klipcode-md .hljs-meta .hljs-keyword,
.klipcode-md .hljs-doctag, .klipcode-md .hljs-section { color: #569cd6; }
.klipcode-md .hljs-built_in, .klipcode-md .hljs-class .hljs-title,
.klipcode-md .hljs-title.class_, .klipcode-md .hljs-type { color: #4ec9b0; }
.klipcode-md .hljs-string, .klipcode-md .hljs-regexp,
.klipcode-md .hljs-addition, .klipcode-md .hljs-meta .hljs-string { color: #ce9178; }
.klipcode-md .hljs-number { color: #b5cea8; }
.klipcode-md .hljs-title, .klipcode-md .hljs-title.function_, .klipcode-md .hljs-function .hljs-title { color: #dcdcaa; }
.klipcode-md .hljs-attr, .klipcode-md .hljs-attribute,
.klipcode-md .hljs-variable, .klipcode-md .hljs-template-variable,
.klipcode-md .hljs-property, .klipcode-md .hljs-params, .klipcode-md .hljs-selector-attr { color: #9cdcfe; }
.klipcode-md .hljs-name, .klipcode-md .hljs-tag .hljs-name { color: #569cd6; }
.klipcode-md .hljs-symbol, .klipcode-md .hljs-bullet, .klipcode-md .hljs-link,
.klipcode-md .hljs-meta, .klipcode-md .hljs-selector-id, .klipcode-md .hljs-selector-class { color: #d7ba7d; }
.klipcode-md .hljs-deletion { color: #ce9178; }
.klipcode-md .hljs-emphasis { font-style: italic; }
.klipcode-md .hljs-strong { font-weight: 600; }
/* Shared by the Markdown editor's code blocks (.klipcode-md) and the
snippet-card previews (.klipcode-code-preview) so both stay visually coherent. */
:is(.klipcode-md, .klipcode-code-preview) .hljs-comment,
:is(.klipcode-md, .klipcode-code-preview) .hljs-quote { color: #6a9955; font-style: italic; }
:is(.klipcode-md, .klipcode-code-preview) .hljs-keyword, :is(.klipcode-md, .klipcode-code-preview) .hljs-selector-tag,
:is(.klipcode-md, .klipcode-code-preview) .hljs-literal, :is(.klipcode-md, .klipcode-code-preview) .hljs-meta .hljs-keyword,
:is(.klipcode-md, .klipcode-code-preview) .hljs-doctag, :is(.klipcode-md, .klipcode-code-preview) .hljs-section { color: #569cd6; }
:is(.klipcode-md, .klipcode-code-preview) .hljs-built_in, :is(.klipcode-md, .klipcode-code-preview) .hljs-class .hljs-title,
:is(.klipcode-md, .klipcode-code-preview) .hljs-title.class_, :is(.klipcode-md, .klipcode-code-preview) .hljs-type { color: #4ec9b0; }
:is(.klipcode-md, .klipcode-code-preview) .hljs-string, :is(.klipcode-md, .klipcode-code-preview) .hljs-regexp,
:is(.klipcode-md, .klipcode-code-preview) .hljs-addition, :is(.klipcode-md, .klipcode-code-preview) .hljs-meta .hljs-string { color: #ce9178; }
:is(.klipcode-md, .klipcode-code-preview) .hljs-number { color: #b5cea8; }
:is(.klipcode-md, .klipcode-code-preview) .hljs-title, :is(.klipcode-md, .klipcode-code-preview) .hljs-title.function_, :is(.klipcode-md, .klipcode-code-preview) .hljs-function .hljs-title { color: #dcdcaa; }
:is(.klipcode-md, .klipcode-code-preview) .hljs-attr, :is(.klipcode-md, .klipcode-code-preview) .hljs-attribute,
:is(.klipcode-md, .klipcode-code-preview) .hljs-variable, :is(.klipcode-md, .klipcode-code-preview) .hljs-template-variable,
:is(.klipcode-md, .klipcode-code-preview) .hljs-property, :is(.klipcode-md, .klipcode-code-preview) .hljs-params, :is(.klipcode-md, .klipcode-code-preview) .hljs-selector-attr { color: #9cdcfe; }
:is(.klipcode-md, .klipcode-code-preview) .hljs-name, :is(.klipcode-md, .klipcode-code-preview) .hljs-tag .hljs-name { color: #569cd6; }
:is(.klipcode-md, .klipcode-code-preview) .hljs-symbol, :is(.klipcode-md, .klipcode-code-preview) .hljs-bullet, :is(.klipcode-md, .klipcode-code-preview) .hljs-link,
:is(.klipcode-md, .klipcode-code-preview) .hljs-meta, :is(.klipcode-md, .klipcode-code-preview) .hljs-selector-id, :is(.klipcode-md, .klipcode-code-preview) .hljs-selector-class { color: #d7ba7d; }
:is(.klipcode-md, .klipcode-code-preview) .hljs-deletion { color: #ce9178; }
:is(.klipcode-md, .klipcode-code-preview) .hljs-emphasis { font-style: italic; }
:is(.klipcode-md, .klipcode-code-preview) .hljs-strong { font-weight: 600; }

/* Light-theme token overrides (VS Code Light+), for legibility on the white surface. */
:root[data-theme="light"] .klipcode-md .hljs-comment,
:root[data-theme="light"] .klipcode-md .hljs-quote { color: #008000; }
:root[data-theme="light"] .klipcode-md .hljs-keyword,
:root[data-theme="light"] .klipcode-md .hljs-selector-tag,
:root[data-theme="light"] .klipcode-md .hljs-literal,
:root[data-theme="light"] .klipcode-md .hljs-meta .hljs-keyword,
:root[data-theme="light"] .klipcode-md .hljs-doctag,
:root[data-theme="light"] .klipcode-md .hljs-section,
:root[data-theme="light"] .klipcode-md .hljs-name,
:root[data-theme="light"] .klipcode-md .hljs-tag .hljs-name { color: #0000ff; }
:root[data-theme="light"] .klipcode-md .hljs-built_in,
:root[data-theme="light"] .klipcode-md .hljs-class .hljs-title,
:root[data-theme="light"] .klipcode-md .hljs-title.class_,
:root[data-theme="light"] .klipcode-md .hljs-type { color: #267f99; }
:root[data-theme="light"] .klipcode-md .hljs-string,
:root[data-theme="light"] .klipcode-md .hljs-regexp,
:root[data-theme="light"] .klipcode-md .hljs-addition,
:root[data-theme="light"] .klipcode-md .hljs-meta .hljs-string { color: #a31515; }
:root[data-theme="light"] .klipcode-md .hljs-number { color: #098658; }
:root[data-theme="light"] .klipcode-md .hljs-title,
:root[data-theme="light"] .klipcode-md .hljs-title.function_,
:root[data-theme="light"] .klipcode-md .hljs-function .hljs-title { color: #795e26; }
:root[data-theme="light"] .klipcode-md .hljs-attr,
:root[data-theme="light"] .klipcode-md .hljs-attribute,
:root[data-theme="light"] .klipcode-md .hljs-variable,
:root[data-theme="light"] .klipcode-md .hljs-template-variable,
:root[data-theme="light"] .klipcode-md .hljs-property,
:root[data-theme="light"] .klipcode-md .hljs-params,
:root[data-theme="light"] .klipcode-md .hljs-selector-attr { color: #001080; }
:root[data-theme="light"] .klipcode-md .hljs-symbol,
:root[data-theme="light"] .klipcode-md .hljs-bullet,
:root[data-theme="light"] .klipcode-md .hljs-link,
:root[data-theme="light"] .klipcode-md .hljs-meta,
:root[data-theme="light"] .klipcode-md .hljs-selector-id,
:root[data-theme="light"] .klipcode-md .hljs-selector-class { color: #0070c1; }
:root[data-theme="light"] .klipcode-md .hljs-deletion { color: #a31515; }
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-comment,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-quote { color: #008000; }
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-keyword,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-selector-tag,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-literal,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-meta .hljs-keyword,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-doctag,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-section,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-name,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-tag .hljs-name { color: #0000ff; }
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-built_in,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-class .hljs-title,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-title.class_,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-type { color: #267f99; }
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-string,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-regexp,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-addition,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-meta .hljs-string { color: #a31515; }
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-number { color: #098658; }
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-title,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-title.function_,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-function .hljs-title { color: #795e26; }
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-attr,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-attribute,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-variable,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-template-variable,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-property,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-params,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-selector-attr { color: #001080; }
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-symbol,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-bullet,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-link,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-meta,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-selector-id,
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-selector-class { color: #0070c1; }
:root[data-theme="light"] :is(.klipcode-md, .klipcode-code-preview) .hljs-deletion { color: #a31515; }
44 changes: 25 additions & 19 deletions src/components/SnippetCards/SnippetCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,7 @@ import { suppressModifierDragStart } from "@/hooks/useMultiSelection";
import { LanguageIcon } from "@/ui/LanguageIcon";
import { Tooltip, TruncateTooltip } from "@/ui/Tooltip";
import { GeneratingTitle, useIsGeneratingTitle } from "@/components/TitleGeneration";

function buildPreviewLines(code: string) {
const lines = code.split("\n").slice(0, 8);

if (lines.length === 0) {
return [""];
}

return lines.map((line) => (line.length > 92 ? `${line.slice(0, 92)}…` : line));
}
import { buildPreviewLines, useHighlightedPreview } from "./snippetPreview";

interface SnippetCardProps {
snippet: SnippetRecord;
Expand Down Expand Up @@ -255,6 +246,9 @@ export function SnippetCard({
const displayName = getSnippetDisplayName(snippet.title, snippet.language, copy.snippetCard.untitled);
const isGeneratingTitle = useIsGeneratingTitle(snippet.id);
const previewLines = buildPreviewLines(snippet.code);
// Syntax-highlighted preview when the language is supported; `null` while the
// grammars load (or when unsupported), where we fall back to plain lines.
const highlightedLines = useHighlightedPreview(snippet.code, snippet.language);

return (
<article
Expand Down Expand Up @@ -378,15 +372,27 @@ export function SnippetCard({

<div className="pointer-events-none relative overflow-hidden px-1 pb-1">
<div className="max-h-[140px] overflow-hidden rounded-lg border border-ink/[0.04] bg-[var(--code-surface)] px-3 py-2 font-mono text-[12px] leading-5 text-ink/90">
<div className="pointer-events-none select-none text-ink/60">
{previewLines.map((line, index) => (
<div key={`${snippet.id}-${index}`} className="flex gap-3">
<span className="w-5 shrink-0 text-right tabular-nums text-ink/25">
{index + 1}
</span>
<span className="min-w-0 flex-1 truncate whitespace-pre">{line || " "}</span>
</div>
))}
<div
className={cn(
"pointer-events-none select-none text-ink/60",
// Reuse the Markdown editor's hljs token colors, kept slightly
// muted so the preview keeps its subdued, faded look.
highlightedLines && "klipcode-code-preview opacity-[0.85]",
)}
>
{previewLines.map((line, index) => {
const highlighted = highlightedLines?.[index];
return (
<div key={`${snippet.id}-${index}`} className="flex gap-3">
<span className="w-5 shrink-0 text-right tabular-nums text-ink/25">
{index + 1}
</span>
<span className="min-w-0 flex-1 truncate whitespace-pre">
{highlighted && highlighted.length > 0 ? highlighted : line || " "}
</span>
</div>
);
})}
</div>
</div>
<div className="pointer-events-none absolute bottom-1 left-1 right-1 h-12 rounded-b-lg bg-gradient-to-t from-surface to-transparent group-hover:from-surface-hover" />
Expand Down
Binary file added src/components/SnippetCards/snippetPreview.tsx
Binary file not shown.
Loading