Skill: Syntax Highlighting

## Purpose

7 stars

Best use case

Skill: Syntax Highlighting is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

## Purpose

Teams using Skill: Syntax Highlighting should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/syntax-highlighting/SKILL.md --create-dirs "https://raw.githubusercontent.com/heldernoid/agentic-build-templates/main/projects/web-applications/pastebin/skills/syntax-highlighting/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/syntax-highlighting/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How Skill: Syntax Highlighting Compares

Feature / AgentSkill: Syntax HighlightingStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

## Purpose

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Skill: Syntax Highlighting

## Purpose
Implement client-side syntax highlighting using highlight.js 11. Covers language detection, hljs initialization, React integration, supported language list, and the copy-to-clipboard flow.

---

## highlight.js Setup (lazy-load only needed languages)

```typescript
// web/src/lib/hljs.ts
import hljs from 'highlight.js/lib/core';

// Register only the languages the app supports (reduces bundle size)
import typescript from 'highlight.js/lib/languages/typescript';
import javascript from 'highlight.js/lib/languages/javascript';
import python from 'highlight.js/lib/languages/python';
import rust from 'highlight.js/lib/languages/rust';
import go from 'highlight.js/lib/languages/go';
import sql from 'highlight.js/lib/languages/sql';
import xml from 'highlight.js/lib/languages/xml';   // covers HTML
import css from 'highlight.js/lib/languages/css';
import bash from 'highlight.js/lib/languages/bash';
import json from 'highlight.js/lib/languages/json';
import yaml from 'highlight.js/lib/languages/yaml';
import markdown from 'highlight.js/lib/languages/markdown';
import c from 'highlight.js/lib/languages/c';
import cpp from 'highlight.js/lib/languages/cpp';
import java from 'highlight.js/lib/languages/java';
import csharp from 'highlight.js/lib/languages/csharp';
import php from 'highlight.js/lib/languages/php';
import ruby from 'highlight.js/lib/languages/ruby';
import swift from 'highlight.js/lib/languages/swift';
import kotlin from 'highlight.js/lib/languages/kotlin';
import scala from 'highlight.js/lib/languages/scala';
import r from 'highlight.js/lib/languages/r';
import matlab from 'highlight.js/lib/languages/matlab';
import lua from 'highlight.js/lib/languages/lua';
import perl from 'highlight.js/lib/languages/perl';
import haskell from 'highlight.js/lib/languages/haskell';
import elixir from 'highlight.js/lib/languages/elixir';
import erlang from 'highlight.js/lib/languages/erlang';
import clojure from 'highlight.js/lib/languages/clojure';
import ini from 'highlight.js/lib/languages/ini';  // covers TOML approx.
import diff from 'highlight.js/lib/languages/diff';

hljs.registerLanguage('typescript', typescript);
hljs.registerLanguage('javascript', javascript);
hljs.registerLanguage('python', python);
hljs.registerLanguage('rust', rust);
hljs.registerLanguage('go', go);
hljs.registerLanguage('sql', sql);
hljs.registerLanguage('xml', xml);
hljs.registerLanguage('css', css);
hljs.registerLanguage('bash', bash);
hljs.registerLanguage('json', json);
hljs.registerLanguage('yaml', yaml);
hljs.registerLanguage('markdown', markdown);
hljs.registerLanguage('c', c);
hljs.registerLanguage('cpp', cpp);
hljs.registerLanguage('java', java);
hljs.registerLanguage('csharp', csharp);
hljs.registerLanguage('php', php);
hljs.registerLanguage('ruby', ruby);
hljs.registerLanguage('swift', swift);
hljs.registerLanguage('kotlin', kotlin);
hljs.registerLanguage('scala', scala);
hljs.registerLanguage('r', r);
hljs.registerLanguage('matlab', matlab);
hljs.registerLanguage('lua', lua);
hljs.registerLanguage('perl', perl);
hljs.registerLanguage('haskell', haskell);
hljs.registerLanguage('elixir', elixir);
hljs.registerLanguage('erlang', erlang);
hljs.registerLanguage('clojure', clojure);
hljs.registerLanguage('ini', ini);
hljs.registerLanguage('diff', diff);

export { hljs };
```

---

## Supported Languages List

