Contextual Clipboard Assistant

Adds a floating toolbar with clipboard-related actions like copy, cut, paste, and save to file. The script integrates multilingual support (English, German, Spanish, French, Italian, Japanese, Chinese, Russian).

// ==UserScript==
// @name           Contextual Clipboard Assistant
// @name:de        Kontextueller Zwischenablage-Assistent
// @name:es        Asistente de Portapapeles Contextual
// @name:fr        Assistant Presse-papiers Contextuel
// @name:it        Assistente Appunti Contestuale
// @name:ja        コンテキストクリップボードアシスタント
// @name:zh-CN     上下文剪贴板助手
// @name:ru        Контекстный Помощник Буфера Обмена
// @description    Adds a floating toolbar with clipboard-related actions like copy, cut, paste, and save to file. The script integrates multilingual support (English, German, Spanish, French, Italian, Japanese, Chinese, Russian).
// @description:de Fügt eine schwebende Symbolleiste mit Zwischenablageaktionen wie Kopieren, Ausschneiden, Einfügen und Speichern hinzu. Das Skript bietet mehrsprachige Unterstützung (Englisch, Deutsch, Spanisch, Französisch, Italienisch, Japanisch, Chinesisch, Russisch).
// @description:es Agrega una barra de herramientas flotante con acciones relacionadas con el portapapeles como copiar, cortar, pegar y guardar en un archivo. El script integra soporte multilingüe (inglés, alemán, español, francés, italiano, japonés, chino, ruso).
// @description:fr Ajoute une barre d'outils flottante avec des actions liées au presse-papiers comme copier, couper, coller et enregistrer dans un fichier. Le script intègre un support multilingue (anglais, allemand, espagnol, français, italien, japonais, chinois, russe).
// @description:it Aggiunge una barra degli strumenti flottante con azioni relative agli appunti come copia, taglia, incolla e salva su file. Lo script integra il supporto multilingue (inglese, tedesco, spagnolo, francese, italiano, giapponese, cinese, russo).
// @description:ja コピー、切り取り、貼り付け、ファイルへの保存など、クリップボード関連のアクションを提供する浮動ツールバーを追加します。スクリプトは多言語サポート(英語、ドイツ語、スペイン語、フランス語、イタリア語、日本語、中国語、ロシア語)を統合しています。
// @description:zh-CN 添加一个浮动工具栏,具有与剪贴板相关的操作,例如复制、剪切、粘贴和保存到文件。脚本集成了多语言支持(英语、德语、西班牙语、法语、意大利语、日语、中文、俄语)。
// @description:ru Добавляет плавающую панель инструментов с действиями, связанными с буфером обмена, такими как копирование, вырезание, вставка и сохранение в файл. Скрипт включает поддержку нескольких языков (английский, немецкий, испанский, французский, итальянский, японский, китайский, русский).
// @icon           https://cdn-icons-png.flaticon.com/128/5435/5435494.png
// @namespace      http://tampermonkey.net/
// @version        2024.12.27
// @author         Copiis
// @license        MIT
// @match          *://*/*
// @grant          GM_addStyle
// @grant          GM_getValue
// @grant          GM_setValue
// @grant          GM_notification
// @grant          GM_registerMenuCommand
// @grant          GM_unregisterMenuCommand
// @grant          clipboardRead
// @grant          clipboardWrite
// @grant          unsafeWindow
// @run-at         document-end
// ==/UserScript==

