Files
orion/app/modules/cms/static/admin/js/content-page-edit.js
Samir Boulahtit 1b8a40f1ff
All checks were successful
CI / dependency-scanning (push) Successful in 27s
CI / docs (push) Successful in 35s
CI / ruff (push) Successful in 8s
CI / pytest (push) Successful in 34m22s
CI / validate (push) Successful in 19s
CI / deploy (push) Successful in 2m25s
feat(validators): add noqa suppression support to security and performance validators
- Add centralized _is_noqa_suppressed() to BaseValidator with normalization
  (accepts both SEC001 and SEC-001 formats for ruff compatibility)
- Wire noqa support into all 21 security and 18 performance check functions
- Add ruff external config for SEC/PERF/MOD/EXC codes in pyproject.toml
- Convert all 280 Python noqa comments to dashless format (ruff-compatible)
- Add site/ to IGNORE_PATTERNS (excludes mkdocs build output)
- Suppress 152 false positive findings (test passwords, seed data, validator
  self-references, Apple Wallet SHA1, etc.)
- Security: 79 errors → 0, 60 warnings → 0
- Performance: 80 warnings → 77 (3 test script suppressions)
- Add proposal doc with noqa inventory and remaining findings recommendations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 22:56:56 +01:00

467 lines
17 KiB
JavaScript