```typescript
// web/src/lib/languages.ts
export interface LanguageOption {
  value: string;    // hljs alias, stored in DB
  label: string;    // display name in selector
  badge: string;    // short abbreviation for badge
  color: string;    // badge background color
  textColor: string;
}

export const LANGUAGES: LanguageOption[] = [
  { value: 'plaintext', label: 'Plain text',  badge: 'TXT', color: '#e7e5e4', textColor: '#57534e' },
  { value: 'typescript', label: 'TypeScript', badge: 'TS',  color: '#3178c6', textColor: '#ffffff' },
  { value: 'javascript', label: 'JavaScript', badge: 'JS',  color: '#f7df1e', textColor: '#1c1917' },
  { value: 'python',     label: 'Python',     badge: 'PY',  color: '#3572a5', textColor: '#ffffff' },
  { value: 'rust',       label: 'Rust',       badge: 'RS',  color: '#dea584', textColor: '#1c1917' },
  { value: 'go',         label: 'Go',         badge: 'GO',  color: '#00add8', textColor: '#1c1917' },
  { value: 'sql',        label: 'SQL',        badge: 'SQL', color: '#e38c00', textColor: '#ffffff' },
  { value: 'xml',        label: 'HTML',       badge: 'HTML',color: '#e34c26', textColor: '#ffffff' },
  { value: 'css',        label: 'CSS',        badge: 'CSS', color: '#563d7c', textColor: '#ffffff' },
  { value: 'bash',       label: 'Shell/Bash', badge: 'SH',  color: '#89e051', textColor: '#1c1917' },
  { value: 'json',       label: 'JSON',       badge: 'JSON',color: '#57534e', textColor: '#ffffff' },
  { value: 'yaml',       label: 'YAML',       badge: 'YAML',color: '#cb171e', textColor: '#ffffff' },
  { value: 'markdown',   label: 'Markdown',   badge: 'MD',  color: '#083fa1', textColor: '#ffffff' },
  { value: 'c',          label: 'C',          badge: 'C',   color: '#555555', textColor: '#ffffff' },
  { value: 'cpp',        label: 'C++',        badge: 'C++', color: '#f34b7d', textColor: '#ffffff' },
  { value: 'java',       label: 'Java',       badge: 'JV',  color: '#b07219', textColor: '#ffffff' },
  { value: 'csharp',     label: 'C#',         badge: 'C#',  color: '#178600', textColor: '#ffffff' },
  { value: 'php',        label: 'PHP',        badge: 'PHP', color: '#4f5d95', textColor: '#ffffff' },
  { value: 'ruby',       label: 'Ruby',       badge: 'RB',  color: '#701516', textColor: '#ffffff' },
  { value: 'swift',      label: 'Swift',      badge: 'SW',  color: '#f05138', textColor: '#ffffff' },
  { value: 'kotlin',     label: 'Kotlin',     badge: 'KT',  color: '#7f52ff', textColor: '#ffffff' },
  { value: 'scala',      label: 'Scala',      badge: 'SC',  color: '#c22d40', textColor: '#ffffff' },
  { value: 'r',          label: 'R',          badge: 'R',   color: '#276dc3', textColor: '#ffffff' },
  { value: 'lua',        label: 'Lua',        badge: 'LUA', color: '#000080', textColor: '#ffffff' },
  { value: 'perl',       label: 'Perl',       badge: 'PL',  color: '#0298c3', textColor: '#ffffff' },
  { value: 'haskell',    label: 'Haskell',    badge: 'HS',  color: '#5e5086', textColor: '#ffffff' },
  { value: 'elixir',     label: 'Elixir',     badge: 'EX',  color: '#6e4a7e', textColor: '#ffffff' },
  { value: 'diff',       label: 'Diff/Patch', badge: 'DIFF',color: '#88562a', textColor: '#ffffff' },
  { value: 'ini',        label: 'INI/TOML',   badge: 'INI', color: '#d1dae3', textColor: '#1c1917' },
];

export function findLanguage(value: string): LanguageOption {
  return LANGUAGES.find(l => l.value === value) ?? LANGUAGES[0];
}
```

---

## PasteViewer Component

