Cách hiển thị code đẹp trên Blogspot bằng ATN CodeBox

Anh Trai Nắng
14 tháng 9
Tips
Mục lục

Trong quá trình viết blog chia sẻ thủ thuật, nhiều lúc chúng ta cần chèn code mẫu (HTML, CSS, JS…) để người đọc dễ tham khảo. Tuy nhiên, việc trình bày code trên Blogspot mặc định thường khá đơn giản, thiếu tính thẩm mỹ và chức năng hỗ trợ.

Vì vậy, ATN CodeBox ra đời để giải quyết vấn đề này – một bộ code giúp hiển thị đoạn mã (AE vẫn hay gọi là khung chứa code) với giao diện hiện đại, có thanh công cụ (toolbar) tiện lợi và responsive trên mọi thiết bị.

Khung chứa code Blogspot ATN CodeBox
Khung chứa code Blogspot ATN CodeBox.
 
 
Thông tin thêm: Ban đầu, khung chứ code này được lấy cảm hứng từ bài viết Tạo khung chứa code có nút Copy và Download Code đẹp cho Blogger của Tôi Share vẫn chưa có đánh stt dòng code. Về sau, sau khi xem bài Khung Chứa Code Pro UX Giao Diện Highlight.js Đẹp Cho Blogger của TruongDevs thì mình đã cho ra bản hoàn chỉnh cuối cùng để sử dụng và nay share code cho anh em luôn! 😊  

Ưu điểm nổi bật của ATN CodeBox

Demo thì là khung chứa code mình đang xài luôn nha các bạn, xài sao chia sẻ cho các bạn vậy luôn!

1. Giao diện đẹp mắt, dễ đọc

  • Nền sáng dịu, font monospace chuẩn kỹ thuật.
  • Có đánh số dòng rõ ràng.
  • Tùy biến scrollbar gọn gàng, chuyên nghiệp.

2. Thanh công cụ (Toolbar) nhiều tính năng

  • Copy nhanh nội dung code chỉ bằng 1 click.
  • Download trực tiếp file code.
  • Preview xem trước ngay trong tab mới.
  • Sao chép liên kết CodeBox để chia sẻ dễ dàng. Cái này mình thấy cũng hay!
  • Mở trên CodePen nếu code có HTML/CSS/JS. Phải nhấn mạnh chỗ này, là khi 1 khung chứa code của các bạn có cả 3 loại HTML/CSS/JS thì khi click ra CodePen nó sẽ tự động chia ra làm 3 mục hết sức chuyên nghiệp. Click test thử đây nha!

3. Hỗ trợ PrismJS highlight

  • Tô màu cú pháp (syntax highlighting). Ưu điểm là khi khung code có tất cả code gì nó đều highlight code đó luôn mà không cần phân biệt html, css hay js gì cả.
  • Hỗ trợ nhiều ngôn ngữ: HTML, CSS, JS…

4. Responsive hoàn toàn

  • Tương thích mobile, tablet, PC.
  • Tự động điều chỉnh toolbar và hiển thị code trên màn hình nhỏ.

5. Trải nghiệm người dùng tối ưu

  • Có thông báo (toast) nhỏ gọn khi copy hoặc tải file.
  • Tích hợp scroll mượt, nhảy đến code block qua hash link.

Hướng dẫn cài đặt ATN CodeBox trên Blogspot

Vào Giao diện > Chỉnh sửa HTML trên Blogspot.

Dán toàn bộ code ATN CodeBox (CSS + JS) vào trước thẻ </body>.

