feat: highlight code blocks

This commit is contained in:
Fernando Porazzi
2023-10-07 12:30:05 +02:00
parent 585f031ce1
commit 9c4871d3c1
7 changed files with 102 additions and 2 deletions

View File

@ -19,6 +19,7 @@
"debug": "^4.3.4",
"dexie": "^3.2.4",
"emojilib": "^3.0.10",
"highlight.js": "^11.8.0",
"light-bolt11-decoder": "^2.1.0",
"match-sorter": "^6.3.1",
"qr-code-styling": "^1.6.0-rc.1",

View File

@ -0,0 +1,14 @@
.codeblock {
overflow: auto;
position: relative;
}
.codeblock pre {
overflow: auto;
line-height: 1.4;
font-size: var(--font-size);
}
.hljs {
background: #f6f8fa;
}

View File

@ -0,0 +1,24 @@
import { useEffect } from "react";
import "highlight.js/styles/github.css";
import "./CodeBlock.css";
const CodeBlock = ({ content, language }: { content: string; language?: string }) => {
useEffect(() => {
const importHljs = async () => {
const hljs = (await import("highlight.js")).default;
hljs.highlightAll();
};
importHljs();
});
return (
<div className={`codeblock ${language && `language-${language}`}`} dir="auto">
<pre>
<code className={language && `language-${language}`}>{content.trim()}</code>
</pre>
</div>
);
};
export default CodeBlock;

View File

@ -11,6 +11,7 @@ import { ProxyImg } from "./ProxyImg";
import { SpotlightMediaModal } from "./Deck/SpotlightMedia";
import HighlightedText from "./HighlightedText";
import { useTextTransformer } from "Hooks/useTextTransformCache";
import CodeBlock from "./CodeBlock";
export interface TextProps {
id: string;
@ -254,6 +255,9 @@ export default function Text({
if (element.type === "custom_emoji") {
chunks.push(<ProxyImg src={element.content} size={15} className="custom-emoji" />);
}
if (element.type === "code_block") {
chunks.push(<CodeBlock content={element.content} language={element.language} />);
}
if (element.type === "text") {
chunks.push(
<div className="text-frag">

View File

@ -34,3 +34,8 @@ export const CashuRegex = /(cashuA[A-Za-z0-9_-]{0,10000}={0,3})/i;
* Regex to match any npub/nevent/naddr/nprofile/note
*/
export const MentionNostrEntityRegex = /@n(pub|profile|event|ote|addr|)1[acdefghjklmnpqrstuvwxyz023456789]+/g;
/**
* Regex to match markdown code content
*/
export const MarkdownCodeRegex = /(```.+?```)/gms;

View File

@ -1,13 +1,31 @@
import { unwrap } from "@snort/shared";
import { CashuRegex, FileExtensionRegex, HashtagRegex, InvoiceRegex, MentionNostrEntityRegex } from "./const";
import {
CashuRegex,
FileExtensionRegex,
HashtagRegex,
InvoiceRegex,
MarkdownCodeRegex,
MentionNostrEntityRegex,
} from "./const";
import { validateNostrLink } from "./nostr-link";
import { splitByUrl } from "./utils";
export interface ParsedFragment {
type: "text" | "link" | "mention" | "invoice" | "media" | "cashu" | "hashtag" | "custom_emoji" | "highlighted_text";
type:
| "text"
| "link"
| "mention"
| "invoice"
| "media"
| "cashu"
| "hashtag"
| "custom_emoji"
| "highlighted_text"
| "code_block";
content: string;
mimeType?: string;
language?: string;
}
export type Fragment = string | ParsedFragment;
@ -179,6 +197,31 @@ function extractCustomEmoji(fragments: Fragment[], tags: Array<Array<string>>) {
.flat();
}
function extractMarkdownCode(fragments: Fragment[]): (string | ParsedFragment)[] {
return fragments
.map(f => {
if (typeof f === "string") {
return f.split(MarkdownCodeRegex).map(i => {
if (i.startsWith("```") && i.endsWith("```")) {
const firstLineBreakIndex = i.indexOf("\n");
const lastLineBreakIndex = i.lastIndexOf("\n");
return {
type: "code_block",
content: i.substring(firstLineBreakIndex, lastLineBreakIndex),
language: i.substring(3, firstLineBreakIndex),
} as ParsedFragment;
} else {
return i;
}
});
}
return f;
})
.flat();
}
export function transformText(body: string, tags: Array<Array<string>>) {
let fragments = extractLinks([body]);
fragments = extractMentions(fragments);
@ -186,6 +229,7 @@ export function transformText(body: string, tags: Array<Array<string>>) {
fragments = extractInvoices(fragments);
fragments = extractCashuTokens(fragments);
fragments = extractCustomEmoji(fragments, tags);
fragments = extractMarkdownCode(fragments);
fragments = fragments
.map(a => {
if (typeof a === "string") {