Qiitaの記事中のソースコードをウインドウの幅で表示するユーザースクリプト

　Qiitaは3カラムレイアウトの真ん中のカラムにしかソースコードが表示されず、ウインドウ幅を広くしてもソースコード表示領域はあまり広がりません。

　それでもあまり困らない場合が多かったのですが、最近はSwiftのソースコードをよく見るようになりもっと幅がほしくなったのでユーザースクリプトを作りました。

　これを使ってQiitaのソースコード表示領域にマウスカーソルを乗せると、コピーボタンの横にボタンが増えます。

![Buttons.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157704/492507bb-3102-65e7-f8c8-fe799c062cc8.png)

　クリックするとウインドウ内のほぼ全面に、モーダルダイアログでソースコードを表示します。×ボタンやモーダルの外をクリックして閉じます。

![Dialog.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157704/9ffec790-80fe-585c-4f0d-64201c100488.png)


## ソースコード

```js
// ==UserScript==
// @name         Qiitaの記事中のソースコードをウインドウの幅で表示
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://qiita.com/*/items/*
// @grant        none
// ==/UserScript==

(() => {
  const style = `
    #source-dialog {
      background: #0008;
      border: none;
      position: fixed;
      width: 100%;
      height: 100%;
      z-index: 999;
    }

    #source-dialog > .card {
      background: #364549;
      border-radius: .5rem;
      height: 100%;
      padding: 0 0 1.5rem 0;
      width: 100%;
      display: flex;
      flex-direction: column;
    }

    #source-dialog > .card > .card-header {
      text-align: right;
      padding-bottom: .5rem;
    }

    #source-dialog > .card > .card-header > .close-button {
      color: white;
    }

    #source-dialog > .card > .card-body {
      flex-grow: 1;
      height: 0;
    }

    #source-dialog > .card > .card-body > .code-frame {
      height: 100%;
    }

    #source-dialog > .card > .card-body > .code-frame > .highlight {
      height: 100%;
      overflow-y: auto;
    }

    .code-copy {
      right: 48px !important;
    }

    .code-open-dialog {
      display: none
    }

    .hovering .code-open-dialog {
      display: flex;
      position: absolute;
      transform: translateY(-8px);
      font-size: 1.5rem;
    }

    .it-MdContent .code-open-dialog {
      right: 32px;
    }

    .co-Item .code-open-dialog {
      right: 16px;
    }

    .open-dialog {
      color: #c8c8c8;
      float: right;
      padding: .5rem;
      margin-top: -0.2rem;
    }
    .p-items_article i.open-dialog {
      margin-right: -1.5rem;
    }
  `;

  const element = document.createElement('style');
  element.innerHTML = style;
  document.head.appendChild(element);

  document.body.insertAdjacentHTML('afterbegin', `
    <dialog id="source-dialog">
      <div class="card">
        <div class="card-header">
          <i class="fa fa-fw fa-close fa-lg close-button"></i>
        </div>
      <div class="card-body">
        <div class="code-frame it-MdContent" data-lang=""></div>
      </div>
    </div>
    </dialog>
  `);

  const wrapperElement = document.querySelector('.allWrapper');
  const dialogElement = document.getElementById('source-dialog');
  const cardElement = dialogElement.querySelector(':scope .card');
  const contentElement = cardElement.querySelector(':scope .code-frame');
  const closeButtonElement = cardElement.querySelector(':scope .close-button');

  const showDialog = codeFrameElement => {
    const language = codeFrameElement.getAttribute('data-lang');
    const highlight = codeFrameElement.querySelector(':scope > .highlight');

    const y = document.documentElement.scrollTop;
    wrapperElement.style.top = String(-1 * y) + 'px';
    wrapperElement.style.position = 'fixed';
    wrapperElement.style.width = '100%';
    wrapperElement.setAttribute('data-scroll-y', y);

    contentElement.setAttribute('data-lang', language);
    const clone = highlight.cloneNode(true);
    contentElement.appendChild(clone);

    dialogElement.setAttribute('open', 'open');
  };

  const closeDialog = () => {
    wrapperElement.style.top = null;
    wrapperElement.style.position = null;
    wrapperElement.style.width = null;
    const y = wrapperElement.getAttribute('data-scroll-y');
    document.documentElement.scrollTop = y;

    dialogElement.removeAttribute('open');

    contentElement.innerHTML = '';
  };

  cardElement.addEventListener('click', e => e.stopPropagation());
  closeButtonElement.addEventListener('click', _ => closeDialog());
  dialogElement.addEventListener('click', _ => closeDialog());

  const addIconToCodeFrame = codeFrame => {
    const iconDiv = document.createElement('div');
    iconDiv.className = 'code-open-dialog';

    const hilightDiv = codeFrame.querySelector(':scope .highlight');
    hilightDiv.insertAdjacentElement('beforebegin', iconDiv);

    const iconElement = document.createElement('i');
    iconElement.className = 'open-dialog fa fa-fw fa-external-link';
    iconDiv.insertAdjacentElement('beforeend', iconElement);
    iconElement.addEventListener('click', _ => showDialog(codeFrame));

    codeFrame.addEventListener('mouseover', _ => {
      codeFrame.classList.add('hovering');
    });
    codeFrame.addEventListener('mouseout', _ => {
      codeFrame.classList.remove('hovering');
    });
  };

  for(const codeFrame of document.querySelectorAll('.allWrapper .code-frame')) {
    addIconToCodeFrame(codeFrame);
  }

  const commentsDiv = document.getElementById('comments');
  new MutationObserver(records => {
    records.forEach(record => {
      for(const addedNode of record.addedNodes) {
        if (addedNode.classList.contains('co-Item')) {
          for(const codeFrame of addedNode.querySelectorAll(':scope .code-frame')) {
            console.log('.codeframe', codeFrame);
            addIconToCodeFrame(codeFrame);
          }
        }
      }
    });
  }).observe(commentsDiv, {childList: true});

})();
```