<!-- ============ ATN CODEBOX ============ -->
<style>
    /* ATN CodeBox &#8211; Light, dịu và responsive (giữ nguyên palette của anh) */
    .atn-pre {
        --atn-bg: #ffffff;
        --atn-fg: #0f172a;
        --atn-border: #e5e7eb;
        --atn-accent: #2563eb;
        --atn-muted: #64748b;
        --atn-btn-bg: #f1f5f9;
        --atn-btn-fg: #0f172a;
        --atn-btn-hover: #e2e8f0;
        position: relative;
        background: var(--atn-bg);
        color: var(--atn-fg);
        border: 1px solid var(--atn-border);
        border-radius: 14px;
        overflow: hidden;
        margin: 16px 0;
        box-shadow: 0 8px 24px rgba(2, 6, 23, .06);
    }
    /* Toolbar */
    .atn-pre .atn-bar {
        display: flex;
        gap: 12px;
        align-items: center;
        justify-content: space-between;
        flex-wrap: wrap;
        padding: 10px 12px;
        background: #f8fafc;
        border-bottom: 1px solid var(--atn-border);
    }
    .atn-pre .atn-title {
        min-width: 0;
        font: 600 14px/1.2 ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        color: var(--atn-fg);
    }
    .atn-pre .atn-meta {
        margin-left: auto;
        margin-right: 8px;
        font: 12px/1.2 ui-sans-serif;
        color: var(--atn-muted);
    }
    /* Actions */
    .atn-pre .atn-actions {
        display: flex;
        gap: 8px;
        flex-wrap: wrap;
    }
    .atn-pre .atn-btn {
        -webkit-tap-highlight-color: transparent;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 36px;
        height: 36px;
        border-radius: 10px;
        border: 1px solid var(--atn-border);
        background: var(--atn-btn-bg);
        color: var(--atn-btn-fg);
        cursor: pointer;
        transition: transform .12s ease, background .15s ease, border-color .15s ease, box-shadow .15s ease;
    }
    .atn-pre .atn-btn:hover {
        background: var(--atn-btn-hover);
        border-color: #d7dee6;
        box-shadow: 0 1px 0 rgba(15, 23, 42, .04) inset;
    }
    .atn-pre .atn-btn:active {
        transform: scale(.96);
    }
    .atn-pre .atn-btn svg {
        width: 18px;
        height: 18px;
        fill: none;
        stroke: currentColor;
        stroke-linecap: round;
        stroke-linejoin: round;
        stroke-width: 1.6;
    }
    .atn-pre.is-copied .atn-btn-copy {
        box-shadow: 0 0 0 2px rgba(34, 197, 94, .28) inset;
    }
    .atn-pre.is-downloading .atn-btn-download {
        box-shadow: 0 0 0 2px rgba(37, 99, 235, .28) inset;
    }
    /* Code area */
    .atn-pre pre {
        margin: 0;
        padding: 16px 18px;
        overflow: auto;
        -webkit-overflow-scrolling: touch;
        font: 13.5px/1.6 ui-monospace, SFMono-Regular, Consolas, Menlo, monospace;
        background: #fff;
        white-space: pre;
        /* trên PC giữ nguyên */
    }
    .atn-pre pre code {
        background: none;
    }
    .atn-pre code {
        padding: 0;
    }
    /* ====== LINE NUMBERS GUTTER (hòa với theme hiện tại) ====== */
    /* chừa chỗ cho gutter */
    .atn-pre pre.line-numbers {
        padding-left: 3.8em;
        position: relative;
    }
    /* container gutter (JS sẽ tạo .line-numbers-rows bên trong pre) */
    .atn-pre .line-numbers-rows {
        position: absolute;
        left: 0;
        width: 3.4em;
        pointer-events: none;
        border-right: 1px solid var(--atn-border);
        box-sizing: border-box;
        text-align: right;
        padding-right: 0.6em;
        font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
        color: var(--atn-muted);
    }
    /* mỗi số 1 hàng */
    .atn-pre .line-numbers-rows &gt;
    span {
        display: block;
        padding: 0 0 0 0;
        margin: 0;
        box-sizing: border-box;
    }
    /* Scrollbar tinh tế (WebKit) */
    .atn-pre pre::-webkit-scrollbar {
        height: 10px;
        width: 10px;
    }
    .atn-pre pre::-webkit-scrollbar-thumb {
        background: #cfd6df;
        border-radius: 10px;
    }
    .atn-pre pre::-webkit-scrollbar-track {
        background: transparent;
    }
    /* Toast */
    #atn-toast {
        position: fixed;
        left: 50%;
        bottom: 18px;
        transform: translate(-50%, 20px);
        z-index: 9999;
        max-width: 90vw;
        padding: 10px 14px;
        border-radius: 12px;
        background: #0b1220;
        color: #e5e7eb;
        border: 1px solid #1f2a44;
        font: 13px/1.35 ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto;
        opacity: 0;
        pointer-events: none;
        transition: opacity .2s ease, transform .2s ease;
        box-shadow: 0 8px 24px rgba(2, 6, 23, .18);
    }
    #atn-toast.is-visible {
        opacity: 1;
        transform: translate(-50%, 0);
    }
    /* ========= Responsive ========= */
    @media (max-width:768px) {
        .atn-pre {
            border-radius: 12px;
        }
        .atn-pre .atn-bar {
            gap: 10px;
            padding: 10px;
        }
        .atn-pre .atn-title {
            font-size: 13px;
            max-width: 60%;
        }
        .atn-pre .atn-meta {
            order: 3;
            width: 100%;
            margin: 0;
        }
        .atn-pre .atn-actions {
            order: 2;
            width: 100%;
            gap: 10px;
        }
        .atn-pre .atn-btn {
            width: 40px;
            height: 40px;
            border-radius: 12px;
        }
        .atn-pre pre {
            padding: 14px 12px;
        }
    }
    @media (max-width:480px) {
        .atn-pre .atn-bar {
            padding: 8px;
        }
        .atn-pre .atn-title {
            width: 100%;
            max-width: 100%;
            font-size: 13px;
            margin-bottom: 2px;
        }
        .atn-pre .atn-actions {
            gap: 8px;
        }
        .atn-pre .atn-btn {
            width: 42px;
            height: 42px;
        }
        .atn-pre pre {
            padding: 12px 10px;
            white-space: pre-wrap;
            overflow-wrap: anywhere;
            word-break: break-word;
        }
    }
    /* Prism token colors (giữ nguyên) */
    .atn-pre code[class*=&quot;
    language-&quot;
    ],
    .atn-pre pre[class*=&quot;
    language-&quot;
    ] {
        text-shadow: none;
    }
    .atn-pre .token.comment,
    .atn-pre .token.prolog,
    .atn-pre .token.doctype,
    .atn-pre .token.cdata {
        color: #94a3b8;
    }
    .atn-pre .token.punctuation {
        color: #475569;
    }
    .atn-pre .token.property,
    .atn-pre .token.tag,
    .atn-pre .token.constant,
    .atn-pre .token.symbol {
        color: #1d4ed8;
    }
    .atn-pre .token.selector,
    .atn-pre .token.attr-name,
    .atn-pre .token.string,
    .atn-pre .token.char,
    .atn-pre .token.builtin,
    .atn-pre .token.inserted {
        color: #059669;
    }
    .atn-pre .token.operator,
    .atn-pre .token.entity,
    .atn-pre .token.url {
        color: #0f172a;
    }
    .atn-pre .token.atrule,
    .atn-pre .token.attr-value,
    .atn-pre .token.keyword {
        color: #7c3aed;
    }
    .atn-pre .token.function,
    .atn-pre .token.class-name {
        color: #dc2626;
    }
    .atn-pre .token.deleted {
        color: #dc2626;
    }
    .atn-pre .token.regex,
    .atn-pre .token.important,
    .atn-pre .token.variable {
        color: #ca8a04;
    }
    .atn-pre .atn-btn:focus {
        outline: none;
        box-shadow: 0 0 0 3px rgba(37, 99, 235, .35);
    }
