添加自定义折叠按钮

在页面上选择元素进行折叠并增加折叠展开按钮

// ==UserScript==
// @name         添加自定义折叠按钮
// @namespace    http://tampermonkey.net/
// @version      20250816.1
// @description  在页面上选择元素进行折叠并增加折叠展开按钮
// @author       atakhalo
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @license MIT
// ==/UserScript==

(function () {
	'use strict';

	let target = null;
	let moveSelect = null;

	// 添加样式
	const style = document.createElement('style');
	style.textContent = `
		.fold-highlight {
			outline: 3px dashed #ff6600 !important;
			cursor: pointer !important;
			position: relative;
			z-index: 9999;
		}

		.fold-highlight::after {
			content: "点击折叠(alt +-*/进行切换父子兄弟)";
			position: absolute;
			top: -25px;
			left: 0;
			background: #ff6600;
			color: white;
			padding: 2px 6px;
			font-size: 12px;
			border-radius: 4px;
			white-space: nowrap;
		}

		.fold-expand-marker {
			margin-top: 12px;
			height: 26px;
			margin-bottom: 20px;
			// color: white;
			// width: 26px;
			display: flex;
			font-size: 16px;
			cursor: pointer;
			box-shadow: 0 2px 6px rgba(0,0,0,0.2);
			// z-index: 10000;
			transition: all 0.2s;
		}

		// .fold-expand-marker:hover {
		//     // transform: scale(1.1);
		//     // box-shadow: 0 4px 10px rgba(0,0,0,0.3);
		// }

		// .fold-expand-marker::after {
		//     content: "展开";
		//     position: absolute;
		//     top: -25px;
		//     right: -5px;
		//     background: #4a6cf7;
		//     color: white;
		//     padding: 2px 6px;
		//     font-size: 12px;
		//     border-radius: 4px;
		//     white-space: nowrap;
		//     opacity: 0;
		//     transition: opacity 0.2s;
		// }

		// .fold-expand-marker:hover::after {
		//     opacity: 1;
		// }

		#foldCounter {
			position: fixed;
			top: 80px;
			left: 25px;
			background: rgba(255,255,255,0.9);
			border: 1px solid #4a6cf7;
			padding: 5px 10px;
			border-radius: 20px;
			font-size: 12px;
			z-index: 9999;
			box-shadow: 0 2px 8px rgba(0,0,0,0.1);
			display: none;
		}

		#foldStatus {
			position: fixed;
			bottom: 20px;
			left: 50%;
			transform: translateX(-50%);
			background: rgba(0,0,0,0.7);
			color: white;
			padding: 8px 16px;
			border-radius: 4px;
			font-size: 14px;
			z-index: 10000;
			display: none;
		}
	`;
	document.head.appendChild(style);

	// 创建状态提示
	const statusMsg = document.createElement('div');
	statusMsg.id = 'foldStatus';
	document.body.appendChild(statusMsg);

	// 显示状态消息
	function showStatus(message, duration = 2000) {
		statusMsg.textContent = message;
		statusMsg.style.display = 'block';
		setTimeout(() => {
			statusMsg.style.display = 'none';
		}, duration);
	}

	GM_registerMenuCommand('选择折叠', toSelect)

	GM_registerMenuCommand('取消选择', stopSelect)
	GM_registerMenuCommand('全部展开', expandAll)
	GM_registerMenuCommand('全部清除', delAll)

	function toSelect() {
		moveSelect = true;
		showStatus('选择模式已激活 - 点击页面元素进行折叠', 3000);
		document.body.addEventListener('mousemove', highlightElement);
		document.body.addEventListener('click', selectElement);
		document.addEventListener('keydown', handleKey);
		// document.addEventListener('wheel', handleWheel);
		document.addEventListener('contextmenu', handleRightClick);
	}

	function stopMoveSelect() {
		moveSelect = false;
		document.body.removeEventListener('mousemove', highlightElement);
	}

	function stopSelect() {
		showStatus('选择模式已取消', 1500);
		endSelect();
	}

	function endSelect() {
		removeHighlights();
		stopMoveSelect();
		target = null;
		document.removeEventListener('keydown', handleKey);
		// document.removeEventListener('wheel', handleWheel);
		document.body.removeEventListener('click', selectElement);
		document.removeEventListener('contextmenu', handleRightClick);
	}

	// 选择元素进行折叠
	function selectElement(e) {
		if (checkFold(target)) // 已经有折叠按钮了
		{

		}
		else {
			foldElement(target);
		}
		endSelect();
		stopE(e);
		showStatus('成功折叠', 1500);
	}

	function checkFold(e) {
		const p = e.previousSibling;
		if (p == null)
			return;
		if (p && p.classList && p.classList.contains('fold-expand-marker')) {
			if (p.isFold === false) {
				p.click();
			}
			return true;
		}
		return false;
	}

	// 递归获取元素的前两个汉字
	function getFirstTwoChineseChars(element) {
		if (!element) return '';
		let text = '';
		for (let node of element.childNodes) {
			if (node.classList?.contains('fold-expand-marker')) {
				continue;
			}
			if (node.nodeType === Node.TEXT_NODE) {
				text += node.textContent.trim();
				if (text.length >= 4) break;
			} else if (node.nodeType === Node.ELEMENT_NODE) {
				text += getFirstTwoChineseChars(node);
				if (text.length >= 4) break;
			}
		}
		return text.slice(0, 4);
	}

	// 折叠元素
	function foldElement(el) {
		// 隐藏元素
		el.style.display = 'none';

		// 创建展开标记
		const expandMarker = document.createElement('div');
		expandMarker.className = 'fold-expand-marker';
		expandMarker.title = '点击展开';
		expandMarker.isFold = true;
		expandMarker.showText = getFirstTwoChineseChars(el);
		expandMarker.textContent = expandMarker.showText + '...展开+';

		// 悬浮显示被遮挡元素内容
		expandMarker.addEventListener('mouseenter', () => {
			if (expandMarker.isFold === false) // 不是折叠就不显示了
			{
				return;
			}
			const preview = document.createElement('div');
			preview.className = 'fold-preview';
			preview.style.position = 'absolute';
			preview.style.zIndex = 9999;
			preview.style.background = '#fff';
			preview.style.border = '1px solid #ccc';
			preview.style.padding = '4px 8px';
			preview.style.boxShadow = '0 2px 6px rgba(0,0,0,0.2)';
			preview.textContent = el.textContent.trim().slice(0, 500);
			document.body.appendChild(preview);

			const rect = expandMarker.getBoundingClientRect();
			preview.style.left = `${rect.left + window.scrollX}px`;
			preview.style.top = `${rect.top + window.scrollY - preview.offsetHeight - 10}px`
			// preview.style.bottom = rect.top + 'px';

			expandMarker._preview = preview;
		});

		expandMarker.addEventListener('mouseleave', () => {
			if (expandMarker._preview) {
				document.body.removeChild(expandMarker._preview);
				expandMarker._preview = null;
			}
		});

		// 点击展开
		expandMarker.addEventListener('click', (e) => {
			if (el.style.display !== 'none') {
				expandMarker.isFold = true
				el.style.display = 'none';
				expandMarker.textContent = expandMarker.showText + '...展开+';
			} else {
				expandMarker.isFold = false
				el.style.display = '';
				expandMarker.textContent = expandMarker.showText + '...折叠-';
			}
			stopE(e);
		});

		const parent = el.parentNode;
		parent.insertBefore(expandMarker, el);
	}

	function expandAll() {
		const button = document.querySelectorAll(".fold-expand-marker");
		button.forEach(element => {
			if (element.isFold === true)
				element.click();
		});
	}

	function delAll() {
		const button = document.querySelectorAll(".fold-expand-marker");
		button.forEach(element => {
			if (element.isFold === true)
				element.click();
			element.remove();
		});
	}

	// 移除高亮
	function removeHighlights() {
		document.querySelectorAll('.fold-highlight').forEach(el => {
			el.classList.remove('fold-highlight');
		});
	}

	// 高亮元素
	function highlightElement(e) {
		highlightTarget(e.target);
	}

	function highlightTarget(newTarget) {
		target = newTarget;
		console.log("target" + target)
		// 排除不需要折叠的元素
		if (!target ||
			target === document.documentElement ||
			target === document.body ||
			target.classList.contains('fold-expand-marker')) {
			removeHighlights();
			return;
		}

		removeHighlights();
		target.classList.add('fold-highlight');
	}


	function handleKey(e) {
		if (e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
			if (moveSelect) {
				stopMoveSelect();
			}
			if (e.key === '+' || e.key === '=') {
				var newTarget = target.parentNode;
				TrySetTarget(newTarget, true); // 按理说不会折叠按钮不会是父节点,保底一下
				stopE(e);
			}
			else if (e.key === '-' || e.key === '_') {
				var newTarget = target.firstElementChild;
				TrySetTarget(newTarget, true);
				stopE(e);
			}
			else if (e.key === '*' || e.key === ']') {
				var newTarget = target.nextElementSibling;
				TrySetTarget(newTarget, true);
				stopE(e);
			}
			else if (e.key === '/' || e.key === '[') {
				var newTarget = target.previousElementSibling;
				TrySetTarget(newTarget, false);
				stopE(e);
			}
		}
	}

	// toNext 表示如果当前是折叠按钮,是否向下一个还是向上一个
	function IsFoldButton(newTarget, toNext) {
		if (newTarget.classList.contains('fold-expand-marker')) {
			if (toNext) {
				return newTarget.nextElementSibling;
			}
			else {
				return newTarget.previousElementSibling;
			}
		}
		return newTarget;
	}

	function TrySetTarget(newTarget, toNext) {
		newTarget = IsFoldButton(newTarget)
		if (newTarget != null) {
			highlightTarget(newTarget, toNext);
		}
		else {
		}
	}

	function handleRightClick(e) {
		stopSelect();
		stopE(e);
	}

	function stopE(e) {
		e.preventDefault();
		e.stopImmediatePropagation();
		e.stopPropagation();
	}
})();