ChatGPT Quick‑Delete (No Popup)

Hover to reveal a grey trash‑can badge; click it to auto‑delete the conversation instantly (no confirmation popup).

2025-04-22 या दिनांकाला. सर्वात नवीन आवृत्ती पाहा.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

You will need to install an extension such as Tampermonkey to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         ChatGPT Quick‑Delete (No Popup)
// @namespace    https://chatgpt.com/
// @version      1.2
// @description  Hover to reveal a grey trash‑can badge; click it to auto‑delete the conversation instantly (no confirmation popup).
// @match        https://chatgpt.com/*
// @match        https://chat.openai.com/*
// @grant        none
// @author       Blackupfreddy
// @license CC-BY-NC-SA-4.0
// ==/UserScript==

(() => {
  /* ══════════ helpers ══════════ */
  const waitFor = (pred, ms = 4000, step = 70) =>
    new Promise(res => {
      const end = Date.now() + ms;
      (function loop() {
        const el = pred();
        if (el) return res(el);
        if (Date.now() > end) return res(null);
        setTimeout(loop, step);
      })();
    });

  const fire = (el, type) =>
    el.dispatchEvent(new MouseEvent(type, { bubbles: true, composed: true }));

  /* ══════════ delete flow ══════════ */
  async function deleteConversation(li) {
    const link = li.querySelector('a[data-history-item-link]');
    const startPath  = location.pathname;
    const targetPath = link && link.getAttribute('href');
    const stay       = targetPath && startPath !== targetPath;

    const dots = li.querySelector('button[data-testid$="-options"]');
    if (!dots) return;
    ['pointerdown','pointerup','click'].forEach(t => fire(dots, t));

    const del = await waitFor(() =>
      [...document.querySelectorAll('[role="menuitem"], button')]
        .find(el => /^delete$/i.test(el.textContent.trim()) && !el.closest('.quick‑delete')));
    if (!del) return;
    ['pointerdown','pointerup','click'].forEach(t => fire(del, t));

    // **No confirmation step**—deletion is automatic
    const confirm = await waitFor(() =>
      document.querySelector('button[data-testid="delete-conversation-confirm-button"], .btn-danger'));
    if (!confirm) return;
    ['pointerdown','pointerup','click'].forEach(t => fire(confirm, t));

    if (stay) setTimeout(() => history.replaceState(null,'',startPath), 80);

    li.style.transition = 'opacity .25s';
    li.style.opacity = '0';
    setTimeout(() => (li.style.display = 'none'), 280);
  }

  /* ══════════ icon injection ══════════ */
  const ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
        viewBox="0 0 24 24" fill="none" stroke="currentColor"
        stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
    <polyline points="3 6 5 6 21 6"></polyline>
    <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6
             m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
    <line x1="10" y1="11" x2="10" y2="17"></line>
    <line x1="14" y1="11" x2="14" y2="17"></line></svg>`;

  function decorate(li) {
    if (li.querySelector('.quick‑delete')) return;

    const grp  = li.querySelector('.group');
    const link = grp?.querySelector('a[data-history-item-link]');
    if (!grp || !link) return;
    grp.style.position = 'relative';

    if (!link.dataset.origPad) {
      link.dataset.origPad = getComputedStyle(link).paddingLeft || '0px';
    }

    const icon = Object.assign(document.createElement('span'), {
      className : 'quick‑delete',
      innerHTML : ICON
    });

    const bg1 = 'var(--sidebar-surface-secondary, #4b5563)';
    const bg2 = 'var(--sidebar-surface-tertiary , #6b7280)';

    Object.assign(icon.style, {
      position        : 'absolute',
      left            : '4px',
      top             : '50%',
      transform       : 'translateY(-50%)',
      cursor          : 'pointer',
      pointerEvents   : 'auto',
      zIndex          : 5,
      padding         : '2px',
      borderRadius    : '4px',
      background      : `linear-gradient(135deg, ${bg1}, ${bg2})`,
      color           : 'var(--token-text-primary)',
      opacity         : 0,
      transition      : 'opacity 100ms'
    });

    grp.addEventListener('mouseenter', () => {
      icon.style.opacity  = '.85';
      link.style.transition = 'padding-left 100ms';
      link.style.paddingLeft = '28px';
    });
    grp.addEventListener('mouseleave', () => {
      icon.style.opacity  = '0';
      link.style.paddingLeft = link.dataset.origPad;
    });

    icon.addEventListener('click', e => {
      e.stopPropagation();
      e.preventDefault();
      deleteConversation(li);
    });

    grp.prepend(icon);
  }

  /* ══════════ observer ══════════ */
  const itemSelector = 'li[data-testid^="history-item-"]';

  function handleMutation(records) {
    for (const rec of records) {
      rec.addedNodes.forEach(node => {
        if (node.nodeType === 1 && node.matches(itemSelector)) decorate(node);
        else if (node.nodeType === 1) node.querySelectorAll?.(itemSelector).forEach(decorate);
      });
    }
  }

  function decorateInBatches(nodes) {
    const batch = nodes.splice(0, 50);
    batch.forEach(decorate);
    if (nodes.length) requestIdleCallback(() => decorateInBatches(nodes));
  }

  function init() {
    const history = document.getElementById('history');
    if (!history) return;
    new MutationObserver(handleMutation)
      .observe(history, { childList: true, subtree: true });
    const startNodes = [...history.querySelectorAll(itemSelector)];
    if (startNodes.length) requestIdleCallback(() => decorateInBatches(startNodes));
  }

  const ready = setInterval(() => {
    if (document.getElementById('history')) {
      clearInterval(ready);
      init();
    }
  }, 150);
})();