ultimate-jekyll-manager 1.2.0 → 1.2.2

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.
@@ -0,0 +1,85 @@
1
+ # Page Loading Protection System
2
+
3
+ Ultimate Jekyll prevents race conditions by disabling buttons during JavaScript initialization.
4
+
5
+ ## How It Works
6
+
7
+ 1. HTML element starts with `data-page-loading="true"` and `aria-busy="true"` (`src/defaults/dist/_layouts/core/root.html`)
8
+ 2. Protected elements are automatically disabled during this state
9
+ 3. Attributes are removed when JavaScript completes (`src/assets/js/core/complete.js`)
10
+
11
+ ## Protected Elements
12
+
13
+ - All form buttons (`<button>`, `<input type="submit">`, `<input type="button">`, `<input type="reset">`)
14
+ - Elements with `.btn` class (Bootstrap buttons)
15
+ - Elements with `.btn-action` class (custom action triggers)
16
+
17
+ ## The `.btn-action` Class
18
+
19
+ Selectively protect non-standard elements that trigger important actions:
20
+
21
+ ```html
22
+ <!-- Protected during page load -->
23
+ <a href="/api/delete" class="custom-link btn-action">Delete Item</a>
24
+ <div class="card-action btn-action" onclick="processData()">Process</div>
25
+
26
+ <!-- NOT protected (regular navigation/UI) -->
27
+ <a href="/about" class="btn btn-primary">About Us</a>
28
+ <button data-bs-toggle="modal">Show Info</button>
29
+ ```
30
+
31
+ **Use `.btn-action` for:**
32
+ - API calls
33
+ - Form submissions
34
+ - Data modifications
35
+ - Payment processing
36
+ - Destructive actions
37
+
38
+ **Don't use for:**
39
+ - Navigation links
40
+ - UI toggles (modals, accordions, tabs)
41
+ - Harmless interactions
42
+
43
+ ## Implementation
44
+
45
+ - **CSS:** `src/assets/css/core/utilities.scss` — Disabled styling
46
+ - **Click Prevention:** `src/defaults/dist/_includes/core/body.html` — Inline script
47
+ - **State Removal:** `src/assets/js/core/complete.js` — Removes loading state
48
+
49
+ ## Form Protection Standards
50
+
51
+ All JS-managed forms use a layered protection strategy to prevent native form submission before JavaScript takes control:
52
+
53
+ ### Layer 1: `onsubmit="return false"` on ALL JS-managed forms
54
+
55
+ Every `<form>` that will be managed by FormManager MUST include `onsubmit="return false"`:
56
+
57
+ ```html
58
+ <form id="my-form" onsubmit="return false">
59
+ ```
60
+
61
+ This is a zero-cost safety net that prevents native form submission if a user clicks submit before FormManager attaches its `e.preventDefault()` handler. FormManager's own submit handling overrides this — there is no conflict.
62
+
63
+ **Exception:** Traditional forms with an `action` attribute that intentionally navigate (e.g., search forms, external form submissions) should NOT include this.
64
+
65
+ ### Layer 2: Button initial state based on use case
66
+
67
+ | Use Case | Initial State | Mechanism |
68
+ |----------|---------------|-----------|
69
+ | Buttons dependent on async data (checkout payment methods) | `hidden` | `data-wm-bind="@show ..."` reveals when data loads |
70
+ | Buttons on auth/sensitive forms | `disabled` | FormManager's `ready()` removes `disabled` |
71
+ | Buttons on simple forms (contact, newsletter) | Default (visible) | FormManager's `autoReady: true` enables quickly |
72
+
73
+ ### Layer 3: FormManager `autoReady` configuration
74
+
75
+ | Scenario | `autoReady` | `ready()` call |
76
+ |----------|-------------|----------------|
77
+ | No async work before form init | `true` (default) | Automatic on DOM ready |
78
+ | Async work before form init (API calls, redirects) | `false` | Explicit call after async completes |
79
+
80
+ **Reference implementations:**
81
+ - Simple form: `src/assets/js/pages/contact/index.js`
82
+ - Auth form: `src/assets/js/libs/auth.js`
83
+ - Async data form: `src/assets/js/pages/payment/checkout/index.js`
84
+
85
+ See also [docs/javascript-libraries.md → FormManager](javascript-libraries.md#formmanager-library) for the FormManager API itself.
@@ -0,0 +1,23 @@
1
+ # Project Structure
2
+
3
+ UJM is a template framework that consuming projects install as an NPM module to build Jekyll sites quickly and efficiently. It provides best-practice configurations, default components, themes, and build tools.
4
+
5
+ > **UJM is NOT a standalone project.** You cannot run `npm start` or `npm run build` directly in this repository. The user already has a development server running in a consuming project — running those commands here would either fail or create duplicate servers unnecessarily. See [the Identity section in CLAUDE.md](../CLAUDE.md#identity) for what IS safe to run inside UJM itself.
6
+
7
+ ## Directory Organization (UJM repo)
8
+
9
+ | Path | Purpose |
10
+ |---|---|
11
+ | `src/gulp/tasks` | Gulp tasks for building Jekyll sites |
12
+ | `src/defaults/src` | Default source files (editable by users, copied to consuming project's `src/`) |
13
+ | `src/defaults/dist` | Default distribution files (not editable by users, copied to consuming project's `dist/`) |
14
+ | `src/assets/css` | Stylesheets (global, pages, themes) |
15
+ | `src/assets/js` | JavaScript modules (core, pages, libraries) |
16
+ | `src/assets/themes` | Theme SCSS and JS files |
17
+
18
+ ## Consuming Project Structure
19
+
20
+ | Path | Purpose |
21
+ |---|---|
22
+ | `src/` | Compiled to `dist/` via npm |
23
+ | `dist/` | Compiled to `_site/` via Jekyll |
package/docs/seo.md ADDED
@@ -0,0 +1,206 @@
1
+ # SEO — Alternatives Pages & Structured Data
2
+
3
+ This doc covers two SEO-focused subsystems:
4
+
5
+ 1. **Alternatives Collection** — competitor comparison landing pages.
6
+ 2. **Schema / Structured Data (JSON-LD)** — `SoftwareApplication` and `FAQPage` JSON-LD blocks.
7
+
8
+ ## Alternatives Collection (SEO Competitor Comparison Pages)
9
+
10
+ UJ provides an `alternatives` collection for SEO landing pages that target users searching for competitors (e.g., "ExampleApp alternatives"). These pages are entirely frontmatter-driven and designed to convert visitors who are comparing products.
11
+
12
+ ### How It Works
13
+
14
+ 1. The `alternatives` collection is registered in `src/config/_config_default.yml` (UJM-controlled)
15
+ 2. Each alternative is a markdown file in the consuming project's `_alternatives/` directory
16
+ 3. The layout chain: `blueprint/alternatives/alternative` → `themes/classy/frontend/pages/alternatives/alternative`
17
+ 4. An index page at `/alternatives` lists all alternatives automatically
18
+ 5. **Shared content lives in the layout** — the theme layout provides default testimonials, stats, FAQs, CTA, and why_switch content so competitor pages only need competitor-specific data
19
+ 6. The layout frontmatter uses `{{ page.resolved.alternative.competitor.name }}` to dynamically insert the competitor name into shared content (e.g., FAQ questions, CTA headlines)
20
+
21
+ ### Creating an Alternative Page
22
+
23
+ In the consuming project, create `src/_alternatives/competitor-name.md`. Only competitor-specific data is needed — shared sections are inherited from the layout:
24
+
25
+ ```yaml
26
+ ---
27
+ layout: blueprint/alternatives/alternative
28
+ sitemap:
29
+ include: true
30
+
31
+ alternative:
32
+ competitor:
33
+ name: "Competitor Name"
34
+ description: "Brief description of the competitor (shown on /alternatives listing)"
35
+ comparison:
36
+ features:
37
+ - name: "Feature Name"
38
+ icon: "sparkles"
39
+ ours:
40
+ value: true # or string like "Unlimited"
41
+ theirs:
42
+ value: false # or string like "Limited"
43
+ ---
44
+ ```
45
+
46
+ That's it! The layout automatically generates:
47
+ - Hero with "Brand vs Competitor Name" headline
48
+ - Why Switch section with default differentiator items
49
+ - Testimonials, Stats, FAQs, and CTA with shared content
50
+ - All text dynamically references the competitor name via `{{ page.resolved.alternative.competitor.name }}`
51
+
52
+ **To override any inherited section**, define it in the competitor's frontmatter — `page.resolved` merge gives page-level values highest priority.
53
+
54
+ ### Available Sections
55
+
56
+ All sections are **optional** — omit or leave empty to hide. Sections with `(shared)` have default content in the layout:
57
+
58
+ | Section | Frontmatter Key | Description |
59
+ |---------|----------------|-------------|
60
+ | Hero | `alternative.hero` | Gradient animated hero with "Brand vs Competitor" headline (shared) |
61
+ | Comparison | `alternative.comparison` | Side-by-side feature table — **must be defined per competitor** |
62
+ | Why Switch | `alternative.why_switch` | Alternating image/text showcase blocks (shared) |
63
+ | Video | `alternative.video` | YouTube embed (default: hidden, set `youtube_id` to show) |
64
+ | Testimonials | `alternative.testimonials` | Reuses `testimonial-scroll.html` component (shared) |
65
+ | Stats | `alternative.stats` | Social proof numbers with icons (shared) |
66
+ | FAQs | `alternative.faqs` | Accordion with switching-related questions (shared) |
67
+ | CTA | `alternative.cta` | Final conversion card with buttons (shared) |
68
+
69
+ ### Dynamic Competitor Name in Frontmatter
70
+
71
+ The layout uses `{{ page.resolved.alternative.competitor.name }}` in its frontmatter defaults to dynamically reference the competitor. This works because the template pipes these values through `| uj_liquify` to resolve Liquid expressions.
72
+
73
+ **Example:** The layout's default FAQ includes:
74
+
75
+ ```yaml
76
+ question: "Can I import my data from {{ page.resolved.alternative.competitor.name }}?"
77
+ ```
78
+
79
+ Which renders as "Can I import my data from ExampleApp?" for an ExampleApp competitor page.
80
+
81
+ ### Reference Implementation
82
+
83
+ - **Minimal competitor page:** `src/defaults/dist/_alternatives/example-competitor.md` — shows the minimum frontmatter needed (competitor name + comparison features)
84
+ - **Layout with all defaults:** `src/defaults/dist/_layouts/themes/classy/frontend/pages/alternatives/alternative.html` — contains shared content for all sections
85
+
86
+ ### File Locations
87
+
88
+ | Purpose | Path |
89
+ |---------|------|
90
+ | Theme layout (alternative page) | `src/defaults/dist/_layouts/themes/classy/frontend/pages/alternatives/alternative.html` |
91
+ | Theme layout (index/listing page) | `src/defaults/dist/_layouts/themes/classy/frontend/pages/alternatives/index.html` |
92
+ | Blueprint (alternative) | `src/defaults/dist/_layouts/blueprint/alternatives/alternative.html` |
93
+ | Blueprint (index) | `src/defaults/dist/_layouts/blueprint/alternatives/index.html` |
94
+ | Default page (index) | `src/defaults/dist/pages/alternatives/index.md` |
95
+ | Sample alternative | `src/defaults/dist/_alternatives/example-competitor.md` |
96
+ | CSS | `src/assets/css/pages/alternatives/alternative/index.scss` |
97
+ | JS | `src/assets/js/pages/alternatives/alternative/index.js` |
98
+
99
+ ## Schema / Structured Data (JSON-LD)
100
+
101
+ UJ automatically generates JSON-LD structured data in `foot.html`. The SoftwareApplication schema with AggregateRating is opt-in via frontmatter.
102
+
103
+ ### SoftwareApplication Schema
104
+
105
+ Renders a `SoftwareApplication` JSON-LD block with deterministic aggregate ratings. Enabled by blueprint layouts (index, pricing, download, alternatives/alternative) — consuming projects can override or disable per page.
106
+
107
+ **How it works:**
108
+
109
+ 1. **`_config.yml`** sets fallback defaults (no `enabled` key — just field defaults like `application_category`, `price`, etc.)
110
+ 2. **Blueprint layouts** set `schema.software_application.enabled: true` with page-appropriate `features`
111
+ 3. **Consuming projects** can override any value in their page frontmatter, or disable with `enabled: false`
112
+
113
+ This follows the standard `page.resolved` merge: page > layout > site.
114
+
115
+ **Deterministic ratings:** Uses the `uj_hash` filter (from jekyll-uj-powertools) seeded with `site.url` by default, producing stable values across builds:
116
+ - Rating: always `4.8` or `4.9` (deterministic per seed)
117
+ - Review count: 200,000–999,999 (deterministic per seed)
118
+ - Override seed per page with `hash_seed` to get different values
119
+
120
+ **Blueprint frontmatter example:**
121
+
122
+ ```yaml
123
+ ### SCHEMA ###
124
+ schema:
125
+ software_application:
126
+ enabled: true
127
+ features:
128
+ - "Free to use"
129
+ - "24/7 availability"
130
+ - "User-friendly interface"
131
+ ```
132
+
133
+ **Consuming project override example:**
134
+
135
+ ```yaml
136
+ schema:
137
+ software_application:
138
+ application_category: "EducationalApplication"
139
+ features:
140
+ - "AI-powered solutions"
141
+ - "24/7 availability"
142
+ ```
143
+
144
+ **Consuming project disable example:**
145
+
146
+ ```yaml
147
+ schema:
148
+ software_application:
149
+ enabled: false
150
+ ```
151
+
152
+ **Available fields:**
153
+
154
+ | Field | Default | Description |
155
+ |-------|---------|-------------|
156
+ | `enabled` | (set by blueprint) | Enable/disable the schema block |
157
+ | `name` | `site.brand.name` | Application name |
158
+ | `description` | `page.resolved.meta.description` | Application description |
159
+ | `application_category` | `WebApplication` | Schema.org application category |
160
+ | `operating_system` | `Web-based` | Target OS |
161
+ | `price` | `0` | Price (string) |
162
+ | `price_currency` | `USD` | Currency code |
163
+ | `features` | `[]` | Feature list for `featureList` field |
164
+ | `hash_seed` | `site.url` | Seed for deterministic rating/count generation |
165
+
166
+ **File locations:**
167
+
168
+ | Purpose | Path |
169
+ |---------|------|
170
+ | Schema block (rendering) | `src/defaults/dist/_includes/core/foot.html` |
171
+ | Site-level defaults | `src/defaults/src/_config.yml` (under `schema:`) |
172
+ | Blueprint activation | `src/defaults/dist/_layouts/blueprint/{index,pricing,download}.html`, `blueprint/alternatives/alternative.html` |
173
+ | Hash filter | `jekyll-uj-powertools/lib/filters/main.rb` (`uj_hash`) |
174
+
175
+ ### FAQPage Schema
176
+
177
+ Renders a `FAQPage` JSON-LD block for pages with FAQ/accordion sections. Enabled by the alternatives blueprint — consuming projects can also enable it on any page with FAQ content.
178
+
179
+ **How it works:**
180
+
181
+ 1. **`_config.yml`** sets `faq_page.items: []` as fallback
182
+ 2. **Blueprint layouts** set `schema.faq_page.enabled: true`
183
+ 3. **Items source (fallback chain):** `schema.faq_page.items` → `page.resolved.faqs.items` → `page.resolved.alternative.faqs.items`. Pages with generic `faqs.items` (like pricing) and alternatives pages both get FAQPage schema automatically without duplicating content
184
+ 4. Questions/answers are processed through `uj_liquify` (supports Liquid expressions like competitor names) and `uj_json_escape`
185
+
186
+ **Blueprint activation:** Enabled by default in `blueprint/pricing.html`, `blueprint/contact.html`, `blueprint/download.html`, `blueprint/extension/index.html`, and `blueprint/alternatives/alternative.html`.
187
+
188
+ **Consuming project usage — provide items directly:**
189
+
190
+ ```yaml
191
+ schema:
192
+ faq_page:
193
+ enabled: true
194
+ items:
195
+ - question: "How do I get started?"
196
+ answer: "Sign up for free and follow the onboarding guide."
197
+ - question: "Is there a free plan?"
198
+ answer: "Yes, our basic plan is completely free."
199
+ ```
200
+
201
+ **Available fields:**
202
+
203
+ | Field | Default | Description |
204
+ |-------|---------|-------------|
205
+ | `enabled` | (set by blueprint) | Enable/disable the schema block |
206
+ | `items` | `[]` | Array of `{question, answer}` objects. Falls back to `alternative.faqs.items` if empty |
@@ -0,0 +1,126 @@
1
+ # XSS Prevention (ZERO TRUST — MANDATORY)
2
+
3
+ **TREAT ALL DYNAMIC DATA AS UNTRUSTED.** This is a zero-trust policy: any value that did not come directly from a hardcoded literal in the source file MUST be escaped before being inserted into the DOM via `innerHTML` or attribute interpolation.
4
+
5
+ This includes — but is not limited to:
6
+ - Firestore document fields (user names, emails, IDs, descriptions, etc.)
7
+ - API response data
8
+ - URL parameters (`location.search`, `URLSearchParams`)
9
+ - User input from form fields
10
+ - OAuth-provided values (displayName, email from Google/GitHub)
11
+ - Any variable whose origin is not a hardcoded source-code constant
12
+
13
+ ## The Rule
14
+
15
+ ```javascript
16
+ // ✅ ALWAYS escape dynamic data before innerHTML
17
+ $el.innerHTML = `<p>${webManager.utilities().escapeHTML(data.title)}</p>`;
18
+ $el.innerHTML = `<a href="${webManager.utilities().escapeHTML(url)}">${webManager.utilities().escapeHTML(label)}</a>`;
19
+
20
+ // ✅ textContent is always safe — no escaping needed
21
+ $el.textContent = data.title;
22
+
23
+ // ❌ NEVER inject dynamic data raw into innerHTML
24
+ $el.innerHTML = `<p>${data.title}</p>`;
25
+ $el.innerHTML = `<a href="${url}">${label}</a>`;
26
+ ```
27
+
28
+ ## NEVER Write Your Own Escape Function
29
+
30
+ Do NOT create a local `escapeHtml` function or any variant. The ONLY allowed escape method is:
31
+
32
+ ```javascript
33
+ webManager.utilities().escapeHTML(str)
34
+ ```
35
+
36
+ ## When Building DOM Programmatically
37
+
38
+ Prefer `document.createElement` + `textContent` for plain text nodes — it is inherently safe:
39
+
40
+ ```javascript
41
+ const $el = document.createElement('div');
42
+ $el.textContent = data.message; // Safe — no escaping needed
43
+ ```
44
+
45
+ Only use `innerHTML` when you need actual HTML structure (tags, classes, etc.), and escape every dynamic value in it.
46
+
47
+ ## Even "Safe" Values Must Be Escaped
48
+
49
+ Even values that *seem* safe (like `Date.toLocaleDateString()` output, numeric calculations, or hardcoded config strings) MUST be escaped when inserted via `innerHTML`. This is defense-in-depth — if the data source ever changes, the escaping is already in place.
50
+
51
+ ```javascript
52
+ // ✅ CORRECT — escape even "safe" values in innerHTML
53
+ $el.innerHTML = `<small>${webManager.utilities().escapeHTML(formatDate(timestamp))}</small>`;
54
+ $el.innerHTML = `<span>${webManager.utilities().escapeHTML(reason)}</span>`;
55
+
56
+ // ❌ WRONG — assuming the value is safe because it's from a date formatter
57
+ $el.innerHTML = `<small>${formatDate(timestamp)}</small>`;
58
+ ```
59
+
60
+ ## Redirects Must Be Validated
61
+
62
+ Never redirect to a URL from untrusted sources without validation:
63
+
64
+ ```javascript
65
+ // ✅ CORRECT — validate before redirect
66
+ const url = urlParams.get('returnUrl');
67
+ if (url && webManager.isValidRedirectUrl(url)) {
68
+ window.location.href = url;
69
+ }
70
+
71
+ // ✅ CORRECT — validate API response URLs have safe scheme
72
+ if (response.url && /^https?:\/\//i.test(response.url)) {
73
+ window.location.href = response.url;
74
+ }
75
+
76
+ // ❌ WRONG — redirect to unvalidated input
77
+ window.location.href = urlParams.get('returnUrl');
78
+ ```
79
+
80
+ ## postMessage Handlers Must Check Origin
81
+
82
+ Always validate `event.origin` when handling `window.addEventListener('message', ...)`:
83
+
84
+ ```javascript
85
+ // ✅ CORRECT
86
+ window.addEventListener('message', (event) => {
87
+ if (event.origin !== window.location.origin && event.origin !== 'https://trusted-domain.com') {
88
+ return;
89
+ }
90
+ // handle message
91
+ });
92
+
93
+ // ❌ WRONG — any origin can send messages
94
+ window.addEventListener('message', (event) => {
95
+ window.location.href = event.data.url; // attacker-controlled redirect
96
+ });
97
+ ```
98
+
99
+ ## Never Use eval() or new Function()
100
+
101
+ Do not use `eval()`, `new Function()`, `setTimeout(string)`, or `setInterval(string)`. These execute arbitrary code and violate CSP policies.
102
+
103
+ ## Sanitize Markdown/Rich Text Output
104
+
105
+ When rendering user-authored markdown or rich text, use DOMPurify to sanitize the output:
106
+
107
+ ```javascript
108
+ import DOMPurify from 'dompurify';
109
+ const safeHTML = DOMPurify.sanitize(md.render(userContent), {
110
+ ALLOWED_TAGS: ['h1', 'h2', 'h3', 'p', 'br', 'a', 'b', 'strong', 'i', 'em', 'ul', 'ol', 'li', 'img', 'code', 'pre'],
111
+ ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class', 'target', 'rel'],
112
+ });
113
+ ```
114
+
115
+ ## Do NOT Escape Values Passed to textContent-Based APIs
116
+
117
+ `showNotification()`, `formManager.showSuccess()`, `formManager.showError()`, and `textContent` assignments use safe text insertion internally. Pre-escaping these causes double-encoding (e.g., `We'll` displays as `We&#039;ll`).
118
+
119
+ ```javascript
120
+ // ✅ CORRECT — these APIs use textContent internally, so they're already safe
121
+ webManager.utilities().showNotification('Thank you! We\'ll be in touch.', 'success');
122
+ formManager.showSuccess('Message sent successfully!');
123
+
124
+ // ❌ WRONG — double-escapes
125
+ webManager.utilities().showNotification(webManager.utilities().escapeHTML(message), 'success');
126
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultimate-jekyll-manager",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Ultimate Jekyll dependency manager",
5
5
  "main": "dist/index.js",
6
6
  "exports": {