# 📋 Clipboard Vault (PowerShell 7.5)

## Overview

A powerful clipboard manager for PowerShell 7.5. It captures everything you copy and provides a rich, searchable history through an interactive HTML interface. It's designed to be a "vault" for your clipboard, preventing data loss and making it easy to find previously copied items.

## Functionality

*   **Live Clipboard Monitoring:** Continuously captures text copied to the clipboard.
*   **Interactive HTML Viewer:** Automatically generates a `Clipboard-Viewer.html` file to browse, search, and manage your history.
*   **Intelligent De-duplication:** When you re-copy an item, it's moved to the top of the history instead of creating a duplicate entry.
*   **Powerful Search:** The HTML viewer supports real-time searching of your clipboard history, including regular expressions.
*   **One-Click Copy:** Easily copy any item from the history back to your clipboard.
*   **Database Cleanup:** A feature in the viewer allows you to remove all historical duplicates, keeping only the most recent unique entries.
*   **JSON Storage:** History is stored in a clean, human-readable `clipboard_history.json` file.

## Usage

1.  Ensure `Clipboard-Manager.ps1` and `Clipboard-Functions.ps1` are in the same directory.
2.  Open a PowerShell 7 terminal.
3.  Navigate to the script's directory.
4.  Run the script: `.\Clipboard-Manager.ps1`
5.  The script will start monitoring. To view your history, open the `Logs\Clipboard-Viewer.html` file that is created in the script's directory. The viewer updates as you copy new items.
6.  To stop the script, press `Ctrl+C` in the PowerShell window.

## Requirements

*   PowerShell 7.5 or later
*   Windows operating system

## Code Example

