Gemini Bulk Delete

Bulk delete Gemini conversations

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

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

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

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

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name         Gemini Bulk Delete
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Bulk delete Gemini conversations
// @author       Antigravity
// @match        https://gemini.google.com/app*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=gemini.google.com
// @noframes
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // --- 1. Mini-Framework Utils ---

    // Feature detection for Trusted Types
    const policy = window.trustedTypes && window.trustedTypes.createPolicy ?
        window.trustedTypes.createPolicy('geminiBulkDeletePolicy', { createHTML: s => s }) :
        { createHTML: s => s };

    // Tagged Template Literal for safe HTML
    function html(strings, ...values) {
        const raw = strings.reduce((acc, str, i) => acc + str + (values[i] || ''), '');
        return policy.createHTML(raw);
    }

    // Reactivity System
    function reactive(target, onChange) {
        return new Proxy(target, {
            set(obj, prop, value) {
                if (obj[prop] !== value) {
                    obj[prop] = value;
                    onChange(prop, value);
                }
                return true;
            }
        });
    }

    // --- 2. Styles ---
    const CHECKBOX_CLASS = 'gemini-bulk-del-checkbox';

    function addStyles() {
        if (document.getElementById('gemini-bulk-style')) return;
        const style = document.createElement('style');
        style.id = 'gemini-bulk-style';
        style.textContent = `
            :root {
                --gemini-bulk-bg: rgba(30, 30, 30, 0.9);
                --gemini-bulk-border: rgba(255, 255, 255, 0.1);
                --gemini-bulk-text: #e3e3e3;
                --gemini-bulk-accent: #8ab4f8;
                --gemini-bulk-accent-hover: #aecbfa;
                --gemini-bulk-danger: #ea4335;
                --gemini-bulk-danger-hover: #f28b82;
            }
            .${CHECKBOX_CLASS} {
                appearance: none;
                -webkit-appearance: none;
                width: 20px;
                height: 20px;
                border: 2px solid var(--gemini-bulk-border);
                border-radius: 6px;
                margin-right: 12px;
                cursor: pointer;
                position: relative;
                transition: all 0.2s ease;
                background-color: transparent;
                flex-shrink: 0;
                z-index: 1000;
            }
            .${CHECKBOX_CLASS}:checked {
                background-color: var(--gemini-bulk-accent);
                border-color: var(--gemini-bulk-accent);
            }
            .${CHECKBOX_CLASS}:checked::after {
                content: '';
                position: absolute;
                left: 50%;
                top: 50%;
                width: 5px;
                height: 10px;
                border: solid #1e1e1e;
                border-width: 0 2px 2px 0;
                transform: translate(-50%, -50%) rotate(45deg);
                margin-top: -2px;
            }
            .${CHECKBOX_CLASS}:hover {
                border-color: var(--gemini-bulk-accent-hover);
            }
            .gemini-bulk-selected {
                background-color: var(--gemini-bulk-border);
            }
            /* Floating Bar Component */
            .gemini-floating-bar {
                position: fixed;
                bottom: 30px;
                left: 50%;
                transform: translateX(-50%) translateY(100px);
                background: var(--gemini-bulk-bg);
                backdrop-filter: blur(12px);
                -webkit-backdrop-filter: blur(12px);
                color: var(--gemini-bulk-text);
                padding: 12px 24px;
                border-radius: 16px;
                display: flex;
                align-items: center;
                gap: 20px;
                box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
                border: 1px solid var(--gemini-bulk-border);
                z-index: 9999;
                font-family: 'Google Sans', Roboto, sans-serif;
                font-size: 14px;
                font-weight: 500;
                transition: transform 0.4s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.4s ease;
                opacity: 0;
                pointer-events: none;
            }
            .gemini-floating-bar.visible {
                transform: translateX(-50%) translateY(0);
                opacity: 1;
                pointer-events: auto;
            }
            .gemini-floating-bar button {
                background: var(--gemini-bulk-danger);
                color: #fff;
                border: none;
                padding: 10px 20px;
                border-radius: 20px;
                cursor: pointer;
                font-weight: 600;
                font-size: 14px;
                letter-spacing: 0.5px;
                transition: background-color 0.2s, transform 0.1s;
                box-shadow: 0 2px 8px rgba(234, 67, 53, 0.3);
            }
            .gemini-floating-bar button:hover {
                background: var(--gemini-bulk-danger-hover);
                box-shadow: 0 4px 12px rgba(234, 67, 53, 0.4);
            }
            .gemini-floating-bar button:active {
                transform: scale(0.96);
            }
        `;
        document.head.appendChild(style);
    }

    // --- 3. App Core ---

    class FloatingBar {
        constructor(store, onDelete) {
            this.store = store;
            this.onDelete = onDelete;
            this.el = null;
        }

        template() {
            const count = this.store.selectedCount;
            const visibleClass = count > 0 ? 'visible' : '';

            return html`
                <div id="gemini-bulk-del-bar" class="gemini-floating-bar ${visibleClass}">
                    <span>${count} selected</span>
                    <button id="gemini-bulk-del-btn">Delete</button>
                </div>
            `;
        }

        mount(container) {
            const wrapper = document.createElement('div');
            wrapper.innerHTML = this.template();
            this.el = wrapper.firstElementChild;
            container.appendChild(this.el);
            this.bindEvents();
        }

        update() {
            if (!this.el) {
                return;
            }

            const count = this.store.selectedCount;
            const countSpan = this.el.querySelector('span');
            if (countSpan) {
                countSpan.textContent = `${count} selected`;
            }

            if (count > 0) {
                this.el.classList.add('visible');
            } else {
                this.el.classList.remove('visible');
            }
        }

        bindEvents() {
            const btn = this.el.querySelector('#gemini-bulk-del-btn');
            if (btn) btn.addEventListener('click', this.onDelete);
        }
    }

    class App {
        constructor() {
            this.store = reactive({ selectedCount: 0 }, () => this.update());
            this.floatingBar = new FloatingBar(this.store, () => this.deleteSelectedItems());
        }

        init() {
            console.log('[Bulk Delete] Vanilla App Init');
            addStyles();
            this.floatingBar.mount(document.body);
            this.initObserver();
            this.injectCheckboxes();
        }

        update() {
            this.floatingBar.update();
        }

        initObserver() {
            const observer = new MutationObserver((mutations) => {
                let shouldUpdate = false;
                for (const m of mutations) {
                    if (m.addedNodes.length > 0) {
                        shouldUpdate = true;
                        break;
                    }
                }
                if (shouldUpdate) this.injectCheckboxes();
            });
            observer.observe(document.body, { childList: true, subtree: true });
        }

        injectCheckboxes() {
            const links = document.querySelectorAll('a[href^="/app/"][data-test-id="conversation"]');
            links.forEach(link => {
                if (link.dataset.bulkDeleteProcessed) return;

                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.className = CHECKBOX_CLASS;

                // Bind events
                checkbox.addEventListener('click', (e) => e.stopPropagation());
                checkbox.addEventListener('change', (e) => this.handleCheckboxChange(e));

                link.insertBefore(checkbox, link.firstChild);
                link.dataset.bulkDeleteProcessed = 'true';
                link.style.display = 'flex';
                link.style.alignItems = 'center';
            });
        }

        handleCheckboxChange(e) {
            const link = e.target.closest('a');
            if (e.target.checked) {
                link.classList.add('gemini-bulk-selected');
            } else {
                link.classList.remove('gemini-bulk-selected');
            }
            this.syncState();
        }

        syncState() {
            this.store.selectedCount = document.querySelectorAll(`.${CHECKBOX_CLASS}:checked`).length;
        }

        async deleteSelectedItems() {
            console.log('[Bulk Delete] Starting deletion...');

            const checkboxes = document.querySelectorAll(`.${CHECKBOX_CLASS}:checked`);
            if (!checkboxes.length) {
                return;
            }

            for (const checkbox of checkboxes) {
                const menuButton = checkbox
                    .closest('div')
                    .querySelector('button[data-test-id="actions-menu-button"]');

                if (menuButton) {
                    await this.deleteItem(menuButton);
                }
            }

            this.syncState();

            console.log('[Bulk Delete] Finished');
        }

        async deleteItem(menuButton) {
            menuButton.click();

            await new Promise(resolve => setTimeout(resolve, 100));

            const deleteOption = document.querySelector('[role="menuitem"][data-test-id="delete-button"]');
            if (!deleteOption) {
                return;
            }

            deleteOption.click();

            await new Promise(resolve => setTimeout(resolve, 100));

            const confirmButton = document.querySelector('button[data-test-id="confirm-button"]');
            if (!confirmButton) {
                return;
            }

            confirmButton.click();
            await new Promise(resolve => setTimeout(resolve, 100));
        }
    }

    // --- Boot ---
    const app = new App();
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        app.init();
    } else {
        window.addEventListener('load', () => app.init());
    }
})();