fix: resolve homepage sections editor and rendering issues
- Fix sections editor not showing by converting isHomepage getter to property - Add Alpine Collapse plugin for accordion animations - Fix Quill editor content not syncing after page load - Add platform dropdown to content page edit form - Create shared templates config (app/templates_config.py) with i18n globals to make _() translation function available in Jinja2 macros - Fix pricing template field names (monthly_price → price_monthly) - Fix translation key (pricing.save_20 → pricing.save_months) - Add tiers context to CMS homepage route for pricing section - Fix architecture validation issues (language defaults, inline SVGs → $icon) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from app.templates_config import templates
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
@@ -24,8 +24,8 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# Get the templates directory
|
# Get the templates directory
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||||
TEMPLATES_DIR = BASE_DIR / "app" / "templates"
|
# TEMPLATES_DIR moved to app.templates_config
|
||||||
templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
|
# templates imported from app.templates_config
|
||||||
|
|
||||||
|
|
||||||
def get_platform_context(request: Request, db: Session) -> dict:
|
def get_platform_context(request: Request, db: Session) -> dict:
|
||||||
@@ -171,6 +171,25 @@ async def homepage(
|
|||||||
context["page"] = cms_homepage
|
context["page"] = cms_homepage
|
||||||
context["platform"] = platform
|
context["platform"] = platform
|
||||||
|
|
||||||
|
# Include subscription tiers for pricing section
|
||||||
|
from models.database.subscription import TIER_LIMITS, TierCode
|
||||||
|
|
||||||
|
tiers = []
|
||||||
|
for tier_code, limits in TIER_LIMITS.items():
|
||||||
|
tiers.append({
|
||||||
|
"code": tier_code.value,
|
||||||
|
"name": limits["name"],
|
||||||
|
"price_monthly": limits["price_monthly_cents"] / 100,
|
||||||
|
"price_annual": (limits["price_annual_cents"] / 100) if limits.get("price_annual_cents") else None,
|
||||||
|
"orders_per_month": limits.get("orders_per_month"),
|
||||||
|
"products_limit": limits.get("products_limit"),
|
||||||
|
"team_members": limits.get("team_members"),
|
||||||
|
"features": limits.get("features", []),
|
||||||
|
"is_popular": tier_code == TierCode.PROFESSIONAL,
|
||||||
|
"is_enterprise": tier_code == TierCode.ENTERPRISE,
|
||||||
|
})
|
||||||
|
context["tiers"] = tiers
|
||||||
|
|
||||||
template_name = cms_homepage.template or "default"
|
template_name = cms_homepage.template or "default"
|
||||||
template_path = f"platform/homepage-{template_name}.html"
|
template_path = f"platform/homepage-{template_name}.html"
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,10 @@
|
|||||||
<!-- 7. SEVENTH: Vendor Selector (depends on Tom Select and API Client) -->
|
<!-- 7. SEVENTH: Vendor Selector (depends on Tom Select and API Client) -->
|
||||||
<script src="{{ url_for('static', path='shared/js/vendor-selector.js') }}"></script>
|
<script src="{{ url_for('static', path='shared/js/vendor-selector.js') }}"></script>
|
||||||
|
|
||||||
<!-- 8. EIGHTH: Alpine.js v3 with CDN fallback (with defer) -->
|
<!-- 8a. Alpine.js Collapse Plugin (must load before Alpine) -->
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.13.3/dist/cdn.min.js"></script>
|
||||||
|
|
||||||
|
<!-- 8b. Alpine.js v3 with CDN fallback (with defer) -->
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
|
|||||||
@@ -91,24 +91,43 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Vendor ID (Platform vs Vendor-specific) -->
|
<!-- Platform Selection -->
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
Page Type
|
Platform <span class="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
x-model="form.platform_id"
|
||||||
|
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
|
||||||
|
:disabled="loadingPlatforms"
|
||||||
|
>
|
||||||
|
<template x-for="plat in (platforms || [])" :key="plat.id">
|
||||||
|
<option :value="plat.id" x-text="plat.name"></option>
|
||||||
|
</template>
|
||||||
|
</select>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
Which platform this page belongs to (e.g., OMS, Loyalty+)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vendor Override (optional) -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
Vendor Override
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
x-model="form.vendor_id"
|
x-model="form.vendor_id"
|
||||||
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
|
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
|
||||||
:disabled="loadingVendors"
|
:disabled="loadingVendors"
|
||||||
>
|
>
|
||||||
<option :value="null">Platform Default</option>
|
<option :value="null">None (Platform Default)</option>
|
||||||
<template x-for="vendor in (vendors || [])" :key="vendor.id">
|
<template x-for="vendor in (vendors || [])" :key="vendor.id">
|
||||||
<option :value="vendor.id" x-text="vendor.name"></option>
|
<option :value="vendor.id" x-text="vendor.name"></option>
|
||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
<span x-show="!form.vendor_id">Platform defaults are shown to all vendors</span>
|
<span x-show="!form.vendor_id">This is a platform-wide default page</span>
|
||||||
<span x-show="form.vendor_id">This page will only be visible for the selected vendor</span>
|
<span x-show="form.vendor_id">This page overrides the default for selected vendor only</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -222,9 +241,7 @@
|
|||||||
<input type="checkbox" x-model="sections.hero.enabled" class="w-4 h-4 text-purple-600 rounded">
|
<input type="checkbox" x-model="sections.hero.enabled" class="w-4 h-4 text-purple-600 rounded">
|
||||||
<span class="ml-2 text-sm text-gray-500">Enabled</span>
|
<span class="ml-2 text-sm text-gray-500">Enabled</span>
|
||||||
</label>
|
</label>
|
||||||
<svg :class="openSection === 'hero' ? 'rotate-180' : ''" class="w-5 h-5 text-gray-400 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<span :class="openSection === 'hero' ? 'rotate-180' : ''" class="transition-transform" x-html="$icon('chevron-down', 'w-5 h-5 text-gray-400')"></span>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<div x-show="openSection === 'hero'" x-collapse class="p-4 space-y-4 border-t border-gray-200 dark:border-gray-700">
|
<div x-show="openSection === 'hero'" x-collapse class="p-4 space-y-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
@@ -307,9 +324,7 @@
|
|||||||
<input type="checkbox" x-model="sections.features.enabled" class="w-4 h-4 text-purple-600 rounded">
|
<input type="checkbox" x-model="sections.features.enabled" class="w-4 h-4 text-purple-600 rounded">
|
||||||
<span class="ml-2 text-sm text-gray-500">Enabled</span>
|
<span class="ml-2 text-sm text-gray-500">Enabled</span>
|
||||||
</label>
|
</label>
|
||||||
<svg :class="openSection === 'features' ? 'rotate-180' : ''" class="w-5 h-5 text-gray-400 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<span :class="openSection === 'features' ? 'rotate-180' : ''" class="transition-transform" x-html="$icon('chevron-down', 'w-5 h-5 text-gray-400')"></span>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<div x-show="openSection === 'features'" x-collapse class="p-4 space-y-4 border-t border-gray-200 dark:border-gray-700">
|
<div x-show="openSection === 'features'" x-collapse class="p-4 space-y-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
@@ -378,9 +393,7 @@
|
|||||||
<input type="checkbox" x-model="sections.pricing.enabled" class="w-4 h-4 text-purple-600 rounded">
|
<input type="checkbox" x-model="sections.pricing.enabled" class="w-4 h-4 text-purple-600 rounded">
|
||||||
<span class="ml-2 text-sm text-gray-500">Enabled</span>
|
<span class="ml-2 text-sm text-gray-500">Enabled</span>
|
||||||
</label>
|
</label>
|
||||||
<svg :class="openSection === 'pricing' ? 'rotate-180' : ''" class="w-5 h-5 text-gray-400 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<span :class="openSection === 'pricing' ? 'rotate-180' : ''" class="transition-transform" x-html="$icon('chevron-down', 'w-5 h-5 text-gray-400')"></span>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<div x-show="openSection === 'pricing'" x-collapse class="p-4 space-y-4 border-t border-gray-200 dark:border-gray-700">
|
<div x-show="openSection === 'pricing'" x-collapse class="p-4 space-y-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
@@ -418,9 +431,7 @@
|
|||||||
<input type="checkbox" x-model="sections.cta.enabled" class="w-4 h-4 text-purple-600 rounded">
|
<input type="checkbox" x-model="sections.cta.enabled" class="w-4 h-4 text-purple-600 rounded">
|
||||||
<span class="ml-2 text-sm text-gray-500">Enabled</span>
|
<span class="ml-2 text-sm text-gray-500">Enabled</span>
|
||||||
</label>
|
</label>
|
||||||
<svg :class="openSection === 'cta' ? 'rotate-180' : ''" class="w-5 h-5 text-gray-400 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<span :class="openSection === 'cta' ? 'rotate-180' : ''" class="transition-transform" x-html="$icon('chevron-down', 'w-5 h-5 text-gray-400')"></span>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<div x-show="openSection === 'cta'" x-collapse class="p-4 space-y-4 border-t border-gray-200 dark:border-gray-700">
|
<div x-show="openSection === 'cta'" x-collapse class="p-4 space-y-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{# Set up language context #}
|
{# Set up language context #}
|
||||||
{% set lang = request.state.language or (platform.default_language if platform else 'fr') %}
|
{% set lang = request.state.language|default("fr") or (platform.default_language if platform else 'fr') %}
|
||||||
{% set default_lang = platform.default_language if platform else 'fr' %}
|
{% set default_lang = platform.default_language if platform else 'fr' %}
|
||||||
|
|
||||||
{# ═══════════════════════════════════════════════════════════════════════════ #}
|
{# ═══════════════════════════════════════════════════════════════════════════ #}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
{#
|
{#
|
||||||
Parameters:
|
Parameters:
|
||||||
- hero: HeroSection object (or dict)
|
- hero: HeroSection object (or dict)
|
||||||
- lang: Current language code (from request.state.language)
|
- lang: Current language code (passed from parent template)
|
||||||
- default_lang: Fallback language (from platform.default_language)
|
- default_lang: Fallback language (passed from parent template)
|
||||||
#}
|
#}
|
||||||
|
|
||||||
{% macro render_hero(hero, lang, default_lang) %}
|
{% macro render_hero(hero, lang, default_lang) %}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<span :class="!annual ? 'text-gray-400' : 'text-gray-900 dark:text-white font-semibold'">
|
<span :class="!annual ? 'text-gray-400' : 'text-gray-900 dark:text-white font-semibold'">
|
||||||
{{ _('pricing.annual') or 'Annual' }}
|
{{ _('pricing.annual') or 'Annual' }}
|
||||||
<span class="text-green-500 text-sm ml-1">{{ _('pricing.save_20') or 'Save 20%' }}</span>
|
<span class="text-green-500 text-sm ml-1">{{ _('pricing.save_months') or 'Save 2 months!' }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -74,8 +74,8 @@
|
|||||||
{# Price #}
|
{# Price #}
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<span class="text-4xl font-extrabold text-gray-900 dark:text-white"
|
<span class="text-4xl font-extrabold text-gray-900 dark:text-white"
|
||||||
x-text="annual ? '{{ tier.annual_price or (tier.monthly_price * 10)|int }}' : '{{ tier.monthly_price }}'">
|
x-text="annual ? '{{ tier.price_annual or (tier.price_monthly * 10)|int }}' : '{{ tier.price_monthly }}'">
|
||||||
{{ tier.monthly_price }}
|
{{ tier.price_monthly }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-gray-500 dark:text-gray-400">/{{ _('pricing.month') or 'mo' }}</span>
|
<span class="text-gray-500 dark:text-gray-400">/{{ _('pricing.month') or 'mo' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -272,6 +272,9 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Store quill instance on container for external access
|
||||||
|
container.__quill = quill;
|
||||||
|
|
||||||
// Find the Alpine component scope
|
// Find the Alpine component scope
|
||||||
const wrapper = container.closest('[x-data]');
|
const wrapper = container.closest('[x-data]');
|
||||||
if (wrapper && wrapper._x_dataStack) {
|
if (wrapper && wrapper._x_dataStack) {
|
||||||
|
|||||||
34
app/templates_config.py
Normal file
34
app/templates_config.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# app/templates_config.py
|
||||||
|
"""
|
||||||
|
Shared Jinja2 templates configuration.
|
||||||
|
|
||||||
|
All route modules should import `templates` from here to ensure
|
||||||
|
consistent globals (like translation function) are available.
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
|
from app.utils.i18n import (
|
||||||
|
LANGUAGE_FLAGS,
|
||||||
|
LANGUAGE_NAMES,
|
||||||
|
SUPPORTED_LANGUAGES,
|
||||||
|
DEFAULT_LANGUAGE,
|
||||||
|
create_translation_context,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Templates directory
|
||||||
|
TEMPLATES_DIR = Path(__file__).parent / "templates"
|
||||||
|
|
||||||
|
# Create shared templates instance
|
||||||
|
templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
|
||||||
|
|
||||||
|
# Add translation function to Jinja2 environment globals
|
||||||
|
# This makes _() available in all templates AND macros
|
||||||
|
_default_translator = create_translation_context(DEFAULT_LANGUAGE)
|
||||||
|
templates.env.globals["_"] = _default_translator
|
||||||
|
templates.env.globals["t"] = _default_translator # Alias
|
||||||
|
templates.env.globals["SUPPORTED_LANGUAGES"] = SUPPORTED_LANGUAGES
|
||||||
|
templates.env.globals["DEFAULT_LANGUAGE"] = DEFAULT_LANGUAGE
|
||||||
|
templates.env.globals["LANGUAGE_NAMES"] = LANGUAGE_NAMES
|
||||||
|
templates.env.globals["LANGUAGE_FLAGS"] = LANGUAGE_FLAGS
|
||||||
4
main.py
4
main.py
@@ -88,8 +88,8 @@ app = FastAPI(
|
|||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Configure Jinja2 Templates
|
# Configure Jinja2 Templates (shared instance with i18n globals)
|
||||||
templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
|
from app.templates_config import templates # noqa: F401 - re-exported for compatibility
|
||||||
|
|
||||||
# Setup custom exception handlers (unified approach)
|
# Setup custom exception handlers (unified approach)
|
||||||
setup_exception_handlers(app)
|
setup_exception_handlers(app)
|
||||||
|
|||||||
@@ -30,10 +30,13 @@ function contentPageEditor(pageId) {
|
|||||||
show_in_footer: true,
|
show_in_footer: true,
|
||||||
show_in_legal: false,
|
show_in_legal: false,
|
||||||
display_order: 0,
|
display_order: 0,
|
||||||
|
platform_id: null,
|
||||||
vendor_id: null
|
vendor_id: null
|
||||||
},
|
},
|
||||||
|
platforms: [],
|
||||||
vendors: [],
|
vendors: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
|
loadingPlatforms: false,
|
||||||
loadingVendors: false,
|
loadingVendors: false,
|
||||||
saving: false,
|
saving: false,
|
||||||
error: null,
|
error: null,
|
||||||
@@ -96,8 +99,8 @@ function contentPageEditor(pageId) {
|
|||||||
}
|
}
|
||||||
window._contentPageEditInitialized = true;
|
window._contentPageEditInitialized = true;
|
||||||
|
|
||||||
// Load vendors for dropdown
|
// Load platforms and vendors for dropdowns
|
||||||
await this.loadVendors();
|
await Promise.all([this.loadPlatforms(), this.loadVendors()]);
|
||||||
|
|
||||||
if (this.pageId) {
|
if (this.pageId) {
|
||||||
// Edit mode - load existing page
|
// Edit mode - load existing page
|
||||||
@@ -117,9 +120,34 @@ function contentPageEditor(pageId) {
|
|||||||
contentPageEditLog.info('=== CONTENT PAGE EDITOR INITIALIZATION COMPLETE ===');
|
contentPageEditLog.info('=== CONTENT PAGE EDITOR INITIALIZATION COMPLETE ===');
|
||||||
},
|
},
|
||||||
|
|
||||||
// Check if we should show section editor
|
// Check if we should show section editor (property, not getter for Alpine compatibility)
|
||||||
get isHomepage() {
|
isHomepage: false,
|
||||||
return this.form.slug === 'home';
|
|
||||||
|
// Update isHomepage when slug changes
|
||||||
|
updateIsHomepage() {
|
||||||
|
this.isHomepage = this.form.slug === 'home';
|
||||||
|
},
|
||||||
|
|
||||||
|
// Load platforms for dropdown
|
||||||
|
async loadPlatforms() {
|
||||||
|
this.loadingPlatforms = true;
|
||||||
|
try {
|
||||||
|
contentPageEditLog.info('Loading platforms...');
|
||||||
|
const response = await apiClient.get('/admin/platforms?is_active=true');
|
||||||
|
const data = response.data || response;
|
||||||
|
this.platforms = data.platforms || data.items || data || [];
|
||||||
|
contentPageEditLog.info(`Loaded ${this.platforms.length} platforms`);
|
||||||
|
|
||||||
|
// Set default platform if not editing and no platform selected
|
||||||
|
if (!this.pageId && !this.form.platform_id && this.platforms.length > 0) {
|
||||||
|
this.form.platform_id = this.platforms[0].id;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
contentPageEditLog.error('Error loading platforms:', err);
|
||||||
|
this.platforms = [];
|
||||||
|
} finally {
|
||||||
|
this.loadingPlatforms = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Load vendors for dropdown
|
// Load vendors for dropdown
|
||||||
@@ -170,11 +198,19 @@ function contentPageEditor(pageId) {
|
|||||||
show_in_footer: page.show_in_footer !== undefined ? page.show_in_footer : true,
|
show_in_footer: page.show_in_footer !== undefined ? page.show_in_footer : true,
|
||||||
show_in_legal: page.show_in_legal || false,
|
show_in_legal: page.show_in_legal || false,
|
||||||
display_order: page.display_order || 0,
|
display_order: page.display_order || 0,
|
||||||
|
platform_id: page.platform_id,
|
||||||
vendor_id: page.vendor_id
|
vendor_id: page.vendor_id
|
||||||
};
|
};
|
||||||
|
|
||||||
contentPageEditLog.info('Page loaded successfully');
|
contentPageEditLog.info('Page loaded successfully');
|
||||||
|
|
||||||
|
// Update computed properties after loading
|
||||||
|
this.updateIsHomepage();
|
||||||
|
|
||||||
|
// Re-initialize Quill editor content after page data is loaded
|
||||||
|
// (Quill may have initialized before loadPage completed)
|
||||||
|
this.syncQuillContent();
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
contentPageEditLog.error('Error loading page:', err);
|
contentPageEditLog.error('Error loading page:', err);
|
||||||
this.error = err.message || 'Failed to load page';
|
this.error = err.message || 'Failed to load page';
|
||||||
@@ -183,6 +219,26 @@ function contentPageEditor(pageId) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Sync Quill editor content after page data loads
|
||||||
|
// Quill may initialize before loadPage completes, leaving editor empty
|
||||||
|
syncQuillContent(retries = 5) {
|
||||||
|
const quillContainer = document.getElementById('content-editor');
|
||||||
|
|
||||||
|
if (!quillContainer || !quillContainer.__quill) {
|
||||||
|
// Quill not ready yet, retry
|
||||||
|
if (retries > 0) {
|
||||||
|
setTimeout(() => this.syncQuillContent(retries - 1), 100);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const quill = quillContainer.__quill;
|
||||||
|
if (this.form.content && quill.root.innerHTML !== this.form.content) {
|
||||||
|
quill.root.innerHTML = this.form.content;
|
||||||
|
contentPageEditLog.debug('Synced Quill content after page load');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// HOMEPAGE SECTIONS METHODS
|
// HOMEPAGE SECTIONS METHODS
|
||||||
// ========================================
|
// ========================================
|
||||||
@@ -355,6 +411,7 @@ function contentPageEditor(pageId) {
|
|||||||
show_in_footer: this.form.show_in_footer,
|
show_in_footer: this.form.show_in_footer,
|
||||||
show_in_legal: this.form.show_in_legal,
|
show_in_legal: this.form.show_in_legal,
|
||||||
display_order: this.form.display_order,
|
display_order: this.form.display_order,
|
||||||
|
platform_id: this.form.platform_id,
|
||||||
vendor_id: this.form.vendor_id
|
vendor_id: this.form.vendor_id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user