Holistiplan Scenario Highlighter

Highlights specific financial fields (Taxable Social Security, Loss Carryforward, Adjusted Gross Income) in selected scenarios

Size

10.2 KB

Version

1.1.4

Created

Nov 21, 2025

Updated

22 days ago

1// ==UserScript==
2// @name		Holistiplan Scenario Highlighter
3// @description		Highlights specific financial fields (Taxable Social Security, Loss Carryforward, Adjusted Gross Income) in selected scenarios
4// @version		1.1.4
5// @match		https://*.app.holistiplan.com/*
6// @icon		https://holistiplan-assets-static.s3.amazonaws.com/static/images/favicon.054031c9cb9a.ico
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('Holistiplan Highlight Extension loaded');
12
13    // Debounce function for MutationObserver
14    function debounce(func, wait) {
15        let timeout;
16        return function executedFunction(...args) {
17            const later = () => {
18                clearTimeout(timeout);
19                func(...args);
20            };
21            clearTimeout(timeout);
22            timeout = setTimeout(later, wait);
23        };
24    }
25
26    // Initialize the extension
27    async function init() {
28        console.log('Initializing Highlight button...');
29        
30        // Wait for the page to be fully loaded
31        if (document.readyState === 'loading') {
32            document.addEventListener('DOMContentLoaded', addHighlightButton);
33        } else {
34            addHighlightButton();
35        }
36
37        // Watch for dynamic content changes
38        const observer = new MutationObserver(debounce(() => {
39            if (!document.querySelector('#highlight-button')) {
40                addHighlightButton();
41            }
42        }, 500));
43
44        observer.observe(document.body, {
45            childList: true,
46            subtree: true
47        });
48    }
49
50    // Add the Highlight button next to Access Log
51    function addHighlightButton() {
52        // Find the Access Log button
53        const accessLogButton = document.querySelector('.btn.btn-primary.btn-sm.ml-2.hide_print');
54        
55        if (!accessLogButton) {
56            console.log('Access Log button not found yet');
57            return;
58        }
59
60        // Check if button already exists
61        if (document.querySelector('#highlight-button')) {
62            console.log('Highlight button already exists');
63            return;
64        }
65
66        // Create the Highlight button
67        const highlightButton = document.createElement('div');
68        highlightButton.id = 'highlight-button';
69        highlightButton.className = 'btn btn-warning btn-sm ml-2 hide_print';
70        highlightButton.innerHTML = 'Highlight <i class="fa fa-highlighter"></i>';
71        highlightButton.style.cursor = 'pointer';
72
73        // Add click event
74        highlightButton.addEventListener('click', handleHighlightClick);
75
76        // Insert the button after Access Log button
77        accessLogButton.parentNode.insertBefore(highlightButton, accessLogButton.nextSibling);
78        
79        console.log('Highlight button added successfully');
80    }
81
82    // Handle the highlight button click
83    async function handleHighlightClick() {
84        console.log('Highlight button clicked');
85
86        // Get all scenario columns
87        const scenarios = getScenarioColumns();
88        
89        if (scenarios.length === 0) {
90            alert('No scenarios found on this page.');
91            return;
92        }
93
94        // Create a prompt with scenario options
95        let promptMessage = 'Which scenario would you like to highlight?\n\n';
96        scenarios.forEach((scenario, index) => {
97            promptMessage += `${index + 1}. ${scenario.name}\n`;
98        });
99        promptMessage += '\nEnter the number of the scenario:';
100
101        const userInput = prompt(promptMessage);
102        
103        if (!userInput) {
104            console.log('User cancelled');
105            return;
106        }
107
108        const scenarioIndex = parseInt(userInput) - 1;
109        
110        if (isNaN(scenarioIndex) || scenarioIndex < 0 || scenarioIndex >= scenarios.length) {
111            alert('Invalid scenario number. Please try again.');
112            return;
113        }
114
115        const selectedScenario = scenarios[scenarioIndex];
116        console.log('Selected scenario:', selectedScenario);
117
118        // Highlight the fields in the selected scenario
119        highlightFields(selectedScenario);
120    }
121
122    // Get all scenario columns from the page
123    function getScenarioColumns() {
124        const scenarios = [];
125        
126        // Find the header row
127        const headerRow = document.querySelector('.row-container.header-row');
128        if (!headerRow) {
129            console.log('Header row not found');
130            return scenarios;
131        }
132
133        // Get all scenario header data slots
134        const headerDataSlots = headerRow.querySelectorAll('.data-slot.header-data');
135        
136        headerDataSlots.forEach((dataSlot, index) => {
137            // Get the scenario index text (e.g., "SCENARIO 1")
138            const scenarioIndexElement = dataSlot.querySelector('.header-text.scenario-index');
139            
140            if (scenarioIndexElement) {
141                const scenarioName = scenarioIndexElement.textContent.trim();
142                scenarios.push({
143                    name: scenarioName,
144                    columnIndex: index, // Use 0-based index for array access
145                    headerElement: dataSlot
146                });
147            }
148        });
149
150        console.log('Found scenarios:', scenarios);
151        return scenarios;
152    }
153
154    // Highlight specific fields in the selected scenario
155    function highlightFields(scenario) {
156        console.log('Highlighting fields for scenario:', scenario.name);
157
158        // Clear any previous highlights
159        clearHighlights();
160
161        let highlightedCount = 0;
162
163        // Find all row containers
164        const allRowContainers = document.querySelectorAll('.row-container');
165        
166        allRowContainers.forEach(rowContainer => {
167            // Get the label from this row
168            const labelElement = rowContainer.querySelector('.label-slot .main-label');
169            if (!labelElement) return;
170
171            const labelText = labelElement.textContent.trim().toLowerCase();
172            
173            // Get the parent div's ID
174            const parentDiv = rowContainer.parentElement;
175            const rowId = parentDiv ? parentDiv.id.toLowerCase() : '';
176
177            // Check if this row contains one of our target fields
178            let shouldHighlight = false;
179            
180            // Federal Taxable Social Security only (exclude state returns)
181            if ((labelText.includes('taxable social security') || rowId.includes('taxable_social_security')) &&
182                rowId === 'social_security_taxable') {
183                shouldHighlight = true;
184            } else if (labelText.includes('adjusted gross income') ||
185                       labelText === 'agi' ||
186                       rowId.includes('adjusted_gross_income') ||
187                       rowId === 'agi') {
188                shouldHighlight = true;
189            }
190
191            if (shouldHighlight) {
192                // Get all data slots in this row
193                const dataSlots = rowContainer.querySelectorAll('.data-slot');
194                
195                // Highlight the data slot at the scenario's column index
196                if (dataSlots[scenario.columnIndex]) {
197                    const targetSlot = dataSlots[scenario.columnIndex];
198                    const targetElement = targetSlot.querySelector('.scenario-analysis-element');
199                    
200                    if (targetElement) {
201                        targetElement.classList.add('highlighted-field');
202                        targetElement.style.backgroundColor = '#ffeb3b';
203                        targetElement.style.border = '2px solid #ff9800';
204                        targetElement.style.transition = 'all 0.3s ease';
205                        highlightedCount++;
206                        console.log('Highlighted:', labelText, 'in column', scenario.columnIndex);
207                    }
208                }
209            }
210        });
211
212        // Also look for "loss to carry forward" which is nested inside other elements
213        const allElements = document.querySelectorAll('.scenario-analysis-element');
214        allElements.forEach(element => {
215            const elementLabel = element.querySelector('.element-label');
216            if (elementLabel) {
217                const labelText = elementLabel.textContent.trim().toLowerCase();
218                
219                if (labelText.includes('loss to carry forward') || labelText.includes('loss carryforward')) {
220                    // Check if this element belongs to the selected scenario
221                    const elementId = element.id;
222                    if (elementId && elementId.endsWith('_' + (scenario.columnIndex + 1))) {
223                        // Highlight only the inner element with sub-element class, not the parent
224                        const subElement = element.querySelector('.element.body-element.sub-element');
225                        if (subElement) {
226                            subElement.classList.add('highlighted-field');
227                            subElement.style.backgroundColor = '#ffeb3b';
228                            subElement.style.border = '2px solid #ff9800';
229                            subElement.style.transition = 'all 0.3s ease';
230                            highlightedCount++;
231                            console.log('Highlighted loss to carry forward in scenario', scenario.columnIndex + 1);
232                        }
233                    }
234                }
235            }
236        });
237
238        if (highlightedCount > 0) {
239            console.log(`Successfully highlighted ${highlightedCount} fields`);
240            
241            // Store the highlight state
242            GM.setValue('highlightedScenario', {
243                scenarioName: scenario.name,
244                columnIndex: scenario.columnIndex,
245                timestamp: Date.now()
246            });
247        } else {
248            alert('No matching fields found to highlight. The fields we look for are:\n- Taxable Social Security (Federal)\n- Loss to Carry Forward\n- Adjusted Gross Income');
249        }
250    }
251
252    // Clear all highlights
253    function clearHighlights() {
254        const highlightedElements = document.querySelectorAll('.highlighted-field');
255        highlightedElements.forEach(element => {
256            element.classList.remove('highlighted-field');
257            element.style.backgroundColor = '';
258            element.style.border = '';
259        });
260        console.log('Cleared previous highlights');
261    }
262
263    // Start the extension
264    init();
265})();
Holistiplan Scenario Highlighter | Robomonkey