fix: 修复移动端文章目录无法显示的问题
- 移动端目录改为从桌面端目录复制内容,避免 headIndex 插件重复初始化导致的冲突 - 添加移动端目录独立的点击事件处理 - 添加桌面端与移动端目录高亮状态同步机制(使用 MutationObserver) - 恢复 headindex.js 为完整版本(与合并文件中的实现一致)
This commit is contained in:
@@ -2516,15 +2516,53 @@ $(document).on("click" , "#blog_categories .tag" , function(){
|
||||
|
||||
function initMobileCatalog() {
|
||||
if (window.mobileCatalogInitialized) return;
|
||||
if ($("#leftbar_mobile_catalog").length === 0) return;
|
||||
if ($("#post_content").length === 0) return;
|
||||
var $mobileContainer = $("#leftbar_mobile_catalog");
|
||||
var $postContent = $("#post_content");
|
||||
if ($mobileContainer.length === 0) return;
|
||||
if ($postContent.length === 0) return;
|
||||
|
||||
// 直接从桌面端目录复制内容,避免重复初始化 headIndex 导致的冲突
|
||||
var $desktopCatalog = $("#leftbar_catalog");
|
||||
if ($desktopCatalog.length > 0 && $desktopCatalog.children().length > 0) {
|
||||
// 复制桌面端目录内容到移动端
|
||||
$mobileContainer.html($desktopCatalog.html());
|
||||
window.mobileCatalogInitialized = true;
|
||||
|
||||
// 绑定移动端目录的点击事件
|
||||
$mobileContainer.off('click.mobileCatalog').on('click.mobileCatalog', '.index-link', function(e) {
|
||||
e.preventDefault();
|
||||
var targetId = $(this).attr('href');
|
||||
if (targetId && targetId.startsWith('#')) {
|
||||
var $target = $(targetId);
|
||||
if ($target.length) {
|
||||
// 更新高亮状态
|
||||
$mobileContainer.find('.index-item').removeClass('current');
|
||||
$(this).closest('.index-item').addClass('current');
|
||||
|
||||
// 滚动到目标位置
|
||||
$('html, body').animate({
|
||||
scrollTop: $target.offset().top - 80
|
||||
}, 500, 'easeOutExpo');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 同步桌面端目录的高亮状态到移动端
|
||||
syncMobileCatalogHighlight();
|
||||
|
||||
setTimeout(scrollMobileCatalogToActive, 150);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果桌面端目录不存在,则独立初始化移动端目录
|
||||
var retryCount = 0;
|
||||
var maxRetries = 30;
|
||||
|
||||
function tryInit() {
|
||||
if (typeof jQuery !== 'undefined' && typeof jQuery.fn.headIndex === 'function') {
|
||||
jQuery(document).headIndex({
|
||||
// 使用一个临时的包装元素来初始化,避免与桌面端冲突
|
||||
var $wrapper = $('<div id="mobile_catalog_wrapper"></div>');
|
||||
$wrapper.headIndex({
|
||||
articleWrapSelector: '#post_content',
|
||||
indexBoxSelector: '#leftbar_mobile_catalog',
|
||||
subItemBoxClass: "index-subItem-box",
|
||||
@@ -2533,7 +2571,6 @@ $(document).on("click" , "#blog_categories .tag" , function(){
|
||||
offset: 80,
|
||||
});
|
||||
window.mobileCatalogInitialized = true;
|
||||
// 初始化后滚动到当前位置
|
||||
setTimeout(scrollMobileCatalogToActive, 150);
|
||||
} else {
|
||||
retryCount++;
|
||||
@@ -2546,6 +2583,46 @@ $(document).on("click" , "#blog_categories .tag" , function(){
|
||||
setTimeout(tryInit, 50);
|
||||
}
|
||||
|
||||
// 同步桌面端目录高亮状态到移动端
|
||||
function syncMobileCatalogHighlight() {
|
||||
var $desktopCatalog = $("#leftbar_catalog");
|
||||
var $mobileContainer = $("#leftbar_mobile_catalog");
|
||||
|
||||
if ($desktopCatalog.length === 0 || $mobileContainer.length === 0) return;
|
||||
|
||||
// 监听桌面端目录的高亮变化
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
||||
var $target = $(mutation.target);
|
||||
if ($target.hasClass('index-item')) {
|
||||
var href = $target.find('> .index-link').attr('href');
|
||||
if (href) {
|
||||
// 同步高亮状态到移动端
|
||||
$mobileContainer.find('.index-item').removeClass('current');
|
||||
$mobileContainer.find('.index-link[href="' + href + '"]').closest('.index-item').addClass('current');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe($desktopCatalog[0], {
|
||||
attributes: true,
|
||||
subtree: true,
|
||||
attributeFilter: ['class']
|
||||
});
|
||||
|
||||
// 初始同步当前高亮状态
|
||||
var $currentItem = $desktopCatalog.find('.index-item.current');
|
||||
if ($currentItem.length > 0) {
|
||||
var href = $currentItem.find('> .index-link').attr('href');
|
||||
if (href) {
|
||||
$mobileContainer.find('.index-link[href="' + href + '"]').closest('.index-item').addClass('current');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重置移动端目录状态(供 PJAX 调用)
|
||||
window.resetMobileCatalog = function() {
|
||||
window.mobileCatalogInitialized = false;
|
||||
|
||||
426
assets/vendor/headindex/headindex.js
vendored
426
assets/vendor/headindex/headindex.js
vendored
@@ -1,126 +1,320 @@
|
||||
/*!
|
||||
* headIndex.js - 文章目录生成插件
|
||||
* 为 Argon 主题提供文章目录功能
|
||||
*
|
||||
* 注意:此文件仅用于后台设置页面预览
|
||||
* 前端页面使用的是 argon_js_merged.js 中的压缩版本
|
||||
*/
|
||||
(function($) {
|
||||
'use strict';
|
||||
;(function($, window) {
|
||||
var headIndex = (function() {
|
||||
function headIndex(element, options) {
|
||||
this.settings = $.extend(true, $.fn.headIndex.default, options || {});
|
||||
this.element = element;
|
||||
this.init();
|
||||
}
|
||||
|
||||
headIndex.prototype = {
|
||||
init: function() {
|
||||
this.articleWrap = $(this.settings.articleWrapSelector);
|
||||
this.headerList = this.articleWrap.find(':header');
|
||||
this.indexBox = $(this.settings.indexBoxSelector);
|
||||
this.indexScrollBox = $(this.settings.indexScrollBoxSelector);
|
||||
this.scrollBody = $(this.settings.scrollSelector);
|
||||
this.scrollWrap = $(this.settings.scrollWrap);
|
||||
this.manual = false;
|
||||
this.mouseHovered = false;
|
||||
|
||||
if (this.indexBox.length === 0 || this.headerList.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.initHeader();
|
||||
this.event();
|
||||
},
|
||||
|
||||
initHeader: function() {
|
||||
for (var i = 0; i < this.headerList.length; i++, this.autoId++) {
|
||||
this.headerList[i].id = this.headerList[i].id || "header-id-" + this.autoId;
|
||||
this.headerList[i].topHeight = this.offsetTop(this.headerList[i]);
|
||||
this.headerList[i].h = Number(this.headerList[i].tagName.charAt(1));
|
||||
}
|
||||
|
||||
this.tempHtml = [];
|
||||
this.buildHtml(this.buildTree());
|
||||
var res = '<ul>' + this.tempHtml.join('') + '</ul>';
|
||||
this.indexBox.html(res);
|
||||
},
|
||||
|
||||
event: function() {
|
||||
var that = this;
|
||||
var manualValTimer = null;
|
||||
|
||||
this.indexBox.on('click.headindex', function(event) {
|
||||
var target = $(event.target);
|
||||
if (target.hasClass(that.settings.linkClass)) {
|
||||
event.preventDefault();
|
||||
var indexItem = target.parent('.' + that.settings.itemClass);
|
||||
that.manual = true;
|
||||
if (manualValTimer) {
|
||||
clearTimeout(manualValTimer);
|
||||
manualValTimer = null;
|
||||
}
|
||||
manualValTimer = setTimeout(function() {
|
||||
that.manual = false;
|
||||
}, 400);
|
||||
that.current(indexItem);
|
||||
that.scrollTo(event.target.getAttribute('href'));
|
||||
}
|
||||
});
|
||||
|
||||
this.indexScrollBox.on('mouseover', function(event) {
|
||||
that.mouseHovered = true;
|
||||
});
|
||||
|
||||
this.indexScrollBox.on('mouseleave', function(event) {
|
||||
that.mouseHovered = false;
|
||||
});
|
||||
|
||||
$(this.scrollWrap).scroll(function() {
|
||||
if (that.manual) return;
|
||||
that.updateCurrent();
|
||||
});
|
||||
|
||||
that.updateCurrent();
|
||||
},
|
||||
|
||||
updateTopHeight: function() {
|
||||
var length = this.headerList.length;
|
||||
var i;
|
||||
if (length === 0) return;
|
||||
|
||||
if (this.headerList[0].topHeight === this.offsetTop(this.headerList[0]) &&
|
||||
this.headerList[length - 1].topHeight === this.offsetTop(this.headerList[length - 1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((this.headerList[0].topHeight - this.offsetTop(this.headerList[0])) ===
|
||||
(this.headerList[length - 1].topHeight - this.offsetTop(this.headerList[length - 1]))) {
|
||||
var hx = this.offsetTop(this.headerList[0]) - this.headerList[0].topHeight;
|
||||
for (i = 0; i < this.headerList.length; i++, this.autoId++) {
|
||||
this.headerList[i].topHeight += hx;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < this.headerList.length; i++, this.autoId++) {
|
||||
this.headerList[i].topHeight = this.offsetTop(this.headerList[i]);
|
||||
}
|
||||
},
|
||||
|
||||
current: function(indexItem) {
|
||||
var subBox, currentClass = 'current';
|
||||
if (indexItem.length === 0 || indexItem.hasClass(currentClass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var otherCurrent = this.indexBox.find('li.' + currentClass);
|
||||
if (otherCurrent.length > 0) {
|
||||
otherCurrent.removeClass(currentClass);
|
||||
}
|
||||
|
||||
this.indexBox.find('ul.open').removeClass('open');
|
||||
subBox = indexItem.children('.' + this.settings.subItemBoxClass);
|
||||
if (subBox.length > 0) {
|
||||
subBox.addClass('open').slideDown();
|
||||
}
|
||||
|
||||
var parentsBox = indexItem.parents('ul.' + this.settings.subItemBoxClass);
|
||||
if (parentsBox.length > 0) {
|
||||
parentsBox.addClass('open').slideDown();
|
||||
}
|
||||
|
||||
subBox = this.indexBox.find('ul.' + this.settings.subItemBoxClass).not('.open');
|
||||
if (subBox.length > 0) {
|
||||
subBox.slideUp();
|
||||
}
|
||||
|
||||
indexItem.addClass(currentClass);
|
||||
|
||||
if (this.mouseHovered) {
|
||||
return;
|
||||
}
|
||||
|
||||
var relativeOffsetTopToWrapper = indexItem.position().top;
|
||||
var indexScrollBoxScrollTop = this.indexScrollBox.scrollTop();
|
||||
var indexScrollBoxHeight = this.indexScrollBox.height();
|
||||
|
||||
if (relativeOffsetTopToWrapper < 20 + indexItem.height()) {
|
||||
var target = indexScrollBoxScrollTop + relativeOffsetTopToWrapper - 20 - indexItem.height();
|
||||
if (target < 30) {
|
||||
target = 0;
|
||||
}
|
||||
this.indexScrollBox.stop().animate({scrollTop: target}, 'normal', 'easeOutExpo');
|
||||
}
|
||||
|
||||
if (relativeOffsetTopToWrapper > indexScrollBoxHeight - 10) {
|
||||
this.indexScrollBox.stop().animate({
|
||||
scrollTop: indexScrollBoxScrollTop + relativeOffsetTopToWrapper - indexScrollBoxHeight + 10 + indexItem.height()
|
||||
}, 'normal', 'easeOutExpo');
|
||||
}
|
||||
},
|
||||
|
||||
buildHtml: function(tree) {
|
||||
if (tree === undefined || tree.length === 0) return;
|
||||
|
||||
for (var i = 0; i < tree.length; i++) {
|
||||
this.tempHtml.push("<li class='" + this.settings.itemClass + "'><a no-pjax class='" + this.settings.linkClass + "' href='#" + tree[i].item.id + "'>" + tree[i].item.innerText + "</a>");
|
||||
if (tree[i].children.length !== 0) {
|
||||
this.tempHtml.push("<ul class='" + this.settings.subItemBoxClass + "'>");
|
||||
this.buildHtml(tree[i].children);
|
||||
this.tempHtml.push("</ul>");
|
||||
}
|
||||
this.tempHtml.push("</li>");
|
||||
}
|
||||
},
|
||||
|
||||
buildTree: function() {
|
||||
var current = null, tempCur, indexTree = [];
|
||||
|
||||
for (var i = 0; i < this.headerList.length; i++) {
|
||||
if (current == null) {
|
||||
current = {
|
||||
item: this.headerList[i],
|
||||
parent: null,
|
||||
children: []
|
||||
};
|
||||
indexTree.push(current);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current.item.h < this.headerList[i].h) {
|
||||
tempCur = {
|
||||
item: this.headerList[i],
|
||||
parent: current,
|
||||
children: []
|
||||
};
|
||||
current.children.push(tempCur);
|
||||
current = tempCur;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current.item.h === this.headerList[i].h) {
|
||||
tempCur = {
|
||||
item: this.headerList[i],
|
||||
parent: current.parent,
|
||||
children: []
|
||||
};
|
||||
((current.parent && current.parent.children) || indexTree).push(tempCur);
|
||||
current = tempCur;
|
||||
continue;
|
||||
}
|
||||
|
||||
while (current != null && current.item.h > this.headerList[i].h) {
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
if (current == null) {
|
||||
current = {
|
||||
item: this.headerList[i],
|
||||
parent: null,
|
||||
children: []
|
||||
};
|
||||
indexTree.push(current);
|
||||
continue;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
|
||||
return indexTree;
|
||||
},
|
||||
|
||||
search: function(start, end, findValue) {
|
||||
if (this.headerList.length === 0) return null;
|
||||
|
||||
if (end - start <= 1) {
|
||||
if (this.headerList[end].topHeight < findValue) {
|
||||
return this.headerList[end];
|
||||
}
|
||||
return this.headerList[start];
|
||||
}
|
||||
|
||||
if (start < end) {
|
||||
var middleIndex = parseInt((start + end) / 2);
|
||||
var middleValue = this.headerList[middleIndex].topHeight;
|
||||
|
||||
if (findValue < middleValue) {
|
||||
end = middleIndex;
|
||||
} else if (findValue > middleValue) {
|
||||
start = middleIndex;
|
||||
} else {
|
||||
return this.headerList[middleIndex];
|
||||
}
|
||||
return this.search(start, end, findValue);
|
||||
}
|
||||
},
|
||||
|
||||
offsetTop: function(elem) {
|
||||
return $(elem).offset().top - this.settings.offset;
|
||||
},
|
||||
|
||||
scrollTo: function(eid) {
|
||||
this.scrollBody.stop().animate({
|
||||
scrollTop: this.offsetTop(document.getElementById(eid.substr(1))) + 8
|
||||
}, 'normal', 'easeOutExpo');
|
||||
},
|
||||
|
||||
updateCurrent: function() {
|
||||
var scrollTop = this.scrollBody.scrollTop();
|
||||
this.updateTopHeight();
|
||||
var find = this.search(0, this.headerList.length - 1, scrollTop);
|
||||
if (!find) {
|
||||
return;
|
||||
}
|
||||
var indexItem = this.indexBox.find('a[href="#' + find.id + '"]').parent('li.' + this.settings.itemClass);
|
||||
this.current(indexItem);
|
||||
},
|
||||
|
||||
clean: function() {
|
||||
if (this.element) {
|
||||
this.element.data("headIndex", null);
|
||||
}
|
||||
},
|
||||
|
||||
ignoreScrollEvent: function(ignore) {
|
||||
if (ignore) {
|
||||
this.manual = true;
|
||||
} else {
|
||||
this.manual = false;
|
||||
this.updateCurrent();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
headIndex.prototype.autoId = 1;
|
||||
|
||||
return headIndex;
|
||||
})();
|
||||
|
||||
$.fn.headIndex = function(options) {
|
||||
var defaults = {
|
||||
articleWrapSelector: '#post_content',
|
||||
indexBoxSelector: '#leftbar_catalog',
|
||||
subItemBoxClass: 'index-subItem-box',
|
||||
scrollOffset: 80,
|
||||
activeClass: 'active'
|
||||
return this.each(function() {
|
||||
var $this = $(this),
|
||||
instance = $this.data("headIndex");
|
||||
instance = new headIndex($this, options);
|
||||
$this.data("headIndex", instance);
|
||||
if ($.type(options) === "string") return instance[options]();
|
||||
});
|
||||
};
|
||||
|
||||
var settings = $.extend({}, defaults, options);
|
||||
var $articleWrap = $(settings.articleWrapSelector);
|
||||
var $indexBox = $(settings.indexBoxSelector);
|
||||
|
||||
if (!$articleWrap.length || !$indexBox.length) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// 查找所有标题元素
|
||||
var $headings = $articleWrap.find('h1, h2, h3, h4, h5, h6');
|
||||
|
||||
if ($headings.length === 0) {
|
||||
$indexBox.html('<div class="no-headings">暂无目录</div>');
|
||||
return this;
|
||||
}
|
||||
|
||||
// 生成目录HTML
|
||||
var indexHtml = '<ul class="catalog-list">';
|
||||
var currentLevel = 0;
|
||||
var indexCounter = 0;
|
||||
|
||||
$headings.each(function(index) {
|
||||
var $heading = $(this);
|
||||
var level = parseInt(this.tagName.substring(1));
|
||||
var text = $heading.text().trim();
|
||||
var id = 'heading-' + index;
|
||||
|
||||
// 为标题添加ID
|
||||
if (!$heading.attr('id')) {
|
||||
$heading.attr('id', id);
|
||||
} else {
|
||||
id = $heading.attr('id');
|
||||
}
|
||||
|
||||
// 处理层级
|
||||
if (level > currentLevel) {
|
||||
for (var i = currentLevel; i < level; i++) {
|
||||
if (i === currentLevel) {
|
||||
indexHtml += '<li><ul class="' + settings.subItemBoxClass + '">';
|
||||
} else {
|
||||
indexHtml += '<li><ul>';
|
||||
}
|
||||
}
|
||||
} else if (level < currentLevel) {
|
||||
for (var j = currentLevel; j > level; j--) {
|
||||
indexHtml += '</ul></li>';
|
||||
}
|
||||
}
|
||||
|
||||
indexHtml += '<li><a href="#' + id + '" class="catalog-link" data-target="' + id + '">' + text + '</a></li>';
|
||||
currentLevel = level;
|
||||
indexCounter++;
|
||||
});
|
||||
|
||||
// 关闭所有未关闭的标签
|
||||
for (var k = 0; k < currentLevel; k++) {
|
||||
indexHtml += '</ul></li>';
|
||||
}
|
||||
indexHtml += '</ul>';
|
||||
|
||||
$indexBox.html(indexHtml);
|
||||
|
||||
// 绑定点击事件
|
||||
$indexBox.on('click', '.catalog-link', function(e) {
|
||||
e.preventDefault();
|
||||
var targetId = $(this).data('target');
|
||||
var $target = $('#' + targetId);
|
||||
|
||||
if ($target.length) {
|
||||
$('html, body').animate({
|
||||
scrollTop: $target.offset().top - settings.scrollOffset
|
||||
}, 500, 'easeOutExpo');
|
||||
|
||||
// 更新活动状态
|
||||
$indexBox.find('.catalog-link').removeClass(settings.activeClass);
|
||||
$(this).addClass(settings.activeClass);
|
||||
}
|
||||
});
|
||||
|
||||
// 滚动时高亮当前章节
|
||||
var throttleTimer = null;
|
||||
$(window).on('scroll', function() {
|
||||
if (throttleTimer) {
|
||||
clearTimeout(throttleTimer);
|
||||
}
|
||||
|
||||
throttleTimer = setTimeout(function() {
|
||||
var scrollTop = $(window).scrollTop();
|
||||
var currentHeading = null;
|
||||
|
||||
$headings.each(function() {
|
||||
var $heading = $(this);
|
||||
var headingTop = $heading.offset().top - settings.scrollOffset - 50;
|
||||
|
||||
if (scrollTop >= headingTop) {
|
||||
currentHeading = $heading.attr('id');
|
||||
}
|
||||
});
|
||||
|
||||
if (currentHeading) {
|
||||
$indexBox.find('.catalog-link').removeClass(settings.activeClass);
|
||||
$indexBox.find('.catalog-link[data-target="' + currentHeading + '"]').addClass(settings.activeClass);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
return this;
|
||||
$.fn.headIndex.default = {
|
||||
articleWrapSelector: ".article-wrap",
|
||||
indexBoxSelector: ".index-box",
|
||||
indexScrollBoxSelector: "#leftbar_part2_inner",
|
||||
scrollSelector: 'body,html',
|
||||
scrollWrap: window,
|
||||
subItemBoxClass: "index-subItem-box",
|
||||
itemClass: "index-item",
|
||||
linkClass: "index-link",
|
||||
offset: 0
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
})(jQuery, window);
|
||||
|
||||
Reference in New Issue
Block a user