(function () {
    'use strict';

    let insertMode = false; // Flag to track if we are in "Insert" mode

    const translations = {
        de: { insertInstruction: "Klicken oder markieren Sie Text, wo der Inhalt der Zwischenablage eingefügt werden soll.", fileSaved: "Datei gespeichert als:" },
        en: { insertInstruction: "Click or select text where you want to paste the clipboard content.", fileSaved: "File saved as:" },
        es: { insertInstruction: "Haga clic o seleccione el texto donde desea pegar el contenido del portapapeles.", fileSaved: "Archivo guardado como:" },
        fr: { insertInstruction: "Cliquez ou sélectionnez le texte où vous souhaitez coller le contenu du presse-papiers.", fileSaved: "Fichier enregistré sous :" },
        it: { insertInstruction: "Fare clic o selezionare il testo in cui incollare il contenuto degli appunti.", fileSaved: "File salvato come:" },
        zh: { insertInstruction: "单击或选择要粘贴剪贴板内容的文本。", fileSaved: "文件已保存为:" },
        ja: { insertInstruction: "クリップボードの内容を貼り付けたい場所をクリックまたは選択します。", fileSaved: "ファイルとして保存されました:" },
        ru: { insertInstruction: "Щелкните или выберите текст, куда нужно вставить содержимое буфера обмена.", fileSaved: "Файл сохранен как:" },
        default: { insertInstruction: "Click or select text where you want to paste the clipboard content.", fileSaved: "File saved as:" }
    };

    const browserLanguage = navigator.language.slice(0, 2);
    const messages = translations[browserLanguage] || translations.default;

    const popup = document.createElement('div');
    popup.style.position = 'absolute';
    popup.style.backgroundColor = '#282c34';
    popup.style.color = '#61dafb';
    popup.style.border = '2px solid #20232a';
    popup.style.boxShadow = '0 4px 6px rgba(0,0,0,0.3)';
    popup.style.padding = '10px';
    popup.style.borderRadius = '10px';
    popup.style.display = 'none';
    popup.style.zIndex = '10000';
    popup.style.fontSize = '14px';
    popup.style.textAlign = 'center';
    popup.style.minWidth = '200px';

    const feedback = document.createElement('div');
    feedback.style.color = '#76c7c0';
    feedback.style.fontSize = '12px';
    feedback.style.marginBottom = '5px';
    feedback.style.maxWidth = '200px';
    feedback.style.wordWrap = 'break-word';
    feedback.style.textAlign = 'center';
    feedback.style.height = 'auto';
    popup.appendChild(feedback);

    const actions = [
        { symbol: '📋', action: copyText, title: 'Copy' },
        { symbol: '✂️', action: cutText, title: 'Cut' },
        { symbol: '📥', action: activateInsertMode, title: 'Paste' },
        { symbol: '💾', action: saveToFile, title: 'Save' },
        { symbol: '📝', action: formatMarkdown, title: 'Format (Markdown)' }
    ];

    actions.forEach(({ symbol, action, title }) => {
        const button = document.createElement('button');
        button.innerHTML = symbol;
        button.title = title;
        button.style.display = 'inline-block';
        button.style.cursor = 'pointer';
        button.style.backgroundColor = '#20232a';
        button.style.color = '#61dafb';
        button.style.border = '1px solid #444';
        button.style.borderRadius = '3px';
        button.style.padding = '5px';
        button.style.margin = '3px';
        button.style.fontSize = '18px';

        button.addEventListener('click', () => {
            action();
            button.style.backgroundColor = '#444';
            setTimeout(() => {
                button.style.backgroundColor = '#20232a';
            }, 200);
        });

        popup.appendChild(button);
    });

    document.body.appendChild(popup);

    document.addEventListener('mouseup', (event) => {
        const selectedText = window.getSelection().toString();
        const elementsUnderCursor = document.elementsFromPoint(event.clientX, event.clientY);
        const isTextFreeArea = elementsUnderCursor.every((el) => {
            const computedStyle = window.getComputedStyle(el);
            return el.nodeType !== Node.TEXT_NODE && computedStyle.pointerEvents === 'none';
        });

        if (selectedText.trim() || isTextFreeArea) {
            positionPopup(event.pageX, event.pageY);
            popup.style.display = 'block';
        }
    });

    popup.addEventListener('mousedown', (event) => {
        event.stopPropagation();
    });

    document.addEventListener('mousedown', (event) => {
        if (!popup.contains(event.target) && !insertMode) {
            hidePopup();
        }
    });

    document.addEventListener('keydown', () => {
        if (!insertMode) {
            hidePopup();
        }
    });

    function activateInsertMode() {
        insertMode = true;
        document.body.style.cursor = 'crosshair';
        showFeedback(messages.insertInstruction);
    }

    document.addEventListener('click', (event) => {
        if (!insertMode) return;

        if (popup.contains(event.target)) return;

        navigator.clipboard.readText().then((clipboardText) => {
            if (!clipboardText) {
                showFeedback('Clipboard is empty.');
                return;
            }

            const selection = window.getSelection();
            const range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null;

            if (range && !range.collapsed) {
                range.deleteContents();
                range.insertNode(document.createTextNode(clipboardText));
                showFeedback('Replaced!');
            } else {
                const range = document.caretRangeFromPoint(event.clientX, event.clientY);
                if (range) {
                    range.insertNode(document.createTextNode(clipboardText));
                    showFeedback('Inserted!');
                } else {
                    showFeedback('Cannot insert here.');
                }
            }

            insertMode = false;
            document.body.style.cursor = '';
            hidePopup();
        }).catch(() => {
            showFeedback('Error reading clipboard.');
            insertMode = false;
            document.body.style.cursor = '';
        });
    });

    function positionPopup(x, y) {
        const popupWidth = popup.offsetWidth || 150;
        const popupHeight = popup.offsetHeight || 50;
        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;

        let adjustedX = x;
        let adjustedY = y;

        if (x + popupWidth > viewportWidth) {
            adjustedX = viewportWidth - popupWidth - 10;
        }
        if (y + popupHeight > viewportHeight) {
            adjustedY = viewportHeight - popupHeight - 10;
        }

        popup.style.left = `${adjustedX}px`;
        popup.style.top = `${adjustedY}px`;
    }

    function showFeedback(message) {
        feedback.textContent = message;
        feedback.style.opacity = '1';
        setTimeout(() => {
            feedback.style.opacity = '0';
        }, 2000);
    }

    function copyText() {
        const selectedText = window.getSelection().toString();
        if (selectedText.trim()) {
            navigator.clipboard.writeText(selectedText).then(() => {
                showFeedback('Copied!');
            });
        }
    }

    function cutText() {
        const selectedText = window.getSelection().toString();
        if (selectedText.trim()) {
            navigator.clipboard.writeText(selectedText).then(() => {
                document.execCommand('delete');
                showFeedback('Cut!');
            });
        }
    }

    function saveToFile() {
        const timestamp = new Date().toISOString().replace(/[:.-]/g, '_');
        const fileName = `${timestamp}.txt`;

        const selectedText = window.getSelection().toString();
        const content = selectedText.trim() ? selectedText : 'No content selected.';

        const blob = new Blob([content], { type: 'text/plain' });
        const a = document.createElement('a');
        a.href = URL.createObjectURL(blob);
        a.download = fileName;
        a.click();
        URL.revokeObjectURL(a.href);

        showFeedback(`${messages.fileSaved} ${fileName}`);
    }

    function formatMarkdown() {
        const selectedText = window.getSelection().toString();
        if (!selectedText.trim()) {
            showFeedback('No text selected for formatting.');
            return;
        }
        const formattedText = `**${selectedText}**`; // Example Markdown formatting
        navigator.clipboard.writeText(formattedText).then(() => {
            showFeedback('Formatted as Markdown and copied!');
        });
    }

    function hidePopup() {
        popup.style.display = 'none';
    }
})();