snapshot
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
"bun-plugin-tailwind": "^0.1.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"highlight.js": "^11.11.1",
|
||||
"lucide-react": "^0.545.0",
|
||||
"marked": "^18.0.0",
|
||||
"react": "^19",
|
||||
@@ -144,6 +145,8 @@
|
||||
|
||||
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
|
||||
|
||||
"highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="],
|
||||
|
||||
"lucide-react": ["lucide-react@0.545.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-7r1/yUuflQDSt4f1bpn5ZAocyIxcTyVyBBChSVtBKn5M+392cPmI5YJMWOJKk/HUWGm5wg83chlAZtCcGbEZtw=="],
|
||||
|
||||
"marked": ["marked@18.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-2e7Qiv/HJSXj8rDEpgTvGKsP8yYtI9xXHKDnrftrmnrJPaFNM7VRb2YCzWaX4BP1iCJ/XPduzDJZMFoqTCcIMA=="],
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"bun-plugin-tailwind": "^0.1.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"highlight.js": "^11.11.1",
|
||||
"lucide-react": "^0.545.0",
|
||||
"marked": "^18.0.0",
|
||||
"react": "^19",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NotesApp } from "@/features/notes/NotesApp";
|
||||
import "highlight.js/styles/github.css";
|
||||
import "./index.css";
|
||||
|
||||
export function App() {
|
||||
|
||||
Binary file not shown.
@@ -157,7 +157,7 @@ export function SnippetCard({ snippet, treeColorByName, treeDisplayByName, onRep
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-row flex-wrap items-center gap-2 border-t border-border/80 px-3 py-1">
|
||||
<div className="flex min-h-7 min-w-0 flex-1 flex-wrap items-center gap-1">
|
||||
<div className="snippet-tag-cloud flex min-h-7 min-w-0 flex-1 flex-wrap items-center gap-1.5">
|
||||
{snippet.tags.length ? (
|
||||
snippet.tags.map((t, i) => (
|
||||
<TagPill
|
||||
|
||||
@@ -117,7 +117,7 @@ export function SnippetEditDialog({
|
||||
onPick={name => applyTagSuggestion(name)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex min-h-[1.5rem] flex-wrap gap-1.5">
|
||||
<div className="snippet-tag-cloud flex min-h-[1.5rem] flex-wrap gap-1.5">
|
||||
{tagsFromString(tagsStr).map((t, i) => (
|
||||
<TagPill
|
||||
key={`${i}-${t}`}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { Hash } from "lucide-react";
|
||||
import type { CSSProperties } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type Props = {
|
||||
tagKey: string;
|
||||
label: string;
|
||||
@@ -8,22 +12,34 @@ export function TagPill({ tagKey, label, hex }: Props) {
|
||||
const has = Boolean(hex && /^#[0-9A-Fa-f]{6}$/i.test(hex));
|
||||
return (
|
||||
<span
|
||||
className={
|
||||
className={cn(
|
||||
"tag-pill inline-flex max-w-full items-center gap-1 rounded-lg border px-2 py-0.5 text-[11px] font-medium leading-tight shadow-sm ring-1 ring-black/[0.04] transition-[box-shadow,transform,background-color] duration-150 dark:ring-white/[0.06]",
|
||||
has
|
||||
? "inline-flex max-w-full items-center rounded-full border-2 px-1.5 py-px text-[11px] font-medium leading-tight"
|
||||
: "inline-flex max-w-full items-center rounded-full border-2 border-border bg-muted/40 px-1.5 py-px text-[11px] leading-tight text-muted-foreground"
|
||||
}
|
||||
? "shadow-[0_1px_2px_rgba(0,0,0,0.06)] dark:shadow-[0_1px_2px_rgba(0,0,0,0.35)]"
|
||||
: "border-border/70 bg-gradient-to-b from-muted/90 to-muted/45 text-muted-foreground dark:from-muted/50 dark:to-muted/25",
|
||||
)}
|
||||
style={
|
||||
has
|
||||
? {
|
||||
borderColor: hex!,
|
||||
backgroundColor: `${hex}33`,
|
||||
}
|
||||
? ({
|
||||
borderColor: `${hex}cc`,
|
||||
background: `linear-gradient(165deg, color-mix(in oklch, ${hex} 28%, transparent) 0%, color-mix(in oklch, ${hex} 12%, transparent) 100%)`,
|
||||
boxShadow: `0 1px 2px color-mix(in oklch, ${hex} 35%, transparent), inset 0 1px 0 color-mix(in oklch, white 22%, transparent)`,
|
||||
color: `color-mix(in oklch, ${hex} 12%, var(--foreground))`,
|
||||
} as CSSProperties)
|
||||
: undefined
|
||||
}
|
||||
title={tagKey === label ? tagKey : `${label} (${tagKey})`}
|
||||
>
|
||||
<span className="truncate">{label}</span>
|
||||
{has ? (
|
||||
<span
|
||||
className="size-1.5 shrink-0 rounded-full ring-1 ring-black/10 dark:ring-white/15"
|
||||
style={{ backgroundColor: hex! }}
|
||||
aria-hidden
|
||||
/>
|
||||
) : (
|
||||
<Hash className="size-2.5 shrink-0 opacity-55" aria-hidden />
|
||||
)}
|
||||
<span className="min-w-0 truncate">{label}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,69 @@
|
||||
import hljs from "highlight.js/lib/common";
|
||||
import { marked } from "marked";
|
||||
|
||||
marked.setOptions({ gfm: true });
|
||||
|
||||
/** Markdown-Sprachkennungen → highlight.js-Name */
|
||||
const LANG_ALIASES: Record<string, string> = {
|
||||
ts: "typescript",
|
||||
tsx: "typescript",
|
||||
js: "javascript",
|
||||
mjs: "javascript",
|
||||
cjs: "javascript",
|
||||
sh: "bash",
|
||||
zsh: "bash",
|
||||
yml: "yaml",
|
||||
md: "markdown",
|
||||
rs: "rust",
|
||||
py: "python",
|
||||
rb: "ruby",
|
||||
kt: "kotlin",
|
||||
kts: "kotlin",
|
||||
text: "plaintext",
|
||||
txt: "plaintext",
|
||||
};
|
||||
|
||||
function resolveLang(raw: string | undefined): string {
|
||||
const t = (raw ?? "").trim().toLowerCase();
|
||||
if (!t) return "plaintext";
|
||||
return LANG_ALIASES[t] ?? t;
|
||||
}
|
||||
|
||||
function langClassSegment(lang: string): string {
|
||||
return lang.replace(/[^a-z0-9_-]/gi, "-") || "plaintext";
|
||||
}
|
||||
|
||||
function escapeHtml(s: string): string {
|
||||
return s
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
let hljsConfigured = false;
|
||||
|
||||
function ensureMarkedHighlighter(): void {
|
||||
if (hljsConfigured) return;
|
||||
hljsConfigured = true;
|
||||
marked.use({
|
||||
renderer: {
|
||||
code({ text, lang }) {
|
||||
const key = resolveLang(lang);
|
||||
const use = hljs.getLanguage(key) ? key : "plaintext";
|
||||
try {
|
||||
const highlighted = hljs.highlight(text, { language: use, ignoreIllegals: true }).value;
|
||||
return `<pre class="snippet-pre"><code class="hljs language-${langClassSegment(use)}">${highlighted}</code></pre>\n`;
|
||||
} catch {
|
||||
return `<pre class="snippet-pre"><code class="hljs language-plaintext">${escapeHtml(text)}</code></pre>\n`;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
ensureMarkedHighlighter();
|
||||
|
||||
export function parseSnippetMarkdown(md: string): string {
|
||||
return marked.parse(md) as string;
|
||||
}
|
||||
|
||||
+69
-13
@@ -10,6 +10,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('./assets/fonts/Inter-Variable.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
|
||||
/* cool Bun background animation 😎 */
|
||||
body::before {
|
||||
content: "";
|
||||
@@ -53,8 +62,8 @@ body::before {
|
||||
/* Markdown rendering (marked HTML) */
|
||||
@layer components {
|
||||
.snippet-md {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.625;
|
||||
font-size: 0.8rem;
|
||||
font-family: 'Inter';
|
||||
}
|
||||
|
||||
.snippet-md :where(p) {
|
||||
@@ -79,14 +88,14 @@ body::before {
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.snippet-md :where(code) {
|
||||
.snippet-md :where(code:not(.hljs)) {
|
||||
border-radius: var(--radius-sm, 0.25rem);
|
||||
background-color: var(--muted);
|
||||
padding: 0.125rem 0.25rem;
|
||||
font-size: 0.875em;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.snippet-md :where(pre) {
|
||||
.snippet-md :where(pre:not(.snippet-pre)) {
|
||||
margin-block: 0.5rem;
|
||||
overflow-x: auto;
|
||||
border-radius: var(--radius-md, 0.375rem);
|
||||
@@ -94,27 +103,69 @@ body::before {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.snippet-md :where(pre code) {
|
||||
.snippet-md :where(pre:not(.snippet-pre) code) {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
font-size: 0.8125rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Highlight.js Codeblöcke */
|
||||
.snippet-md :where(pre.snippet-pre) {
|
||||
margin-block: 0.5rem;
|
||||
overflow-x: auto;
|
||||
border-radius: var(--radius-md, 0.375rem);
|
||||
border: 1px solid var(--border);
|
||||
background-color: color-mix(in oklch, var(--muted) 88%, var(--background));
|
||||
padding: 0.65rem 0.75rem;
|
||||
box-shadow: inset 0 1px 0 color-mix(in oklch, white 55%, transparent);
|
||||
}
|
||||
|
||||
.dark .snippet-md :where(pre.snippet-pre) {
|
||||
box-shadow: inset 0 1px 0 color-mix(in oklch, white 8%, transparent);
|
||||
}
|
||||
|
||||
.snippet-md :where(pre.snippet-pre code.hljs) {
|
||||
display: block;
|
||||
width: max-content;
|
||||
min-width: 100%;
|
||||
padding: 0;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.55;
|
||||
tab-size: 2;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.snippet-tag-cloud {
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid color-mix(in oklch, var(--border) 85%, transparent);
|
||||
background: linear-gradient(
|
||||
165deg,
|
||||
color-mix(in oklch, var(--muted) 55%, transparent) 0%,
|
||||
color-mix(in oklch, var(--muted) 22%, transparent) 100%
|
||||
);
|
||||
padding: 0.4rem 0.5rem;
|
||||
box-shadow: inset 0 1px 0 color-mix(in oklch, white 40%, transparent);
|
||||
}
|
||||
|
||||
.dark .snippet-tag-cloud {
|
||||
box-shadow: inset 0 1px 0 color-mix(in oklch, white 6%, transparent);
|
||||
}
|
||||
|
||||
.snippet-md :where(h1) {
|
||||
margin-block: 0 0.5rem;
|
||||
font-size: 1.25rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.snippet-md :where(h2) {
|
||||
margin-block: 0.75rem 0.5rem;
|
||||
font-size: 1.125rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.snippet-md :where(h3) {
|
||||
margin-block: 0.5rem 0.25rem;
|
||||
font-size: 1rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -140,7 +191,7 @@ body::before {
|
||||
.snippet-md :where(table) {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.8125rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.snippet-md :where(th, td) {
|
||||
@@ -161,7 +212,7 @@ body::before {
|
||||
|
||||
/* Snippet-Karten: etwas dichter, Fokus auf Fließtext */
|
||||
.snippet-md-card-wrap .snippet-md {
|
||||
font-size: 0.8125rem;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
@@ -182,11 +233,16 @@ body::before {
|
||||
margin-block-end: 0.25rem;
|
||||
}
|
||||
|
||||
.snippet-md-card-wrap .snippet-md :where(pre) {
|
||||
.snippet-md-card-wrap .snippet-md :where(pre:not(.snippet-pre)) {
|
||||
margin-block: 0.375rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.snippet-md-card-wrap .snippet-md :where(pre.snippet-pre) {
|
||||
margin-block: 0.375rem;
|
||||
padding: 0.45rem 0.55rem;
|
||||
}
|
||||
|
||||
/* App dialogs: shared layout (Tailwind here, short class names in TSX) */
|
||||
.dialog-snippet-header {
|
||||
@apply shrink-0 space-y-1 border-b px-5 pb-3 pt-5 pr-12 text-start sm:px-6 sm:pr-14;
|
||||
|
||||
Reference in New Issue
Block a user