ultimate-jekyll-manager 0.0.118 → 0.0.120
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.
- package/CLAUDE.md +409 -23
- package/README.md +171 -2
- package/TODO.md +10 -2
- package/_backup/form-manager.backup.js +1020 -0
- package/dist/assets/js/core/auth.js +5 -4
- package/dist/assets/js/core/cookieconsent.js +24 -17
- package/dist/assets/js/core/exit-popup.js +15 -12
- package/dist/assets/js/core/social-sharing.js +8 -4
- package/dist/assets/js/libs/auth/pages.js +78 -149
- package/dist/assets/js/libs/dev.js +192 -129
- package/dist/assets/js/libs/form-manager.js +643 -775
- package/dist/assets/js/pages/account/index.js +3 -2
- package/dist/assets/js/pages/account/sections/api-keys.js +37 -52
- package/dist/assets/js/pages/account/sections/connections.js +37 -46
- package/dist/assets/js/pages/account/sections/delete.js +57 -78
- package/dist/assets/js/pages/account/sections/profile.js +37 -56
- package/dist/assets/js/pages/account/sections/security.js +102 -125
- package/dist/assets/js/pages/admin/notifications/new/index.js +73 -151
- package/dist/assets/js/pages/blog/index.js +33 -53
- package/dist/assets/js/pages/contact/index.js +112 -173
- package/dist/assets/js/pages/download/index.js +39 -86
- package/dist/assets/js/pages/oauth2/index.js +17 -17
- package/dist/assets/js/pages/payment/checkout/index.js +23 -36
- package/dist/assets/js/pages/pricing/index.js +5 -2
- package/dist/assets/js/pages/test/libraries/form-manager/index.js +194 -0
- package/dist/assets/themes/classy/css/components/_cards.scss +2 -2
- package/dist/defaults/_.env +6 -0
- package/dist/defaults/_.gitignore +7 -1
- package/dist/defaults/dist/_includes/core/body.html +5 -13
- package/dist/defaults/dist/_includes/core/foot.html +1 -0
- package/dist/defaults/dist/_includes/themes/classy/frontend/sections/nav.html +51 -36
- package/dist/defaults/dist/_layouts/blueprint/admin/notifications/new.html +13 -2
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/about.html +84 -42
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/account/index.html +26 -21
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/signin.html +2 -2
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/signup.html +2 -2
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/blog/index.html +72 -58
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/blog/post.html +46 -29
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/contact.html +46 -53
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/download.html +111 -73
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/index.html +111 -56
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html +127 -81
- package/dist/defaults/dist/pages/test/libraries/form-manager.html +181 -0
- package/dist/defaults/dist/pages/test/libraries/lazy-loading.html +1 -1
- package/dist/gulp/tasks/defaults.js +210 -1
- package/dist/gulp/tasks/serve.js +18 -0
- package/dist/lib/logger.js +1 -1
- package/firebase-debug.log +770 -0
- package/package.json +6 -6
- package/.playwright-mcp/page-2025-10-22T19-11-27-666Z.png +0 -0
- package/.playwright-mcp/page-2025-10-22T19-11-57-357Z.png +0 -0
package/CLAUDE.md
CHANGED
|
@@ -26,19 +26,179 @@ The local development server URL is stored in `.temp/_config_browsersync.yml` in
|
|
|
26
26
|
|
|
27
27
|
## Asset Organization
|
|
28
28
|
|
|
29
|
-
###
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
### Ultimate Jekyll Manager Files (THIS project)
|
|
30
|
+
|
|
31
|
+
**CSS:**
|
|
32
|
+
- `src/assets/css/ultimate-jekyll-manager.scss` - Main UJ stylesheet (provides core styles)
|
|
32
33
|
- `src/assets/css/global/` - Global UJ styles
|
|
33
|
-
- `src/assets/css/pages/` - Page-specific styles
|
|
34
|
+
- `src/assets/css/pages/` - Page-specific styles provided by UJ
|
|
34
35
|
- Format: `src/assets/css/pages/[page-name]/index.scss`
|
|
36
|
+
- Example: `src/assets/css/pages/download/index.scss`
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
- `src/assets/js/ultimate-jekyll-manager.js` - Main UJ JavaScript entry point
|
|
38
|
-
- `src/assets/js/main.js` - Site-wide JavaScript (runs on every page)
|
|
38
|
+
**JavaScript:**
|
|
39
|
+
- `src/assets/js/ultimate-jekyll-manager.js` - Main UJ JavaScript entry point (provides core functionality)
|
|
39
40
|
- `src/assets/js/core/` - Core UJ modules
|
|
40
|
-
- `src/assets/js/pages/` - Page-specific JavaScript
|
|
41
|
+
- `src/assets/js/pages/` - Page-specific JavaScript provided by UJ
|
|
41
42
|
- Format: `src/assets/js/pages/[page-name]/index.js`
|
|
43
|
+
- Example: `src/assets/js/pages/download/index.js`
|
|
44
|
+
- `src/assets/js/libs/` - UJ library modules (prerendered-icons, form-manager, authorized-fetch, etc.)
|
|
45
|
+
|
|
46
|
+
**Default Pages & Layouts:**
|
|
47
|
+
|
|
48
|
+
UJ provides default page templates and layouts in `src/defaults/dist/` that are copied to consuming projects. These are NOT meant to be edited by users.
|
|
49
|
+
|
|
50
|
+
- Format: `src/defaults/dist/_layouts/themes/[theme-id]/frontend/pages/[page-name].html`
|
|
51
|
+
- Examples:
|
|
52
|
+
- `src/defaults/dist/_layouts/themes/classy/frontend/pages/download.html`
|
|
53
|
+
- `src/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html`
|
|
54
|
+
- `src/defaults/dist/_layouts/themes/classy/frontend/pages/payment/checkout.html`
|
|
55
|
+
- `src/defaults/dist/_layouts/themes/classy/frontend/pages/payment/confirmation.html`
|
|
56
|
+
- `src/defaults/dist/_layouts/themes/classy/frontend/pages/contact.html`
|
|
57
|
+
- Core layouts:
|
|
58
|
+
- `src/defaults/dist/_layouts/core/root.html` - Root HTML wrapper
|
|
59
|
+
- `src/defaults/dist/_layouts/themes/[theme-id]/frontend/core/base.html` - Theme base layout
|
|
60
|
+
|
|
61
|
+
**Complete UJ Page Example:**
|
|
62
|
+
- **HTML:** `src/defaults/dist/_layouts/themes/classy/frontend/pages/download.html`
|
|
63
|
+
- **CSS:** `src/assets/css/pages/download/index.scss`
|
|
64
|
+
- **JS:** `src/assets/js/pages/download/index.js`
|
|
65
|
+
|
|
66
|
+
These files serve as blueprints and reference implementations. When building custom pages in consuming projects, reference these for patterns and best practices.
|
|
67
|
+
|
|
68
|
+
**IMPORTANT:** Consuming projects CAN create files with the same paths in their own `src/` directory to override UJ defaults, but this should ONLY be done when absolutely necessary. Prefer using `src/pages/` and `src/_layouts/` for custom pages instead of overriding UJ default files.
|
|
69
|
+
|
|
70
|
+
### Section Configuration Files (JSON)
|
|
71
|
+
|
|
72
|
+
UJ provides JSON configuration files for common sections like navigation and footer. These JSON files are consumed by corresponding HTML templates during the build process.
|
|
73
|
+
|
|
74
|
+
**Configuration Files:**
|
|
75
|
+
- `src/defaults/src/_includes/frontend/sections/nav.json` - Navigation configuration
|
|
76
|
+
- `src/defaults/src/_includes/frontend/sections/footer.json` - Footer configuration
|
|
77
|
+
|
|
78
|
+
**How It Works:**
|
|
79
|
+
1. JSON files contain structured data (links, labels, settings)
|
|
80
|
+
2. HTML templates in `src/defaults/dist/_includes/themes/[theme-id]/frontend/sections/` read and render this data
|
|
81
|
+
3. The build process converts `.json` → data loaded by `.html` templates
|
|
82
|
+
|
|
83
|
+
**Customizing Navigation/Footer:**
|
|
84
|
+
|
|
85
|
+
Consuming projects should create their own JSON files in `src/_includes/frontend/sections/`:
|
|
86
|
+
- `src/_includes/frontend/sections/nav.json`
|
|
87
|
+
- `src/_includes/frontend/sections/footer.json`
|
|
88
|
+
|
|
89
|
+
**Example: Footer Configuration**
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
logo: {
|
|
94
|
+
href: '/',
|
|
95
|
+
class: 'filter-adaptive',
|
|
96
|
+
text: '{{ site.brand.name }}',
|
|
97
|
+
description: '{{ site.meta.description }}',
|
|
98
|
+
},
|
|
99
|
+
links: [
|
|
100
|
+
{
|
|
101
|
+
label: 'Company',
|
|
102
|
+
href: null,
|
|
103
|
+
links: [
|
|
104
|
+
{
|
|
105
|
+
label: 'About Us',
|
|
106
|
+
href: '/about',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
label: 'Pricing',
|
|
110
|
+
href: '/pricing',
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
socials: {
|
|
116
|
+
enabled: true,
|
|
117
|
+
},
|
|
118
|
+
copyright: {
|
|
119
|
+
enabled: true,
|
|
120
|
+
text: null,
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Note:** These are JSON5 files (support comments, trailing commas, unquoted keys). The corresponding HTML templates automatically process these files during the build.
|
|
126
|
+
|
|
127
|
+
### Customizing Default Pages via Frontmatter
|
|
128
|
+
|
|
129
|
+
**BEST PRACTICE:** UJ default pages are designed to be customized through frontmatter WITHOUT writing any HTML. Consuming projects can create a simple page that includes ONLY frontmatter to configure the default page's behavior.
|
|
130
|
+
|
|
131
|
+
**How It Works:**
|
|
132
|
+
1. UJ default pages use `page.resolved` to access merged frontmatter (site → layout → page)
|
|
133
|
+
2. **IMPORTANT:** Before customizing, READ the UJ default page in `src/defaults/dist/_layouts/` to understand available frontmatter options and how they're used
|
|
134
|
+
3. Consuming projects create a page in `src/pages/` with custom frontmatter
|
|
135
|
+
4. The page uses a UJ layout (e.g., `blueprint/pricing`)
|
|
136
|
+
5. Frontmatter overrides default values without any HTML
|
|
137
|
+
|
|
138
|
+
**Example: Customizing the Pricing Page**
|
|
139
|
+
|
|
140
|
+
**Step 1:** Read the UJ default pricing page to see available frontmatter options:
|
|
141
|
+
- File: `src/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html`
|
|
142
|
+
- Look for frontmatter at the top and how `page.resolved.pricing` is used in the HTML
|
|
143
|
+
|
|
144
|
+
**Step 2:** In consuming project, create `src/pages/pricing.html`:
|
|
145
|
+
|
|
146
|
+
```yaml
|
|
147
|
+
---
|
|
148
|
+
### ALL PAGES ###
|
|
149
|
+
layout: blueprint/pricing
|
|
150
|
+
permalink: /pricing
|
|
151
|
+
|
|
152
|
+
### PAGE CONFIG ###
|
|
153
|
+
pricing:
|
|
154
|
+
price_per_unit:
|
|
155
|
+
enabled: true
|
|
156
|
+
feature_id: "credits"
|
|
157
|
+
label: "credit"
|
|
158
|
+
plans:
|
|
159
|
+
- id: "basic"
|
|
160
|
+
name: "Basic"
|
|
161
|
+
tagline: "best for getting started"
|
|
162
|
+
url: "/download"
|
|
163
|
+
pricing:
|
|
164
|
+
monthly: 0
|
|
165
|
+
annually: 0
|
|
166
|
+
features:
|
|
167
|
+
- id: "credits"
|
|
168
|
+
name: "Credits"
|
|
169
|
+
value: 1
|
|
170
|
+
icon: "sparkles"
|
|
171
|
+
...
|
|
172
|
+
---
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
That's it! No HTML needed. The UJ pricing layout reads `page.resolved.pricing` and renders the plans accordingly.
|
|
176
|
+
|
|
177
|
+
**When to Use Frontmatter Customization:**
|
|
178
|
+
- ✅ Customizing UJ default pages (pricing, contact, download, etc.)
|
|
179
|
+
- ✅ Changing configuration without touching HTML
|
|
180
|
+
- ✅ Maintaining upgradability when UJ updates
|
|
181
|
+
|
|
182
|
+
**When to Create Custom Pages:**
|
|
183
|
+
- ❌ Building entirely new page types
|
|
184
|
+
- ❌ Needing custom HTML structure
|
|
185
|
+
- ❌ Pages with unique layouts not provided by UJ
|
|
186
|
+
|
|
187
|
+
### Consuming Project Files
|
|
188
|
+
|
|
189
|
+
**CSS:**
|
|
190
|
+
- `src/assets/css/main.scss` - Site-wide custom styles (runs on every page, edits by consuming project)
|
|
191
|
+
- `src/assets/css/pages/` - Page-specific custom styles
|
|
192
|
+
- Format: `src/assets/css/pages/[page-name]/index.scss`
|
|
193
|
+
|
|
194
|
+
**JavaScript:**
|
|
195
|
+
- `src/assets/js/main.js` - Site-wide custom JavaScript (runs on every page, edits by consuming project)
|
|
196
|
+
- `src/assets/js/pages/` - Page-specific custom JavaScript
|
|
197
|
+
- Format: `src/assets/js/pages/[page-name]/index.js`
|
|
198
|
+
|
|
199
|
+
**Pages & Layouts:**
|
|
200
|
+
- `src/pages/` - Individual page HTML/Markdown files
|
|
201
|
+
- `src/_layouts/` - Custom layouts for the consuming project
|
|
42
202
|
|
|
43
203
|
**Asset Loading:** Page-specific CSS/JS files are automatically included based on the page's canonical path. Override with `asset_path` frontmatter.
|
|
44
204
|
|
|
@@ -170,36 +330,95 @@ asset_path: blog/post
|
|
|
170
330
|
|
|
171
331
|
Uses `/assets/css/pages/{{ asset_path }}.bundle.css` instead of deriving from `page.canonical.path`. Useful when multiple pages share assets (e.g., all blog posts).
|
|
172
332
|
|
|
173
|
-
## Icon
|
|
333
|
+
## Icon System
|
|
174
334
|
|
|
175
|
-
Ultimate Jekyll
|
|
335
|
+
Ultimate Jekyll uses Font Awesome icons but does NOT include the Font Awesome JavaScript or CSS library. All icons must be rendered server-side using Jekyll's `{% uj_icon %}` tag.
|
|
176
336
|
|
|
177
|
-
###
|
|
337
|
+
### When to Use `{% uj_icon %}` vs Prerendered Icons
|
|
178
338
|
|
|
179
|
-
|
|
339
|
+
**IMPORTANT:** Use the correct method based on WHERE the icon will be used:
|
|
340
|
+
|
|
341
|
+
#### Use `{% uj_icon %}` in HTML/Liquid Templates
|
|
342
|
+
|
|
343
|
+
When icons are part of the static HTML template, use `{% uj_icon %}` directly:
|
|
344
|
+
|
|
345
|
+
```liquid
|
|
346
|
+
<!-- Alerts -->
|
|
347
|
+
<div class="alert alert-success">
|
|
348
|
+
{% uj_icon "circle-check", "fa-sm" %} Success message
|
|
349
|
+
</div>
|
|
350
|
+
|
|
351
|
+
<!-- Buttons -->
|
|
352
|
+
<button class="btn btn-primary">
|
|
353
|
+
{% uj_icon "paper-plane", "fa-md me-2" %}
|
|
354
|
+
Send
|
|
355
|
+
</button>
|
|
356
|
+
|
|
357
|
+
<!-- Labels -->
|
|
358
|
+
<label>
|
|
359
|
+
{% uj_icon "envelope", "fa-sm me-1 text-info" %}
|
|
360
|
+
Email
|
|
361
|
+
</label>
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**Use this when:**
|
|
365
|
+
- The icon is in a Jekyll template (.html file)
|
|
366
|
+
- The icon is static and known at build time
|
|
367
|
+
- The icon is part of the page structure
|
|
368
|
+
|
|
369
|
+
#### Use Prerendered Icons in JavaScript
|
|
370
|
+
|
|
371
|
+
When icons need to be dynamically inserted via JavaScript, pre-render them in frontmatter and access them via the library:
|
|
372
|
+
|
|
373
|
+
**1. Add icons to page frontmatter:**
|
|
180
374
|
```yaml
|
|
181
375
|
---
|
|
182
376
|
prerender_icons:
|
|
183
|
-
- name: "
|
|
184
|
-
class: "fa-
|
|
185
|
-
- name: "
|
|
186
|
-
class: "fa-
|
|
187
|
-
- name: "
|
|
377
|
+
- name: "mobile"
|
|
378
|
+
class: "fa-sm me-1"
|
|
379
|
+
- name: "envelope"
|
|
380
|
+
class: "fa-sm me-1"
|
|
381
|
+
- name: "bell"
|
|
382
|
+
class: "fa-sm me-1"
|
|
188
383
|
---
|
|
189
384
|
```
|
|
190
385
|
|
|
191
|
-
2.
|
|
386
|
+
**2. Import the library in JavaScript:**
|
|
387
|
+
```javascript
|
|
388
|
+
import { getPrerenderedIcon } from '__main_assets__/js/libs/prerendered-icons.js';
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**3. Use in your code:**
|
|
392
|
+
```javascript
|
|
393
|
+
const iconHTML = getPrerenderedIcon('mobile');
|
|
394
|
+
$badge.innerHTML = `${iconHTML}Push Notification`;
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**Use this when:**
|
|
398
|
+
- Icons are dynamically inserted via JavaScript
|
|
399
|
+
- Icons are part of dynamically generated content
|
|
400
|
+
- Icons are added to elements created with `document.createElement()`
|
|
401
|
+
|
|
402
|
+
### What NOT to Do
|
|
192
403
|
|
|
193
|
-
|
|
404
|
+
**NEVER use manual icon HTML in JavaScript:**
|
|
194
405
|
```javascript
|
|
195
|
-
|
|
406
|
+
// ❌ WRONG - Bootstrap Icons (we don't use Bootstrap Icons)
|
|
407
|
+
$el.innerHTML = '<i class="bi bi-check-circle"></i> Text';
|
|
408
|
+
|
|
409
|
+
// ❌ WRONG - Manual Font Awesome (we don't have FA JS/CSS)
|
|
410
|
+
$el.innerHTML = '<i class="fa-solid fa-check"></i> Text';
|
|
411
|
+
|
|
412
|
+
// ✅ CORRECT - Use prerendered icons
|
|
413
|
+
const iconHTML = getPrerenderedIcon('circle-check');
|
|
414
|
+
$el.innerHTML = `${iconHTML} Text`;
|
|
196
415
|
```
|
|
197
416
|
|
|
198
417
|
### Benefits
|
|
199
418
|
- Icons are rendered server-side with proper Font Awesome classes
|
|
200
419
|
- No client-side icon generation overhead
|
|
201
420
|
- Consistent icon styling across the application
|
|
202
|
-
-
|
|
421
|
+
- No Font Awesome JavaScript/CSS library needed
|
|
203
422
|
|
|
204
423
|
## CSS Guidelines
|
|
205
424
|
|
|
@@ -378,21 +597,188 @@ const response = await authorizedFetch(url, options);
|
|
|
378
597
|
|
|
379
598
|
#### FormManager Library
|
|
380
599
|
|
|
381
|
-
|
|
600
|
+
Lightweight form state management library with built-in validation, state machine, and event system.
|
|
382
601
|
|
|
383
602
|
**Import:**
|
|
384
603
|
```javascript
|
|
385
604
|
import { FormManager } from '__main_assets__/js/libs/form-manager.js';
|
|
386
605
|
```
|
|
387
606
|
|
|
388
|
-
**Usage:**
|
|
607
|
+
**Basic Usage:**
|
|
389
608
|
```javascript
|
|
390
609
|
const formManager = new FormManager('#my-form', options);
|
|
610
|
+
|
|
611
|
+
formManager.on('submit', async ({ data, $submitButton }) => {
|
|
612
|
+
const response = await fetch('/api', { body: JSON.stringify(data) });
|
|
613
|
+
if (!response.ok) throw new Error('Failed');
|
|
614
|
+
formManager.showSuccess('Form submitted!');
|
|
615
|
+
});
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
**State Machine:**
|
|
619
|
+
```
|
|
620
|
+
initializing → ready ⇄ submitting → ready (or submitted)
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
**Configuration Options:**
|
|
624
|
+
```javascript
|
|
625
|
+
{
|
|
626
|
+
autoReady: true, // Auto-transition to initialState when DOM ready
|
|
627
|
+
initialState: 'ready', // State after autoReady fires
|
|
628
|
+
allowResubmit: true, // Allow resubmission after success (false = 'submitted' state)
|
|
629
|
+
resetOnSuccess: false, // Clear form fields after successful submission
|
|
630
|
+
warnOnUnsavedChanges: false, // Warn user before leaving with unsaved changes
|
|
631
|
+
submittingText: 'Processing...', // Text shown on submit button during submission
|
|
632
|
+
submittedText: 'Processed!' // Text shown on submit button after success (when allowResubmit: false)
|
|
633
|
+
}
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
**Events:**
|
|
637
|
+
|
|
638
|
+
| Event | Payload | Description |
|
|
639
|
+
|-------|---------|-------------|
|
|
640
|
+
| `submit` | `{ data, $submitButton }` | Form submission (throw error to show failure) |
|
|
641
|
+
| `validation` | `{ data, setError }` | Custom validation before submit |
|
|
642
|
+
| `change` | `{ field, name, value, data }` | Field value changed |
|
|
643
|
+
| `statechange` | `{ state, previousState }` | State transition |
|
|
644
|
+
|
|
645
|
+
**Validation System:**
|
|
646
|
+
|
|
647
|
+
FormManager runs validation automatically before `submit`:
|
|
648
|
+
1. **HTML5 validation** - Checks `required`, `minlength`, `maxlength`, `min`, `max`, `pattern`, `type="email"`, `type="url"`
|
|
649
|
+
2. **Custom validation** - Use `validation` event for business logic
|
|
650
|
+
|
|
651
|
+
```javascript
|
|
652
|
+
fm.on('validation', ({ data, setError }) => {
|
|
653
|
+
if (data.age && parseInt(data.age) < 18) {
|
|
654
|
+
setError('age', 'You must be 18 or older');
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
Errors display with Bootstrap's `is-invalid` class and `.invalid-feedback` elements.
|
|
660
|
+
|
|
661
|
+
**Autofocus:**
|
|
662
|
+
|
|
663
|
+
When the form transitions to `ready` state, FormManager automatically focuses the field with the `autofocus` attribute (if present and not disabled).
|
|
664
|
+
|
|
665
|
+
**Methods:**
|
|
666
|
+
|
|
667
|
+
| Method | Description |
|
|
668
|
+
|--------|-------------|
|
|
669
|
+
| `on(event, callback)` | Register event listener (chainable) |
|
|
670
|
+
| `ready()` | Transition to ready state |
|
|
671
|
+
| `getData()` | Get form data as nested object (supports dot notation) |
|
|
672
|
+
| `setData(obj)` | Set form values from nested object |
|
|
673
|
+
| `showSuccess(msg)` | Show success notification |
|
|
674
|
+
| `showError(msg)` | Show error notification |
|
|
675
|
+
| `reset()` | Reset form and go to ready state |
|
|
676
|
+
| `isDirty()` | Check if form has unsaved changes |
|
|
677
|
+
| `setDirty(bool)` | Set dirty state |
|
|
678
|
+
| `clearFieldErrors()` | Clear all field validation errors |
|
|
679
|
+
| `throwFieldErrors({ field: msg })` | Set and display field errors, throw error |
|
|
680
|
+
|
|
681
|
+
**Nested Field Names (Dot Notation):**
|
|
682
|
+
|
|
683
|
+
Use dot notation in field names for nested data:
|
|
684
|
+
```html
|
|
685
|
+
<input name="user.address.city" value="NYC">
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
Results in:
|
|
689
|
+
```javascript
|
|
690
|
+
{ user: { address: { city: 'NYC' } } }
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
**Checkbox Handling:**
|
|
694
|
+
- **Single checkbox:** Returns `true`/`false`
|
|
695
|
+
- **Checkbox group (same name):** Returns object `{ value1: true, value2: false }`
|
|
696
|
+
|
|
697
|
+
**Multiple Submit Buttons:**
|
|
698
|
+
|
|
699
|
+
Access the clicked button via `$submitButton`:
|
|
700
|
+
```html
|
|
701
|
+
<button type="submit" data-action="save">Save</button>
|
|
702
|
+
<button type="submit" data-action="draft">Save Draft</button>
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
```javascript
|
|
706
|
+
fm.on('submit', async ({ data, $submitButton }) => {
|
|
707
|
+
const action = $submitButton?.dataset?.action; // 'save' or 'draft'
|
|
708
|
+
});
|
|
391
709
|
```
|
|
392
710
|
|
|
393
711
|
**Reference:** `src/assets/js/libs/form-manager.js`
|
|
712
|
+
**Test Page:** `src/assets/js/pages/test/libraries/form-manager/index.js`
|
|
394
713
|
**Example:** `src/assets/js/pages/contact/index.js`
|
|
395
714
|
|
|
715
|
+
## Analytics & Tracking
|
|
716
|
+
|
|
717
|
+
Ultimate Jekyll uses three tracking platforms: Google Analytics (gtag), Facebook Pixel (fbq), and TikTok Pixel (ttq).
|
|
718
|
+
|
|
719
|
+
### Tracking Guidelines
|
|
720
|
+
|
|
721
|
+
**IMPORTANT Rules:**
|
|
722
|
+
- Track important user events with `gtag()`, `fbq()`, and `ttq()` functions
|
|
723
|
+
- NEVER add conditional checks for tracking functions (e.g., `if (typeof gtag !== 'undefined')`)
|
|
724
|
+
- Always assume tracking functions exist - they're globally available or stubbed
|
|
725
|
+
- Reference standard events documentation before implementing custom tracking
|
|
726
|
+
|
|
727
|
+
**Standard Events Documentation:**
|
|
728
|
+
- **Google Analytics GA4:** https://developers.google.com/analytics/devguides/collection/ga4/reference/events
|
|
729
|
+
- **Facebook Pixel:** https://www.facebook.com/business/help/402791146561655?id=1205376682832142
|
|
730
|
+
- **TikTok Pixel:** https://ads.tiktok.com/help/article/standard-events-parameters?redirected=2
|
|
731
|
+
|
|
732
|
+
### Platform-Specific Requirements
|
|
733
|
+
|
|
734
|
+
#### TikTok Pixel Requirements
|
|
735
|
+
TikTok has strict validation requirements:
|
|
736
|
+
|
|
737
|
+
**Required Parameters:**
|
|
738
|
+
- `content_id` - MUST be included in all events
|
|
739
|
+
|
|
740
|
+
**Valid Content Types:**
|
|
741
|
+
- `"product"`
|
|
742
|
+
- `"product_group"`
|
|
743
|
+
- `"destination"`
|
|
744
|
+
- `"hotel"`
|
|
745
|
+
- `"flight"`
|
|
746
|
+
- `"vehicle"`
|
|
747
|
+
|
|
748
|
+
Any other content type will generate a validation error.
|
|
749
|
+
|
|
750
|
+
**Example:**
|
|
751
|
+
```javascript
|
|
752
|
+
// ✅ CORRECT
|
|
753
|
+
ttq.track('ViewContent', {
|
|
754
|
+
content_id: 'product-123',
|
|
755
|
+
content_type: 'product'
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
// ❌ WRONG - Missing content_id
|
|
759
|
+
ttq.track('ViewContent', {
|
|
760
|
+
content_type: 'product'
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// ❌ WRONG - Invalid content_type
|
|
764
|
+
ttq.track('ViewContent', {
|
|
765
|
+
content_id: 'product-123',
|
|
766
|
+
content_type: 'custom' // Not in approved list
|
|
767
|
+
});
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
### Tracking Implementation
|
|
771
|
+
|
|
772
|
+
**IMPORTANT:** Always track events to ALL THREE platforms in this order:
|
|
773
|
+
1. Google Analytics (gtag)
|
|
774
|
+
2. Facebook Pixel (fbq)
|
|
775
|
+
3. TikTok Pixel (ttq)
|
|
776
|
+
|
|
777
|
+
Track events directly without existence checks. All three tracking calls should be made together for every event.
|
|
778
|
+
|
|
779
|
+
**Development Mode:**
|
|
780
|
+
In development mode, all tracking calls are intercepted and logged to the console for debugging. See `src/assets/js/libs/dev.js` for implementation.
|
|
781
|
+
|
|
396
782
|
## Audit Workflow
|
|
397
783
|
|
|
398
784
|
When fixing issues identified by the audit task (`src/gulp/tasks/audit.js`):
|
package/README.md
CHANGED
|
@@ -381,14 +381,183 @@ const response = await authorizedFetch(serverApiURL, {
|
|
|
381
381
|
|
|
382
382
|
#### FormManager Library
|
|
383
383
|
|
|
384
|
-
|
|
384
|
+
Lightweight form state management library with built-in validation, state machine, and event system.
|
|
385
385
|
|
|
386
386
|
**Import:**
|
|
387
387
|
```javascript
|
|
388
388
|
import { FormManager } from '__main_assets__/js/libs/form-manager.js';
|
|
389
389
|
```
|
|
390
390
|
|
|
391
|
-
**
|
|
391
|
+
**Basic Usage:**
|
|
392
|
+
```javascript
|
|
393
|
+
const formManager = new FormManager('#my-form', {
|
|
394
|
+
allowResubmit: true, // Allow form to be submitted again after success
|
|
395
|
+
resetOnSuccess: false, // Don't clear fields after success
|
|
396
|
+
warnOnUnsavedChanges: false // Don't warn on page leave
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Handle form submission
|
|
400
|
+
formManager.on('submit', async ({ data, $submitButton }) => {
|
|
401
|
+
const response = await fetch('/api/submit', {
|
|
402
|
+
method: 'POST',
|
|
403
|
+
body: JSON.stringify(data)
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
if (!response.ok) {
|
|
407
|
+
throw new Error('Submission failed');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
formManager.showSuccess('Form submitted successfully!');
|
|
411
|
+
});
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
**State Machine:**
|
|
415
|
+
```
|
|
416
|
+
initializing → ready ⇄ submitting → ready (or submitted)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**Events:**
|
|
420
|
+
|
|
421
|
+
| Event | Payload | Description |
|
|
422
|
+
|-------|---------|-------------|
|
|
423
|
+
| `submit` | `{ data, $submitButton }` | Form submission. Throw an error to show failure message. |
|
|
424
|
+
| `validation` | `{ data, setError }` | Custom validation before submit. Use `setError(fieldName, message)` to add errors. |
|
|
425
|
+
| `change` | `{ field, name, value, data }` | Called when any field value changes. |
|
|
426
|
+
| `statechange` | `{ state, previousState }` | Called when form state changes. |
|
|
427
|
+
|
|
428
|
+
**Validation:**
|
|
429
|
+
|
|
430
|
+
FormManager runs validation automatically before the `submit` event:
|
|
431
|
+
|
|
432
|
+
1. **HTML5 Validation** - Automatically checks `required`, `minlength`, `maxlength`, `min`, `max`, `pattern`, `type="email"`, `type="url"`
|
|
433
|
+
2. **Custom Validation** - Use the `validation` event for business logic
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
formManager.on('validation', ({ data, setError }) => {
|
|
437
|
+
// Custom validation runs AFTER HTML5 validation
|
|
438
|
+
if (data.age && parseInt(data.age) < 18) {
|
|
439
|
+
setError('age', 'You must be 18 or older');
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (data.password !== data.confirmPassword) {
|
|
443
|
+
setError('confirmPassword', 'Passwords do not match');
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Validation errors are displayed using Bootstrap's `is-invalid` class and `.invalid-feedback` elements. The first field with an error is automatically focused.
|
|
449
|
+
|
|
450
|
+
**Autofocus:**
|
|
451
|
+
|
|
452
|
+
When the form transitions to `ready` state, FormManager automatically focuses any field with the `autofocus` attribute:
|
|
453
|
+
|
|
454
|
+
```html
|
|
455
|
+
<input type="text" name="email" autofocus>
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**Methods:**
|
|
459
|
+
|
|
460
|
+
| Method | Description |
|
|
461
|
+
|--------|-------------|
|
|
462
|
+
| `on(event, callback)` | Register event listener (chainable) |
|
|
463
|
+
| `ready()` | Manually transition to ready state (for `autoReady: false`) |
|
|
464
|
+
| `getData()` | Get form data as nested object |
|
|
465
|
+
| `setData(obj)` | Populate form from a nested object |
|
|
466
|
+
| `showSuccess(msg)` | Show success notification |
|
|
467
|
+
| `showError(msg)` | Show error notification |
|
|
468
|
+
| `reset()` | Reset form and state |
|
|
469
|
+
| `isDirty()` | Check if form has unsaved changes |
|
|
470
|
+
| `clearFieldErrors()` | Clear all validation error displays |
|
|
471
|
+
| `throwFieldErrors({ field: msg })` | Set errors and throw (for use in submit handler) |
|
|
472
|
+
|
|
473
|
+
**Nested Field Names (Dot Notation):**
|
|
474
|
+
|
|
475
|
+
Use dot notation in field `name` attributes for nested data structures:
|
|
476
|
+
|
|
477
|
+
```html
|
|
478
|
+
<input name="user.name" value="John">
|
|
479
|
+
<input name="user.address.city" value="NYC">
|
|
480
|
+
<input name="user.address.zip" value="10001">
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Produces:
|
|
484
|
+
```javascript
|
|
485
|
+
{
|
|
486
|
+
user: {
|
|
487
|
+
name: 'John',
|
|
488
|
+
address: {
|
|
489
|
+
city: 'NYC',
|
|
490
|
+
zip: '10001'
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
**Checkbox Handling:**
|
|
497
|
+
|
|
498
|
+
- **Single checkbox:** Returns `true` or `false`
|
|
499
|
+
- **Checkbox group (multiple with same name):** Returns object with each value as key
|
|
500
|
+
|
|
501
|
+
```html
|
|
502
|
+
<!-- Single checkbox -->
|
|
503
|
+
<input type="checkbox" name="subscribe" checked>
|
|
504
|
+
<!-- Result: { subscribe: true } -->
|
|
505
|
+
|
|
506
|
+
<!-- Checkbox group -->
|
|
507
|
+
<input type="checkbox" name="features" value="darkmode" checked>
|
|
508
|
+
<input type="checkbox" name="features" value="analytics">
|
|
509
|
+
<input type="checkbox" name="features" value="beta" checked>
|
|
510
|
+
<!-- Result: { features: { darkmode: true, analytics: false, beta: true } } -->
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**Multiple Submit Buttons:**
|
|
514
|
+
|
|
515
|
+
Access the clicked submit button to handle different actions:
|
|
516
|
+
|
|
517
|
+
```html
|
|
518
|
+
<button type="submit" data-action="save">Save</button>
|
|
519
|
+
<button type="submit" data-action="draft">Save as Draft</button>
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
```javascript
|
|
523
|
+
formManager.on('submit', async ({ data, $submitButton }) => {
|
|
524
|
+
const action = $submitButton?.dataset?.action;
|
|
525
|
+
|
|
526
|
+
if (action === 'draft') {
|
|
527
|
+
await saveDraft(data);
|
|
528
|
+
formManager.showSuccess('Draft saved!');
|
|
529
|
+
} else {
|
|
530
|
+
await saveAndPublish(data);
|
|
531
|
+
formManager.showSuccess('Published!');
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
**Manual Ready Mode:**
|
|
537
|
+
|
|
538
|
+
For forms that need async initialization (e.g., loading data from API):
|
|
539
|
+
|
|
540
|
+
```javascript
|
|
541
|
+
const formManager = new FormManager('#my-form', { autoReady: false });
|
|
542
|
+
|
|
543
|
+
// Load data, then mark ready
|
|
544
|
+
const userData = await fetchUserData();
|
|
545
|
+
formManager.setData(userData);
|
|
546
|
+
formManager.ready(); // Now form is interactive
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
**Configuration Options:**
|
|
550
|
+
|
|
551
|
+
| Option | Default | Description |
|
|
552
|
+
|--------|---------|-------------|
|
|
553
|
+
| `autoReady` | `true` | Auto-transition to ready when DOM is ready |
|
|
554
|
+
| `initialState` | `'ready'` | State after autoReady fires |
|
|
555
|
+
| `allowResubmit` | `true` | Allow form resubmission after success |
|
|
556
|
+
| `resetOnSuccess` | `false` | Clear fields after successful submission |
|
|
557
|
+
| `warnOnUnsavedChanges` | `false` | Show browser warning when leaving with unsaved changes |
|
|
558
|
+
| `submittingText` | `'Processing...'` | Text shown on submit button during submission |
|
|
559
|
+
|
|
560
|
+
**Test Page:** Navigate to `/test/libraries/form-manager` to see FormManager in action with various configurations.
|
|
392
561
|
|
|
393
562
|
### Icons
|
|
394
563
|
* Fontawesome
|