// noqa: js-006 - async init pattern is safe, loadData has try/catch
// static/admin/js/content-page-edit.js
// Use centralized logger
const contentPageEditLog = window.LogConfig.loggers.contentPageEdit || window.LogConfig.createLogger('contentPageEdit');
// ============================================
// CONTENT PAGE EDITOR FUNCTION
// ============================================
function contentPageEditor(pageId) {
return {
// Inherit base layout functionality from init-alpine.js
...data(),
// Page identifier for sidebar active state
currentPage: 'content-pages',
// Editor state
pageId: pageId,
form: {
slug: '',
title: '',
content: '',
content_format: 'html',
template: 'default',
meta_description: '',
meta_keywords: '',
is_published: false,
show_in_header: false,
show_in_footer: true,
show_in_legal: false,
display_order: 0,
platform_id: null,
store_id: null
},
platforms: [],
stores: [],
loading: false,
loadingPlatforms: false,
loadingStores: false,
saving: false,
error: null,
successMessage: null,
// ========================================
// HOMEPAGE SECTIONS STATE
// ========================================
supportedLanguages: ['fr', 'de', 'en'],
defaultLanguage: 'fr',
currentLang: 'fr',
openSection: null,
sectionsLoaded: false,
languageNames: {
en: 'English',
fr: 'Français',
de: 'Deutsch',
lb: 'Lëtzebuergesch'
},
sections: {
hero: {
enabled: true,
badge_text: { translations: {} },
title: { translations: {} },
subtitle: { translations: {} },
background_type: 'gradient',
buttons: []
},
features: {
enabled: true,
title: { translations: {} },
subtitle: { translations: {} },
features: [],
layout: 'grid'
},
pricing: {
enabled: true,
title: { translations: {} },
subtitle: { translations: {} },
use_subscription_tiers: true
},
cta: {
enabled: true,
title: { translations: {} },
subtitle: { translations: {} },
buttons: [],
background_type: 'gradient'
}
},
// Initialize
async init() {
contentPageEditLog.info('=== CONTENT PAGE EDITOR INITIALIZING ===');
contentPageEditLog.info('Page ID:', this.pageId);
// Prevent multiple initializations
if (window._contentPageEditInitialized) {
contentPageEditLog.warn('Content page editor already initialized, skipping...');
return;
}
window._contentPageEditInitialized = true;
// Load platforms and stores for dropdowns
await Promise.all([this.loadPlatforms(), this.loadStores()]);
if (this.pageId) {
// Edit mode - load existing page
contentPageEditLog.group('Loading page for editing');
await this.loadPage();
contentPageEditLog.groupEnd();
// Load sections if this is a homepage
if (this.form.slug === 'home') {
await this.loadSections();
}
} else {
// Create mode - use default values
contentPageEditLog.info('Create mode - using default form values');
}
contentPageEditLog.info('=== CONTENT PAGE EDITOR INITIALIZATION COMPLETE ===');
},
// Check if we should show section editor (property, not getter for Alpine compatibility)
isHomepage: false,
// 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 stores for dropdown
async loadStores() {
this.loadingStores = true;
try {
contentPageEditLog.info('Loading stores...');
const response = await apiClient.get('/admin/stores?is_active=true&limit=100');
const data = response.data || response;
this.stores = data.stores || data.items || data || [];
contentPageEditLog.info(`Loaded ${this.stores.length} stores`);
} catch (err) {
contentPageEditLog.error('Error loading stores:', err);
this.stores = [];
} finally {
this.loadingStores = false;
}
},
// Load existing page
async loadPage() {
this.loading = true;
this.error = null;
try {
contentPageEditLog.info(`Fetching page ${this.pageId}...`);
const response = await apiClient.get(`/admin/content-pages/${this.pageId}`);
contentPageEditLog.debug('API Response:', response);
if (!response) {
throw new Error('Invalid API response');
}
// Handle response - API returns object directly
const page = response.data || response;
this.form = {
slug: page.slug || '',
title: page.title || '',
content: page.content || '',
content_format: page.content_format || 'html',
template: page.template || 'default',
meta_description: page.meta_description || '',
meta_keywords: page.meta_keywords || '',
is_published: page.is_published || false,
show_in_header: page.show_in_header || false,
show_in_footer: page.show_in_footer !== undefined ? page.show_in_footer : true,
show_in_legal: page.show_in_legal || false,
display_order: page.display_order || 0,
platform_id: page.platform_id,
store_id: page.store_id
};
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) {
contentPageEditLog.error('Error loading page:', err);
this.error = err.message || 'Failed to load page';
} finally {
this.loading = false;
}
},
// 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; // # noqa: SEC-015
contentPageEditLog.debug('Synced Quill content after page load');
}
},
// ========================================
// HOMEPAGE SECTIONS METHODS
// ========================================
// Load sections for homepage
async loadSections() {
if (!this.pageId || this.form.slug !== 'home') {
contentPageEditLog.debug('Skipping section load - not a homepage');
return;
}
try {
contentPageEditLog.info('Loading homepage sections...');
const response = await apiClient.get(`/admin/content-pages/${this.pageId}/sections`);
const data = response.data || response;
this.supportedLanguages = data.supported_languages || ['fr', 'de', 'en'];
this.defaultLanguage = data.default_language || 'fr';
this.currentLang = this.defaultLanguage;
if (data.sections) {
this.sections = this.mergeWithDefaults(data.sections);
contentPageEditLog.info('Sections loaded:', Object.keys(data.sections));
} else {
this.initializeEmptySections();
contentPageEditLog.info('No sections found - initialized empty structure');
}
this.sectionsLoaded = true;
} catch (err) {
contentPageEditLog.error('Error loading sections:', err);
}
},
// Merge loaded sections with default structure
mergeWithDefaults(loadedSections) {
const defaults = this.getDefaultSectionStructure();
// Deep merge each section
for (const key of ['hero', 'features', 'pricing', 'cta']) {
if (loadedSections[key]) {
defaults[key] = { ...defaults[key], ...loadedSections[key] };
}
}
return defaults;
},
// Get default section structure
getDefaultSectionStructure() {
const emptyTranslations = () => {
const t = {};
this.supportedLanguages.forEach(lang => t[lang] = '');
return { translations: t };
};
return {
hero: {
enabled: true,
badge_text: emptyTranslations(),
title: emptyTranslations(),
subtitle: emptyTranslations(),
background_type: 'gradient',
buttons: []
},
features: {
enabled: true,
title: emptyTranslations(),
subtitle: emptyTranslations(),
features: [],
layout: 'grid'
},
pricing: {
enabled: true,
title: emptyTranslations(),
subtitle: emptyTranslations(),
use_subscription_tiers: true
},
cta: {
enabled: true,
title: emptyTranslations(),
subtitle: emptyTranslations(),
buttons: [],
background_type: 'gradient'
}
};
},
// Initialize empty sections for all languages
initializeEmptySections() {
this.sections = this.getDefaultSectionStructure();
},
// Add a button to hero or cta section
addButton(sectionName) {
const newButton = {
text: { translations: {} },
url: '',
style: 'primary'
};
this.supportedLanguages.forEach(lang => {
newButton.text.translations[lang] = '';
});
this.sections[sectionName].buttons.push(newButton);
contentPageEditLog.debug(`Added button to ${sectionName}`);
},
// Remove a button from hero or cta section
removeButton(sectionName, index) {
this.sections[sectionName].buttons.splice(index, 1);
contentPageEditLog.debug(`Removed button ${index} from ${sectionName}`);
},
// Add a feature card
addFeature() {
const newFeature = {
icon: '',
title: { translations: {} },
description: { translations: {} }
};
this.supportedLanguages.forEach(lang => {
newFeature.title.translations[lang] = '';
newFeature.description.translations[lang] = '';
});
this.sections.features.features.push(newFeature);
contentPageEditLog.debug('Added feature card');
},
// Remove a feature card
removeFeature(index) {
this.sections.features.features.splice(index, 1);
contentPageEditLog.debug(`Removed feature ${index}`);
},
// Save sections
async saveSections() {
if (!this.pageId || !this.isHomepage) return;
try {
contentPageEditLog.info('Saving sections...');
await apiClient.put(`/admin/content-pages/${this.pageId}/sections`, this.sections);
contentPageEditLog.info('Sections saved successfully');
} catch (err) {
contentPageEditLog.error('Error saving sections:', err);
throw err;
}
},
// Save page (create or update)
async savePage() {
if (this.saving) return;
this.saving = true;
this.error = null;
this.successMessage = null;
try {
contentPageEditLog.info(this.pageId ? 'Updating page...' : 'Creating page...');
const payload = {
slug: this.form.slug,
title: this.form.title,
content: this.form.content,
content_format: this.form.content_format,
template: this.form.template,
meta_description: this.form.meta_description,
meta_keywords: this.form.meta_keywords,
is_published: this.form.is_published,
show_in_header: this.form.show_in_header,
show_in_footer: this.form.show_in_footer,
show_in_legal: this.form.show_in_legal,
display_order: this.form.display_order,
platform_id: this.form.platform_id,
store_id: this.form.store_id
};
contentPageEditLog.debug('Payload:', payload);
let response;
if (this.pageId) {
// Update existing page
response = await apiClient.put(`/admin/content-pages/${this.pageId}`, payload);
// Also save sections if this is a homepage
if (this.isHomepage && this.sectionsLoaded) {
await this.saveSections();
}
this.successMessage = 'Page updated successfully!';
contentPageEditLog.info('Page updated');
} else {
// Create new page - use store or platform endpoint based on selection
const endpoint = this.form.store_id
? '/admin/content-pages/store'
: '/admin/content-pages/platform';
response = await apiClient.post(endpoint, payload);
this.successMessage = 'Page created successfully!';
contentPageEditLog.info('Page created', { endpoint, store_id: this.form.store_id });
// Redirect to edit page after creation
const pageData = response.data || response;
if (pageData && pageData.id) {
setTimeout(() => {
window.location.href = `/admin/content-pages/${pageData.id}/edit`;
}, 1500);
}
}
// Clear success message after 3 seconds
setTimeout(() => {
this.successMessage = null;
}, 3000);
} catch (err) {
contentPageEditLog.error('Error saving page:', err);
this.error = err.message || 'Failed to save page';
// Scroll to top to show error
window.scrollTo({ top: 0, behavior: 'smooth' });
} finally {
this.saving = false;
}
}
};
}