ultimate-jekyll-manager 1.3.11 → 1.4.0

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/CLAUDE.md +2 -2
  3. package/README.md +4 -0
  4. package/dist/assets/js/core/auth.js +14 -0
  5. package/dist/assets/js/libs/auth.js +62 -2
  6. package/dist/assets/js/libs/form-manager.js +10 -0
  7. package/dist/assets/themes/classy/css/components/_forms.scss +25 -1
  8. package/dist/commands/setup.js +20 -10
  9. package/dist/defaults/dist/_alternatives/example-competitor.md +2 -2
  10. package/dist/defaults/dist/_includes/admin/sections/sidebar.json +1 -1
  11. package/dist/defaults/dist/_layouts/blueprint/admin/calendar/index.html +4 -4
  12. package/dist/defaults/dist/_layouts/blueprint/admin/firebase/index.html +2 -2
  13. package/dist/defaults/dist/_layouts/blueprint/admin/users/index.html +5 -5
  14. package/dist/defaults/dist/_layouts/blueprint/admin/users/new.html +3 -3
  15. package/dist/defaults/dist/_layouts/blueprint/legal/cookies.md +6 -6
  16. package/dist/defaults/dist/_layouts/blueprint/legal/terms.md +1 -1
  17. package/dist/defaults/dist/_layouts/blueprint/portal/email-preferences.html +2 -2
  18. package/dist/defaults/dist/_layouts/themes/classy/backend/pages/dashboard/index.html +5 -5
  19. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/404.html +2 -2
  20. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/about.html +3 -3
  21. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/account/index.html +22 -22
  22. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/alternatives/alternative.html +11 -11
  23. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/alternatives/index.html +10 -10
  24. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/app.html +4 -4
  25. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/oauth2.html +3 -3
  26. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/reset.html +3 -3
  27. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/signin.html +10 -9
  28. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/signup.html +8 -8
  29. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/blog/categories/category.html +2 -2
  30. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/blog/categories/index.html +1 -1
  31. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/blog/tags/index.html +1 -1
  32. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/blog/tags/tag.html +2 -2
  33. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/contact.html +13 -13
  34. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/download.html +13 -13
  35. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/extension/index.html +7 -7
  36. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/extension/installed.html +1 -1
  37. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/feedback.html +3 -3
  38. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/index.html +29 -29
  39. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/payment/checkout.html +16 -16
  40. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/payment/confirmation.html +5 -5
  41. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/portal/email-preferences.html +4 -4
  42. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html +40 -20
  43. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/status.html +2 -2
  44. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/team/index.html +10 -10
  45. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/team/member.html +1 -1
  46. package/dist/defaults/dist/pages/test/account/dashboard.html +1 -1
  47. package/dist/defaults/dist/pages/test/components/hero-demo-custom.html +2 -2
  48. package/dist/defaults/dist/pages/test/components/hero-demo-form.html +4 -4
  49. package/dist/defaults/dist/pages/test/components/hero-demo-input.html +3 -3
  50. package/dist/defaults/dist/pages/test/components/hero-demo-side.html +2 -2
  51. package/dist/defaults/dist/pages/test/components/hero-demo-video.html +2 -2
  52. package/dist/defaults/dist/pages/test/index.md +2 -2
  53. package/dist/defaults/dist/pages/test/libraries/appearance.html +6 -6
  54. package/dist/defaults/dist/pages/test/libraries/bootstrap.html +99 -99
  55. package/dist/defaults/dist/pages/test/libraries/cover.html +4 -4
  56. package/dist/defaults/dist/pages/test/libraries/error.html +5 -5
  57. package/dist/defaults/dist/pages/test/libraries/firestore.html +2 -2
  58. package/dist/defaults/dist/pages/test/libraries/form-manager.html +9 -9
  59. package/dist/defaults/dist/pages/test/libraries/lazy-loading.html +9 -9
  60. package/dist/defaults/dist/pages/test/redirect/external.md +2 -2
  61. package/dist/defaults/dist/pages/test/redirect/internal.md +2 -2
  62. package/dist/defaults/dist/pages/test/translation/index.md +3 -3
  63. package/dist/defaults/src/_includes/backend/sections/sidebar.json +3 -3
  64. package/dist/defaults/src/_includes/frontend/sections/footer.json +5 -5
  65. package/dist/defaults/src/_includes/frontend/sections/nav.json +1 -1
  66. package/dist/defaults/src/_includes/global/sections/account.json +2 -2
  67. package/dist/gulp/main.js +5 -0
  68. package/dist/gulp/tasks/imagemin.js +160 -79
  69. package/dist/utils/attach-log-file.js +78 -0
  70. package/docs/images.md +27 -0
  71. package/docs/local-development.md +11 -0
  72. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -14,6 +14,36 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
14
14
  - `Fixed` for any bug fixes.
15
15
  - `Security` in case of vulnerabilities.
16
16
 
17
+ ---
18
+ ## [1.4.0] - 2026-05-27
19
+
20
+ ### Added
21
+
22
+ - **`UJ_IMAGEMIN_REWRITE_SOURCES=true` flag** (opt-in, off by default) — when set, the `imagemin` gulp task scans every image scheduled for processing and rewrites in place any whose longest dimension exceeds 4096px. Uses `sharp` with `fit: 'inside'` (aspect-preserving), JPEG quality 80 / mozjpeg / progressive, PNG quality 80. Cache hashes for affected files are updated so the new content becomes the new cache key. Intended as a one-off cleanup for repos with pre-existing oversized source images that silently stall `gulp-responsive-modern`/`sharp`. See [docs/images.md](docs/images.md#cleanup-for-existing-oversized-sources-uj_imagemin_rewrite_sources).
23
+ - **Log-file tee** (`src/utils/attach-log-file.js`): every line of stdout/stderr produced by the gulp pipeline is duplicated to `logs/dev.log` (during `npm start`) or `logs/build.log` (during `npm run build`) in the consumer project root. ANSI color codes are stripped from the file output; terminal output is unchanged. Files truncate fresh on each run. Skipped when `UJ_IS_SERVER=true` (CI/cloud) — no `logs/` directory is created in workspace contexts. Attached at the top of `src/gulp/main.js`. See [docs/local-development.md](docs/local-development.md#log-files).
24
+ - **Pricing page: 7-day money-back guarantee badge** under the billing toggle and per-card on each paid plan; free plan shows "Upgrade any time" with a rocket icon.
25
+ - **Pricing page: trial-aware CTA copy** — buttons now read "Get free trial" only when the plan's `trial.days > 0`, otherwise "Get started". Free plan stays "Get started". Avoids misleading users into thinking they need a trial for a free plan or that every paid plan offers one.
26
+ - **Auth: field-level error rendering.** New `isPasswordError()` and `passwordErrorMessage()` helpers in `src/assets/js/libs/auth.js` route Firebase password errors (`auth/weak-password`, `auth/missing-password`, `auth/wrong-password`, `auth/password-does-not-meet-requirements`) onto the password input via `FormManager.throwFieldErrors()` instead of the form-level banner. Signin: `auth/invalid-credential` | `auth/wrong-password` | `auth/user-not-found` highlight both email + password with "Incorrect email or password" (Firebase intentionally collapses these to prevent email enumeration). `auth/invalid-email` highlights the email field. Signup: when the email already exists and auto-signin fails, the message lands inline on the email field rather than throwing a generic banner error.
27
+ - **Dev-only consent-guard warning** in `src/assets/js/core/auth.js` `sendUserSignupMetadata` catch block: shows a `webManager.utilities().showNotification()` with the exact wall-clock time the consent guard will sign the user out (and remaining mm:ss) when the metadata POST fails. Wrapped in `/* @dev-only:start */` blocks so production builds strip it.
28
+
29
+ ### Changed
30
+
31
+ - **Sentence-case copy normalization** across ~60 layouts, pages, and section configs. Examples: "Sign Out" → "Sign out", "API Keys" → "API keys", "Save Changes" → "Save changes", "Contact Us" → "Contact us", "Main Menu" → "Main menu", "All Items" → "All items", "Admin Panel" → "Admin panel", "Sign Up" → "Sign up". Touches frontend pages (account, auth, contact, download, extension, index, payment, pricing, team, etc.), blueprint admin/legal/portal pages, test pages, and nav/footer/sidebar/account JSON section configs.
32
+ - **FormManager: `.has-validation` on input-group when a field is marked invalid.** Bootstrap requires this class on the wrapping `.input-group` so the trailing element (e.g. a password-visibility toggle) keeps its border-radius once a sibling `.invalid-feedback` is appended.
33
+ - **Classy theme forms SCSS:** restored rounded right corners on `.input-group.has-validation > *:nth-last-child(2)` so the visually-last interactive element keeps its `$classy-radius-lg`. Also added `.form-control.is-invalid:focus` override to keep the danger (red) focus ring instead of the brand-blue ring the classy theme was applying.
34
+
35
+ ### Fixed
36
+
37
+ - **`imagemin` gulp task race condition (build mode):** the task was declared `async function imagemin(complete)` and returned a stream directly. Async functions wrap their return value in a Promise, so gulp resolved the task on the (already-resolved) Promise instead of waiting for the stream's `'finish'` event. Downstream tasks (jekyll, audit, minifyHtml) then started while imagemin was still writing to `dist/`, and jekyll would snapshot `_site/` before late images landed. Builds reported success while silently shipping a partial site. Fixed by explicitly `await`ing stream completion via `new Promise((resolve, reject) => { ... .on('finish', resolve).on('error', reject) })` before moving to the cache-push step, then `return complete()`. The wait only ever runs in build mode (dev mode short-circuits earlier in the task), so `npm start` startup is unaffected.
38
+ - **Oversized source images silently failing to land in `_site/`.** Source images with very large dimensions (10000px+ longest side) decode into hundreds of MB per worker in `sharp`, which can stall the `gulp-responsive-modern` stream so quietly that gulp reports the task complete. The build appears successful but some images never reach `_site/`. Documented the constraint and the new `UJ_IMAGEMIN_REWRITE_SOURCES` cleanup flag in [docs/images.md](docs/images.md). Recommended fix is to cap images at the upload step; the rewrite flag is a fallback for cleaning up existing repos.
39
+
40
+ ---
41
+ ## [1.3.12] - 2026-05-25
42
+
43
+ ### Fixed
44
+
45
+ - **Fresh-consumer `npx mgr setup` now works on the first run.** Two ordering bugs prevented setup from succeeding on a brand-new consumer project: (1) `ensureBundle()` ran `bundle install` before `ensureCoreFiles()` scaffolded the `Gemfile`, failing with "Could not locate Gemfile" — reordered so core files are scaffolded first. (2) The gulpfile eagerly `require()`s every task module at load time, and `sass.js`/`distribute.js`/`imagemin.js` all call `Manager.getUJMConfig()` at module top-level, so a fresh consumer (with no `config/ultimate-jekyll-manager.json`) couldn't even invoke `gulp defaults` — extended `ensureCoreFiles()` to seed both `src/_config.yml` and `config/ultimate-jekyll-manager.json` from `dist/defaults/` before invoking gulp. Both steps remain idempotent (existing-file guard + `{ overwrite: false }` copy).
46
+
17
47
  ---
18
48
  ## [1.3.11] - 2026-05-24
19
49
 
package/CLAUDE.md CHANGED
@@ -192,13 +192,13 @@ Deep references live in `docs/`. Treat docs as a first-class deliverable. **When
192
192
  ### Project & dev environment
193
193
 
194
194
  - [docs/project-structure.md](docs/project-structure.md) — UJM repo layout and consuming-project layout
195
- - [docs/local-development.md](docs/local-development.md) — browsersync URL, Firebase emulator connect, PurgeCSS safelist
195
+ - [docs/local-development.md](docs/local-development.md) — browsersync URL, Firebase emulator connect, PurgeCSS safelist, log files (`logs/dev.log`, `logs/build.log`)
196
196
  - [docs/assets.md](docs/assets.md) — UJM vs consumer file layout, section config (nav/footer/account), frontmatter-driven page customization, webpack aliases, page module pattern
197
197
 
198
198
  ### Pages, layouts, content
199
199
 
200
200
  - [docs/layouts-and-pages.md](docs/layouts-and-pages.md) — page types, layout chain, `asset_path` frontmatter
201
- - [docs/images.md](docs/images.md) — `@post/` shortcut for blog post images, BEM admin/post image handling
201
+ - [docs/images.md](docs/images.md) — `@post/` shortcut for blog post images, BEM admin/post image handling, imagemin pipeline + source-size constraints + `UJ_IMAGEMIN_REWRITE_SOURCES` cleanup flag
202
202
  - [docs/icons.md](docs/icons.md) — Font Awesome conventions, `{% uj_icon %}` vs prerendered icons in JS, size reference
203
203
  - [docs/seo.md](docs/seo.md) — Alternatives collection (competitor comparison pages) + Schema/JSON-LD (`SoftwareApplication`, `FAQPage`)
204
204
 
package/README.md CHANGED
@@ -113,6 +113,8 @@ npm start -- --all-posts
113
113
  ### Other ENV variables
114
114
  ```bash
115
115
  UJ_PURGECSS=true # Enables PurgeCSS to remove unused CSS (normally only happens in production builds)
116
+ UJ_IMAGEMIN_CACHE=false # Disables the GitHub-backed imagemin cache (forces local processing)
117
+ UJ_IMAGEMIN_REWRITE_SOURCES=true # One-off cleanup: shrinks oversized source images (>4096px) in place. See docs/images.md
116
118
  ```
117
119
 
118
120
  ## Running Specific Tasks
@@ -168,6 +170,8 @@ The imagemin task will:
168
170
  - Cache processed images in `cache-imagemin` branch (when using GitHub cache)
169
171
  - Skip already processed images on subsequent runs
170
172
 
173
+ **Keep sources reasonably sized.** Source images larger than ~4096px on the longest side can stall `sharp`/`gulp-responsive-modern` in a way gulp can't detect, causing them to silently fail to land in `_site/`. Cap images at the upload step where possible. For one-off cleanup of an existing repo, run with `UJ_IMAGEMIN_REWRITE_SOURCES=true npm run build` — see [docs/images.md](docs/images.md#cleanup-for-existing-oversized-sources-uj_imagemin_rewrite_sources) for details.
174
+
171
175
  <!-- Developing -->
172
176
  ## 🛠 Developing
173
177
  1. Clone the repo to your local machine.
@@ -320,6 +320,20 @@ async function sendUserSignupMetadata(user) {
320
320
  } catch (error) {
321
321
  console.error('[Auth] Error sending user metadata:', error);
322
322
  // Don't throw - we don't want to block the signup flow
323
+
324
+ /* @dev-only:start */
325
+ {
326
+ const accountAge = Date.now() - new Date(user.metadata.creationTime).getTime();
327
+ const msRemaining = Math.max(0, SIGNUP_MAX_AGE - accountAge);
328
+ const signoutAt = new Date(Date.now() + msRemaining).toLocaleTimeString();
329
+ const minutes = Math.floor(msRemaining / 1000 / 60);
330
+ const seconds = Math.floor((msRemaining / 1000) % 60);
331
+ webManager.utilities().showNotification(
332
+ `[DEV] Failed to send signup metadata. User will be signed out by consent guard at ${signoutAt} (in ${minutes}m ${seconds}s) unless retried.`,
333
+ { type: 'warning', timeout: 0 }
334
+ );
335
+ }
336
+ /* @dev-only:end */
323
337
  }
324
338
  }
325
339
 
@@ -530,8 +530,9 @@ export default function () {
530
530
  formManager.showSuccess('Successfully signed in!');
531
531
  return;
532
532
  } catch (signInError) {
533
- // Throw error for outer catch to handle
534
- throw new Error('An account with this email already exists');
533
+ // Couldn't auto-sign-them-in surface inline on the email field so
534
+ // the user sees exactly which input is the problem.
535
+ formManager.throwFieldErrors({ email: 'An account with this email already exists' });
535
536
  }
536
537
  }
537
538
 
@@ -542,6 +543,12 @@ export default function () {
542
543
  throw new Error(blockingMessage);
543
544
  }
544
545
 
546
+ // Password-specific Firebase errors should land on the password field, not
547
+ // the form-level banner — gives the user a clear "fix this input" signal.
548
+ if (isPasswordError(error.code)) {
549
+ formManager.throwFieldErrors({ password: passwordErrorMessage(error) });
550
+ }
551
+
545
552
  // Re-throw the error to be handled by the form handler
546
553
  throw error;
547
554
  }
@@ -573,6 +580,27 @@ export default function () {
573
580
  if (blockingMessage) {
574
581
  throw new Error(blockingMessage);
575
582
  }
583
+
584
+ // Firebase intentionally collapses wrong-email and wrong-password into a
585
+ // single `auth/invalid-credential` to prevent email enumeration. Since we
586
+ // don't know which field is wrong, highlight both with a shared message.
587
+ if (error.code === 'auth/invalid-credential' || error.code === 'auth/wrong-password' || error.code === 'auth/user-not-found') {
588
+ formManager.throwFieldErrors({
589
+ email: 'Incorrect email or password',
590
+ password: 'Incorrect email or password',
591
+ });
592
+ }
593
+
594
+ // Password-format errors on the password field (e.g. missing).
595
+ if (isPasswordError(error.code)) {
596
+ formManager.throwFieldErrors({ password: passwordErrorMessage(error) });
597
+ }
598
+
599
+ // Bad email format → email field.
600
+ if (error.code === 'auth/invalid-email') {
601
+ formManager.throwFieldErrors({ email: 'Please enter a valid email address' });
602
+ }
603
+
576
604
  throw error;
577
605
  }
578
606
  }
@@ -794,6 +822,38 @@ export default function () {
794
822
  });
795
823
  }
796
824
 
825
+ // Firebase password-related auth error codes — these belong on the password
826
+ // field, not the form-level error banner.
827
+ function isPasswordError(errorCode) {
828
+ return [
829
+ 'auth/weak-password',
830
+ 'auth/missing-password',
831
+ 'auth/wrong-password',
832
+ 'auth/password-does-not-meet-requirements',
833
+ ].includes(errorCode);
834
+ }
835
+
836
+ // Map a Firebase password error to a short, field-appropriate message.
837
+ function passwordErrorMessage(error) {
838
+ if (error?.code === 'auth/weak-password') {
839
+ return 'Password is too weak. Use at least 6 characters.';
840
+ }
841
+ if (error?.code === 'auth/missing-password') {
842
+ return 'Password is required';
843
+ }
844
+ if (error?.code === 'auth/wrong-password') {
845
+ return 'Incorrect password';
846
+ }
847
+ if (error?.code === 'auth/password-does-not-meet-requirements') {
848
+ // Firebase message looks like: "Firebase: Missing password requirements:
849
+ // [Password must contain at least 8 characters] (auth/...)." Pull the
850
+ // bracketed list so the user sees the actual rule(s) they missed.
851
+ const match = error?.message?.match(/\[([^\]]+)\]/);
852
+ return match ? match[1] : 'Password does not meet the requirements';
853
+ }
854
+ return error?.message || 'Invalid password';
855
+ }
856
+
797
857
  // Helper function to determine if an error is a user error
