mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-06-27 17:25:43 -04:00
add language indicator to code blocks
This commit is contained in:
@ -176,7 +176,5 @@
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.youtube-embed {
|
||||
@import "react-lite-youtube-embed/dist/LiteYouTubeEmbed.css";
|
||||
}
|
||||
@import "react-lite-youtube-embed/dist/LiteYouTubeEmbed.css";
|
||||
}
|
||||
|
@ -11,9 +11,9 @@ export const metadata = createMetadata({
|
||||
|
||||
export const WarningMarquee = () => (
|
||||
<Marquee>
|
||||
<span>
|
||||
<span className="leading-none select-none">
|
||||
🚨 Trigger warning: excessive marquees, animated GIFs, Comic Sans, popups,{" "}
|
||||
<code style={{ fontWeight: "normal", fontSize: "0.9em" }}>
|
||||
<code className="text-[0.9rem] font-normal">
|
||||
color: <span style={{ color: "#32cd32" }}>limegreen</span>
|
||||
</code>{" "}
|
||||
ahead...
|
||||
@ -23,7 +23,8 @@ export const WarningMarquee = () => (
|
||||
|
||||
export const PageStyles = () => (
|
||||
<style>
|
||||
{`body {
|
||||
{`
|
||||
body {
|
||||
cursor: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAZklEQVR4AWIAgn/uBT6A9uoAAwAQiIJo97/0Rgy0ANoJH8MPeEgtqwPQEACqCoQHAKECQKgAECoAhAoAoQJAqAAQxh1oPQfcW3kJpxHtL1AAHAwEwwdYiH8BIEgBTBRAAAEEEEAAG7mRt30hEhoLAAAAAElFTkSuQmCC") 2 1, auto;
|
||||
}
|
||||
a, button {
|
||||
@ -78,8 +79,7 @@ _Previously on the [Cringey Chronicles™](https://web.archive.org/web/20010
|
||||
<iframe
|
||||
src="https://jakejarvis.github.io/my-first-website/"
|
||||
title="My Terrible, Horrible, No Good, Very Bad First Website"
|
||||
className="border-ring w-full border-2"
|
||||
style={{ height: "500px" }}
|
||||
className="border-ring h-[500px] w-full border-2"
|
||||
/>
|
||||
_[November 2001](https://jakejarvis.github.io/my-first-website/) ([view
|
||||
source](https://github.com/jakejarvis/my-first-website))_
|
||||
|
@ -14,9 +14,7 @@ export const Terminal = () => (
|
||||
<div
|
||||
className="relative mx-auto my-6 w-full rounded-lg"
|
||||
style={{
|
||||
backgroundImage: `url(${backgroundImg.src})`,
|
||||
backgroundRepeat: "repeat",
|
||||
backgroundPosition: "center",
|
||||
background: `url(${backgroundImg.src}) center no-repeat`,
|
||||
}}
|
||||
>
|
||||
<code className="border-ring block rounded-lg border border-solid bg-black/60 p-4 text-sm break-all text-white/90 backdrop-blur-xs backdrop-saturate-150">
|
||||
|
@ -1,16 +1,19 @@
|
||||
import { codeToHtml } from "shiki";
|
||||
import reactToText from "react-to-text";
|
||||
import { CodeIcon } from "lucide-react";
|
||||
import CopyButton from "@/components/copy-button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { ComponentProps, ComponentPropsWithoutRef } from "react";
|
||||
|
||||
const CodeBlock = async ({
|
||||
lineNumbers = false,
|
||||
showLineNumbers = false,
|
||||
showCopyButton = true,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: ComponentPropsWithoutRef<"pre"> & {
|
||||
lineNumbers?: boolean;
|
||||
showLineNumbers?: boolean;
|
||||
showCopyButton?: boolean;
|
||||
}) => {
|
||||
// escape hatch if this code wasn't meant to be highlighted
|
||||
if (!children || typeof children !== "object" || !("props" in children)) {
|
||||
@ -35,22 +38,57 @@ const CodeBlock = async ({
|
||||
},
|
||||
});
|
||||
|
||||
const getFullLang = (lang: string) => {
|
||||
// replace the file extension with the full language name when it makes sense to
|
||||
switch (lang.toLowerCase()) {
|
||||
case "js":
|
||||
return "JavaScript";
|
||||
case "jsx":
|
||||
return "JavaScript (JSX)";
|
||||
case "ts":
|
||||
return "TypeScript";
|
||||
case "tsx":
|
||||
return "TypeScript (JSX)";
|
||||
case "sh":
|
||||
case "bash":
|
||||
case "zsh":
|
||||
return "Shell";
|
||||
case "py":
|
||||
return "Python";
|
||||
case "md":
|
||||
return "Markdown";
|
||||
case "mdx":
|
||||
return "Markdown (MDX)";
|
||||
default:
|
||||
return lang;
|
||||
}
|
||||
};
|
||||
|
||||
const fullLang = getFullLang(lang);
|
||||
|
||||
return (
|
||||
<div className={cn("bg-muted/35 relative isolate rounded-lg border-2 font-mono", className)}>
|
||||
<div
|
||||
className={cn(
|
||||
"grid max-h-[500px] w-full overflow-x-auto p-4 **:bg-transparent! md:max-h-[650px] dark:**:text-[var(--shiki-dark)]! [&_pre]:whitespace-normal",
|
||||
"grid max-h-[500px] w-full overflow-x-auto p-4 **:bg-transparent! data-language:pt-9 md:max-h-[650px] dark:**:text-[var(--shiki-dark)]! [&_pre]:whitespace-normal",
|
||||
"[&_.line]:inline-block [&_.line]:min-w-full [&_.line]:py-1 [&_.line]:leading-none [&_.line]:whitespace-pre [&_.line]:after:hidden",
|
||||
"data-line-numbers:[&_.line]:before:text-muted-foreground data-line-numbers:[counter-reset:line] data-line-numbers:[&_.line]:[counter-increment:line] data-line-numbers:[&_.line]:before:mr-5 data-line-numbers:[&_.line]:before:inline-block data-line-numbers:[&_.line]:before:w-5 data-line-numbers:[&_.line]:before:text-right data-line-numbers:[&_.line]:before:content-[counter(line)]"
|
||||
)}
|
||||
data-language={lang}
|
||||
data-line-numbers={lineNumbers || undefined}
|
||||
data-language={lang || undefined}
|
||||
data-line-numbers={showLineNumbers || undefined}
|
||||
dangerouslySetInnerHTML={{ __html: codeHighlighted }}
|
||||
/>
|
||||
<CopyButton
|
||||
source={codeString}
|
||||
className="text-foreground/85 hover:text-primary bg-muted/10 absolute top-0 right-0 size-10 rounded-tr-lg rounded-bl-lg border-b-2 border-l-2 p-0 backdrop-blur-xs [&_svg]:my-auto [&_svg]:inline-block [&_svg]:size-4.5 [&_svg]:align-text-bottom"
|
||||
/>
|
||||
{fullLang && (
|
||||
<span className="text-foreground/75 bg-muted/40 absolute top-0 left-0 flex items-center rounded-tl-md rounded-br-lg border-r-2 border-b-2 py-[5px] pr-[10px] font-mono text-xs font-medium tracking-wide uppercase backdrop-blur-xs select-none">
|
||||
<CodeIcon className="stroke-primary/90 mr-[8px] ml-[10px] inline-block size-[14px]" /> <span>{fullLang}</span>
|
||||
</span>
|
||||
)}
|
||||
{showCopyButton && (
|
||||
<CopyButton
|
||||
source={codeString}
|
||||
className="text-foreground/75 hover:text-primary bg-muted/40 absolute top-0 right-0 size-10 rounded-tr-md rounded-bl-lg border-b-2 border-l-2 p-0 backdrop-blur-xs select-none [&_svg]:my-auto [&_svg]:inline-block [&_svg]:size-4.5 [&_svg]:align-text-bottom"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -3,13 +3,14 @@
|
||||
import { forwardRef, useState, useEffect } from "react";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { ClipboardIcon, CheckIcon } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { Ref, ComponentPropsWithoutRef, ComponentRef, MouseEventHandler } from "react";
|
||||
|
||||
const CopyButton = (
|
||||
{
|
||||
source,
|
||||
timeout = 2000,
|
||||
style,
|
||||
className,
|
||||
...rest
|
||||
}: ComponentPropsWithoutRef<"button"> & {
|
||||
source: string;
|
||||
@ -51,7 +52,7 @@ const CopyButton = (
|
||||
ref={ref}
|
||||
onClick={handleCopy}
|
||||
disabled={copied}
|
||||
style={{ cursor: copied ? "default" : "pointer", ...style }}
|
||||
className={cn("cursor-pointer disabled:cursor-default", className)}
|
||||
{...rest}
|
||||
>
|
||||
{copied ? <CheckIcon className="stroke-success" /> : <ClipboardIcon />}
|
||||
|
11
components/third-party/youtube.tsx
vendored
11
components/third-party/youtube.tsx
vendored
@ -3,15 +3,10 @@
|
||||
import YouTubeEmbed from "react-lite-youtube-embed";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
// lite-youtube-embed CSS is imported in app/global.css to save a request
|
||||
|
||||
const YouTube = ({ ...rest }: Omit<ComponentPropsWithoutRef<typeof YouTubeEmbed>, "title">) => {
|
||||
return (
|
||||
<div
|
||||
// lite-youtube-embed CSS is imported in app/global.css to save a request
|
||||
className="youtube-embed"
|
||||
>
|
||||
<YouTubeEmbed cookie={false} containerElement="div" title="" {...rest} />
|
||||
</div>
|
||||
);
|
||||
return <YouTubeEmbed cookie={false} containerElement="div" title="" {...rest} />;
|
||||
};
|
||||
|
||||
export default YouTube;
|
||||
|
11
lib/posts.ts
11
lib/posts.ts
@ -127,9 +127,12 @@ export const getViews: {
|
||||
slug?: any
|
||||
): // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
Promise<any> => {
|
||||
// ensure the prefix is consistent for all keys in the KV store
|
||||
const KEY_PREFIX = `hits:${POSTS_DIR}/`;
|
||||
|
||||
if (typeof slug === "string") {
|
||||
try {
|
||||
const views = await kv.get<string>(`hits:${POSTS_DIR}/${slug}`);
|
||||
const views = await kv.get<string>(`${KEY_PREFIX}${slug}`);
|
||||
|
||||
return views ? parseInt(views, 10) : undefined;
|
||||
} catch (error) {
|
||||
@ -144,12 +147,10 @@ export const getViews: {
|
||||
const pages: Record<string, number> = {};
|
||||
|
||||
// get the value (number of views) for each key (the slug of the page)
|
||||
const values = await kv.mget<string[]>(...allSlugs.map((slug) => `hits:${POSTS_DIR}/${slug}`));
|
||||
const values = await kv.mget<string[]>(...allSlugs.map((slug) => `${KEY_PREFIX}${slug}`));
|
||||
|
||||
// pair the slugs with their view counts
|
||||
allSlugs.forEach(
|
||||
(slug, index) => (pages[slug.replace(`hits:${POSTS_DIR}/`, "")] = parseInt(values[index], 10))
|
||||
);
|
||||
allSlugs.forEach((slug, index) => (pages[slug.replace(KEY_PREFIX, "")] = parseInt(values[index], 10)));
|
||||
|
||||
return pages;
|
||||
} catch (error) {
|
||||
|
@ -72,7 +72,7 @@ export const useMDXComponents = (components: MDXComponents): MDXComponents => {
|
||||
h3: ({ className, id, children, ...rest }) => (
|
||||
<h3
|
||||
className={cn(
|
||||
"group mt-6 mb-2.5 scroll-m-4 text-lg leading-relaxed font-semibold md:text-xl [&_code]:text-[0.9em] [&_strong]:font-bold [&+*]:mt-0",
|
||||
"group mt-6 mb-4 scroll-m-4 text-lg leading-relaxed font-semibold md:text-xl [&_code]:text-[0.9em] [&_strong]:font-bold [&+*]:mt-0",
|
||||
className
|
||||
)}
|
||||
id={id}
|
||||
|
@ -24,7 +24,7 @@ Below are the code snippets you can grab and customize to make your own ["waving
|
||||
## CSS
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```css lineNumbers
|
||||
```css showLineNumbers
|
||||
.wave {
|
||||
animation-name: wave-animation; /* Refers to the name of your @keyframes element below */
|
||||
animation-duration: 2.5s; /* Change to speed up or slow down */
|
||||
|
@ -34,8 +34,7 @@ I've written a simple implementation below, which...
|
||||
<iframe
|
||||
src="https://jakejarvis.github.io/dark-mode-example/"
|
||||
title="Dark Mode Example"
|
||||
className="border-ring w-full border-2"
|
||||
style={{ height: "190px" }}
|
||||
className="border-ring h-[190px] w-full border-2"
|
||||
></iframe>
|
||||
|
||||
A _very_ barebones example is embedded above ([view the source here](https://github.com/jakejarvis/dark-mode-example), or [open in a new window](https://jakejarvis.github.io/dark-mode-example/) if your browser is blocking the frame) and you can try it out on this site by clicking the 💡 lightbulb in the upper right corner of this page. You'll notice that the dark theme sticks when refreshing this page, navigating between other pages, or if you were to return to this example weeks from now.
|
||||
@ -46,7 +45,7 @@ A _very_ barebones example is embedded above ([view the source here](https://git
|
||||
|
||||
I have cleaned up this code a bit, added a few features, and packaged it as an [📦 NPM module](https://www.npmjs.com/package/dark-mode-switcheroo) (zero dependencies and still [only ~500 bytes](https://bundlephobia.com/package/dark-mode-switcheroo) minified and gzipped!). Here's a small snippet of the updated method for the browser (pulling the module from [UNPKG](https://unpkg.com/browse/dark-mode-switcheroo/)), but definitely [read the readme](https://github.com/jakejarvis/dark-mode#readme) for much more detail on the API.
|
||||
|
||||
```html lineNumbers
|
||||
```html showLineNumbers
|
||||
<button class="dark-mode-toggle" style="visibility: hidden;">💡 Click to see the light... or not.</button>
|
||||
|
||||
<script src="https://unpkg.com/dark-mode-switcheroo/dist/dark-mode.min.js"></script>
|
||||
@ -94,7 +93,7 @@ The [example HTML and CSS below](#html-css) is still helpful for reference.
|
||||
### Full JS:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```js lineNumbers
|
||||
```js showLineNumbers
|
||||
/*! Dark mode switcheroo | MIT License | jrvs.io/darkmode */
|
||||
|
||||
(function () {
|
||||
@ -177,7 +176,7 @@ The [example HTML and CSS below](#html-css) is still helpful for reference.
|
||||
### HTML & CSS Example:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```html lineNumbers
|
||||
```html showLineNumbers
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
|
@ -102,7 +102,7 @@ I removed the company's name because an important part of responsible _disclosur
|
||||
|
||||
The `poc-d4ca9e8ceb.html` proof-of-concept file contained this single, hidden line:
|
||||
|
||||
```html lineNumbers
|
||||
```html showLineNumbers
|
||||
<!-- subdomain takeover POC by @jakejarvis on Bugcrowd -->
|
||||
```
|
||||
|
||||
|
@ -43,7 +43,7 @@ If you're bored on a rainy day, potential activities could include:
|
||||
|
||||
Who cares if somebody wants to delete a post with the ID "`*`" no matter the author? ([delete_reply_submit.php](https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/delete_reply_submit.php#L9))
|
||||
|
||||
```php lineNumbers
|
||||
```php showLineNumbers
|
||||
<?php
|
||||
$query2 = "DELETE FROM jbb_replies
|
||||
WHERE replyID ='$replyID'";
|
||||
@ -54,7 +54,7 @@ $result2 = mysql_query ($query2)
|
||||
|
||||
Sessions based on storing an auto-incremented user ID in a cookie. ([login_submit.php](https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/login_submit.php#L28))
|
||||
|
||||
```php lineNumbers
|
||||
```php showLineNumbers
|
||||
<?php
|
||||
session_id($user->userID);
|
||||
session_start();
|
||||
@ -66,7 +66,7 @@ $_SESSION["ck_groupID"] = $user->groupID;
|
||||
|
||||
Viewing a "private" message based solely on a sequential message ID. ([pm_view.php](https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/pm_view.php#L13))
|
||||
|
||||
```php lineNumbers
|
||||
```php showLineNumbers
|
||||
<?php
|
||||
$query1 = "SELECT * FROM jbb_pm WHERE pmID = '$pmID'";
|
||||
?>
|
||||
@ -74,7 +74,7 @@ $query1 = "SELECT * FROM jbb_pm WHERE pmID = '$pmID'";
|
||||
|
||||
Incredibly ambitious emoticon and [BBCode](https://en.wikipedia.org/wiki/BBCode) support. I honestly can't begin to explain this logic. ([functions.php](https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/functions.php#L18))
|
||||
|
||||
```php lineNumbers
|
||||
```php showLineNumbers
|
||||
<?php
|
||||
$replacement = '<img src=images/emoticons/smile.gif>';
|
||||
$replacement2 = '<img src=images/emoticons/bigsmile.gif>';
|
||||
@ -111,7 +111,7 @@ $topicval = str_replace('
|
||||
|
||||
Saving new passwords as plaintext — probably the least problematic problem. ([register_submit.php](https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/register_submit.php#L10))
|
||||
|
||||
```php lineNumbers
|
||||
```php showLineNumbers
|
||||
<?php
|
||||
$query = "INSERT INTO jbb_users (username, password, email, avatar) VALUES ('$username','$password','$email','images/avatars/noavatar.gif')";
|
||||
?>
|
||||
@ -119,7 +119,7 @@ $query = "INSERT INTO jbb_users (username, password, email, avatar) VALUES ('$us
|
||||
|
||||
I guess I gave up on counting `$query`s by ones... ([functions.php](https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/functions.php#L231))
|
||||
|
||||
```php lineNumbers
|
||||
```php showLineNumbers
|
||||
<?php
|
||||
while ($topic = mysql_fetch_object($result30)) {
|
||||
$query40 = "SELECT * FROM jbb_users WHERE userID = '$topic->userID'";
|
||||
|
@ -28,7 +28,7 @@ If you run your own server, these can be added by way of your Apache or nginx co
|
||||
|
||||
The following script can be added as a Worker and customized to your needs. Some can be extremely picky with syntax, so be sure to [read the documentation](https://www.netsparker.com/whitepaper-http-security-headers/) carefully. You can fiddle with it in [the playground](https://cloudflareworkers.com/), too. Simply modify the current headers to your needs, or add new ones to the `newHeaders` or `removeHeaders` arrays.
|
||||
|
||||
```js lineNumbers
|
||||
```js showLineNumbers
|
||||
let addHeaders = {
|
||||
"Content-Security-Policy": "default-src 'self'; upgrade-insecure-requests",
|
||||
"Strict-Transport-Security": "max-age=1000",
|
||||
|
@ -86,7 +86,7 @@
|
||||
"@jakejarvis/eslint-config": "^4.0.7",
|
||||
"@tailwindcss/postcss": "^4.1.5",
|
||||
"@types/mdx": "^2.0.13",
|
||||
"@types/node": "^22.15.14",
|
||||
"@types/node": "^22.15.15",
|
||||
"@types/prop-types": "^15.7.14",
|
||||
"@types/react": "^19.1.3",
|
||||
"@types/react-dom": "^19.1.3",
|
||||
@ -131,7 +131,8 @@
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"allowedVersions": {
|
||||
"react": "^19"
|
||||
"react": "^19",
|
||||
"react-dom": "^19"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@ -208,8 +208,8 @@ importers:
|
||||
specifier: ^2.0.13
|
||||
version: 2.0.13
|
||||
'@types/node':
|
||||
specifier: ^22.15.14
|
||||
version: 22.15.14
|
||||
specifier: ^22.15.15
|
||||
version: 22.15.15
|
||||
'@types/prop-types':
|
||||
specifier: ^15.7.14
|
||||
version: 15.7.14
|
||||
@ -1242,8 +1242,8 @@ packages:
|
||||
'@types/nlcst@2.0.3':
|
||||
resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
|
||||
|
||||
'@types/node@22.15.14':
|
||||
resolution: {integrity: sha512-BL1eyu/XWsFGTtDWOYULQEs4KR0qdtYfCxYAUYRoB7JP7h9ETYLgQTww6kH8Sj2C0pFGgrpM0XKv6/kbIzYJ1g==}
|
||||
'@types/node@22.15.15':
|
||||
resolution: {integrity: sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==}
|
||||
|
||||
'@types/prop-types@15.7.14':
|
||||
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
|
||||
@ -5221,7 +5221,7 @@ snapshots:
|
||||
|
||||
'@types/concat-stream@2.0.3':
|
||||
dependencies:
|
||||
'@types/node': 22.15.14
|
||||
'@types/node': 22.15.15
|
||||
|
||||
'@types/debug@4.1.12':
|
||||
dependencies:
|
||||
@ -5257,7 +5257,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
'@types/node@22.15.14':
|
||||
'@types/node@22.15.15':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
@ -8799,7 +8799,7 @@ snapshots:
|
||||
'@types/concat-stream': 2.0.3
|
||||
'@types/debug': 4.1.12
|
||||
'@types/is-empty': 1.2.3
|
||||
'@types/node': 22.15.14
|
||||
'@types/node': 22.15.15
|
||||
'@types/unist': 3.0.3
|
||||
concat-stream: 2.0.0
|
||||
debug: 4.4.0
|
||||
|
Reference in New Issue
Block a user