From 97d166f0c7b9d50368113a56cc54ff643111050c Mon Sep 17 00:00:00 2001 From: Watson Date: Sat, 12 Apr 2025 21:21:40 -0600 Subject: [PATCH 1/7] Update proc.html Added search bar for process list. --- Fermion/pages/proc.html | 53 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/Fermion/pages/proc.html b/Fermion/pages/proc.html index 94a6be8..e411748 100644 --- a/Fermion/pages/proc.html +++ b/Fermion/pages/proc.html @@ -192,6 +192,10 @@
+
+ + +
@@ -332,6 +336,10 @@ function(th, td, tablesort) { return parseInt(td.text()); }); + + // Initialize search functionality after table is populated + setupProcessSearch(); + }).catch((err) => { $("#ProcSet").remove(); var node = document.createElement("p"); @@ -375,7 +383,50 @@ document.getElementById("CloseDevice").onclick = function () { window.close(); } + + // Process search functionality + function setupProcessSearch() { + const searchInput = document.getElementById('procSearch'); + const clearButton = document.getElementById('clearSearch'); + + // Function to filter the table rows based on search input + function filterTable() { + const filter = searchInput.value.toLowerCase(); + const table = document.getElementById('ProcSet'); + const rows = table.getElementsByTagName('tr'); + + // Skip the header row (index 0) + for (let i = 1; i < rows.length; i++) { + const pidCell = rows[i].cells[2]; // PID column + const nameCell = rows[i].cells[4]; // Process name column + + if (pidCell && nameCell) { + const pidText = pidCell.textContent || pidCell.innerText; + const nameText = nameCell.textContent || nameCell.innerText; + + // Show row if PID or process name contains the search string + if (pidText.includes(filter) || nameText.toLowerCase().includes(filter)) { + rows[i].style.display = ''; + } else { + rows[i].style.display = 'none'; + } + } + } + } + + // Add event listeners + searchInput.addEventListener('keyup', filterTable); + + // Clear search and reset table + clearButton.addEventListener('click', function() { + searchInput.value = ''; + filterTable(); + searchInput.focus(); + }); + } + + - \ No newline at end of file + From e8932ab63dbf7797323cee095c98b160dc718363 Mon Sep 17 00:00:00 2001 From: Watson Date: Sun, 13 Apr 2025 22:10:35 +0000 Subject: [PATCH 2/7] Enhancement to the Fermion user interface. --- Fermion/pages/index.html | 611 ++++++++++++++++++------------ Fermion/src/custom.css | 409 ++++++++++++++++++-- Fermion/src/renderer.js | 796 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 1546 insertions(+), 270 deletions(-) diff --git a/Fermion/pages/index.html b/Fermion/pages/index.html index f3e3d52..cb13ba9 100644 --- a/Fermion/pages/index.html +++ b/Fermion/pages/index.html @@ -1,177 +1,301 @@ - - - - - - - -
-
-
-
-
-
-
+ + + @@ -182,103 +306,104 @@

Frida Tools