798
858
  function isUserError(errorCode) {
799
859
  const userErrors = [
@@ -595,6 +595,16 @@ export class FormManager {
595
595
  // Add invalid class to field
596
596
  $field.classList.add('is-invalid');
597
597
 
598
+ // Bootstrap requires `.has-validation` on the wrapping `.input-group` so the
599
+ // trailing element (e.g. a password-visibility toggle button) keeps its
600
+ // border-radius once a sibling `.invalid-feedback` is rendered. Without
601
+ // this, the appended feedback makes the trailing button no longer
602
+ // `:last-child` and Bootstrap strips its right corners to 0.
603
+ const $inputGroup = $field.closest('.input-group');
604
+ if ($inputGroup) {
605
+ $inputGroup.classList.add('has-validation');
606
+ }
607
+
598
608
  // Find or create feedback element
599
609
  let $feedback = $field.parentElement.querySelector('.invalid-feedback');
600
610
  if (!$feedback) {
@@ -5,7 +5,8 @@
5
5
  // Input Groups
6
6
  // ============================================
7
7
  .input-group {
8
- > .form-control {
8
+ > .form-control,
9
+ > .form-control.is-invalid {
9
10
  border-radius: $classy-radius-lg;
10
11
 
11
12
  &:not(:last-child) {
@@ -60,6 +61,21 @@
60
61
  border-bottom-left-radius: 0;
61
62
  }
62
63
  }
64
+
65
+ // When FormManager appends a sibling `.invalid-feedback` inside the
66
+ // input-group, the trailing button/input-group-text is no longer
67
+ // `:last-child` and the rules above strip its right border-radius.
68
+ // Bootstrap signals this layout with `.has-validation` on the input-group;
69
+ // restore the rounded right corners for the visually-last interactive
70
+ // element (the `:nth-last-child(2)` — i.e. the element before the feedback).
71
+ &.has-validation {
72
+ > .btn:nth-last-child(2),
73
+ > .input-group-text:nth-last-child(2),
74
+ > .form-control:nth-last-child(2) {
75
+ border-top-right-radius: $classy-radius-lg;
76
+ border-bottom-right-radius: $classy-radius-lg;
77
+ }
78
+ }
63
79
  }
64
80
 
65
81
  // ============================================
@@ -78,6 +94,14 @@
78
94
  background-color: var(--bs-body-bg);
79
95
  }
80
96
 
97
+ // Keep danger styling visible when an invalid field is focused. Without this
98
+ // override the classy `:focus` rule above wins on specificity and the user
99
+ // sees the brand-blue ring instead of the red one Bootstrap normally applies.
100
+ &.is-invalid:focus {
101
+ border-color: $danger;
102
+ box-shadow: 0 0 0 0.25rem rgba($danger, 0.25);
103
+ }
104
+
81
105
  // // Override Chrome/Safari autofill background color with subtle indicator
82
106
  &:-webkit-autofill,
83
107
  &:-webkit-autofill:hover,
@@ -90,11 +90,6 @@ module.exports = async function (options) {
90
90
  await ensureRubyVersion();
91
91
  }
92
92
 
93
- // Ensure proper bundler version + install/update gems
94
- if (options.checkBundle) {
95
- await ensureBundle();
96
- }
97
-
98
93
  // Ensure peer dependencies are installed
99
94
  if (options.checkPeerDependencies) {
100
95
  await ensurePeerDependencies();
@@ -107,11 +102,17 @@ module.exports = async function (options) {
107
102
  setupScripts();
108
103
  }
109
104
 
110
- // Ensure _config.yml exists
105
+ // Ensure _config.yml + scaffolded files (Gemfile, etc.) exist.
106
+ // Must run BEFORE ensureBundle so `bundle install` has a Gemfile to read on first setup.
111
107
  if (options.ensureCoreFiles) {
112
108
  await ensureCoreFiles();
113
109
  }
114
110
 
111
+ // Ensure proper bundler version + install/update gems
112
+ if (options.checkBundle) {
113
+ await ensureBundle();
114
+ }
115
+
115
116
  // Create CNAME file
116
117
  if (options.createCname) {
117
118
  createCname();
@@ -267,11 +268,20 @@ async function ensureCoreFiles() {
267
268
 
268
269
  logger.log('No src/_config.yml found. Creating default config file...');
269
270
 
270
- const sourcePath = path.join(rootPathPackage, 'dist/defaults/src/_config.yml');
271
- const targetPath = path.join(rootPathProject, 'src/_config.yml');
271
+ // Files that must exist BEFORE the gulpfile loads. Several task modules
272
+ // (sass/distribute/imagemin) read these at module top-level, so a fresh
273
+ // consumer can't even invoke `gulp defaults` without them.
274
+ const coreFiles = [
275
+ 'src/_config.yml',
276
+ 'config/ultimate-jekyll-manager.json',
277
+ ];
272
278
 
273
- jetpack.copy(sourcePath, targetPath);
274
- logger.log(`Copied default _config.yml to src/_config.yml`);
279
+ coreFiles.forEach((relPath) => {
280
+ const sourcePath = path.join(rootPathPackage, 'dist/defaults', relPath);
281
+ const targetPath = path.join(rootPathProject, relPath);
282
+ jetpack.copy(sourcePath, targetPath, { overwrite: false });
283
+ logger.log(`Copied default ${relPath}`);
284
+ });
275
285
 
276
286
  // Inject new config into config variable
277
287
  config = Manager.getConfig('project');
@@ -42,13 +42,13 @@ alternative:
42
42
  value: "Full REST API"
43
43
  theirs:
44
44
  value: "Limited"
45
- - name: "Custom Branding"
45
+ - name: "Custom branding"
46
46
  icon: "palette"
47
47
  ours:
48
48
  value: true
49
49
  theirs:
50
50
  value: false
51
- - name: "Priority Support"
51
+ - name: "Priority support"
52
52
  icon: "headset"
53
53
  ours:
54
54
  value: "24/7"
@@ -24,7 +24,7 @@
24
24
  icon: 'list'
25
25
  },
26
26
  {
27
- label: 'Create New',
27
+ label: 'Create new',
28
28
  href: '/admin/users/new',
29
29
  icon: 'plus'
30
30
  }
@@ -78,7 +78,7 @@ prerender_icons:
78
78
  <div class="modal-header">
79
79
  <h5 class="modal-title" id="campaign-modal-title">
80
80
  {% uj_icon "plus", "fa-md me-2" %}
81
- <span id="campaign-modal-title-text">Create Campaign</span>
81
+ <span id="campaign-modal-title-text">Create campaign</span>
82
82
  </h5>
83
83
  <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
84
84
  </div>
@@ -561,7 +561,7 @@ prerender_icons:
561
561
  <button type="button" class="btn btn-outline-adaptive" data-bs-dismiss="modal">Cancel</button>
562
562
  <button type="submit" form="campaign-editor-form" class="btn btn-adaptive" id="btn-save-campaign">
563
563
  {% uj_icon "paper-plane", "fa-sm me-1" %}
564
- <span class="button-text">Save Campaign</span>
564
+ <span class="button-text">Save campaign</span>
565
565
  </button>
566
566
  </div>
567
567
  </div>
@@ -576,7 +576,7 @@ prerender_icons:
576
576
  <div class="modal-header">
577
577
  <h5 class="modal-title" id="campaign-results-title">
578
578
  {% uj_icon "eye", "fa-md me-2" %}
579
- <span id="campaign-results-title-text">Campaign Details</span>
579
+ <span id="campaign-results-title-text">Campaign details</span>
580
580
  </h5>
581
581
  <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
582
582
  </div>
@@ -586,7 +586,7 @@ prerender_icons:
586
586
  <div class="modal-footer">
587
587
  <button type="button" class="btn btn-outline-danger d-none" id="btn-retry-campaign">
588
588
  {% uj_icon "rotate-right", "fa-sm me-1" %}
589
- Retry (Create New)
589
+ Retry (Create new)
590
590
  </button>
591
591
  <button type="button" class="btn btn-outline-adaptive" data-bs-dismiss="modal">Close</button>
592
592
  </div>
@@ -56,7 +56,7 @@ prerender_icons:
56
56
  <h6 class="mb-0">{% uj_icon "database", "fa-sm me-2" %} Collections</h6>
57
57
  </div>
58
58
  <div class="card-body p-2">
59
- <!-- Quick Access -->
59
+ <!-- Quick access -->
60
60
  <div class="nav flex-column collection-nav" id="collection-links">
61
61
  <a href="#" class="nav-link px-2 py-1 d-flex justify-content-between align-items-center" data-collection="users">
62
62
  {% uj_icon "folder", "fa-sm me-2" %} users
@@ -127,7 +127,7 @@ prerender_icons:
127
127
  </div>
128
128
  <button type="submit" class="btn btn-sm btn-adaptive w-100">
129
129
  {% uj_icon "magnifying-glass", "fa-sm me-2" %}
130
- <span class="button-text">Run Query</span>
130
+ <span class="button-text">Run query</span>
131
131
  </button>
132
132
  </form>
133
133
  <button class="btn btn-sm btn-outline-adaptive w-100 mt-2 d-none" id="btn-clear-query">
@@ -84,7 +84,7 @@ prerender_icons:
84
84
  </div>
85
85
  </div>
86
86
 
87
- <!-- Active Users (30d) -->
87
+ <!-- Active users (30d) -->
88
88
  <div class="col-lg-4 col-md-6">
89
89
  <div class="card h-100">
90
90
  <div class="card-body text-center">
@@ -164,7 +164,7 @@ prerender_icons:
164
164
  <div class="modal-dialog modal-lg modal-dialog-scrollable">
165
165
  <div class="modal-content">
166
166
  <div class="modal-header">
167
- <h6 class="modal-title" id="user-detail-modal-label">User Details</h6>
167
+ <h6 class="modal-title" id="user-detail-modal-label">User details</h6>
168
168
  <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
169
169
  </div>
170
170
  <div class="modal-body p-0">
@@ -185,7 +185,7 @@ prerender_icons:
185
185
  </div>
186
186
  </div>
187
187
 
188
- <!-- Sign In As User Modal -->
188
+ <!-- Sign in as user modal -->
189
189
  <div class="modal fade" id="signin-as-modal" tabindex="-1" aria-labelledby="signin-as-modal-label" aria-hidden="true">
190
190
  <div class="modal-dialog modal-dialog-centered">
191
191
  <div class="modal-content">
@@ -248,7 +248,7 @@ prerender_icons:
248
248
  <div class="modal-dialog">
249
249
  <div class="modal-content">
250
250
  <div class="modal-header">
251
- <h6 class="modal-title" id="user-edit-modal-label">Edit User</h6>
251
+ <h6 class="modal-title" id="user-edit-modal-label">Edit user</h6>
252
252
  <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
253
253
  </div>
254
254
  <div class="modal-body">
@@ -292,7 +292,7 @@ prerender_icons:
292
292
  <button type="button" class="btn btn-sm btn-outline-adaptive" data-bs-dismiss="modal">Cancel</button>
293
293
  <button type="submit" class="btn btn-sm btn-adaptive">
294
294
  {% uj_icon "check", "fa-sm me-1" %}
295
- <span class="button-text">Save Changes</span>
295
+ <span class="button-text">Save changes</span>
296
296
  </button>
297
297
  </div>
298
298
  </form>
@@ -36,12 +36,12 @@ meta:
36
36
  <div class="col-lg-7">
37
37
  <div class="card">
38
38
  <div class="card-header">
39
- <h5 class="mb-0">Create New User</h5>
39
+ <h5 class="mb-0">Create new user</h5>
40
40
  </div>
41
41
  <div class="card-body">
42
42
  <form id="create-user-form" novalidate onsubmit="return false">
43
43
  <div class="mb-3">
44
- <label for="email" class="form-label">Email Address <span class="text-danger">*</span></label>
44
+ <label for="email" class="form-label">Email address <span class="text-danger">*</span></label>
45
45
  <input type="email" class="form-control" id="email" name="user.email" required autofocus>
46
46
  </div>
47
47
  <div class="row">
@@ -61,7 +61,7 @@ meta:
61
61
  <a href="/admin/users" class="btn btn-outline-adaptive">Cancel</a>
62
62
  <button type="submit" class="btn btn-adaptive">
63
63
  {% uj_icon "check", "fa-md me-2" %}
64
- <span class="button-text">Create User</span>
64
+ <span class="button-text">Create user</span>
65
65
  </button>
66
66
  </div>
67
67
  </form>
@@ -25,7 +25,7 @@ web_manager:
25
25
  </p>
26
26
 
27
27
  <p>
28
- <strong>What Are Cookies</strong>
28
+ <strong>What are cookies</strong>
29
29
  </p>
30
30
 
31
31
  <p>
@@ -38,7 +38,7 @@ web_manager:
38
38
  </p>
39
39
 
40
40
  <p>
41
- <strong>How We Use Cookies</strong>
41
+ <strong>How we use cookies</strong>
42
42
  </p>
43
43
 
44
44
  <p>
@@ -47,7 +47,7 @@ web_manager:
47
47
  </p>
48
48
 
49
49
  <p>
50
- <strong>Disabling Cookies</strong>
50
+ <strong>Disabling cookies</strong>
51
51
  </p>
52
52
 
53
53
  <p>
@@ -57,7 +57,7 @@ web_manager:
57
57
 
58
58
 
59
59
  <p>
60
- <strong>The Cookies We Set</strong>
60
+ <strong>The cookies we set</strong>
61
61
  </p>
62
62
 
63
63
  <ul>
@@ -133,7 +133,7 @@ web_manager:
133
133
  </ul>
134
134
 
135
135
  <p>
136
- <strong>Third Party Cookies</strong>
136
+ <strong>Third party cookies</strong>
137
137
  </p>
138
138
 
139
139
  <p>
@@ -191,7 +191,7 @@ web_manager:
191
191
  {{ content | uj_content_format }}
192
192
 
193
193
  <p>
194
- <strong>More Information</strong>
194
+ <strong>More information</strong>
195
195
  </p>
196
196
 
197
197
  <p>
@@ -122,7 +122,7 @@ Upon cancellation, you will not be charged any further subscription fees. No pay
122
122
 
123
123
  Any other purchases, including one-time payments, add-ons, or other sales on our site that do not include a free trial period are final and non-refundable once processed.
124
124
 
125
- Should you request a refund, we will determine the eligibility of your refund. If approved, you will be refunded via the same payment method you originally used. Requests can be made using our [refund request form](https://itwcreativeworks.com/portal/account/payment/refund).
125
+ Should you request a refund, we will determine the eligibility of your refund. If approved, you will be refunded via the same payment method you originally used. Requests can be made using our [refund request form]({{ site.url }}/account#refund).
126
126
 
127
127
  ### Debt Collection
128
128
  In the event of an outstanding balance on your account, we reserve the right to engage debt collection agencies to recover the unpaid amount.
@@ -4,9 +4,9 @@ layout: themes/[ site.theme.id ]/frontend/pages/portal/email-preferences
4
4
 
5
5
  ### REGULAR PAGES ###
6
6
  meta:
7
- title: "Email Preferences - {{ site.brand.name }}"
7
+ title: "Email preferences - {{ site.brand.name }}"
8
8
  description: "Manage your email preferences for {{ site.brand.name }}."
9
- breadcrumb: "Email Preferences"
9
+ breadcrumb: "Email preferences"
10
10
 
11
11
  ### MISC ###
12
12
  sitemap:
@@ -101,7 +101,7 @@ recent_activity:
101
101
 
102
102
  quick_actions:
103
103
  - id: "create_project"
104
- label: "Create New Project"
104
+ label: "Create new project"
105
105
  icon: "plus"
106
106
  style: "primary"
107
107
  href: "#"
@@ -113,7 +113,7 @@ quick_actions:
113
113
  href: "#"
114
114
 
115
115
  - id: "invite_members"
116
- label: "Invite Team Members"
116
+ label: "Invite team members"
117
117
  icon: "users"
118
118
  style: "outline-adaptive"
119
119
  href: "#"
@@ -176,7 +176,7 @@ progress_items:
176
176
  <div class="col-lg-8">
177
177
  <div class="card">
178
178
  <div class="card-body">
179
- <h5 class="mb-3">Recent Activity</h5>
179
+ <h5 class="mb-3">Recent activity</h5>
180
180
  <div class="list-group list-group-flush">
181
181
  {% for activity in page.resolved.recent_activity %}
182
182
  <div class="list-group-item bg-body-tertiary px-0">
@@ -205,7 +205,7 @@ progress_items:
205
205
  <div class="col-lg-4">
206
206
  <div class="card">
207
207
  <div class="card-body">
208
- <h5 class="mb-3">Quick Actions</h5>
208
+ <h5 class="mb-3">Quick actions</h5>
209
209
  <div class="d-grid gap-2">
210
210
  {% for action in page.resolved.quick_actions %}
211
211
  {% if action.href %}
@@ -227,7 +227,7 @@ progress_items:
227
227
  <!-- Progress Overview -->
228
228
  <div class="card mt-3">
229
229
  <div class="card-body">
230
- <h5 class="mb-3">Progress Overview</h5>
230
+ <h5 class="mb-3">Progress overview</h5>
231
231
  {% for item in page.resolved.progress_items %}
232
232
  <div class="mb-3">
233
233
  <div class="d-flex justify-content-between mb-1">
@@ -45,11 +45,11 @@ suggested_pages:
45
45
  <div class="d-grid gap-2 d-sm-block text-center mb-4">
46
46
  <a href="{{ site.url }}" class="btn btn-adaptive btn-lg mb-2 mb-sm-0 me-sm-2">
47
47
  {% uj_icon "house", "me-2" %}
48
- Back to Home
48
+ Back to home
49
49
  </a>
50
50
  <button class="btn btn-outline-adaptive btn-lg mb-2 mb-sm-0" onclick="window.history.back()">
51
51
  {% uj_icon "arrow-left", "me-2" %}
52
- Go Back
52
+ Go back
53
53
  </button>
54
54
  </div>
55
55
 
@@ -58,10 +58,10 @@ stats:
58
58
  label: "Founded"
59
59
  icon: "calendar"
60
60
  - number: "50+"
61
- label: "Team Members"
61
+ label: "Team members"
62
62
  icon: "users"
63
63
  - number: "1M+"
64
- label: "Happy Customers"
64
+ label: "Happy customers"
65
65
  icon: "smile"
66
66
  - number: "120+"
67
67
  label: "Countries"
@@ -98,7 +98,7 @@ team_cta:
98
98
  headline_accent: "team"
99
99
  subheadline: "Get to know the passionate individuals behind {{ site.brand.name }} who work every day to make your experience exceptional"
100
100
  button:
101
- text: "Meet the Team"
101
+ text: "Meet the team"
102
102
  icon: "arrow-right"
103
103
  href: "/team"
104
104
  ---