// ==UserScript==
// @name NodeSeek Addon
// @namespace http://tampermonkey.net/
// @version 0.1
// @description NodeSeek 插件
// @author lauvinson
// @match https://www.nodeseek.com/*
// @match http://www.nodeseek.com/*
// @grant none
// @license GNU GPLv3
// ==/UserScript==
(function() {
'use strict';
// 样式
const style = document.createElement('style');
style.textContent = `
.ns-iframe-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 9999;
display: flex;
justify-content: flex-end; /* 调整为靠右对齐 */
align-items: center;
}
.ns-iframe-wrapper {
position: relative;
width: 70%;
height: 100%;
background: white;
border-radius: 8px;
overflow: hidden;
}
/* 添加左侧信息卡片样式 */
.ns-sidebar-info {
position: fixed;
left: 15%;
top: 50%;
transform: translate(-50%, -50%);
width: 280px;
background: rgba(255, 255, 255, 0.95);
border-radius: 8px;
padding: 20px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
z-index: 9998;
display: none; /* 初始状态为隐藏 */
flex-direction: column;
align-items: center;
text-align: center;
}
/* 添加前后帖子导航卡片样式 */
.ns-post-nav-card {
position: fixed;
left: 5%;
width: 250px;
background: rgba(255, 255, 255, 0.75);
border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
z-index: 9997;
display: none; /* 初始状态为隐藏 */
flex-direction: column;
align-items: center;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
opacity: 0.7;
}
.ns-post-nav-card:hover {
opacity: 1;
background: rgba(255, 255, 255, 0.95);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}
.ns-post-nav-card.prev {
top: 25%;
transform: translateY(-50%);
}
.ns-post-nav-card.next {
top: 75%;
transform: translateY(-50%);
}
.ns-post-nav-card .nav-label {
display: block;
font-size: 13px;
color: #888;
margin-bottom: 5px;
}
.ns-post-nav-card .nav-title {
font-size: 15px;
color: #333;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
.ns-sidebar-info .avatar-container {
margin-bottom: 15px;
}
.ns-sidebar-info .avatar-container img {
width: 60px;
height: 60px;
border-radius: 50%;
object-fit: cover;
border: 2px solid #2ea44f;
}
.ns-sidebar-info .title {
font-size: 18px;
font-weight: bold;
margin: 0 0 15px 0;
color: #333;
}
.ns-sidebar-info .user-info {
margin-bottom: 10px;
color: #666;
font-size: 14px;
}
/* 添加媒体查询,小屏幕使用100%宽度 */
@media screen and (max-width: 800px) {
.ns-iframe-wrapper {
width: 100%;
border-radius: 0;
}
.ns-iframe-container {
justify-content: center;
}
.ns-sidebar-info {
display: none; /* 小屏幕隐藏左侧信息 */
}
}
.ns-iframe {
width: 100%;
height: 100%;
border: none;
opacity: 0; /* 初始隐藏iframe */
transition: opacity 0.3s ease;
}
.ns-close-btn {
position: absolute;
top: 10px;
right: 10px;
background: #f44336;
color: white;
border: none;
border-radius: 50%;
width: 30px;
height: 30px;
font-size: 16px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
}
/* 回到顶部和底部按钮样式 */
.ns-to-top-btn, .ns-to-bottom-btn {
position: absolute;
right: 20px;
width: 40px;
height: 40px;
background: rgba(46, 164, 79, 0.8);
color: white;
border: none;
border-radius: 50%;
font-size: 20px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
opacity: 0;
transition: opacity 0.3s ease, background-color 0.2s ease;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.ns-to-top-btn:hover, .ns-to-bottom-btn:hover {
background: rgba(46, 164, 79, 1);
}
.ns-to-top-btn {
bottom: 70px;
}
.ns-to-bottom-btn {
bottom: 20px;
}
/* 新增按钮显示动画 */
.ns-btn-show {
opacity: 1;
}
/* 新增样式 - 使列表项可点击并添加悬停效果 */
.post-list-item {
cursor: pointer;
transition: background-color 0.2s ease;
}
.post-list-item:hover {
background-color: #f5f5f5;
}
/* 加载指示器 */
.ns-loader {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50px;
height: 50px;
border: 2px solid #f3f3f3;
border-top: 2px solid #2ea44f;
border-radius: 50%;
animation: spin 1s linear infinite;
z-index: 5;
}
@keyframes spin {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
}
/* 表情包搜索样式 */
.ns-emoji-search {
position: relative;
margin-top: 5px;
margin-bottom: 5px;
padding: 0 1% 0 1%;
width: 98%;
display: flex;
align-items: center;
}
.ns-emoji-search input {
flex: 1;
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
margin-right: 5px;
}
/* 搜索按钮样式 - 保留用于自定义exp-item */
.exp-item.ns-emoji-btn {
cursor: pointer;
background-color: #2ea44f !important;
color: white !important;
transition: background-color 0.2s;
}
.exp-item.ns-emoji-btn:hover {
background-color: #2c974b !important;
opacity: 0.9;
}
.ns-emoji-results {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
margin-bottom: 5px;
display: flex;
flex-wrap: wrap;
gap: 10px;
max-height: 300px;
overflow-y: auto;
padding: 10px;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
z-index: 100;
}
.ns-emoji-results-header {
width: 100%;
text-align: center;
margin-bottom: 10px;
font-size: 14px;
color: #666;
}
.ns-emoji-item {
width: 80px;
height: 80px;
border-radius: 4px;
cursor: pointer;
overflow: hidden;
transition: transform 0.2s;
border: 1px solid #eee;
background-color: white;
flex-shrink: 0;
}
.ns-emoji-item:hover {
transform: scale(1.1);
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
border-color: #2ea44f;
z-index: 1;
}
.ns-emoji-item img {
width: 100%;
height: 100%;
object-fit: contain;
}
/* 调试信息样式 */
.ns-debug-info {
background-color: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px;
margin-top: 5px;
font-family: monospace;
font-size: 12px;
max-height: 100px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-all;
}
`;
document.head.appendChild(style);
// 主函数
function init() {
// 找到所有帖子列表项
const postItems = document.querySelectorAll('.post-list-item');
// 创建帖子列表映射,用于获取前后帖子
const postMap = new Map();
let postOrder = [];
// 将所有帖子信息存入映射
postItems.forEach((item, index) => {
const postLink = item.querySelector('.post-title a[href^="/post-"]');
if (postLink) {
const href = postLink.href;
const title = postLink.textContent.trim();
postMap.set(href, {
title: title,
element: item,
index: index
});
postOrder.push(href);
}
});
// 为每个帖子列表项添加点击事件处理
postItems.forEach(item => {
item.addEventListener('click', function(e) {
// 检查点击的是否是头像或头像的容器
const isAvatarClick = e.target.classList.contains('avatar') ||
e.target.classList.contains('avatar-normal') ||
e.target.closest('a[href^="/space/"]') !== null ||
e.target.closest('.avatar-wrapper') !== null;
// 如果点击的是头像相关元素,不阻止默认行为,允许正常跳转
if (isAvatarClick) {
return; // 直接返回,不阻止事件,允许正常导航
}
// 找到当前列表项中的帖子链接
const postLink = item.querySelector('.post-title a[href^="/post-"]');
if (postLink) {
// 阻止事件冒泡和默认行为
e.preventDefault();
e.stopPropagation();
// 收集帖子信息 - 直接使用原有DOM元素
const postTitle = postLink.textContent.trim();
const avatarImg = item.querySelector('.avatar-normal, img.avatar');
const userElement = item.querySelector('.author-name, .post-username, .username');
const postInfoElement = item.querySelector('.post-info, .info');
// 获取前后帖子信息
const currentPostUrl = postLink.href;
const currentIndex = postMap.get(currentPostUrl)?.index;
let prevPost = null;
let nextPost = null;
if (currentIndex !== undefined) {
// 获取前一篇帖子
if (currentIndex > 0) {
const prevUrl = postOrder[currentIndex - 1];
const prevInfo = postMap.get(prevUrl);
if (prevInfo) {
prevPost = {
url: prevUrl,
title: prevInfo.title
};
}
}
// 获取后一篇帖子
if (currentIndex < postOrder.length - 1) {
const nextUrl = postOrder[currentIndex + 1];
const nextInfo = postMap.get(nextUrl);
if (nextInfo) {
nextPost = {
url: nextUrl,
title: nextInfo.title
};
}
}
}
// 在iframe中打开帖子,传入原始DOM元素和前后帖子信息
openInIframe(postLink.href, {
title: postTitle,
avatarElement: avatarImg,
userElement: userElement,
infoElement: postInfoElement,
prevPost: prevPost,
nextPost: nextPost,
postMap: postMap,
postOrder: postOrder
});
}
});
});
}
// 表情包搜索API
async function searchEmojis(query) {
// 使用提供的API
const url = `https://oiapi.net/API/EmoticonPack/?keyword=${encodeURIComponent(query)}`;
try {
console.log('搜索表情包,请求URL:', url);
const response = await fetch(url);
console.log('API响应状态:', response.status);
if (!response.ok) {
console.error('API响应非200:', response.status, response.statusText);
return { error: `API响应错误: ${response.status}`, data: [] };
}
const data = await response.json();
console.log('API返回数据:', data);
// 检查是否有数据 - 注意这里API返回的成功code是1而不是200
if (data.code === 1 && Array.isArray(data.data) && data.data.length > 0) {
console.log(`找到${data.data.length}个表情包`);
return {
error: null,
data: data.data.map(item => ({
url: item.url,
preview: item.url,
width: item.width,
height: item.height,
type: item.type,
size: item.size
}))
};
} else {
console.log('API返回成功但无数据:', data);
return {
error: '未找到相关表情包',
data: []
};
}
} catch (error) {
console.error('搜索表情包出错:', error);
return { error: '请求出错: ' + error.message, data: [] };
}
}
// 在iframe中打开链接
function openInIframe(url, domElements = null) {
// 禁用父页面滚动
const originalBodyOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
// 创建容器
const container = document.createElement('div');
container.className = 'ns-iframe-container';
// 创建iframe包装器
const wrapper = document.createElement('div');
wrapper.className = 'ns-iframe-wrapper';
// 创建加载指示器容器
const loaderContainer = document.createElement('div');
loaderContainer.className = 'ns-loader-container';
loaderContainer.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: white;
z-index: 10;
`;
// 如果有帖子信息,创建并添加左侧信息卡片
if (domElements) {
// 创建左侧信息卡片
const sidebarInfo = document.createElement('div');
sidebarInfo.className = 'ns-sidebar-info';
// 创建并添加前一篇帖子导航卡片(如果有)
if (domElements.prevPost) {
const prevNav = document.createElement('div');
prevNav.className = 'ns-post-nav-card prev';
prevNav.innerHTML = `
<span class="nav-label">上一篇</span>
<div class="nav-title">${domElements.prevPost.title}</div>
`;
prevNav.setAttribute('data-url', domElements.prevPost.url);
prevNav.addEventListener('click', function() {
loadNewPost(this.getAttribute('data-url'), domElements);
});
container.appendChild(prevNav);
}
// 添加帖子标题
if (domElements.title) {
const titleElement = document.createElement('div');
titleElement.className = 'title';
titleElement.textContent = domElements.title;
sidebarInfo.appendChild(titleElement);
}
// 添加头像
if (domElements.avatarElement) {
const avatarContainer = document.createElement('div');
avatarContainer.className = 'avatar-container';
// 克隆原始头像并添加样式
const avatarClone = domElements.avatarElement.cloneNode(true);
avatarContainer.appendChild(avatarClone);
sidebarInfo.appendChild(avatarContainer);
}
// 添加用户名和其他信息
if (domElements.userElement) {
const userInfo = document.createElement('div');
userInfo.className = 'user-info';
const usernameClone = domElements.userElement.cloneNode(true);
userInfo.appendChild(usernameClone);
sidebarInfo.appendChild(userInfo);
}
// 添加帖子信息
if (domElements.infoElement) {
const postInfo = document.createElement('div');
postInfo.className = 'post-info';
const infoClone = domElements.infoElement.cloneNode(true);
postInfo.appendChild(infoClone);
sidebarInfo.appendChild(postInfo);
}
// 将信息卡片添加到容器中
container.appendChild(sidebarInfo);
// 创建并添加下一篇帖子导航卡片(如果有)
if (domElements.nextPost) {
const nextNav = document.createElement('div');
nextNav.className = 'ns-post-nav-card next';
nextNav.innerHTML = `
<span class="nav-label">下一篇</span>
<div class="nav-title">${domElements.nextPost.title}</div>
`;
nextNav.setAttribute('data-url', domElements.nextPost.url);
nextNav.addEventListener('click', function() {
loadNewPost(this.getAttribute('data-url'), domElements);
});
container.appendChild(nextNav);
}
// 将信息卡片添加到容器中
container.appendChild(sidebarInfo);
// 创建简单信息容器用于加载界面
const infoContainer = document.createElement('div');
infoContainer.style.cssText = `
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 600px;
padding: 10px 20px 20px;
margin-bottom: 20px;
text-align: center;
`;
// 直接复制头像元素,保留原有类名和样式,放在第一行居中
if (domElements.avatarElement) {
const avatarContainer = document.createElement('div');
avatarContainer.style.cssText = `
display: flex;
justify-content: center;
margin-bottom: 15px;
width: 100%;
`;
const avatarClone = domElements.avatarElement.cloneNode(true);
avatarContainer.appendChild(avatarClone);
infoContainer.appendChild(avatarContainer);
}
// 添加标题,放在第二行居中
if (domElements.title) {
const titleElement = document.createElement('div');
titleElement.textContent = domElements.title;
titleElement.style.cssText = `
font-size: 18px;
font-weight: bold;
margin: 0 0 15px 0;
text-align: center;
color: #333;
width: 100%;
`;
infoContainer.appendChild(titleElement);
}
// 用户名和帖子信息,放在第三行居中
const infoWrapper = document.createElement('div');
infoWrapper.style.cssText = `
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
margin-bottom: 20px;
`;
// 直接复制用户名元素,保留原有类名和样式
if (domElements.userElement) {
const usernameContainer = document.createElement('div');
usernameContainer.style.cssText = `
display: flex;
justify-content: center;
width: 100%;
margin-bottom: 5px;
`;
const usernameClone = domElements.userElement.cloneNode(true);
usernameContainer.appendChild(usernameClone);
infoWrapper.appendChild(usernameContainer);
}
// 直接复制帖子信息元素,保留原有类名和样式
if (domElements.infoElement) {
const infoElementContainer = document.createElement('div');
infoElementContainer.style.cssText = `
display: flex;
justify-content: center;
width: 100%;
`;
const infoClone = domElements.infoElement.cloneNode(true);
infoElementContainer.appendChild(infoClone);
infoWrapper.appendChild(infoElementContainer);
}
infoContainer.appendChild(infoWrapper);
// 创建加载指示器
const loader = document.createElement('div');
loader.className = 'ns-loader';
loader.style.cssText = `
width: 40px;
height: 40px;
margin: 100px 0;
border: 2px solid #f3f3f3;
border-top: 2px solid #2ea44f;
border-radius: 50%;
animation: spin 1s linear infinite;
`;
infoContainer.appendChild(loader);
loaderContainer.appendChild(infoContainer);
} else {
// 如果没有元素信息,只显示加载指示器
const loader = document.createElement('div');
loader.className = 'ns-loader';
loader.style.cssText = `
width: 40px;
height: 40px;
border: 2px solid #f3f3f3;
border-top: 2px solid #2ea44f;
border-radius: 50%;
animation: spin 1s linear infinite;
`;
loaderContainer.appendChild(loader);
}
// 创建关闭按钮
const closeBtn = document.createElement('button');
closeBtn.className = 'ns-close-btn';
closeBtn.innerHTML = '✕';
closeBtn.title = 'ESC'; // 添加悬停提示,显示ESC快捷键
closeBtn.onclick = function(e) {
e.stopPropagation(); // 防止事件传递到container
closeIframe();
};
// 创建iframe
const iframe = document.createElement('iframe');
iframe.className = 'ns-iframe';
iframe.src = url;
// 防止滚动穿透
iframe.addEventListener('load', function() {
try {
// 尝试阻止iframe内部滚动穿透到父页面
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
const iframeBody = iframeDoc.body;
// 在iframe中滚动到底部或顶部时,阻止继续滚动影响父页面
iframeDoc.addEventListener('wheel', function(e) {
const scrollingElement = iframeDoc.scrollingElement || iframeBody;
const scrollTop = scrollingElement.scrollTop;
const scrollHeight = scrollingElement.scrollHeight;
const clientHeight = scrollingElement.clientHeight;
// 如果已经滚动到底部,并且继续向下滚动
if (scrollTop + clientHeight >= scrollHeight && e.deltaY > 0) {
e.preventDefault();
}
// 如果已经滚动到顶部,并且继续向上滚动
if (scrollTop <= 0 && e.deltaY < 0) {
e.preventDefault();
}
}, { passive: false });
} catch (error) {
console.error('无法阻止iframe滚动穿透:', error);
}
});
// iframe加载完成后处理页面内容
iframe.onload = function() {
try {
// 获取iframe文档对象
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
// 移除header和footer
const header = iframeDoc.querySelector('header');
const footer = iframeDoc.querySelector('footer');
if (header) header.style.display = 'none';
if (footer) footer.style.display = 'none';
// 移除右侧面板
const rightPanel = iframeDoc.getElementById('nsk-right-panel-container');
if (rightPanel) rightPanel.style.display = 'none';
// 添加自定义样式到iframe内部
const styleElement = iframeDoc.createElement('style');
styleElement.textContent = `
/* 隐藏可能的导航和页脚 */
header, .header, #header,
footer, .footer, #footer,
nav, .nav, #nav,
.site-header, .site-footer,
.main-header, .main-footer,
.page-header, .page-footer,
#nsk-right-panel-container {
display: none !important;
}
/* 调整主要内容区域 */
body {
padding-top: 0 !important;
margin-top: 0 !important;
}
/* 让内容区域占满整个空间 */
.container, .main-content, .content,
#content, .page-content, .site-content,
main, .main, #main {
padding-top: 10px !important;
margin-top: 0 !important;
max-width: 100% !important;
}
/* 调整文章内容宽度,右侧面板被移除后 */
.post-detail-card {
width: 100% !important;
max-width: 100% !important;
}
/* 强制设置nsk-container的margin为0 */
.nsk-container {
margin: 0 !important;
}
/* 平滑滚动效果 */
html {
scroll-behavior: smooth;
}
`;
iframeDoc.head.appendChild(styleElement);
// 添加回到顶部和底部按钮
const toTopBtn = document.createElement('button');
toTopBtn.className = 'ns-to-top-btn';
toTopBtn.innerHTML = '↑';
toTopBtn.title = '回到顶部';
toTopBtn.onclick = () => {
iframeDoc.documentElement.scrollTop = 0;
};
const toBottomBtn = document.createElement('button');
toBottomBtn.className = 'ns-to-bottom-btn';
toBottomBtn.innerHTML = '↓';
toBottomBtn.title = '到达底部';
toBottomBtn.onclick = () => {
iframeDoc.documentElement.scrollTop = iframeDoc.documentElement.scrollHeight;
};
wrapper.appendChild(toTopBtn);
wrapper.appendChild(toBottomBtn);
// 滚动时显示/隐藏按钮
let scrollTimer;
const handleScroll = () => {
// 清除之前的定时器
clearTimeout(scrollTimer);
const scrollTop = iframeDoc.documentElement.scrollTop;
const scrollHeight = iframeDoc.documentElement.scrollHeight;
const clientHeight = iframeDoc.documentElement.clientHeight;
// 如果滚动位置超过100px,显示回到顶部按钮
if (scrollTop > 100) {
toTopBtn.classList.add('ns-btn-show');
} else {
toTopBtn.classList.remove('ns-btn-show');
}
// 如果距离底部超过200px,显示回到底部按钮
if (scrollHeight - scrollTop - clientHeight > 200) {
toBottomBtn.classList.add('ns-btn-show');
} else {
toBottomBtn.classList.remove('ns-btn-show');
}
// 设置5秒后自动隐藏按钮
scrollTimer = setTimeout(() => {
toTopBtn.classList.remove('ns-btn-show');
toBottomBtn.classList.remove('ns-btn-show');
}, 5000);
};
// 初始检查滚动位置
handleScroll();
// 监听滚动事件
iframeDoc.addEventListener('scroll', handleScroll);
// 添加表情包搜索功能到评论框
addEmojiSearchToCommentBox(iframeDoc);
// 处理翻页链接,拦截点击实现无刷新翻页
setupPagination(iframeDoc, iframe);
// 处理完成后移除加载指示器并显示iframe
wrapper.removeChild(loaderContainer);
iframe.style.opacity = 1;
// 显示左侧信息卡片(仅在iframe加载完成后)
const sidebarInfo = container.querySelector('.ns-sidebar-info');
if (sidebarInfo) {
sidebarInfo.style.display = 'flex';
}
// 显示前后帖子导航卡片
const prevNavCard = container.querySelector('.ns-post-nav-card.prev');
if (prevNavCard) {
prevNavCard.style.display = 'flex';
}
const nextNavCard = container.querySelector('.ns-post-nav-card.next');
if (nextNavCard) {
nextNavCard.style.display = 'flex';
}
} catch (error) {
console.error('无法修改iframe内容:', error);
// 出错时也显示iframe,确保用户能看到内容
wrapper.removeChild(loaderContainer);
iframe.style.opacity = 1;
// 即使出错也显示左侧信息卡片
const sidebarInfo = container.querySelector('.ns-sidebar-info');
if (sidebarInfo) {
sidebarInfo.style.display = 'flex';
}
// 即使出错也显示前后帖子导航卡片
const prevNavCard = container.querySelector('.ns-post-nav-card.prev');
if (prevNavCard) {
prevNavCard.style.display = 'flex';
}
const nextNavCard = container.querySelector('.ns-post-nav-card.next');
if (nextNavCard) {
nextNavCard.style.display = 'flex';
}
}
};
// 组装元素
wrapper.appendChild(closeBtn);
wrapper.appendChild(loaderContainer); // 先添加加载指示器容器
wrapper.appendChild(iframe);
container.appendChild(wrapper);
document.body.appendChild(container);
// 点击遮罩层关闭iframe
container.addEventListener('click', function(e) {
// 只有点击遮罩层本身才关闭,点击iframe内部不触发
if (e.target === container) {
closeIframe();
}
});
// 阻止iframe包装器的点击事件冒泡
wrapper.addEventListener('click', function(e) {
e.stopPropagation();
});
// 关闭iframe的函数
function closeIframe() {
document.body.removeChild(container);
document.removeEventListener('keydown', escListener);
// 恢复父页面滚动
document.body.style.overflow = originalBodyOverflow;
}
// 按ESC键关闭iframe
function escListener(e) {
if (e.key === 'Escape') {
closeIframe();
}
}
document.addEventListener('keydown', escListener);
// 加载新帖子的函数 - 移到openInIframe内部以便访问container变量
function loadNewPost(url, currentDomElements) {
// 获取当前的postMap和postOrder
const postMap = currentDomElements.postMap;
const postOrder = currentDomElements.postOrder;
if (!postMap || !postOrder) return;
// 获取新帖子的信息
const newPostInfo = postMap.get(url);
if (!newPostInfo) return;
const newPostIndex = newPostInfo.index;
const newPostElement = newPostInfo.element;
if (!newPostElement) return;
// 获取新帖子的详细信息
const postLink = newPostElement.querySelector('.post-title a[href^="/post-"]');
if (!postLink) return;
const postTitle = postLink.textContent.trim();
const avatarImg = newPostElement.querySelector('.avatar-normal, img.avatar');
const userElement = newPostElement.querySelector('.author-name, .post-username, .username');
const postInfoElement = newPostElement.querySelector('.post-info, .info');
// 获取新帖子的前后帖子信息
let prevPost = null;
let nextPost = null;
// 获取前一篇帖子
if (newPostIndex > 0) {
const prevUrl = postOrder[newPostIndex - 1];
const prevInfo = postMap.get(prevUrl);
if (prevInfo) {
prevPost = {
url: prevUrl,
title: prevInfo.title
};
}
}
// 获取后一篇帖子
if (newPostIndex < postOrder.length - 1) {
const nextUrl = postOrder[newPostIndex + 1];
const nextInfo = postMap.get(nextUrl);
if (nextInfo) {
nextPost = {
url: nextUrl,
title: nextInfo.title
};
}
}
// 更新左侧信息卡片
const sidebarInfo = container.querySelector('.ns-sidebar-info');
if (sidebarInfo) {
// 临时隐藏侧边栏,避免旧内容闪烁
sidebarInfo.style.display = 'none';
// 清空现有内容
sidebarInfo.innerHTML = '';
// 添加帖子标题
if (postTitle) {
const titleElement = document.createElement('div');
titleElement.className = 'title';
titleElement.textContent = postTitle;
sidebarInfo.appendChild(titleElement);
}
// 添加头像
if (avatarImg) {
const avatarContainer = document.createElement('div');
avatarContainer.className = 'avatar-container';
// 克隆原始头像并添加样式
const avatarClone = avatarImg.cloneNode(true);
avatarContainer.appendChild(avatarClone);
sidebarInfo.appendChild(avatarContainer);
}
// 添加用户名和其他信息
if (userElement) {
const userInfo = document.createElement('div');
userInfo.className = 'user-info';
const usernameClone = userElement.cloneNode(true);
userInfo.appendChild(usernameClone);
sidebarInfo.appendChild(userInfo);
}
// 添加帖子信息
if (postInfoElement) {
const postInfo = document.createElement('div');
postInfo.className = 'post-info';
const infoClone = postInfoElement.cloneNode(true);
postInfo.appendChild(infoClone);
sidebarInfo.appendChild(postInfo);
}
}
// 更新前后帖子导航卡片
// 移除旧的导航卡片
const oldPrevCard = container.querySelector('.ns-post-nav-card.prev');
if (oldPrevCard) {
container.removeChild(oldPrevCard);
}
const oldNextCard = container.querySelector('.ns-post-nav-card.next');
if (oldNextCard) {
container.removeChild(oldNextCard);
}
// 添加新的前一篇导航卡片(如果有)
if (prevPost) {
const prevNav = document.createElement('div');
prevNav.className = 'ns-post-nav-card prev';
prevNav.innerHTML = `
<span class="nav-label">上一篇</span>
<div class="nav-title">${prevPost.title}</div>
`;
prevNav.setAttribute('data-url', prevPost.url);
prevNav.addEventListener('click', function() {
loadNewPost(this.getAttribute('data-url'), {
postMap: postMap,
postOrder: postOrder
});
});
container.appendChild(prevNav);
}
// 添加新的下一篇导航卡片(如果有)
if (nextPost) {
const nextNav = document.createElement('div');
nextNav.className = 'ns-post-nav-card next';
nextNav.innerHTML = `
<span class="nav-label">下一篇</span>
<div class="nav-title">${nextPost.title}</div>
`;
nextNav.setAttribute('data-url', nextPost.url);
nextNav.addEventListener('click', function() {
loadNewPost(this.getAttribute('data-url'), {
postMap: postMap,
postOrder: postOrder
});
});
container.appendChild(nextNav);
}
// 显示加载指示器
const newLoaderContainer = document.createElement('div');
newLoaderContainer.className = 'ns-loader-container';
newLoaderContainer.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: white;
z-index: 10;
`;
// 添加帖子信息到加载指示器,与初始加载时保持一致
if (postTitle || avatarImg || userElement || postInfoElement) {
// 创建简单信息容器用于加载界面
const infoContainer = document.createElement('div');
infoContainer.style.cssText = `
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 600px;
padding: 10px 20px 20px;
margin-bottom: 20px;
text-align: center;
`;
// 添加头像元素,放在第一行居中
if (avatarImg) {
const avatarContainer = document.createElement('div');
avatarContainer.style.cssText = `
display: flex;
justify-content: center;
margin-bottom: 15px;
width: 100%;
`;
const avatarClone = avatarImg.cloneNode(true);
avatarContainer.appendChild(avatarClone);
infoContainer.appendChild(avatarContainer);
}
// 添加标题,放在第二行居中
if (postTitle) {
const titleElement = document.createElement('div');
titleElement.textContent = postTitle;
titleElement.style.cssText = `
font-size: 18px;
font-weight: bold;
margin: 0 0 15px 0;
text-align: center;
color: #333;
width: 100%;
`;
infoContainer.appendChild(titleElement);
}
// 用户名和帖子信息,放在第三行居中
const infoWrapper = document.createElement('div');
infoWrapper.style.cssText = `
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
margin-bottom: 20px;
`;
// 添加用户名元素
if (userElement) {
const usernameContainer = document.createElement('div');
usernameContainer.style.cssText = `
display: flex;
justify-content: center;
width: 100%;
margin-bottom: 5px;
`;
const usernameClone = userElement.cloneNode(true);
usernameContainer.appendChild(usernameClone);
infoWrapper.appendChild(usernameContainer);
}
// 添加帖子信息元素
if (postInfoElement) {
const infoElementContainer = document.createElement('div');
infoElementContainer.style.cssText = `
display: flex;
justify-content: center;
width: 100%;
`;
const infoClone = postInfoElement.cloneNode(true);
infoElementContainer.appendChild(infoClone);
infoWrapper.appendChild(infoElementContainer);
}
infoContainer.appendChild(infoWrapper);
// 创建加载指示器
const loader = document.createElement('div');
loader.className = 'ns-loader';
loader.style.cssText = `
width: 40px;
height: 40px;
margin: 100px 0;
border: 2px solid #f3f3f3;
border-top: 2px solid #2ea44f;
border-radius: 50%;
animation: spin 1s linear infinite;
`;
infoContainer.appendChild(loader);
newLoaderContainer.appendChild(infoContainer);
} else {
// 如果没有帖子信息,只显示加载指示器
const loader = document.createElement('div');
loader.className = 'ns-loader';
loader.style.cssText = `
width: 40px;
height: 40px;
border: 2px solid #f3f3f3;
border-top: 2px solid #2ea44f;
border-radius: 50%;
animation: spin 1s linear infinite;
`;
newLoaderContainer.appendChild(loader);
}
// 更新iframe的src
iframe.style.opacity = 0;
wrapper.appendChild(newLoaderContainer);
iframe.src = url;
// iframe加载完成后,移除加载指示器并显示左侧信息卡片
iframe.onload = function() {
try {
// 原有的iframe加载完成处理代码...
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
// 移除header和footer
const header = iframeDoc.querySelector('header');
const footer = iframeDoc.querySelector('footer');
if (header) header.style.display = 'none';
if (footer) footer.style.display = 'none';
// 移除右侧面板
const rightPanel = iframeDoc.getElementById('nsk-right-panel-container');
if (rightPanel) rightPanel.style.display = 'none';
// 添加自定义样式到iframe内部
const styleElement = iframeDoc.createElement('style');
styleElement.textContent = `
/* 隐藏可能的导航和页脚 */
header, .header, #header,
footer, .footer, #footer,
nav, .nav, #nav,
.site-header, .site-footer,
.main-header, .main-footer,
.page-header, .page-footer,
#nsk-right-panel-container {
display: none !important;
}
/* 调整主要内容区域 */
body {
padding-top: 0 !important;
margin-top: 0 !important;
}
/* 让内容区域占满整个空间 */
.container, .main-content, .content,
#content, .page-content, .site-content,
main, .main, #main {
padding-top: 10px !important;
margin-top: 0 !important;
max-width: 100% !important;
}
/* 调整文章内容宽度,右侧面板被移除后 */
.post-detail-card {
width: 100% !important;
max-width: 100% !important;
}
/* 强制设置nsk-container的margin为0 */
.nsk-container {
margin: 0 !important;
}
/* 平滑滚动效果 */
html {
scroll-behavior: smooth;
}
`;
iframeDoc.head.appendChild(styleElement);
// 添加表情包搜索功能到评论框
addEmojiSearchToCommentBox(iframeDoc);
// 处理翻页链接,拦截点击实现无刷新翻页
setupPagination(iframeDoc, iframe);
// 处理完成后移除加载指示器并显示iframe
wrapper.removeChild(newLoaderContainer);
iframe.style.opacity = 1;
// 显示左侧信息卡片
if (sidebarInfo) {
sidebarInfo.style.display = 'flex';
}
// 显示前后帖子导航卡片
const prevNavCard = container.querySelector('.ns-post-nav-card.prev');
if (prevNavCard) {
prevNavCard.style.display = 'flex';
}
const nextNavCard = container.querySelector('.ns-post-nav-card.next');
if (nextNavCard) {
nextNavCard.style.display = 'flex';
}
} catch (error) {
console.error('无法修改iframe内容:', error);
// 出错时也显示iframe,确保用户能看到内容
wrapper.removeChild(newLoaderContainer);
iframe.style.opacity = 1;
// 即使出错也显示左侧信息卡片
if (sidebarInfo) {
sidebarInfo.style.display = 'flex';
}
// 即使出错也显示前后帖子导航卡片
const prevNavCard = container.querySelector('.ns-post-nav-card.prev');
if (prevNavCard) {
prevNavCard.style.display = 'flex';
}
const nextNavCard = container.querySelector('.ns-post-nav-card.next');
if (nextNavCard) {
nextNavCard.style.display = 'flex';
}
}
};
}
}
// 添加表情包搜索功能到评论框
function addEmojiSearchToCommentBox(doc) {
// 监听评论框加载
const checkCommentBox = setInterval(() => {
const commentBox = doc.querySelector('.md-editor');
if (commentBox) {
clearInterval(checkCommentBox);
// 找到表情选择区域
const expressionArea = commentBox.querySelector('.expression');
if (!expressionArea) return;
// 检查是否已经存在搜索框,避免重复添加
const existingSearchContainer = expressionArea.parentNode.querySelector('.ns-emoji-search');
if (existingSearchContainer) return;
// 创建表情包搜索容器
const searchContainer = doc.createElement('div');
searchContainer.className = 'ns-emoji-search';
searchContainer.style.position = 'relative';
searchContainer.style.display = 'flex'; // 直接显示
// 创建搜索输入框
const searchInput = doc.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = '搜索表情包...';
// 创建结果显示区域
const resultsContainer = doc.createElement('div');
resultsContainer.className = 'ns-emoji-results';
resultsContainer.style.display = 'none';
// 创建调试信息区域
const debugContainer = doc.createElement('div');
debugContainer.className = 'ns-debug-info';
debugContainer.style.display = 'none';
// 组装元素
searchContainer.appendChild(searchInput);
searchContainer.appendChild(resultsContainer);
searchContainer.appendChild(debugContainer);
// 将搜索容器直接添加到expression元素后面
expressionArea.parentNode.insertBefore(searchContainer, expressionArea.nextSibling);
// 获取编辑器实例
const editorArea = commentBox.querySelector('#code-mirror-editor');
let cmEditor = null;
// 尝试获取CodeMirror编辑器实例
if (editorArea) {
const cmElement = editorArea.querySelector('.CodeMirror');
if (cmElement && cmElement.CodeMirror) {
cmEditor = cmElement.CodeMirror;
}
}
// 回车键搜索
searchInput.addEventListener('keydown', async (e) => {
if (e.key === 'Enter') {
const query = searchInput.value.trim();
if (!query) return;
// 显示"正在搜索"提示
resultsContainer.innerHTML = '<div style="text-align:center;padding:10px;">正在搜索表情包...</div>';
resultsContainer.style.display = 'flex';
// 搜索表情包
const response = await searchEmojis(query);
// 显示结果
if (response.error || response.data.length === 0) {
resultsContainer.innerHTML = `<div style="text-align:center;padding:10px;">${response.error || '未找到相关表情包'}</div>`;
return;
}
// 清空结果容器
resultsContainer.innerHTML = '';
// 添加结果标题
const headerDiv = doc.createElement('div');
headerDiv.className = 'ns-emoji-results-header';
headerDiv.textContent = `找到 ${response.data.length} 个表情包`;
resultsContainer.appendChild(headerDiv);
// 添加表情包元素
response.data.forEach(emoji => {
const emojiItem = doc.createElement('div');
emojiItem.className = 'ns-emoji-item';
const img = doc.createElement('img');
img.src = emoji.url;
img.setAttribute('data-url', emoji.url);
img.setAttribute('title', `${emoji.width}x${emoji.height} ${(emoji.size/1024).toFixed(1)}KB`);
// 添加加载错误处理
img.onerror = function() {
// 图片加载失败时显示错误提示
this.onerror = null;
this.src = 'data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%25%22%20height%3D%22100%25%22%3E%3Ctext%20x%3D%2250%25%22%20y%3D%2250%25%22%20font-size%3D%2210%22%20text-anchor%3D%22middle%22%20dominant-baseline%3D%22middle%22%3E图片加载失败%3C%2Ftext%3E%3C%2Fsvg%3E';
};
emojiItem.appendChild(img);
// 点击表情包插入到编辑器
emojiItem.addEventListener('click', () => {
const imgUrl = emoji.url;
// 使用搜索关键词作为图片alt文本,而不是固定的"表情包"
const markdownImg = ``;
// 如果找到了CodeMirror编辑器,则插入到编辑器中
if (cmEditor) {
const cursor = cmEditor.getCursor();
cmEditor.replaceRange(markdownImg, cursor);
cmEditor.focus();
} else {
// 找不到编辑器,尝试使用剪贴板
try {
navigator.clipboard.writeText(markdownImg).then(() => {
alert('已复制表情包Markdown到剪贴板,请粘贴到评论框');
});
} catch (err) {
console.error('无法复制到剪贴板:', err);
alert('无法自动插入表情包,请手动复制: ' + markdownImg);
}
}
// 隐藏结果
resultsContainer.style.display = 'none';
});
resultsContainer.appendChild(emojiItem);
});
}
});
// 点击页面其他区域关闭结果框
doc.addEventListener('click', (e) => {
if (!searchContainer.contains(e.target)) {
resultsContainer.style.display = 'none';
}
});
}
}, 500);
}
// 设置翻页功能
function setupPagination(iframeDoc, iframe) {
// 获取所有分页链接
const paginationLinks = iframeDoc.querySelectorAll('.nsk-pager a');
if (paginationLinks.length === 0) return;
// 拦截所有分页链接点击事件
paginationLinks.forEach(link => {
link.addEventListener('click', async function(e) {
e.preventDefault();
// 显示加载状态
const pageContent = iframeDoc.querySelector('.comment-container');
if (pageContent) {
// 创建加载指示器
const loadingIndicator = iframeDoc.createElement('div');
loadingIndicator.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
`;
const spinner = iframeDoc.createElement('div');
spinner.style.cssText = `
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #2ea44f;
border-radius: 50%;
animation: spin 1s linear infinite;
`;
// 添加动画样式
const animStyle = iframeDoc.createElement('style');
animStyle.textContent = ` @keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`;
iframeDoc.head.appendChild(animStyle);
loadingIndicator.appendChild(spinner);
// 设置相对定位以便于放置加载指示器
if (window.getComputedStyle(pageContent).position === 'static') {
pageContent.style.position = 'relative';
}
pageContent.appendChild(loadingIndicator);
}
const targetUrl = this.href;
try {
// 使用fetch异步获取新页面内容
const response = await fetch(targetUrl);
if (!response.ok) throw new Error('网络响应不正常');
const htmlText = await response.text();
// 创建临时DOM解析HTML
const parser = new DOMParser();
const newDoc = parser.parseFromString(htmlText, 'text/html');
// 提取主要内容区域
const newContent = newDoc.querySelector('.comment-container');
if (!newContent) throw new Error('无法找到评论内容');
// 获取当前评论容器
const currentContent = iframeDoc.querySelector('.comment-container');
if (currentContent) {
// 替换内容
currentContent.innerHTML = newContent.innerHTML;
// 更新页面URL(但不刷新页面)
if (iframe.contentWindow.history && iframe.contentWindow.history.pushState) {
iframe.contentWindow.history.pushState({}, '', targetUrl);
}
// 重新绑定新页面中的分页链接
setupPagination(iframeDoc, iframe);
// 移除评论区内可能存在的旧表情搜索框,避免重复添加
const oldSearchContainers = iframeDoc.querySelectorAll('.ns-emoji-search');
oldSearchContainers.forEach(container => {
if (container && container.parentNode) {
container.parentNode.removeChild(container);
}
});
// 添加表情包搜索功能到新页面的评论框
addEmojiSearchToCommentBox(iframeDoc);
// 处理引用和回复按钮
setupCommentButtons(iframeDoc);
// 翻页后滚动到顶部
iframeDoc.documentElement.scrollTop = 0;
// 触发滚动事件,以便更新按钮状态
const scrollEvent = new Event('scroll');
iframeDoc.dispatchEvent(scrollEvent);
} else {
throw new Error('无法找到当前评论容器');
}
} catch (error) {
console.error('异步加载页面失败:', error);
// 加载失败时回退到传统导航方式
iframe.src = targetUrl;
}
// 删除加载指示器
const loadingIndicator = iframeDoc.querySelector('.comment-container > div[style*="position: absolute"]');
if (loadingIndicator) {
loadingIndicator.parentNode.removeChild(loadingIndicator);
}
});
});
}
// 设置评论按钮功能(引用和回复)
function setupCommentButtons(iframeDoc) {
// 获取所有引用和回复按钮
const quoteButtons = iframeDoc.querySelectorAll('.comment-menu .menu-item:has(svg[href="#quote"])');
const replyButtons = iframeDoc.querySelectorAll('.comment-menu .menu-item:has(svg[href="#back"])');
// 为引用按钮添加事件
quoteButtons.forEach(button => {
button.addEventListener('click', function(e) {
// 找到对应的评论内容
const commentItem = this.closest('.content-item');
if (!commentItem) return;
// 获取评论ID和作者
const commentId = commentItem.getAttribute('data-comment-id');
const authorElement = commentItem.querySelector('.author-name');
const author = authorElement ? authorElement.textContent : '用户';
// 获取评论内容
const contentElement = commentItem.querySelector('.post-content');
const content = contentElement ? contentElement.textContent.trim() : '';
// 找到编辑器并插入引用
const editor = iframeDoc.querySelector('.CodeMirror');
if (editor && editor.CodeMirror) {
const quote = `> **${author}** 说:\n${content.split('\n').map(line => '> ' + line).join('\n')}\n\n`;
editor.CodeMirror.replaceSelection(quote);
editor.CodeMirror.focus();
// 滚动到编辑器
const editorElement = iframeDoc.querySelector('.md-editor');
if (editorElement) {
editorElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
});
});
// 为回复按钮添加事件
replyButtons.forEach(button => {
button.addEventListener('click', function(e) {
// 找到对应的评论项
const commentItem = this.closest('.content-item');
if (!commentItem) return;
// 获取作者名
const authorElement = commentItem.querySelector('.author-name');
const author = authorElement ? authorElement.textContent : '用户';
// 找到编辑器并插入@
const editor = iframeDoc.querySelector('.CodeMirror');
if (editor && editor.CodeMirror) {
editor.CodeMirror.replaceSelection(`@${author} `);
editor.CodeMirror.focus();
// 滚动到编辑器
const editorElement = iframeDoc.querySelector('.md-editor');
if (editorElement) {
editorElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
});
});
}
// 当DOM加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();