Typecho主题文章目录插件OwODirectoryPlugin

插件开发及发布
回复
DevilChen
帖子: 5
注册时间: 2024年 10月 2日 00:13

Typecho主题文章目录插件OwODirectoryPlugin

帖子 DevilChen »

插件名称:OwODirectoryPlugin

更新!看有回复说应用到Joe主题是目录内容为空白,已解决!!!

这是一款文章目录的插件,Typecho的 默认主题,jasmine主题和Joe主题均适用,其他主题没试过。。。
针对默认主题,jasmine主题,对于Joe主题有个修改 往下读可见
用法:将文件解压放在/usr/plugins目录下,后台点击启用 在你的主题文件中的post.php中添加以下代码:

代码: 全选

 <!-- 目录按钮和容器 -->
<div class="directory-container">
    <div class="directory-toggle">
        <span class="iconify" id="toggle-icon" data-icon="lets-icons:expand-left-double-light" data-inline="false"></span>
    </div>
    <div class="article-directory">
        <!-- 目录将由 JavaScript 动态生成 -->
    </div>
</div>
演示站:https://www.chendk.info

可以前往:https://github.com/devilchendk/OwODirectoryPlugin 下载

Jasmine主题效果图:
效果图
效果图
CatalogShow.gif (1.2 MiB) 查看 1824 次


针对Joe主题

在post.php中插入:

代码: 全选

        <!-- 目录按钮和容器 -->
        <div class="directory-container">
            <div class="directory-toggle">
                <span class="iconify" id="toggle-icon" data-icon="lets-icons:expand-left-double-light" data-inline="false"></span>
            </div>
            <div class="article-directory">
                <!-- 目录将由 JavaScript 动态生成 -->
            </div>
        </div>
例如:
Joe主题下插入代码示例
Joe主题下插入代码示例
Snipaste_2024-10-09_14-55-26.png (91.96 KiB) 查看 1860 次
更改插件的script.js代码或直接用以下代码替换(如果是github上下载的,只需要进入插件中的script.js中将该文件中下方注释掉的代码恢复,注释掉上方代码即可):

代码: 全选

document.addEventListener('DOMContentLoaded', function () {
    const directoryToggle = document.querySelector('.directory-toggle');
    const directoryContainer = document.querySelector('.directory-container');
    const toggleIcon = document.querySelector('#toggle-icon');
    const articleDirectory = document.querySelector('.article-directory');
    const contentSelector = '.joe_detail .joe_detail__article';  // 更加精确地选择文章正文部分
    let isExpanded = false;

    // 初始状态,鼠标悬停提示为 "点击展开目录"
    directoryToggle.setAttribute('title', '点击展开目录');

    // 目录展开/收起动画
    directoryToggle.addEventListener('click', function () {
        isExpanded = !isExpanded;
        if (isExpanded) {
            directoryContainer.classList.add('directory-expanded');
            toggleIcon.setAttribute('data-icon', 'lets-icons:collapse-right-double-light');
            directoryToggle.classList.add('moved-left');
            directoryToggle.setAttribute('title', '点击收起目录');
        } else {
            directoryContainer.classList.remove('directory-expanded');
            toggleIcon.setAttribute('data-icon', 'lets-icons:expand-left-double-light');
            directoryToggle.classList.remove('moved-left');
            directoryToggle.setAttribute('title', '点击展开目录');
        }
    });

    // 查找文章内容容器,只查找 .joe_detail__article 内的标题
    let content = document.querySelector(contentSelector);
    if (!content) {
        console.error("未能找到文章内容容器。");
        return;
    }

    // 生成目录
    const headings = content.querySelectorAll('h1, h2, h3, h4, h5, h6');
    if (headings.length === 0) {
        console.error("没有找到标题");
        return;
    }

    let tocHtml = '<ul>';
    let minLevel = 7;

    // 计算最小的标题层级
    headings.forEach((heading) => {
        const level = parseInt(heading.tagName.substring(1));
        if (level < minLevel) minLevel = level;
    });

    // 生成目录结构
    headings.forEach((heading, index) => {
        const level = parseInt(heading.tagName.substring(1));
        const indent = (level - minLevel) * 20;
        const anchor = `heading-${index}`;
        heading.id = anchor;
        tocHtml += `<li style="margin-left: ${indent}px;"><a href="#${anchor}" data-anchor="${anchor}">${heading.textContent}</a></li>`;
    });

    tocHtml += '</ul>';
    articleDirectory.innerHTML = tocHtml;

    // 点击目录跳转
    const tocLinks = document.querySelectorAll('.article-directory a');
    tocLinks.forEach(link => {
        link.addEventListener('click', function (event) {
            event.preventDefault();
            const targetId = event.target.getAttribute('data-anchor');
            const targetElement = document.getElementById(targetId);
            if (targetElement) {
                window.scrollTo({
                    top: targetElement.offsetTop - 10,
                    behavior: 'smooth'
                });
                history.pushState(null, null, `#${targetId}`);
            }
        });
    });

    // 监听滚动并高亮当前标题
    window.addEventListener('scroll', function () {
        let currentHeadingId = null;
        const offset = window.innerHeight * 0.25;

        headings.forEach(function (heading) {
            const rect = heading.getBoundingClientRect();
            if (rect.top < offset && rect.bottom > 0) {
                currentHeadingId = heading.id;
            }
        });

        tocLinks.forEach(function (link) {
            if (link.getAttribute('data-anchor') === currentHeadingId) {
                link.classList.add('active');  // 高亮当前项
            } else {
                link.classList.remove('active');  // 移除高亮
            }
        });
    });
});
Joe主题下的效果图:
Joe主题下的效果图
Joe主题下的效果图
Snipaste_2024-10-09_14-53-50.png (176.47 KiB) 查看 1860 次
上次由 DevilChen 在 2024年 10月 15日 21:13,总共编辑 13 次。
bubuzsd
帖子: 1
注册时间: 2024年 10月 9日 15:32

