Greasy Fork is available in English.

Rain Classroom Helper

优化雨课堂使用体验

Nainštalovať tento skript?
Autor skriptu navrhuje

Tiež sa vám môže páčiť XuetangX Keyboard Shortcut.

Nainštalovať tento skript
// ==UserScript==
// @name         Rain Classroom Helper
// @namespace    https://raineggplant.com/
// @version      0.2.4
// @description  优化雨课堂使用体验
// @author       RainEggplant
// @match        *://www.yuketang.cn/web*
// @match        *://pro.yuketang.cn/web*
// @match        *://changjiang.yuketang.cn/web*
// @grant        GM_addStyle
// @require      https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js
// @homepageURL  https://github.com/RainEggplant/rain-classroom-helper
// ==/UserScript==

(function () {
  'use strict';
  const DEBUG = false;

  // 调整左边栏样式
  GM_addStyle(`
    .left .panel {
      padding-top: 34px !important;
      max-height: unset !important;
    }
    .nav-list {
      font-size: 18px !important;
    }
    .nav-item {
      height: 45px !important;
      line-height: 32px !important;
      padding-top: 6px !important;
    }
    .kecheng, .kejian, .shiti, .geren, .addlink {
      width: 30px !important;
    }
    .left .panel .nav-list .nav-item .name {
      padding-left: 18px !important;
    }
  `);

  // 调整右边栏样式
  GM_addStyle(`
    .right {
      width: 320px !important;
    }
    .right .control-panel {
      padding-top: 32px !important;
      max-height: unset !important;
    }
    .title {
      font-size: 22px !important;
    }
    .page-nav-control {
      width: 320px !important;
    }
    .page-control {
      padding-top: 15px !important;
    }
    .control-desc {
      display: none !important;
    }
    .page-nav {
      padding: unset !important;
      font-size: 18px !important;
    }
    .print-preview-box {
      margin: 0px 0 0 !important;
    }
    .contact-us {
      display: none;
    }
    .right .control-panel .page-control .paging-number-list .page-no:nth-child(5n) {
      margin: 0 14px 13px 0 !important;
     }
  `);

  // 调整中间 iframe 为自适应宽度
  GM_addStyle(`
    .wrapper-inner {
      width: 92% !important;
    }
    .center {
      width: auto !important;
      margin-left: 180px !important;
      margin-right: 320px !important;
      float: none !important;
    }
    .center .rain-iframe {
      width: calc(95% - 40px) !important;
      height: 95% !important;
    }
    .student__timeline-wrapper {
      top: 2.33rem !important;
    }
  `);

  // 调整布局
  waitForKeyElements('div.index-view.none.J_index', function () {
    // note: you must move div.right instead of div.center, or the sidebar
    // will lost its funtion
    $('div.right.fr').insertBefore($('div.center.fl'));
  });

  // 缩小 “体验新版” 尺寸
  waitForKeyElements('a.newWebEntry', function () {
    $('a.newWebEntry')
      .find('img')
      .attr('style', 'width: 150px; margin-top: 20px;');
  });

  // 添加右边栏视频框
  GM_addStyle(`
    #video-iframe {
      width: 320px;
      height: 304px;
    }
  `);

  waitForKeyElements('div.control-panel.Absolute-Center', function () {
    const videoIFrame = '<iframe id="video-iframe" src="about:blank" style="display: none;"/>';
    $('div.page-control.J_pageNo').after(videoIFrame);
  });

  // 添加 GitHub 项目图标
  waitForKeyElements('ul.nav-list', function () {
    const liAbout = `
      <li class="nav-item clearfix J_nav">
        <a href="https://github.com/RainEggplant/rain-classroom-helper" target="_blank">
          <img alt="GitHub stars" style="width:auto;" src="https://img.shields.io/github/stars/RainEggplant/rain-classroom-helper?style=social">
        </a>
      </li>
    `;
    $('ul.nav-list').append(liAbout);
  });

  waitForKeyElements('div.left.fl', function () {
    const divLeftHidden = `
      <div id="left-hidden" class="fl" style="display: none;">
        <p id="show-left" style="top: 50%; position: absolute; color: #fff; z-index: 1;">▶</p>
      </div>
    `;
    $('div.left.fl').before(divLeftHidden);
    $('#show-left').on('click', showLeftPanel);
  });

  // 添加控制样式的滑块
  waitForKeyElements('div.panel.Absolute-Center', function () {
    const panelWidthControls = `
      <div style="margin-top: 20px; color: #fff; font-size: 14px;">
        <p>
          <label for="panel-width">右边栏宽度</label>
          <input type="range" id="right-panel-width" value="320" min="200" max="576" style="width: 160px;">
        </p>
        <p>
          <label for="panel-width">全屏占比</label>
          <input type="range" id="content-width" value="92" min="50" max="98" style="width: 160px;">
        </p>
        <p>
          <button id="hide-left" type="button" style="color: #000; font-size: 12px;">◀ 隐藏左边栏</button>
        </p>
      </div>
    `;
    $('div.panel.Absolute-Center').append(panelWidthControls);
    $('#right-panel-width').on('change', setRightPanelWidth);
    $('#content-width').on('change', setContentWidth);
    $('#hide-left').on('click', hideLeftPanel);
  });

  function setRightPanelWidth() {
    const width = 'width: ' + $(this).val() + 'px !important;';
    const height = 'height: ' + (0.75 * $(this).val() + 60) + 'px !important;';
    const marginLeft = 'margin-left: ' + $('div.center.fl').css('margin-left') + ';';
    const marginRight = 'margin-right: ' + $(this).val() + 'px !important;';
    const iframeDisplay = 'display: ' + $('#video-iframe').css('display') + ';';
    $('div.right.fr').attr('style', width);
    $('div.page-nav-control').attr('style', width);
    $('div.center.fl').attr('style', marginLeft + ' ' + marginRight);
    $('#video-iframe').attr('style', width + ' ' + height + ' ' + iframeDisplay);
  }

  function setContentWidth() {
    const width = 'width: ' + $(this).val() + '% !important;';
    $('div.wrapper-inner.clearfix.J_inner').attr('style', width);
  }

  function hideLeftPanel() {
    const marginLeft = 'margin-left: 0px !important;';
    const marginRight = 'margin-right: '
      + $('div.center.fl').css('margin-right') + ' !important;';
    $('div.left.fl').css('display', 'none');
    $('div.center.fl').attr('style', marginLeft + ' ' + marginRight);
    $('#left-hidden').css('display', 'block');
  }

  function showLeftPanel() {
    const marginLeft = 'margin-left: 180px !important;';
    const marginRight = 'margin-right: '
      + $('div.center.fl').css('margin-right') + ' !important;';
    $('div.left.fl').css('display', 'block');
    $('div.center.fl').attr('style', marginLeft + ' ' + marginRight);
    $('#left-hidden').css('display', 'none');
  }

  waitForKeyElements('body', function () {
    // 中间 iframe 加载出内容后绑定处理视频的函数
    addVideoHandler();
  }, true, '#rainiframe');

  function addVideoHandler() {
    // 首次进入或通过左边栏改变页面时
    let iframeUrl = '';
    let isVideoLoaded = false;
    let iframeBody = $('#rainiframe').contents().find('body')[0];
    const iframeObserver = new MutationObserver(function () {
      DEBUG && console.log('iframe mutated');
      const newIFrameUrl = $('#rainiframe').contents()[0].location.href;
      DEBUG && console.log(newIFrameUrl);

      const videoSection = iframeBody.querySelector('section.live__wrap');
      if (videoSection) {
        // 存在视频
        // 去除中央 rainIFrame 中的视频
        $(videoSection).contents().find('video').removeAttr('src');
        $(videoSection).empty();

        if (iframeUrl && newIFrameUrl.includes(iframeUrl)) {
          DEBUG && console.log('entering a sub page');
          return;
        }

        // 在右边栏显示视频
        // note: 不要使用 $("#video-iframe").attr("src", iframeUrl);
        //       因为这样会留下访问记录,从而使后退、前进功能异常
        iframeUrl = newIFrameUrl;
        const videoIFrame = $('#video-iframe')[0];
        videoIFrame.contentWindow.location.replace(iframeUrl);
        $('#video-iframe').css({ display: 'block' });

        // 去除视频框中无关元素
        waitForKeyElements('.live__view', function () {
          const liveView = $('#video-iframe').contents().find('.live__view');
          // liveView.children('section.live__wrap').css('padding-top', '0px');
          liveView.children(':not(.live__wrap)').remove();
          const observer = new MutationObserver(function (mutations) {
            mutations.forEach(function (mutation) {
              if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                liveView.children(':not(.live__wrap)').remove();
              }
            });
          });
          observer.observe(liveView[0], { childList: true });
        }, true, '#video-iframe');

        isVideoLoaded = true;
      } else {
        if (isVideoLoaded && !(iframeUrl && newIFrameUrl.includes(iframeUrl))) {
          // 退出视频课程时停止播放并隐藏右边栏视频
          $('#video-iframe').css({ display: 'none' });
          iframeUrl = newIFrameUrl;
          // DO NOT USE: $("#video-iframe").attr("src", "/v/index");
          const videoIFrame = $('#video-iframe')[0];
          videoIFrame.contentWindow.location.replace('about:blank');

          isVideoLoaded = false;
        }
      }
    });

    const config = { childList: true, subtree: true };
    iframeObserver.observe(iframeBody, config);
    DEBUG && console.log('observation started');

    // 在 iframe 被重新加载(点击左侧导航栏、进入直播)时,重新添加 handler
    $('#rainiframe')[0].contentWindow.addEventListener('unload', () => {
      DEBUG && console.log('#rainiframe has (re)loaded');
      // 右边栏视频停止播放并隐藏
      $('#video-iframe').css({ display: 'none' });
      const videoIFrame = $('#video-iframe')[0];
      videoIFrame.contentWindow.location.replace('about:blank');
      $('#rainiframe').contents().empty();
      waitForKeyElements('body', function () {
        addVideoHandler();
      }, true, '#rainiframe');
    });
  }


  // ==== DO NOT MODIFY
  // ==== third-party utility functions:
  /*--- waitForKeyElements():  A utility function, for Greasemonkey scripts,
    that detects and handles AJAXed content.
    IMPORTANT: This function requires your script to have loaded jQuery.
  */
  function waitForKeyElements(
    selectorTxt,    /* Required: The jQuery selector string that
                      specifies the desired element(s).
                  */
    actionFunction, /* Required: The code to run when elements are
                      found. It is passed a jNode to the matched
                      element.
                  */
    bWaitOnce,      /* Optional: If false, will continue to scan for
                      new elements even after the first match is
                      found.
                  */
    iframeSelector  /* Optional: If set, identifies the iframe to
                      search.
                  */
  ) {
    var targetNodes, btargetsFound;

    if (typeof iframeSelector == 'undefined')
      targetNodes = $(selectorTxt);
    else
      targetNodes = $(iframeSelector).contents()
        .find(selectorTxt);

    if (targetNodes && targetNodes.length > 0) {
      btargetsFound = true;
      /*--- Found target node(s).  Go through each and act if they
          are new.
      */
      targetNodes.each(function () {
        var jThis = $(this);
        var alreadyFound = jThis.data('alreadyFound') || false;

        if (!alreadyFound) {
          //--- Call the payload function.
          var cancelFound = actionFunction(jThis);
          if (cancelFound)
            btargetsFound = false;
          else
            jThis.data('alreadyFound', true);
        }
      });
    }
    else {
      btargetsFound = false;
    }

    //--- Get the timer-control variable for this selector.
    var controlObj = waitForKeyElements.controlObj || {};
    var controlKey = selectorTxt.replace(/[^\w]/g, '_');
    var timeControl = controlObj[controlKey];

    //--- Now set or clear the timer as appropriate.
    if (btargetsFound && bWaitOnce && timeControl) {
      //--- The only condition where we need to clear the timer.
      clearInterval(timeControl);
      delete controlObj[controlKey];
    }
    else {
      //--- Set a timer, if needed.
      if (!timeControl) {
        timeControl = setInterval(function () {
          waitForKeyElements(
            selectorTxt, actionFunction, bWaitOnce, iframeSelector
          );
        }, 300);
        controlObj[controlKey] = timeControl;
      }
    }
    waitForKeyElements.controlObj = controlObj;
  }

})();