// ==UserScript== // @name 任务隐藏 // @namespace https://scriptcat.org/ // @version 1.0.2 // @description 隐藏不需要看见的任务。悬浮球可拖拽、贴边半隐藏;点击展开胶囊与面板;长时间无操作自动收回为球。 // @author CC // @match *://*/* // @grant none // @run-at document-idle // @noframes // ==/UserScript== /* * 使用说明: * 1. 功能: * - 每行“操作”列末尾有 ✕ 按钮,点击即隐藏该任务(按“任务key”记录) * - 悬浮球:显示已隐藏数量;可任意拖动;贴近左/右边缘释放会吸附并半隐藏, * 鼠标悬停自动弹出;位置按域名持久化 * - 单击悬浮球展开为胶囊,再次单击打开/关闭管理面板(恢复/清空/临时显示) * - 约 6 秒无操作自动收回到悬浮球状态 */ (function () { 'use strict'; var ROOT_SEL = '.TaskManageTable'; var STORE_KEY = 'sctaskhide:' + location.host; var POS_KEY = 'sctaskhide:pos:' + location.host; var COL_NAME = 'el-table_1_column_2'; var COL_KEY = 'el-table_1_column_3'; var BALL_SIZE = 44; var SNAP_PX = 80; // 距边缘多少像素内吸附 var DRAG_TH = 6; // 位移超过该值视为拖拽而非点击 var IDLE_MS = 6000; // 无操作多久后收回成球 var hiddenMap = load(); var ghostMode = false; var pendingTimer = null; /* 悬浮球状态 */ var mode = 'ball'; // 'ball' | 'cap' var panelOpen = false; var hovering = false; var dragging = false; var idleTimer = null; var pos = loadPos(); /* ---------------- 存储 ---------------- */ function load() { try { var v = JSON.parse(localStorage.getItem(STORE_KEY)); return v && typeof v === 'object' ? v : {}; } catch (e) { return {}; } } function persist() { try { localStorage.setItem(STORE_KEY, JSON.stringify(hiddenMap)); } catch (e) {} updateCountUI(); } function loadPos() { try { var v = JSON.parse(localStorage.getItem(POS_KEY)); if (v && typeof v.x === 'number') { return { x: v.x, y: v.y, tuck: v.tuck === 'left' || v.tuck === 'right' ? v.tuck : 'none' }; } } catch (e) {} return null; } function savePos() { try { localStorage.setItem(POS_KEY, JSON.stringify(pos)); } catch (e) {} } /* ---------------- 工具 ---------------- */ function esc(s) { return String(s).replace(/[&<>"']/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); } function cellText(row, colClass, idxFallback) { var td = row.querySelector('td.' + colClass); if (!td) td = row.children[idxFallback]; if (!td) return ''; var cell = td.querySelector('.cell') || td; return (cell.textContent || '').trim(); } function rowKey(row) { return cellText(row, COL_KEY, 2); } function rowName(row) { return cellText(row, COL_NAME, 1); } /* ---------------- 样式 ---------------- */ var css = [ '.sct-hide-btn{color:#f56c6c;cursor:pointer;font-size:15px;font-weight:700;line-height:1;', 'background:none;border:none;padding:0 2px;flex:none;}', '.sct-hide-btn:hover{transform:scale(1.25);}', // 注意:sct-op-cell 就加在 .cell 元素自身上,必须用复合选择器 .cell.sct-op-cell 'div.cell.sct-op-cell{display:flex !important;align-items:center;flex-wrap:nowrap !important;', 'gap:6px;overflow:visible !important;}', 'div.cell.sct-op-cell .el-button{margin:0 !important;padding:0 !important;font-size:13px !important;', 'white-space:nowrap;flex:none;}', 'div.cell.sct-op-cell i.iconfont{font-size:15px;}', /* ---- 悬浮球容器 ---- */ '#sct-widget{position:fixed;z-index:99999;left:0;top:0;cursor:grab;', 'touch-action:none;user-select:none;-webkit-user-select:none;', 'transition:transform .25s ease;will-change:left,top;}', '#sct-widget.sct-dragging{cursor:grabbing;transition:none;}', '#sct-widget.sct-tuck-l{transform:translateX(-52%) !important;}', '#sct-widget.sct-tuck-r{transform:translateX(52%) !important;}', '#sct-widget:not(.sct-dragging):hover{transform:none !important;}', /* 球态 */ '#sct-ball{width:44px;height:44px;border-radius:50%;border:none;color:#fff;font-size:15px;', 'font-weight:700;display:flex;align-items:center;justify-content:center;cursor:inherit;', 'background:radial-gradient(circle at 30% 30%,#79bbff,#409eff 60%,#2f7cd6);', 'box-shadow:0 3px 12px rgba(64,158,255,.45);padding:0;font-family:inherit;}', '#sct-ball .sct-n{background:#f56c6c;border-radius:9px;min-width:18px;height:18px;line-height:18px;', 'font-size:11px;padding:0 4px;margin-top:20px;margin-left:20px;box-shadow:0 1px 4px rgba(0,0,0,.35);}', /* 胶囊态(展开) */ '#sct-cap{display:none;border:none;border-radius:22px;background:#409eff;color:#fff;', 'padding:11px 16px;font-size:13px;cursor:inherit;white-space:nowrap;', 'box-shadow:0 3px 12px rgba(64,158,255,.45);font-family:inherit;}', '#sct-widget.sct-mode-cap #sct-ball{display:none;}', '#sct-widget.sct-mode-cap #sct-cap{display:block;}', '#sct-widget.sct-mode-cap{filter:drop-shadow(0 0 0 transparent);}', /* ---- 管理面板 ---- */ '#sct-panel{position:fixed;z-index:99998;width:320px;max-height:60vh;', 'background:#fff;color:#303133;border-radius:10px;box-shadow:0 4px 20px rgba(0,0,0,.25);', 'display:none;flex-direction:column;overflow:hidden;font-size:13px;font-family:inherit;}', '#sct-panel .sct-head{display:flex;align-items:center;gap:8px;padding:10px 12px;', 'border-bottom:1px solid #ebeef5;font-weight:600;}', '#sct-panel .sct-head label{margin-left:auto;font-weight:400;display:flex;align-items:center;gap:4px;cursor:pointer;}', '#sct-panel .sct-list{overflow:auto;padding:6px 0;}', '#sct-panel .sct-item{display:flex;align-items:center;gap:6px;padding:6px 12px;}', '#sct-panel .sct-item span{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}', '#sct-panel .sct-item i{color:#909399;font-style:normal;font-size:11px;}', '#sct-panel .sct-item button{background:#f56c6c;border:none;color:#fff;border-radius:4px;', 'padding:2px 8px;cursor:pointer;font-size:12px;}', '#sct-panel .sct-empty{padding:16px;text-align:center;color:#909399;}', '#sct-panel .sct-foot{padding:8px 12px;border-top:1px solid #ebeef5;text-align:right;}', '#sct-panel .sct-foot button{background:#909399;border:none;color:#fff;border-radius:4px;', 'padding:4px 10px;cursor:pointer;font-size:12px;}', '.sct-toast{position:fixed;top:18px;left:50%;transform:translateX(-50%);z-index:100001;', 'background:#67c23a;color:#fff;padding:8px 16px;border-radius:6px;font-size:13px;', 'box-shadow:0 2px 10px rgba(0,0,0,.2);}' ].join(''); var styleEl = document.createElement('style'); styleEl.textContent = css; document.head.appendChild(styleEl); /* ---------------- Toast ---------------- */ var toastTimer = null; function toast(msg) { var old = document.querySelector('.sct-toast'); if (old) old.remove(); var d = document.createElement('div'); d.className = 'sct-toast'; d.textContent = msg; document.body.appendChild(d); clearTimeout(toastTimer); toastTimer = setTimeout(function () { d.remove(); }, 1800); } /* ---------------- 行处理 ---------------- */ function ensureHideBtn(row) { if (row.querySelector('.sct-hide-btn')) return; var tds = row.querySelectorAll('td'); var last = tds[tds.length - 1]; if (!last) return; var cell = last.querySelector('.cell'); if (!cell) return; cell.classList.add('sct-op-cell'); var btn = document.createElement('button'); btn.className = 'sct-hide-btn'; btn.type = 'button'; btn.textContent = '\u2715'; btn.title = '隐藏此任务'; btn.addEventListener('click', function (e) { e.stopPropagation(); e.preventDefault(); var key = rowKey(row); if (!key) return; hiddenMap[key] = rowName(row) || key; persist(); applyAll(); toast('已隐藏:' + hiddenMap[key]); }); cell.appendChild(btn); } var FIXED_EXTRA = 36; function widenFixedColumn() { var root = document.querySelector(ROOT_SEL); if (!root) return; root.querySelectorAll('.el-table__fixed-right').forEach(function (fix) { if (fix.dataset.sctWidened) return; fix.dataset.sctWidened = '1'; fix.querySelectorAll('colgroup col').forEach(function (col) { var w = parseInt(col.getAttribute('width'), 10); if (w === 220) col.setAttribute('width', String(w + FIXED_EXTRA)); }); fix.style.width = (parseInt(fix.style.width, 10) || 220) + FIXED_EXTRA + 'px'; var patch = root.querySelector('.el-table__fixed-right-patch'); if (patch) { var pw = parseInt(patch.style.width, 10) || 0; if (pw > 0) patch.style.width = (pw + FIXED_EXTRA) + 'px'; } }); } function applyAll() { var root = document.querySelector(ROOT_SEL); if (!root) return; root.querySelectorAll('.el-table__body-wrapper tbody tr').forEach(function (row) { var key = rowKey(row); if (!key) return; ensureHideBtn(row); row.style.display = (!!hiddenMap[key] && !ghostMode) ? 'none' : ''; }); root.querySelectorAll('.el-table__fixed-body-wrapper tbody tr').forEach(function (row) { var key = rowKey(row); if (!key) return; ensureHideBtn(row); row.style.display = (!!hiddenMap[key] && !ghostMode) ? 'none' : ''; }); widenFixedColumn(); } /* ---------------- 悬浮球 ---------------- */ var widget, ballBtn, capBtn, panel, listBox; function buildUI() { widget = document.createElement('div'); widget.id = 'sct-widget'; widget.innerHTML = '' + ''; document.body.appendChild(widget); ballBtn = widget.querySelector('#sct-ball'); capBtn = widget.querySelector('#sct-cap'); panel = document.createElement('div'); panel.id = 'sct-panel'; panel.innerHTML = '
已隐藏的任务' + '
' + '
' + '
'; document.body.appendChild(panel); listBox = panel.querySelector('.sct-list'); panel.querySelector('#sct-ghost').addEventListener('change', function (e) { ghostMode = e.target.checked; applyAll(); renderPanel(); }); panel.querySelector('#sct-clear').addEventListener('click', function () { if (!Object.keys(hiddenMap).length) return; if (!confirm('确定要恢复显示全部已隐藏任务吗?')) return; hiddenMap = {}; persist(); applyAll(); renderPanel(); toast('已全部恢复'); }); ['pointerenter', 'pointerleave'].forEach(function (evt) { [widget, panel].forEach(function (el) { el.addEventListener(evt, function (e) { hovering = evt === 'pointerenter' || e.type === 'pointerenter'; if (hovering) poke(); else scheduleCollapse(); }); }); }); attachDrag(); restorePosition(); applyMode(); updateCountUI(); poke(); } function updateCountUI() { var n = Object.keys(hiddenMap).length; if (ballBtn) ballBtn.querySelector('.sct-n').textContent = n; if (capBtn) capBtn.querySelector('.sct-n').textContent = '(' + n + ')'; } function applyMode() { widget.classList.toggle('sct-mode-cap', mode === 'cap'); widget.classList.toggle('sct-mode-ball', mode === 'ball'); } function setMode(m) { if (mode === m) return; mode = m; applyMode(); if (m === 'ball') closePanel(); } function expandCapsule() { setMode('cap'); applyPosition(); poke(); } /* ---------- 定位 / 吸附半隐藏 ---------- */ function widgetW() { return widget.offsetWidth || BALL_SIZE; } function clampY(y) { var vh = window.innerHeight; var h = widget.offsetHeight || BALL_SIZE; return Math.min(Math.max(y, 8), vh - h - 8); } function clampX(x) { var vw = window.innerWidth; var w = widgetW(); return Math.min(Math.max(x, 8), vw - w - 8); } function setPosition(x, y) { pos.x = clampX(x); pos.y = clampY(y); widget.style.left = pos.x + 'px'; widget.style.top = pos.y + 'px'; } function applyPosition() { setPosition(pos.x, pos.y); } function applyTuck() { widget.classList.toggle('sct-tuck-l', pos.tuck === 'left'); widget.classList.toggle('sct-tuck-r', pos.tuck === 'right'); } function restorePosition() { if (!pos) { var vw = window.innerWidth, vh = window.innerHeight; pos = { x: vw - BALL_SIZE - 24, y: Math.round(vh * 0.75), tuck: 'none' }; } applyPosition(); applyTuck(); } /* ---------- 拖拽 + 点击 ---------- */ function attachDrag() { var sx = 0, sy = 0, ox = 0, oy = 0, moved = false, pid = null; widget.addEventListener('pointerdown', function (e) { if (e.button !== undefined && e.button !== 0) return; pid = e.pointerId; sx = e.clientX; sy = e.clientY; ox = pos.x; oy = pos.y; moved = false; widget.setPointerCapture(pid); }); widget.addEventListener('pointermove', function (e) { if (pid === null) return; var dx = e.clientX - sx, dy = e.clientY - sy; if (!moved && Math.hypot(dx, dy) > DRAG_TH) { moved = true; dragging = true; widget.classList.add('sct-dragging'); pos.tuck = 'none'; applyTuck(); } if (moved) { setPosition(ox + dx, oy + dy); e.preventDefault(); } }); function finish(e) { if (pid === null) return; try { widget.releasePointerCapture(pid); } catch (err) {} pid = null; if (moved) { dragging = false; widget.classList.remove('sct-dragging'); snapToEdge(); savePos(); poke(); } else { handleClick(); } moved = false; } widget.addEventListener('pointerup', finish); widget.addEventListener('pointercancel', finish); } function snapToEdge() { var vw = window.innerWidth; var cx = pos.x + widgetW() / 2; var distL = cx, distR = vw - cx; if (distL < SNAP_PX) { pos.x = 8; pos.tuck = 'left'; } else if (distR < SNAP_PX) { pos.x = vw - widgetW() - 8; pos.tuck = 'right'; } else { pos.tuck = 'none'; } applyPosition(); applyTuck(); } function handleClick() { poke(); if (mode === 'ball') { expandCapsule(); return; } // 胶囊态:点击切换面板 if (panelOpen) closePanel(); else openPanel(); poke(); } /* ---------- 面板 ---------- */ function openPanel() { renderPanel(); panel.style.visibility = 'hidden'; panel.style.display = 'flex'; // 依据面板实际尺寸定位在胶囊上方;放不下则放到下方 var r = widget.getBoundingClientRect(); var pw = panel.offsetWidth, ph = panel.offsetHeight; var left = Math.min(Math.max(r.left, 8), window.innerWidth - pw - 8); var top = r.top - ph - 10; if (top < 8) top = r.bottom + 10; panel.style.left = left + 'px'; panel.style.top = Math.max(top, 8) + 'px'; panel.style.visibility = ''; panelOpen = true; } function closePanel() { panel.style.display = 'none'; panelOpen = false; } function togglePanel() { panelOpen ? closePanel() : openPanel(); } function renderPanel() { var keys = Object.keys(hiddenMap); if (!keys.length) { listBox.innerHTML = '
暂无隐藏的任务
点击表格行“操作”里的 ✕ 添加
'; return; } listBox.innerHTML = keys.map(function (k) { return '
' + esc(hiddenMap[k]) + ' (' + esc(k) + ')' + '
'; }).join(''); listBox.querySelectorAll('button[data-k]').forEach(function (b) { b.addEventListener('click', function () { var k = b.getAttribute('data-k'); delete hiddenMap[k]; persist(); applyAll(); renderPanel(); toast('已恢复:' + k); }); }); } /* ---------- 自动收回 ---------- */ function poke() { clearTimeout(idleTimer); idleTimer = setTimeout(scheduleCollapse, IDLE_MS); } function scheduleCollapse() { if (dragging || hovering || panelOpen) { poke(); return; } // 面板开着时不打扰阅读 if (mode !== 'ball') setMode('ball'); } /* ---------------- 监听 DOM 变化(SPA/翻页) ---------------- */ function scheduleApply() { if (pendingTimer) return; pendingTimer = setTimeout(function () { pendingTimer = null; applyAll(); }, 200); } function init() { buildUI(); applyAll(); var mo = new MutationObserver(function (muts) { for (var i = 0; i < muts.length; i++) { var m = muts[i]; var t = m.target; if (t && t.closest && t.closest('#sct-widget,#sct-panel')) continue; if (t && t.classList && t.classList.contains('sct-toast')) continue; scheduleApply(); break; } }); mo.observe(document.body, { childList: true, subtree: true }); window.addEventListener('resize', function () { applyPosition(); snapToEdgeIfNeeded(); if (panelOpen) { closePanel(); } }); } function snapToEdgeIfNeeded() { // 窗口变化后如果原本贴边,重新贴边 if (pos.tuck !== 'none') { var vw = window.innerWidth; if (pos.tuck === 'left') pos.x = 8; else pos.x = vw - widgetW() - 8; applyPosition(); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();