```PowerShell
# Configuration
$LogDir = "$PSScriptRoot\Logs"
$ClipboardLogFile = "$LogDir\clipboard_history.json"
$MaxHistoryItems = 50

# Ensure log directory exists
if (!(Test-Path $LogDir)) { New-Item -ItemType Directory -Path $LogDir | Out-Null }

Write-Host "--- Clipboard Vault Active ---" -ForegroundColor Cyan
Write-Host "Press 'Ctrl+C' in this window to stop monitoring." -ForegroundColor Gray

function Update-HtmlViewer {
    param($History)
    
    $HtmlPath = "$PSScriptRoot\Logs\Clipboard-Viewer.html"
    
    $Header = @"
<!DOCTYPE html>
<html>
<head>
    <title>Clipboard Vault Viewer</title>
    <style>
        body { font-family: 'Segoe UI', sans-serif; background: #1a1a1a; color: #e0e0e0; padding: 20px; }
        .entry { background: #2d2d2d; border-radius: 8px; padding: 15px; margin-bottom: 10px; border: 1px solid #3d3d3d; transition: 0.2s; }
        .entry:hover { border-color: #00ff9d; }
        .meta { font-size: 0.8em; color: #888; margin-bottom: 8px; display: flex; justify-content: space-between; }
        .content { font-family: 'Cascadia Code', monospace; white-space: pre-wrap; background: #121212; padding: 10px; border-radius: 4px; font-size: 0.9em; }
        .copy-btn { background: #00ff9d; color: #1a1a1a; border: none; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-weight: bold; }
        .copy-btn:active { transform: scale(0.95); }
        input { width: 100%; padding: 10px; border-radius: 5px; border: 1px solid #3d3d3d; background: #2d2d2d; color: white; margin-bottom: 20px; }
    </style>
</head>
<body>
    <h2>📋 Clipboard Vault</h2>
    <div style="margin-bottom: 15px; display: flex; align-items: center; gap: 10px;">
        <input type="checkbox" id="hideDupes" style="width: auto; margin: 0;" onchange="filter()">
        <label for="hideDupes">Hide Duplicate Content</label>
    </div>
    <button class="copy-btn" style="background: #ff4d4d;" onclick="requestCleanup()">🗑️ DELETE DUPLICATES</button>
    <input type="text" id="search" placeholder="Search clips (Regex supported)..." onkeyup="filter()">
    <div id="container">
"@

    $Body = ""
    foreach ($Item in $History) {
        $EscapedContent = [System.Web.HttpUtility]::HtmlEncode($Item.Content)
        $Body += @"
        <div class="entry" data-hash="$($Item.Hash)">
            <div class="meta">
                <span>$($Item.Timestamp)</span>
                <button class="copy-btn" onclick="copyToClipboard(this)">COPY</button>
            </div>
            <div class="content">$EscapedContent</div>
        </div>
"@
    }

    $Footer = @"
    </div>
    <script>
        function requestCleanup() {
            // We create a tiny "flag" file that the PowerShell script will see
            // This is a clever way for a browser to 'talk' to a local script
            const blob = new Blob(['cleanup'], {type: 'text/plain'});
            const anchor = document.createElement('a');
            anchor.download = 'cleanup.req'; // The extension the script looks for
            anchor.href = window.URL.createObjectURL(blob);
            anchor.click();
            alert('Cleanup request sent! PowerShell will process this in a few seconds.');
        }
    </script>
    <script>
        function copyToClipboard(btn) {
            const text = btn.parentElement.nextElementSibling.innerText;
            navigator.clipboard.writeText(text);
            const original = btn.innerText;
            btn.innerText = '✅ SAVED';
            setTimeout(() => btn.innerText = original, 1000);
        }

        function filter() {
            const query = document.getElementById('search').value;
            const hideDupes = document.getElementById('hideDupes').checked;
            const entries = document.querySelectorAll('.entry');
            const regex = new RegExp(query, 'i');
            const seenHashes = new Set();
            
            entries.forEach(entry => {
                const text = entry.querySelector('.content').innerText;
                const hash = entry.getAttribute('data-hash');
                
                let isMatch = regex.test(text);
                let isDuplicate = seenHashes.has(hash);
                
                // Mark hash as seen
                seenHashes.add(hash);

                // Logic: Show if it matches search AND (if hideDupes is on, it must not be a duplicate)
                if (isMatch && (!hideDupes || !isDuplicate)) {
                    entry.style.display = 'block';
                } else {
                    entry.style.display = 'none';
                }
            });
        }
    </script>
</body>
</html>
"@

    $Header + $Body + $Footer | Out-File $HtmlPath -Force
}

$LastContent = ""

# The "Engine"
try {
    while ($true) {
        # Fetch clipboard text (PS 7.5 compatible)
        $CurrentContent = Get-Clipboard -Raw -ErrorAction SilentlyContinue

        # Inside your while ($true) loop, at the very beginning:

        $ReqFile = "$LogDir\cleanup.req"
        if (Test-Path $ReqFile) {
            Write-Host "🧹 Cleanup Requested! Removing duplicates..." -ForegroundColor Yellow
            
            if (Test-Path $ClipboardLogFile) {
                $CurrentHistory = Get-Content $ClipboardLogFile | ConvertFrom-Json
                
                # This magic line keeps only the newest unique hash
                $CleanedHistory = $CurrentHistory | Group-Object Hash | ForEach-Object { $_.Group | Select-Object -First 1 }
                
                # Sort back by time (Newest first)
                $CleanedHistory = $CleanedHistory | Sort-Object Timestamp -Descending
                
                # Save and Update
                $CleanedHistory | ConvertTo-Json | Out-File $ClipboardLogFile -Force
                Update-HtmlViewer -History $CleanedHistory
            }
            
            Remove-Item $ReqFile -ErrorAction SilentlyContinue
            Write-Host "✨ Database Cleaned." -ForegroundColor Green
        }

        # Inside your while($true) loop:
        if ($null -ne $CurrentContent -and $CurrentContent -ne $LastContent) {
            $LastContent = $CurrentContent
            
            $History = if (Test-Path $ClipboardLogFile) { Get-Content $ClipboardLogFile | ConvertFrom-Json } else { @() }

            # Create a unique ID based on the content so we can identify duplicates
            $ContentHash = [Convert]::ToBase64String([System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes($CurrentContent)))

            # Remove the old version if it exists (so it "jumps" to the top instead of duplicating)
            $History = $History | Where-Object { $_.Hash -ne $ContentHash }

            $NewEntry = [PSCustomObject]@{
                Timestamp = (Get-Date).ToString("HH:mm:ss")
                Hash      = $ContentHash  # Add this!
                Content   = $CurrentContent.Trim()
            }

            $History = ,$NewEntry + $History | Select-Object -First $MaxHistoryItems
            $History | ConvertTo-Json | Out-File $ClipboardLogFile -Force
            Update-HtmlViewer -History $History
        }

        # Check if it's new, not null, and not just empty whitespace
        if ($null -ne $CurrentContent -and $CurrentContent -ne $LastContent -and !([string]::IsNullOrWhiteSpace($CurrentContent))) {
            
            $LastContent = $CurrentContent
            
            # Load existing history
            $History = if (Test-Path $ClipboardLogFile) { 
                Get-Content $ClipboardLogFile | ConvertFrom-Json 
            } else { @() }

            # Create a new Entry
            $NewEntry = [PSCustomObject]@{
                Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
                Content   = $CurrentContent.Trim()
            }

            # Add to history (Newest at top)
            $History = ,$NewEntry + $History | Select-Object -First $MaxHistoryItems

            # Save to JSON
            $History | ConvertTo-Json -Depth 3 | Out-File $ClipboardLogFile -Force

            # Update HTML Viewer
            Update-HtmlViewer -History $History

            # Visual Feedback
            $Display = if ($CurrentContent.Length -gt 40) { $CurrentContent.Substring(0,40).Trim() + "..." } else { $CurrentContent.Trim() }
            Write-Host "[$(Get-Date -Format HH:mm:ss)] 📋 Captured: $Display" -ForegroundColor Green
        }

        Start-Sleep -Milliseconds 500
    }
}
catch {
    Write-Error "Vault Error: $($_.Exception.Message)"
}
finally {
    Write-Host "`nVault Closed." -ForegroundColor Yellow
}
```