@@ -70,8 +70,115 @@ const headerKeyInput = document.getElementById("header-key");
7070const headerValueInput = document . getElementById ( "header-value" ) ;
7171const addHeaderBtn = document . getElementById ( "add-header-btn" ) ;
7272const cancelEditBtn = document . getElementById ( "cancel-edit-btn" ) ;
73+ const customPayloadInput = document . getElementById ( "webhook-custom-payload" ) ;
74+ const variablesAutocomplete = document . getElementById ( "variables-autocomplete" ) ;
7375let headers = [ ] ;
7476
77+ // Define available variables for autocompletion
78+ const availableVariables = [
79+ "{{tab.title}}" , "{{tab.url}}" , "{{tab.id}}" , "{{tab.windowId}}" ,
80+ "{{tab.index}}" , "{{tab.pinned}}" , "{{tab.audible}}" , "{{tab.incognito}}" ,
81+ "{{tab.status}}" , "{{browser}}" , "{{platform}}" , "{{triggeredAt}}" , "{{identifier}}"
82+ ] ;
83+
84+ // Implement autocompletion for custom payload
85+ customPayloadInput . addEventListener ( 'input' , function ( e ) {
86+ const cursorPosition = this . selectionStart ;
87+ const text = this . value ;
88+ const textBeforeCursor = text . substring ( 0 , cursorPosition ) ;
89+
90+ // Check if we're typing a variable (starts with {{)
91+ const match = textBeforeCursor . match ( / \{ \{ [ \w \. \- ] * $ / ) ;
92+
93+ if ( match ) {
94+ const partialVar = match [ 0 ] ;
95+ const matchingVars = availableVariables . filter ( v => v . startsWith ( partialVar ) ) ;
96+
97+ if ( matchingVars . length > 0 ) {
98+ // Show autocomplete dropdown
99+ variablesAutocomplete . innerHTML = '' ;
100+ variablesAutocomplete . classList . remove ( 'hidden' ) ;
101+
102+ matchingVars . forEach ( variable => {
103+ const item = document . createElement ( 'div' ) ;
104+ item . className = 'autocomplete-item' ;
105+ item . textContent = variable ;
106+ item . addEventListener ( 'click' , ( ) => {
107+ // Insert the variable at cursor position
108+ const beforeVar = text . substring ( 0 , cursorPosition - partialVar . length ) ;
109+ const afterVar = text . substring ( cursorPosition ) ;
110+ const newText = beforeVar + variable + afterVar ;
111+ customPayloadInput . value = newText ;
112+
113+ // Move cursor after the inserted variable
114+ const newCursorPos = beforeVar . length + variable . length ;
115+ customPayloadInput . focus ( ) ;
116+ customPayloadInput . setSelectionRange ( newCursorPos , newCursorPos ) ;
117+
118+ // Hide autocomplete
119+ variablesAutocomplete . classList . add ( 'hidden' ) ;
120+ } ) ;
121+ variablesAutocomplete . appendChild ( item ) ;
122+ } ) ;
123+ } else {
124+ variablesAutocomplete . classList . add ( 'hidden' ) ;
125+ }
126+ } else {
127+ variablesAutocomplete . classList . add ( 'hidden' ) ;
128+ }
129+ } ) ;
130+
131+ // Hide autocomplete when clicking outside
132+ document . addEventListener ( 'click' , function ( e ) {
133+ if ( ! customPayloadInput . contains ( e . target ) && ! variablesAutocomplete . contains ( e . target ) ) {
134+ variablesAutocomplete . classList . add ( 'hidden' ) ;
135+ }
136+ } ) ;
137+
138+ // Add keyboard navigation for autocomplete
139+ customPayloadInput . addEventListener ( 'keydown' , function ( e ) {
140+ if ( variablesAutocomplete . classList . contains ( 'hidden' ) ) return ;
141+
142+ const items = variablesAutocomplete . querySelectorAll ( '.autocomplete-item' ) ;
143+ const activeItem = variablesAutocomplete . querySelector ( '.autocomplete-item.active' ) ;
144+ let activeIndex = - 1 ;
145+
146+ if ( activeItem ) {
147+ activeIndex = Array . from ( items ) . indexOf ( activeItem ) ;
148+ }
149+
150+ switch ( e . key ) {
151+ case 'ArrowDown' :
152+ e . preventDefault ( ) ;
153+ if ( activeIndex < items . length - 1 ) {
154+ if ( activeItem ) activeItem . classList . remove ( 'active' ) ;
155+ items [ activeIndex + 1 ] . classList . add ( 'active' ) ;
156+ items [ activeIndex + 1 ] . scrollIntoView ( { block : 'nearest' } ) ;
157+ }
158+ break ;
159+ case 'ArrowUp' :
160+ e . preventDefault ( ) ;
161+ if ( activeIndex > 0 ) {
162+ if ( activeItem ) activeItem . classList . remove ( 'active' ) ;
163+ items [ activeIndex - 1 ] . classList . add ( 'active' ) ;
164+ items [ activeIndex - 1 ] . scrollIntoView ( { block : 'nearest' } ) ;
165+ }
166+ break ;
167+ case 'Enter' :
168+ e . preventDefault ( ) ;
169+ if ( activeItem ) {
170+ activeItem . click ( ) ;
171+ } else if ( items . length > 0 ) {
172+ items [ 0 ] . click ( ) ;
173+ }
174+ break ;
175+ case 'Escape' :
176+ e . preventDefault ( ) ;
177+ variablesAutocomplete . classList . add ( 'hidden' ) ;
178+ break ;
179+ }
180+ } ) ;
181+
75182function renderHeaders ( ) {
76183 headersListDiv . textContent = '' ;
77184 headers . forEach ( ( header , idx ) => {
@@ -128,12 +235,21 @@ form.addEventListener("submit", async (e) => {
128235 const url = urlInput . value . trim ( ) ;
129236 const method = methodSelect . value ;
130237 const identifier = identifierInput . value . trim ( ) ;
238+ const customPayload = customPayloadInput . value . trim ( ) ;
131239 let { webhooks = [ ] } = await browser . storage . sync . get ( "webhooks" ) ;
132240
133241 if ( editWebhookId ) {
134242 // Edit mode: update existing webhook
135243 webhooks = webhooks . map ( ( wh ) =>
136- wh . id === editWebhookId ? { ...wh , label, url, method, headers : [ ...headers ] , identifier } : wh
244+ wh . id === editWebhookId ? {
245+ ...wh ,
246+ label,
247+ url,
248+ method,
249+ headers : [ ...headers ] ,
250+ identifier,
251+ customPayload : customPayload || null
252+ } : wh
137253 ) ;
138254 editWebhookId = null ;
139255 cancelEditBtn . classList . add ( "hidden" ) ;
@@ -146,6 +262,7 @@ form.addEventListener("submit", async (e) => {
146262 method,
147263 headers : [ ...headers ] ,
148264 identifier,
265+ customPayload : customPayload || null
149266 } ;
150267 webhooks . push ( newWebhook ) ;
151268 }
@@ -155,6 +272,9 @@ form.addEventListener("submit", async (e) => {
155272 urlInput . value = "" ;
156273 methodSelect . value = "POST" ;
157274 identifierInput . value = "" ;
275+ customPayloadInput . value = "" ;
276+ headerKeyInput . value = "" ;
277+ headerValueInput . value = "" ;
158278 headers = [ ] ;
159279 renderHeaders ( ) ;
160280 // Always reset to save button after submit
@@ -182,6 +302,8 @@ webhookList.addEventListener("click", async (e) => {
182302 urlInput . value = "" ;
183303 methodSelect . value = "POST" ;
184304 identifierInput . value = "" ;
305+ headerKeyInput . value = "" ;
306+ headerValueInput . value = "" ;
185307 headers = [ ] ;
186308 renderHeaders ( ) ;
187309 cancelEditBtn . classList . add ( "hidden" ) ;
@@ -196,6 +318,7 @@ webhookList.addEventListener("click", async (e) => {
196318 urlInput . value = webhook . url ;
197319 methodSelect . value = webhook . method || "POST" ;
198320 identifierInput . value = webhook . identifier || "" ;
321+ customPayloadInput . value = webhook . customPayload || "" ;
199322 headers = Array . isArray ( webhook . headers ) ? [ ...webhook . headers ] : [ ] ;
200323 renderHeaders ( ) ;
201324 cancelEditBtn . classList . remove ( "hidden" ) ;
@@ -213,6 +336,9 @@ cancelEditBtn.addEventListener("click", () => {
213336 urlInput . value = "" ;
214337 methodSelect . value = "POST" ;
215338 identifierInput . value = "" ;
339+ customPayloadInput . value = "" ;
340+ headerKeyInput . value = "" ;
341+ headerValueInput . value = "" ;
216342 headers = [ ] ;
217343 renderHeaders ( ) ;
218344 cancelEditBtn . classList . add ( "hidden" ) ;
0 commit comments