Greasy Fork is available in English.

AOJ-copy-sample

beta, Arena を含む AOJ のサンプルをボタン一つでコピーできる拡張機能です。

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name         AOJ-copy-sample
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  beta, Arena を含む AOJ のサンプルをボタン一つでコピーできる拡張機能です。
// @author       stoq
// @include      /https:\/\/judge.u-aizu.ac.jp\/onlinejudge\/description.*/
// @include      https://onlinejudge.u-aizu.ac.jp/*
// @include      /https:\/\/onlinejudge.u-aizu.ac.jp\/services\/room.*/
// @include      /https:\/\/onlinejudge.u-aizu.ac.jp\/courses.*/
// @include      /https:\/\/onlinejudge.u-aizu.ac.jp\/challenges.*/
// @require https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js
// @require https://code.jquery.com/jquery-3.2.1.min.js
// ==/UserScript==

(function () {
  'use strict';

  const AOJ = 0;
  const AOJArena = 1;
  const AOJBeta = 2;
  let site;

  function main() {
    const head = document.getElementsByTagName('head')[0];

    // ボタンとポップアップ の css を定義
    let css_element = document.createElement('style');
    css_element.type = 'text/css';
    css_element.textContent =
      `
      ${site === AOJ ? ".wrapper" : ""}
      ${site === AOJArena ? "#description_html" : ""}
      ${site === AOJBeta ? ".problemBody" : ""}
      {
        position:relative;
      }
      .btn-clipboard {
      position: absolute;
      right: 0;
      display: block;
      padding: .25rem .5rem;
      font-size: ${site != AOJ ? 11.9 : 13}px;
      color: #767676;
      background-color: #fff;
      border: 1px solid #ccc;
      border-radius: 4px;
      cursor: pointer;
      vertical-align: top;
      user-select: none;
      ${site === AOJArena ? "margin: 0px 15px;" : ""}
      ${site === AOJBeta ? "margin: 2.0px 0.3px 0px;" : ""}
      }
      button:focus {
        outline:0;
      }
      .popup{
        display: none;
        width: 255px;
        height: 60px;
        z-index: 10;
        position: fixed;
        padding: 10px 10px 10px 10px;
        right:20px;
        bottom:20px;
        color: #fff;
        background-color: #191919;
        border-radius: .25rem;
      }`;
    head.appendChild(css_element);

    // ポップアップを作成
    let body = document.getElementsByTagName('body')[0];
    body.insertAdjacentHTML('beforeend', '<div class="popup">コピーしました</div>');

    let description; // 問題文部分
    if (site === AOJ) {
      const page = document.getElementById('page');
      description = page.getElementsByClassName('wrapper')[0].getElementsByClassName('description')[0];
    } else if (site === AOJArena) {
      description = document.getElementById('description_html');
    } else if (site === AOJBeta) {
      description = document.getElementsByClassName('problemBody')[0];
    }

    // pre タグごとにコピーボタンを作成
    const pre_elements = description.getElementsByTagName('pre');
    for (let i = 0; i < pre_elements.length; i++) {
      if (pre_elements[i].childElementCount > 0) continue;
      const pre_id = 'pre-' + String(i);
      const button_id = 'button-' + String(i);
      pre_elements[i].setAttribute('id', pre_id);
      let buttonHTML = document.createElement('button');
      buttonHTML.target = pre_id;
      buttonHTML.type = 'button';
      buttonHTML.textContent = 'Copy';
      buttonHTML.classList.add("btn-clipboard");
      buttonHTML.addEventListener("mouseover", () => {
        buttonHTML.style.color = '#fff';
        buttonHTML.style.background = '#767676';
      }, false);
      buttonHTML.addEventListener("mouseleave", () => {
        buttonHTML.style.color = '#767676';
        buttonHTML.style.background = '#fff';
      }, false);
      pre_elements[i].before(buttonHTML);
    }

    // コピーとポップアップの表示
    let cancelFlag = false;
    $(".btn-clipboard").on('click', function (event) {
      const pre_id = event.target.target;
      const copy_str = document.getElementById(pre_id).innerText;
      navigator.clipboard.writeText(copy_str)
        .then(() => {
          if (cancelFlag) return;
          cancelFlag = true;
          $('.popup').fadeIn(400);
          $(".popup").delay(1000);
          $('.popup').fadeOut(400);
          setTimeout(function () {
            cancelFlag = false;
          }, 400 + 1000 + 400);
        })
        .catch(err => {
          alert('error');
        });
    });
  }

  window.onload = function () {
    if (location.hostname === 'judge.u-aizu.ac.jp') {
      site = AOJ;
    } else if (location.href.indexOf('https://onlinejudge.u-aizu.ac.jp/services/room') === 0) {
      site = AOJArena;
    } else {
      site = AOJBeta;
    }

    if (site == AOJ) {
      main();
    } else if (site == AOJArena) {
      const target = document.getElementById('description_html');
      let executing = false;
      function callback(mutations) {
        if (target.innerHTML) {
          if (executing || target.getElementsByClassName('btn-clipboard')[0]) return;
          executing = true;
          main();
          executing = false;
        }
      }
      const observer = new MutationObserver(callback);
      const config = {
        childList: true,
        subtree: true
      };
      observer.observe(target, config);
    } else if (site == AOJBeta) {
      const target = document.getElementById('content');
      function callback(mutations) {
        for (let mutation of mutations) {
          for (let node of mutation.addedNodes) {
            if (!(node instanceof HTMLElement)) continue;
            if (node.getElementsByClassName('problemBody').length > 0) {
              main();
            }
          }
        }
      }
      const observer = new MutationObserver(callback);
      const config = {
        childList: true,
        subtree: true
      };
      observer.observe(target, config);
    }
  }
})();