Highlight code blocks #645

Merged
Kieran merged 1 commits from fernandoporazzi/snort:code-highlighter into main 2023-10-09 09:10:39 +00:00
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") {

View File

@ -2718,6 +2718,7 @@ __metadata:
emojilib: ^3.0.10
eslint: ^8.48.0
eslint-webpack-plugin: ^4.0.1
highlight.js: ^11.8.0
html-webpack-plugin: ^5.5.1
jest: ^29.5.0
jest-environment-jsdom: ^29.5.0
@ -7278,6 +7279,13 @@ __metadata:
languageName: node
linkType: hard
"highlight.js@npm:^11.8.0":
version: 11.8.0
resolution: "highlight.js@npm:11.8.0"
checksum: d2578a57aee7315946ff19379053fd0a28b127baabf7617ab1d28d62cdc4eaf3d75053569cb8479a5afdc7a68f1ba9a6c1d612d8ae399b4b9aa43093b4fb6831
languageName: node
linkType: hard
"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2":
version: 3.3.2
resolution: "hoist-non-react-statics@npm:3.3.2"