feat: add line numbers to code blocks with gutter styling

- Implemented line number display with absolute positioned gutter (40px width, right-aligned text)
- Added dynamic padding to pre element based on showLineNumbers state (3.5rem left padding when enabled)
- Moved hljs import to top-level for better performance and removed redundant dynamic imports
- Added ViewEncapsulation.None to enable global hljs theme styling
- Enhanced gutter styling with semi-transparent background, border, and pointer
This commit is contained in:
Bruno Charest 2025-11-19 18:53:27 -05:00
parent 2a5047b7f0
commit b695095593
2 changed files with 62 additions and 30 deletions

View File

@ -9,12 +9,14 @@ import {
inject,
HostListener,
ChangeDetectionStrategy,
ChangeDetectorRef
ChangeDetectorRef,
ViewEncapsulation
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Block, CodeProps } from '../../../core/models/block.model';
import { CodeThemeService } from '../../../services/code-theme.service';
import hljs from 'highlight.js/lib/common';
@Component({
selector: 'app-code-block',
@ -22,6 +24,7 @@ import { CodeThemeService } from '../../../services/code-theme.service';
imports: [CommonModule, FormsModule],
styleUrls: ['./code-themes.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
template: `
<div class="relative group w-full">
<!-- Header -->
@ -352,10 +355,12 @@ import { CodeThemeService } from '../../../services/code-theme.service';
class="rounded-b-md border border-t-0 border-border overflow-hidden transition-colors duration-200"
[ngClass]="getThemeClass()"
>
<div class="relative w-full">
<pre
class="m-0 max-h-[400px] overflow-auto text-sm leading-6 px-3 py-3 custom-scrollbar"
class="m-0 max-h-[400px] overflow-auto text-sm leading-6 custom-scrollbar"
[class.whitespace-pre-wrap]="props.enableWrap"
[style.font-family]="getFontFamily()"
[style.padding]="props.showLineNumbers ? '12px 12px 12px 3.5rem' : '12px'"
>
@if (highlightedHtml) {
<code class="hljs block" [innerHTML]="highlightedHtml" [style.font-family]="getFontFamily()"></code>
@ -365,6 +370,19 @@ import { CodeThemeService } from '../../../services/code-theme.service';
</code>
}
</pre>
@if (props.showLineNumbers) {
<div
class="absolute top-0 left-0 py-3 w-10 text-right text-xs leading-6 text-neutral-500 pointer-events-none select-none border-r border-white/5 bg-black/5 h-full"
[style.font-family]="getFontFamily()"
aria-hidden="true"
>
@for (line of lineNumbers; track $index) {
<div class="pr-2">{{ line }}</div>
}
</div>
}
</div>
</div>
}
</div>
@ -703,20 +721,16 @@ export class CodeBlockComponent implements AfterViewInit {
this._lastHighlightSignature = signature;
try {
// Use the common Highlight.js bundle (works better with modern bundlers)
const hljsModule = await import('highlight.js/lib/common');
const hljs: any = (hljsModule as any).default ?? hljsModule;
let html: string;
try {
const hasLang = !!(lang && typeof hljs.getLanguage === 'function' && hljs.getLanguage(lang));
const hasLang = !!(lang && typeof (hljs as any).getLanguage === 'function' && (hljs as any).getLanguage(lang));
if (hasLang) {
html = hljs.highlight(code, { language: lang }).value;
html = (hljs as any).highlight(code, { language: lang }).value;
} else {
html = hljs.highlightAuto(code).value;
html = (hljs as any).highlightAuto(code).value;
}
} catch {
html = hljs.highlightAuto(code).value;
html = (hljs as any).highlightAuto(code).value;
}
this.highlightedHtml = html;

File diff suppressed because one or more lines are too long