Quick search - Ctrl+Shift+F - 2.3

Horizontal floating search window with paste & search functionality

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Quick search - Ctrl+Shift+F - 2.3
// @namespace    http://tampermonkey.net/
// @version      2.3
// @description  Horizontal floating search window with paste & search functionality
// @author       </j0tsarup>
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_addElement
// @run-at       document-start
// ==/UserScript==

/*
 * 
 * • Keyboard shortcuts: Ctrl+Shift+F (open), ESC (close)

 * FEATURES:
 * • Floating search window with drag-to-move functionality
 * • Paste & search directly from clipboard
 * • Multiple search engines: Google, Bing, DuckDuckGo, Yahoo
 * • Optional location-based search
 * • Toggle bubble for quick access
 * • Clean, modern UI with smooth animations
 * 
 * CHANGELOG v2.3:
 * • Added version number to script name
 * • Added formatted header with feature list
 * • Updated author information
 * • Improved code documentation
 * 
 * ═══════════════════════════════════════════════════════════════════════════
 */

(function() {
    'use strict';

    // Wait for document to be ready
    function init() {
        // Add CSS styles with higher priority
        GM_addStyle(`
        #floatingSearchWindow {
            position: fixed !important;
            top: 50% !important;
            left: 50% !important;
            transform: translate(-50%, -50%) !important;
            width: 900px !important;
            max-width: 90vw !important;
            background: #ffffff !important;
            border-radius: 12px !important;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15) !important;
            z-index: 2147483647 !important;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif !important;
            display: none !important;
            padding: 20px !important;
        }

        #floatingSearchWindow.active {
            display: block !important;
        }

        .search-header {
            display: flex !important;
            justify-content: space-between !important;
            align-items: center !important;
            margin-bottom: 16px !important;
            padding-bottom: 12px !important;
            border-bottom: 1px solid #e5e5e5 !important;
        }

        .search-title {
            font-size: 14px !important;
            font-weight: 500 !important;
            color: #000000 !important;
            margin: 0 !important;
        }

        .header-controls {
            display: flex !important;
            gap: 8px !important;
            align-items: center !important;
        }

        .bubble-toggle {
            font-size: 11px !important;
            padding: 6px 12px !important;
            background: #f5f5f5 !important;
            border: 1px solid #ddd !important;
            border-radius: 6px !important;
            cursor: pointer !important;
            color: #000000 !important;
            transition: all 0.2s !important;
        }

        .bubble-toggle:hover {
            background: #e8e8e8 !important;
        }

        .bubble-toggle.enabled {
            background: #000000 !important;
            color: #ffffff !important;
            border-color: #000000 !important;
        }

        .close-btn {
            background: none !important;
            border: none !important;
            font-size: 20px !important;
            color: #666 !important;
            cursor: pointer !important;
            padding: 0 !important;
            width: 24px !important;
            height: 24px !important;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            border-radius: 4px !important;
            transition: all 0.2s !important;
        }

        .close-btn:hover {
            background: #f0f0f0 !important;
            color: #000 !important;
        }

        .search-main {
            display: flex !important;
            gap: 12px !important;
            align-items: stretch !important;
            margin-bottom: 16px !important;
        }

        .search-input-wrapper {
            flex: 1 !important;
            position: relative !important;
        }

        .search-input {
            width: 100% !important;
            padding: 14px 16px !important;
            border: 2px solid #e5e5e5 !important;
            border-radius: 8px !important;
            font-size: 14px !important;
            transition: all 0.2s !important;
            box-sizing: border-box !important;
            font-family: inherit !important;
            color: #000000 !important;
        }

        .search-input::placeholder {
            color: #999 !important;
        }

        .search-input:focus {
            outline: none !important;
            border-color: #000000 !important;
        }

        .search-btn {
            padding: 14px 28px !important;
            background: #000000 !important;
            color: #ffffff !important;
            border: none !important;
            border-radius: 8px !important;
            font-size: 14px !important;
            font-weight: 500 !important;
            cursor: pointer !important;
            transition: all 0.2s !important;
            white-space: nowrap !important;
        }

        .search-btn:hover {
            background: #333333 !important;
        }

        .paste-search-btn {
            padding: 14px 24px !important;
            background: #f5f5f5 !important;
            color: #000000 !important;
            border: 2px solid #e5e5e5 !important;
            border-radius: 8px !important;
            font-size: 14px !important;
            font-weight: 500 !important;
            cursor: pointer !important;
            transition: all 0.2s !important;
            white-space: nowrap !important;
        }

        .paste-search-btn:hover {
            background: #e8e8e8 !important;
            border-color: #000000 !important;
        }

        .search-options {
            display: flex !important;
            gap: 8px !important;
            flex-wrap: wrap !important;
        }

        .option-group {
            display: flex !important;
            gap: 6px !important;
        }

        .option-btn {
            padding: 8px 16px !important;
            background: #f5f5f5 !important;
            color: #000000 !important;
            border: 2px solid #e5e5e5 !important;
            border-radius: 6px !important;
            font-size: 13px !important;
            cursor: pointer !important;
            transition: all 0.2s !important;
            font-weight: 500 !important;
        }

        .option-btn:hover {
            background: #e8e8e8 !important;
        }

        .option-btn.active {
            background: #000000 !important;
            color: #ffffff !important;
            border-color: #000000 !important;
        }

        .location-input {
            padding: 8px 14px !important;
            border: 2px solid #e5e5e5 !important;
            border-radius: 6px !important;
            font-size: 13px !important;
            font-family: inherit !important;
            color: #000000 !important;
            min-width: 150px !important;
        }

        .location-input::placeholder {
            color: #999 !important;
        }

        .location-input:focus {
            outline: none !important;
            border-color: #000000 !important;
        }

        .toggle-bubble {
            position: fixed !important;
            bottom: 30px !important;
            right: 30px !important;
            width: 56px !important;
            height: 56px !important;
            background: #000000 !important;
            border: none !important;
            border-radius: 50% !important;
            color: white !important;
            font-size: 20px !important;
            cursor: pointer !important;
            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2) !important;
            z-index: 2147483646 !important;
            transition: all 0.3s !important;
            display: none !important;
        }

        .toggle-bubble.visible {
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
        }

        .toggle-bubble:hover {
            transform: scale(1.1) !important;
            box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3) !important;
        }

        .draggable-handle {
            position: absolute !important;
            top: 0 !important;
            left: 0 !important;
            right: 0 !important;
            height: 12px !important;
            cursor: move !important;
        }
    `);

    // Create the floating window HTML
    const searchWindow = document.createElement('div');
    searchWindow.id = 'floatingSearchWindow';
    searchWindow.innerHTML = `
        <div class="draggable-handle"></div>
        <div class="search-header">
            <h2 class="search-title">rumpsy's quick search</h2>
            <div class="header-controls">
                <button class="bubble-toggle" id="bubbleToggle">bubble: off</button>
                <button class="close-btn" id="closeSearch">×</button>
            </div>
        </div>
        <div class="search-main">
            <div class="search-input-wrapper">
                <input type="text" class="search-input" id="searchQuery" placeholder="search query">
            </div>
            <button class="search-btn" id="executeSearch">search</button>
            <button class="paste-search-btn" id="pasteSearch">paste & search</button>
        </div>
        <div class="search-options">
            <div class="option-group">
                <button class="option-btn active" data-engine="google">google</button>
                <button class="option-btn" data-engine="bing">bing</button>
                <button class="option-btn" data-engine="duckduckgo">duckduckgo</button>
                <button class="option-btn" data-engine="yahoo">yahoo</button>
            </div>
            <input type="text" class="location-input" id="searchLocation" placeholder="location (optional)">
        </div>
    `;

    // Create toggle bubble
    const toggleBubble = document.createElement('button');
    toggleBubble.className = 'toggle-bubble';
    toggleBubble.innerHTML = '🔍';
    toggleBubble.title = 'Open Search (Ctrl+Shift+F)';

    // Append to body
    if (document.body) {
        document.body.appendChild(searchWindow);
        document.body.appendChild(toggleBubble);
    } else {
        // Wait for body to be available
        const observer = new MutationObserver(() => {
            if (document.body) {
                document.body.appendChild(searchWindow);
                document.body.appendChild(toggleBubble);
                observer.disconnect();
            }
        });
        observer.observe(document.documentElement, { childList: true });
    }

    // State
    let selectedEngine = 'google';
    let bubbleEnabled = false;

    // Toggle window visibility
    function toggleWindow() {
        searchWindow.classList.toggle('active');
        if (searchWindow.classList.contains('active')) {
            document.getElementById('searchQuery').focus();
        }
    }

    toggleBubble.addEventListener('click', toggleWindow);

    // Close button
    document.getElementById('closeSearch').addEventListener('click', () => {
        searchWindow.classList.remove('active');
    });

    // Bubble toggle
    document.getElementById('bubbleToggle').addEventListener('click', () => {
        bubbleEnabled = !bubbleEnabled;
        const bubbleBtn = document.getElementById('bubbleToggle');
        if (bubbleEnabled) {
            toggleBubble.classList.add('visible');
            bubbleBtn.classList.add('enabled');
            bubbleBtn.textContent = 'bubble: on';
        } else {
            toggleBubble.classList.remove('visible');
            bubbleBtn.classList.remove('enabled');
            bubbleBtn.textContent = 'bubble: off';
        }
    });

    // Engine selection
    document.querySelectorAll('.option-btn[data-engine]').forEach(btn => {
        btn.addEventListener('click', () => {
            document.querySelectorAll('.option-btn[data-engine]').forEach(b => b.classList.remove('active'));
            btn.classList.add('active');
            selectedEngine = btn.dataset.engine;
        });
    });

    // Make window draggable
    let isDragging = false;
    let currentX;
    let currentY;
    let initialX;
    let initialY;

    const dragHandle = searchWindow.querySelector('.draggable-handle');

    dragHandle.addEventListener('mousedown', (e) => {
        isDragging = true;
        initialX = e.clientX - searchWindow.offsetLeft;
        initialY = e.clientY - searchWindow.offsetTop;
    });

    document.addEventListener('mousemove', (e) => {
        if (isDragging) {
            e.preventDefault();
            currentX = e.clientX - initialX;
            currentY = e.clientY - initialY;
            searchWindow.style.transform = 'none';
            searchWindow.style.left = currentX + 'px';
            searchWindow.style.top = currentY + 'px';
        }
    });

    document.addEventListener('mouseup', () => {
        isDragging = false;
    });

    // Search functionality
    function performSearch(query = null) {
        const searchQuery = query || document.getElementById('searchQuery').value.trim();
        const location = document.getElementById('searchLocation').value.trim();

        if (!searchQuery) {
            alert('Please enter a search query');
            return;
        }

        let fullQuery = searchQuery;
        if (location) {
            fullQuery += ' ' + location;
        }

        const encodedQuery = encodeURIComponent(fullQuery);
        let searchURL = '';

        switch(selectedEngine) {
            case 'google':
                searchURL = `https://www.google.com/search?q=${encodedQuery}`;
                break;
            case 'bing':
                searchURL = `https://www.bing.com/search?q=${encodedQuery}`;
                break;
            case 'duckduckgo':
                searchURL = `https://duckduckgo.com/?q=${encodedQuery}`;
                break;
            case 'yahoo':
                searchURL = `https://search.yahoo.com/search?p=${encodedQuery}`;
                break;
        }

        window.open(searchURL, '_blank');
    }

    document.getElementById('executeSearch').addEventListener('click', () => performSearch());

    document.getElementById('searchQuery').addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            performSearch();
        }
    });

    // Paste and search functionality
    document.getElementById('pasteSearch').addEventListener('click', async () => {
        try {
            const text = await navigator.clipboard.readText();
            if (text) {
                performSearch(text);
            } else {
                alert('Clipboard is empty');
            }
        } catch (err) {
            alert('Unable to read clipboard. Please paste manually.');
        }
    });

    // Keyboard shortcut: Ctrl+Shift+F to toggle
    document.addEventListener('keydown', (e) => {
        if (e.ctrlKey && e.shiftKey && e.key === 'F') {
            e.preventDefault();
            toggleWindow();
        }
        // ESC to close
        if (e.key === 'Escape' && searchWindow.classList.contains('active')) {
            searchWindow.classList.remove('active');
        }
    });

    // Close window when clicking outside
    searchWindow.addEventListener('click', (e) => {
        if (e.target === searchWindow) {
            searchWindow.classList.remove('active');
        }
    });

}

// Initialize when DOM is ready
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
} else {
    init();
}

})();