Last active
September 3, 2026 11:31
-
-
Save AsP3X/32a79618f153b86f281d9c063b58e2db to your computer and use it in GitHub Desktop.
A last modified filter highlighter for freshservice.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Freshservice Stale Tickets | |
| // @namespace sth | |
| // @version 1.4 | |
| // @description Highlight stale Freshservice tickets and open them in tabs | |
| // @match https://*.freshservice.com/* | |
| // @match https://*.myfreshworks.com/* | |
| // @match *://*/a/tickets* | |
| // @run-at document-idle | |
| // @grant none | |
| // ==/UserScript== | |
| (() => { | |
| const NS = 'sth'; | |
| const HOST_ID = `${NS}-host`; | |
| const STYLE_ID = `${NS}-page-style`; | |
| const ROW_MARK = `${NS}-row`; | |
| const CELL_MARK = `${NS}-cell`; | |
| const ROW_SEL = 'tr.et-tr'; | |
| const DATE_SEL = 'td[data-name="updated_at_date"] [data-test-id="date-cell"]'; | |
| const TICKET_LINK_SEL = 'a.subject-cell[href], a[href*="/tickets/"]'; | |
| const STORAGE_KEY = `${NS}-settings`; | |
| const MS_DAY = 24 * 60 * 60 * 1000; | |
| const DEFAULTS = { | |
| days: 6, | |
| color: '#e65100', | |
| enabled: true, | |
| collapsed: false, | |
| statuses: [], | |
| statusOpen: false, | |
| x: null, | |
| y: null | |
| }; | |
| const loadSettings = () => { | |
| try { | |
| return { ...DEFAULTS, ...JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}') }; | |
| } catch { | |
| return { ...DEFAULTS }; | |
| } | |
| }; | |
| const saveSettings = (s) => localStorage.setItem(STORAGE_KEY, JSON.stringify(s)); | |
| let settings = loadSettings(); | |
| if (!Array.isArray(settings.statuses)) settings.statuses = []; | |
| if (settings.statusOpen == null) settings.statusOpen = false; | |
| document.getElementById(HOST_ID)?.remove(); | |
| document.getElementById(STYLE_ID)?.remove(); | |
| window.__staleTicketObserver?.disconnect(); | |
| const pageStyle = document.createElement('style'); | |
| pageStyle.id = STYLE_ID; | |
| document.head.appendChild(pageStyle); | |
| const hexToRgba = (hex, a) => { | |
| const h = hex.replace('#', ''); | |
| const n = h.length === 3 ? h.split('').map((c) => c + c).join('') : h; | |
| return `rgba(${parseInt(n.slice(0, 2), 16)}, ${parseInt(n.slice(2, 4), 16)}, ${parseInt(n.slice(4, 6), 16)}, ${a})`; | |
| }; | |
| const applyPageStyles = () => { | |
| const c = settings.color; | |
| pageStyle.textContent = ` | |
| .${ROW_MARK} { | |
| background-color: ${hexToRgba(c, 0.18)} !important; | |
| box-shadow: inset 4px 0 0 ${c} !important; | |
| } | |
| .${ROW_MARK} > td { | |
| background-color: ${hexToRgba(c, 0.18)} !important; | |
| } | |
| .${CELL_MARK} { | |
| background-color: ${hexToRgba(c, 0.35)} !important; | |
| outline: 2px solid ${c} !important; | |
| border-radius: 3px; | |
| } | |
| `; | |
| }; | |
| const MONTHS = { | |
| jan: 0, feb: 1, mar: 2, apr: 3, may: 4, jun: 5, | |
| jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11 | |
| }; | |
| function parseTicketDate(raw) { | |
| if (!raw) return null; | |
| const str = String(raw).replace(/\s+/g, ' ').trim(); | |
| let m = str.match(/(\d{1,2})\s+([A-Za-z]{3})\.?,?\s+(\d{4})(?:[,\s]+(\d{1,2}):(\d{2}))?/); | |
| if (m && MONTHS[m[2].toLowerCase()] != null) { | |
| return new Date(+m[3], MONTHS[m[2].toLowerCase()], +m[1], +(m[4] || 0), +(m[5] || 0)); | |
| } | |
| m = str.match(/(\d{1,2})[./](\d{1,2})[./](\d{4})(?:[,\s]+(\d{1,2}):(\d{2}))?/); | |
| if (m) return new Date(+m[3], +m[2] - 1, +m[1], +(m[4] || 0), +(m[5] || 0)); | |
| const native = new Date(str.replace(/,/g, '')); | |
| return Number.isNaN(native.getTime()) ? null : native; | |
| } | |
| function ticketHref(row) { | |
| const a = row.querySelector(TICKET_LINK_SEL); | |
| if (!a) return null; | |
| try { | |
| return new URL(a.getAttribute('href'), location.origin).href; | |
| } catch { | |
| return null; | |
| } | |
| } | |
| function rowStatus(row) { | |
| const el = row.querySelector('.status-result, td[data-name="status"] [title]'); | |
| return String(el?.getAttribute('title') || el?.textContent || '').replace(/\s+/g, ' ').trim(); | |
| } | |
| function statusWanted(row) { | |
| const tags = (settings.statuses || []).map((s) => s.toLowerCase()); | |
| if (!tags.length) return false; | |
| return tags.includes(rowStatus(row).toLowerCase()); | |
| } | |
| function collectStaleRows() { | |
| const now = Date.now(); | |
| const out = []; | |
| document.querySelectorAll(ROW_SEL).forEach((row) => { | |
| const cell = row.querySelector(DATE_SEL); | |
| const date = cell ? parseTicketDate(cell.getAttribute('title') || cell.textContent) : null; | |
| const stale = !!(date && (now - date.getTime()) / MS_DAY >= settings.days); | |
| const byStatus = statusWanted(row); | |
| if (!stale && !byStatus) return; | |
| out.push({ row, cell, href: ticketHref(row), date, stale, byStatus }); | |
| }); | |
| return out; | |
| } | |
| function clearMarks() { | |
| document.querySelectorAll(`.${ROW_MARK}`).forEach((el) => { | |
| el.classList.remove(ROW_MARK); | |
| el.removeAttribute('data-stale-days'); | |
| }); | |
| document.querySelectorAll(`.${CELL_MARK}`).forEach((el) => el.classList.remove(CELL_MARK)); | |
| } | |
| let lastStats = { tickets: 0, marked: 0 }; | |
| function markTickets() { | |
| clearMarks(); | |
| const rows = document.querySelectorAll(ROW_SEL); | |
| const stale = collectStaleRows(); | |
| if (settings.enabled) { | |
| const now = Date.now(); | |
| stale.forEach(({ row, cell, date, byStatus }) => { | |
| row.classList.add(ROW_MARK); | |
| if (date) row.dataset.staleDays = String(Math.floor((now - date.getTime()) / MS_DAY)); | |
| if (byStatus) row.dataset.statusMark = rowStatus(row); | |
| if (cell) cell.classList.add(CELL_MARK); | |
| }); | |
| } | |
| lastStats = { tickets: rows.length, marked: stale.length }; | |
| renderStats(); | |
| } | |
| function openStaleTickets() { | |
| markTickets(); | |
| const urls = [...new Set(collectStaleRows().map((x) => x.href).filter(Boolean))]; | |
| if (!urls.length) { | |
| console.log('[stale-tickets] no stale ticket links on this page'); | |
| return; | |
| } | |
| if (urls.length > 8 && !confirm(`Open ${urls.length} stale tickets in new tabs?`)) return; | |
| let opened = 0; | |
| urls.forEach((url) => { | |
| const win = window.open(url, '_blank', 'noopener'); | |
| if (win) opened += 1; | |
| }); | |
| if (opened < urls.length) { | |
| alert(`Opened ${opened} of ${urls.length} tabs. Allow pop-ups for this site to open the rest.`); | |
| } | |
| } | |
| const host = document.createElement('div'); | |
| host.id = HOST_ID; | |
| host.style.cssText = 'all:initial;position:fixed;z-index:2147483647;bottom:20px;right:20px;top:auto;left:auto;touch-action:none;'; | |
| document.documentElement.appendChild(host); | |
| const shadow = host.attachShadow({ mode: 'open' }); | |
| shadow.innerHTML = ` | |
| <style> | |
| :host { all: initial; } | |
| * { box-sizing: border-box; font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif; } | |
| .fab, .panel { | |
| color: #e8eaed; | |
| background: linear-gradient(180deg, rgba(28,32,38,.92), rgba(18,20,24,.94)); | |
| border: 1px solid rgba(255,255,255,.10); | |
| box-shadow: 0 18px 50px rgba(0,0,0,.35), 0 0 0 1px rgba(255,255,255,.04) inset; | |
| backdrop-filter: blur(18px); | |
| -webkit-backdrop-filter: blur(18px); | |
| } | |
| .fab { | |
| display: none; align-items: center; gap: 8px; height: 44px; | |
| padding: 0 14px 0 8px; border-radius: 999px; | |
| cursor: grab; user-select: none; | |
| } | |
| .fab.show { display: flex; } | |
| .fab:active, .fab.dragging { cursor: grabbing; } | |
| .fab .dot { | |
| width: 28px; height: 28px; border-radius: 50%; | |
| display: grid; place-items: center; pointer-events: none; | |
| background: var(--accent, #e65100); color: #fff; font-size: 12px; font-weight: 700; | |
| } | |
| .fab .label { font-size: 13px; font-weight: 600; pointer-events: none; } | |
| .panel { width: 288px; border-radius: 18px; overflow: hidden; } | |
| .panel.hide { display: none; } | |
| .panel.dragging, .fab.dragging { opacity: .92; } | |
| .head { | |
| display: flex; align-items: center; gap: 10px; | |
| padding: 14px 14px 12px; | |
| border-bottom: 1px solid rgba(255,255,255,.07); | |
| cursor: grab; user-select: none; | |
| } | |
| .head:active { cursor: grabbing; } | |
| .logo { | |
| width: 32px; height: 32px; border-radius: 9px; | |
| display: grid; place-items: center; pointer-events: none; | |
| background: var(--accent, #e65100); color: #fff; flex: 0 0 auto; | |
| } | |
| .titles { flex: 1; min-width: 0; pointer-events: none; } | |
| .titles h1 { margin: 0; font-size: 13.5px; font-weight: 650; letter-spacing: -.01em; } | |
| .titles p { margin: 2px 0 0; font-size: 11px; color: #9aa3ad; } | |
| .grip { color: #6b7380; display: grid; place-items: center; pointer-events: none; } | |
| .icon-btn { | |
| width: 28px; height: 28px; border: 0; border-radius: 8px; | |
| background: transparent; color: #9aa3ad; cursor: pointer; | |
| display: grid; place-items: center; | |
| } | |
| .icon-btn:hover { background: rgba(255,255,255,.08); color: #fff; } | |
| .body { padding: 14px; display: grid; gap: 14px; } | |
| .row-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; } | |
| .label { font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: #8b949e; } | |
| .toggle { | |
| width: 42px; height: 24px; border-radius: 999px; border: 0; | |
| background: #3a4048; position: relative; cursor: pointer; padding: 0; | |
| } | |
| .toggle.on { background: var(--accent, #e65100); } | |
| .toggle i { | |
| position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%; | |
| background: #fff; transition: left .16s ease; box-shadow: 0 1px 4px rgba(0,0,0,.3); | |
| } | |
| .toggle.on i { left: 21px; } | |
| .days-card { | |
| background: rgba(255,255,255,.04); | |
| border: 1px solid rgba(255,255,255,.06); | |
| border-radius: 14px; padding: 12px; | |
| } | |
| .days-top { display: flex; align-items: baseline; justify-content: space-between; } | |
| .days-val { font-size: 28px; font-weight: 700; letter-spacing: -.03em; line-height: 1; color: #fff; } | |
| .days-val span { font-size: 13px; font-weight: 600; color: #9aa3ad; margin-left: 4px; } | |
| input[type="range"] { | |
| -webkit-appearance: none; appearance: none; width: 100%; height: 4px; margin: 14px 0 8px; | |
| background: linear-gradient(90deg, var(--accent) var(--p, 20%), #3a4048 var(--p, 20%)); | |
| border-radius: 99px; outline: none; | |
| } | |
| input[type="range"]::-webkit-slider-thumb { | |
| -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%; | |
| background: #fff; border: 3px solid var(--accent, #e65100); cursor: pointer; | |
| } | |
| .presets { display: flex; gap: 6px; } | |
| .chip { | |
| flex: 1; height: 28px; border-radius: 8px; border: 1px solid rgba(255,255,255,.08); | |
| background: rgba(255,255,255,.03); color: #c5cbd3; font-size: 11px; font-weight: 650; cursor: pointer; | |
| } | |
| .chip:hover { background: rgba(255,255,255,.07); } | |
| .chip.on { | |
| background: color-mix(in srgb, var(--accent) 22%, transparent); | |
| border-color: color-mix(in srgb, var(--accent) 55%, transparent); | |
| color: #fff; | |
| } | |
| .tagbox { | |
| background: rgba(255,255,255,.04); | |
| border: 1px solid rgba(255,255,255,.06); | |
| border-radius: 14px; padding: 10px; | |
| } | |
| .tagbox-head { | |
| width: 100%; display: flex; align-items: center; justify-content: space-between; | |
| gap: 8px; border: 0; background: transparent; color: inherit; cursor: pointer; padding: 0; | |
| } | |
| .tagbox-head .chevron { color: #8b949e; transition: transform .16s ease; } | |
| .tagbox.open .chevron { transform: rotate(180deg); } | |
| .tagbox-body { display: none; margin-top: 8px; } | |
| .tagbox.open .tagbox-body { display: block; } | |
| .tag-count { | |
| font-size: 10px; font-weight: 700; color: #9aa3ad; | |
| background: rgba(255,255,255,.06); border-radius: 999px; padding: 2px 7px; | |
| } | |
| .tags { display: flex; flex-wrap: wrap; gap: 6px; } | |
| .tag { | |
| display: inline-flex; align-items: center; gap: 6px; | |
| height: 24px; padding: 0 8px; border-radius: 999px; | |
| background: color-mix(in srgb, var(--accent) 22%, transparent); | |
| border: 1px solid color-mix(in srgb, var(--accent) 50%, transparent); | |
| color: #fff; font-size: 11px; font-weight: 650; | |
| } | |
| .tag button { | |
| width: 14px; height: 14px; border: 0; padding: 0; border-radius: 50%; | |
| background: transparent; color: #fff; cursor: pointer; line-height: 1; font-size: 12px; | |
| } | |
| .tagbox input { | |
| width: 100%; height: 30px; margin-top: 8px; border-radius: 8px; | |
| border: 1px solid rgba(255,255,255,.10); | |
| background: rgba(255,255,255,.05); color: #f2f4f7; | |
| padding: 0 10px; font-size: 12px; outline: none; | |
| } | |
| .tagbox input::placeholder { color: #7d8692; } | |
| .hints { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; } | |
| .hint { | |
| height: 22px; padding: 0 8px; border-radius: 999px; cursor: pointer; | |
| border: 1px dashed rgba(255,255,255,.16); | |
| background: transparent; color: #9aa3ad; font-size: 11px; font-weight: 600; | |
| } | |
| .hint:hover { color: #fff; border-color: rgba(255,255,255,.35); } | |
| .color-row { display: flex; gap: 8px; } | |
| .swatch { width: 28px; height: 28px; border-radius: 8px; border: 2px solid transparent; cursor: pointer; padding: 0; } | |
| .swatch.on { border-color: #fff; } | |
| .picker-wrap { | |
| position: relative; width: 28px; height: 28px; border-radius: 8px; overflow: hidden; | |
| border: 1px dashed rgba(255,255,255,.25); | |
| } | |
| .picker-wrap input { position: absolute; inset: -4px; width: 36px; height: 36px; border: 0; padding: 0; cursor: pointer; background: none; } | |
| .stats { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } | |
| .stat { background: rgba(255,255,255,.04); border-radius: 12px; padding: 10px 12px; } | |
| .stat b { display: block; font-size: 18px; font-weight: 700; letter-spacing: -.02em; } | |
| .stat span { font-size: 10px; color: #8b949e; text-transform: uppercase; letter-spacing: .05em; font-weight: 600; } | |
| .foot { display: grid; grid-template-columns: 1fr; gap: 8px; padding: 0 14px 14px; } | |
| .ghost, .primary { | |
| height: 34px; border-radius: 10px; font-size: 12px; font-weight: 600; cursor: pointer; | |
| } | |
| .ghost { | |
| border: 1px solid rgba(255,255,255,.08); | |
| background: transparent; color: #c5cbd3; | |
| } | |
| .ghost:hover { background: rgba(255,255,255,.06); color: #fff; } | |
| .danger:hover { color: #ff8a80; border-color: rgba(255,138,128,.35); } | |
| .primary { | |
| grid-column: 1 / -1; | |
| border: 0; color: #fff; | |
| background: var(--accent, #e65100); | |
| } | |
| .primary:hover { filter: brightness(1.08); } | |
| </style> | |
| <div class="fab" id="fab" title="Drag to move · click to open"> | |
| <span class="dot" id="fabCount">0</span> | |
| <span class="label">Stale tickets</span> | |
| </div> | |
| <section class="panel" id="panel"> | |
| <header class="head" id="dragHandle"> | |
| <div class="logo"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none"> | |
| <path d="M12 8v5l3 2" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/> | |
| <circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2.2"/> | |
| </svg> | |
| </div> | |
| <div class="titles"> | |
| <h1>Stale tickets</h1> | |
| <p>Drag to move</p> | |
| </div> | |
| <span class="grip" aria-hidden="true"> | |
| <svg width="12" height="16" viewBox="0 0 12 16" fill="currentColor"> | |
| <circle cx="3" cy="2" r="1.2"/><circle cx="9" cy="2" r="1.2"/> | |
| <circle cx="3" cy="8" r="1.2"/><circle cx="9" cy="8" r="1.2"/> | |
| <circle cx="3" cy="14" r="1.2"/><circle cx="9" cy="14" r="1.2"/> | |
| </svg> | |
| </span> | |
| <button class="icon-btn" id="collapse" title="Minimize to pill"> | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none"> | |
| <path d="M5 12h14" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/> | |
| </svg> | |
| </button> | |
| </header> | |
| <div class="body"> | |
| <div class="row-between"> | |
| <span class="label">Highlight</span> | |
| <button class="toggle" id="enabled" aria-label="Toggle highlight"><i></i></button> | |
| </div> | |
| <div class="days-card"> | |
| <div class="days-top"> | |
| <span class="label">Updated older than</span> | |
| <div class="days-val" id="daysLabel">6<span>days</span></div> | |
| </div> | |
| <input type="range" id="days" min="1" max="30" step="1" /> | |
| <div class="presets"> | |
| <button class="chip" data-days="3">3d</button> | |
| <button class="chip" data-days="6">6d</button> | |
| <button class="chip" data-days="10">10d</button> | |
| <button class="chip" data-days="14">14d</button> | |
| </div> | |
| </div> | |
| <div class="tagbox" id="statusBox"> | |
| <button type="button" class="tagbox-head" id="statusToggle"> | |
| <span class="label">Also mark status</span> | |
| <span style="display:flex;align-items:center;gap:8px"> | |
| <span class="tag-count" id="statusCount">0</span> | |
| <svg class="chevron" width="12" height="12" viewBox="0 0 24 24" fill="none"> | |
| <path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/> | |
| </svg> | |
| </span> | |
| </button> | |
| <div class="tagbox-body"> | |
| <div class="tags" id="statusTags"></div> | |
| <input id="statusInput" type="text" placeholder="Type Open, Pending… Enter" autocomplete="off" spellcheck="false" /> | |
| <div class="hints" id="statusHints"></div> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="row-between" style="margin-bottom:8px"><span class="label">Color</span></div> | |
| <div class="color-row"> | |
| <button class="swatch" data-color="#e65100" style="background:#e65100"></button> | |
| <button class="swatch" data-color="#c62828" style="background:#c62828"></button> | |
| <button class="swatch" data-color="#6a1b9a" style="background:#6a1b9a"></button> | |
| <button class="swatch" data-color="#1565c0" style="background:#1565c0"></button> | |
| <button class="swatch" data-color="#2e7d32" style="background:#2e7d32"></button> | |
| <label class="picker-wrap" title="Custom color"> | |
| <input type="color" id="customColor" /> | |
| </label> | |
| </div> | |
| </div> | |
| <div class="stats"> | |
| <div class="stat"><b id="statTickets">0</b><span>Scanned</span></div> | |
| <div class="stat"><b id="statMarked">0</b><span>Stale</span></div> | |
| </div> | |
| </div> | |
| <div class="foot"> | |
| <button class="primary" id="openStale">Open stale in tabs</button> | |
| <button class="ghost" id="rescan">Rescan</button> | |
| </div> | |
| </section> | |
| `; | |
| const $ = (id) => shadow.getElementById(id); | |
| const panel = $('panel'); | |
| const fab = $('fab'); | |
| function clampPos(x, y) { | |
| const rect = host.getBoundingClientRect(); | |
| const pad = 8; | |
| return { | |
| x: Math.min(Math.max(pad, x), Math.max(pad, window.innerWidth - rect.width - pad)), | |
| y: Math.min(Math.max(pad, y), Math.max(pad, window.innerHeight - rect.height - pad)) | |
| }; | |
| } | |
| function placeDefault() { | |
| host.style.top = 'auto'; | |
| host.style.left = 'auto'; | |
| host.style.right = '20px'; | |
| host.style.bottom = '20px'; | |
| } | |
| function placeAt(x, y) { | |
| const p = clampPos(x, y); | |
| host.style.left = p.x + 'px'; | |
| host.style.top = p.y + 'px'; | |
| host.style.right = 'auto'; | |
| host.style.bottom = 'auto'; | |
| return p; | |
| } | |
| function applySavedPosition() { | |
| if (Number.isFinite(settings.x) && Number.isFinite(settings.y)) { | |
| requestAnimationFrame(() => placeAt(settings.x, settings.y)); | |
| } else { | |
| placeDefault(); | |
| } | |
| } | |
| function renderStats() { | |
| if (!$('statTickets')) return; | |
| $('statTickets').textContent = String(lastStats.tickets); | |
| $('statMarked').textContent = String(lastStats.marked); | |
| $('fabCount').textContent = String(lastStats.marked); | |
| const btn = $('openStale'); | |
| if (btn) btn.textContent = lastStats.marked | |
| ? `Open ${lastStats.marked} stale tab${lastStats.marked === 1 ? '' : 's'}` | |
| : 'Open stale in tabs'; | |
| const badge = document.querySelector('#sth-nav-item [data-sth-badge]'); | |
| if (badge) badge.textContent = String(lastStats.marked || 0); | |
| } | |
| function syncUI() { | |
| shadow.querySelectorAll('.panel, .fab, .logo, .toggle, input[type="range"], .primary').forEach((el) => { | |
| el.style.setProperty('--accent', settings.color); | |
| }); | |
| $('enabled').classList.toggle('on', settings.enabled); | |
| $('days').value = String(settings.days); | |
| $('days').style.setProperty('--p', ((settings.days - 1) / 29) * 100 + '%'); | |
| $('daysLabel').innerHTML = `${settings.days}<span>day${settings.days === 1 ? '' : 's'}</span>`; | |
| $('customColor').value = settings.color; | |
| shadow.querySelectorAll('.chip').forEach((chip) => { | |
| chip.classList.toggle('on', Number(chip.dataset.days) === settings.days); | |
| }); | |
| shadow.querySelectorAll('.swatch').forEach((sw) => { | |
| sw.classList.toggle('on', sw.dataset.color.toLowerCase() === settings.color.toLowerCase()); | |
| }); | |
| panel.classList.toggle('hide', settings.collapsed); | |
| fab.classList.toggle('show', settings.collapsed); | |
| applyPageStyles(); | |
| applySavedPosition(); | |
| renderStatusTags(); | |
| const box = $('statusBox'); | |
| if (box) box.classList.toggle('open', !!settings.statusOpen); | |
| const count = $('statusCount'); | |
| if (count) count.textContent = String((settings.statuses || []).length); | |
| } | |
| function discoveredStatuses() { | |
| const set = new Set(); | |
| document.querySelectorAll(ROW_SEL).forEach((row) => { | |
| const s = rowStatus(row); | |
| if (s) set.add(s); | |
| }); | |
| return [...set].sort((a, b) => a.localeCompare(b)); | |
| } | |
| function addStatus(raw) { | |
| const name = String(raw || '').replace(/\s+/g, ' ').trim(); | |
| if (!name) return; | |
| const exists = settings.statuses.some((s) => s.toLowerCase() === name.toLowerCase()); | |
| if (exists) return; | |
| update({ statuses: [...settings.statuses, name] }); | |
| } | |
| function removeStatus(name) { | |
| update({ | |
| statuses: settings.statuses.filter((s) => s.toLowerCase() !== String(name).toLowerCase()) | |
| }); | |
| } | |
| function renderStatusTags() { | |
| const wrap = $('statusTags'); | |
| const hints = $('statusHints'); | |
| if (!wrap || !hints) return; | |
| wrap.innerHTML = settings.statuses.map((s) => | |
| `<span class="tag">${s}<button type="button" data-remove="${s}" aria-label="Remove ${s}">×</button></span>` | |
| ).join(''); | |
| wrap.querySelectorAll('button[data-remove]').forEach((btn) => { | |
| btn.addEventListener('click', () => removeStatus(btn.dataset.remove)); | |
| }); | |
| const selected = new Set(settings.statuses.map((s) => s.toLowerCase())); | |
| hints.innerHTML = discoveredStatuses() | |
| .filter((s) => !selected.has(s.toLowerCase())) | |
| .map((s) => `<button type="button" class="hint" data-add="${s}">${s}</button>`) | |
| .join(''); | |
| hints.querySelectorAll('button[data-add]').forEach((btn) => { | |
| btn.addEventListener('click', () => addStatus(btn.dataset.add)); | |
| }); | |
| } | |
| function update(partial) { | |
| settings = { ...settings, ...partial }; | |
| saveSettings(settings); | |
| syncUI(); | |
| if (!('x' in partial || 'y' in partial)) markTickets(); | |
| } | |
| const didDrag = { current: false }; | |
| function makeDraggable(handle, visual) { | |
| handle.addEventListener('pointerdown', (e) => { | |
| if (e.button != null && e.button !== 0) return; | |
| if (handle !== fab && e.target.closest('#collapse, .icon-btn, button, input, label')) return; | |
| e.preventDefault(); | |
| const rect = host.getBoundingClientRect(); | |
| const origX = rect.left; | |
| const origY = rect.top; | |
| const startX = e.clientX; | |
| const startY = e.clientY; | |
| let moved = false; | |
| didDrag.current = false; | |
| const onMove = (ev) => { | |
| const dx = ev.clientX - startX; | |
| const dy = ev.clientY - startY; | |
| if (!moved && Math.hypot(dx, dy) < 3) return; | |
| moved = true; | |
| didDrag.current = true; | |
| visual.classList.add('dragging'); | |
| const p = placeAt(origX + dx, origY + dy); | |
| settings.x = p.x; | |
| settings.y = p.y; | |
| }; | |
| const onUp = () => { | |
| window.removeEventListener('pointermove', onMove); | |
| window.removeEventListener('pointerup', onUp); | |
| visual.classList.remove('dragging'); | |
| if (moved) saveSettings(settings); | |
| }; | |
| window.addEventListener('pointermove', onMove); | |
| window.addEventListener('pointerup', onUp); | |
| }); | |
| } | |
| makeDraggable($('dragHandle'), panel); | |
| makeDraggable(fab, fab); | |
| $('statusToggle').addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| update({ statusOpen: !settings.statusOpen }); | |
| }); | |
| const statusInput = $('statusInput'); | |
| const commitStatusInput = () => { | |
| addStatus(statusInput.value); | |
| statusInput.value = ''; | |
| }; | |
| ['keydown', 'keypress', 'keyup'].forEach((type) => { | |
| statusInput.addEventListener(type, (e) => e.stopPropagation()); | |
| }); | |
| statusInput.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' || e.key === ',') { | |
| e.preventDefault(); | |
| commitStatusInput(); | |
| } else if (e.key === 'Backspace' && !statusInput.value && settings.statuses.length) { | |
| removeStatus(settings.statuses[settings.statuses.length - 1]); | |
| } | |
| }); | |
| statusInput.addEventListener('blur', () => { | |
| if (statusInput.value.trim()) commitStatusInput(); | |
| }); | |
| $('enabled').addEventListener('click', () => update({ enabled: !settings.enabled })); | |
| $('days').addEventListener('input', (e) => update({ days: Number(e.target.value) })); | |
| shadow.querySelectorAll('.chip').forEach((chip) => { | |
| chip.addEventListener('click', () => update({ days: Number(chip.dataset.days) })); | |
| }); | |
| shadow.querySelectorAll('.swatch').forEach((sw) => { | |
| sw.addEventListener('click', () => update({ color: sw.dataset.color })); | |
| }); | |
| $('customColor').addEventListener('input', (e) => update({ color: e.target.value })); | |
| $('collapse').addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| update({ collapsed: true }); | |
| }); | |
| fab.addEventListener('click', () => { | |
| if (didDrag.current) { | |
| didDrag.current = false; | |
| return; | |
| } | |
| update({ collapsed: false }); | |
| }); | |
| $('openStale').addEventListener('click', openStaleTickets); | |
| $('rescan').addEventListener('click', markTickets); | |
| window.addEventListener('resize', () => { | |
| if (Number.isFinite(settings.x) && Number.isFinite(settings.y)) { | |
| const p = placeAt(settings.x, settings.y); | |
| settings.x = p.x; | |
| settings.y = p.y; | |
| saveSettings(settings); | |
| } | |
| }); | |
| applyPageStyles(); | |
| syncUI(); | |
| markTickets(); | |
| let timer; | |
| window.__staleTicketObserver = new MutationObserver((muts) => { | |
| if (muts.every((m) => host.contains(m.target) || m.target.closest?.(`#${HOST_ID}`))) return; | |
| clearTimeout(timer); | |
| timer = setTimeout(markTickets, 300); | |
| }); | |
| window.__staleTicketObserver.observe(document.body, { childList: true, subtree: true }); | |
| })(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| javascript:void((async()=>{const b=Uint8Array.from(atob('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'),c=>c.charCodeAt(0));const t=await new Response(new Blob([b]).stream().pipeThrough(new DecompressionStream('gzip'))).text();(0,eval)(t)})()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment