ultimate-jekyll-manager 1.6.4 → 1.6.6

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.
@@ -60,6 +60,7 @@ prerender_icons:
60
60
  - name: "wifi"
61
61
  - name: "battery-full"
62
62
  - name: "table-list"
63
+ - name: "vial"
63
64
  ---
64
65
 
65
66
  <!-- Calendar Root -->
@@ -79,6 +80,7 @@ prerender_icons:
79
80
  <h5 class="modal-title" id="campaign-modal-title">
80
81
  {% uj_icon "plus", "fa-md me-2" %}
81
82
  <span id="campaign-modal-title-text">Create campaign</span>
83
+ <small class="text-muted fw-normal ms-2 d-none" id="campaign-modal-id" style="font-size: 0.6rem"></small>
82
84
  </h5>
83
85
  <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
84
86
  </div>
@@ -87,7 +89,7 @@ prerender_icons:
87
89
 
88
90
  <!-- Campaign Type -->
89
91
  <h6 class="mb-3">
90
- {% uj_icon "bullhorn", "fa-sm me-2 text-info" %}
92
+ {% uj_icon "bullhorn", "me-2 text-info" %}
91
93
  Campaign type
92
94
  </h6>
93
95
  <div class="row mb-4">
@@ -186,9 +188,8 @@ prerender_icons:
186
188
  <div class="col-md-6">
187
189
  <label for="campaign-template" class="form-label">Template</label>
188
190
  <select class="form-select" id="campaign-template" name="campaign.template">
189
- <option value="default" selected>Default</option>
190
- <option value="main/basic/card">Card</option>
191
- <option value="main/basic/plain">Plain</option>
191
+ <option value="card" selected>Card</option>
192
+ <option value="plain">Plain</option>
192
193
  </select>
193
194
  </div>
194
195
  <div class="col-md-6">
@@ -201,6 +202,18 @@ prerender_icons:
201
202
  </select>
202
203
  </div>
203
204
  </div>
205
+
206
+ <div class="mb-3">
207
+ <label for="campaign-discount-code" class="form-label">
208
+ Discount code
209
+ <small class="text-muted fw-normal ms-1">(optional — resolves {discount.code} and {discount.percent})</small>
210
+ </label>
211
+ <input type="text"
212
+ class="form-control"
213
+ id="campaign-discount-code"
214
+ name="campaign.discountCode"
215
+ placeholder="e.g. UPGRADE15">
216
+ </div>
204
217
  </div>
205
218
 
206
219
  <!-- Push-only fields -->
@@ -271,19 +284,6 @@ prerender_icons:
271
284
  </div>
272
285
  </div>
273
286
 
274
- <!-- Discount Code -->
275
- <div class="mb-3">
276
- <label for="campaign-discount-code" class="form-label">
277
- Discount code
278
- <small class="text-muted fw-normal ms-1">(optional)</small>
279
- </label>
280
- <input type="text"
281
- class="form-control"
282
- id="campaign-discount-code"
283
- name="campaign.discountCode"
284
- placeholder="e.g. UPGRADE15">
285
- </div>
286
-
287
287
  <!-- Test Mode -->
288
288
  <div class="form-check mb-4">
289
289
  <input class="form-check-input"
@@ -300,12 +300,12 @@ prerender_icons:
300
300
 
301
301
  <!-- Schedule -->
302
302
  <h6 class="mb-3 mt-4">
303
- {% uj_icon "clock", "fa-sm me-2 text-info" %}
303
+ {% uj_icon "clock", "me-2 text-info" %}
304
304
  Schedule
305
305
  <small class="text-muted fw-normal ms-1">(UTC)</small>
306
306
  </h6>
307
307
 
308
- <div class="row mb-3">
308
+ <div class="row mb-2">
309
309
  <div class="col-md-5">
310
310
  <label for="campaign-date" class="form-label">
311
311
  Date (UTC) <span class="text-danger">*</span>
@@ -330,16 +330,19 @@ prerender_icons:
330
330
  <div class="invalid-feedback">Time is required</div>
331
331
  </div>
332
332
  <div class="col-md-3 d-flex align-items-end">
333
- <button type="button" class="btn btn-sm btn-outline-primary w-100" id="btn-send-now">
334
- {% uj_icon "paper-plane", "fa-sm me-1" %}
335
- Send now
333
+ <button type="button" class="btn btn-md btn-outline-primary w-100" id="btn-send-now">
334
+ {% uj_icon "clock", "fa-sm me-1" %}
335
+ Set now
336
336
  </button>
337
337
  </div>
338
338
  </div>
339
+ <div class="mb-3" id="campaign-local-time-row" style="display:none">
340
+ <span class="badge bg-body-secondary text-body-secondary" id="campaign-time-local"></span>
341
+ </div>
339
342
 
340
343
  <!-- Recurrence -->
341
344
  <h6 class="mb-3 mt-4">
342
- {% uj_icon "repeat", "fa-sm me-2 text-info" %}
345
+ {% uj_icon "repeat", "me-2 text-info" %}
343
346
  Recurrence
344
347
  <small class="text-muted fw-normal ms-1">(optional)</small>
345
348
  </h6>
@@ -361,20 +364,32 @@ prerender_icons:
361
364
  <select class="form-select" id="campaign-recurrence-pattern" name="campaign.recurrence.pattern">
362
365
  <option value="daily">Daily</option>
363
366
  <option value="weekly">Weekly</option>
364
- <option value="monthly" selected>Monthly</option>
367
+ <option value="monthly" selected>Monthly (day of month)</option>
368
+ <option value="monthly-weekday">Monthly (Nth weekday)</option>
365
369
  <option value="quarterly">Quarterly</option>
366
370
  <option value="yearly">Yearly</option>
367
371
  </select>
368
372
  </div>
369
373
  <div class="col-md-4">
370
374
  <label for="campaign-recurrence-hour" class="form-label">Hour (UTC)</label>
371
- <input type="number"
372
- class="form-control"
373
- id="campaign-recurrence-hour"
374
- name="campaign.recurrence.hour"
375
- value="14"
376
- min="0"
377
- max="23">
375
+ <div class="input-group">
376
+ <input type="number"
377
+ class="form-control"
378
+ id="campaign-recurrence-hour"
379
+ name="campaign.recurrence.hour"
380
+ value="14"
381
+ min="0"
382
+ max="23">
383
+ <span class="input-group-text">:</span>
384
+ <input type="number"
385
+ class="form-control"
386
+ id="campaign-recurrence-minute"
387
+ name="campaign.recurrence.minute"
388
+ value="0"
389
+ min="0"
390
+ max="59"
391
+ placeholder="Min">
392
+ </div>
378
393
  </div>
379
394
  <div class="col-md-4">
380
395
  <label for="campaign-recurrence-day" class="form-label">
@@ -391,6 +406,18 @@ prerender_icons:
391
406
  </div>
392
407
  </div>
393
408
 
409
+ <div class="row mb-3 d-none" id="recurrence-nth-row">
410
+ <div class="col-md-4">
411
+ <label for="campaign-recurrence-nth" class="form-label">Occurrence</label>
412
+ <select class="form-select" id="campaign-recurrence-nth" name="campaign.recurrence.nth">
413
+ <option value="1">1st</option>
414
+ <option value="2" selected>2nd</option>
415
+ <option value="3">3rd</option>
416
+ <option value="4">4th</option>
417
+ </select>
418
+ </div>
419
+ </div>
420
+
394
421
  <div class="row mb-3 d-none" id="recurrence-month-row">
395
422
  <div class="col-md-4">
396
423
  <label for="campaign-recurrence-month" class="form-label">Month</label>
@@ -418,7 +445,7 @@ prerender_icons:
418
445
 
419
446
  <!-- Targeting -->
420
447
  <h6 class="mb-3 mt-4">
421
- {% uj_icon "users", "fa-sm me-2 text-success" %}
448
+ {% uj_icon "users", "me-2 text-success" %}
422
449
  Targeting
423
450
  </h6>
424
451
 
@@ -472,32 +499,33 @@ prerender_icons:
472
499
 
473
500
  <div class="mb-3">
474
501
  <label for="campaign-segments" class="form-label">
475
- Segments
476
- <small class="text-muted fw-normal ms-1">(comma-separated IDs)</small>
502
+ Include segments
503
+ <small class="text-muted fw-normal ms-1">(AND — contacts must match ALL listed segments)</small>
477
504
  </label>
478
505
  <input type="text"
479
506
  class="form-control"
480
507
  id="campaign-segments"
481
508
  name="campaign.segments"
482
- placeholder="e.g. seg_abc123, seg_def456">
509
+ placeholder="e.g. subscription_free, lifecycle_30d">
510
+ <small class="form-text text-muted">SSOT keys: subscription_free, subscription_paid, subscription_trialing, subscription_cancelled, subscription_churned_trial, subscription_churned_paid, lifecycle_7d, lifecycle_30d, engagement_active_30d, engagement_inactive_90d, etc.</small>
483
511
  </div>
484
512
 
485
513
  <div class="mb-3">
486
514
  <label for="campaign-exclude-segments" class="form-label">
487
515
  Exclude segments
488
- <small class="text-muted fw-normal ms-1">(comma-separated IDs)</small>
516
+ <small class="text-muted fw-normal ms-1">(AND — contacts matching ANY excluded segment are removed)</small>
489
517
  </label>
490
518
  <input type="text"
491
519
  class="form-control"
492
520
  id="campaign-exclude-segments"
493
521
  name="campaign.excludeSegments"
494
- placeholder="e.g. seg_unsubscribed">
522
+ placeholder="e.g. subscription_paid, subscription_trialing">
495
523
  </div>
496
524
 
497
525
  <!-- Advanced Settings (collapsible) -->
498
526
  <h6 class="mb-3 mt-4">
499
527
  <a class="text-decoration-none" data-bs-toggle="collapse" href="#advanced-settings" role="button" aria-expanded="false">
500
- {% uj_icon "sliders", "fa-sm me-2 text-info" %}
528
+ {% uj_icon "sliders", "me-2 text-info" %}
501
529
  Advanced settings
502
530
  <small class="text-muted fw-normal ms-1">(optional)</small>
503
531
  </a>
@@ -548,23 +576,26 @@ prerender_icons:
548
576
  </div>
549
577
  </div>
550
578
 
579
+ <div class="modal-footer d-flex justify-content-between">
580
+ <div>
581
+ <button type="submit" name="action" value="delete" formnovalidate class="btn btn-outline-danger d-none" id="btn-delete-campaign">
582
+ {% uj_icon "trash", "fa-sm me-1" %}
583
+ <span class="button-text">Delete</span>
584
+ </button>
585
+ </div>
586
+ <div class="d-flex gap-2">
587
+ <button type="submit" name="action" value="test" formnovalidate class="btn btn-outline-warning" id="btn-send-test">
588
+ {% uj_icon "vial", "fa-sm me-1" %}
589
+ <span class="button-text">Send test</span>
590
+ </button>
591
+ <button type="submit" name="action" value="save" class="btn btn-adaptive" id="btn-save-campaign">
592
+ {% uj_icon "paper-plane", "fa-sm me-1" %}
593
+ <span class="button-text">Save campaign</span>
594
+ </button>
595
+ </div>
596
+ </div>
551
597
  </form>
552
598
  </div>
553
- <div class="modal-footer d-flex justify-content-between">
554
- <div>
555
- <button type="button" class="btn btn-outline-danger d-none" id="btn-delete-campaign">
556
- {% uj_icon "trash", "fa-sm me-1" %}
557
- Delete
558
- </button>
559
- </div>
560
- <div class="d-flex gap-2">
561
- <button type="button" class="btn btn-outline-adaptive" data-bs-dismiss="modal">Cancel</button>
562
- <button type="submit" form="campaign-editor-form" class="btn btn-adaptive" id="btn-save-campaign">
563
- {% uj_icon "paper-plane", "fa-sm me-1" %}
564
- <span class="button-text">Save campaign</span>
565
- </button>
566
- </div>
567
- </div>
568
599
  </div>
569
600
  </div>
570
601
  </div>
@@ -1277,6 +1277,36 @@ badges:
1277
1277
  </form>
1278
1278
  </div>
1279
1279
  </div>
1280
+ <div class="card mt-3">
1281
+ <div class="card-body">
1282
+ <div class="d-flex justify-content-between align-items-center mb-1">
1283
+ <h5 class="card-title mb-0">Push notifications</h5>
1284
+ <span id="push-notification-status"><span class="badge bg-secondary">Checking...</span></span>
1285
+ </div>
1286
+ <p class="text-muted small mb-3">Receive push notifications in your browser for important updates.</p>
1287
+
1288
+ <form id="push-subscribe-form" novalidate onsubmit="return false" style="display:none">
1289
+ <button type="submit" class="btn btn-adaptive btn-sm" id="push-subscribe-btn">
1290
+ {% uj_icon "bell", "me-1" %}
1291
+ <span class="button-text">Enable push notifications</span>
1292
+ </button>
1293
+ </form>
1294
+
1295
+ <div class="mt-3 pt-3 border-top">
1296
+ <a class="small text-muted text-decoration-none d-flex align-items-center" data-bs-toggle="collapse" href="#push-token-details" role="button" aria-expanded="false">
1297
+ {% uj_icon "code", "fa-xs me-1" %} Token details
1298
+ </a>
1299
+ <div class="collapse mt-2" id="push-token-details">
1300
+ <div class="input-group input-group-sm">
1301
+ <input type="text" class="form-control font-monospace bg-body-tertiary" id="push-token-value" readonly value="No token" style="font-size: 0.65rem;">
1302
+ <button type="button" class="btn btn-outline-adaptive" id="copy-push-token-btn">
1303
+ {% uj_icon "copy" %}
1304
+ </button>
1305
+ </div>
1306
+ </div>
1307
+ </div>
1308
+ </div>
1309
+ </div>
1280
1310
  </section>
1281
1311
 
1282
1312
  <!-- API Keys Section -->
@@ -17,8 +17,8 @@ const ujmConfig = Manager.getUJMConfig();
17
17
  // Settings
18
18
  const CACHE_DIR = '.temp/cache/imagemin';
19
19
  const CACHE_BRANCH = 'cache-uj-imagemin';
20
- const MAX_SOURCE_DIMENSION = 4096;
21
- const REWRITE_QUALITY = 80;
20
+ const IMAGE_MAX_DIMENSION = 2048;
21
+ const IMAGE_JPEG_QUALITY = 80;
22
22
 
23
23
  // Variables
24
24
  let githubCache;
@@ -129,7 +129,7 @@ async function imagemin(complete) {
129
129
  }
130
130
 
131
131
  // Optionally rewrite oversized source images on disk (opt-in via UJ_IMAGEMIN_REWRITE_SOURCES=true).
132
- // Caps longest dimension at MAX_SOURCE_DIMENSION so gulp-responsive-modern + sharp don't stall
132
+ // Caps longest dimension at IMAGE_MAX_DIMENSION so gulp-responsive-modern + sharp don't stall
133
133
  // on huge inputs. Runs BEFORE determineFilesToProcess so cached-but-oversized images get
134
134
  // rewritten too; the new on-disk content hashes differently than the stored meta hash, so
135
135
  // determineFilesToProcess naturally picks the rewritten image up for re-optimization.