```tsx
// web/src/components/PasteViewer.tsx
import { useMemo } from 'react';
import { hljs } from '../lib/hljs';

interface Props {
  content: string;
  language: string;    // hljs alias
  showLineNumbers?: boolean;
}

export function PasteViewer({ content, language, showLineNumbers = true }: Props) {
  const highlighted = useMemo(() => {
    if (language === 'plaintext') {
      // Escape HTML manually for plain text
      return content
        .replace(/&/g, '&')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;');
    }
    try {
      return hljs.highlight(content, { language }).value;
    } catch {
      // Fallback to plain text if language not registered
      return content.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    }
  }, [content, language]);

  const lines = highlighted.split('\n');

  return (
    <div style={{ background: '#1c1917', borderRadius: 16, overflow: 'hidden', border: '1px solid #292524' }}>
      <div style={{ display: 'flex', overflowX: 'auto' }}>
        {showLineNumbers && (
          <div style={{
            padding: '16px 12px 16px 16px',
            textAlign: 'right',
            userSelect: 'none',
            fontFamily: 'var(--font-mono)',
            fontSize: 13,
            lineHeight: 1.6,
            color: '#57534e',
            minWidth: 40,
            borderRight: '1px solid #292524',
            flexShrink: 0,
          }}>
            {lines.map((_, i) => (
              <div key={i}>{i + 1}</div>
            ))}
          </div>
        )}
        <pre style={{
          padding: 16,
          fontFamily: 'var(--font-mono)',
          fontSize: 13,
          lineHeight: 1.6,
          color: '#e7e5e4',
          flex: 1,
          margin: 0,
          overflow: 'visible',
        }}>
          <code dangerouslySetInnerHTML={{ __html: highlighted }} />
        </pre>
      </div>
    </div>
  );
}
```

---

## LanguageBadge Component

```tsx
// web/src/components/LanguageBadge.tsx
import { findLanguage } from '../lib/languages';

interface Props {
  language: string;
  size?: 'sm' | 'md';
}

export function LanguageBadge({ language, size = 'md' }: Props) {
  const lang = findLanguage(language);
  return (
    <span style={{
      background: lang.color,
      color: lang.textColor,
      borderRadius: 9999,
      padding: size === 'sm' ? '2px 8px' : '3px 10px',
      fontSize: size === 'sm' ? 11 : 12,
      fontWeight: 600,
      display: 'inline-flex',
      alignItems: 'center',
      flexShrink: 0,
    }}>
      {lang.badge}
    </span>
  );
}
```

---

## LanguageSelector Component

```tsx
// web/src/components/LanguageSelector.tsx
import { LANGUAGES } from '../lib/languages';

interface Props {
  value: string;
  onChange: (value: string) => void;
}

export function LanguageSelector({ value, onChange }: Props) {
  return (
    <select
      className="select"
      value={value}
      onChange={e => onChange(e.target.value)}
      style={{ fontSize: 13 }}
    >
      {LANGUAGES.map(lang => (
        <option key={lang.value} value={lang.value}>
          {lang.label}
        </option>
      ))}
    </select>
  );
}
```

---

## Copy to Clipboard Hook

```typescript
// web/src/hooks/useCopy.ts
import { useState, useCallback } from 'react';

export function useCopy(text: string, resetMs = 2000) {
  const [copied, setCopied] = useState(false);

  const copy = useCallback(async () => {
    try {
      if (navigator.clipboard) {
        await navigator.clipboard.writeText(text);
      } else {
        // Fallback for older browsers
        const el = document.createElement('textarea');
        el.value = text;
        el.style.position = 'fixed';
        el.style.opacity = '0';
        document.body.appendChild(el);
        el.select();
        document.execCommand('copy');
        document.body.removeChild(el);
      }
      setCopied(true);
      setTimeout(() => setCopied(false), resetMs);
    } catch {
      console.error('Copy failed');
    }
  }, [text, resetMs]);

  return { copied, copy };
}
```

---

## CopyButton Component

```tsx
// web/src/components/CopyButton.tsx
import { useCopy } from '../hooks/useCopy';

interface Props {
  text: string;
  size?: 'sm' | 'md';
}

export function CopyButton({ text, size = 'sm' }: Props) {
  const { copied, copy } = useCopy(text);
  return (
    <button
      className={`btn btn-secondary${size === 'sm' ? ' btn-sm' : ''}`}
      onClick={copy}
      title="Copy to clipboard"
    >
      {copied ? 'Copied!' : 'Copy'}
    </button>
  );
}
```

---

## hljs CSS Theme (dark, custom tokens)

