// ==UserScript==
// @name AcWing Better!
// @version 3.13
// @description AcWing界面美化,功能增强,视频时间点标记跳转,代码markdown一键复制
// @author 北极小狐
// @match https://www.acwing.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=acwing.com
// @grant none
// @run-at document-start
// @require https://cdn.bootcdn.net/ajax/libs/turndown/7.1.1/turndown.min.js
// @license MIT
// @namespace https://greasyfork.org/users/747162
// ==/UserScript==
// 样式
function loadCssCode(code){
var style = document.createElement('style');
style.type = 'text/css';
style.rel = 'stylesheet';
style.appendChild(document.createTextNode(code));
var head = document.getElementsByTagName('head')[0];
head.appendChild(style);
}
loadCssCode(`
/* 宽度自适应 */
.container {
width: auto !important;
}
/* 去除没用的图标 */
.file-explorer-main-field-item.file-explorer-main-field-item-desktop {
width: 0px;
height: 0px;
overflow: hidden;
}
/* 背景bing壁纸 */
#acwing_body {
background: white url(https://bingw.jasonzeng.dev) fixed !important;
}
/* 解决评论区某些同学发超长单词会导致整个页面显示x-scrollbar的**行为 */
.comment-conent {
overflow-x: auto;
}
/* 其他 */
.fs-gui-taskbar {
height: 3.5vh !important;
background-color: #dde1e5 !important;
}
.fs-gui-taskbar-widgets-apps-item > img {
height: 2.2vh !important;
width: 2.2vh !important;
margin: 0.5vh 0.5vh 0.5vh 0.5vh !important;
}
.fs-gui-taskbar-widgets-clock{
width: 0px !important;
height: 0px !important;
overflow: hidden !important;
}
.fs-gui-taskbar-widgets-apps-item {
margin-right: 2vh !important;
}
#fs-gui-taskbar-search-field {
font-size: 1.3vh !important;
}
.fs-gui-taskbar-search-icon {
font-size: 1.6vh !important;
top: 0.95vh !important;
left: 4.3vh !important;
}
footer#acwing_footer .copyright {
color: #fff;
}
footer#acwing_footer .copyright a, .links a, footer#acwing_footer .container {
color: #fff;
}
.fs-gui-taskbar-begin {
height: 3vh !important;
width: 3vh !important;
margin: 0.2vh !important;
border-radius: 60%;
background-color: #fffefe80 !important;
}
button.fs-gui-taskbar-begin.pull-left.btn.btn-default img {
width: 83% !important;
}
#fs-gui-taskbar-search-field {
height: 90% !important;
margin: 0.15vh;
border-radius: 100px;
border-width: 0.2vh;
border-style: solid;
border-color: #c7d2dd;
}
#fs-gui-taskbar-search-field:focus-visible {
border-width: 0.2vh;
border-style: solid;
border-color: #8bb2d9;
outline: -webkit-focus-ring-color auto 0px;
}
/* 复制按钮 */
pre.hljs {
display: flex;
justify-content: space-between;
}
span.copy-button {
cursor: pointer;
background-color: #e6e6e6;
color: #727378;
height: 2vh;
font-size: 1.3vh;
border-radius: 0.3rem;
padding: 1px 5px;
margin: 5px;
box-shadow: 0 0 1px #0000004d;
}
span.copy-button.copied {
background-color: #07e65196;
color: #104f2b;
}
/* html2md */
.html2md-panel {
display: flex;
justify-content: flex-end;
}
button.html2mdButton {
height: 3vh;
width: 3vh;
}
button.html2mdButton {
cursor: pointer;
background-color: #e6e6e6;
color: #727378;
height: 3vh;
width: auto;
font-size: 1.3vh;
border-radius: 0.3rem;
border: none;
padding: 1px 5px;
margin: 5px;
box-shadow: 0 0 1px #0000004d;
}
button.html2mdButton.copied {
background-color: #07e65196;
color: #104f2b;
}
button.html2mdButton.html2md-view.mdViewed {
background-color: #ff980057;
color: #5a3a0c;
}
/* 打卡框 */
.ui.bottom.attached.tab.segment.active {
padding: 0px;
}
/* 视频bar */
.embed-responsive {
height: max-content;
padding-bottom: 0px;
}
.player_bar {
margin: 2px;
display: flex;
justify-content: space-between;
}
.player_bar_go {
cursor: pointer;
width: 50px;
color: #999;
height: auto;
font-size: 1.3vh;
border-radius: 0.3rem;
padding: 1px 5px;
margin: 5px;
border: none;
background: linear-gradient(-225deg,#d5dbe4,#f8f8f8);
box-shadow: inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 2px 1px rgba(30,35,90,.4);
display: flex;
justify-content: center;
align-items: center;
}
button#player_bar_list_add_new_item_btn {
height: 30px;
width: 50px;
background-color: #00aeec;
color: #ffffff;
font-size: 1.3vh;
border-radius: 0rem 0.5rem 0.5rem 0rem;
padding: 1px 5px;
margin: 5px 5px 5px 0px;
border: none;
box-shadow: 0 0 1px #0000004d;
}
div#player_bar_list {
display: grid;
width: 100%;
border-radius: 0.3rem 0rem 0rem 0.3rem;
margin: 5px 0px 5px 0px;
border: 1px dashed #00aeeccc;
}
div#player_bar_list input[type="radio"] {
appearance: none;
width: 0;
height: 0;
overflow: hidden;
}
div#player_bar_list input[type=radio]:focus {
outline: 0px;
}
label.player_bar_ul_li_text {
max-width: 100%;
height: 90px;
overflow-x: auto;
font-weight: 400;
margin: 0px 4px;
box-shadow: 0 0 1px #0000004d;
padding: 3px;
}
ul#player_bar_ul li button {
background-color: #e6e6e6;
color: #727378;
height: 23px;
font-size: 14px;
border-radius: 0.3rem;
padding: 1px 5px;
margin: 5px;
border: none;
box-shadow: 0 0 1px #0000004d;
}
ul#player_bar_ul {
list-style-type: none;
padding-inline-start: 0px;
display: flex;
overflow-x: auto;
max-width: 100%;
margin: 0px;
}
ul#player_bar_ul li {
height: 100px;
width: 8vh;
display: grid;
overflow: hidden;
margin: 0.4vh 0.2vh;
min-width: 100px;
}
label.player_bar_ul_li_text:hover {
background-color: #eae4dc24;
}
input[type="radio"]:checked + .player_bar_ul_li_text {
background: linear-gradient(-225deg,#ede0bc,#f8f8f8);
box-shadow: inset 0 0px 0 0 #cdcde6, inset 0 0px 1px 1px #fff, 0 0px 2px 1px rgb(30 35 90 / 32%);
}
ul#player_bar_ul::-webkit-scrollbar {
width: 5px;
height: 8px;
}
ul#player_bar_ul::-webkit-scrollbar-thumb {
border-radius: 2px;
border: 1px solid rgba(56,56,56,.3411764706);
background-clip: padding-box;
background-color: #a29bb84a;
background-image: -webkit-linear-gradient(45deg,hsla(0deg,0%,100%,.4) 25%,transparent 0,transparent 50%,hsla(0deg,0%,100%,.4) 0,hsla(0deg,0%,100%,.4) 75%,transparent 0,transparent);
}
ul#player_bar_ul::-webkit-scrollbar-track {
background-color: #f1f1f1;
border-radius: 5px;
}
label.player_bar_ul_li_text::-webkit-scrollbar {
width: 5px;
height: 7px;
background-color: #aaa;
}
label.player_bar_ul_li_text::-webkit-scrollbar-thumb {
border: 1px solid rgba(56,56,56,.3411764706);
background-clip: padding-box;
background-color: #a29bb84a;
}
label.player_bar_ul_li_text::-webkit-scrollbar-track {
background-color: #f1f1f1;
}
.player_bar_list_add_div {
display: flex;
height: 4vh;
margin: 4px 2px;
}
input#player_bar_list_add_input {
width: 100%;
height: 30px;
background-color: #ffffff;
color: #727378;
font-size: 1.3vh;
border-radius: 0.3rem 0rem 0rem 0.3rem;
padding: 1px 5px;
margin: 5px 0px 5px 0px;
border: 1px solid #00aeeccc;
border-right: none;
box-shadow: 0 0 1px #0000004d;
}
input#player_bar_list_add_input:focus-visible {
border-width: 0.2vh;
border-style: solid;
border-color: #8bb2d9;
outline: -webkit-focus-ring-color auto 0px;
}
button#player_bar_list_add_new_item_btn.added {
background-color: #07e65196;
color: #104f2b;
}
div#player_bar_go.gone {
color: #3f5a14;
font-weight: 600;
background: linear-gradient(-225deg,#9CCC65,#E6EE9C);
box-shadow: inset 0 -2px 0 0 #cde3e6, inset 0 0 1px 1px #c6fd7d, 0 1px 2px 1px rgb(30 90 44 / 40%);
}
/* bar修改菜单 */
div#player_bar_menu {
position: absolute;
border-width: 2px;
border-style: solid;
border-color: #8bb2d9;
box-shadow: 1px 1px 4px 0px #0000004d;
}
div#player_bar_menu_edit {
cursor: pointer;
background-color: #ffffff;
color: black;
box-shadow: inset 0px -1px 0px 0px #8bb2d9;
padding: 2px 6px;
}
div#player_bar_menu_delete {
cursor: pointer;
background-color: #ffff;
box-shadow: inset 0px 1px 0px 0px #8bb2d9;
color: black;
padding: 2px 6px;
}
div#player_bar_menu_edit:hover {
background-color: #00aeec;
color: white;
}
div#player_bar_menu_delete:hover {
background-color: #FF5722;
color: white;
}
`);
// 添加复制按钮
function addCopy(){
// 获取所有 .hljs 中的代码块
const codeBlocks = document.querySelectorAll('.hljs code');
// 循环遍历每个代码块
codeBlocks.forEach(codeBlock => {
// 创建一个 span 元素,并设置样式
const beforeButton = document.createElement('span');
beforeButton.textContent = "Copy";
beforeButton.className = 'copy-button';
// 在代码块前面插入按钮
codeBlock.parentNode.insertBefore(beforeButton, codeBlock.nextSibling);
// 为按钮添加点击事件
beforeButton.addEventListener('click', event => {
// 创建临时文本域
const textarea = document.createElement('textarea');
textarea.value = codeBlock.textContent.replace(/\n+$/, '');
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
// 更新复制按钮文本
beforeButton.classList.add('copied');
beforeButton.textContent = "Copied";
setTimeout(() => {
beforeButton.classList.remove('copied');
beforeButton.textContent = "Copy";
}, 2000);
}, false);
});
}
// 移除复制按钮
function removeCopy(){
var elements = document.querySelectorAll('.hljs .copy-button');
for (var i = 0; i < elements.length; i++) {
elements[i].parentNode.removeChild(elements[i]);
}
}
document.addEventListener('DOMContentLoaded', function() {
// 让某些链接在新窗口打开
var regExps = [
/常用代码模板/,
/example/,
/test/
];
var aTags = document.getElementsByTagName('a');
for (var i = 0; i < aTags.length; i++) {
for (var j = 0; j < regExps.length; j++) {
if (regExps[j].test(aTags[i].textContent)) {
aTags[i].setAttribute('target', '_blank');
break;
}
}
}
// 自动恢复进度条
setTimeout(function() {
try {
document.querySelector('.play-jump').click();
} catch (error) {
// do nothing
}
}, 3000);
// 复制按钮
addCopy();
// 移除广告元素
let ADidADList = ["1024-activity","test"];
ADtraverseDom(document.body);
function ADtraverseDom(node) {
if (node.nodeType === Node.ELEMENT_NODE && ADidADList.includes(node.id)) {
node.parentNode.removeChild(node);
} else {
for (let i = 0; i < node.childNodes.length; i++) {
ADtraverseDom(node.childNodes[i]);
}
}
}
// 修改打卡页代码框默认高度
var element = document.getElementById("martor-content");
if(element){
var style = window.getComputedStyle(element);
element.style.height = "55vh";
}
});
// MarkDown
document.addEventListener('DOMContentLoaded', function() {
let debug = false; // whether to enable on editor
let turndownService = new TurndownService();
turndownService.keep(['del']);
// code block
turndownService.addRule('pre', {
filter: 'pre',
replacement: function (content, node) {
let t = $(node).attr("class").split(/\s+/).slice(-1);
if (t == "hljs") t = "";
return "```" + t + "\n" + content.trim() + "\n```";
}
});
// remove <script> math
turndownService.addRule('remove-script', {
filter: function (node, options) {
return node.tagName.toLowerCase() == "script" && node.type.startsWith("math/tex");
},
replacement: function (content, node) {
return "";
}
});
// inline math
turndownService.addRule('inline-math', {
filter: function (node, options) {
return node.tagName.toLowerCase() == "span" && node.className == "MathJax";
},
replacement: function (content, node) {
return "$ " + $(node).next().text() + " $";
}
});
// block math
turndownService.addRule('block-math', {
filter: function (node, options) {
return node.tagName.toLowerCase() == "div" && node.className == "MathJax_Display";
},
replacement: function (content, node) {
return "\n$$\n" + $(node).next().text() + "\n$$\n";
}
});
// add buttons
$("div[data-tab='preview-tab-content']").each(function() {
if (debug || $(this).prev().attr('data-tab') != "editor-tab-content")
$(this).before(
"<div class='html2md-panel'> <button class='html2mdButton html2md-view'>MarkDown视图</button> <button class='html2mdButton html2md-cb'>Copy</button> </div>"
);
});
$(".html2md-cb").click(function() {
let target = $(this).parent().next().get(0);
if (!target.markdown){
removeCopy();
target.markdown = turndownService.turndown($(target).html());
addCopy();
}
const textarea = document.createElement('textarea');
textarea.value = target.markdown;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
// console.log(markdown);
$(this).addClass("copied");
$(this).text("Copied");
// 更新复制按钮文本
setTimeout(() => {
$(this).removeClass("copied");
$(this).text("Copy");
}, 2000);
});
$(".html2md-view").click(function() {
let target = $(this).parent().next().get(0);
if (target.viewmd) {
target.viewmd = false;
$(this).text("MarkDown视图");
$(this).removeClass("mdViewed");
$(target).html(target.original_html);
addCopy();
} else {
target.viewmd = true;
removeCopy();
if (!target.original_html)
target.original_html = $(target).html();
if (!target.markdown)
target.markdown = turndownService.turndown($(target).html());
$(this).text("原始内容");
$(this).addClass("mdViewed");
$(target).html(`<span oninput="$(this).parent().get(0).markdown=this.value;" style="width:auto; height:auto; white-space: pre;"> ${target.markdown} </span>`);
}
});
});
// 播放器添加节点标签功能
function addPlayerBar(player_bar_video){
// 创建元素
var player = document.querySelector('.prism-player');
var player_bar = document.createElement('div');
player_bar.classList.add('player_bar');
player.parentNode.insertBefore(player_bar, player.nextSibling);
var player_bar_list = document.createElement('div');
player_bar_list.classList.add('player_bar_list');
player_bar_list.setAttribute("id","player_bar_list");
player_bar.appendChild(player_bar_list);
var player_bar_ul = document.createElement('ul');
player_bar_ul.classList.add('player_bar_ul');
player_bar_ul.setAttribute("id","player_bar_ul");
player_bar_list.appendChild(player_bar_ul);
var player_bar_go = document.createElement('div');
player_bar_go.classList.add('player_bar_go');
player_bar_go.setAttribute("id","player_bar_go");
var player_bar_goText = document.createTextNode("Go!");
player_bar_go.appendChild(player_bar_goText);
player_bar.appendChild(player_bar_go);
var player_bar_list_add_div = document.createElement('div');
player_bar_list_add_div.classList.add('player_bar_list_add_div');
player_bar.parentNode.insertBefore(player_bar_list_add_div, player_bar.nextSibling);
var player_bar_list_add_input = document.createElement('input');
player_bar_list_add_input.classList.add('player_bar_list_add_input');
player_bar_list_add_input.setAttribute("type","text");
player_bar_list_add_input.setAttribute("id","player_bar_list_add_input");
player_bar_list_add_input.setAttribute("placeholder","在这里输入备注内容,点击Add添加一个时间点标记;选中一个标记,点击Go跳转;右键标记,修改或删除");
player_bar_list_add_div.appendChild(player_bar_list_add_input);
var player_bar_list_add_button = document.createElement('button');
player_bar_list_add_button.classList.add('player_bar_list_add_button');
player_bar_list_add_button.setAttribute("id","player_bar_list_add_new_item_btn");
var player_bar_list_add_buttonText = document.createTextNode("Add");
player_bar_list_add_button.appendChild(player_bar_list_add_buttonText);
player_bar_list_add_div.appendChild(player_bar_list_add_button);
//存储cookie的名称和标识符
const COOKIE_NAME = "listItems";
const PAGE_IDENTIFIER = window.location.href;
//计数器
let counter = 0;
//获取cookie中的数据
function getListData() {
let data = getCookie(COOKIE_NAME);
if (!data) {
data = {};
} else {
data = JSON.parse(data);
}
if (!data[PAGE_IDENTIFIER]) {
data[PAGE_IDENTIFIER] = [];
}
return data[PAGE_IDENTIFIER];
}
//将数据保存到cookie中
function saveListData(data) {
let cookieData = getCookie(COOKIE_NAME);
if (cookieData) {
cookieData = JSON.parse(cookieData);
} else {
cookieData = {};
}
cookieData[PAGE_IDENTIFIER] = data;
setCookie(COOKIE_NAME, JSON.stringify(cookieData), 36500);
}
//创建新的li元素
function createListItemElement(text) {
const li = document.createElement("li");
const radio = document.createElement("input");
radio.type = "radio";
radio.name = "player_bar_ul";
radio.id = counter++;
li.appendChild(radio);
const label = document.createElement("label");
label.textContent = text;
label.classList.add("player_bar_ul_li_text");
label.setAttribute("for", radio.id);
li.appendChild(label);
li.addEventListener("contextmenu", (event) => {
//阻止默认右键菜单
event.preventDefault();
//显示菜单
menu.style.display = "block";
menu.style.left = event.pageX + "px";
menu.style.top = event.pageY + "px";
const deleteItem = document.getElementById("player_bar_menu_delete");
const editItem = document.getElementById("player_bar_menu_edit");
function onDelete() {
deleteItem.removeEventListener("click", onDelete);
const list = document.getElementById("player_bar_ul");
const index = Array.from(list.children).indexOf(li);
const data = getListData();
data.splice(index, 1);
saveListData(data);
li.remove();
menu.style.display = "none";
}
function onEdit() {
editItem.removeEventListener("click", onEdit);
const list = document.getElementById("player_bar_ul");
const index = Array.from(list.children).indexOf(li);
const data = getListData();
label.textContent = data[index].text;
const text = prompt("请输入修改后的内容", label.textContent);
if (text !== undefined && text!==null) {
data[index].text = text.trim();
saveListData(data);
}
renderList();//重新渲染
menu.style.display = "none";
}
deleteItem.addEventListener("click", onDelete);
editItem.addEventListener("click", onEdit);
document.addEventListener("click", (event) => {
//点击菜单外部,隐藏菜单并移除事件监听器
if (!menu.contains(event.target)) {
menu.style.display = "none";
// 移除所有事件监听器
deleteItem.removeEventListener("click", onDelete);
editItem.removeEventListener("click", onEdit);
}
});
});
return li;
}
//渲染列表
function renderList() {
const listContainer = document.getElementById("player_bar_list");
const list = document.getElementById("player_bar_ul");
list.innerHTML = "";
const data = getListData();
data.forEach((item) => {
list.appendChild(createListItemElement(item.text));
});
}
//新增列表项
function addNewItem() {
const input = document.getElementById("player_bar_list_add_input");
const text = input.value.trim();
if (text === "") {
alert("请输入内容");
return;
}
const data = getListData();
data.push({text: text, time: player_bar_video.currentTime});
saveListData(data);
const list = document.getElementById("player_bar_ul");
list.appendChild(createListItemElement(text));
input.value = "";
}
//设置cookie
function setCookie(name, value, days) {
let cookie = `${name}=${encodeURIComponent(value)}; path=/`;
if (days) {
const expireDate = new Date();
expireDate.setDate(expireDate.getDate() + days);
cookie += `; expires=${expireDate.toUTCString()}`;
}
document.cookie = cookie;
}
//获取cookie
function getCookie(name) {
const cookies = document.cookie.split("; ");
for (let i = 0; i < cookies.length; i++) {
const [cookieName, cookieValue] = cookies[i].split("=");
if (cookieName === name) {
return decodeURIComponent(cookieValue);
}
}
return null;
}
//为添加按钮添加事件处理程序
var player_bar_add_button = document.getElementById("player_bar_list_add_new_item_btn")
player_bar_add_button.addEventListener("click", ()=>{
addNewItem();
player_bar_add_button.classList.add('added');
player_bar_add_button.textContent = "Added";
setTimeout(() => {
player_bar_add_button.classList.remove('added');
player_bar_add_button.textContent = "Add";
}, 2000);
});
//在页面加载时渲染列表
renderList();
//为跳转按钮添加事件处理程序
var click_player_bar_go = document.getElementById("player_bar_go");
click_player_bar_go.addEventListener("click", () => {
const selected = document.querySelector('input[name="player_bar_ul"]:checked');
if (selected) {
const data = getListData();
const index = Array.from(selected.parentNode.parentNode.children).indexOf(selected.parentNode);
player_bar_video.currentTime = data[index].time;
click_player_bar_go.classList.add('gone');
click_player_bar_go.textContent = "Gone";
setTimeout(() => {
click_player_bar_go.classList.remove('gone');
click_player_bar_go.textContent = "Go!";
}, 2000);
} else {
alert("请选择一项");
}
});
//创建自定义菜单
const menu = document.createElement("div");
menu.id = "player_bar_menu";
menu.style.display = "none";
menu.innerHTML = `
<div id="player_bar_menu_edit">修改</div>
<div id="player_bar_menu_delete">删除</div>
`;
document.body.appendChild(menu);
}
window.addEventListener('load', function() {
var player_bar_video = document.querySelector('video');
if(player_bar_video!=null)addPlayerBar(player_bar_video);
});
// 待实现
// [] 弹幕可复制
// [] 弹幕关键字屏蔽