| | import hljs from 'highlight.js';
|
| | import {
|
| | NEWLINE,
|
| | DEFAULT_LANGUAGE,
|
| | LANG_PATTERN,
|
| | AMPERSAND_REGEX,
|
| | LT_REGEX,
|
| | GT_REGEX,
|
| | FENCE_PATTERN
|
| | } from '$lib/constants/code';
|
| |
|
| | export interface IncompleteCodeBlock {
|
| | language: string;
|
| | code: string;
|
| | openingIndex: number;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | export function highlightCode(code: string, language: string): string {
|
| | if (!code) return '';
|
| |
|
| | try {
|
| | const lang = language.toLowerCase();
|
| | const isSupported = hljs.getLanguage(lang);
|
| |
|
| | if (isSupported) {
|
| | return hljs.highlight(code, { language: lang }).value;
|
| | } else {
|
| | return hljs.highlightAuto(code).value;
|
| | }
|
| | } catch {
|
| |
|
| | return code
|
| | .replace(AMPERSAND_REGEX, '&')
|
| | .replace(LT_REGEX, '<')
|
| | .replace(GT_REGEX, '>');
|
| | }
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | export function detectIncompleteCodeBlock(markdown: string): IncompleteCodeBlock | null {
|
| |
|
| |
|
| | const fencePattern = new RegExp(FENCE_PATTERN.source, FENCE_PATTERN.flags);
|
| | const fences: number[] = [];
|
| | let fenceMatch;
|
| |
|
| | while ((fenceMatch = fencePattern.exec(markdown)) !== null) {
|
| |
|
| | const pos = fenceMatch[0].startsWith(NEWLINE) ? fenceMatch.index + 1 : fenceMatch.index;
|
| | fences.push(pos);
|
| | }
|
| |
|
| |
|
| | if (fences.length % 2 === 0) {
|
| | return null;
|
| | }
|
| |
|
| |
|
| |
|
| | const openingIndex = fences[fences.length - 1];
|
| | const afterOpening = markdown.slice(openingIndex + 3);
|
| |
|
| |
|
| | const langMatch = afterOpening.match(LANG_PATTERN);
|
| | const language = langMatch?.[1] || DEFAULT_LANGUAGE;
|
| | const codeStartIndex = openingIndex + 3 + (langMatch?.[0]?.length ?? 0);
|
| | const code = markdown.slice(codeStartIndex);
|
| |
|
| | return {
|
| | language,
|
| | code,
|
| | openingIndex
|
| | };
|
| | }
|
| |
|