```css
/* web/src/styles/hljs-theme.css */
/* Inline token colours matching the design mock palette */
.hljs-keyword    { color: #c792ea; }
.hljs-built_in   { color: #82aaff; }
.hljs-type       { color: #82aaff; }
.hljs-literal    { color: #ff5874; }
.hljs-number     { color: #f78c6c; }
.hljs-string     { color: #c3e88d; }
.hljs-template-tag    { color: #89ddff; }
.hljs-template-variable { color: #89ddff; }
.hljs-comment    { color: #546e7a; font-style: italic; }
.hljs-doctag     { color: #546e7a; font-style: italic; }
.hljs-meta       { color: #89ddff; }
.hljs-attr       { color: #ffcb6b; }
.hljs-attribute  { color: #a8a29e; }
.hljs-name       { color: #f07178; }
.hljs-section    { color: #82aaff; font-weight: bold; }
.hljs-tag        { color: #f07178; }
.hljs-variable   { color: #eeffff; }
.hljs-symbol     { color: #89ddff; }
.hljs-bullet     { color: #89ddff; }
.hljs-link       { color: #c3e88d; text-decoration: underline; }
.hljs-selector-class { color: #ffcb6b; }
.hljs-selector-id    { color: #82aaff; }
.hljs-selector-tag   { color: #f07178; }
.hljs-deletion   { background: #3d1515; color: #ff5874; }
.hljs-addition   { background: #1a3d1a; color: #c3e88d; }
```

---

## Vitest Tests

```typescript
// web/src/lib/__tests__/hljs.test.ts
import { describe, it, expect } from 'vitest';
import { hljs } from '../hljs';
import { findLanguage, LANGUAGES } from '../languages';

describe('hljs', () => {
  it('highlights TypeScript without throwing', () => {
    const result = hljs.highlight('const x: number = 1;', { language: 'typescript' });
    expect(result.value).toContain('hljs-keyword');
  });

  it('returns fallback for unregistered language', () => {
    // plaintext should not throw
    expect(() => {
      hljs.highlight('hello world', { language: 'plaintext' });
    }).toThrow(); // plaintext is not registered; we handle this in PasteViewer
  });
});

describe('findLanguage', () => {
  it('returns plaintext for unknown value', () => {
    expect(findLanguage('unknown').value).toBe('plaintext');
  });

  it('finds typescript', () => {
    expect(findLanguage('typescript').badge).toBe('TS');
  });

  it('covers all registered hljs languages', () => {
    const hljsLangs = new Set(['plaintext', 'typescript', 'javascript', 'python', 'rust', 'go',
      'sql', 'xml', 'css', 'bash', 'json', 'yaml', 'markdown']);
    for (const lang of LANGUAGES) {
      if (hljsLangs.has(lang.value)) {
        expect(lang.label.length).toBeGreaterThan(0);
      }
    }
  });
});
```

---

## Character and Line Counter Hook

```typescript
// web/src/hooks/useEditorStats.ts
import { useMemo } from 'react';

export function useEditorStats(content: string) {
  return useMemo(() => {
    const lines = content.split('\n').length;
    const chars = content.length;
    const bytes = new TextEncoder().encode(content).length;
    const overLimit = bytes > 524288;  // 512 KB
    return { lines, chars, bytes, overLimit };
  }, [content]);
}
```

---

## Key Integration Notes

1. Import `highlight.js/lib/core` (not the full bundle) to avoid a 900 KB bundle.
2. Each language registration adds roughly 5-20 KB gzipped; all 30 languages together are under 200 KB gzipped.
3. The `PasteViewer` uses `dangerouslySetInnerHTML` only with output from `hljs.highlight()`, not with raw user content. The content is HTML-escaped by hljs before token wrapping.
4. Plain text pastes skip hljs entirely and receive manual `&`, `<`, `>` escaping to prevent XSS.
5. Server does not run hljs. Highlighting is purely client-side to avoid Node.js overhead on large pastes.
6. The `useCopy` fallback uses `document.execCommand('copy')` which is deprecated but needed for non-HTTPS environments during local development.

Related Skills

Skill: Uptime Monitoring

7
from heldernoid/agentic-build-templates

## Overview

Skill: Status Page

7
from heldernoid/agentic-build-templates

## Overview

Skill: unit-conversion

7
from heldernoid/agentic-build-templates

## Overview

Skill: recipe-scaler

7
from heldernoid/agentic-build-templates

## Overview

reading-list

7
from heldernoid/agentic-build-templates

Operate the reading-list API to save, manage, tag, search, and export articles.

email-digest

7
from heldernoid/agentic-build-templates

Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.

websocket-realtime

7
from heldernoid/agentic-build-templates

Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".

poll-builder

7
from heldernoid/agentic-build-templates

Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.

Skill: personal-finance

7
from heldernoid/agentic-build-templates

## Overview

Skill: csv-import

7
from heldernoid/agentic-build-templates

## Overview

Skill: Pastebin Core

7
from heldernoid/agentic-build-templates

## Purpose

Skill: Cost Reporting

7
from heldernoid/agentic-build-templates

## Overview