Clean up demo site: remove manual IDs and frontend-only approach

- Remove all manual data-content-id attributes from HTML files
- Archive old insertr JS/CSS assets to demo-site/archive/
- Remove hardcoded script includes and CSS links
- Remove old authentication controls and mock API
- Enable pure zero-config approach with class='insertr' only
- Parser now generates all 40 content IDs automatically
This commit is contained in:
2025-09-03 12:18:43 +02:00
parent c2591e4fdd
commit 9bc2751ff2
10 changed files with 44 additions and 67 deletions

View File

@@ -0,0 +1,409 @@
/**
* Insertr - Element-Level Edit-in-place CMS Library
* Modular architecture with configuration system
*/
class Insertr {
constructor(options = {}) {
this.options = {
apiEndpoint: options.apiEndpoint || '/api/content',
authEndpoint: options.authEndpoint || '/api/auth',
autoInit: options.autoInit !== false,
...options
};
// Core state
this.state = {
isAuthenticated: false,
editMode: false,
currentUser: null,
activeEditor: null
};
this.editableElements = new Map();
this.statusIndicator = null;
// Initialize modules
this.config = new InsertrConfig(options.config);
this.validation = new InsertrValidation(this.config);
this.formRenderer = new InsertrFormRenderer(this.validation);
this.contentManager = new InsertrContentManager(options);
this.markdownProcessor = new InsertrMarkdownProcessor();
if (this.options.autoInit) {
this.init();
}
}
/**
* Initialize the CMS system
*/
async init() {
console.log('🚀 Insertr initializing with modular architecture...');
// Scan for editable elements
this.scanForEditableElements();
// Setup authentication controls
this.setupAuthenticationControls();
// Create status indicator
this.createStatusIndicator();
// Apply initial state
this.updateBodyClasses();
console.log(`📝 Found ${this.editableElements.size} editable elements`);
}
/**
* Scan for editable elements and set them up
*/
scanForEditableElements() {
const elements = document.querySelectorAll('.insertr');
elements.forEach(element => {
const contentId = element.getAttribute('data-content-id');
if (!contentId) {
console.warn('Insertr element missing data-content-id:', element);
return;
}
this.editableElements.set(contentId, element);
this.setupEditableElement(element, contentId);
});
}
/**
* Setup individual editable element
* @param {HTMLElement} element - Element to setup
* @param {string} contentId - Content identifier
*/
setupEditableElement(element, contentId) {
// Generate field configuration
const fieldConfig = this.config.generateFieldConfig(element);
element._insertrConfig = fieldConfig;
// Add edit button
this.addEditButton(element, contentId);
// Load saved content if available
const savedContent = this.contentManager.getContent(contentId);
if (savedContent) {
this.contentManager.applyContentToElement(element, savedContent);
}
}
/**
* Add edit button to element
* @param {HTMLElement} element - Element to add button to
* @param {string} contentId - Content identifier
*/
addEditButton(element, contentId) {
// Create edit button
const editBtn = document.createElement('button');
editBtn.className = 'insertr-edit-btn';
editBtn.innerHTML = '✏️';
editBtn.title = `Edit ${element._insertrConfig.label}`;
editBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
this.startEditing(contentId);
});
// Position relative for button placement
if (getComputedStyle(element).position === 'static') {
element.style.position = 'relative';
}
element.appendChild(editBtn);
}
/**
* Start editing an element
* @param {string} contentId - Content identifier
*/
startEditing(contentId) {
const element = this.editableElements.get(contentId);
if (!element || !this.state.editMode) return;
// Close any active editor
if (this.state.activeEditor && this.state.activeEditor !== contentId) {
this.cancelEditing(this.state.activeEditor);
}
const config = element._insertrConfig;
const currentContent = this.contentManager.extractContentFromElement(element);
// Create and show edit form
const form = this.formRenderer.createEditForm(contentId, config, currentContent);
const overlay = this.formRenderer.showEditForm(element, form);
// Setup form event handlers
this.setupFormHandlers(overlay, contentId);
this.state.activeEditor = contentId;
}
/**
* Setup form event handlers
* @param {HTMLElement} overlay - Form overlay
* @param {string} contentId - Content identifier
*/
setupFormHandlers(overlay, contentId) {
const saveBtn = overlay.querySelector('.insertr-btn-save');
const cancelBtn = overlay.querySelector('.insertr-btn-cancel');
if (saveBtn) {
saveBtn.addEventListener('click', () => {
this.saveElementContent(contentId, overlay);
});
}
if (cancelBtn) {
cancelBtn.addEventListener('click', () => {
this.cancelEditing(contentId);
});
}
// Handle Enter to save, Escape to cancel
overlay.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.saveElementContent(contentId, overlay);
} else if (e.key === 'Escape') {
e.preventDefault();
this.cancelEditing(contentId);
}
});
}
/**
* Save element content
* @param {string} contentId - Content identifier
* @param {HTMLElement} overlay - Form overlay
*/
async saveElementContent(contentId, overlay) {
const element = this.editableElements.get(contentId);
const form = overlay.querySelector('.insertr-edit-form');
const config = element._insertrConfig;
if (!element || !form) return;
// Extract form data
const formData = this.formRenderer.extractFormData(form, config);
// Validate the data
const validation = this.validateFormData(formData, config);
if (!validation.valid) {
alert(validation.message);
return;
}
try {
// Show saving state
element.classList.add('insertr-saving');
// Save to server (mock for now)
await this.contentManager.saveToServer(contentId, formData);
// Apply content to element
this.contentManager.applyContentToElement(element, formData);
// Close form
this.formRenderer.hideEditForm(overlay);
this.state.activeEditor = null;
// Show success feedback
element.classList.add('insertr-save-success');
setTimeout(() => {
element.classList.remove('insertr-save-success');
}, 2000);
} catch (error) {
console.error('Failed to save content:', error);
alert('Failed to save content. Please try again.');
} finally {
element.classList.remove('insertr-saving');
}
}
/**
* Validate form data before saving
* @param {string|Object} data - Form data to validate
* @param {Object} config - Field configuration
* @returns {Object} Validation result
*/
validateFormData(data, config) {
if (config.type === 'link' && config.includeUrl) {
// Validate link data
const textValidation = this.validation.validateInput(data.text, 'text');
if (!textValidation.valid) return textValidation;
const urlValidation = this.validation.validateInput(data.url, 'link');
if (!urlValidation.valid) return urlValidation;
return { valid: true };
} else {
// Validate single content
return this.validation.validateInput(data, config.type);
}
}
/**
* Cancel editing
* @param {string} contentId - Content identifier
*/
cancelEditing(contentId) {
const overlay = document.querySelector('.insertr-form-overlay');
if (overlay) {
this.formRenderer.hideEditForm(overlay);
}
if (this.state.activeEditor === contentId) {
this.state.activeEditor = null;
}
}
/**
* Update markdown preview (called by form renderer)
* @param {HTMLElement} previewElement - Preview container
* @param {string} markdown - Markdown content
*/
updateMarkdownPreview(previewElement, markdown) {
if (this.markdownProcessor.isReady()) {
const html = this.markdownProcessor.createPreview(markdown);
previewElement.innerHTML = html;
} else {
previewElement.innerHTML = '<p><em>Markdown processor not available</em></p>';
}
}
/**
* Render markdown content (called by content manager)
* @param {HTMLElement} element - Element to update
* @param {string} markdownText - Markdown content
*/
renderMarkdown(element, markdownText) {
if (this.markdownProcessor.isReady()) {
this.markdownProcessor.applyToElement(element, markdownText);
} else {
console.warn('Markdown processor not available');
element.textContent = markdownText;
}
}
// Authentication and UI methods (simplified)
/**
* Setup authentication controls
*/
setupAuthenticationControls() {
const authToggle = document.getElementById('auth-toggle');
const editToggle = document.getElementById('edit-mode-toggle');
if (authToggle) {
authToggle.addEventListener('click', () => this.toggleAuthentication());
}
if (editToggle) {
editToggle.addEventListener('click', () => this.toggleEditMode());
}
}
/**
* Toggle authentication state
*/
toggleAuthentication() {
this.state.isAuthenticated = !this.state.isAuthenticated;
this.state.currentUser = this.state.isAuthenticated ? { name: 'Demo User' } : null;
if (!this.state.isAuthenticated) {
this.state.editMode = false;
}
this.updateBodyClasses();
this.updateStatusIndicator();
const authBtn = document.getElementById('auth-toggle');
if (authBtn) {
authBtn.textContent = this.state.isAuthenticated ? 'Logout' : 'Login as Client';
}
}
/**
* Toggle edit mode
*/
toggleEditMode() {
if (!this.state.isAuthenticated) return;
this.state.editMode = !this.state.editMode;
if (!this.state.editMode && this.state.activeEditor) {
this.cancelEditing(this.state.activeEditor);
}
this.updateBodyClasses();
this.updateStatusIndicator();
const editBtn = document.getElementById('edit-mode-toggle');
if (editBtn) {
editBtn.textContent = `Edit Mode: ${this.state.editMode ? 'On' : 'Off'}`;
}
}
/**
* Update body CSS classes based on state
*/
updateBodyClasses() {
document.body.classList.toggle('insertr-authenticated', this.state.isAuthenticated);
document.body.classList.toggle('insertr-edit-mode', this.state.editMode);
const editToggle = document.getElementById('edit-mode-toggle');
if (editToggle) {
editToggle.style.display = this.state.isAuthenticated ? 'inline-block' : 'none';
}
}
/**
* Create status indicator
*/
createStatusIndicator() {
// Implementation similar to original, simplified for brevity
this.updateStatusIndicator();
}
/**
* Update status indicator
*/
updateStatusIndicator() {
// Implementation similar to original, simplified for brevity
console.log(`Status: Auth=${this.state.isAuthenticated}, Edit=${this.state.editMode}`);
}
/**
* Get configuration instance (for external customization)
* @returns {InsertrConfig} Configuration instance
*/
getConfig() {
return this.config;
}
/**
* Get content manager instance
* @returns {InsertrContentManager} Content manager instance
*/
getContentManager() {
return this.contentManager;
}
}
// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = Insertr;
}
// Auto-initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
window.insertr = new Insertr();
});