Re: Typecho主题文章目录插件OwODirectoryPlugin

帖子 bubuzsd »

Joe主题,打开目录按钮是空的,没有获取到目录内容
DevilChen
帖子: 5
注册时间: 2024年 10月 2日 00:13

Re: Typecho主题文章目录插件OwODirectoryPlugin

帖子 DevilChen »

bubuzsd 写了: 2024年 10月 9日 15:42 Joe主题,打开目录按钮是空的,没有获取到目录内容
已更新插件对应于Joe主题的修改,可能还在审核,我在回复中说一下:
插入示例:
Snipaste_2024-10-09_13-14-49.png
Snipaste_2024-10-09_13-14-49.png (108.78 KiB) 查看 1750 次
替换插件中script.js的代码为:

代码: 全选

document.addEventListener('DOMContentLoaded', function () {
    const directoryToggle = document.querySelector('.directory-toggle');
    const directoryContainer = document.querySelector('.directory-container');
    const toggleIcon = document.querySelector('#toggle-icon');
    const articleDirectory = document.querySelector('.article-directory');
    const contentSelectors = ['.joe_detail', '.post-content', '.entry-content', '.article-content', '.markdown-body', '.content-body'];
    let isExpanded = false;

    // 初始状态,鼠标悬停提示为 "点击展开目录"
    directoryToggle.setAttribute('title', '点击展开目录');

    // 目录展开/收起动画
    directoryToggle.addEventListener('click', function () {
        isExpanded = !isExpanded;
        if (isExpanded) {
            directoryContainer.classList.add('directory-expanded');
            toggleIcon.setAttribute('data-icon', 'lets-icons:collapse-right-double-light');
            directoryToggle.classList.add('moved-left');
            directoryToggle.setAttribute('title', '点击收起目录');
        } else {
            directoryContainer.classList.remove('directory-expanded');
            toggleIcon.setAttribute('data-icon', 'lets-icons:expand-left-double-light');
            directoryToggle.classList.remove('moved-left');
            directoryToggle.setAttribute('title', '点击展开目录');
        }
    });

    // 查找文章内容容器
    let content = null;
    for (let i = 0; i < contentSelectors.length; i++) {
        content = document.querySelector(contentSelectors[i]);
        if (content) break;
    }

    if (!content) {
        console.error("未能找到文章内容容器。");
        return;
    }

    // 生成目录
    const headings = content.querySelectorAll('h1, h2, h3, h4, h5, h6');
    if (headings.length === 0) {
        console.error("没有找到标题");
        return;
    }

    let tocHtml = '<ul>';
    let minLevel = 7;

    // 计算最小的标题层级
    headings.forEach((heading) => {
        const level = parseInt(heading.tagName.substring(1));
        if (level < minLevel) minLevel = level;
    });

    // 生成目录结构
    headings.forEach((heading, index) => {
        const level = parseInt(heading.tagName.substring(1));
        const indent = (level - minLevel) * 20;
        const anchor = `heading-${index}`;
        heading.id = anchor;
        tocHtml += `<li style="margin-left: ${indent}px;"><a href="#${anchor}" data-anchor="${anchor}">${heading.textContent}</a></li>`;
    });

    tocHtml += '</ul>';
    articleDirectory.innerHTML = tocHtml;

    // 点击目录跳转
    const tocLinks = document.querySelectorAll('.article-directory a');
    tocLinks.forEach(link => {
        link.addEventListener('click', function (event) {
            event.preventDefault();
            const targetId = event.target.getAttribute('data-anchor');
            const targetElement = document.getElementById(targetId);
            if (targetElement) {
                window.scrollTo({
                    top: targetElement.offsetTop - 10,
                    behavior: 'smooth'
                });
                history.pushState(null, null, `#${targetId}`);
            }
        });
    });

    // 监听滚动并高亮当前标题
    window.addEventListener('scroll', function () {
        let currentHeadingId = null;
        const offset = window.innerHeight * 0.25;

        headings.forEach(function (heading) {
            const rect = heading.getBoundingClientRect();
            if (rect.top < offset && rect.bottom > 0) {
                currentHeadingId = heading.id;
            }
        });

        tocLinks.forEach(function (link) {
            if (link.getAttribute('data-anchor') === currentHeadingId) {
                link.classList.add('active');  // 高亮当前项
            } else {
                link.classList.remove('active');  // 移除高亮
            }
        });
    });
});
效果图:
Snipaste_2024-10-09_13-23-26.png
Snipaste_2024-10-09_13-23-26.png (249.67 KiB) 查看 1750 次
DevilChen
帖子: 5
注册时间: 2024年 10月 2日 00:13

