// ==UserScript==
// @name CSDN 专栏优化脚本 📚
// @description 通过在 CSDN 专栏页面添加一个侧边栏菜单,列出当前专栏的所有文章,提升阅读体验 🌟
// @version 1.4.1
// @author Silence
// @match *://blog.csdn.net/*/article/*
// @match *://*.blog.csdn.net/article/*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-start
// @license MIT
// @namespace https://greasyfork.org/users/1394594
// ==/UserScript==
(function () {
'use strict';
const CONFIG = {
cleanParams: GM_getValue('cleanParams', true)
};
// 在脚本开始时立即执行清理
cleanAnalyticsParams();
// 监听URL变化,处理动态加载的情况
window.addEventListener('popstate', cleanAnalyticsParams);
window.addEventListener('pushState', cleanAnalyticsParams);
window.addEventListener('replaceState', cleanAnalyticsParams);
const $ = (Selector, el) => (el || document).querySelector(Selector);
const $$ = (Selector, el) => (el || document).querySelectorAll(Selector);
window.onload = function () {
console.log('CSDN 专栏优化脚本开始加载');
initSidebar();
};
// 添加缓存相关的常量
const CACHE_KEY_PREFIX = 'csdn_column_';
const CACHE_EXPIRE_TIME = 24 * 60 * 60 * 1000; // 24小时过期
// 缓存操作工具函数
const CacheUtil = {
/**
* 获取缓存数据
* @param {string} key - 缓存键名
* @returns {any|null} - 缓存数据或null
*/
get(key) {
const data = localStorage.getItem(CACHE_KEY_PREFIX + key);
if (!data) return null;
try {
const { value, timestamp } = JSON.parse(data);
// 检查是否过期
if (Date.now() - timestamp > CACHE_EXPIRE_TIME) {
this.remove(key);
return null;
}
return value;
} catch (e) {
return null;
}
},
/**
* 设置缓存数据
* @param {string} key - 缓存键名
* @param {any} value - 缓存数据
*/
set(key, value) {
const data = {
value,
timestamp: Date.now()
};
localStorage.setItem(CACHE_KEY_PREFIX + key, JSON.stringify(data));
},
/**
* 删除缓存数据
* @param {string} key - 缓存键名
*/
remove(key) {
localStorage.removeItem(CACHE_KEY_PREFIX + key);
}
};
/**
* 获取专栏文章列表(带缓存和懒加载)
* @param {string} columnId - 专栏ID
* @param {string} blogUsername - 博客用户名
* @param {number} articleCount - 文章总数
* @returns {Promise<Array>} - 文章列表
*/
async function getColumnArticles(columnId, blogUsername, articleCount) {
// 尝试从缓存获取
const cacheKey = `${columnId}_${blogUsername}`;
const cachedData = CacheUtil.get(cacheKey);
if (cachedData) {
console.log('从缓存获取专栏文章');
// 确保缓存的数据也是排序的
return sortArticles(cachedData);
}
const pageSize = 100; // 每页最大100条
const totalPages = Math.ceil(articleCount / pageSize);
let allArticles = [];
try {
// 懒加载:先只加载第一页
const firstPageData = await fetchArticlePage(columnId, blogUsername, 1, pageSize);
allArticles = firstPageData;
// 如果有更多页,异步加载其余页面
if (totalPages > 1) {
loadRemainingPages(columnId, blogUsername, totalPages, pageSize).then(articles => {
allArticles = allArticles.concat(articles);
// 排序后再缓存
const sortedArticles = sortArticles(allArticles);
CacheUtil.set(cacheKey, sortedArticles);
// 触发更新UI
updateArticleList(sortedArticles);
});
} else {
// 只有一页时直接排序并缓存
const sortedArticles = sortArticles(allArticles);
CacheUtil.set(cacheKey, sortedArticles);
allArticles = sortedArticles;
}
} catch (error) {
console.error('获取专栏文章失败:', error);
}
// 返回排序后的结果
return sortArticles(allArticles);
}
/**
* 按文章ID排序
* @param {Array} articles - 文章列表
* @returns {Array} - 排序后的文章列表
*/
function sortArticles(articles) {
return articles.sort((a, b) => {
const aId = parseInt(a.url.split('/').pop());
const bId = parseInt(b.url.split('/').pop());
return aId - bId;
});
}
/**
* 获取单页文章数据
* @param {string} columnId 专栏ID
* @param {string} blogUsername 博客用户名
* @param {number} page 页码
* @param {number} pageSize 每页文章数量
* @return {Promise<Array<{url: string, title: string}>>} 文章列表
*/
async function fetchArticlePage(columnId, blogUsername, page, pageSize) {
try {
const response = await fetch(
`https://blog.csdn.net/phoenix/web/v1/column/article/list?columnId=${columnId}&blogUsername=${blogUsername}&page=${page}&pageSize=${pageSize}`
);
const data = await response.json();
if (data.code === 200) {
return data.data.map(article => ({
url: article.url,
title: article.title
}));
}
throw new Error(`获取专栏文章失败: ${data.message}`);
} catch (error) {
console.error(`获取第${page}页文章失败:`, error);
return [];
}
}
/**
* 异步加载剩余页面
* @param {string} columnId 专栏ID
* @param {string} blogUsername 博客用户名
* @param {number} totalPages 总页数
* @param {number} pageSize 每页文章数量
* @return {Promise<Array<{url: string, title: string}>>} 文章列表
*/
async function loadRemainingPages(columnId, blogUsername, totalPages, pageSize) {
const remainingPages = Array.from(
{ length: totalPages - 1 },
(_, i) => fetchArticlePage(columnId, blogUsername, i + 2, pageSize)
);
try {
const results = await Promise.all(remainingPages);
return results.flat();
} catch (error) {
console.error('加载剩余页面失败:', error);
return [];
}
}
/**
* 更新文章列表UI
* @param {Object} articles 文章信息
*/
function updateArticleList(articles) {
const menu = document.querySelector('.column-menu');
if (!menu) return;
const currentColumnIndex = menu.querySelector('.column-selector').value;
showColumnArticles({
columnTitle: menu.querySelector('option:checked').textContent,
articles
}, menu);
}
/**
* 获取专栏信息
* @returns {Promise<{blogUsername: string, columnId: string}>} 专栏信息
*/
function getColumnInfo() {
const columnInfoListDom = $$('#blogColumnPayAdvert .column-group-item');
const promises = Array.from(columnInfoListDom).map(async element => {
const columnUrl = element.querySelector('.item-target').href;
const columnTitle = element.querySelector('.item-target').title;
const columnInfo = element.querySelector('.item-m').querySelectorAll('span');
let articleCount = 0;
columnInfo.forEach(info => {
if (info.innerText.includes('篇文章')) {
articleCount = info.innerText.replace(' 篇文章', '');
}
})
console.log('文章数量: ', articleCount);
// 从columnUrl获取blogUserName和columnId
const urlInfo = parseColumnUrl(columnUrl);
if (!urlInfo) {
console.error('无法解析专栏 URL:', columnUrl);
return null;
}
const { blogUsername, columnId } = urlInfo;
console.log('解析结果:', { blogUsername, columnId });
// 访问专栏地址,获取专栏所有文章列表
try {
const articles = await getColumnArticles(columnId, blogUsername, articleCount);
return { columnTitle, articles };
} catch (error) {
console.error('Error fetching column articles:', error);
return null;
}
});
return Promise.all(promises).then(results => {
return results.filter(column => column !== null); // 过滤掉null值
});
}
/**
* 解析专栏 URL 获取用户名和专栏 ID
* @param {string} url 专栏地址
* @returns {Promise<{blogUsername: string, columnId: string}>} 专栏信息
*/
function parseColumnUrl(url) {
// 使用正则表达式匹配 URL 中的用户名和专栏 ID
const regex = /blog\.csdn\.net\/([^\/]+)\/category_(\d+)\.html/;
const match = url.match(regex);
if (match) {
return {
blogUsername: match[1], // 第一个捕获组是用户名
columnId: match[2] // 第二个捕获组是专栏 ID
};
}
return null;
}
/**
* 构建专栏目录菜单
* @param {Object} columnInfo 专栏目录信息
* @returns {Object} 菜单元素
*/
function buildMenu(columnInfo) {
const currentUrl = window.location.href;
const menu = document.createElement('div');
menu.classList.add('column-menu');
// 添加专栏选择器
const columnSelector = document.createElement('select');
columnSelector.classList.add('column-selector');
// 找到当前文章所在的专栏
let currentColumnIndex = 0;
columnInfo.forEach((column, index) => {
const option = document.createElement('option');
option.value = index;
option.textContent = column.columnTitle;
columnSelector.appendChild(option);
// 检查当前文章是否在这个专栏中
if (column.articles.some(article => article.url.split('/').pop().split('?')[0] === currentUrl.split('/').pop().split('?')[0])) {
currentColumnIndex = index;
}
});
// 设置当前专栏为默认选中
columnSelector.value = currentColumnIndex;
// 添加切换事件
columnSelector.addEventListener('change', (e) => {
const selectedIndex = e.target.value;
showColumnArticles(columnInfo[selectedIndex], menu);
});
menu.appendChild(columnSelector);
// 显示当前专栏的文章
showColumnArticles(columnInfo[currentColumnIndex], menu);
return menu;
}
/**
* 展示专栏文章列表
* @param {Object} column 专栏信息
* @param {Object} menu 菜单元素
*/
function showColumnArticles(column, menu) {
const currentUrl = window.location.href;
// 移除现有的文章列表
const existingList = menu.querySelector('.article-list');
if (existingList) {
existingList.remove();
}
const articleList = document.createElement('ul');
articleList.classList.add('article-list');
let activeArticleElement = null;
column.articles.forEach(article => {
const articleItem = document.createElement('li');
const articleLink = document.createElement('a');
articleLink.href = article.url;
articleLink.textContent = article.title;
if (article.url.split('/').pop().split('?')[0] === currentUrl.split('/').pop().split('?')[0]) {
articleItem.classList.add('column-active');
activeArticleElement = articleItem;
}
articleItem.appendChild(articleLink);
articleList.appendChild(articleItem);
});
menu.appendChild(articleList);
// 滚动到当前文章
if (activeArticleElement) {
// 等待 DOM 更新完成后再滚动
setTimeout(() => {
activeArticleElement.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}, 100);
}
}
/**
* 初始化侧边栏
*/
async function initSidebar() {
try {
// 获取专栏信息
const columnInfo = await getColumnInfo();
const article = document.querySelector('.blog-content-box');
const headers = article?.querySelectorAll('h1, h2, h3, h4, h5, h6');
// 如果既没有专栏信息也没有文章目录,则不添加侧边栏
if ((!columnInfo || columnInfo.length === 0) && (!headers || headers.length === 0)) {
return;
}
const sidebar = document.createElement('div');
sidebar.id = 'custom-sidebar';
sidebar.classList.add('column-menu-sidebar');
if (columnInfo && columnInfo.length > 0) {
// 有专栏信息时显示专栏目录
const menu = buildMenu(columnInfo);
addMenuToSidebar(menu, false, true);
} else {
// 没有专栏信息但有文章目录时直接显示文章目录
addMenuToSidebar(null, true, false);
}
} catch (error) {
console.error('初始化侧边栏失败:', error);
}
}
/**
* 添加侧边栏到页面
* @param {HTMLElement} menu - 菜单元素
* @param {boolean} showTocDirectly - 是否直接显示文章目录
* @param {boolean} hasColumnMenu - 是否有专栏目录
*/
function addMenuToSidebar(menu, showTocDirectly = false, hasColumnMenu = true) {
const sidebar = document.createElement('div');
sidebar.id = 'custom-sidebar';
sidebar.classList.add('column-menu-sidebar');
// 添加标题栏
const titleBar = document.createElement('div');
titleBar.classList.add('sidebar-title');
// 添加标题文本容器
const titleContent = document.createElement('div');
titleContent.classList.add('title-content');
titleContent.textContent = showTocDirectly ? '文章目录' : '专栏文章';
// 添加按钮容器
const buttonContainer = document.createElement('div');
buttonContainer.classList.add('title-buttons');
// 添加目录切换按钮
if (hasColumnMenu) {
const toggleTocBtn = document.createElement('button');
toggleTocBtn.classList.add('sidebar-btn', 'toggle-toc-btn');
toggleTocBtn.innerHTML = showTocDirectly ? '📚' : '📑';
toggleTocBtn.title = '切换文章目录';
toggleTocBtn.onclick = () => toggleTocMode(showTocDirectly);
buttonContainer.appendChild(toggleTocBtn);
}
// 添加定位按钮(仅在专栏模式下显示)
if (!showTocDirectly && hasColumnMenu) {
const locateBtn = document.createElement('button');
locateBtn.classList.add('sidebar-btn', 'locate-btn');
locateBtn.innerHTML = '🔍';
locateBtn.title = '定位当前文章';
locateBtn.onclick = () => {
const activeArticle = sidebar.querySelector('.column-active');
if (activeArticle) {
activeArticle.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
};
buttonContainer.appendChild(locateBtn);
}
// 添加配置按钮
const configBtn = document.createElement('button');
configBtn.classList.add('sidebar-btn', 'config-btn');
configBtn.innerHTML = '⚙';
configBtn.title = '设置';
configBtn.onclick = showConfig;
buttonContainer.appendChild(configBtn);
// 添加收起按钮
const collapseBtn = document.createElement('button');
collapseBtn.classList.add('sidebar-btn', 'collapse-btn');
collapseBtn.innerHTML = '×';
collapseBtn.title = '收起侧边栏';
collapseBtn.onclick = () => toggleSidebar(false);
buttonContainer.appendChild(collapseBtn);
// 组装标题栏
titleBar.appendChild(titleContent);
titleBar.appendChild(buttonContainer);
sidebar.appendChild(titleBar);
// 添加返回顶部按钮
const backToTopBtn = document.createElement('button');
backToTopBtn.classList.add('back-to-top');
backToTopBtn.title = '返回顶部';
// 监听滚动事件
sidebar.addEventListener('scroll', () => {
if (sidebar.scrollTop > 300) {
backToTopBtn.style.display = 'flex';
} else {
backToTopBtn.style.display = 'none';
}
});
backToTopBtn.onclick = () => {
sidebar.scrollTo({
top: 0,
behavior: 'smooth'
});
};
sidebar.appendChild(backToTopBtn);
if (menu && !showTocDirectly) {
sidebar.appendChild(menu);
}
// 插入侧边栏到页面
const blogContentBox = document.querySelector('.blog-content-box');
if (blogContentBox) {
blogContentBox.insertAdjacentElement('beforeBegin', sidebar);
} else {
document.body.insertBefore(sidebar, document.body.firstChild);
}
adjustMainContentStyle(true);
// 如果需要直接显示文章目录
if (showTocDirectly) {
generateToc(sidebar);
}
}
/**
* 切换侧边栏的显示状态
* @param {boolean} show 是否显示
*/
function toggleSidebar(show) {
const sidebar = document.querySelector('#custom-sidebar');
let expandBtn = document.querySelector('#sidebar-expand-btn');
if (show) {
sidebar.style.transform = 'translateX(0)';
if (expandBtn) {
expandBtn.style.display = 'none';
}
adjustMainContentStyle(true);
} else {
sidebar.style.transform = 'translateX(-250px)';
// 如果展开按钮不存在,则创建
if (!expandBtn) {
expandBtn = document.createElement('div');
expandBtn.id = 'sidebar-expand-btn';
expandBtn.onclick = () => toggleSidebar(true);
document.body.appendChild(expandBtn);
}
expandBtn.style.display = 'block';
adjustMainContentStyle(false);
}
}
/**
* 切换目录模式
* @param {boolean} [isInTocMode=false] - 当前是否在目录模式
*/
function toggleTocMode(isInTocMode = false) {
const sidebar = document.querySelector('#custom-sidebar');
const menu = sidebar.querySelector('.column-menu');
const existingToc = sidebar.querySelector('.article-toc');
const titleContent = sidebar.querySelector('.title-content');
const toggleBtn = sidebar.querySelector('.toggle-toc-btn');
if (existingToc) {
// 切换回专栏模式
existingToc.remove(); // 移除而不是隐藏
if (menu) menu.style.display = 'block';
titleContent.textContent = '专栏文章';
toggleBtn.innerHTML = '📑';
} else {
// 切换到目录模式
if (menu) menu.style.display = 'none';
titleContent.textContent = '文章目录';
toggleBtn.innerHTML = '📚';
generateToc(sidebar);
}
}
/**
* 显示配置面板
*/
function showConfig() {
const configPanel = document.createElement('div');
configPanel.classList.add('config-panel');
const cleanParamsOption = document.createElement('label');
cleanParamsOption.innerHTML = `
<input type="checkbox" ${CONFIG.cleanParams ? 'checked' : ''}>
去除链接中的分析参数
`;
cleanParamsOption.querySelector('input').onchange = (e) => {
CONFIG.cleanParams = e.target.checked;
GM_setValue('cleanParams', CONFIG.cleanParams);
};
configPanel.appendChild(cleanParamsOption);
// 添加关闭按钮
const closeBtn = document.createElement('button');
closeBtn.textContent = '关闭';
closeBtn.onclick = () => {
configPanel.remove();
};
configPanel.appendChild(closeBtn);
document.body.appendChild(configPanel);
}
/**
* 去除链接中的分析参数
*/
function cleanAnalyticsParams() {
if (!CONFIG.cleanParams) return ;
const paramsToRemove = ['spm', 'utm_source', 'utm_medium', 'utm_campaign', 'depth_1-utm_source', 'depth_1-utm_medium', 'depth_1-utm_campaign'];
const url = new URL(window.location.href);
let changed = false;
paramsToRemove.forEach(param => {
if (url.searchParams.has(param)) {
url.searchParams.delete(param);
changed = true;
}
});
if (changed) {
window.history.replaceState({}, '', url.toString());
}
}
/**
* 生成文章目录
* @param {HTMLElement} sidebar - 侧边栏元素
*/
function generateToc(sidebar) {
const article = document.querySelector('.blog-content-box');
if (!article) return;
const toc = document.createElement('div');
toc.classList.add('article-toc');
// 获取所有标题
const headers = article.querySelectorAll('h1, h2, h3, h4, h5, h6');
const tocList = document.createElement('ul');
tocList.classList.add('toc-list');
// 创建目录树结构
const headerTree = buildHeaderTree(headers);
renderHeaderTree(headerTree, tocList);
toc.appendChild(tocList);
sidebar.appendChild(toc);
// 添加目录滚动监听
addTocScrollSpy(headers, tocList);
}
/**
* 构建标题树结构
* @param {NodeList} headers - 标题元素列表
* @returns {Array} 标题树结构
*/
function buildHeaderTree(headers) {
const tree = [];
const stack = [{ level: 0, children: tree }];
headers.forEach((header, index) => {
const level = parseInt(header.tagName.charAt(1));
const node = {
id: header.id || `toc-heading-${index}`,
title: header.textContent,
level,
children: []
};
if (!header.id) {
header.id = node.id;
}
while (stack[stack.length - 1].level >= level) {
stack.pop();
}
stack[stack.length - 1].children.push(node);
stack.push({ level, children: node.children });
});
return tree;
}
/**
* 渲染标题树
* @param {Array} tree - 标题树结构
* @param {HTMLElement} parent - 父容器元素
*/
function renderHeaderTree(tree, parent) {
tree.forEach(node => {
const item = document.createElement('li');
item.classList.add(`toc-level-${node.level}`);
const titleContainer = document.createElement('div');
titleContainer.classList.add('toc-title-container');
// 只有当有子节点时才添加展开/折叠按钮
if (node.children.length > 0) {
const toggleBtn = document.createElement('span');
toggleBtn.classList.add('toc-toggle');
toggleBtn.innerHTML = '▼';
toggleBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
const subList = item.querySelector('ul');
if (subList) {
const isExpanded = subList.style.display !== 'none';
subList.style.display = isExpanded ? 'none' : 'block';
toggleBtn.innerHTML = isExpanded ? '▶' : '▼';
}
};
titleContainer.appendChild(toggleBtn);
} else {
// 添加一个空的占位符,保持对齐
const spacer = document.createElement('span');
spacer.classList.add('toc-toggle-spacer');
titleContainer.appendChild(spacer);
}
const link = document.createElement('a');
link.href = `#${node.id}`;
link.textContent = node.title;
link.onclick = (e) => {
e.preventDefault();
document.getElementById(node.id).scrollIntoView({ behavior: 'smooth' });
};
titleContainer.appendChild(link);
item.appendChild(titleContainer);
if (node.children.length > 0) {
const subList = document.createElement('ul');
renderHeaderTree(node.children, subList);
item.appendChild(subList);
}
parent.appendChild(item);
});
}
/**
* 添加目录滚动监听
* @param {HTMLElement} headers
* @param {HTMLElement} tocList
*/
function addTocScrollSpy(headers, tocList) {
const tocLinks = tocList.querySelectorAll('a');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
const id = entry.target.id;
const tocLink = tocList.querySelector(`a[href="#${id}"]`);
if (entry.isIntersecting) {
tocLinks.forEach(link => link.classList.remove('toc-active'));
tocLink?.classList.add('toc-active');
}
});
}, {
rootMargin: '-20% 0px -80% 0px'
});
headers.forEach(header => observer.observe(header));
}
/**
* 调整主内容区域的样式
* @param {boolean} isExpanded 是否展开
*/
function adjustMainContentStyle(isExpanded) {
const mainContent = document.querySelector('.blog-content-box');
if (mainContent) {
const margin = isExpanded ? '250px' : '0';
mainContent.style.marginLeft = margin;
mainContent.style.marginRight = '0';
// 调整顶部工具栏
const topToolbarBox = document.querySelector('#toolbarBox');
if (topToolbarBox) {
topToolbarBox.style.marginLeft = margin;
}
// 调整底部工具栏
const bottomToolbox = document.querySelector('#toolBarBox');
if (bottomToolbox) {
bottomToolbox.style.marginLeft = margin;
}
// 调整评论区
const footer = document.querySelector('#pcCommentBox');
if (footer) {
footer.style.marginLeft = margin;
}
}
}
/**
* 添加点击事件到菜单
* @param {Object} menu 菜单对象
*/
function addClickEventToMenu(menu) {
const articleLinks = menu.querySelectorAll('.article-list a');
articleLinks.forEach(link => {
link.addEventListener('click', event => {
event.preventDefault();
const targetUrl = link.getAttribute('href');
// 直接在当前页面重新加载
window.location.href = targetUrl;
});
});
}
// 更新样式
const customStyle = `
/* 侧边栏基础样式 */
#custom-sidebar {
all: unset;
position: fixed;
left: 0;
top: 0;
width: 250px;
height: 100vh;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
z-index: 999;
overflow-x: auto;
overflow-y: auto;
padding: 0;
border-right: 1px solid #eee;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial;
transition: transform 0.3s ease;
}
/* 标题栏样式组 */
.sidebar-title {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 15px;
font-size: 16px;
font-weight: bold;
border-bottom: 1px solid #eee;
background-color: #f8f9fa;
}
.title-buttons {
display: flex;
gap: 8px;
align-items: center;
}
.title-content {
flex: 1;
}
/* 按钮通用样式 */
.sidebar-btn {
background: none;
border: none;
color: #666;
font-size: 16px;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
}
.sidebar-btn:hover {
background-color: rgba(0, 0, 0, 0.05);
color: #1890ff;
}
.sidebar-btn:active {
transform: scale(0.95);
}
/* 特定按钮样式 */
.locate-btn { font-size: 14px; }
.collapse-btn { font-size: 18px; }
.toggle-toc-btn { font-size: 16px; }
/* 展开按钮样式 */
#sidebar-expand-btn {
position: fixed;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 50px;
background-color: #fff;
box-shadow: 2px 0 4px rgba(0,0,0,0.1);
cursor: pointer;
z-index: 999;
border-radius: 0 4px 4px 0;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s;
text-align: center;
line-height: 50px;
}
#sidebar-expand-btn:hover {
background-color: #f0f0f0;
}
#sidebar-expand-btn::after {
content: '›';
font-size: 20px;
color: #666;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
line-height: 1;
}
/* 专栏选择器样式 */
.column-selector {
width: 90%;
margin: 10px auto;
display: block;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
background-color: #fff;
}
.column-selector:hover {
border-color: #40a9ff;
}
.column-selector:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24,144,255,0.2);
}
/* 文章列表样式 */
.article-list {
list-style: none;
padding: 0;
margin: 0;
background-color: #fff;
}
.article-list li {
padding: 0;
border-bottom: 1px solid #f0f0f0;
background-color: #fff;
}
.article-list li a {
display: block;
padding: 12px 15px;
color: #000;
text-decoration: none;
font-size: 14px;
line-height: 1.5;
transition: all 0.2s;
background-color: #fff;
white-space: normal;
word-break: break-all;
}
.article-list li:hover {
background-color: #f8f9fa;
}
.article-list li:hover a {
color: #1890ff;
}
.column-active {
background-color: #e6f7ff;
}
.column-active a {
color: #1890ff !important;
font-weight: 500;
}
/* 目录树样式 */
.article-toc {
padding: 10px 0;
overflow-y: auto;
height: calc(100vh - 50px);
}
.toc-list, .toc-list ul {
list-style: none;
padding: 0;
margin: 0;
}
.toc-list > li {
padding-left: 0;
}
.toc-list ul > li {
padding-left: 20px;
background-color: #fff !important;
}
.toc-title-container {
display: flex;
align-items: center;
padding: 8px 15px;
cursor: pointer;
transition: all 0.2s;
background-color: #fff !important;
}
.toc-title-container:hover {
background-color: #f8f9fa;
}
.toc-toggle, .toc-toggle-spacer {
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
margin-right: 5px;
}
.toc-toggle:hover {
color: #1890ff;
}
/* 目录链接样式 */
.toc-list a {
flex: 1;
color: #333;
text-decoration: none;
font-size: 14px;
line-height: 1.5;
transition: all 0.2s;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
word-break: break-all;
}
.toc-list a:hover {
color: #1890ff;
}
/* 目录级别样式 */
.toc-level-1 > .toc-title-container { font-size: 16px; font-weight: 500; }
.toc-level-2 > .toc-title-container { font-size: 15px; }
.toc-level-3 > .toc-title-container { font-size: 14px; }
.toc-level-4 > .toc-title-container,
.toc-level-5 > .toc-title-container,
.toc-level-6 > .toc-title-container { font-size: 13px; }
/* 激活状态样式 */
.toc-active {
color: #1890ff !important;
font-weight: 500;
}
.toc-active > .toc-title-container {
background-color: #e6f7ff;
}
/* 滚动条样式 */
#custom-sidebar::-webkit-scrollbar {
width: 6px;
}
#custom-sidebar::-webkit-scrollbar-track {
background: #f1f1f1;
}
#custom-sidebar::-webkit-scrollbar-thumb {
background: #888;
border-radius: 3px;
}
#custom-sidebar::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* 响应式样式 */
@media (max-width: 1200px) {
#custom-sidebar {
width: 200px;
}
.blog-content-box {
margin-left: 200px !important;
}
#sidebar-expand-btn {
width: 16px;
}
.column-selector {
width: 85%;
}
}
/* 过渡动画 */
.blog-content-box,
#toolbarBox,
#toolBarBox,
#pcCommentBox {
transition: margin-left 0.3s ease;
}
/* 返回顶部按钮样式 */
.back-to-top {
position: fixed;
top: 10%;
left: 190px;
width: 40px;
height: 40px;
background-color: #fff;
border: 1px solid #eee;
border-radius: 50%;
cursor: pointer;
display: none;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
z-index: 1000;
}
.back-to-top:hover {
background-color: #f8f9fa;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
transform: translateY(-2px);
}
.back-to-top::after {
content: '↑';
font-size: 20px;
color: #1890ff;
font-weight: bold;
}
.column-menu {
position: relative;
height: 100%;
}
.config-panel {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
z-index: 1000;
}
.config-panel label {
display: block;
margin: 10px 0;
cursor: pointer;
}
.config-panel button {
margin-top: 15px;
padding: 5px 15px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.config-btn {
font-size: 16px;
}
`;
// 如果支持GM_addStyle,则使用它来添加样式
if (typeof GM_addStyle !== 'undefined') {
GM_addStyle(customStyle);
} else {
// 否则,创建一个style元素并添加到head中
const styleEl = document.createElement('style');
styleEl.type = 'text/css';
styleEl.innerHTML = customStyle;
document.head.appendChild(styleEl);
}
})();