Rain Classroom Helper

优化雨课堂使用体验

目前為 2020-02-27 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Rain Classroom Helper
  3. // @namespace https://raineggplant.com/
  4. // @version 0.2.4
  5. // @description 优化雨课堂使用体验
  6. // @author RainEggplant
  7. // @match *://www.yuketang.cn/web*
  8. // @match *://pro.yuketang.cn/web*
  9. // @match *://changjiang.yuketang.cn/web*
  10. // @grant GM_addStyle
  11. // @require https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js
  12. // @homepageURL https://github.com/RainEggplant/rain-classroom-helper
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17. const DEBUG = false;
  18.  
  19. // 调整左边栏样式
  20. GM_addStyle(`
  21. .left .panel {
  22. padding-top: 34px !important;
  23. max-height: unset !important;
  24. }
  25. .nav-list {
  26. font-size: 18px !important;
  27. }
  28. .nav-item {
  29. height: 45px !important;
  30. line-height: 32px !important;
  31. padding-top: 6px !important;
  32. }
  33. .kecheng, .kejian, .shiti, .geren, .addlink {
  34. width: 30px !important;
  35. }
  36. .left .panel .nav-list .nav-item .name {
  37. padding-left: 18px !important;
  38. }
  39. .left .contact-us {
  40. bottom: 10px !important;
  41. display: block !important;
  42. }
  43. `);
  44.  
  45. // 调整右边栏样式
  46. GM_addStyle(`
  47. .right {
  48. width: 320px !important;
  49. }
  50. .right .control-panel {
  51. padding-top: 32px !important;
  52. max-height: unset !important;
  53. }
  54. .title {
  55. font-size: 22px !important;
  56. }
  57. .page-nav-control {
  58. width: 320px !important;
  59. }
  60. .page-control {
  61. padding-top: 15px !important;
  62. }
  63. .control-desc {
  64. display: none !important;
  65. }
  66. .page-nav {
  67. padding: unset !important;
  68. font-size: 18px !important;
  69. }
  70. .print-preview-box {
  71. margin: 0px 0 0 !important;
  72. }
  73. .contact-us {
  74. display: none;
  75. }
  76. .right .control-panel .page-control .paging-number-list .page-no:nth-child(5n) {
  77. margin: 0 14px 13px 0 !important;
  78. }
  79. `);
  80.  
  81. // 调整中间 iframe 为自适应宽度
  82. GM_addStyle(`
  83. .wrapper-inner {
  84. width: 92% !important;
  85. }
  86. .center {
  87. width: auto !important;
  88. margin-left: 180px !important;
  89. margin-right: 320px !important;
  90. float: none !important;
  91. }
  92. .center .rain-iframe {
  93. width: calc(95% - 40px) !important;
  94. height: 95% !important;
  95. }
  96. .student__timeline-wrapper {
  97. top: 2.33rem !important;
  98. }
  99. `);
  100.  
  101. // 调整布局
  102. waitForKeyElements('div.index-view.none.J_index', function () {
  103. // note: you must move div.right instead of div.center, or the sidebar
  104. // will lost its funtion
  105. $('div.right.fr').insertBefore($('div.center.fl'));
  106. });
  107.  
  108. // 缩小 “体验新版” 尺寸
  109. waitForKeyElements('a.newWebEntry', function () {
  110. $('a.newWebEntry')
  111. .find('img')
  112. .attr('style', 'width: 150px; margin-top: 20px;');
  113. });
  114.  
  115. // 添加右边栏视频框
  116. GM_addStyle(`
  117. #video-iframe {
  118. width: 320px;
  119. height: 304px;
  120. }
  121. `);
  122.  
  123. waitForKeyElements('div.control-panel.Absolute-Center', function () {
  124. const videoIFrame = '<iframe id="video-iframe" src="about:blank" style="display: none;"/>';
  125. $('div.page-control.J_pageNo').after(videoIFrame);
  126. });
  127.  
  128. // 添加 GitHub 项目图标
  129. waitForKeyElements('ul.nav-list', function () {
  130. const liAbout = `
  131. <li class="nav-item clearfix J_nav">
  132. <a href="https://github.com/RainEggplant/rain-classroom-helper" target="_blank">
  133. <img alt="GitHub stars" style="width:auto;" src="https://img.shields.io/github/stars/RainEggplant/rain-classroom-helper?style=social">
  134. </a>
  135. </li>
  136. `;
  137. $('ul.nav-list').append(liAbout);
  138. });
  139.  
  140. waitForKeyElements('div.left.fl', function () {
  141. const divLeftHidden = `
  142. <div id="left-hidden" class="fl" style="display: none;">
  143. <p id="show-left" style="top: 50%; position: absolute; color: #fff; z-index: 1;">▶</p>
  144. </div>
  145. `;
  146. $('div.left.fl').before(divLeftHidden);
  147. $('#show-left').on('click', showLeftPanel);
  148. });
  149.  
  150. // 添加控制样式的滑块
  151. waitForKeyElements('div.panel.Absolute-Center', function () {
  152. const panelWidthControls = `
  153. <div style="margin-top: 20px; color: #fff; font-size: 14px;">
  154. <p>
  155. <label for="panel-width">右边栏宽度</label>
  156. <input type="range" id="right-panel-width" value="320" min="200" max="576" style="width: 160px;">
  157. </p>
  158. <p>
  159. <label for="panel-width">全屏占比</label>
  160. <input type="range" id="content-width" value="92" min="50" max="98" style="width: 160px;">
  161. </p>
  162. <p>
  163. <button id="hide-left" type="button" style="color: #000; font-size: 12px;">◀ 隐藏左边栏</button>
  164. </p>
  165. </div>
  166. `;
  167. $('div.panel.Absolute-Center').append(panelWidthControls);
  168. $('#right-panel-width').on('change', setRightPanelWidth);
  169. $('#content-width').on('change', setContentWidth);
  170. $('#hide-left').on('click', hideLeftPanel);
  171. });
  172.  
  173. function setRightPanelWidth() {
  174. const width = 'width: ' + $(this).val() + 'px !important;';
  175. const height = 'height: ' + (0.75 * $(this).val() + 60) + 'px !important;';
  176. const marginLeft = 'margin-left: ' + $('div.center.fl').css('margin-left') + ';';
  177. const marginRight = 'margin-right: ' + $(this).val() + 'px !important;';
  178. const iframeDisplay = 'display: ' + $('#video-iframe').css('display') + ';';
  179. $('div.right.fr').attr('style', width);
  180. $('div.page-nav-control').attr('style', width);
  181. $('div.center.fl').attr('style', marginLeft + ' ' + marginRight);
  182. $('#video-iframe').attr('style', width + ' ' + height + ' ' + iframeDisplay);
  183. }
  184.  
  185. function setContentWidth() {
  186. const width = 'width: ' + $(this).val() + '% !important;';
  187. $('div.wrapper-inner.clearfix.J_inner').attr('style', width);
  188. }
  189.  
  190. function hideLeftPanel() {
  191. const marginLeft = 'margin-left: 0px !important;';
  192. const marginRight = 'margin-right: '
  193. + $('div.center.fl').css('margin-right') + ' !important;';
  194. $('div.left.fl').css('display', 'none');
  195. $('div.center.fl').attr('style', marginLeft + ' ' + marginRight);
  196. $('#left-hidden').css('display', 'block');
  197. }
  198.  
  199. function showLeftPanel() {
  200. const marginLeft = 'margin-left: 180px !important;';
  201. const marginRight = 'margin-right: '
  202. + $('div.center.fl').css('margin-right') + ' !important;';
  203. $('div.left.fl').css('display', 'block');
  204. $('div.center.fl').attr('style', marginLeft + ' ' + marginRight);
  205. $('#left-hidden').css('display', 'none');
  206. }
  207.  
  208. waitForKeyElements('body', function () {
  209. // 中间 iframe 加载出内容后绑定处理视频的函数
  210. addVideoHandler();
  211. }, true, '#rainiframe');
  212.  
  213. function addVideoHandler() {
  214. // 首次进入或通过左边栏改变页面时
  215. let iframeUrl = '';
  216. let isVideoLoaded = false;
  217. let iframeBody = $('#rainiframe').contents().find('body')[0];
  218. const iframeObserver = new MutationObserver(function () {
  219. DEBUG && console.log('iframe mutated');
  220. const newIFrameUrl = $('#rainiframe').contents()[0].location.href;
  221. DEBUG && console.log(newIFrameUrl);
  222.  
  223. const videoSection = iframeBody.querySelector('section.live__wrap');
  224. if (videoSection) {
  225. // 存在视频
  226. // 去除中央 rainIFrame 中的视频
  227. $(videoSection).contents().find('video').removeAttr('src');
  228. $(videoSection).empty();
  229.  
  230. if (iframeUrl && newIFrameUrl.includes(iframeUrl)) {
  231. DEBUG && console.log('entering a sub page');
  232. return;
  233. }
  234.  
  235. // 在右边栏显示视频
  236. // note: 不要使用 $("#video-iframe").attr("src", iframeUrl);
  237. // 因为这样会留下访问记录,从而使后退、前进功能异常
  238. iframeUrl = newIFrameUrl;
  239. const videoIFrame = $('#video-iframe')[0];
  240. videoIFrame.contentWindow.location.replace(iframeUrl);
  241. $('#video-iframe').css({ display: 'block' });
  242.  
  243. // 去除视频框中无关元素
  244. waitForKeyElements('.live__view', function () {
  245. const liveView = $('#video-iframe').contents().find('.live__view');
  246. // liveView.children('section.live__wrap').css('padding-top', '0px');
  247. liveView.children(':not(.live__wrap)').remove();
  248. const observer = new MutationObserver(function (mutations) {
  249. mutations.forEach(function (mutation) {
  250. if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  251. liveView.children(':not(.live__wrap)').remove();
  252. }
  253. });
  254. });
  255. observer.observe(liveView[0], { childList: true });
  256. }, true, '#video-iframe');
  257.  
  258. isVideoLoaded = true;
  259. } else {
  260. if (isVideoLoaded && !(iframeUrl && newIFrameUrl.includes(iframeUrl))) {
  261. // 退出视频课程时停止播放并隐藏右边栏视频
  262. $('#video-iframe').css({ display: 'none' });
  263. iframeUrl = newIFrameUrl;
  264. // DO NOT USE: $("#video-iframe").attr("src", "/v/index");
  265. const videoIFrame = $('#video-iframe')[0];
  266. videoIFrame.contentWindow.location.replace('about:blank');
  267.  
  268. isVideoLoaded = false;
  269. }
  270. }
  271. });
  272.  
  273. const config = { childList: true, subtree: true };
  274. iframeObserver.observe(iframeBody, config);
  275. DEBUG && console.log('observation started');
  276.  
  277. // 在 iframe 被重新加载(点击左侧导航栏、进入直播)时,重新添加 handler
  278. $('#rainiframe')[0].contentWindow.addEventListener('unload', () => {
  279. DEBUG && console.log('#rainiframe has (re)loaded');
  280. // 右边栏视频停止播放并隐藏
  281. $('#video-iframe').css({ display: 'none' });
  282. const videoIFrame = $('#video-iframe')[0];
  283. videoIFrame.contentWindow.location.replace('about:blank');
  284. $('#rainiframe').contents().empty();
  285. waitForKeyElements('body', function () {
  286. addVideoHandler();
  287. }, true, '#rainiframe');
  288. });
  289. }
  290.  
  291.  
  292. // ==== DO NOT MODIFY
  293. // ==== third-party utility functions:
  294. /*--- waitForKeyElements(): A utility function, for Greasemonkey scripts,
  295. that detects and handles AJAXed content.
  296. IMPORTANT: This function requires your script to have loaded jQuery.
  297. */
  298. function waitForKeyElements(
  299. selectorTxt, /* Required: The jQuery selector string that
  300. specifies the desired element(s).
  301. */
  302. actionFunction, /* Required: The code to run when elements are
  303. found. It is passed a jNode to the matched
  304. element.
  305. */
  306. bWaitOnce, /* Optional: If false, will continue to scan for
  307. new elements even after the first match is
  308. found.
  309. */
  310. iframeSelector /* Optional: If set, identifies the iframe to
  311. search.
  312. */
  313. ) {
  314. var targetNodes, btargetsFound;
  315.  
  316. if (typeof iframeSelector == 'undefined')
  317. targetNodes = $(selectorTxt);
  318. else
  319. targetNodes = $(iframeSelector).contents()
  320. .find(selectorTxt);
  321.  
  322. if (targetNodes && targetNodes.length > 0) {
  323. btargetsFound = true;
  324. /*--- Found target node(s). Go through each and act if they
  325. are new.
  326. */
  327. targetNodes.each(function () {
  328. var jThis = $(this);
  329. var alreadyFound = jThis.data('alreadyFound') || false;
  330.  
  331. if (!alreadyFound) {
  332. //--- Call the payload function.
  333. var cancelFound = actionFunction(jThis);
  334. if (cancelFound)
  335. btargetsFound = false;
  336. else
  337. jThis.data('alreadyFound', true);
  338. }
  339. });
  340. }
  341. else {
  342. btargetsFound = false;
  343. }
  344.  
  345. //--- Get the timer-control variable for this selector.
  346. var controlObj = waitForKeyElements.controlObj || {};
  347. var controlKey = selectorTxt.replace(/[^\w]/g, '_');
  348. var timeControl = controlObj[controlKey];
  349.  
  350. //--- Now set or clear the timer as appropriate.
  351. if (btargetsFound && bWaitOnce && timeControl) {
  352. //--- The only condition where we need to clear the timer.
  353. clearInterval(timeControl);
  354. delete controlObj[controlKey];
  355. }
  356. else {
  357. //--- Set a timer, if needed.
  358. if (!timeControl) {
  359. timeControl = setInterval(function () {
  360. waitForKeyElements(
  361. selectorTxt, actionFunction, bWaitOnce, iframeSelector
  362. );
  363. }, 300);
  364. controlObj[controlKey] = timeControl;
  365. }
  366. }
  367. waitForKeyElements.controlObj = controlObj;
  368. }
  369.  
  370. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址