Re: Typecho主题文章目录插件OwODirectoryPlugin

帖子 DevilChen »

bubuzsd 写了: 2024年 10月 9日 15:42 Joe主题,打开目录按钮是空的,没有获取到目录内容
这这这,有点小错误,这样解决:
插入post.php:
Snipaste_2024-10-09_14-55-26.png
Snipaste_2024-10-09_14-55-26.png (91.96 KiB) 查看 1721 次
更换插件中script.js文件代码(前面回复的定位不精确,可能会获得其他文章的主标题,更换下面的代码就好):
document.addEventListener('DOMContentLoaded', function () {
const directoryToggle = document.querySelector('.directory-toggle');
const directoryContainer = document.querySelector('.directory-container');
const toggleIcon = document.querySelector('#toggle-icon');
const articleDirectory = document.querySelector('.article-directory');
const contentSelector = '.joe_detail .joe_detail__article'; // 更加精确地选择文章正文部分
let isExpanded = false;

// 初始状态,鼠标悬停提示为 "点击展开目录"
directoryToggle.setAttribute('title', '点击展开目录');

// 目录展开/收起动画
directoryToggle.addEventListener('click', function () {
isExpanded = !isExpanded;
if (isExpanded) {
directoryContainer.classList.add('directory-expanded');
toggleIcon.setAttribute('data-icon', 'lets-icons:collapse-right-double-light');
directoryToggle.classList.add('moved-left');
directoryToggle.setAttribute('title', '点击收起目录');
} else {
directoryContainer.classList.remove('directory-expanded');
toggleIcon.setAttribute('data-icon', 'lets-icons:expand-left-double-light');
directoryToggle.classList.remove('moved-left');
directoryToggle.setAttribute('title', '点击展开目录');
}
});

// 查找文章内容容器,只查找 .joe_detail__article 内的标题
let content = document.querySelector(contentSelector);
if (!content) {
console.error("未能找到文章内容容器。");
return;
}

// 生成目录
const headings = content.querySelectorAll('h1, h2, h3, h4, h5, h6');
if (headings.length === 0) {
console.error("没有找到标题");
return;
}

let tocHtml = '<ul>';
let minLevel = 7;

// 计算最小的标题层级
headings.forEach((heading) => {
const level = parseInt(heading.tagName.substring(1));
if (level < minLevel) minLevel = level;
});

// 生成目录结构
headings.forEach((heading, index) => {
const level = parseInt(heading.tagName.substring(1));
const indent = (level - minLevel) * 20;
const anchor = `heading-${index}`;
heading.id = anchor;
tocHtml += `<li style="margin-left: ${indent}px;"><a href="#${anchor}" data-anchor="${anchor}">${heading.textContent}</a></li>`;
});

tocHtml += '</ul>';
articleDirectory.innerHTML = tocHtml;

// 点击目录跳转
const tocLinks = document.querySelectorAll('.article-directory a');
tocLinks.forEach(link => {
link.addEventListener('click', function (event) {
event.preventDefault();
const targetId = event.target.getAttribute('data-anchor');
const targetElement = document.getElementById(targetId);
if (targetElement) {
window.scrollTo({
top: targetElement.offsetTop - 10,
behavior: 'smooth'
});
history.pushState(null, null, `#${targetId}`);
}
});
});

// 监听滚动并高亮当前标题
window.addEventListener('scroll', function () {
let currentHeadingId = null;
const offset = window.innerHeight * 0.25;

headings.forEach(function (heading) {
const rect = heading.getBoundingClientRect();
if (rect.top < offset && rect.bottom > 0) {
currentHeadingId = heading.id;
}
});

tocLinks.forEach(function (link) {
if (link.getAttribute('data-anchor') === currentHeadingId) {
link.classList.add('active'); // 高亮当前项
} else {
link.classList.remove('active'); // 移除高亮
}
});
});
});
回复