// ==UserScript==
// @name 💡 链接速览
// @namespace https://ez118.github.io/
// @version 2.0.0
// @description 快速预览网页链接,鼠标移至链接并按下Enter键即可预览。
// @author ZZY_WISU
// @match *://*/*
// @connect *
// @license GPLv3
// @icon data:image/webp;base64,UklGRpIAAABXRUJQVlA4WAoAAAAwAAAAIgAAIgAAVlA4THMAAAAvIoAIEA8wdtMxwfMf8HBT27ac7ISpY0UAgwXK3yVSkPBLOpBGhY4nIRrIHyxE9F9t2zaMNjs9Z3Ciy+S0SIRMFiRLzBPrG68Vl+/A+bPjaPffdtt35+i+8zsVbKJIKRoVS1brpkDONc8HYi4oREE3pXwJAA==
// @run-at document-end
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant window.onurlchange
// @require https://unpkg.com/[email protected]/dist/zepto.min.js
// @require https://unpkg.com/@mozilla/[email protected]/Readability.js
// ==/UserScript==
const contentEleSelList = {
"blog.csdn.net": "#article_content",
"zhuanlan.zhihu.com": ".Post-Main",
"jingyan.baidu.com": "#format-exp",
"www.bilibili.com": "#article-content",
"zhidao.baidu.com": "#qb-content",
"www.cnblogs.com": "#topics",
"www.sohu.com": "#mp-editor"
}; /* 储存特定网站内容优化数据(文章主体的父元素) */
const mediaPrevSupport = [
{
"site": "https://v.youku.com/v_show/*.html",
"player": "https://player.youku.com/embed/*",
"type": "video"
},
{
"site": "https://v.qq.com/x/page/*.html",
"player": "https://v.qq.com/txp/iframe/player.html?vid=*",
"type": "video"
},
{
"site": "https://www.bilibili.com/video/BV*/",
"player": "https://www.bilibili.com/blackboard/html5mobileplayer.html?bvid=*",
"type": "video"
},
{
"site": "https://www.bilibili.com/video/av*/",
"player": "https://www.bilibili.com/blackboard/html5mobileplayer.html?aid=*",
"type": "video"
},
{
"site": "https://www.youtube.com/watch?v=*",
"player": "https://www.youtube.com/embed/*",
"type": "video"
},
{
"site": "https://music.163.com/#/song?id=*",
"player": "https://music.163.com/outchain/player?type=2&id=*&auto=0&height=66",
"type": "music"
},
{
"site": "https://music.163.com/song?id=*",
"player": "https://music.163.com/outchain/player?type=2&id=*&auto=0&height=66",
"type": "music"
},
{
"site": "https://open.spotify.com/track/*",
"player": "https://open.spotify.com/embed/track/*",
"type": "music"
},
{
"site": "https://music.apple.com/cn/song/*",
"player": "https://embed.music.apple.com/cn/album/*",
"type": "music"
},
{
"site": "https://music.youtube.com/watch?v=*",
"player": "https://www.youtube.com/embed/*",
"type": "music"
}
]; /* 储存支持预览播放视频/预览试听音乐的网站及其嵌入播放器链接 */
function judgeMediaSupport(url){
var jflag = null;
mediaPrevSupport.forEach(function(item, index) {
if (url.includes(item.site.split("*")[0])) {
jflag = { "state": true, "data": item };
}
});
return jflag || { "state": false, "data": null };
}
function getWebContents(html, url) {
/* 去掉影响转换的标签 */
html = html.replace(/<script.*?>.*?<\/script>/gis, "")
.replace(/<style.*?>.*?<\/style>/gis, "")
.replace(/<nav.*?>.*?<\/nav>/gis, "");
/* 提取正文 */
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const readability = new Readability(doc);
const result = readability.parse(doc);
return result.content;
}
function openReader(url) {
/* 打开阅读器 */
/* 阅读器加载提示 */
var closeBtn = $("#userscript-closeBtn");
var previewReader = $("#userscript-webPreviewReader");
previewReader.html("<p style='font-size:22px;margin-top:33%;' align='center'>正在载入...<br/><span>" + url + "</span></p>");
previewReader.show();
closeBtn.show();
/* 判断当前链接是支持预览的视频网站,并作出对应处理 */
var showMedia = judgeMediaSupport(url);
if(showMedia.state){
/* 被支持的视频网站的处理 */
var origUrl = url;
var frameUrl = "";
var mediaType = (showMedia.data.type == "video") ? "视频" : "音乐";
/* 将链接参数与嵌入式播放器链接拼接 */
url = url.replace(showMedia.data.site.split("*")[0], "");
url = url + "?#";
url = url.split("#")[0].split("?")[0];
url = url.replace(showMedia.data.site.split("*")[1], "");
frameUrl = showMedia.data.player.replace("*", url);
previewReader.html(`
<div id="FadeInContainer">
<div style="height:48px; overflow:hidden;">
<p style="margin:16px 14px;font-size:medium;user-select:none;">${mediaType}预览</p>
</div>
<iframe id="videoFrame" style="min-height:300px;" src="${frameUrl}"></iframe>
<br>
<a href="${origUrl}" target="_blank">在原网站中继续 ▶ </a><br/>
<a href="${frameUrl}" target="_blank">在播放器中继续 ▶ </a>
</div>
`);
} else {
/* 普通网站的处理 */
GM_xmlhttpRequest({
method: "GET",
url: url,
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8"
},
onload: (response) => {
var result = response.responseText;
/* 源数据处理(csdn存在利用img的onerror属性注入xss脚本的行为) */
result = result.replace(/<img\s+[^>]*src\s*=\s*["']{2}[^>]*>/gi, ''); /* 删除src为空的标签 */
result = result.replace(/<img([^>]*)onerror\s*=\s*(['"]?[^'">]*['"]?)([^>]*)>/gi, '<img$1$3>'); /* 删除所有img标签的onerror属性 */
/* 对指定网站进行内容过滤,指定元素获取 */
let orig_result_backup = result;
const domain = url.split("/")[2];
if (contentEleSelList[domain]) {
try {
const selector = contentEleSelList[domain];
result = $(result).find(selector).html();
} catch (e) { console.log("[WebPrvw] 无法对特定网站进行内容优化") }
}
if (!result) { result = orig_result_backup; }
/* 调用解析网页 */
let web_content = getWebContents(result, url);
/* 将所有结果添加进阅读器,并显示 */
previewReader.html(`
<div id="FadeInContainer">
<div style="height:48px; overflow:hidden;">
<p style="margin:16px 14px;font-size:medium;user-select:none;">正文预览</p>
</div>
<div class="ContentShow">
${web_content}
</div>
</div>
`);
},
onerror: () => console.log("[WebPrvw] 请求失败")
});
}
/* 执行结束 */
}
function initAnalyze() {
// 创建提示框
const tooltip = $('<div class="userscript-webPreviewTooltip" style="display:none;"></div>');
$('body').append(tooltip);
// 获取所有有效的 a 标签
const $links = $('a:not(#userscript-webPreviewReader)');
// 过滤出有效链接(非 javascript: 和 mailto:)
const $validLinks = $links.filter(function() {
const href = $(this).attr('href');
return href && !href.startsWith('javascript:') && !href.startsWith('mailto:');
});
// 绑定鼠标悬停事件
$validLinks.on('mouseover', function(e) {
const rect = this.getBoundingClientRect();
tooltip.css({
left: rect.left + window.scrollX,
top: rect.top + window.scrollY - 30,
display: 'block'
});
tooltip.text('按 Enter 键预览');
}).on('mouseout', function() {
tooltip.hide();
});
// 记录当前鼠标悬停的链接
let hoveredLink = null;
$(document).on('mousemove', function(e) {
let LinkCounter = 0;
$validLinks.each(function() {
const rect = this.getBoundingClientRect();
if (
e.clientX >= rect.left &&
e.clientX <= rect.right &&
e.clientY >= rect.top &&
e.clientY <= rect.bottom
) {
hoveredLink = this;
LinkCounter += 1;
}
});
if(LinkCounter <= 0) {
hoveredLink = null;
}
});
// 监听 Enter 键
$(document).on('keydown', function(e) {
if (e.key.toLowerCase() == 'enter' && hoveredLink) {
openReader(hoveredLink.href);
}
});
}
/* =========================== */
function init(){
/* 初始化 */
/* 插入样式 */
GM_addStyle(`
:root{--bg-color:#FFFFFFAA;--text-color:#386a1f;--border-color:#285a0f;--hover-bg-color:#edf1e5;--active-bg-color:#d7e1cd;--close-btn-bg:#386a1f;--close-btn-text:#FFF;--reader-bg:#fdfdf6;--reader-text-color:#131f0d;--link-color:#386a1f;--link-hover:#487631;--pre-bg-color:#eeeee8;--pre-border-color:#dee5d8;--code-bg-color:#e2e3dd}
@media (prefers-color-scheme:dark){:root{--bg-color:#00390a55;--text-color:#7edb7b;--border-color:#7edb7b;--hover-bg-color:#00390aAA;--active-bg-color:#7edb7b;--close-btn-bg:#7edb7b;--close-btn-text:#00390a;--reader-bg:#1a1c19;--reader-text-color:#e2e3dd;--link-color:#7edb7b;--link-hover:#76cd74;--pre-bg-color:#1e201d;--pre-border-color:#424940;--code-bg-color:#42494047}}
.userscript-webPreviewTooltip{position:absolute;z-index:9999;user-select:none;background:var(--active-bg-color);color:var(--close-btn-text);padding:1px 8px;font-size:12px;font-weight:normal;height:fit-content;border-radius:16px;border:1px solid var(--border-color);}
.userscript-closeBtn{position:fixed;top:calc(8% + 5px);right:18px;z-index:100000;background:var(--close-btn-bg);color:var(--close-btn-text);padding:8px 20px;margin:6px;border-radius:30px;font-weight:bold;border:0;border-bottom:1px solid var(--border-color);cursor:pointer}
.userscript-closeBtn:hover{background:var(--link-hover)}
.userscript-webPreviewReader{font-size:medium;text-align:left;position:fixed;top:8vh;right:10px;bottom:0px;z-index:99999;width:35%;height:calc(100vh - 8%);min-width:340px;background:var(--reader-bg);color:var(--reader-text-color);overflow:hidden;box-shadow:0 0 0 1px rgba(0,0,0,.1),0 2px 4px 1px rgba(0,0,0,.18);border-radius:28px 28px 0px 0px}
.userscript-webPreviewReader #videoFrame{width:calc(100% - 16px);height:calc(100% - 120px);background:var(--code-bg-color);border:none;border-radius:30px;margin:8px 8px;box-shadow:0 .5px 1.5px 0 rgba(0,0,0,.19),0 0 1px 0 rgba(0,0,0,.039)}
.userscript-webPreviewReader #FadeInContainer{overflow-y:scroll;overflow-x:hidden;border-radius:15px 15px 0px 0px;width:100%;height:100%}
#FadeInContainer .ContentShow{padding:16px;margin:8px;background:var(--code-bg-color);border-radius:30px;overflow:hidden;color:var(--reader-text-color);box-shadow:0 .5px 1.5px 0 rgba(0,0,0,.19),0 0 1px 0 rgba(0,0,0,.039)}
#FadeInContainer img{max-width:92% !important;max-height:85vh !important;position:relative !important;top:0 !important;left:0 !important;border-radius:10px}
#FadeInContainer svg{max-width:40% !important;max-height:60vh !important;position:relative !important;top:0 !important;left:0 !important;border-radius:10px}
#FadeInContainer a{color:var(--link-color);text-decoration:underline 1px solid var(--link-hover);margin:0px 3px}
#FadeInContainer code{font-family:Consolas,Courier,Courier New,monospace}
#FadeInContainer pre{color:var(--reader-text-color);background:var(--pre-bg-color);width:90%;padding:5px;margin:5px 0px;overflow-y:auto;height:fit-content;border:1px solid var(--pre-border-color);border-radius:5px}
#FadeInContainer code:not(pre code){color:var(--reader-text-color);background:var(--code-bg-color);border-radius:0.25rem;padding:.125rem .375rem;line-height:1.75;word-wrap:break-word;border:1px solid var(--pre-border-color)}
`);
/* 页面加载时插入DOM */
/* 阅读器 */
if( $("#userscript-webPreviewReader").length == 0 ){
var $previewReader = $('<div>', {
class: 'userscript-webPreviewReader',
id: 'userscript-webPreviewReader'
}).appendTo('body');
var $closeBtn = $('<button>', {
text: '关闭',
class: 'userscript-closeBtn',
id: 'userscript-closeBtn',
}).appendTo('body');
$closeBtn.on('click', function() {
$previewReader.hide();
$closeBtn.hide();
});
}
/* 隐藏阅读器 */
$("#userscript-webPreviewReader").hide();
$("#userscript-closeBtn").hide();
/* 自动匹配搜索结果并插入按钮 */
initAnalyze();
return;
}
(function() {
'use strict';
init();
window.addEventListener('urlchange', (info) => {
if($(".userscript-webPreviewReader").length > 0 && $(".userscript-webPreviewTooltip").length > 0) { return; }
setTimeout(function(){
init();
}, 1600)
});
})();