@@ -305,7 +305,7 @@ module.exports = series(
305
305
  // Helper Functions
306
306
  // ============================================================================
307
307
 
308
- // Rewrite oversized source images in place, capping longest dimension at MAX_SOURCE_DIMENSION.
308
+ // Rewrite oversized source images in place, capping longest dimension at IMAGE_MAX_DIMENSION.
309
309
  // Only affects files whose decoded longest side exceeds the cap. Cache invalidation is implicit:
310
310
  // the new content hashes differently than the previously-cached entry, so determineFilesToProcess
311
311
  // will pick affected files up for re-optimization on its own.
@@ -315,14 +315,14 @@ async function rewriteOversizedSources(files) {
315
315
  return;
316
316
  }
317
317
 
318
- logger.log(`🔍 Checking ${responsiveFiles.length} source images for oversize (>${MAX_SOURCE_DIMENSION}px longest side)...`);
318
+ logger.log(`🔍 Checking ${responsiveFiles.length} source images for oversize (>${IMAGE_MAX_DIMENSION}px longest side)...`);
319
319
 
320
320
  let rewritten = 0;
321
321
  for (const file of responsiveFiles) {
322
322
  const metadata = await sharp(file).metadata();
323
323
  const longest = Math.max(metadata.width || 0, metadata.height || 0);
324
324
 
325
- if (longest <= MAX_SOURCE_DIMENSION) {
325
+ if (longest <= IMAGE_MAX_DIMENSION) {
326
326
  continue;
327
327
  }
328
328
 
@@ -331,15 +331,15 @@ async function rewriteOversizedSources(files) {
331
331
 
332
332
  // Resize, encode to a buffer (sharp can't write back to its own input file directly), then overwrite.
333
333
  const pipeline = sharp(file).resize({
334
- width: MAX_SOURCE_DIMENSION,
335
- height: MAX_SOURCE_DIMENSION,
334
+ width: IMAGE_MAX_DIMENSION,
335
+ height: IMAGE_MAX_DIMENSION,
336
336
  fit: 'inside',
337
337
  withoutEnlargement: true,
338
338
  });
339
339
 
340
340
  const buffer = ext === 'png'
341
- ? await pipeline.png({ quality: REWRITE_QUALITY }).toBuffer()
342
- : await pipeline.jpeg({ quality: REWRITE_QUALITY, progressive: true, mozjpeg: true }).toBuffer();
341
+ ? await pipeline.png({ quality: IMAGE_JPEG_QUALITY }).toBuffer()
342
+ : await pipeline.jpeg({ quality: IMAGE_JPEG_QUALITY, progressive: true, mozjpeg: true }).toBuffer();
343
343
 
344
344
  jetpack.write(file, buffer);
345
345
  const sizeAfter = buffer.length;
@@ -350,11 +350,11 @@ async function rewriteOversizedSources(files) {
350
350
  // with the new hash when determineFilesToProcess() runs.
351
351
 
352
352
  rewritten++;
353
- logger.log(`✂️ Rewrote ${path.relative(rootPathProject, file)}: ${metadata.width}x${metadata.height} → max ${MAX_SOURCE_DIMENSION}px, ${formatBytes(sizeBefore)} → ${formatBytes(sizeAfter)}`);
353
+ logger.log(`✂️ Rewrote ${path.relative(rootPathProject, file)}: ${metadata.width}x${metadata.height} → max ${IMAGE_MAX_DIMENSION}px, ${formatBytes(sizeBefore)} → ${formatBytes(sizeAfter)}`);
354
354
  }
355
355
 
356
356
  if (rewritten === 0) {
357
- logger.log(`✅ No oversized sources found (all within ${MAX_SOURCE_DIMENSION}px)`);
357
+ logger.log(`✅ No oversized sources found (all within ${IMAGE_MAX_DIMENSION}px)`);
358
358
  } else {
359
359
  logger.log(`✂️ Rewrote ${rewritten} oversized source image(s)`);
360
360
  }