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})();