feat: integrate CodeMirror with custom macOS-style themes for code block syntax highlighting
This commit is contained in:
@@ -164,3 +164,218 @@
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<!-- CodeMirror Styles -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.css" integrity="sha384-zaeBlB/vwYsDRSlFajnDd7OydJ0cWk+c2OWybl3eSUf6hW2EbhlCsQPqKr3gkznT" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/theme/material-darker.min.css" integrity="sha384-eZTPTN0EvJdn23s24UDYJmUM2T7C2ZFa3qFLypeBruJv8mZeTusKUAO/j5zPAQ6l" crossorigin="anonymous">
|
||||
<!-- Google Fonts JetBrains Mono -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,300..800;1,300..800&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
/* Custom modern code block window styling */
|
||||
.code-window {
|
||||
background-color: #212529;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 12px 24px rgba(30, 34, 40, 0.08), 0 4px 12px rgba(30, 34, 40, 0.04);
|
||||
margin: 2rem 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.code-window-header {
|
||||
background-color: #1a1d20;
|
||||
padding: 0.8rem 1.2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.code-window-dots {
|
||||
display: flex;
|
||||
gap: 7px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.code-window-dot {
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.dot-red { background-color: #ff5f56; }
|
||||
.dot-yellow { background-color: #ffbd2e; }
|
||||
.dot-green { background-color: #27c93f; }
|
||||
|
||||
.code-window-lang {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: #aab0bc;
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
padding: 3px 10px;
|
||||
border-radius: 6px;
|
||||
font-family: 'Space Grotesk', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
/* Customizing CodeMirror specifically */
|
||||
.CodeMirror {
|
||||
font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', Courier, monospace !important;
|
||||
font-size: 0.85rem !important;
|
||||
line-height: 1.65 !important;
|
||||
height: auto !important;
|
||||
background: #212529 !important;
|
||||
padding: 0.8rem 0 !important;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
max-height: 550px !important;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
background-color: #212529 !important;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.05) !important;
|
||||
width: 48px !important;
|
||||
}
|
||||
|
||||
.CodeMirror-linenumber {
|
||||
color: #4a505e !important;
|
||||
padding-left: 12px !important;
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid transparent !important; /* Hide cursor for read-only blocks */
|
||||
}
|
||||
|
||||
/* Styling selection */
|
||||
.CodeMirror-selected {
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
.CodeMirror-focused .CodeMirror-selected {
|
||||
background: rgba(255, 255, 255, 0.15) !important;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@push('scripts')
|
||||
<!-- CodeMirror Core JS -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.js" integrity="sha384-uTzi9PU3axyLeKbgCPqQdNFT5Q23BRgFbmtrolYC7ROlbWVv7IggV+3r0ALSQd/s" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- CodeMirror Modes -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/xml/xml.min.js" integrity="sha384-xPpkMo5nDgD98fIcuRVYhxkZV6/9Y4L8s3p0J5c4MxgJkyKJ8BJr+xfRkq7kn6Tw" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/javascript/javascript.min.js" integrity="sha384-g0o+WW9mdIxA7LaaCKTkRm0M5TVT+Bb4s9eocxPsI2G0Xm0POG9iD6G6qP1IIsfS" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/css/css.min.js" integrity="sha384-fpeIC2FZuPmw7mIsTvgB5BNc8QVxQC/nWg2W+CgPYOAiBiYVuHe2E8HiTWHBMIJQ" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/clike/clike.min.js" integrity="sha384-bZx++XMDqT0QRVoq6eVZnkq04LmwWZ0eLo0tFdSGkOJgH8qiqeMIIFJ+mRaM0wfb" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/php/php.min.js" integrity="sha384-1FUwPY2kaZKXw258/9CYBSS+zcc3CPggxE1zLjmYYiOdkcOw3KcXH5VNJWWbjw2U" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/python/python.min.js" integrity="sha384-Xy+2exU6lBoT4OpUOtnQb+cUpn+nlJQEHvRobWVtwz6wIsw4oNoO7xyd/l8rYgMy" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/sql/sql.min.js" integrity="sha384-HxXmA1hLc56V6Ja4yfcCwAprmbnS4tuvKYS0qKG3t6oxOFMflcnYq5fOnt6wVCda" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/shell/shell.min.js" integrity="sha384-dmPRq54XNtYnlnAo7QrwJnZNa5r0JP5gU8z4H0686Cgz37qrR2jmNaDHUXyMI4wn" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/yaml/yaml.min.js" integrity="sha384-LekyoEGHzBMGUx26EEtC5G6HD6iyIr0msuy3LP3F8V98vVS5ZoNfIyiirXsoqAt/" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- Initialization Script -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const codeBlocks = document.querySelectorAll('.post-content pre code');
|
||||
if (codeBlocks.length === 0) return;
|
||||
|
||||
codeBlocks.forEach(function(codeEl) {
|
||||
// Extract raw code text securely (safely handled by textContent)
|
||||
const rawCode = codeEl.textContent || codeEl.innerText;
|
||||
|
||||
// Retrieve and parse language from class
|
||||
let lang = 'code';
|
||||
|
||||
const classes = codeEl.className.split(' ');
|
||||
for (const cls of classes) {
|
||||
if (cls.startsWith('language-') || cls.startsWith('lang-')) {
|
||||
lang = cls.replace('language-', '').replace('lang-', '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Map languages to CodeMirror modes
|
||||
const modeMap = {
|
||||
'js': 'javascript',
|
||||
'javascript': 'javascript',
|
||||
'ts': 'javascript',
|
||||
'typescript': 'javascript',
|
||||
'json': 'javascript',
|
||||
'html': 'xml',
|
||||
'xml': 'xml',
|
||||
'svg': 'xml',
|
||||
'css': 'css',
|
||||
'php': 'php',
|
||||
'python': 'python',
|
||||
'py': 'python',
|
||||
'sql': 'sql',
|
||||
'bash': 'shell',
|
||||
'sh': 'shell',
|
||||
'shell': 'shell',
|
||||
'yaml': 'yaml',
|
||||
'yml': 'yaml'
|
||||
};
|
||||
|
||||
const resolvedMode = modeMap[lang.toLowerCase()] || 'javascript';
|
||||
|
||||
// Display nice label for the language badge
|
||||
const displayLang = lang === 'code' ? 'Code' : lang;
|
||||
|
||||
// Build premium macOS-style window wrapping structure strictly using secure DOM methods (no innerHTML)
|
||||
const preEl = codeEl.parentNode;
|
||||
if (!preEl || preEl.tagName !== 'PRE') return;
|
||||
|
||||
const codeWindow = document.createElement('div');
|
||||
codeWindow.className = 'code-window';
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.className = 'code-window-header';
|
||||
|
||||
const dots = document.createElement('div');
|
||||
dots.className = 'code-window-dots';
|
||||
|
||||
const dotRed = document.createElement('div');
|
||||
dotRed.className = 'code-window-dot dot-red';
|
||||
const dotYellow = document.createElement('div');
|
||||
dotYellow.className = 'code-window-dot dot-yellow';
|
||||
const dotGreen = document.createElement('div');
|
||||
dotGreen.className = 'code-window-dot dot-green';
|
||||
|
||||
dots.appendChild(dotRed);
|
||||
dots.appendChild(dotYellow);
|
||||
dots.appendChild(dotGreen);
|
||||
|
||||
const langBadge = document.createElement('div');
|
||||
langBadge.className = 'code-window-lang';
|
||||
langBadge.textContent = displayLang; // Secure text content insertion
|
||||
|
||||
header.appendChild(dots);
|
||||
header.appendChild(langBadge);
|
||||
|
||||
const editorContainer = document.createElement('div');
|
||||
editorContainer.className = 'code-editor-container';
|
||||
|
||||
codeWindow.appendChild(header);
|
||||
codeWindow.appendChild(editorContainer);
|
||||
|
||||
// Replace the legacy pre tag with our beautiful new code window
|
||||
preEl.parentNode.replaceChild(codeWindow, preEl);
|
||||
|
||||
// Instantiate CodeMirror inside the new container
|
||||
CodeMirror(editorContainer, {
|
||||
value: rawCode.trim(),
|
||||
mode: resolvedMode,
|
||||
theme: 'material-darker',
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
readOnly: 'nocursor',
|
||||
viewportMargin: Infinity
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
|
||||
Reference in New Issue
Block a user