備忘録
備忘録
学びと解説
学びと解説
Python
Python
Linux
Linux

記事内に商品プロモーションを含む場合があります。

はてなブログのコードブロックにコピーボタンを追加する方法【コピペで実装可】

はじめに

はてなブログにおいて、コードブロックの利便性を高めるためには「コピーボタン」を導入することが効果的だ。

この記事では、Markdown記法で記述された記事にコードブロックのコピーボタンを追加する方法について、初心者でも分かりやすく手順を解説する。


手順

1. デザインの設定

まず、はてなブログの「デザイン設定」を開き、「カスタマイズ」タブをクリックする。

2. CSSの設定

以下の手順でCSSを設定する。 1. 「デザインCSS」を選択する。 2. 以下のコードをコピーし、入力欄に貼り付ける。

/* コードブロックのコンテナ */
.code-block-container {
    position: relative;
    background: #1D2020;
    font-size: 14px;
    font-weight: 500;
    color: #FFFFFF;
    overflow: visible;
    border-radius: 6px;
    margin-bottom: 16px !important;
}

/* コピーボタン */
.copy-button, 
.gist-copy-button {
    position: absolute;
    top: 5px;
    right: 5px;
    padding: 4px 8px;
    font-size: 12px;
    font-weight: 500;
    background-color: rgba(255, 255, 255, 0.8);
    color: #333;
    border: 1px solid #d1d5db;
    border-radius: 4px;
    cursor: pointer;
    z-index: 1000;
    transition: all 0.2s ease;
    opacity: 1; /* デフォルトで表示 */
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

/* アクティブ時やホバー時 */
.code-block-container.active .copy-button,
.gist.active .gist-copy-button,
.copy-button:hover,
.gist-copy-button:hover {
    opacity: 0.9; /* 完全に表示 */
    background-color: #ffffff;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}

/* クリック時のスタイル */
.copy-button:active, 
.gist-copy-button:active {
    background-color: #e5e7eb;
}

これでコードブロックの見た目とコピーボタンのスタイルが適用される。

3. フッターHTMLの設定

「カスタマイズ」タブの「フッターHTML」に以下のJavaScriptを追加する。これにより、コードブロックにコピーボタンが動的に追加される。

<script>
/**
 * コードブロックに言語名のブロックを追加する関数
 * @param {HTMLElement} container - コードブロックのコンテナ要素
 * @param {HTMLElement} block - コードブロック
 */
function addFileNameBlock(container, block) {
    const language = block.getAttribute('data-lang');
    if (language && !container.querySelector('.file-name-block')) {
        const fileNameBlock = document.createElement('div');
        fileNameBlock.className = 'file-name-block';
        fileNameBlock.textContent = language;
        container.insertBefore(fileNameBlock, container.firstChild);
    }
}

/**
 * 通常のコードブロックにコピーボタンを追加する関数
 */
function addCopyButtons() {
    document.querySelectorAll('pre:not(.gist pre)').forEach((block) => {
        if (!block.closest('.code-block-container')) {
            const container = document.createElement('div');
            container.className = 'code-block-container';
            block.parentNode.insertBefore(container, block);
            container.appendChild(block);
        }

        const container = block.closest('.code-block-container');

        // ファイル名ブロックの追加
        addFileNameBlock(container, block);

        // コピーボタンがまだない場合のみ追加
        if (!container.querySelector('.copy-button')) {
            const copyButton = document.createElement('button');
            copyButton.className = 'copy-button';
            copyButton.textContent = 'Copy';
            container.insertBefore(copyButton, container.firstChild);

            // コピーボタンのクリックイベント
            copyButton.addEventListener('click', (e) => {
                e.stopPropagation();
                const codeElement = block.querySelector('code') || block;
                const code = codeElement.innerText.trim();
                copyTextToClipboard(code, copyButton);
            });

            // コンテナのクリックでアクティブ状態を切り替え
            container.addEventListener('click', () => {
                container.classList.toggle('active');
            });
        }
    });
}

/**
 * Gistにコピーボタンを追加する関数
 */
function addGistCopyButtons() {
    console.log('Adding copy buttons to Gists');
    document.querySelectorAll('.gist').forEach((gist) => {
        if (!gist.querySelector('.gist-copy-button')) {
            const copyButton = document.createElement('button');
            copyButton.className = 'gist-copy-button';
            copyButton.textContent = 'Copy';
            gist.appendChild(copyButton);

            // Gistのコピーボタンのクリックイベント
            copyButton.addEventListener('click', (e) => {
                e.stopPropagation();
                const codeElement = gist.querySelector('.js-file-line-container');
                if (codeElement) {
                    const code = codeElement.innerText.trim();
                    copyTextToClipboard(code, copyButton);
                } else {
                    console.error('Could not find code element in Gist');
                    updateButtonText(copyButton, false);
                }
            });

            // Gistのクリックでアクティブ状態を切り替え
            gist.addEventListener('click', () => {
                gist.classList.toggle('active');
            });
        }
    });
}

/**
 * テキストをクリップボードにコピーする関数
 * @param {string} text - コピーするテキスト
 * @param {HTMLElement} button - コピーボタン要素
 */
function copyTextToClipboard(text, button) {
    if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text)
            .then(() => updateButtonText(button, true))
            .catch((err) => {
                console.error('Clipboard write failed:', err);
                fallbackCopyTextToClipboard(text, button);
            });
    } else {
        fallbackCopyTextToClipboard(text, button);
    }
}

/**
 * クリップボードへのコピーのフォールバック方法
 * @param {string} text - コピーするテキスト
 * @param {HTMLElement} button - コピーボタン要素
 */
function fallbackCopyTextToClipboard(text, button) {
    const textArea = document.createElement('textarea');
    textArea.value = text;
    textArea.style.position = 'fixed';
    document.body.appendChild(textArea);
    textArea.focus();
    textArea.select();

    try {
        const successful = document.execCommand('copy');
        updateButtonText(button, successful);
    } catch (err) {
        console.error('Fallback: Unable to copy', err);
        updateButtonText(button, false);
    } finally {
        document.body.removeChild(textArea);
    }
}

/**
 * コピーボタンのテキストを更新する関数
 * @param {HTMLElement} button - コピーボタン要素
 * @param {boolean} successful - コピー操作が成功したかどうか
 */
function updateButtonText(button, successful) {
    button.textContent = successful ? '✅Copied' : '❎Copy Failure';
    setTimeout(() => {
        button.textContent = 'Copy';
    }, 2000);
}

/**
 * コピーボタンの初期化関数
 */
function initializeCopyButtons() {
    addCopyButtons();
    addGistCopyButtons();
}

// 初期化のイベント登録
document.addEventListener('DOMContentLoaded', initializeCopyButtons);
window.addEventListener('load', initializeCopyButtons);

// 動的に追加されたコードブロックに対応するためのMutationObserver
const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
        if (mutation.addedNodes && mutation.addedNodes.length > 0) {
            initializeCopyButtons();
        }
    });
});

observer.observe(document.body, {
    childList: true,
    subtree: true,
});
</script>

4. 確認と微調整

ブログをプレビューして、コードブロックに「Copy」ボタンが表示されているか確認する。


まとめ

本記事では、はてなブログにコードブロックのコピーボタンを追加する方法を紹介した。 この設定を導入することで、読者は簡単にコードをコピーできるようになる。