A script that allows using the delete key to delete a journal entry in rabbit hole with multiple selection support.

I wrote this script so one can press the delete key on the keyboard to delete the note currently selected in the rabbit hole journal.

How to use

It can be pasted into console or you can search this up on greasyfork.
First, browse to hole.rabbit.tech/journal, then select a note

  • Select a note
    And use the delete key on the keyboard to delete the note

  • Press delete key to delete the selected note

  • Multiple selections can be made by holding down the ctrl key.

// ==UserScript==
// @name         Bind Delete Key to Delete in Rabbit Hole with multiple selection support
// @namespace    http://tampermonkey.net/
// @license      MIT
// @version      2.1
// @description  Binds the Delete key to trigger delete in rabbit hole journal backend. Multiple selection is made with ctrl key.
// @author       Rekt
// @match        *://hole.rabbit.tech/*
// @grant        none
// ==/UserScript==
(function() {
    'use strict';

    let selectedItems = new Set();
    let accessToken = null;

    // Hook into fetch to capture access token
    const originalFetch = window.fetch;
    window.fetch = async function(...args) {
        const response = await originalFetch(...args);
        if (args[1] && args[1].body) {
            try {
                const body = JSON.parse(args[1].body);
                if (body.accessToken) {
                    accessToken = body.accessToken;
                }
            } catch (error) {
                console.error("Error parsing request body:", error);
            }
        }
        return response;
    };

    // Function to delete entries using HTTP deletion and remove placeholders
    async function deleteEntries(entryIds) {
        if (!accessToken) {
            console.error('Access token not available');
            return;
        }

        for (let entryId of entryIds) {
            try {
                const payload = { accessToken, entryId, deleteEntry: true };

                const response = await fetch('/apis/updateJournalEntry', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Authorization': `Bearer ${accessToken}`
                    },
                    body: JSON.stringify(payload)
                });

                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }

                // Remove placeholder from the page
                const element = document.querySelector(`[data-entry-id="${entryId}"]`);
                if (element) {
                    element.remove();
                }
            } catch (error) {
                console.error(`Error deleting entry ${entryId}:`, error);
            }
        }
    }

    // Function to handle the delete key
    function handleDeleteKey(event) {
        // Prevent deletion if the user is focused on a contenteditable element
        const focusedElement = document.activeElement;
        if (focusedElement.hasAttribute('contenteditable')) {
            return;
        }

        // If delete key is pressed and there are selected items, delete them
        if (event.key === 'Delete') {
            const entryIds = Array.from(selectedItems);
            if (entryIds.length > 0) {
                deleteEntries(entryIds);
                selectedItems.clear();
            } else {
                // Delete single selected item without Ctrl
                const target = document.querySelector('li[data-entry-id].selected');
                if (target) {
                    const entryId = target.getAttribute('data-entry-id');
                    deleteEntries([entryId]);
                    target.classList.remove('selected');
                }
            }
            event.preventDefault(); // Prevent default behavior if necessary
        }
    }

    // Function to handle selection with Ctrl key
    function handleSelection(event) {
        // Check if the clicked element is a list item with a data-entry-id
        const target = event.target.closest('li[data-entry-id]');
        if (!target) return;

        const entryId = target.getAttribute('data-entry-id');

        // If Ctrl is pressed, allow multiple selection and toggle current selection state
        if (event.ctrlKey) {
            if (selectedItems.has(entryId)) {
                selectedItems.delete(entryId);
                target.classList.remove('selected');
            } else {
                selectedItems.add(entryId);
                target.classList.add('selected');
            }
        } else { 
            // Single selection without Ctrl, clear previous selections and select current item
            selectedItems.forEach(id => {
                const element = document.querySelector(`[data-entry-id="${id}"]`);
                if (element) element.classList.remove('selected');
            });
            selectedItems.clear();
            selectedItems.add(entryId);

            // Do not apply styling for single selection
        }

        // Apply styling only for multiple selections
        selectedItems.forEach(id => {
            const element = document.querySelector(`[data-entry-id="${id}"]`);
            if (element && selectedItems.size > 1) element.classList.add('selected');
        });
    }

    // Add event listeners
    document.addEventListener('keydown', handleDeleteKey);
    document.addEventListener('click', handleSelection);

    // Add CSS for the selected class to highlight the selected items
    const style = document.createElement('style');
    style.innerHTML = `
        .selected {
            background-color: yellow;
        }
    `;
    document.head.appendChild(style);

})();
3 Likes