- + + \ No newline at end of file diff --git a/Fermion/src/custom.css b/Fermion/src/custom.css index 1042db2..2e1eaa4 100644 --- a/Fermion/src/custom.css +++ b/Fermion/src/custom.css @@ -2,18 +2,23 @@ background-color: #8a8a8a; background-repeat: no-repeat; background-position: calc(50% - 7rem); -} - -.gutter.gutter-vertical { + } + + .gutter.gutter-vertical { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII='); cursor: row-resize; -} - -.MonacoEditorRow { - height: calc(65vh - 76px); -} - -textarea { + } + + .gutter.gutter-horizontal { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); + cursor: col-resize; + } + + .MonacoEditorRow { + height: calc(65vh - 76px); + } + + textarea { border: none; overflow: auto; outline: none; @@ -21,23 +26,381 @@ textarea { -moz-box-shadow: none; box-shadow: none; resize: none !important; -} - -textarea#FridaOut { - height:100%; - width: calc(100vw - 12rem); - overflow:visible; + } + + textarea#FridaOut { + width: 100%; + /* Take full width of container */ + height: 100%; + overflow: visible; border: 1px solid transparent; color: #ffffff; font-family: monospace; font-size: 0.8em; line-height: 1.7em; background-color: #423636; -} - -textarea#FridaOut::-moz-selection { color: white; background: rgb(194, 194, 194); } -textarea#FridaOut::selection { color: white; background: rgb(194, 194, 194); } - -.ui.toggle.checkbox input:checked ~ label:before { + padding: 8px; + box-sizing: border-box; + } + + textarea#FridaOut::-moz-selection { + color: white; + background: rgb(194, 194, 194); + } + + textarea#FridaOut::selection { + color: white; + background: rgb(194, 194, 194); + } + + .ui.toggle.checkbox input:checked~label:before { background-color: #424d5c !important; -} \ No newline at end of file + } + + /* Fix for toolbar spacing and overflow issues */ + .frida-toolbar { + display: flex; + background-color: #2d2d2d; + padding: 5px 10px; + border-bottom: 1px solid #555; + align-items: center; + justify-content: flex-start; + flex-wrap: nowrap; + gap: 8px; + width: 100%; + /* Take full width of parent container */ + box-sizing: border-box; + max-width: 100%; + /* Prevent overflow */ + overflow: hidden; + } + + /* Group non-search controls to keep them together */ + .toolbar-controls { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; + /* Don't allow these controls to shrink */ + } + + /* Make search container responsive */ + /* Updated search container to better handle the clear button */ + .search-container { + display: flex; + margin-left: auto; + max-width: 180px; + min-width: 80px; + width: auto; + flex: 0 1 auto; + gap: 2px; + } + + + /* Hide clear button by default */ + #clear-search { + display: none; + } + + /* Show clear button only when there's text in the search box */ + #regex-search:not(:placeholder-shown) ~ #clear-search { + display: flex; + } + + .search-container input { + flex: 1; + background-color: #3a3a3a; + border: 1px solid #555; + border-radius: 4px; + color: #fff; + padding: 5px; + font-size: 12px; + min-width: 0; + /* Allow input to shrink below min-content */ + width: 100%; + text-overflow: ellipsis; + /* Show ellipsis for overflow text */ + } + + /* Make the search button more compact if needed */ + .search-container .toolbar-button { + padding: 3px; + flex-shrink: 0; + } + + /* Make toolbar buttons more compact */ + .toolbar-button { + background: none; + border: none; + cursor: pointer; + color: #ccc; + padding: 4px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + } + + .toolbar-button:hover { + background-color: #3a3a3a; + } + + /* Make font-controls more compact */ + .font-controls { + display: flex; + margin: 0; + gap: 2px; + } + + /* Updates for split panels */ + #split-1 { + display: flex; + flex-direction: column; + width: 100%; + min-width: 0; + /* Allow the split panel to shrink */ + overflow: hidden; + } + + /* Ensure the textarea container takes full available width */ + .textarea-container { + flex: 1; + position: relative; + overflow: hidden; + width: 100%; + } + + /* Handle container width for split views properly */ + .container.px-4.mx-auto { + width: 100%; + max-width: 100%; + padding-left: 0; + padding-right: 0; + margin-left: 0; + overflow: hidden; + } + + /* Ensure the split component takes proper width */ + .split { + width: 100% !important; + max-width: 100%; + min-width: 0; + overflow: hidden; + } + + /* Adjust the split component for horizontal orientation */ + .split.horizontal { + display: flex; + flex-direction: row; + height: 100%; + } + + .split.horizontal>#split-0, + .split.horizontal>#split-1 { + height: 100% !important; + min-height: 100%; + /* Ensure minimum height */ + } + + .gutter.gutter-horizontal { + width: 8px !important; + min-width: 8px !important; + cursor: col-resize; + } + + @media (max-width: 800px) { + .gutter-horizontal { + cursor: col-resize; + min-width: 8px !important; + } + } + + /* Responsive adjustments for Monaco editor container */ + #split-0 { + min-width: 0; + max-width: 100%; + } + + #split-0 #container { + width: 100% !important; + height: 100% !important; + } + + #container { + height: 100%; + width: 100% !important; + /* Override inline styles */ + max-width: 100%; + min-width: 0; + overflow: visible; + } + + /* Updated highlight overlay styling */ + .highlight-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + color: transparent; + overflow: auto; + font-family: monospace; + font-size: 0.8em; + line-height: 1.7em; + padding: 8px; + box-sizing: border-box; + border: 1px solid transparent; + z-index: 5; + white-space: pre-wrap !important; /* Critical for proper alignment */ + } + + /* Improved highlight match styling */ + .highlight-match { + background-color: var(--highlight-bg, #ffff00); + color: var(--highlight-text, #000000) !important; + padding: 0; + border-radius: 2px; + display: inline; + position: relative; + white-space: pre-wrap !important; /* Critical for proper alignment */ + } + + /* Color Modal */ + .color-modal { + display: none; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0, 0, 0, 0.5); + } + + .color-modal-content { + background-color: #2d2d2d; + margin: 15% auto; + padding: 20px; + border: 1px solid #555; + width: 400px; + border-radius: 8px; + color: #ccc; + } + + .close-modal { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; + cursor: pointer; + } + + .close-modal:hover, + .close-modal:focus { + color: #fff; + text-decoration: none; + } + + .color-setting { + margin: 15px 0; + } + + .color-input-group { + display: flex; + align-items: center; + margin-top: 5px; + } + + .color-input-group input[type="color"] { + width: 40px; + height: 25px; + background-color: transparent; + border: none; + margin-right: 10px; + } + + .color-input-group input[type="text"] { + flex: 1; + background-color: #3a3a3a; + border: 1px solid #555; + border-radius: 4px; + color: #fff; + padding: 5px 10px; + font-size: 12px; + } + + .modal-buttons { + display: flex; + justify-content: flex-end; + margin-top: 20px; + } + + .modal-buttons button { + margin-left: 10px; + padding: 6px 12px; + background-color: #424d5c; + border: none; + border-radius: 4px; + color: #fff; + cursor: pointer; + } + + .modal-buttons button:hover { + background-color: #536580; + } + + /* Match indicator styling */ + .match-indicator { + position: absolute; + bottom: 10px; + right: 10px; + background-color: #424d5c; + color: #fff; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + z-index: 10; + pointer-events: none; + opacity: 0.9; + box-shadow: 0 1px 3px rgba(0,0,0,0.2); + } + + /* Notification styling */ + .notification { + position: fixed; + bottom: 20px; + right: 20px; + background-color: #424d5c; + color: #fff; + padding: 8px 16px; + border-radius: 4px; + z-index: 1000; + font-size: 12px; + opacity: 0.9; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); + animation: fadeInOut 3s ease-in-out; + } + + + @keyframes fadeInOut { + 0% { opacity: 0; } + 10% { opacity: 0.9; } + 80% { opacity: 0.9; } + 100% { opacity: 0; } + } + + + /* Responsive adjustments */ + @media (max-width: 800px) { + .search-container { + max-width: 140px; + min-width: 70px; + } + + .match-indicator { + font-size: 10px; + padding: 3px 6px; + } + } \ No newline at end of file diff --git a/Fermion/src/renderer.js b/Fermion/src/renderer.js index 6c51fc0..3b7b21b 100644 --- a/Fermion/src/renderer.js +++ b/Fermion/src/renderer.js @@ -24,6 +24,16 @@ let logMutex = new MutexPromise('48011b2b9a930ee19e26320e5adbffa2e309663c'); let RunningLog = []; var deviceId = 'local'; +// Enhanced Frida Output variables +let currentFontSize = 0.8; // em units +let isVertical = true; +let searchRegex = null; +let bgColor = '#423636'; +let textColor = '#ffffff'; +let highlightColor = '#ffff00'; +let highlightTextColor = '#000000'; +let originalSplit = null; + // Instrument ////////////////////////////////////////////////// @@ -565,6 +575,11 @@ function appendFridaLog(data) { } FridaOut.scrollTop = FridaOut.scrollHeight; + + // If search is active, update highlights + if (searchRegex) { + performSearch(); + } } function ChangeLogExclusive(mutex, locktype, data) { @@ -706,7 +721,7 @@ document.getElementById("FermionOpen").onclick = function () { currentFilePath = result.filePaths[0]; }); } - }).catch(err =>{ + }).catch(err => { appendFridaLog("[!] Error opening file: " + err) }) } @@ -731,7 +746,7 @@ document.getElementById("FermionSave").onclick = function () { currentFilePath = result.filePath; }); } - }).catch(err =>{ + }).catch(err => { appendFridaLog("[!] Error saving file: " + err) }) } @@ -758,10 +773,10 @@ document.getElementById("getDeviceDetail").onclick = function () { if (result.hasOwnProperty("name")) { appendFridaLog(" |_ Host Name : " + result.name + "\n"); } - }).catch(err =>{ + }).catch(err => { appendFridaLog("[!] Failed to enumerate device properties: " + err + "\n"); }); - }).catch(err =>{ + }).catch(err => { appendFridaLog("[!] Failed to acquire device context: " + err + "\n"); }); } @@ -833,4 +848,777 @@ document.addEventListener("keydown", function (e) { // Trigger script reload document.getElementById("FridaReload").click(); } +}, false); + +////////////////////////////////////////////////// +// Frida Output Enhancement Functions +////////////////////////////////////////////////// + +// Store DOM references +const fridaOut = document.getElementById('FridaOut'); +const copyButton = document.getElementById('copy-output'); +const decreaseFontButton = document.getElementById('decrease-font'); +const increaseFontButton = document.getElementById('increase-font'); +const regexSearch = document.getElementById('regex-search'); +const searchButton = document.getElementById('search-button'); +const toggleLayoutButton = document.getElementById('toggle-layout'); +const colorSettingsButton = document.getElementById('color-settings'); +const colorModal = document.getElementById('color-modal'); +const closeModalBtn = document.querySelector('.close-modal'); +const applyColorsButton = document.getElementById('apply-colors'); +const resetColorsButton = document.getElementById('reset-colors'); +const bgColorPicker = document.getElementById('background-color'); +const bgColorHex = document.getElementById('background-color-hex'); +const textColorPicker = document.getElementById('text-color'); +const textColorHex = document.getElementById('text-color-hex'); +const highlightColorPicker = document.getElementById('highlight-color'); +const highlightColorHex = document.getElementById('highlight-color-hex'); +const highlightOverlay = document.getElementById('highlight-overlay'); + +// Initialize features when DOM is loaded +document.addEventListener('DOMContentLoaded', function () { + // Make sure window.split is available before storing it + setTimeout(() => { + if (window.split) { + originalSplit = window.split; + } + }, 500); + + // Load saved settings from localStorage + loadSettings(); + + // Add event listeners + addEventListeners(); + + // Initialize the layout button icon + initLayoutButton(); + + // Initial UI adjustment + setTimeout(adjustUIForSidebar, 100); + + // Initialize search functionality + setTimeout(initSearchFunctionality, 500); + +}); + +// Add event listeners to UI elements +function addEventListeners() { + // Copy button functionality + copyButton.addEventListener('click', copyOutputToClipboard); + + // Font size controls + decreaseFontButton.addEventListener('click', decreaseFontSize); + increaseFontButton.addEventListener('click', increaseFontSize); + + // Search functionality + searchButton.addEventListener('click', performSearch); + regexSearch.addEventListener('keypress', function (e) { + if (e.key === 'Enter') { + performSearch(); + } + }); + regexSearch.addEventListener('input', function () { + if (this.value.trim() === '') { + clearHighlights(); + searchRegex = null; + } + }); + + // Layout toggle + toggleLayoutButton.addEventListener('click', toggleLayout); + + // Color settings + colorSettingsButton.addEventListener('click', function () { + colorModal.style.display = 'block'; + }); + + closeModalBtn.addEventListener('click', function () { + colorModal.style.display = 'none'; + }); + + window.addEventListener('click', function (event) { + if (event.target === colorModal) { + colorModal.style.display = 'none'; + } + }); + + // Link color pickers with hex inputs + bgColorPicker.addEventListener('input', function () { + bgColorHex.value = this.value; + }); + + bgColorHex.addEventListener('input', function () { + if (/^#[0-9A-F]{6}$/i.test(this.value)) { + bgColorPicker.value = this.value; + } + }); + + textColorPicker.addEventListener('input', function () { + textColorHex.value = this.value; + }); + + textColorHex.addEventListener('input', function () { + if (/^#[0-9A-F]{6}$/i.test(this.value)) { + textColorPicker.value = this.value; + } + }); + + highlightColorPicker.addEventListener('input', function () { + highlightColorHex.value = this.value; + }); + + highlightColorHex.addEventListener('input', function () { + if (/^#[0-9A-F]{6}$/i.test(this.value)) { + highlightColorPicker.value = this.value; + } + }); + + // Apply and reset buttons + applyColorsButton.addEventListener('click', applyColors); + resetColorsButton.addEventListener('click', resetColors); + + // Sync scrolling between textarea and highlight overlay + fridaOut.addEventListener('scroll', function () { + highlightOverlay.scrollTop = fridaOut.scrollTop; + highlightOverlay.scrollLeft = fridaOut.scrollLeft; + }); + + // Handle textarea input and content changes for highlighting + fridaOut.addEventListener('input', function () { + // If search is active, update highlights + if (searchRegex) { + performSearch(); + } + }); + + // Monitor textarea size changes to adjust highlight overlay + new ResizeObserver(() => { + if (searchRegex) { + performSearch(); + } + }).observe(fridaOut); + + // Add event listeners for sidebar state changes and window resize + window.addEventListener('resize', adjustUIForSidebar); + + // Add a listener for possible sidebar toggle events + document.addEventListener('click', function (event) { + // Check if click might be related to sidebar toggle + if (event.target.closest('nav') || event.target.closest('.navbar-menu')) { + // Wait a moment for DOM to update + setTimeout(adjustUIForSidebar, 100); + } + }); +} + +// Copy output to clipboard +function copyOutputToClipboard() { + fridaOut.select(); + document.execCommand('copy'); + window.getSelection().removeAllRanges(); + + showNotification('Copied to clipboard!'); +} + +// Show notification +function showNotification(message) { + // Remove any existing notifications + const existingNotification = document.querySelector('.notification'); + if (existingNotification) { + existingNotification.remove(); + } + + // Create notification element + const notification = document.createElement('div'); + notification.className = 'notification'; + notification.textContent = message; + document.body.appendChild(notification); + + // Remove after 2 seconds + setTimeout(() => { + if (notification.parentNode) { + notification.parentNode.removeChild(notification); + } + }, 2000); +} + +// Decrease font size +function decreaseFontSize() { + if (currentFontSize > 0.5) { + currentFontSize -= 0.1; + currentFontSize = Math.round(currentFontSize * 10) / 10; // Round to 1 decimal place + fridaOut.style.fontSize = currentFontSize + 'em'; + highlightOverlay.style.fontSize = currentFontSize + 'em'; + saveSettings(); + + // Reapply search highlighting if active + if (searchRegex) { + performSearch(); + } + } +} + +// Increase font size +function increaseFontSize() { + if (currentFontSize < 2.0) { + currentFontSize += 0.1; + currentFontSize = Math.round(currentFontSize * 10) / 10; // Round to 1 decimal place + fridaOut.style.fontSize = currentFontSize + 'em'; + highlightOverlay.style.fontSize = currentFontSize + 'em'; + saveSettings(); + + // Reapply search highlighting if active + if (searchRegex) { + performSearch(); + } + } +} + +// Improved synchronization function for textarea and overlay +function syncOverlayDimensions() { + if (!highlightOverlay || !fridaOut) return; + + // Get computed dimensions from the textarea + const computedStyle = window.getComputedStyle(fridaOut); + + // Apply exact dimensions and properties to the overlay + highlightOverlay.style.width = computedStyle.width; + highlightOverlay.style.height = computedStyle.height; + highlightOverlay.style.padding = computedStyle.padding; + highlightOverlay.style.boxSizing = computedStyle.boxSizing; + highlightOverlay.style.fontFamily = computedStyle.fontFamily; + highlightOverlay.style.fontSize = computedStyle.fontSize; + highlightOverlay.style.lineHeight = computedStyle.lineHeight; + highlightOverlay.style.letterSpacing = computedStyle.letterSpacing; + highlightOverlay.style.wordSpacing = computedStyle.wordSpacing; + highlightOverlay.style.textAlign = computedStyle.textAlign; + highlightOverlay.style.whiteSpace = 'pre-wrap'; // This matches textarea behavior + + // Sync scroll position + highlightOverlay.scrollTop = fridaOut.scrollTop; + highlightOverlay.scrollLeft = fridaOut.scrollLeft; +} + + +// Updated CSS to apply to the highlight overlay +function updateOverlayStyles() { + const style = document.createElement('style'); + style.textContent = ` + .highlight-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + color: transparent; + overflow: auto; + font-family: monospace; + font-size: 0.8em; + line-height: 1.7em; + padding: 8px; + box-sizing: border-box; + border: 1px solid transparent; + z-index: 5; + white-space: pre-wrap; /* Match textarea behavior */ + } + + .highlight-match { + background-color: var(--highlight-bg, #ffff00); + color: var(--highlight-text, #000000) !important; + padding: 0; + border-radius: 2px; + display: inline; + } + `; + document.head.appendChild(style); +} + +// Enhanced function to adjust UI based on sidebar state +function adjustUIForSidebar() { + // Get the actual width of the container + const containerWidth = document.getElementById('split-1').offsetWidth; + + // Adjust search container width based on available space + const searchContainer = document.querySelector('.search-container'); + if (searchContainer) { + // Make search container narrower if space is limited + if (containerWidth < 300) { + searchContainer.style.maxWidth = '100px'; + searchContainer.style.minWidth = '60px'; + } else { + searchContainer.style.maxWidth = '180px'; + searchContainer.style.minWidth = '80px'; + } + } + + // Update overlay dimensions after sidebar changes + syncOverlayDimensions(); + + // If search is active, reapply highlights + if (searchRegex) { + performSearch(); + } +} + +// Initialize the search-related functionality +function initSearchFunctionality() { + // Update overlay styles + updateOverlayStyles(); + + // Set clear button event + document.getElementById('clear-search').addEventListener('click', clearSearch); + + // Handle input changes for search box + document.getElementById('regex-search').addEventListener('input', function () { + if (this.value.trim() === '') { + clearSearch(); + } + }); + + // Handle search button clicks + document.getElementById('search-button').addEventListener('click', performSearch); + + // Handle Enter key in search box + document.getElementById('regex-search').addEventListener('keypress', function (e) { + if (e.key === 'Enter') { + performSearch(); + } + }); + + // Sync scrolling between textarea and highlight overlay + document.getElementById('FridaOut').addEventListener('scroll', function () { + document.getElementById('highlight-overlay').scrollTop = this.scrollTop; + document.getElementById('highlight-overlay').scrollLeft = this.scrollLeft; + }); +} + +// Perform search +function performSearch() { + const searchTerm = document.getElementById('regex-search').value.trim(); + + if (searchTerm === '') { + searchRegex = null; + clearHighlights(); + return; + } + + try { + // Create a new regex object + searchRegex = new RegExp(searchTerm, 'gi'); + highlightMatches(); + } catch (error) { + showNotification('Invalid regular expression: ' + error.message); + searchRegex = null; + clearHighlights(); + } +} + +// Function to clear the search +function clearSearch() { + const searchInput = document.getElementById('regex-search'); + searchInput.value = ''; + searchRegex = null; + clearHighlights(); + + // Remove match indicator if present + const matchIndicator = document.querySelector('.match-indicator'); + if (matchIndicator) { + matchIndicator.parentNode.removeChild(matchIndicator); + } +} + +// Add event listener for the clear button +document.getElementById('clear-search').addEventListener('click', clearSearch); + +// Clear search function listener +document.getElementById('regex-search').addEventListener('input', function () { + if (this.value.trim() === '') { + clearSearch(); + } +}); + +// Enhanced highlight function +function highlightMatches() { + if (!searchRegex) return; + + // Clear previous highlights + highlightOverlay.innerHTML = ''; + + // Get the current text + const content = fridaOut.value; + if (!content) return; + + // Ensure overlay dimensions match textarea exactly + syncOverlayDimensions(); + + // Count total matches + let totalMatches = 0; + let tempContent = content; + let tempMatch; + // Create a fresh regex to avoid lastIndex issues + const countRegex = new RegExp(searchRegex.source, 'gi'); + while ((tempMatch = countRegex.exec(tempContent)) !== null) { + totalMatches++; + // Prevent infinite loops for zero-length matches + if (tempMatch.index === countRegex.lastIndex) { + countRegex.lastIndex++; + } + } + + // Create a fresh regex for the actual processing + const processRegex = new RegExp(searchRegex.source, 'gi'); + + // Transform content to HTML with highlights + let htmlContent = ''; + let lastIndex = 0; + let match; + + while ((match = processRegex.exec(content)) !== null) { + // Add text before the match + htmlContent += content.substring(lastIndex, match.index); + + // Add the highlighted match + htmlContent += `${match[0]}`; + + lastIndex = processRegex.lastIndex; + + // Prevent infinite loops for zero-length matches + if (match.index === processRegex.lastIndex) { + processRegex.lastIndex++; + } + } + + // Add remaining text after the last match + if (lastIndex < content.length) { + htmlContent += content.substring(lastIndex); + } + + // Replace newlines with
tags for proper display + htmlContent = htmlContent.replace(/\n/g, '
'); + + // Set the HTML content + highlightOverlay.innerHTML = htmlContent; + + // Show match count if matches found + if (totalMatches > 0) { + showMatchCount(totalMatches); + } else { + showNotification('No matches found'); + } +} + + +// Show match count indicator +function showMatchCount(count) { + // Remove any existing indicators + const existingIndicator = document.querySelector('.match-indicator'); + if (existingIndicator) { + existingIndicator.remove(); + } + + // Create indicator + const indicator = document.createElement('div'); + indicator.className = 'match-indicator'; + indicator.textContent = count + ' matches'; + document.querySelector('.textarea-container').appendChild(indicator); + + // Remove after 3 seconds + setTimeout(() => { + if (indicator.parentNode) { + indicator.parentNode.removeChild(indicator); + } + }, 3000); +} + +// Clear highlights +function clearHighlights() { + highlightOverlay.innerHTML = ''; + + // Remove match indicator if present + const matchIndicator = document.querySelector('.match-indicator'); + if (matchIndicator) { + matchIndicator.parentNode.removeChild(matchIndicator); + } +} + + +const verticalIcon = ` + + +`; + +const horizontalIcon = ` + + +`; + +// Toggle layout direction +function toggleLayout() { + // Toggle the layout state + isVertical = !isVertical; + + // Ensure split instance exists + if (typeof window.split !== 'undefined') { + // Get elements we'll need to manipulate + const split0 = document.getElementById('split-0'); + const split1 = document.getElementById('split-1'); + const container = document.getElementById('container'); + const fridaOutContainer = document.querySelector('.textarea-container'); + const splitContainer = document.querySelector('.split'); + + // Force visibility of the output area during transition + fridaOut.style.display = 'block'; + + // Destroy existing split + window.split.destroy(); + + // Remove all inline styles that might interfere + split0.removeAttribute('style'); + split1.removeAttribute('style'); + + // Apply basic required styles + split0.style.overflow = 'hidden'; + split1.style.overflow = 'hidden'; + container.style.width = '100%'; + container.style.height = '100%'; + + // Update container classes + if (isVertical) { + splitContainer.classList.remove('horizontal'); + } else { + splitContainer.classList.add('horizontal'); + } + + // Create new split with a delay to ensure DOM updates + setTimeout(() => { + window.split = Split(['#split-0', '#split-1'], { + direction: isVertical ? 'vertical' : 'horizontal', + sizes: isVertical ? [63, 37] : [50, 50], + minSize: 100, + gutterSize: 8, + onDragEnd: function () { + window.dispatchEvent(new Event('resize')); + if (MonacoCodeEditor) { + MonacoCodeEditor.layout(); + } + } + }); + + // Force output container to take full width/height + fridaOutContainer.style.width = '100%'; + fridaOutContainer.style.height = '100%'; + fridaOut.style.width = '100%'; + fridaOut.style.height = '100%'; + + // Force Monaco editor layout update + if (MonacoCodeEditor) { + MonacoCodeEditor.layout(); + } + + // Update layout button icon + updateLayoutButtonIcon(); + + // Sync overlay dimensions + syncOverlayDimensions(); + }, 50); + + // Save settings + saveSettings(); + } +} + + +// Function to update the layout button icon based on current state +function updateLayoutButtonIcon() { + const toggleButton = document.getElementById('toggle-layout'); + + // Note: We've swapped the logic here - show what layout we're CURRENTLY in + // (not what we'll switch to next) + toggleButton.innerHTML = isVertical ? verticalIcon : horizontalIcon; + toggleButton.title = isVertical ? "Switch to horizontal layout" : "Switch to vertical layout"; +} + +function initLayoutButton() { + // Set the initial icon based on the current layout + updateLayoutButtonIcon(); +} + +// Apply colors +function applyColors() { + bgColor = bgColorPicker.value; + textColor = textColorPicker.value; + highlightColor = highlightColorPicker.value; + highlightTextColor = getContrastColor(highlightColor); + + // Apply colors to textarea + fridaOut.style.backgroundColor = bgColor; + fridaOut.style.color = textColor; + + // Set CSS variables for highlight colors + document.documentElement.style.setProperty('--highlight-bg', highlightColor); + document.documentElement.style.setProperty('--highlight-text', highlightTextColor); + + // Close modal + colorModal.style.display = 'none'; + + // Save settings + saveSettings(); + + // Reapply highlighting if active + if (searchRegex) { + performSearch(); + } +} + +// Reset colors to defaults +function resetColors() { + // Default colors + bgColor = '#423636'; + textColor = '#ffffff'; + highlightColor = '#ffff00'; + highlightTextColor = '#000000'; + + // Update color pickers and hex inputs + bgColorPicker.value = bgColor; + bgColorHex.value = bgColor; + textColorPicker.value = textColor; + textColorHex.value = textColor; + highlightColorPicker.value = highlightColor; + highlightColorHex.value = highlightColor; + + // Apply colors + fridaOut.style.backgroundColor = bgColor; + fridaOut.style.color = textColor; + + // Set CSS variables + document.documentElement.style.setProperty('--highlight-bg', highlightColor); + document.documentElement.style.setProperty('--highlight-text', highlightTextColor); + + // Close modal + colorModal.style.display = 'none'; + + // Save settings + saveSettings(); + + // Reapply highlighting if active + if (searchRegex) { + performSearch(); + } +} + +// Get contrast color (black or white) based on background color +function getContrastColor(hexColor) { + // Convert hex to RGB + const r = parseInt(hexColor.substr(1, 2), 16); + const g = parseInt(hexColor.substr(3, 2), 16); + const b = parseInt(hexColor.substr(5, 2), 16); + + // Calculate luminance - standard formula for perceived brightness + const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; + + // Return black for light colors, white for dark + return luminance > 0.5 ? '#000000' : '#ffffff'; +} + +// Save settings to localStorage +function saveSettings() { + const settings = { + fontSize: currentFontSize, + bgColor: bgColor, + textColor: textColor, + highlightColor: highlightColor, + isVertical: isVertical + }; + + localStorage.setItem('fermion_output_settings', JSON.stringify(settings)); +} + +// Load settings from localStorage +function loadSettings() { + const savedSettings = localStorage.getItem('fermion_output_settings'); + if (savedSettings) { + try { + const settings = JSON.parse(savedSettings); + + // Apply font size + if (settings.fontSize && !isNaN(settings.fontSize)) { + currentFontSize = settings.fontSize; + fridaOut.style.fontSize = currentFontSize + 'em'; + highlightOverlay.style.fontSize = currentFontSize + 'em'; + } + + // Apply colors + if (settings.bgColor) { + bgColor = settings.bgColor; + bgColorPicker.value = bgColor; + bgColorHex.value = bgColor; + fridaOut.style.backgroundColor = bgColor; + } + + if (settings.textColor) { + textColor = settings.textColor; + textColorPicker.value = textColor; + textColorHex.value = textColor; + fridaOut.style.color = textColor; + } + + if (settings.highlightColor) { + highlightColor = settings.highlightColor; + highlightColorPicker.value = highlightColor; + highlightColorHex.value = highlightColor; + highlightTextColor = getContrastColor(highlightColor); + } + + // Set CSS variables + document.documentElement.style.setProperty('--highlight-bg', highlightColor); + document.documentElement.style.setProperty('--highlight-text', highlightTextColor); + + // Apply layout direction + if (settings.hasOwnProperty('isVertical')) { + isVertical = settings.isVertical; + + // If not vertical, toggle the layout after a short delay + if (!isVertical && typeof window.split !== 'undefined') { + setTimeout(function () { + toggleLayout(); + }, 500); + } + } + } catch (error) { + console.error('Error loading settings:', error); + } + } +} + +// Initialize on load +window.addEventListener('load', function () { + // Make sure highlighting overlay has the correct initial font size + highlightOverlay.style.fontSize = currentFontSize + 'em'; + + // Add the color modal to the document body if it's not already there + if (!document.getElementById('color-modal')) { + const modalHTML = document.getElementById('color-modal'); + document.body.appendChild(modalHTML); + } +}); + +// Prevent default actions for F5 and Ctrl+R +// This is to prevent the default refresh behavior of the browser +document.addEventListener("keydown", function (e) { + // Prevent F5 key + if (e.key === 'F5' || e.keyCode === 116) { + e.preventDefault(); + // For Debug purposes + //appendFridaLog('[i] Refresh prevented (F5)'); + return false; + } + + // Prevent Ctrl+R + if ((window.navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) && (e.key === 'r' || e.keyCode === 82)) { + e.preventDefault(); + // For Debug purposes + //appendFridaLog('[i] Refresh prevented (Ctrl+R)'); + return false; + } }, false); \ No newline at end of file From b3f928e49315a5cea1c858e3d8b7bb8eb7211f34 Mon Sep 17 00:00:00 2001 From: Watson Date: Mon, 14 Apr 2025 14:29:01 -0600 Subject: [PATCH 3/7] Update renderer.js Updated to include output save, output clear, and search feature enhancements. --- Fermion/src/renderer.js | 560 +++++++++++++++++++++++++++++++++++----- 1 file changed, 491 insertions(+), 69 deletions(-) diff --git a/Fermion/src/renderer.js b/Fermion/src/renderer.js index 3b7b21b..92fdd39 100644 --- a/Fermion/src/renderer.js +++ b/Fermion/src/renderer.js @@ -34,6 +34,13 @@ let highlightColor = '#ffff00'; let highlightTextColor = '#000000'; let originalSplit = null; +// Add these new search-related variables +let matchPositions = []; +let currentMatchIndex = -1; +let totalMatches = 0; +let currentMatchColor = '#FFD300'; +let currentMatchTextColor = '#000000'; + // Instrument ////////////////////////////////////////////////// @@ -874,6 +881,11 @@ const textColorHex = document.getElementById('text-color-hex'); const highlightColorPicker = document.getElementById('highlight-color'); const highlightColorHex = document.getElementById('highlight-color-hex'); const highlightOverlay = document.getElementById('highlight-overlay'); +const currentMatchColorPicker = document.getElementById('current-match-color'); +const currentMatchColorHex = document.getElementById('current-match-color-hex'); +const clearOutputButton = document.getElementById('clear-output'); +const saveOutputButton = document.getElementById('save-output'); + // Initialize features when DOM is loaded document.addEventListener('DOMContentLoaded', function () { @@ -896,9 +908,16 @@ document.addEventListener('DOMContentLoaded', function () { // Initial UI adjustment setTimeout(adjustUIForSidebar, 100); - // Initialize search functionality - setTimeout(initSearchFunctionality, 500); + // Initialize Monaco editor with custom theme + setTimeout(addMonacoSettingsListeners, 1500); + // Initialize search functionality with new features + setTimeout(() => { + initSearchFunctionality(); + addSearchNavButtons(); + addSearchKeyboardShortcuts(); + addSearchStyles(); + }, 500); }); // Add event listeners to UI elements @@ -914,13 +933,17 @@ function addEventListeners() { searchButton.addEventListener('click', performSearch); regexSearch.addEventListener('keypress', function (e) { if (e.key === 'Enter') { - performSearch(); + if (searchRegex) { + navigateToNextMatch(); + } else { + performSearch(); + } } }); + regexSearch.addEventListener('input', function () { if (this.value.trim() === '') { - clearHighlights(); - searchRegex = null; + clearSearch(); } }); @@ -936,12 +959,27 @@ function addEventListeners() { colorModal.style.display = 'none'; }); + clearOutputButton.addEventListener('click', clearOutputArea); + + saveOutputButton.addEventListener('click', saveOutputArea); + + window.addEventListener('click', function (event) { if (event.target === colorModal) { colorModal.style.display = 'none'; } }); + currentMatchColorPicker.addEventListener('input', function () { + currentMatchColorHex.value = this.value; + }); + + currentMatchColorHex.addEventListener('input', function () { + if (/^#[0-9A-F]{6}$/i.test(this.value)) { + currentMatchColorPicker.value = this.value; + } + }); + // Link color pickers with hex inputs bgColorPicker.addEventListener('input', function () { bgColorHex.value = this.value; @@ -973,6 +1011,7 @@ function addEventListeners() { } }); + // Apply and reset buttons applyColorsButton.addEventListener('click', applyColors); resetColorsButton.addEventListener('click', resetColors); @@ -1011,6 +1050,130 @@ function addEventListeners() { }); } +// Function to add navigation buttons to the toolbar +function addSearchNavButtons() { + // First check if buttons already exist + if (document.getElementById('prev-match') || document.getElementById('next-match')) { + return; + } + + const searchContainer = document.querySelector('.search-container'); + + // Create navigation buttons + const navContainer = document.createElement('div'); + navContainer.className = 'search-nav-buttons'; + navContainer.style.display = 'none'; // Hidden by default + + const prevButton = document.createElement('button'); + prevButton.id = 'prev-match'; + prevButton.className = 'toolbar-button'; + prevButton.title = 'Previous match (Shift+Enter)'; + prevButton.innerHTML = ``; + + const nextButton = document.createElement('button'); + nextButton.id = 'next-match'; + nextButton.className = 'toolbar-button'; + nextButton.title = 'Next match (Enter)'; + nextButton.innerHTML = ``; + + navContainer.appendChild(prevButton); + navContainer.appendChild(nextButton); + + // Insert before the search button + const searchButton = document.getElementById('search-button'); + searchContainer.insertBefore(navContainer, searchButton); + + // Add event listeners + prevButton.addEventListener('click', navigateToPreviousMatch); + nextButton.addEventListener('click', navigateToNextMatch); +} + +// Function to navigate to the previous match +function navigateToPreviousMatch() { + if (matchPositions.length === 0) return; + + currentMatchIndex = (currentMatchIndex <= 0) ? matchPositions.length - 1 : currentMatchIndex - 1; + highlightCurrentMatch(); + scrollToCurrentMatch(); + updateMatchCounter(); +} + +// Function to navigate to the next match +function navigateToNextMatch() { + if (matchPositions.length === 0) return; + + currentMatchIndex = (currentMatchIndex >= matchPositions.length - 1) ? 0 : currentMatchIndex + 1; + highlightCurrentMatch(); + scrollToCurrentMatch(); + updateMatchCounter(); +} + +// Function to scroll the textarea to show the current match +function scrollToCurrentMatch() { + if (currentMatchIndex < 0 || matchPositions.length === 0) return; + + const fridaOut = document.getElementById('FridaOut'); + const match = matchPositions[currentMatchIndex]; + + // Calculate position in the textarea + const textBeforeMatch = fridaOut.value.substring(0, match.start); + const lines = textBeforeMatch.split('\n'); + + // Create a temporary element to measure text dimensions + const temp = document.createElement('div'); + temp.style.position = 'absolute'; + temp.style.visibility = 'hidden'; + temp.style.whiteSpace = 'pre'; + temp.style.font = window.getComputedStyle(fridaOut).font; + document.body.appendChild(temp); + + // Calculate the line height + temp.textContent = 'M'; + const lineHeight = temp.offsetHeight; + + // Calculate approximate scroll position + const lineNumber = lines.length - 1; + const approximateScrollTop = lineNumber * lineHeight; + + // Remove the temporary element + document.body.removeChild(temp); + + // Scroll to position, with some offset to center it in the view + const viewportHeight = fridaOut.clientHeight; + fridaOut.scrollTop = approximateScrollTop - (viewportHeight / 2) + lineHeight; +} + +// Function to update the UI counter showing current match position +function updateMatchCounter() { + // Update or create the match counter + let counter = document.querySelector('.match-indicator'); + + if (!counter) { + counter = document.createElement('div'); + counter.className = 'match-indicator'; + document.querySelector('.textarea-container').appendChild(counter); + } + + if (matchPositions.length > 0) { + counter.textContent = `${currentMatchIndex + 1} of ${matchPositions.length} matches`; + counter.style.display = 'block'; + + // Show navigation buttons + const navButtons = document.querySelector('.search-nav-buttons'); + if (navButtons) { + navButtons.style.display = 'flex'; + } + } else { + counter.style.display = 'none'; + + // Hide navigation buttons + const navButtons = document.querySelector('.search-nav-buttons'); + if (navButtons) { + navButtons.style.display = 'none'; + } + } +} + // Copy output to clipboard function copyOutputToClipboard() { fridaOut.select(); @@ -1099,7 +1262,6 @@ function syncOverlayDimensions() { highlightOverlay.scrollLeft = fridaOut.scrollLeft; } - // Updated CSS to apply to the highlight overlay function updateOverlayStyles() { const style = document.createElement('style'); @@ -1147,7 +1309,7 @@ function adjustUIForSidebar() { searchContainer.style.maxWidth = '100px'; searchContainer.style.minWidth = '60px'; } else { - searchContainer.style.maxWidth = '180px'; + searchContainer.style.maxWidth = '220px'; // Updated to accommodate navigation buttons searchContainer.style.minWidth = '80px'; } } @@ -1182,7 +1344,11 @@ function initSearchFunctionality() { // Handle Enter key in search box document.getElementById('regex-search').addEventListener('keypress', function (e) { if (e.key === 'Enter') { - performSearch(); + if (searchRegex) { + navigateToNextMatch(); + } else { + performSearch(); + } } }); @@ -1193,13 +1359,95 @@ function initSearchFunctionality() { }); } +// Add keyboard shortcuts for search +function addSearchKeyboardShortcuts() { + // Global Ctrl+F to focus search box + document.addEventListener('keydown', function (e) { + // Check if this is Ctrl+F (or Cmd+F on Mac) + if ((e.ctrlKey || e.metaKey) && e.key === 'f') { + // Don't intercept if user is typing in Monaco editor (it has its own search) + const activeElement = document.activeElement; + const isInMonacoEditor = activeElement && + (activeElement.closest('.monaco-editor') || + document.querySelector('.monaco-editor').contains(activeElement)); + + if (!isInMonacoEditor) { + e.preventDefault(); + // Focus the search box + const searchInput = document.getElementById('regex-search'); + searchInput.focus(); + searchInput.select(); + } + } + }); + + // Search box keyboard navigation + const searchInput = document.getElementById('regex-search'); + searchInput.addEventListener('keydown', function (e) { + if (e.key === 'Enter') { + // Shift+Enter to go to previous match + if (e.shiftKey) { + e.preventDefault(); + navigateToPreviousMatch(); + } + // Enter to go to next match + else { + e.preventDefault(); + // If we have an active search, navigate to next match + if (searchRegex) { + navigateToNextMatch(); + } + // Otherwise, perform search + else { + performSearch(); + } + } + } + // Escape to clear search + else if (e.key === 'Escape') { + e.preventDefault(); + clearSearch(); + } + }); + + // Textarea Esc key to clear search + document.getElementById('FridaOut').addEventListener('keydown', function (e) { + if (e.key === 'Escape' && searchRegex) { + e.preventDefault(); + clearSearch(); + } + }); +} + +// Add search styles +function addSearchStyles() { + const styleEl = document.createElement('style'); + styleEl.textContent = ` + .highlight-match.current-match { + background-color: var(--current-match-bg, ${currentMatchColor}) !important; + color: var(--current-match-text, ${currentMatchTextColor}) !important; + } + + .search-nav-buttons { + display: flex; + align-items: center; + margin-right: 5px; + } + `; + document.head.appendChild(styleEl); + + // Set initial CSS variables + document.documentElement.style.setProperty('--current-match-bg', currentMatchColor); + document.documentElement.style.setProperty('--current-match-text', currentMatchTextColor); +} + // Perform search function performSearch() { const searchTerm = document.getElementById('regex-search').value.trim(); if (searchTerm === '') { searchRegex = null; - clearHighlights(); + clearSearch(); return; } @@ -1219,24 +1467,34 @@ function clearSearch() { const searchInput = document.getElementById('regex-search'); searchInput.value = ''; searchRegex = null; + matchPositions = []; + currentMatchIndex = -1; + totalMatches = 0; clearHighlights(); - // Remove match indicator if present + // Hide match indicator const matchIndicator = document.querySelector('.match-indicator'); if (matchIndicator) { - matchIndicator.parentNode.removeChild(matchIndicator); + matchIndicator.style.display = 'none'; + } + + // Hide navigation buttons + const navButtons = document.querySelector('.search-nav-buttons'); + if (navButtons) { + navButtons.style.display = 'none'; } } -// Add event listener for the clear button -document.getElementById('clear-search').addEventListener('click', clearSearch); +// Clear highlights +function clearHighlights() { + highlightOverlay.innerHTML = ''; -// Clear search function listener -document.getElementById('regex-search').addEventListener('input', function () { - if (this.value.trim() === '') { - clearSearch(); + // Remove match indicator if present + const matchIndicator = document.querySelector('.match-indicator'); + if (matchIndicator) { + matchIndicator.parentNode.removeChild(matchIndicator); } -}); +} // Enhanced highlight function function highlightMatches() { @@ -1249,23 +1507,13 @@ function highlightMatches() { const content = fridaOut.value; if (!content) return; + // Reset match tracking + matchPositions = []; + totalMatches = 0; + // Ensure overlay dimensions match textarea exactly syncOverlayDimensions(); - // Count total matches - let totalMatches = 0; - let tempContent = content; - let tempMatch; - // Create a fresh regex to avoid lastIndex issues - const countRegex = new RegExp(searchRegex.source, 'gi'); - while ((tempMatch = countRegex.exec(tempContent)) !== null) { - totalMatches++; - // Prevent infinite loops for zero-length matches - if (tempMatch.index === countRegex.lastIndex) { - countRegex.lastIndex++; - } - } - // Create a fresh regex for the actual processing const processRegex = new RegExp(searchRegex.source, 'gi'); @@ -1278,10 +1526,18 @@ function highlightMatches() { // Add text before the match htmlContent += content.substring(lastIndex, match.index); - // Add the highlighted match - htmlContent += `${match[0]}`; + // Store match position + matchPositions.push({ + start: match.index, + end: processRegex.lastIndex, + text: match[0] + }); + + // Add the highlighted match with a data attribute for the match index + htmlContent += `${match[0]}`; lastIndex = processRegex.lastIndex; + totalMatches++; // Prevent infinite loops for zero-length matches if (match.index === processRegex.lastIndex) { @@ -1300,49 +1556,53 @@ function highlightMatches() { // Set the HTML content highlightOverlay.innerHTML = htmlContent; - // Show match count if matches found - if (totalMatches > 0) { - showMatchCount(totalMatches); - } else { - showNotification('No matches found'); - } -} + // If we have matches, set the current match to the first one + if (matchPositions.length > 0) { + // Default to first match if we don't have a current match index + if (currentMatchIndex < 0 || currentMatchIndex >= matchPositions.length) { + currentMatchIndex = 0; + } + // Highlight the current match + highlightCurrentMatch(); -// Show match count indicator -function showMatchCount(count) { - // Remove any existing indicators - const existingIndicator = document.querySelector('.match-indicator'); - if (existingIndicator) { - existingIndicator.remove(); - } + // Show match counter + updateMatchCounter(); - // Create indicator - const indicator = document.createElement('div'); - indicator.className = 'match-indicator'; - indicator.textContent = count + ' matches'; - document.querySelector('.textarea-container').appendChild(indicator); + // Scroll to the current match + scrollToCurrentMatch(); + } else { + // No matches found + currentMatchIndex = -1; + showNotification('No matches found'); - // Remove after 3 seconds - setTimeout(() => { - if (indicator.parentNode) { - indicator.parentNode.removeChild(indicator); + // Hide navigation buttons + const navButtons = document.querySelector('.search-nav-buttons'); + if (navButtons) { + navButtons.style.display = 'none'; } - }, 3000); + } } -// Clear highlights -function clearHighlights() { - highlightOverlay.innerHTML = ''; +// Function to highlight the current match +function highlightCurrentMatch() { + // Remove any existing current-match classes + const allMatches = highlightOverlay.querySelectorAll('.highlight-match'); + allMatches.forEach(match => { + match.style.backgroundColor = highlightColor; + match.style.color = highlightTextColor; + }); - // Remove match indicator if present - const matchIndicator = document.querySelector('.match-indicator'); - if (matchIndicator) { - matchIndicator.parentNode.removeChild(matchIndicator); + // Apply current match highlighting + if (currentMatchIndex >= 0 && currentMatchIndex < matchPositions.length) { + const currentMatch = highlightOverlay.querySelector(`.highlight-match[data-match-index="${currentMatchIndex}"]`); + if (currentMatch) { + currentMatch.style.backgroundColor = currentMatchColor; + currentMatch.style.color = currentMatchTextColor; + } } } - const verticalIcon = ` @@ -1428,7 +1688,6 @@ function toggleLayout() { } } - // Function to update the layout button icon based on current state function updateLayoutButtonIcon() { const toggleButton = document.getElementById('toggle-layout'); @@ -1450,6 +1709,8 @@ function applyColors() { textColor = textColorPicker.value; highlightColor = highlightColorPicker.value; highlightTextColor = getContrastColor(highlightColor); + currentMatchColor = currentMatchColorPicker.value; + currentMatchTextColor = getContrastColor(currentMatchColor); // Apply colors to textarea fridaOut.style.backgroundColor = bgColor; @@ -1458,6 +1719,8 @@ function applyColors() { // Set CSS variables for highlight colors document.documentElement.style.setProperty('--highlight-bg', highlightColor); document.documentElement.style.setProperty('--highlight-text', highlightTextColor); + document.documentElement.style.setProperty('--current-match-bg', currentMatchColor); + document.documentElement.style.setProperty('--current-match-text', currentMatchTextColor); // Close modal colorModal.style.display = 'none'; @@ -1478,6 +1741,8 @@ function resetColors() { textColor = '#ffffff'; highlightColor = '#ffff00'; highlightTextColor = '#000000'; + currentMatchColor = '#FFD300'; + currentMatchTextColor = '#000000'; // Update color pickers and hex inputs bgColorPicker.value = bgColor; @@ -1486,6 +1751,8 @@ function resetColors() { textColorHex.value = textColor; highlightColorPicker.value = highlightColor; highlightColorHex.value = highlightColor; + currentMatchColorPicker.value = currentMatchColor; + currentMatchColorHex.value = currentMatchColor; // Apply colors fridaOut.style.backgroundColor = bgColor; @@ -1494,6 +1761,8 @@ function resetColors() { // Set CSS variables document.documentElement.style.setProperty('--highlight-bg', highlightColor); document.documentElement.style.setProperty('--highlight-text', highlightTextColor); + document.documentElement.style.setProperty('--current-match-bg', currentMatchColor); + document.documentElement.style.setProperty('--current-match-text', currentMatchTextColor); // Close modal colorModal.style.display = 'none'; @@ -1523,17 +1792,106 @@ function getContrastColor(hexColor) { // Save settings to localStorage function saveSettings() { + // Get the current Monaco theme + const themeSelect = document.getElementById("MonacoThemeSelect"); + const currentTheme = themeSelect ? themeSelect.value : "Kuroir"; // Default if not found + + // Get the current wrap state + const wrapToggle = document.getElementById("FermionMonacoWrap"); + const isWrapEnabled = wrapToggle && wrapToggle.children[0] ? + wrapToggle.children[0].checked : false; + const settings = { fontSize: currentFontSize, bgColor: bgColor, textColor: textColor, highlightColor: highlightColor, - isVertical: isVertical + currentMatchColor: currentMatchColor, + isVertical: isVertical, + // Add Monaco Editor settings + monacoTheme: currentTheme, + monacoWrap: isWrapEnabled }; localStorage.setItem('fermion_output_settings', JSON.stringify(settings)); } +// Function to clear the output area +function clearOutputArea() { + // Get the textarea + const fridaOut = document.getElementById('FridaOut'); + + // Clear the textarea content + fridaOut.value = ''; + + // Clear highlights if search is active + clearHighlights(); + + // Reset search-related variables + searchRegex = null; + matchPositions = []; + currentMatchIndex = -1; + totalMatches = 0; + + // Hide match indicator + const matchIndicator = document.querySelector('.match-indicator'); + if (matchIndicator) { + matchIndicator.style.display = 'none'; + } + + // Hide navigation buttons + const navButtons = document.querySelector('.search-nav-buttons'); + if (navButtons) { + navButtons.style.display = 'none'; + } + + // Show notification + showNotification('Output cleared'); +} + + +// Function to save the output area content to a file +function saveOutputArea() { + // Get the content from the textarea + const fridaOut = document.getElementById('FridaOut'); + const content = fridaOut.value; + + // If content is empty, show notification and return + if (!content.trim()) { + showNotification('Nothing to save'); + return; + } + + // Show save dialog + dialog.showSaveDialog( + { + title: "Save Frida Output", + defaultPath: "frida-output.txt", + filters: [ + { name: 'Text Files', extensions: ['txt'] }, + { name: 'Log Files', extensions: ['log'] }, + { name: 'All Files', extensions: ['*'] } + ] + } + ).then(result => { + if (result.filePath) { + // Write the content to the file + fs.writeFile(result.filePath, content, (err) => { + if (err) { + appendFridaLog("[!] Error saving output: " + err.message); + return; + } else { + showNotification('Output saved successfully'); + appendFridaLog("[+] Output saved.."); + appendFridaLog(" |-> Path: " + result.filePath); + } + }); + } + }).catch(err => { + appendFridaLog("[!] Error saving output: " + err); + }); +} + // Load settings from localStorage function loadSettings() { const savedSettings = localStorage.getItem('fermion_output_settings'); @@ -1570,9 +1928,18 @@ function loadSettings() { highlightTextColor = getContrastColor(highlightColor); } + if (settings.currentMatchColor) { + currentMatchColor = settings.currentMatchColor; + currentMatchColorPicker.value = currentMatchColor; + currentMatchColorHex.value = currentMatchColor; + currentMatchTextColor = getContrastColor(currentMatchColor); + } + // Set CSS variables document.documentElement.style.setProperty('--highlight-bg', highlightColor); document.documentElement.style.setProperty('--highlight-text', highlightTextColor); + document.documentElement.style.setProperty('--current-match-bg', currentMatchColor); + document.documentElement.style.setProperty('--current-match-text', currentMatchTextColor); // Apply layout direction if (settings.hasOwnProperty('isVertical')) { @@ -1585,6 +1952,38 @@ function loadSettings() { }, 500); } } + + // Apply Monaco Editor theme + if (settings.monacoTheme) { + // Wait for Monaco editor to be fully initialized + setTimeout(function () { + const themeSelect = document.getElementById("MonacoThemeSelect"); + if (themeSelect) { + // Set the dropdown value + themeSelect.value = settings.monacoTheme; + // Apply the theme + setMonacoTheme(); + } + }, 1000); // Allow time for Monaco to initialize + } + + // Apply Monaco Editor wrap setting + if (settings.hasOwnProperty('monacoWrap')) { + setTimeout(function () { + const wrapToggle = document.getElementById("FermionMonacoWrap"); + if (wrapToggle && wrapToggle.children[0]) { + // Set the checkbox state + wrapToggle.children[0].checked = settings.monacoWrap; + + // Apply the wrap setting to Monaco + if (MonacoCodeEditor) { + MonacoCodeEditor.updateOptions({ + wordWrap: settings.monacoWrap ? "on" : "off" + }); + } + } + }, 1000); // Allow time for Monaco to initialize + } } catch (error) { console.error('Error loading settings:', error); } @@ -1621,4 +2020,27 @@ document.addEventListener("keydown", function (e) { //appendFridaLog('[i] Refresh prevented (Ctrl+R)'); return false; } -}, false); \ No newline at end of file +}, false); + +// Add event listeners to save settings when Monaco editor settings change +function addMonacoSettingsListeners() { + // Theme change listener + const themeSelect = document.getElementById("MonacoThemeSelect"); + if (themeSelect) { + themeSelect.addEventListener('change', function () { + // The setMonacoTheme function is already called by the onchange attribute, + // so we just need to save the settings + setTimeout(saveSettings, 100); + }); + } + + // Wrap toggle listener - add this only if the existing onclick doesn't save settings + const wrapToggle = document.getElementById("FermionMonacoWrap"); + if (wrapToggle) { + wrapToggle.addEventListener('click', function () { + // The existing onclick handler toggles the wrap state, + // we just need to save the settings afterward + setTimeout(saveSettings, 100); + }); + } +} From 8ee5fa5385d939a830416eb2094f7954c10cc3a3 Mon Sep 17 00:00:00 2001 From: Watson Date: Mon, 14 Apr 2025 14:29:32 -0600 Subject: [PATCH 4/7] Update custom.css Updated to include output save, output clear, and search feature enhancements. --- Fermion/src/custom.css | 848 ++++++++++++++++++++++------------------- 1 file changed, 453 insertions(+), 395 deletions(-) diff --git a/Fermion/src/custom.css b/Fermion/src/custom.css index 2e1eaa4..e899eff 100644 --- a/Fermion/src/custom.css +++ b/Fermion/src/custom.css @@ -1,406 +1,464 @@ .gutter { - background-color: #8a8a8a; - background-repeat: no-repeat; - background-position: calc(50% - 7rem); - } - - .gutter.gutter-vertical { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII='); - cursor: row-resize; - } - - .gutter.gutter-horizontal { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); + background-color: #8a8a8a; + background-repeat: no-repeat; + background-position: calc(50% - 7rem); +} + +.gutter.gutter-vertical { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII='); + cursor: row-resize; +} + +.gutter.gutter-horizontal { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); + cursor: col-resize; +} + +.MonacoEditorRow { + height: calc(65vh - 76px); +} + +textarea { + border: none; + overflow: auto; + outline: none; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + resize: none !important; +} + +textarea#FridaOut { + width: 100%; + /* Take full width of container */ + height: 100%; + overflow: visible; + border: 1px solid transparent; + color: #ffffff; + font-family: monospace; + font-size: 0.8em; + line-height: 1.7em; + background-color: #423636; + padding: 8px; + box-sizing: border-box; +} + +textarea#FridaOut::-moz-selection { + color: white; + background: rgb(194, 194, 194); +} + +textarea#FridaOut::selection { + color: white; + background: rgb(194, 194, 194); +} + +.ui.toggle.checkbox input:checked~label:before { + background-color: #424d5c !important; +} + +/* Fix for toolbar spacing and overflow issues */ +.frida-toolbar { + display: flex; + background-color: #2d2d2d; + padding: 5px 10px; + border-bottom: 1px solid #555; + align-items: center; + justify-content: flex-start; + flex-wrap: nowrap; + gap: 8px; + width: 100%; + /* Take full width of parent container */ + box-sizing: border-box; + max-width: 100%; + /* Prevent overflow */ + overflow: hidden; +} + +/* Group non-search controls to keep them together */ +.toolbar-controls { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; + /* Don't allow these controls to shrink */ +} + +/* Make search container responsive */ +/* Updated search container to better handle the clear button */ +.search-container { + display: flex; + margin-left: auto; + max-width: 220px; + /* Increased to accommodate navigation buttons */ + min-width: 80px; + width: auto; + flex: 0 1 auto; + gap: 2px; + align-items: center; +} + +/* Search navigation buttons container */ +.search-nav-buttons { + display: none; + /* Hidden by default, shown when search has results */ + align-items: center; + margin-right: 5px; +} + +/* Hide clear button by default */ +#clear-search { + display: none; +} + +/* Show clear button only when there's text in the search box */ +#regex-search:not(:placeholder-shown)~#clear-search { + display: flex; +} + +.search-container input { + flex: 1; + background-color: #3a3a3a; + border: 1px solid #555; + border-radius: 4px; + color: #fff; + padding: 5px; + font-size: 12px; + min-width: 0; + /* Allow input to shrink below min-content */ + width: 100%; + text-overflow: ellipsis; + /* Show ellipsis for overflow text */ +} + +/* Make the search button more compact if needed */ +.search-container .toolbar-button { + padding: 3px; + flex-shrink: 0; +} + +/* Make toolbar buttons more compact */ +.toolbar-button { + background: none; + border: none; + cursor: pointer; + color: #ccc; + padding: 4px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; +} + +.toolbar-button:hover { + background-color: #3a3a3a; +} + +/* Make font-controls more compact */ +.font-controls { + display: flex; + margin: 0; + gap: 2px; +} + +/* Updates for split panels */ +#split-1 { + display: flex; + flex-direction: column; + width: 100%; + min-width: 0; + /* Allow the split panel to shrink */ + overflow: hidden; +} + +/* Ensure the textarea container takes full available width */ +.textarea-container { + flex: 1; + position: relative; + overflow: hidden; + width: 100%; +} + +/* Handle container width for split views properly */ +.container.px-4.mx-auto { + width: 100%; + max-width: 100%; + padding-left: 0; + padding-right: 0; + margin-left: 0; + overflow: hidden; +} + +/* Ensure the split component takes proper width */ +.split { + width: 100% !important; + max-width: 100%; + min-width: 0; + overflow: hidden; +} + +/* Adjust the split component for horizontal orientation */ +.split.horizontal { + display: flex; + flex-direction: row; + height: 100%; +} + +.split.horizontal>#split-0, +.split.horizontal>#split-1 { + height: 100% !important; + min-height: 100%; + /* Ensure minimum height */ +} + +.gutter.gutter-horizontal { + width: 8px !important; + min-width: 8px !important; + cursor: col-resize; +} + +@media (max-width: 800px) { + .gutter-horizontal { cursor: col-resize; + min-width: 8px !important; } - - .MonacoEditorRow { - height: calc(65vh - 76px); - } - - textarea { - border: none; - overflow: auto; - outline: none; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; - resize: none !important; - } - - textarea#FridaOut { - width: 100%; - /* Take full width of container */ - height: 100%; - overflow: visible; - border: 1px solid transparent; - color: #ffffff; - font-family: monospace; - font-size: 0.8em; - line-height: 1.7em; - background-color: #423636; - padding: 8px; - box-sizing: border-box; - } - - textarea#FridaOut::-moz-selection { - color: white; - background: rgb(194, 194, 194); - } - - textarea#FridaOut::selection { - color: white; - background: rgb(194, 194, 194); - } - - .ui.toggle.checkbox input:checked~label:before { - background-color: #424d5c !important; +} + +/* Responsive adjustments for Monaco editor container */ +#split-0 { + min-width: 0; + max-width: 100%; +} + +#split-0 #container { + width: 100% !important; + height: 100% !important; +} + +#container { + height: 100%; + width: 100% !important; + /* Override inline styles */ + max-width: 100%; + min-width: 0; + overflow: visible; +} + +/* Updated highlight overlay styling */ +.highlight-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + color: transparent; + overflow: auto; + font-family: monospace; + font-size: 0.8em; + line-height: 1.7em; + padding: 8px; + box-sizing: border-box; + border: 1px solid transparent; + z-index: 5; + white-space: pre-wrap !important; + /* Critical for proper alignment */ +} + +/* Improved highlight match styling */ +.highlight-match { + background-color: var(--highlight-bg, #ffff00); + color: var(--highlight-text, #000000) !important; + padding: 0; + border-radius: 2px; + display: inline; + position: relative; + white-space: pre-wrap !important; + /* Critical for proper alignment */ +} + +/* Style for current match highlighting */ +.highlight-match[data-match-index].current-match { + background-color: var(--current-match-bg, #FFD300) !important; + color: var(--current-match-text, #000000) !important; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2); + animation: pulse-highlight 1s ease-out 1; +} + +/* Animation for current match to make it more visible */ +@keyframes pulse-highlight { + 0% { + box-shadow: 0 0 0 0 rgba(var(--current-match-bg-rgb, 255, 211, 0), 0.5); + } + + 70% { + box-shadow: 0 0 0 3px rgba(var(--current-match-bg-rgb, 255, 211, 0), 0); + } + + 100% { + box-shadow: 0 0 0 0 rgba(var(--current-match-bg-rgb, 255, 211, 0), 0); + } +} + +.highlight-match[data-match-index].current-match { + animation: pulse-highlight 1s ease-out 1; +} + +/* Color Modal */ +.color-modal { + display: none; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0, 0, 0, 0.5); +} + +.color-modal-content { + background-color: #2d2d2d; + margin: 15% auto; + padding: 20px; + border: 1px solid #555; + width: 400px; + border-radius: 8px; + color: #ccc; +} + +.close-modal { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; + cursor: pointer; +} + +.close-modal:hover, +.close-modal:focus { + color: #fff; + text-decoration: none; +} + +.color-setting { + margin: 15px 0; +} + +.color-input-group { + display: flex; + align-items: center; + margin-top: 5px; +} + +.color-input-group input[type="color"] { + width: 40px; + height: 25px; + background-color: transparent; + border: none; + margin-right: 10px; +} + +.color-input-group input[type="text"] { + flex: 1; + background-color: #3a3a3a; + border: 1px solid #555; + border-radius: 4px; + color: #fff; + padding: 5px 10px; + font-size: 12px; +} + +.modal-buttons { + display: flex; + justify-content: flex-end; + margin-top: 20px; +} + +.modal-buttons button { + margin-left: 10px; + padding: 6px 12px; + background-color: #424d5c; + border: none; + border-radius: 4px; + color: #fff; + cursor: pointer; +} + +.modal-buttons button:hover { + background-color: #536580; +} + +/* Match indicator styling */ +.match-indicator { + position: absolute; + bottom: 10px; + right: 10px; + background-color: #424d5c; + color: #fff; + padding: 5px 10px; + border-radius: 4px; + font-size: 12px; + z-index: 10; + pointer-events: none; + opacity: 0.95; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); +} + +/* Notification styling */ +.notification { + position: fixed; + bottom: 20px; + right: 20px; + background-color: #424d5c; + color: #fff; + padding: 8px 16px; + border-radius: 4px; + z-index: 1000; + font-size: 12px; + opacity: 0.9; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + animation: fadeInOut 3s ease-in-out; +} + +@keyframes fadeInOut { + 0% { + opacity: 0; + } + + 10% { + opacity: 0.9; } - - /* Fix for toolbar spacing and overflow issues */ - .frida-toolbar { - display: flex; - background-color: #2d2d2d; - padding: 5px 10px; - border-bottom: 1px solid #555; - align-items: center; - justify-content: flex-start; - flex-wrap: nowrap; - gap: 8px; - width: 100%; - /* Take full width of parent container */ - box-sizing: border-box; - max-width: 100%; - /* Prevent overflow */ - overflow: hidden; + + 80% { + opacity: 0.9; } - - /* Group non-search controls to keep them together */ - .toolbar-controls { - display: flex; - align-items: center; - gap: 8px; - flex-shrink: 0; - /* Don't allow these controls to shrink */ + + 100% { + opacity: 0; } - - /* Make search container responsive */ - /* Updated search container to better handle the clear button */ +} + +/* Responsive adjustments */ +@media (max-width: 800px) { .search-container { - display: flex; - margin-left: auto; max-width: 180px; - min-width: 80px; - width: auto; - flex: 0 1 auto; - gap: 2px; - } - - - /* Hide clear button by default */ - #clear-search { - display: none; - } - - /* Show clear button only when there's text in the search box */ - #regex-search:not(:placeholder-shown) ~ #clear-search { - display: flex; + /* Smaller on small screens */ } - - .search-container input { - flex: 1; - background-color: #3a3a3a; - border: 1px solid #555; - border-radius: 4px; - color: #fff; - padding: 5px; - font-size: 12px; - min-width: 0; - /* Allow input to shrink below min-content */ - width: 100%; - text-overflow: ellipsis; - /* Show ellipsis for overflow text */ + + .search-nav-buttons { + margin-right: 2px; } - - /* Make the search button more compact if needed */ - .search-container .toolbar-button { - padding: 3px; - flex-shrink: 0; - } - - /* Make toolbar buttons more compact */ - .toolbar-button { - background: none; - border: none; - cursor: pointer; - color: #ccc; - padding: 4px; - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; - } - - .toolbar-button:hover { - background-color: #3a3a3a; - } - - /* Make font-controls more compact */ - .font-controls { - display: flex; - margin: 0; - gap: 2px; - } - - /* Updates for split panels */ - #split-1 { - display: flex; - flex-direction: column; - width: 100%; - min-width: 0; - /* Allow the split panel to shrink */ - overflow: hidden; - } - - /* Ensure the textarea container takes full available width */ - .textarea-container { - flex: 1; - position: relative; - overflow: hidden; - width: 100%; - } - - /* Handle container width for split views properly */ - .container.px-4.mx-auto { - width: 100%; - max-width: 100%; - padding-left: 0; - padding-right: 0; - margin-left: 0; - overflow: hidden; - } - - /* Ensure the split component takes proper width */ - .split { - width: 100% !important; - max-width: 100%; - min-width: 0; - overflow: hidden; - } - - /* Adjust the split component for horizontal orientation */ - .split.horizontal { - display: flex; - flex-direction: row; - height: 100%; - } - - .split.horizontal>#split-0, - .split.horizontal>#split-1 { - height: 100% !important; - min-height: 100%; - /* Ensure minimum height */ - } - - .gutter.gutter-horizontal { - width: 8px !important; - min-width: 8px !important; - cursor: col-resize; - } - - @media (max-width: 800px) { - .gutter-horizontal { - cursor: col-resize; - min-width: 8px !important; - } - } - - /* Responsive adjustments for Monaco editor container */ - #split-0 { - min-width: 0; - max-width: 100%; - } - - #split-0 #container { - width: 100% !important; - height: 100% !important; - } - - #container { - height: 100%; - width: 100% !important; - /* Override inline styles */ - max-width: 100%; - min-width: 0; - overflow: visible; - } - - /* Updated highlight overlay styling */ - .highlight-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - pointer-events: none; - color: transparent; - overflow: auto; - font-family: monospace; - font-size: 0.8em; - line-height: 1.7em; - padding: 8px; - box-sizing: border-box; - border: 1px solid transparent; - z-index: 5; - white-space: pre-wrap !important; /* Critical for proper alignment */ - } - - /* Improved highlight match styling */ - .highlight-match { - background-color: var(--highlight-bg, #ffff00); - color: var(--highlight-text, #000000) !important; - padding: 0; - border-radius: 2px; - display: inline; - position: relative; - white-space: pre-wrap !important; /* Critical for proper alignment */ - } - - /* Color Modal */ - .color-modal { - display: none; - position: fixed; - z-index: 1000; - left: 0; - top: 0; - width: 100%; - height: 100%; - overflow: auto; - background-color: rgba(0, 0, 0, 0.5); - } - - .color-modal-content { - background-color: #2d2d2d; - margin: 15% auto; - padding: 20px; - border: 1px solid #555; - width: 400px; - border-radius: 8px; - color: #ccc; - } - - .close-modal { - color: #aaa; - float: right; - font-size: 28px; - font-weight: bold; - cursor: pointer; - } - - .close-modal:hover, - .close-modal:focus { - color: #fff; - text-decoration: none; - } - - .color-setting { - margin: 15px 0; - } - - .color-input-group { - display: flex; - align-items: center; - margin-top: 5px; - } - - .color-input-group input[type="color"] { - width: 40px; - height: 25px; - background-color: transparent; - border: none; - margin-right: 10px; - } - - .color-input-group input[type="text"] { - flex: 1; - background-color: #3a3a3a; - border: 1px solid #555; - border-radius: 4px; - color: #fff; - padding: 5px 10px; - font-size: 12px; - } - - .modal-buttons { - display: flex; - justify-content: flex-end; - margin-top: 20px; - } - - .modal-buttons button { - margin-left: 10px; - padding: 6px 12px; - background-color: #424d5c; - border: none; - border-radius: 4px; - color: #fff; - cursor: pointer; - } - - .modal-buttons button:hover { - background-color: #536580; - } - - /* Match indicator styling */ + .match-indicator { - position: absolute; - bottom: 10px; - right: 10px; - background-color: #424d5c; - color: #fff; - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - z-index: 10; - pointer-events: none; - opacity: 0.9; - box-shadow: 0 1px 3px rgba(0,0,0,0.2); - } - - /* Notification styling */ - .notification { - position: fixed; - bottom: 20px; - right: 20px; - background-color: #424d5c; - color: #fff; - padding: 8px 16px; - border-radius: 4px; - z-index: 1000; - font-size: 12px; - opacity: 0.9; - box-shadow: 0 2px 4px rgba(0,0,0,0.2); - animation: fadeInOut 3s ease-in-out; - } - - - @keyframes fadeInOut { - 0% { opacity: 0; } - 10% { opacity: 0.9; } - 80% { opacity: 0.9; } - 100% { opacity: 0; } - } - - - /* Responsive adjustments */ - @media (max-width: 800px) { - .search-container { - max-width: 140px; - min-width: 70px; - } - - .match-indicator { - font-size: 10px; - padding: 3px 6px; - } - } \ No newline at end of file + font-size: 10px; + padding: 3px 6px; + } +} + +:root { + --highlight-bg: #ffff00; + --highlight-text: #000000; + --current-match-bg: #FFD300; + --current-match-text: #000000; +} From dea09feadfdb7131d0955b2282694af6c2dc9231 Mon Sep 17 00:00:00 2001 From: Watson Date: Mon, 14 Apr 2025 14:30:10 -0600 Subject: [PATCH 5/7] Update index.html Updated to include output save, output clear, and search feature enhancements. --- Fermion/pages/index.html | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Fermion/pages/index.html b/Fermion/pages/index.html index cb13ba9..8a87419 100644 --- a/Fermion/pages/index.html +++ b/Fermion/pages/index.html @@ -231,6 +231,24 @@

Options

+ +
@@ -283,6 +301,13 @@

Color Settings

+
+ +
+ + +
+