</style>
<script type='text/javascript'>
    //<![CDATA[
    /*! ATN CodeBox – Prism + improved line-numbers (ES5, IIFE) */
    (function() {
        var TOAST_ID = 'atn-toast';

        /* ======= Auto detect header offset (fixed/sticky) ======= */
        (function() {
            function detectHeaderOffset() {
                var header = document.querySelector('header, #header, .header');
                if (header) {
                    var st = window.getComputedStyle(header);
                    if (st.position === 'fixed' || st.position === 'sticky') {
                        window.atnHeaderOffset = header.offsetHeight || 0;
                    }
                }
            }
            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', detectHeaderOffset);
            } else {
                detectHeaderOffset();
            }
            window.addEventListener('resize', detectHeaderOffset);
        })();

        /* ============== Toast ============== */
        function ensureToast() {
            var t = document.getElementById(TOAST_ID);
            if (!t) {
                t = document.createElement('div');
                t.id = TOAST_ID;
                t.setAttribute('role', 'status');
                t.setAttribute('aria-live', 'polite');
                document.body.appendChild(t);
            }
            return t;
        }

        function showToast(msg) {
            var t = ensureToast();
            t.textContent = msg;
            if (t.classList) t.classList.add('is-visible');
            else t.className += ' is-visible';
            clearTimeout(showToast._t);
            showToast._t = setTimeout(function() {
                if (t.classList) t.classList.remove('is-visible');
                else t.className = t.className.replace(/\bis-visible\b/, '');
            }, 1400);
        }

        /* ============== Prism loader (markup, css, clike, javascript) - plugin NOT used ============== */
        function loadPrism(cb) {
            if (window.Prism) {
                cb && cb();
                return;
            }
            var link = document.createElement('link');
            link.rel = 'stylesheet';
            link.href = 'https://cdn.jsdelivr.net/npm/prismjs@1/themes/prism.min.css';
            document.head.appendChild(link);
            var core = document.createElement('script');
            core.src = 'https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-core.min.js';
            core.defer = true;
            core.onload = function() {
                var parts = ['markup', 'css', 'clike', 'javascript'],
                    ok = 0;
                for (var i = 0; i < parts.length; i++) {
                    (function(n) {
                        var s = document.createElement('script');
                        s.src = 'https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-' + n + '.min.js';
                        s.defer = true;
                        s.onload = function() {
                            ok++;
                            if (ok === parts.length) {
                                cb && cb();
                            }
                        };
                        document.head.appendChild(s);
                    })(parts[i]);
                }
            };
            document.head.appendChild(core);
        }

        /* ============== Helpers ============== */
        function encodeHtml(s) {
            return String(s).replace(/[&<>"']/g, function(c) {
                return {
                    '&': '&amp;',
                    '<': '&lt;',
                    '>': '&gt;',
                    '"': '&quot;',
                    "'": '&#39;'
                } [c];
            });
        }

        function decodeHtml(s) {
            var ta = document.createElement('textarea');
            ta.innerHTML = s;
            return ta.value;
        }

        function getData(box, name, def) {
            var v = box.getAttribute('data-' + name);
            return (v === null || typeof v === 'undefined') ? def : v;
        }

        function getDataBool(box, name, def) {
            var v = getData(box, name, null);
            if (v === null) return def;
            return String(v).toLowerCase() !== 'false';
        }

        function getDataNum(box, name, def) {
            var v = getData(box, name, null);
            if (v === null) return def;
            var n = Number(v);
            return isNaN(n) ? def : n;
        }

        function normalizePreToText(pre) {
            var raw = pre.__atnRawHtml ? pre.__atnRawHtml :
                (pre.querySelector && pre.querySelector('code') ? pre.querySelector('code').innerHTML : pre.innerHTML);
            var html = String(raw).replace(/\r\n/g, '\n').replace(/<br\s*\/?>/gi, '\n').replace(/<\/?[^>]+>/g, '');
            var text = decodeHtml(html);
            text = text.replace(/^\s*\n+/, '').replace(/\n+\s*$/, '');
            return text;
        }

        function decodeAndNormalize(html) {
            return decodeHtml(String(html).replace(/\r\n/g, '\n').replace(/<br\s*\/?>/gi, '\n').replace(/&nbsp;|\u00a0/gi, ' ')).replace(/^\s*\n+/, '').replace(/\n+\s*$/, '');
        }

        function extractBlocks(src) {
            var css = [],
                js = [],
                m, rStyle = /<style[^>]*>([\s\S]*?)<\/style>/gi,
                rScript = /<script[^>]*>([\s\S]*?)<\/script>/gi;
            while ((m = rStyle.exec(src)) !== null) {
                css.push(m[1]);
            }
            while ((m = rScript.exec(src)) !== null) {
                js.push(m[1]);
            }
            var html = src.replace(rStyle, '').replace(rScript, '').trim();
            return {
                html: html,
                css: css.join('\n\n').trim(),
                js: js.join('\n\n').trim()
            };
        }

        function editorsMask(h, c, j) {
            return (h ? 1 : 0) + '' + (c ? 1 : 0) + '' + (j ? 1 : 0);
        }

        function djb2(str) {
            var h = 5381,
                i = 0,
                c;
            for (i = 0; i < str.length; i++) {
                c = str.charCodeAt(i);
                h = ((h << 5) + h) + c;
                h = h & 0xffffffff;
            }
            if (h < 0) h = (h >>> 0);
            return h.toString(36);
        }

        function buildStableId(box, codeText, title, ext) {
            if (box.id) return box.id;
            var dataId = getData(box, 'id', null);
            if (dataId) {
                box.id = dataId;
                return dataId;
            }
            var key = (title || '') + '|' + (ext || '') + '|' + (codeText || '');
            if (key.length > 800) key = key.slice(0, 800);
            var id = 'atn-code-' + djb2(key);
            var n = 1,
                base = id;
            while (document.getElementById(id)) {
                id = base + '-' + (++n);
            }
            box.id = id;
            return id;
        }

        function smoothScrollToEl(el) {
            var headerOffset = window.atnHeaderOffset || 64;
            var topNow = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
            var rect = el.getBoundingClientRect();
            var y = rect.top + topNow - headerOffset;
            try {
                window.scrollTo({
                    top: y,
                    behavior: 'smooth'
                });
            } catch (_) {
                window.scrollTo(0, y);
            }
        }

        /* ============== NEW: createLineNumbers (đếm dòng chuẩn 1–1) ============== */
        function createLineNumbers(pre, box) {
            try {
                // remove old nếu có
                var old = pre.querySelector('.line-numbers-rows');
                if (old) old.remove();

                // code element
                var codeEl = pre.querySelector('code') || pre;
                var codeText = (codeEl.textContent || '');
                var lineCount = ((codeText.match(/\n/g) || []).length) + (codeText ? 1 : 0);

                // build container
                var rows = document.createElement('span');
                rows.className = 'line-numbers-rows';
                rows.setAttribute('aria-hidden', 'true');

                for (var i = 1; i <= lineCount; i++) {
                    var sp = document.createElement('span');
                    sp.textContent = i;
                    rows.appendChild(sp);
                }

                pre.insertBefore(rows, pre.firstChild);
            } catch (e) {}
        }

        /* ============== Core bind ============== */
        function bindBox(box) {
            if (box.getAttribute('data-atnBound') === '1') return;
            var pre = box.querySelector ? box.querySelector('pre') : null;
            if (!pre) return;
            if (!pre.__atnRawHtml) pre.__atnRawHtml = pre.innerHTML;

            // bật class line-numbers (CSS chừa chỗ)
            try {
                if (pre.classList) pre.classList.add('line-numbers');
                else pre.className = (pre.className ? pre.className + ' ' : '') + 'line-numbers';
            } catch (e) {}

            var lang = String(getData(box, 'lang', '')).toLowerCase();
            var title = getData(box, 'title', 'snippet.txt');
            var filename = (getData(box, 'filename', title) || '').replace(/\s+/g, '_');
            var ext = String(getData(box, 'ext', '.txt')).toLowerCase();
            var delaySec = getDataNum(box, 'delay', 0);
            var enableCopy = getDataBool(box, 'copy', true);
            var enableDL = getDataBool(box, 'download', true);
            var enablePrev = getDataBool(box, 'preview', true);
            var prevTheme = getData(box, 'previewTheme', 'dark');

            // ======= LẤY CODE TEXT =======
            var codeText;
            if (lang === 'html') {
                var rawHTML = pre.__atnRawHtml ? pre.__atnRawHtml : pre.innerHTML;
                codeText = decodeHtml(String(rawHTML).replace(/\r\n/g, '\n').replace(/<br\s*\/?>/gi, '\n'));
                codeText = codeText.replace(/^\s*\n+/, '').replace(/\n+\s*$/, '');
            } else {
                codeText = normalizePreToText(pre);
            }

            var lineCount = ((codeText.match(/\n/g) || []).length) + (codeText ? 1 : 0);
            buildStableId(box, codeText, title, ext);

            var prismClass = (lang === 'html') ? 'language-markup' : (lang === 'css') ? 'language-css' :
                (lang === 'js') ? 'language-javascript' : 'language-markup';

            pre.innerHTML = '<code class="' + prismClass + '">' + encodeHtml(codeText) + '</code>';
            var codeEl = pre.querySelector ? pre.querySelector('code') : null;

            var bar = document.createElement('div');
            bar.className = 'atn-bar';
            bar.innerHTML = '' +
                '<div class="atn-title" title="' + encodeHtml(title) + '">' + encodeHtml(title) + '</div>' +
                '<div class="atn-meta">' + lineCount + ' dòng</div>' +
                '<div class="atn-actions"></div>';
            var actions = bar.querySelector ? bar.querySelector('.atn-actions') : null;

            function mkBtn(cls, ttl, svg) {
                var b = document.createElement('button');
                b.type = 'button';
                b.className = 'atn-btn ' + cls;
                b.title = ttl;
                b.setAttribute('aria-label', ttl);
                b.innerHTML = svg;
                return b;
            }

            /* Copy Link + ép cuộn */
            var btnLink = mkBtn('atn-btn-link', 'Sao chép liên kết CodeBox',
                '<svg viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 1 0-7l1-1a5 5 0 0 1 7 7l-1 1"/><path d="M14 11a5 5 0 0 1 0 7l-1 1a5 5 0 0 1-7-7l1-1"/></svg>');
            btnLink.onclick = function() {
                var url = (location.origin || '') + (location.pathname || '') + '#' + box.id;
                try {
                    if (navigator.clipboard && window.isSecureContext) {
                        navigator.clipboard.writeText(url).then(function() {
                            showToast('Đã copy liên kết CodeBox');
                        }, function() {
                            showToast('Không copy được liên kết');
                        });
                    } else {
                        var ta = document.createElement('textarea');
                        ta.value = url;
                        document.body.appendChild(ta);
                        ta.select();
                        document.execCommand('copy');
                        ta.parentNode.removeChild(ta);
                        showToast('Đã copy liên kết CodeBox');
                    }
                } catch (_) {
                    showToast('Không copy được liên kết');
                }
                if (location.hash !== '#' + box.id) {
                    location.hash = box.id;
                }
                smoothScrollToEl(box);
                box.style.outline = '2px solid #2563eb';
                box.style.outlineOffset = '-2px';
                setTimeout(function() {
                    box.style.outline = '';
                }, 1200);
            };
            if (actions) actions.appendChild(btnLink);

            /* Preview */
            if (enablePrev) {
                var btnPrev = mkBtn('atn-btn-preview', 'Xem trước', '<svg viewBox="0 0 24 24"><path d="M3 7h18M3 12h18M3 17h18"/></svg>');
                btnPrev.onclick = function() {
                    var dark = (prevTheme === 'dark');
                    var style = dark ?
                        'html,body{margin:0;background:#0b1220;color:#e5e7eb;font:14px/1.5 monospace}pre{white-space:pre-wrap;word-break:break-word;padding:20px}' :
                        'html,body{margin:0;background:#fff;color:#111;font:14px/1.5 monospace}pre{white-space:pre-wrap;word-break:break-word;padding:20px}';
                    var doc = '<!doctype html><meta charset="utf-8"><title>' + encodeHtml(filename + ext) + '</title><style>' + style + '</style><pre>' + encodeHtml(codeText) + '</pre>';
                    var blob = new Blob([doc], {
                        type: 'text/html'
                    });
                    var url = URL.createObjectURL(blob);
                    window.open(url, '_blank');
                    setTimeout(function() {
                        URL.revokeObjectURL(url);
                    }, 30000);
                };
                if (actions) actions.appendChild(btnPrev);
            }

            /* Download */
            if (enableDL) {
                var btnDL = mkBtn('atn-btn-download', 'Tải xuống', '<svg viewBox="0 0 24 24"><path d="M12 3v12m0 0 4-4m-4 4-4-4M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/></svg>');
                btnDL.onclick = function() {
                    function start() {
                        if (box.classList) box.classList.add('is-downloading');
                        var blob = new Blob([codeText], {
                            type: 'text/plain'
                        });
                        var a = document.createElement('a');
                        a.href = URL.createObjectURL(blob);
                        a.download = filename + ext;
                        document.body.appendChild(a);
                        a.click();
                        a.parentNode.removeChild(a);
                        setTimeout(function() {
                            URL.revokeObjectURL(a.href);
                        }, 30000);
                        setTimeout(function() {
                            if (box.classList) box.classList.remove('is-downloading');
                        }, 900);
                        showToast('Đã bắt đầu tải file');
                    }
                    if (delaySec > 0) {
                        var r = delaySec,
                            meta = bar.querySelector('.atn-meta'),
                            old = meta.textContent;
                        var timer = setInterval(function() {
                            meta.textContent = 'Đang chờ ' + (r--) + 's…';
                            if (r < 0) {
                                clearInterval(timer);
                                meta.textContent = old;
                                start();
                            }
                        }, 1000);
                    } else start();
                };
                if (actions) actions.appendChild(btnDL);
            }

            /* Copy */
            if (enableCopy) {
                var btnCopy = mkBtn('atn-btn-copy', 'Copy', '<svg viewBox="0 0 24 24"><path d="M9 9h9a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2Z"/><path xmlns="http://www.w3.org/2000/svg" d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>');
                btnCopy.onclick = function() {
                    try {
                        if (navigator.clipboard && window.isSecureContext) {
                            navigator.clipboard.writeText(codeText).then(function() {
                                if (box.classList) box.classList.add('is-copied');
                                setTimeout(function() {
                                    if (box.classList) box.classList.remove('is-copied');
                                }, 900);
                                showToast('Đã copy nội dung');
                            }, function() {
                                showToast('Copy không thành công');
                            });
                        } else {
                            var ta = document.createElement('textarea');
                            ta.value = codeText;
                            document.body.appendChild(ta);
                            ta.select();
                            document.execCommand('copy');
                            ta.parentNode.removeChild(ta);
                            if (box.classList) box.classList.add('is-copied');
                            setTimeout(function() {
                                if (box.classList) box.classList.remove('is-copied');
                            }, 900);
                            showToast('Đã copy nội dung');
                        }
                    } catch (_) {
                        showToast('Copy không thành công');
                    }
                };
                if (actions) actions.appendChild(btnCopy);
            }

            /* CodePen (nếu có HTML/CSS/JS) */
            var hasHtmlExt = /\.(html?)$/.test(ext),
                hasCssExt = /\.(css)$/.test(ext),
                hasJsExt = /\.(js)$/.test(ext);
            var rawBefore = pre.__atnRawHtml || '';
            var tagPresent = /<script[\s>]/i.test(rawBefore) || /<style[\s>]/i.test(rawBefore);
            var hasLang = (lang === 'html' || lang === 'css' || lang === 'js');
            var shouldPen = hasLang || hasHtmlExt || hasCssExt || hasJsExt || tagPresent;
            if (shouldPen) {
                var btnPen = mkBtn('atn-btn-codepen', 'Mở CodePen', '<svg viewBox="0 0 24 24"><path d="M12 2l9.5 6v8L12 22 2.5 16V8L12 2z"/><path d="M12 22V12M21.5 8 12 12 2.5 8M21.5 16 12 12 2.5 16"/></svg>');
                btnPen.onclick = function() {
                    var originalDecoded = decodeAndNormalize(rawBefore),
                        html = '',
                        css = '',
                        js = '';
                    if (lang === 'html') {
                        var p = extractBlocks(originalDecoded);
                        html = p.html;
                        css = p.css;
                        js = p.js;
                    } else if (lang === 'css') {
                        css = decodeAndNormalize(codeText);
                    } else if (lang === 'js') {
                        js = decodeAndNormalize(codeText);
                    } else {
                        if (hasHtmlExt || tagPresent) {
                            var p2 = extractBlocks(originalDecoded);
                            html = p2.html;
                            css = p2.css;
                            js = p2.js;
                        } else if (hasCssExt) {
                            css = decodeAndNormalize(codeText);
                        } else if (hasJsExt) {
                            js = decodeAndNormalize(codeText);
                        }
                    }
                    if (!html && !css && !js) html = decodeAndNormalize(codeText);
                    var data = {
                        title: title,
                        editors: editorsMask(html, css, js),
                        html: html,
                        css: css,
                        js: js
                    };
                    var form = document.createElement('form');
                    form.action = 'https://codepen.io/pen/define';
                    form.method = 'POST';
                    form.target = '_blank';
                    var input = document.createElement('input');
                    input.type = 'hidden';
                    input.name = 'data';
                    input.value = JSON.stringify(data);
                    form.appendChild(input);
                    document.body.appendChild(form);
                    form.submit();
                    form.parentNode.removeChild(form);
                };
                if (actions) actions.appendChild(btnPen);
            }

            box.insertBefore(bar, pre);
            box.setAttribute('data-atnBound', '1');

            /* Highlight then create line numbers */
            loadPrism(function() {
                try {
                    if (window.Prism && codeEl) window.Prism.highlightElement(codeEl);
                } catch (_) {}
                // let layout settle then create gutter
                setTimeout(function() {
                    try {
                        createLineNumbers(pre, box);
                    } catch (e) {}
                }, 30);
            });
        }

        function init() {
            var list = document.querySelectorAll ? document.querySelectorAll('.atn-pre') : [];
            for (var i = 0; i < list.length; i++) {
                bindBox(list[i]);
            }
        }

        /* ============== Lifecycle ============== */
        if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
        else init();
        var mo = new MutationObserver(function() {
            init();
        });
        mo.observe(document.body, {
            childList: true,
            subtree: true
        });

        /* ============== Resize: recalc gutter ============== */
        var __atnResizeTimer = null;
        window.addEventListener('resize', function() {
            clearTimeout(__atnResizeTimer);
            __atnResizeTimer = setTimeout(function() {
                var pres = document.querySelectorAll('.atn-pre pre.line-numbers');
                for (var i = 0; i < pres.length; i++) {
                    try {
                        createLineNumbers(pres[i], pres[i].closest('.atn-pre'));
                    } catch (e) {}
                }
            }, 120);
        });

        /* ============== Robust hash jump (retry) ============== */
        function robustJump() {
            var id = decodeURIComponent((location.hash || '').replace(/^#/, ''));
            if (!id) return;
            var tries = 0,
                max = 80;
            if (robustJump._timer) clearInterval(robustJump._timer);
            robustJump._timer = setInterval(function() {
                var el = document.getElementById(id);
                if (!el) {
                    if (++tries >= max) clearInterval(robustJump._timer);
                    return;
                }
                var node = el,
                    host = null;
                while (node && node !== document.body) {
                    if ((' ' + node.className + ' ').indexOf(' atn-pre ') > -1 || node.className === 'atn-pre') {
                        host = node;
                        break;
                    }
                    node = node.parentNode;
                }
                if (host) el = host;
                clearInterval(robustJump._timer);
                setTimeout(function() {
                    try {
                        if (el.getAttribute('data-atnBound') !== '1' && window.ATNCodeBox && ATNCodeBox.init) {
                            ATNCodeBox.init();
                        }
                    } catch (e) {}
                    smoothScrollToEl(el);
                    el.style.outline = '2px solid #2563eb';
                    el.style.outlineOffset = '-2px';
                    setTimeout(function() {
                        el.style.outline = '';
                    }, 1200);
                }, 30);
            }, 100);
        }
        window.addEventListener('load', robustJump);
        window.addEventListener('hashchange', robustJump);

        // public API
        window.ATNCodeBox = {
            init: init,
            jump: robustJump
        };
    })();
    //]]>
</script>
<!-- ============ /ATN CODEBOX ============ -->
 

Khi cần chèn code, sử dụng khối <div class="atn-pre"> bao quanh <pre>...</pre> như sau:

<div class="atn-pre"
     data-copy="true" /* tắt/mở copy code */
     data-download="true" /* tắt/mở download code */
     data-ext=".html" /* luôn để mặt định */
     data-filename="atn-codebox" /* tên file code khi bấm download xuống */
     data-id="atn-codebox" /* để khi sao chép liên kết sẽ đi thẳng tới codebox qua id mình đặt tên này */
     data-lang="html" /* luôn để mặt định */
     data-preview="true" /* tắt/mở xem trước code */
     data-title="ATN Codebox ProVIP"> /* tên hiển thị trên đầu khung code, muốn đặt gì thì đặt */
 <pre>
 	/* Code của AE đặt ở đây (nhớ mã hóa code trước khi chèn vào đây, mọi thao tác chèn code như này đều ở phần HTML) */
 </pre>
</div>

Tùy chỉnh data-title, data-filename, data-id theo tên các bạn đặt nha, chỉ thường xuyên sửa 3 thằng này thôi (tui có giải thích kỹ trong code rồi).

Hướng dẫn chi tiết sử dụng ATN CodeBox
Hướng dẫn chi tiết sử dụng ATN CodeBox.

Với lại, nhớ Mã hóa code (ảnh trên ảnh dưới là chi tiết lắm luôn rồi đó) xong chèn vào phần pre như ảnh tui đăng để cho code hiển thị đẹp nhất nha!

Trang mã hóa code trên Anh Trai Nắng blog
Trang mã hóa code có dạng như này.

Kết luận

Nếu anh em đang tìm giải pháp hiển thị code đẹp, tiện ích, hiện đại trên Blogspot, thì ATN CodeBox là lựa chọn hoàn hảo. Không chỉ giúp blog chuyên nghiệp hơn, mà còn nâng cao trải nghiệm người đọc khi tham khảo code mẫu.

👉 Hãy thử cài đặt ngay để blog của bạn thêm phần “đẳng cấp” nhé!

Chia sẻ:
Đã sao chép link!

Bài viết liên quan

Nhận xét (2)

Hiển thị
  1. Tổng thể code ổn, đầy đủ fallback và UX mượt. Tuy nhiên vẫn còn vài điểm nên tối ưu:
    - Copy link bỏ mất query string → nên giữ nguyên toàn bộ URL + hash.
    - MutationObserver đang bắt toàn bộ body, dễ tốn tài nguyên → nên debounce và lọc selector.
    - Line-numbers reflow nhẹ, chưa co giãn theo số chữ số → nên tính gutter động và chỉ re-build khi cần.
    - Chỉ load 4 ngôn ngữ Prism, thiếu python/json/bash… → cân nhắc dùng autoloader hoặc nạp thêm.
    - A11y: cần bổ sung role/aria cho container và pre để screen reader dễ hiểu.
    Ngoài ra, header offset có thể sai nếu header sticky thay đổi chiều cao khi scroll.

    Trả lờiXóa
    Trả lời
    1. để copy đống tối ưu này bỏ vào chat gpt cho nó thêm 😁

      Xóa

Đăng nhận xét