ultimate-jekyll-manager 0.0.199 → 0.0.201

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,9 +6,10 @@
6
6
  "Read(//Users/ian/Developer/Repositories/ITW-Creative-works/ultimate-jekyll-manager/src/**)",
7
7
  "Read(//Users/ian/Developer/Repositories/Trusteroo/trusteroo-website/**)",
8
8
  "Bash(cat:*)",
9
- "Read(//Users/ian/Developer/Repositories/ITW-Creative-Works/ultimate-jekyll-legacy/special/master/pages/admin/notifications/**)"
9
+ "Read(//Users/ian/Developer/Repositories/ITW-Creative-Works/ultimate-jekyll-legacy/special/master/pages/admin/notifications/**)",
10
+ "mcp__mcp-router__chrome-devtools__navigate_page"
10
11
  ],
11
12
  "deny": [],
12
13
  "ask": []
13
14
  }
14
- }
15
+ }
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const argv = require('yargs').argv;
2
+ const argv = require('yargs')(process.argv.slice(2)).parseSync();
3
3
  const cli = new (require('../dist/cli.js'))(argv);
4
4
  (async function() {
5
5
  'use strict';
@@ -156,9 +156,16 @@ function setAnalyticsUserId(user, webManager) {
156
156
  // Send user metadata to server (affiliate, UTM params, etc.)
157
157
  async function sendUserSignupMetadata(user, webManager) {
158
158
  try {
159
- // Check if this is a new user account (created in last 5 minutes)
159
+ // Skip on auth pages to avoid blocking redirect (metadata will be sent on destination page)
160
+ const pagePath = document.documentElement.getAttribute('data-page-path');
161
+ const authPages = ['/signin', '/signup', '/reset'];
162
+ if (authPages.includes(pagePath)) {
163
+ return;
164
+ }
165
+
166
+ // Check if this is a new user account (created in last X minutes)
160
167
  const accountAge = Date.now() - new Date(user.metadata.creationTime).getTime();
161
- const signupProcessed = webManager.storage().get('flags.signupProcessed', false);
168
+ const signupProcessed = webManager.storage().get('flags.signupProcessed', null) === user.uid;
162
169
 
163
170
  /* @dev-only:start */
164
171
  {
@@ -202,8 +209,8 @@ async function sendUserSignupMetadata(user, webManager) {
202
209
  // Log
203
210
  console.log('[Auth] User metadata sent successfully:', response);
204
211
 
205
- // Mark signup as sent (keep the attribution data for reference)
206
- webManager.storage().set('flags.signupProcessed', true);
212
+ // Mark signup as sent for this user (keep the attribution data for reference)
213
+ webManager.storage().set('flags.signupProcessed', user.uid);
207
214
  } catch (error) {
208
215
  console.error('[Auth] Error sending user metadata:', error);
209
216
  // Don't throw - we don't want to block the signup flow
@@ -71,6 +71,9 @@ export default function (Manager, options) {
71
71
  url.searchParams.set('itm_campaign', 'exit-popup');
72
72
  url.searchParams.set('itm_content', window.location.pathname);
73
73
  $button.href = url.toString();
74
+
75
+ // Remove data-bs-dismiss so the link navigation works properly
76
+ $button.removeAttribute('data-bs-dismiss');
74
77
  }
75
78
  }
76
79
 
@@ -13,6 +13,9 @@ export default function (Manager) {
13
13
  const url = new URL(window.location.href);
14
14
  const useAuthPopup = url.searchParams.get('authPopup') === 'true' || window !== window.top;
15
15
 
16
+ // Fire wakeup call immediately to prevent cold start on signup API call (fire-and-forget)
17
+ // wakeupServer(webManager);
18
+
16
19
  // Handle DOM ready
17
20
  webManager.dom().ready()
18
21
  .then(async () => {
@@ -653,4 +656,13 @@ export default function (Manager) {
653
656
  content_name: 'Password Reset'
654
657
  });
655
658
  }
659
+
660
+ // Wakeup server to prevent cold start on signup API call
661
+ function wakeupServer(webManager) {
662
+ const serverApiURL = `${webManager.getApiUrl()}/backend-manager/user/signup?wakeup=true`;
663
+
664
+ fetch(serverApiURL, { method: 'POST' })
665
+ .then(() => console.log('[Auth] Server wakeup sent'))
666
+ .catch(() => {}); // Silently ignore errors
667
+ }
656
668
  }
@@ -156,7 +156,7 @@ export class FormManager {
156
156
 
157
157
  // Focus the field with autofocus attribute if it exists (desktop only)
158
158
  const $autofocusField = this.$form.querySelector('[autofocus]');
159
- if ($autofocusField && !$autofocusField.disabled && window.Manager?.webManager?.utilities()?.getDeviceType() === 'desktop') {
159
+ if ($autofocusField && !$autofocusField.disabled && window.Manager?.webManager?.utilities()?.getDevice() === 'desktop') {
160
160
  this._focusField($autofocusField);
161
161
  }
162
162
  }
@@ -9,7 +9,7 @@ import authorizedFetch from '__main_assets__/js/libs/authorized-fetch.js';
9
9
  let webManager = null;
10
10
  let appData = null;
11
11
  let accountData = null;
12
- let connectionForms = new Map(); // Store FormManager instances for each provider
12
+ let connectionForms = new Map();
13
13
 
14
14
  // Supported providers (must match IDs in HTML)
15
15
  const supportedProviders = ['google', 'discord', 'github', 'twitter', 'facebook'];
@@ -38,79 +38,49 @@ export async function loadData(account, sharedAppData) {
38
38
 
39
39
  // Display available and connected OAuth providers
40
40
  function displayConnections() {
41
- console.log('[DEBUG] displayConnections() called');
42
-
43
- // Hide loading state
44
41
  const $loading = document.getElementById('connections-loading');
42
+
45
43
  if ($loading) {
46
44
  $loading.classList.add('d-none');
47
45
  }
48
46
 
49
- // Check if appData is loaded
50
47
  if (!appData) {
51
- console.log('[DEBUG] No appData, showing loading state');
52
- // Show loading if no app data yet
53
48
  if ($loading) {
54
49
  $loading.classList.remove('d-none');
55
50
  }
56
51
  return;
57
52
  }
58
53
 
59
- // Get available OAuth providers from app data
60
54
  const availableProviders = appData?.oauth2 || {};
61
55
  const userConnections = accountData?.oauth2 || {};
62
56
 
63
- console.log('[DEBUG] Available OAuth providers:', availableProviders);
64
- console.log('[DEBUG] User connections:', userConnections);
65
-
66
- // Check if any providers are configured
67
57
  let hasEnabledProviders = false;
68
58
 
69
- // Process each supported provider
70
59
  supportedProviders.forEach(providerId => {
71
- console.log(`[DEBUG] Processing provider: ${providerId}`);
72
-
73
60
  const providerSettings = availableProviders[providerId];
74
61
  const $providerElement = document.getElementById(`connection-${providerId}`);
75
62
 
76
- console.log(`[DEBUG] ${providerId} - providerSettings:`, providerSettings);
77
- console.log(`[DEBUG] ${providerId} - providerSettings.enabled:`, providerSettings?.enabled);
78
- console.log(`[DEBUG] ${providerId} - $providerElement exists:`, !!$providerElement);
79
-
80
63
  if (!$providerElement) {
81
- console.warn(`[DEBUG] ${providerId} - Provider element not found in DOM`);
82
64
  return;
83
65
  }
84
66
 
85
- // Check if provider is enabled
86
67
  const isEnabled = providerSettings && providerSettings.enabled !== false;
87
- console.log(`[DEBUG] ${providerId} - isEnabled:`, isEnabled);
88
68
 
89
69
  if (isEnabled) {
90
70
  hasEnabledProviders = true;
91
-
92
- // Show the provider element first
93
- console.log(`[DEBUG] ${providerId} - Removing d-none class`);
94
71
  $providerElement.classList.remove('d-none');
95
72
 
96
- // Initialize FormManager for this provider before updating status
97
- console.log(`[DEBUG] ${providerId} - About to initialize FormManager`);
98
73
  initializeProviderForm(providerId);
99
74
 
100
- // Update provider status based on user connection
101
75
  const userConnection = userConnections[providerId];
102
- console.log(`[DEBUG] ${providerId} - userConnection:`, userConnection);
103
- console.log(`[DEBUG] ${providerId} - About to update provider status`);
104
76
  updateProviderStatus(providerId, userConnection, providerSettings);
105
77
  } else {
106
- // Hide disabled providers
107
- console.log(`[DEBUG] ${providerId} - Provider disabled, adding d-none class`);
108
78
  $providerElement.classList.add('d-none');
109
79
  }
110
80
  });
111
81
 
112
- // Show empty message if no providers are enabled
113
82
  const $empty = document.getElementById('connections-empty');
83
+
114
84
  if ($empty) {
115
85
  if (hasEnabledProviders) {
116
86
  $empty.classList.add('d-none');
@@ -122,30 +92,15 @@ function displayConnections() {
122
92
 
123
93
  // Update provider status display
124
94
  function updateProviderStatus(providerId, userConnection, providerSettings) {
125
- console.log(`[DEBUG] updateProviderStatus() called for ${providerId}`);
126
- console.log(`[DEBUG] ${providerId} - userConnection:`, userConnection);
127
- console.log(`[DEBUG] ${providerId} - userConnection truthy:`, !!userConnection);
128
- console.log(`[DEBUG] ${providerId} - userConnection.identity:`, userConnection?.identity);
129
- console.log(`[DEBUG] ${providerId} - providerSettings:`, providerSettings);
130
-
131
95
  const $status = document.getElementById(`${providerId}-connection-status`);
132
96
  const $description = document.getElementById(`${providerId}-connection-description`);
133
97
  const $form = document.getElementById(`connection-form-${providerId}`);
134
98
  const $connectButton = $form?.querySelector('button[data-action="connect"]');
135
99
  const $disconnectButton = $form?.querySelector('button[data-action="disconnect"]');
136
100
 
137
- console.log(`[DEBUG] ${providerId} - DOM elements found:`, {
138
- $status: !!$status,
139
- $description: !!$description,
140
- $form: !!$form,
141
- $connectButton: !!$connectButton,
142
- $disconnectButton: !!$disconnectButton,
143
- });
144
-
145
101
  const isConnected = userConnection && userConnection.identity;
146
- console.log(`[DEBUG] ${providerId} - isConnected:`, isConnected);
147
102
 
148
- // Always show description on the left
103
+ // Set description
149
104
  if ($description) {
150
105
  const defaultDescriptions = {
151
106
  google: 'Enable single sign-on with your Google account',
@@ -155,19 +110,20 @@ function updateProviderStatus(providerId, userConnection, providerSettings) {
155
110
  facebook: 'Connect your Facebook account for social features',
156
111
  };
157
112
 
158
- // Use provider description or fallback to default
159
- const descriptionText = providerSettings?.description || defaultDescriptions[providerId] || `Connect your ${providerId.charAt(0).toUpperCase() + providerId.slice(1)} account`;
113
+ const descriptionText = providerSettings?.description
114
+ || defaultDescriptions[providerId]
115
+ || `Connect your ${providerId.charAt(0).toUpperCase() + providerId.slice(1)} account`;
116
+
160
117
  $description.textContent = descriptionText;
161
118
  $description.classList.remove('d-none');
162
119
  }
163
120
 
164
- // Handle connection status display under the button
121
+ // Set status
165
122
  if ($status) {
166
123
  if (isConnected) {
167
124
  const displayName = getConnectionDisplayName(userConnection);
168
125
  let statusText = `Connected: ${displayName}`;
169
126
 
170
- // Add last updated date if available
171
127
  if (userConnection.updated && userConnection.updated.timestamp) {
172
128
  const date = new Date(userConnection.updated.timestamp);
173
129
  const dateStr = date.toLocaleDateString('en-US', {
@@ -181,26 +137,19 @@ function updateProviderStatus(providerId, userConnection, providerSettings) {
181
137
  $status.textContent = statusText;
182
138
  $status.classList.remove('d-none');
183
139
  } else {
184
- // Not connected - hide status text
185
140
  $status.textContent = '';
186
141
  $status.classList.add('d-none');
187
142
  }
188
143
  }
189
144
 
190
- // Show/hide appropriate button
145
+ // Toggle buttons
191
146
  if ($connectButton && $disconnectButton) {
192
- console.log(`[DEBUG] ${providerId} - Updating buttons. isConnected:`, isConnected);
193
-
194
147
  if (isConnected) {
195
- // Hide connect button, show disconnect button
196
148
  $connectButton.classList.add('d-none');
197
149
  $disconnectButton.classList.remove('d-none');
198
- console.log(`[DEBUG] ${providerId} - Showing disconnect button`);
199
150
  } else {
200
- // Show connect button, hide disconnect button
201
151
  $connectButton.classList.remove('d-none');
202
152
  $disconnectButton.classList.add('d-none');
203
- console.log(`[DEBUG] ${providerId} - Showing connect button`);
204
153
  }
205
154
  }
206
155
  }
@@ -211,7 +160,6 @@ function getConnectionDisplayName(connection) {
211
160
  return 'Unknown';
212
161
  }
213
162
 
214
- // Try different fields based on provider
215
163
  return connection.identity.global_name
216
164
  || connection.identity.username
217
165
  || connection.identity.name
@@ -223,35 +171,24 @@ function getConnectionDisplayName(connection) {
223
171
  // Initialize FormManager for a provider
224
172
  function initializeProviderForm(providerId) {
225
173
  const formId = `connection-form-${providerId}`;
226
- const form = document.getElementById(formId);
174
+ const $form = document.getElementById(formId);
227
175
 
228
- if (!form) {
229
- console.warn(`Form not found for provider: ${providerId}`);
176
+ if (!$form) {
230
177
  return;
231
178
  }
232
179
 
233
- // Skip if already initialized
234
180
  if (connectionForms.has(providerId)) {
235
- console.log(`FormManager already initialized for ${providerId}`);
236
181
  return;
237
182
  }
238
183
 
239
- console.log(`Initializing FormManager for ${providerId}`);
240
-
241
184
  const formManager = new FormManager(`#${formId}`, {
242
185
  submittingText: 'Connecting...',
243
186
  });
244
187
 
245
- // Store the FormManager instance
246
188
  connectionForms.set(providerId, formManager);
247
189
 
248
- // Listen for state changes to update button after FormManager is ready
249
190
  formManager.on('statechange', ({ state }) => {
250
- console.log(`[DEBUG] ${providerId} - FormManager state changed to:`, state);
251
-
252
- // When FormManager transitions to ready, update the button status
253
191
  if (state === 'ready') {
254
- console.log(`[DEBUG] ${providerId} - FormManager is ready, updating button status`);
255
192
  const userConnection = accountData?.oauth2?.[providerId];
256
193
  const providerSettings = appData?.oauth2?.[providerId];
257
194
  updateProviderStatus(providerId, userConnection, providerSettings);
@@ -260,18 +197,14 @@ function initializeProviderForm(providerId) {
260
197
 
261
198
  formManager.on('submit', async ({ data, $submitButton }) => {
262
199
  const provider = data.provider;
263
-
264
- // Determine action from the clicked button's data-action attribute
265
200
  const action = $submitButton?.getAttribute('data-action');
266
201
 
267
- console.log(`[DEBUG] ${providerId} - Form submitted. Action:`, action, 'Provider:', provider);
268
-
269
202
  if (action === 'connect') {
270
203
  await handleConnect(provider);
271
204
  } else if (action === 'disconnect') {
272
205
  const success = await handleDisconnect(provider);
206
+
273
207
  if (success) {
274
- // Get provider settings to pass for description display
275
208
  const providerSettings = appData?.oauth2?.[provider];
276
209
  updateProviderStatus(provider, null, providerSettings);
277
210
  }
@@ -282,30 +215,25 @@ function initializeProviderForm(providerId) {
282
215
  // Handle connect action
283
216
  async function handleConnect(providerId) {
284
217
  const provider = appData?.oauth2?.[providerId];
218
+
285
219
  if (!provider || provider.enabled === false) {
286
220
  throw new Error('This connection service is not available.');
287
221
  }
288
222
 
289
- // Get scope from provider settings (pass as array)
290
- const scope = provider.scope || [];
223
+ // Build URL with query params for GET request
224
+ // Don't send scope - let the backend use the provider's default scopes
225
+ const url = new URL(getApiUrl());
226
+ url.searchParams.set('provider', providerId);
227
+ url.searchParams.set('action', 'authorize');
228
+ url.searchParams.set('redirect', 'false');
291
229
 
292
- const response = await authorizedFetch(getApiUrl(), {
293
- method: 'POST',
230
+ const response = await authorizedFetch(url.toString(), {
231
+ method: 'GET',
294
232
  timeout: 30000,
295
233
  response: 'json',
296
234
  tries: 2,
297
- body: {
298
- redirect: false,
299
- provider: providerId,
300
- state: 'authorize',
301
- scope: scope,
302
- referrer: window.location.href,
303
- },
304
235
  });
305
236
 
306
- console.log('OAuth connect response:', response);
307
-
308
- // For authorize requests, server returns an object with URL to redirect to
309
237
  if (response.url) {
310
238
  window.location.href = response.url;
311
239
  } else {
@@ -317,50 +245,36 @@ async function handleConnect(providerId) {
317
245
  async function handleDisconnect(providerId) {
318
246
  const providerName = providerId.charAt(0).toUpperCase() + providerId.slice(1);
319
247
 
320
- // Wait 1 ms
321
248
  await new Promise(resolve => setTimeout(resolve, 1));
322
249
 
323
- // Confirm disconnection
324
250
  if (!confirm(`Are you sure you want to disconnect your ${providerName} account?`)) {
325
251
  throw new Error('Disconnection cancelled');
326
252
  }
327
253
 
328
- // Get provider settings for scope (pass as array)
329
- const provider = appData?.oauth2?.[providerId] || {};
330
- const scope = provider.scope || [];
254
+ // Build URL with query params for DELETE request
255
+ const url = new URL(getApiUrl());
256
+ url.searchParams.set('provider', providerId);
331
257
 
332
- const response = await authorizedFetch(getApiUrl(), {
333
- method: 'POST',
258
+ const response = await authorizedFetch(url.toString(), {
259
+ method: 'DELETE',
334
260
  timeout: 30000,
335
261
  response: 'json',
336
262
  tries: 2,
337
- body: {
338
- redirect: false,
339
- provider: providerId,
340
- state: 'deauthorize',
341
- scope: scope,
342
- referrer: window.location.href,
343
- },
344
263
  });
345
264
 
346
- console.log('OAuth disconnect response:', response);
347
-
348
265
  if (response.success) {
349
- // Update local account data
350
266
  if (accountData.oauth2 && accountData.oauth2[providerId]) {
351
267
  delete accountData.oauth2[providerId];
352
268
  }
353
269
 
354
- // Return success
355
270
  return true;
356
- } else {
357
- throw new Error(response.message || 'Failed to disconnect');
358
271
  }
272
+
273
+ throw new Error(response.message || 'Failed to disconnect');
359
274
  }
360
275
 
361
276
  // Called when section is shown
362
277
  export function onShow() {
363
- // Refresh connections display when section is shown
364
278
  if (accountData && appData) {
365
279
  displayConnections();
366
280
  }
@@ -62,13 +62,6 @@ function setupDeleteAccountForm() {
62
62
  throw new Error('Account deletion cancelled. You must type "DELETE" exactly to confirm.');
63
63
  }
64
64
 
65
- // Final confirmation
66
- const finalConfirm = confirm('This is your last chance to cancel.\n\nAre you sure you want to permanently delete your account?');
67
-
68
- if (!finalConfirm) {
69
- throw new Error('Account deletion cancelled.');
70
- }
71
-
72
65
  // Send delete request to server
73
66
  const response = await authorizedFetch(`${webManager.getApiUrl()}/backend-manager/user`, {
74
67
  method: 'DELETE',
@@ -108,4 +101,4 @@ export async function loadData() {
108
101
  // Called when section is shown
109
102
  export function onShow() {
110
103
  // Nothing needed
111
- }
104
+ }
@@ -1,125 +1,74 @@
1
1
  // Libraries
2
2
  import authorizedFetch from '__main_assets__/js/libs/authorized-fetch.js';
3
+
3
4
  let webManager = null;
4
5
 
5
6
  // Module
6
- export default (Manager, options) => {
7
+ export default (Manager) => {
7
8
  return new Promise(async function (resolve) {
8
- // Set webManager
9
9
  webManager = Manager.webManager;
10
10
 
11
- // Initialize when DOM is ready
12
11
  await webManager.dom().ready();
13
12
 
14
- // Wait for auth state to be determined before handling OAuth callback
15
- // This is REQUIRED because authorizedFetch needs auth.currentUser to be available
13
+ // Wait for auth state before handling callback
14
+ // Required because authorizedFetch needs auth.currentUser
16
15
  webManager.auth().listen({ once: true }, () => {
17
- // Handle OAuth callback after auth state is known
18
16
  handleOAuthCallback();
19
17
  });
20
18
 
21
- // Resolve after initialization
22
19
  return resolve();
23
20
  });
24
21
  };
25
22
 
26
- // Parse and validate OAuth callback
23
+ // Handle OAuth callback
27
24
  async function handleOAuthCallback() {
28
- const $loading = document.getElementById('oauth2-loading');
29
- const $result = document.getElementById('oauth2-result');
30
25
  const $provider = document.getElementById('oauth2-provider');
31
- const $errorMessage = document.getElementById('error-message');
32
- const $returnButton = document.getElementById('return-button');
33
- const $resultSuccess = document.getElementById('result-success');
34
- const $resultError = document.getElementById('result-error');
35
26
 
36
27
  try {
37
- // Get URL parameters
38
28
  const urlParams = new URLSearchParams(window.location.search);
39
29
  const code = urlParams.get('code');
40
- const state = urlParams.get('state');
30
+ const encryptedState = urlParams.get('state');
41
31
  const error = urlParams.get('error');
42
32
  const errorDescription = urlParams.get('error_description');
43
33
 
44
- // Check for OAuth errors
34
+ // Check for OAuth errors from provider
45
35
  if (error) {
46
36
  throw new Error(errorDescription || error || 'OAuth authorization was denied');
47
37
  }
48
38
 
49
- // Validate required parameters
50
39
  if (!code) {
51
40
  throw new Error('Missing authorization code');
52
41
  }
53
42
 
54
- if (!state) {
43
+ if (!encryptedState) {
55
44
  throw new Error('Missing state parameter');
56
45
  }
57
46
 
58
- // Parse state
59
- let stateParsed;
60
- try {
61
- stateParsed = JSON.parse(decodeURIComponent(state));
62
- } catch (e) {
63
- console.error('Failed to parse state:', e);
64
- throw new Error('Invalid state parameter');
65
- }
66
-
67
- console.log('OAuth callback state:', stateParsed);
68
-
69
- // Validate state
70
- if (!stateParsed.provider) {
71
- throw new Error('Missing provider in state');
72
- }
73
-
74
- if (!stateParsed.serverUrl) {
75
- throw new Error('Missing server URL');
76
- }
77
-
78
- // Update provider name
79
- const providerName = capitalizeFirstLetter(stateParsed.provider);
80
- $provider.textContent = providerName;
81
-
82
- // Validate redirect URL
83
- if (stateParsed.redirectUrl && !webManager.isValidRedirectUrl(stateParsed.redirectUrl)) {
84
- throw new Error('Invalid redirect URL');
85
- }
86
-
87
- // Build tokenize payload
88
- const payload = {
89
- state: 'tokenize',
90
- provider: stateParsed.provider,
91
- code: code
92
- };
93
-
94
- // Add any additional OAuth parameters
95
- urlParams.forEach((value, key) => {
96
- if (key !== 'state' && key !== 'code') {
97
- payload[key] = value;
98
- }
99
- });
47
+ // Update provider display (we can't read encrypted state, so use generic text)
48
+ $provider.textContent = 'Provider';
100
49
 
101
- console.log('Tokenize payload:', payload);
50
+ // Build API URL using webManager (no need to read from state)
51
+ const apiUrl = `${webManager.getApiUrl()}/backend-manager/user/oauth2`;
102
52
 
103
- // Build the RESTful URL (replace /backend-manager with /backend-manager/user/oauth2)
104
- const serverUrl = stateParsed.serverUrl.replace(/\/backend-manager\/?$/, '/backend-manager/user/oauth2');
105
-
106
- // Call server to complete OAuth flow
107
- const response = await authorizedFetch(serverUrl, {
53
+ // Send tokenize request with encrypted state
54
+ // Note: tries=1 because auth codes can only be used once
55
+ const response = await authorizedFetch(apiUrl, {
108
56
  method: 'POST',
109
57
  timeout: 60000,
110
58
  response: 'json',
111
- tries: 2,
112
- body: payload,
59
+ tries: 1,
60
+ body: {
61
+ action: 'tokenize',
62
+ code: code,
63
+ encryptedState: encryptedState,
64
+ },
113
65
  });
114
66
 
115
- console.log('Tokenize response:', response);
116
-
117
67
  if (!response.success) {
118
68
  throw new Error(response.message || 'Failed to complete authorization');
119
69
  }
120
70
 
121
- // Show success
122
- showSuccess(stateParsed.redirectUrl || stateParsed.referrer || '/account#connections');
71
+ showSuccess();
123
72
 
124
73
  } catch (error) {
125
74
  console.error('OAuth callback error:', error);
@@ -128,19 +77,18 @@ async function handleOAuthCallback() {
128
77
  }
129
78
 
130
79
  // Show success state
131
- function showSuccess(redirectUrl) {
80
+ function showSuccess() {
132
81
  const $loading = document.getElementById('oauth2-loading');
133
82
  const $result = document.getElementById('oauth2-result');
134
83
  const $resultSuccess = document.getElementById('result-success');
135
84
 
136
- // Hide loading, show result
137
85
  $loading.classList.add('d-none');
138
86
  $result.classList.remove('d-none');
139
87
  $resultSuccess.classList.remove('d-none');
140
88
 
141
- // Redirect after delay
89
+ // Redirect to account page after delay
142
90
  setTimeout(() => {
143
- window.location.href = redirectUrl;
91
+ window.location.href = '/account#connections';
144
92
  }, 500);
145
93
  }
146
94
 
@@ -152,32 +100,12 @@ function showError(message) {
152
100
  const $errorMessage = document.getElementById('error-message');
153
101
  const $returnButton = document.getElementById('return-button');
154
102
 
155
- // Hide loading, show error
156
103
  $loading.classList.add('d-none');
157
104
  $result.classList.remove('d-none');
158
105
  $resultError.classList.remove('d-none');
159
106
 
160
- // Set error message
161
107
  $errorMessage.textContent = message;
162
108
 
163
- // Set return button href
164
- const urlParams = new URLSearchParams(window.location.search);
165
- const state = urlParams.get('state');
166
-
167
- if (state) {
168
- try {
169
- const state = JSON.parse(decodeURIComponent(state));
170
- if (state.referrer) {
171
- $returnButton.href = state.referrer;
172
- }
173
- } catch (e) {
174
- // Use default
175
- }
176
- }
177
- }
178
-
179
- // Capitalize first letter
180
- function capitalizeFirstLetter(string) {
181
- if (!string) return '';
182
- return string.charAt(0).toUpperCase() + string.slice(1);
109
+ // Default return URL
110
+ $returnButton.href = '/account#connections';
183
111
  }
@@ -9,12 +9,16 @@
9
9
  // ============================================
10
10
  $avatar-sizes: (
11
11
  null: 3rem, // default
12
+ 2xs: 0.5rem,
12
13
  xs: 1.5rem,
13
14
  sm: 2rem,
14
15
  md: 2.5rem,
15
16
  lg: 3.5rem,
16
17
  xl: 5rem,
17
- xxl: 7.5rem
18
+ 2xl: 7.5rem,
19
+ 3xl: 10rem,
20
+ 4xl: 12.5rem,
21
+ 5xl: 15rem
18
22
  ) !default;
19
23
 
20
24
  // ============================================
@@ -3,93 +3,66 @@
3
3
  // =============================================================================
4
4
  // Buttons that automatically flip between light/dark styles based on theme.
5
5
  // These are universal utilities available to all UJ themes.
6
- // Note: Cannot use @extend with Bootstrap classes due to module scoping,
7
- // so we use CSS custom properties directly.
6
+ // Uses CSS custom properties from _color-shades.scss for hover/active states.
8
7
 
9
8
  // ============================================
10
- // Button Style Mixins (DRY)
9
+ // Solid Adaptive Buttons
11
10
  // ============================================
12
- @mixin btn-dark-styles {
11
+ // Dark button in light mode, light button in dark mode
12
+ .btn-adaptive {
13
13
  --bs-btn-color: #fff;
14
14
  --bs-btn-bg: var(--bs-dark);
15
15
  --bs-btn-border-color: var(--bs-dark);
16
16
  --bs-btn-hover-color: #fff;
17
- --bs-btn-hover-bg: #1c1c1c;
18
- --bs-btn-hover-border-color: #1a1a1a;
19
- --bs-btn-focus-shadow-rgb: 66, 70, 73;
17
+ --bs-btn-hover-bg: var(--bs-dark-hover);
18
+ --bs-btn-hover-border-color: var(--bs-dark-active);
20
19
  --bs-btn-active-color: #fff;
21
- --bs-btn-active-bg: #1a1a1a;
22
- --bs-btn-active-border-color: #181818;
23
- --bs-btn-disabled-color: #fff;
24
- --bs-btn-disabled-bg: var(--bs-dark);
25
- --bs-btn-disabled-border-color: var(--bs-dark);
20
+ --bs-btn-active-bg: var(--bs-dark-active);
21
+ --bs-btn-active-border-color: var(--bs-dark-active);
26
22
  }
27
23
 
28
- @mixin btn-light-styles {
24
+ [data-bs-theme="dark"] .btn-adaptive {
29
25
  --bs-btn-color: #000;
30
26
  --bs-btn-bg: var(--bs-light);
31
27
  --bs-btn-border-color: var(--bs-light);
32
28
  --bs-btn-hover-color: #000;
33
- --bs-btn-hover-bg: #e9ecef;
34
- --bs-btn-hover-border-color: #e2e6ea;
35
- --bs-btn-focus-shadow-rgb: 211, 212, 213;
29
+ --bs-btn-hover-bg: var(--bs-light-hover);
30
+ --bs-btn-hover-border-color: var(--bs-light-active);
36
31
  --bs-btn-active-color: #000;
37
- --bs-btn-active-bg: #e2e6ea;
38
- --bs-btn-active-border-color: #dae0e5;
39
- --bs-btn-disabled-color: #000;
40
- --bs-btn-disabled-bg: var(--bs-light);
41
- --bs-btn-disabled-border-color: var(--bs-light);
32
+ --bs-btn-active-bg: var(--bs-light-active);
33
+ --bs-btn-active-border-color: var(--bs-light-active);
42
34
  }
43
35
 
44
- @mixin btn-outline-dark-styles {
36
+ // ============================================
37
+ // Outline Adaptive Buttons
38
+ // ============================================
39
+ .btn-outline-adaptive {
45
40
  --bs-btn-color: var(--bs-dark);
41
+ --bs-btn-bg: transparent;
46
42
  --bs-btn-border-color: var(--bs-dark);
47
43
  --bs-btn-hover-color: #fff;
48
44
  --bs-btn-hover-bg: var(--bs-dark);
49
45
  --bs-btn-hover-border-color: var(--bs-dark);
50
- --bs-btn-focus-shadow-rgb: 33, 37, 41;
51
46
  --bs-btn-active-color: #fff;
52
47
  --bs-btn-active-bg: var(--bs-dark);
53
48
  --bs-btn-active-border-color: var(--bs-dark);
54
- --bs-btn-disabled-color: var(--bs-dark);
55
- --bs-btn-disabled-border-color: var(--bs-dark);
49
+
50
+ color: var(--bs-body-color);
51
+ border-color: var(--bs-dark);
56
52
  }
57
53
 
58
- @mixin btn-outline-light-styles {
54
+ [data-bs-theme="dark"] .btn-outline-adaptive {
59
55
  --bs-btn-color: var(--bs-light);
56
+ --bs-btn-bg: transparent;
60
57
  --bs-btn-border-color: var(--bs-light);
61
58
  --bs-btn-hover-color: #000;
62
59
  --bs-btn-hover-bg: var(--bs-light);
63
60
  --bs-btn-hover-border-color: var(--bs-light);
64
- --bs-btn-focus-shadow-rgb: 248, 249, 250;
65
61
  --bs-btn-active-color: #000;
66
62
  --bs-btn-active-bg: var(--bs-light);
67
63
  --bs-btn-active-border-color: var(--bs-light);
68
- --bs-btn-disabled-color: var(--bs-light);
69
- --bs-btn-disabled-border-color: var(--bs-light);
70
- }
71
64
 
72
- // ============================================
73
- // Solid Adaptive Buttons
74
- // ============================================
75
- // Dark button in light mode, light button in dark mode
76
- .btn-adaptive {
77
- @include btn-dark-styles;
78
- }
79
-
80
- [data-bs-theme="dark"] .btn-adaptive {
81
- @include btn-light-styles;
82
- }
83
-
84
- // ============================================
85
- // Outline Adaptive Buttons
86
- // ============================================
87
- .btn-outline-adaptive {
88
- @include btn-outline-dark-styles;
89
- }
90
-
91
- [data-bs-theme="dark"] .btn-outline-adaptive {
92
- @include btn-outline-light-styles;
65
+ border-color: var(--bs-light);
93
66
  }
94
67
 
95
68
  // ============================================
@@ -97,19 +70,59 @@
97
70
  // ============================================
98
71
  // The opposite of adaptive - light in light mode, dark in dark mode
99
72
  .btn-adaptive-inverse {
100
- @include btn-light-styles;
73
+ --bs-btn-color: #000;
74
+ --bs-btn-bg: var(--bs-light);
75
+ --bs-btn-border-color: var(--bs-light);
76
+ --bs-btn-hover-color: #000;
77
+ --bs-btn-hover-bg: var(--bs-light-hover);
78
+ --bs-btn-hover-border-color: var(--bs-light-active);
79
+ --bs-btn-active-color: #000;
80
+ --bs-btn-active-bg: var(--bs-light-active);
81
+ --bs-btn-active-border-color: var(--bs-light-active);
101
82
  }
102
83
 
103
84
  [data-bs-theme="dark"] .btn-adaptive-inverse {
104
- @include btn-dark-styles;
85
+ --bs-btn-color: #fff;
86
+ --bs-btn-bg: var(--bs-dark);
87
+ --bs-btn-border-color: var(--bs-dark);
88
+ --bs-btn-hover-color: #fff;
89
+ --bs-btn-hover-bg: var(--bs-dark-hover);
90
+ --bs-btn-hover-border-color: var(--bs-dark-active);
91
+ --bs-btn-active-color: #fff;
92
+ --bs-btn-active-bg: var(--bs-dark-active);
93
+ --bs-btn-active-border-color: var(--bs-dark-active);
105
94
  }
106
95
 
96
+ // ============================================
97
+ // Outline Inverse Adaptive Buttons
98
+ // ============================================
107
99
  .btn-outline-adaptive-inverse {
108
- @include btn-outline-light-styles;
100
+ --bs-btn-color: var(--bs-light);
101
+ --bs-btn-bg: transparent;
102
+ --bs-btn-border-color: var(--bs-light);
103
+ --bs-btn-hover-color: #000;
104
+ --bs-btn-hover-bg: var(--bs-light);
105
+ --bs-btn-hover-border-color: var(--bs-light);
106
+ --bs-btn-active-color: #000;
107
+ --bs-btn-active-bg: var(--bs-light);
108
+ --bs-btn-active-border-color: var(--bs-light);
109
+
110
+ color: var(--bs-body-color);
111
+ border-color: var(--bs-light);
109
112
  }
110
113
 
111
114
  [data-bs-theme="dark"] .btn-outline-adaptive-inverse {
112
- @include btn-outline-dark-styles;
115
+ --bs-btn-color: var(--bs-dark);
116
+ --bs-btn-bg: transparent;
117
+ --bs-btn-border-color: var(--bs-dark);
118
+ --bs-btn-hover-color: #fff;
119
+ --bs-btn-hover-bg: var(--bs-dark);
120
+ --bs-btn-hover-border-color: var(--bs-dark);
121
+ --bs-btn-active-color: #fff;
122
+ --bs-btn-active-bg: var(--bs-dark);
123
+ --bs-btn-active-border-color: var(--bs-dark);
124
+
125
+ border-color: var(--bs-dark);
113
126
  }
114
127
 
115
128
  // ============================================
@@ -0,0 +1,25 @@
1
+ // =============================================================================
2
+ // Color Shade CSS Custom Properties
3
+ // =============================================================================
4
+ // Provides hover/active shade variants for light and dark colors.
5
+ // These are used by adaptive buttons and can be reused elsewhere.
6
+
7
+ :root,
8
+ [data-bs-theme="light"] {
9
+ // Dark color shades (for hover/active states on dark buttons)
10
+ --bs-dark-hover: #3a3a44;
11
+ --bs-dark-active: #34343e;
12
+
13
+ // Light color shades (for hover/active states on light buttons)
14
+ --bs-light-hover: #d3d6db;
15
+ --bs-light-active: #c8ccd1;
16
+ }
17
+
18
+ [data-bs-theme="dark"] {
19
+ // In dark mode, the values are the same but semantic meaning is swapped
20
+ --bs-dark-hover: #3a3a44;
21
+ --bs-dark-active: #34343e;
22
+
23
+ --bs-light-hover: #d3d6db;
24
+ --bs-light-active: #c8ccd1;
25
+ }
@@ -8,6 +8,7 @@
8
8
  @use 'buttons-adaptive';
9
9
 
10
10
  // Colors
11
+ @use 'color-shades';
11
12
  @use 'soft-colors';
12
13
 
13
14
  // Components
@@ -110,11 +110,10 @@
110
110
  // Outline Button Overrides
111
111
  // ============================================
112
112
  [class*="btn-outline-"] {
113
- background: transparent !important;
114
113
  border-width: 2px !important;
115
114
  border-style: solid !important;
116
115
 
117
- &:not(:hover) {
116
+ &:not(:hover):not(:active):not(.active) {
118
117
  background: transparent !important;
119
118
  }
120
119
  }
package/dist/build.js CHANGED
@@ -3,7 +3,7 @@ const path = require('path');
3
3
  const jetpack = require('fs-jetpack');
4
4
  const fs = require('fs');
5
5
  const JSON5 = require('json5');
6
- const argv = require('yargs').argv;
6
+ const argv = require('yargs')(process.argv.slice(2)).parseSync();
7
7
  const { force, execute } = require('node-powertools');
8
8
  const yaml = require('js-yaml');
9
9
 
@@ -208,7 +208,7 @@ badges:
208
208
 
209
209
  <!-- Profile Section -->
210
210
  <section id="profile-section" class="account-section d-none">
211
- <h2 class="h3 mb-4" >My Profile</h2>
211
+ <h2 class="h3 mb-4" >My profile</h2>
212
212
 
213
213
  <form id="profile-form" novalidate>
214
214
  <!-- Avatar and Edit Button -->
@@ -244,7 +244,7 @@ badges:
244
244
  <!-- Account Details Section -->
245
245
  <div class="card mb-3">
246
246
  <div class="card-body">
247
- <h5 class="card-title text-body mb-3">Account Details</h5>
247
+ <h5 class="card-title text-body mb-3">Account details</h5>
248
248
 
249
249
  <!-- Email and UID Fields Row -->
250
250
  <div class="row g-3">
@@ -276,7 +276,7 @@ badges:
276
276
  <!-- Personal Information Section -->
277
277
  <div class="card mb-3">
278
278
  <div class="card-body">
279
- <h5 class="card-title text-body mb-3">Personal Information</h5>
279
+ <h5 class="card-title text-body mb-3">Personal information</h5>
280
280
 
281
281
  <!-- Name Fields Row -->
282
282
  <div class="row g-3 mb-3">
@@ -748,7 +748,7 @@ badges:
748
748
 
749
749
  <div class="card">
750
750
  <div class="card-body">
751
- <h5 class="card-title">Email Notifications</h5>
751
+ <h5 class="card-title">Email notifications</h5>
752
752
 
753
753
  <div class="form-check form-switch mb-3">
754
754
  <input class="form-check-input" type="checkbox" id="marketing-emails" checked>
@@ -779,12 +779,12 @@ badges:
779
779
 
780
780
  <!-- Security Section -->
781
781
  <section id="security-section" class="account-section d-none">
782
- <h2 class="h3 mb-4" >Security and Data</h2>
782
+ <h2 class="h3 mb-4" >Security and data</h2>
783
783
 
784
784
  <!-- Sign-in Methods Section -->
785
785
  <div class="card mb-3">
786
786
  <div class="card-body">
787
- <h5 class="card-title">Sign-in Methods</h5>
787
+ <h5 class="card-title">Sign-in methods</h5>
788
788
  <p class="text-muted mb-0">Manage how you sign in to your account. You can connect multiple methods for added convenience and security.</p>
789
789
 
790
790
  <div id="signin-methods-list">
@@ -846,7 +846,7 @@ badges:
846
846
  <div class="card">
847
847
  <div class="card-body">
848
848
  <div class="d-flex justify-content-between align-items-center mb-3">
849
- <h5 class="card-title mb-0">Active Sessions</h5>
849
+ <h5 class="card-title mb-0">Active sessions</h5>
850
850
  <form id="signout-all-sessions-form" class="d-inline" novalidate>
851
851
  <input type="hidden" name="action" value="signout-all">
852
852
  <button type="submit" class="btn btn-outline-danger btn-sm">
@@ -873,11 +873,11 @@ badges:
873
873
 
874
874
  <!-- Connections Section -->
875
875
  <section id="connections-section" class="account-section d-none">
876
- <h2 class="h3 mb-4" >Connected Accounts</h2>
876
+ <h2 class="h3 mb-4" >Connected accounts</h2>
877
877
 
878
878
  <div class="card">
879
879
  <div class="card-body">
880
- <h5 class="card-title">Manage Connections</h5>
880
+ <h5 class="card-title">Manage connections</h5>
881
881
  <p class="text-muted mb-0">Connect your external accounts to access additional features.</p>
882
882
 
883
883
  <div id="connections-list" class="list-group list-group-flush">
@@ -933,14 +933,14 @@ badges:
933
933
 
934
934
  <!-- Billing Section -->
935
935
  <section id="billing-section" class="account-section d-none">
936
- <h2 class="h3 mb-4" >Billing and Usage</h2>
936
+ <h2 class="h3 mb-4" >Billing and usage</h2>
937
937
 
938
938
  <div class="card mb-3">
939
939
  <div class="card-body">
940
940
  <div class="d-flex justify-content-between align-items-center mb-3">
941
941
  <div>
942
942
  <h5 class="card-title mb-1 d-flex align-items-center">
943
- <span class="me-2">Current Plan</span>
943
+ <span class="me-2">Current plan</span>
944
944
  <span id="plan-status"></span>
945
945
  </h5>
946
946
  <p class="card-text mb-0">
@@ -980,7 +980,7 @@ badges:
980
980
  <div class="card">
981
981
  <div class="card-body">
982
982
  <div class="d-flex justify-content-between align-items-center mb-3">
983
- <h5 class="card-title mb-0">Team Members</h5>
983
+ <h5 class="card-title mb-0">Team members</h5>
984
984
  <button class="btn btn-primary btn-sm" id="invite-team-member-btn">
985
985
  {% uj_icon "user-plus", "me-2" %}
986
986
  <span class="button-text">
@@ -1011,7 +1011,7 @@ badges:
1011
1011
  <!-- Referral Code Card -->
1012
1012
  <div class="card mb-3">
1013
1013
  <div class="card-body">
1014
- <h5 class="card-title">Your Referral Code</h5>
1014
+ <h5 class="card-title">Your referral code</h5>
1015
1015
  <p class="card-text text-muted">Share your referral code with friends to earn rewards.</p>
1016
1016
 
1017
1017
  <div class="mb-0">
@@ -1030,17 +1030,17 @@ badges:
1030
1030
  <!-- Referrals Stats Card -->
1031
1031
  <div class="card">
1032
1032
  <div class="card-body">
1033
- <h5 class="card-title">Referral Statistics</h5>
1033
+ <h5 class="card-title">Referral statistics</h5>
1034
1034
 
1035
1035
  <!-- Stats Summary -->
1036
1036
  <div class="row text-center mb-4">
1037
1037
  <div class="col">
1038
1038
  <div class="h3 mb-0" id="total-referrals">0</div>
1039
- <div class="text-muted small">Total Referrals</div>
1039
+ <div class="text-muted small">Total referrals</div>
1040
1040
  </div>
1041
1041
  <div class="col">
1042
1042
  <div class="h3 mb-0" id="recent-referrals">0</div>
1043
- <div class="text-muted small">This Month</div>
1043
+ <div class="text-muted small">This month</div>
1044
1044
  </div>
1045
1045
  </div>
1046
1046
 
@@ -1050,7 +1050,7 @@ badges:
1050
1050
  <h2 class="accordion-header" id="referrals-list-heading">
1051
1051
  <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#referrals-list-collapse" aria-expanded="false" aria-controls="referrals-list-collapse">
1052
1052
  <span class="me-2">{% uj_icon "users", "fa-sm" %}</span>
1053
- View Referral Details
1053
+ View referral details
1054
1054
  <span class="badge bg-primary ms-2" id="referrals-badge">0</span>
1055
1055
  </button>
1056
1056
  </h2>
@@ -1072,12 +1072,12 @@ badges:
1072
1072
 
1073
1073
  <!-- API Keys Section -->
1074
1074
  <section id="api-keys-section" class="account-section d-none">
1075
- <h2 class="h3 mb-4" >API Keys</h2>
1075
+ <h2 class="h3 mb-4" >API keys</h2>
1076
1076
 
1077
1077
  <div class="card">
1078
1078
  <div class="card-body">
1079
1079
  <div class="d-flex justify-content-between align-items-center mb-3">
1080
- <h5 class="card-title mb-0">Your API Key</h5>
1080
+ <h5 class="card-title mb-0">Your API key</h5>
1081
1081
  <form id="reset-api-key-form" class="d-inline" novalidate>
1082
1082
  <button type="submit" class="btn btn-outline-danger btn-sm" id="reset-api-key-btn">
1083
1083
  {% uj_icon "rotate", "me-1" %}
@@ -1091,7 +1091,7 @@ badges:
1091
1091
 
1092
1092
  <!-- API Key Display -->
1093
1093
  <div class="mb-3">
1094
- <label for="api-key-input" class="form-label small text-muted">Private API Key</label>
1094
+ <label for="api-key-input" class="form-label small text-muted">Private API key</label>
1095
1095
  <div class="input-group">
1096
1096
  <input type="text" class="form-control font-monospace" id="api-key-input" readonly value="Loading...">
1097
1097
  <button type="button" class="btn btn-outline-adaptive" id="copy-api-key-btn">
@@ -1116,7 +1116,7 @@ badges:
1116
1116
 
1117
1117
  <!-- Delete Account Section (Hidden by default) -->
1118
1118
  <section id="delete-section" class="account-section d-none">
1119
- <h2 class="h3 mb-4 text-danger" >Delete Account</h2>
1119
+ <h2 class="h3 mb-4 text-danger" >Delete account</h2>
1120
1120
 
1121
1121
  <div class="alert alert-danger">
1122
1122
  <h5 class="alert-heading">
@@ -1135,7 +1135,7 @@ badges:
1135
1135
 
1136
1136
  <div class="card border-danger">
1137
1137
  <div class="card-body">
1138
- <h5 class="card-title text-danger">Confirm Account Deletion</h5>
1138
+ <h5 class="card-title text-danger">Confirm account deletion</h5>
1139
1139
 
1140
1140
  <form id="delete-account-form" novalidate>
1141
1141
  <div class="mb-3">
@@ -453,6 +453,13 @@ meta:
453
453
  <button type="button" class="btn btn-outline-light">Light</button>
454
454
  <button type="button" class="btn btn-outline-dark">Dark</button>
455
455
 
456
+ <h3 class="mt-4 mb-3">Adaptive Buttons (Theme-Aware)</h3>
457
+ <p class="text-muted">These buttons automatically adapt to light/dark mode. Use instead of btn-light/btn-dark.</p>
458
+ <button type="button" class="btn btn-adaptive">Adaptive</button>
459
+ <button type="button" class="btn btn-outline-adaptive">Outline Adaptive</button>
460
+ <button type="button" class="btn btn-adaptive-inverse">Adaptive Inverse</button>
461
+ <button type="button" class="btn btn-outline-adaptive-inverse">Outline Adaptive Inverse</button>
462
+
456
463
  <h3 class="mt-4 mb-3">Button Sizes</h3>
457
464
  <button type="button" class="btn btn-primary btn-lg">Large button</button>
458
465
  <button type="button" class="btn btn-primary">Default button</button>
@@ -8,7 +8,7 @@ const path = require('path');
8
8
  const { minimatch } = require('minimatch');
9
9
  const { template } = require('node-powertools');
10
10
  const createTemplateTransform = require('./utils/template-transform');
11
- const argv = require('yargs').argv;
11
+ const argv = require('yargs')(process.argv.slice(2)).parseSync();
12
12
  const JSON5 = require('json5');
13
13
 
14
14
  // Load package
@@ -218,6 +218,10 @@ function sass(complete) {
218
218
  /^active$/,
219
219
  /^disabled$/,
220
220
 
221
+ // Buttons
222
+ // /^btn-outline-adaptive$/,
223
+ // /^btn-adaptive$/,
224
+
221
225
  // Accordion specific
222
226
  /^accordion/,
223
227
  /^collapsed$/,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultimate-jekyll-manager",
3
- "version": "0.0.199",
3
+ "version": "0.0.201",
4
4
  "description": "Ultimate Jekyll dependency manager",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -62,7 +62,7 @@
62
62
  "dependencies": {
63
63
  "@babel/core": "^7.28.6",
64
64
  "@babel/preset-env": "^7.28.6",
65
- "@fullhuman/postcss-purgecss": "^7.0.2",
65
+ "@fullhuman/postcss-purgecss": "^8.0.0",
66
66
  "@minify-html/node": "^0.18.1",
67
67
  "@octokit/rest": "^22.0.1",
68
68
  "@popperjs/core": "^2.11.8",
@@ -71,7 +71,7 @@
71
71
  "babel-loader": "^10.0.0",
72
72
  "browser-sync": "^3.0.4",
73
73
  "chalk": "^5.6.2",
74
- "cheerio": "^1.1.2",
74
+ "cheerio": "^1.2.0",
75
75
  "chrome-launcher": "^1.2.1",
76
76
  "dotenv": "^17.2.3",
77
77
  "fast-xml-parser": "^5.3.3",
@@ -84,25 +84,25 @@
84
84
  "gulp-responsive-modern": "^1.0.0",
85
85
  "gulp-sass": "^6.0.1",
86
86
  "html-minifier-terser": "^7.2.0",
87
- "html-validate": "^10.5.0",
87
+ "html-validate": "^10.7.0",
88
88
  "itwcw-package-analytics": "^1.0.6",
89
- "js-yaml": "^3.14.2",
89
+ "js-yaml": "^4.1.1",
90
90
  "json5": "^2.2.3",
91
- "libsodium-wrappers": "^0.8.0",
92
- "lodash": "^4.17.21",
91
+ "libsodium-wrappers": "^0.8.2",
92
+ "lodash": "^4.17.23",
93
93
  "minimatch": "^10.1.1",
94
94
  "node-powertools": "^2.3.2",
95
95
  "npm-api": "^1.0.1",
96
96
  "postcss": "^8.5.6",
97
- "prettier": "^3.8.0",
98
- "sass": "^1.97.2",
97
+ "prettier": "^3.8.1",
98
+ "sass": "^1.97.3",
99
99
  "spellchecker": "^3.7.1",
100
100
  "through2": "^4.0.2",
101
101
  "web-manager": "^4.1.6",
102
102
  "webpack": "^5.104.1",
103
103
  "wonderful-fetch": "^1.3.4",
104
104
  "wonderful-version": "^1.3.2",
105
- "yargs": "^17.7.2"
105
+ "yargs": "^18.0.0"
106
106
  },
107
107
  "peerDependencies": {
108
108
  "gulp": "^5.0.1"