From af88e57272e224a809482a897e58dcf531850533 Mon Sep 17 00:00:00 2001
From: Joakim
Date: Fri, 29 Aug 2025 22:41:15 +0200
Subject: [PATCH] Add working frontend prototype with edit-in-place
functionality
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
⨠Features implemented:
- Complete demo website (Acme Consulting Services)
- Core insertr.js library with edit-in-place functionality
- Support for simple text and rich markdown content
- Mock authentication system (login/logout simulation)
- Edit mode toggle for client interface
- Local storage persistence
- Visual feedback for saving content
- Multi-page support (index.html, about.html)
- Responsive design with clean UI
šÆ Three user experiences:
- Customer: Clean website with no edit interface
- Client: Same website + edit buttons when authenticated
- Developer: Simple class-based integration
šļø Architecture:
- Vanilla JavaScript (no framework dependencies)
- Alpine.js ready for enhanced interactivity
- CSS-only styling for edit indicators
- Mock API structure for backend planning
Next: Connect to Go backend with real persistence
---
demo-site/README.md | 115 +++++++++
demo-site/about.html | 125 ++++++++++
demo-site/assets/style.css | 254 +++++++++++++++++++
demo-site/index.html | 109 +++++++++
demo-site/insertr/insertr.css | 211 ++++++++++++++++
demo-site/insertr/insertr.js | 418 ++++++++++++++++++++++++++++++++
demo-site/mock-api/content.json | 88 +++++++
7 files changed, 1320 insertions(+)
create mode 100644 demo-site/README.md
create mode 100644 demo-site/about.html
create mode 100644 demo-site/assets/style.css
create mode 100644 demo-site/index.html
create mode 100644 demo-site/insertr/insertr.css
create mode 100644 demo-site/insertr/insertr.js
create mode 100644 demo-site/mock-api/content.json
diff --git a/demo-site/README.md b/demo-site/README.md
new file mode 100644
index 0000000..56850a2
--- /dev/null
+++ b/demo-site/README.md
@@ -0,0 +1,115 @@
+# Insertr Demo Site
+
+This is a prototype demonstration of the Insertr edit-in-place CMS system.
+
+## What is Insertr?
+
+Insertr allows developers to make any website content editable by simply adding a CSS class. Clients can then log in and edit content directly on their website without needing to learn a complex admin interface.
+
+## Three User Types
+
+### 1. The Customer (End User)
+- Sees a clean, professional website
+- No editing interface visible
+- Fast loading with minimal overhead
+
+### 2. The Client (Content Manager)
+- Logs in to see the same website with subtle edit buttons
+- Clicks edit buttons to modify content inline
+- Can edit both simple text and rich markdown content
+- Changes are saved immediately
+
+### 3. The Developer (You)
+- Simple integration: just add `class="insertr"` and `data-content-id="unique-id"`
+- No complex setup or framework dependencies
+- Works with any existing website
+
+## Demo Instructions
+
+1. **Open `index.html` in your browser** - You'll see the customer view
+2. **Click "Login as Client"** - This simulates authentication
+3. **Click "Edit Mode: Off" to turn on editing** - Now you'll see edit buttons (āļø) appear
+4. **Click any edit button** to modify content
+5. **Try both simple text and rich content** (marked with š)
+6. **Navigate to `about.html`** to see how it works across pages
+
+## Technical Details
+
+### For Developers
+
+To make content editable, just add the insertr class and data attribute:
+
+```html
+
+
+
Your Website Title
+
+
+
+
+
About Us
+
We help businesses succeed...
+
+```
+
+### Integration
+
+Include the Insertr library in your HTML:
+
+```html
+
+
+```
+
+That's it! The library will automatically scan for editable elements and set up the editing interface.
+
+## Current Features
+
+- ā
Edit-in-place for simple text content
+- ā
Markdown editing for rich content
+- ā
Mock authentication (login/logout)
+- ā
Edit mode toggle
+- ā
Local storage persistence
+- ā
Visual feedback for saving
+- ā
Multi-page support
+- ā
Responsive design
+
+## Planned Features
+
+- [ ] Real backend API integration
+- [ ] Authentik OAuth integration
+- [ ] File upload and image management
+- [ ] Content versioning and rollback
+- [ ] Multi-user permissions
+- [ ] Admin dashboard
+- [ ] Git-based deployment
+
+## Architecture
+
+This prototype demonstrates the frontend experience. The full system will include:
+
+- **Go backend** with REST API
+- **File-based content storage** with Git versioning
+- **Authentik OAuth** for secure authentication
+- **Multi-tenant support** for hosting multiple client sites
+- **Developer tools** for easy integration
+
+## Files Structure
+
+```
+demo-site/
+āāā index.html # Homepage demo
+āāā about.html # Additional page demo
+āāā assets/
+ā āāā style.css # Demo site styling
+āāā insertr/
+ā āāā insertr.js # Core library
+ā āāā insertr.css # Edit interface styling
+ā āāā components/ # Future: edit components
+āāā mock-api/
+ āāā content.json # Mock backend data structure
+```
+
+## Try It Now!
+
+Open `index.html` in your browser and experience the three different user views by using the authentication controls in the top right corner.
\ No newline at end of file
diff --git a/demo-site/about.html b/demo-site/about.html
new file mode 100644
index 0000000..36fcb74
--- /dev/null
+++ b/demo-site/about.html
@@ -0,0 +1,125 @@
+
+
+
+
+
+ About - Acme Consulting Services
+
+
+
+
+
+
+
+
+
Acme Consulting
+
+
+
+
+ Login as Client
+ Edit Mode: Off
+
+
+
+
+
+
+
+
+
About Acme Consulting
+
We're a team of experienced consultants dedicated to helping small businesses thrive in today's competitive marketplace.
+
+
+
+
+
+
+
+
+
Our Story
+
Founded in 2020, Acme Consulting emerged from a simple observation: small businesses needed access to the same high-quality strategic advice that large corporations receive, but in a format that was accessible, affordable, and actionable.
+
+
Our founders, with combined experience of over 30 years in business strategy, operations, and technology, recognized that the traditional consulting model wasn't serving the needs of growing businesses. We set out to change that.
+
+
Today, we've helped over 200 businesses streamline their operations, clarify their strategy, and achieve sustainable growth. Our approach combines proven methodologies with a deep understanding of the unique challenges facing small to medium-sized businesses.
+
+
+
+
+
+
+
+
+
Our Team
+
We're a diverse group of strategists, operators, and technology experts united by our passion for helping businesses succeed.
+
+
+
+
+
+
Sarah Chen
+
Founder & CEO
+
Former McKinsey consultant with 15 years of experience in strategy and operations. MBA from Stanford.
+
+
+
+
+
Michael Rodriguez
+
Head of Operations
+
20 years in manufacturing and supply chain optimization. Expert in lean methodologies and process improvement.
+
+
+
+
+
Emma Thompson
+
Digital Strategy Lead
+
Former tech startup founder turned consultant. Specializes in digital transformation and technology adoption.
+
+
+
+
+
+
+
+
+
+
+
Our Values
+
+
+
Client-First
+
Every recommendation we make is designed with your specific business context and goals in mind.
+
+
+
Practical Solutions
+
We believe in strategies that you can actually implement with your current resources and capabilities.
+
+
+
Long-term Partnership
+
We're not just consultants; we're partners in your business success for the long haul.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo-site/assets/style.css b/demo-site/assets/style.css
new file mode 100644
index 0000000..4100a62
--- /dev/null
+++ b/demo-site/assets/style.css
@@ -0,0 +1,254 @@
+/* Reset and Base Styles */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ line-height: 1.6;
+ color: #333;
+}
+
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 20px;
+}
+
+/* Navigation */
+.navbar {
+ background: #fff;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+ position: fixed;
+ top: 0;
+ width: 100%;
+ z-index: 1000;
+}
+
+.navbar .container {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1rem 20px;
+}
+
+.logo {
+ color: #2563eb;
+ font-size: 1.5rem;
+ font-weight: bold;
+}
+
+.nav-links {
+ display: flex;
+ list-style: none;
+ gap: 2rem;
+}
+
+.nav-links a {
+ text-decoration: none;
+ color: #333;
+ font-weight: 500;
+ transition: color 0.3s;
+}
+
+.nav-links a:hover {
+ color: #2563eb;
+}
+
+.auth-controls {
+ display: flex;
+ gap: 1rem;
+ align-items: center;
+}
+
+/* Buttons */
+.btn-primary, .btn-secondary {
+ padding: 0.75rem 1.5rem;
+ border: none;
+ border-radius: 6px;
+ font-weight: 500;
+ text-decoration: none;
+ cursor: pointer;
+ transition: all 0.3s;
+ display: inline-block;
+ font-size: 0.9rem;
+}
+
+.btn-primary {
+ background: #2563eb;
+ color: white;
+}
+
+.btn-primary:hover {
+ background: #1d4ed8;
+}
+
+.btn-secondary {
+ background: #f3f4f6;
+ color: #374151;
+ border: 1px solid #d1d5db;
+}
+
+.btn-secondary:hover {
+ background: #e5e7eb;
+}
+
+/* Hero Section */
+.hero {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 8rem 0 4rem;
+ text-align: center;
+}
+
+.hero h1 {
+ font-size: 3rem;
+ margin-bottom: 1rem;
+ font-weight: 700;
+}
+
+.hero .lead {
+ font-size: 1.25rem;
+ margin-bottom: 2rem;
+ opacity: 0.9;
+ max-width: 600px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+/* Services Section */
+.services {
+ padding: 4rem 0;
+ background: #f9fafb;
+}
+
+.services h2 {
+ text-align: center;
+ font-size: 2.5rem;
+ margin-bottom: 1rem;
+ color: #1f2937;
+}
+
+.section-subtitle {
+ text-align: center;
+ font-size: 1.125rem;
+ color: #6b7280;
+ margin-bottom: 3rem;
+}
+
+.services-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 2rem;
+ margin-top: 3rem;
+}
+
+.service-card {
+ background: white;
+ padding: 2rem;
+ border-radius: 12px;
+ box-shadow: 0 4px 6px rgba(0,0,0,0.05);
+ transition: transform 0.3s, box-shadow 0.3s;
+}
+
+.service-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 8px 25px rgba(0,0,0,0.1);
+}
+
+.service-card h3 {
+ color: #2563eb;
+ margin-bottom: 1rem;
+ font-size: 1.5rem;
+}
+
+/* Testimonial Section */
+.testimonial {
+ padding: 4rem 0;
+ background: #2563eb;
+ color: white;
+ text-align: center;
+}
+
+.testimonial blockquote {
+ max-width: 800px;
+ margin: 0 auto;
+}
+
+.testimonial p {
+ font-size: 1.5rem;
+ font-style: italic;
+ margin-bottom: 1rem;
+}
+
+.testimonial cite {
+ font-size: 1rem;
+ opacity: 0.8;
+ font-style: normal;
+}
+
+/* CTA Section */
+.cta {
+ padding: 4rem 0;
+ text-align: center;
+ background: white;
+}
+
+.cta h2 {
+ font-size: 2.5rem;
+ margin-bottom: 1rem;
+ color: #1f2937;
+}
+
+.cta p {
+ font-size: 1.125rem;
+ color: #6b7280;
+ margin-bottom: 2rem;
+ max-width: 600px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+/* Footer */
+.footer {
+ background: #1f2937;
+ color: white;
+ padding: 2rem 0;
+ text-align: center;
+}
+
+.footer p {
+ margin-bottom: 0.5rem;
+ opacity: 0.8;
+}
+
+/* Responsive Design */
+@media (max-width: 768px) {
+ .navbar .container {
+ flex-direction: column;
+ gap: 1rem;
+ }
+
+ .nav-links {
+ gap: 1rem;
+ }
+
+ .auth-controls {
+ flex-direction: column;
+ gap: 0.5rem;
+ }
+
+ .hero h1 {
+ font-size: 2rem;
+ }
+
+ .hero .lead {
+ font-size: 1.125rem;
+ }
+
+ .services-grid {
+ grid-template-columns: 1fr;
+ }
+}
\ No newline at end of file
diff --git a/demo-site/index.html b/demo-site/index.html
new file mode 100644
index 0000000..1e40766
--- /dev/null
+++ b/demo-site/index.html
@@ -0,0 +1,109 @@
+
+
+
+
+
+ Acme Consulting Services
+
+
+
+
+
+
+
+
+
Acme Consulting
+
+
+
+
+ Login as Client
+ Edit Mode: Off
+
+
+
+
+
+
+
+
+
Transform Your Business with Expert Consulting
+
We help small businesses grow through strategic planning, process optimization, and digital transformation. Our team brings 15+ years of experience to drive your success.
+
Get Started Today
+
+
+
+
+
+
+
+
+
Our Services
+
Comprehensive solutions tailored to your business needs
+
+
+
+
+
+
Strategic Planning
+
Develop clear roadmaps and actionable strategies that align with your business goals and drive sustainable growth.
+
+
+
+
+
Operations Optimization
+
Streamline processes, reduce costs, and improve efficiency through proven methodologies and best practices.
+
+
+
+
+
Digital Transformation
+
Modernize your technology stack and digital presence to compete effectively in today's marketplace.
+
+
+
+
+
+
+
+
+
+
+
+ "Acme Consulting transformed our operations completely. We saw a 40% increase in efficiency within 6 months of implementing their recommendations."
+ Sarah Johnson, CEO of TechStart Inc.
+
+
+
+
+
+
+
+
+
+
Ready to Transform Your Business?
+
Contact us today for a free consultation and discover how we can help you achieve your goals.
+
Schedule Consultation
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo-site/insertr/insertr.css b/demo-site/insertr/insertr.css
new file mode 100644
index 0000000..0dd0025
--- /dev/null
+++ b/demo-site/insertr/insertr.css
@@ -0,0 +1,211 @@
+/* Insertr Core Styles */
+
+/* Hide edit indicators by default (customer view) */
+.insertr-edit-btn {
+ display: none;
+}
+
+/* Show edit controls when authenticated and edit mode is on */
+.insertr-authenticated.insertr-edit-mode .insertr {
+ position: relative;
+ border: 2px dashed transparent;
+ transition: all 0.3s ease;
+}
+
+.insertr-authenticated.insertr-edit-mode .insertr:hover {
+ border-color: #3b82f6;
+ background-color: rgba(59, 130, 246, 0.05);
+}
+
+/* Edit button styling */
+.insertr-authenticated.insertr-edit-mode .insertr-edit-btn {
+ display: block;
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ width: 32px;
+ height: 32px;
+ background: #3b82f6;
+ color: white;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 16px;
+ z-index: 10;
+ transition: all 0.2s;
+ box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
+}
+
+.insertr-edit-btn:hover {
+ background: #2563eb;
+ transform: scale(1.05);
+}
+
+/* Edit form container */
+.insertr-edit-form {
+ position: relative;
+ background: white;
+ border: 2px solid #3b82f6;
+ border-radius: 8px;
+ padding: 1rem;
+ margin: 0.5rem 0;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
+ z-index: 20;
+}
+
+/* Form controls */
+.insertr-form-group {
+ margin-bottom: 1rem;
+}
+
+.insertr-form-group:last-child {
+ margin-bottom: 0;
+}
+
+.insertr-form-label {
+ display: block;
+ font-weight: 600;
+ color: #374151;
+ margin-bottom: 0.5rem;
+ font-size: 0.875rem;
+}
+
+.insertr-form-input,
+.insertr-form-textarea {
+ width: 100%;
+ padding: 0.75rem;
+ border: 1px solid #d1d5db;
+ border-radius: 6px;
+ font-family: inherit;
+ font-size: 1rem;
+ transition: border-color 0.2s, box-shadow 0.2s;
+}
+
+.insertr-form-input:focus,
+.insertr-form-textarea:focus {
+ outline: none;
+ border-color: #3b82f6;
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+}
+
+.insertr-form-textarea {
+ min-height: 120px;
+ resize: vertical;
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
+}
+
+/* Form actions */
+.insertr-form-actions {
+ display: flex;
+ gap: 0.5rem;
+ justify-content: flex-end;
+ margin-top: 1rem;
+ padding-top: 1rem;
+ border-top: 1px solid #e5e7eb;
+}
+
+.insertr-btn-save {
+ background: #10b981;
+ color: white;
+ border: none;
+ padding: 0.5rem 1rem;
+ border-radius: 6px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background-color 0.2s;
+}
+
+.insertr-btn-save:hover {
+ background: #059669;
+}
+
+.insertr-btn-cancel {
+ background: #6b7280;
+ color: white;
+ border: none;
+ padding: 0.5rem 1rem;
+ border-radius: 6px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background-color 0.2s;
+}
+
+.insertr-btn-cancel:hover {
+ background: #4b5563;
+}
+
+/* Content type indicators */
+.insertr[data-content-type="rich"]::before {
+ content: "š";
+ position: absolute;
+ top: -8px;
+ left: -8px;
+ background: #8b5cf6;
+ color: white;
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ display: none;
+ align-items: center;
+ justify-content: center;
+ font-size: 12px;
+ z-index: 5;
+}
+
+.insertr-authenticated.insertr-edit-mode .insertr[data-content-type="rich"]:hover::before {
+ display: flex;
+}
+
+/* Loading and success states */
+.insertr-saving {
+ opacity: 0.7;
+ pointer-events: none;
+}
+
+.insertr-save-success {
+ border-color: #10b981 !important;
+ background-color: rgba(16, 185, 129, 0.05) !important;
+}
+
+.insertr-save-success::after {
+ content: "ā Saved";
+ position: absolute;
+ top: -8px;
+ right: -8px;
+ background: #10b981;
+ color: white;
+ padding: 2px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ font-weight: 500;
+ z-index: 15;
+ animation: fadeInOut 2s ease-in-out;
+}
+
+@keyframes fadeInOut {
+ 0%, 100% { opacity: 0; transform: translateY(10px); }
+ 20%, 80% { opacity: 1; transform: translateY(0); }
+}
+
+/* Authentication status indicator */
+.insertr-auth-status {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ background: #1f2937;
+ color: white;
+ padding: 0.75rem 1rem;
+ border-radius: 8px;
+ font-size: 0.875rem;
+ z-index: 1000;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+ transition: all 0.3s;
+}
+
+.insertr-auth-status.authenticated {
+ background: #10b981;
+}
+
+.insertr-auth-status.edit-mode {
+ background: #3b82f6;
+}
\ No newline at end of file
diff --git a/demo-site/insertr/insertr.js b/demo-site/insertr/insertr.js
new file mode 100644
index 0000000..37d0d84
--- /dev/null
+++ b/demo-site/insertr/insertr.js
@@ -0,0 +1,418 @@
+/**
+ * Insertr - Edit-in-place CMS Library
+ * Simple integration: add class="insertr" to any element
+ */
+
+class Insertr {
+ constructor(options = {}) {
+ this.options = {
+ apiEndpoint: options.apiEndpoint || '/api/content',
+ authEndpoint: options.authEndpoint || '/api/auth',
+ storageKey: 'insertr_content',
+ autoInit: options.autoInit !== false,
+ ...options
+ };
+
+ this.state = {
+ isAuthenticated: false,
+ editMode: false,
+ currentUser: null,
+ contentCache: new Map()
+ };
+
+ this.editablElements = new Map();
+ this.statusIndicator = null;
+
+ if (this.options.autoInit) {
+ this.init();
+ }
+ }
+
+ async init() {
+ console.log('š Insertr initializing...');
+
+ // Load content from localStorage (mock persistence)
+ this.loadContentFromStorage();
+
+ // 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.editablElements.size} editable elements`);
+ }
+
+ 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;
+ }
+
+ // Store reference and setup
+ this.editablElements.set(contentId, element);
+ this.setupEditableElement(element, contentId);
+ });
+ }
+
+ setupEditableElement(element, contentId) {
+ // Add edit button
+ const editBtn = document.createElement('button');
+ editBtn.className = 'insertr-edit-btn';
+ editBtn.innerHTML = 'āļø';
+ editBtn.title = 'Edit content';
+ editBtn.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ this.startEditing(contentId);
+ });
+
+ element.style.position = 'relative';
+ element.appendChild(editBtn);
+
+ // Load saved content if available
+ const savedContent = this.state.contentCache.get(contentId);
+ if (savedContent) {
+ this.applyContentToElement(element, savedContent);
+ }
+ }
+
+ startEditing(contentId) {
+ const element = this.editablElements.get(contentId);
+ if (!element) return;
+
+ const contentType = element.getAttribute('data-content-type') || 'simple';
+ const currentContent = this.extractContentFromElement(element);
+
+ // Create edit form
+ const form = this.createEditForm(contentId, contentType, currentContent);
+
+ // Hide original content and show form
+ const originalContent = element.querySelector('.insertr-original-content') || element.cloneNode(true);
+ originalContent.classList.add('insertr-original-content');
+ originalContent.style.display = 'none';
+
+ // Clear element and add form
+ element.innerHTML = '';
+ element.appendChild(originalContent);
+ element.appendChild(form);
+ }
+
+ createEditForm(contentId, contentType, currentContent) {
+ const form = document.createElement('div');
+ form.className = 'insertr-edit-form';
+
+ let formHTML = '';
+
+ if (contentType === 'rich') {
+ formHTML = `
+
+ Content (Markdown)
+
+
+ `;
+ } else {
+ formHTML = `
+
+ Content
+
+
+ `;
+ }
+
+ formHTML += `
+
+ Cancel
+ Save
+
+ `;
+
+ form.innerHTML = formHTML;
+
+ // Bind events
+ form.querySelector('.insertr-btn-cancel').addEventListener('click', () => {
+ this.cancelEditing(contentId);
+ });
+
+ form.querySelector('.insertr-btn-save').addEventListener('click', () => {
+ const input = form.querySelector('input, textarea');
+ this.saveContent(contentId, input.value);
+ });
+
+ // Focus on input
+ setTimeout(() => {
+ const input = form.querySelector('input, textarea');
+ input.focus();
+ if (input.type === 'text') {
+ input.select();
+ }
+ }, 100);
+
+ return form;
+ }
+
+ async saveContent(contentId, newContent) {
+ const element = this.editablElements.get(contentId);
+ if (!element) return;
+
+ // Add saving state
+ element.classList.add('insertr-saving');
+
+ try {
+ // In a real implementation, this would make an API call
+ // For now, we'll simulate it and store in localStorage
+ await this.simulateSave(contentId, newContent);
+
+ // Update cache
+ this.state.contentCache.set(contentId, newContent);
+ this.saveContentToStorage();
+
+ // Apply new content to element
+ this.applyContentToElement(element, newContent, true);
+
+ // Show success state
+ 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');
+ }
+ }
+
+ applyContentToElement(element, content, isEdit = false) {
+ const contentType = element.getAttribute('data-content-type') || 'simple';
+ let originalContent = element.querySelector('.insertr-original-content');
+
+ if (!originalContent) {
+ // First time, clone current content
+ originalContent = element.cloneNode(true);
+ originalContent.classList.add('insertr-original-content');
+ }
+
+ // Update content based on type
+ if (contentType === 'rich') {
+ const html = this.markdownToHtml(content);
+ originalContent.innerHTML = html + 'āļø ';
+ } else {
+ // For simple content, try to maintain structure but update text
+ const textNodes = this.getTextNodes(originalContent);
+ if (textNodes.length > 0) {
+ textNodes[0].textContent = content;
+ }
+ }
+
+ // Replace element content
+ element.innerHTML = '';
+ originalContent.style.display = '';
+ element.appendChild(originalContent);
+
+ // Re-setup edit functionality
+ this.setupEditableElement(element, element.getAttribute('data-content-id'));
+ }
+
+ cancelEditing(contentId) {
+ const element = this.editablElements.get(contentId);
+ if (!element) return;
+
+ const originalContent = element.querySelector('.insertr-original-content');
+ if (originalContent) {
+ element.innerHTML = '';
+ originalContent.style.display = '';
+ element.appendChild(originalContent);
+ this.setupEditableElement(element, contentId);
+ }
+ }
+
+ extractContentFromElement(element) {
+ const clone = element.cloneNode(true);
+ // Remove edit button
+ const editBtn = clone.querySelector('.insertr-edit-btn');
+ if (editBtn) editBtn.remove();
+
+ return clone.textContent.trim() || clone.innerHTML.trim();
+ }
+
+ setupAuthenticationControls() {
+ const authToggle = document.getElementById('auth-toggle');
+ const editModeToggle = document.getElementById('edit-mode-toggle');
+
+ if (authToggle) {
+ authToggle.addEventListener('click', () => {
+ this.toggleAuthentication();
+ });
+ }
+
+ if (editModeToggle) {
+ editModeToggle.addEventListener('click', () => {
+ this.toggleEditMode();
+ });
+ }
+ }
+
+ toggleAuthentication() {
+ this.state.isAuthenticated = !this.state.isAuthenticated;
+
+ const authToggle = document.getElementById('auth-toggle');
+ const editModeToggle = document.getElementById('edit-mode-toggle');
+
+ if (this.state.isAuthenticated) {
+ authToggle.textContent = 'Logout';
+ authToggle.className = 'btn-secondary';
+ editModeToggle.style.display = 'block';
+ this.state.currentUser = { name: 'Demo Client', role: 'editor' };
+ } else {
+ authToggle.textContent = 'Login as Client';
+ authToggle.className = 'btn-secondary';
+ editModeToggle.style.display = 'none';
+ this.state.editMode = false;
+ this.state.currentUser = null;
+ }
+
+ this.updateBodyClasses();
+ this.updateStatusIndicator();
+ }
+
+ toggleEditMode() {
+ if (!this.state.isAuthenticated) return;
+
+ this.state.editMode = !this.state.editMode;
+
+ const editModeToggle = document.getElementById('edit-mode-toggle');
+ if (editModeToggle) {
+ editModeToggle.textContent = `Edit Mode: ${this.state.editMode ? 'On' : 'Off'}`;
+ editModeToggle.className = this.state.editMode ? 'btn-primary' : 'btn-secondary';
+ }
+
+ this.updateBodyClasses();
+ this.updateStatusIndicator();
+ }
+
+ updateBodyClasses() {
+ document.body.classList.toggle('insertr-authenticated', this.state.isAuthenticated);
+ document.body.classList.toggle('insertr-edit-mode', this.state.editMode);
+ }
+
+ createStatusIndicator() {
+ this.statusIndicator = document.createElement('div');
+ this.statusIndicator.className = 'insertr-auth-status';
+ document.body.appendChild(this.statusIndicator);
+ this.updateStatusIndicator();
+ }
+
+ updateStatusIndicator() {
+ if (!this.statusIndicator) return;
+
+ let status = 'Public View';
+ let className = 'insertr-auth-status';
+
+ if (this.state.isAuthenticated) {
+ if (this.state.editMode) {
+ status = 'āļø Edit Mode Active';
+ className += ' edit-mode';
+ } else {
+ status = 'š¤ Client Authenticated';
+ className += ' authenticated';
+ }
+ }
+
+ this.statusIndicator.textContent = status;
+ this.statusIndicator.className = className;
+ }
+
+ // Utility methods for content conversion
+ markdownToHtml(markdown) {
+ // Simple markdown to HTML conversion (in real app, use a proper library)
+ return markdown
+ .replace(/^# (.*$)/gim, '$1 ')
+ .replace(/^## (.*$)/gim, '$1 ')
+ .replace(/^### (.*$)/gim, '$1 ')
+ .replace(/\*\*(.*?)\*\*/gim, '$1 ')
+ .replace(/\*(.*?)\*/gim, '$1 ')
+ .replace(/^\- (.*$)/gim, '$1 ')
+ .replace(/\n\n/gim, '')
+ .replace(/^(?!<[h|l|p])(.+)$/gim, '
$1
')
+ .replace(/(.*<\/li>)/gims, '');
+ }
+
+ htmlToMarkdown(html) {
+ // Simple HTML to markdown conversion
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = html;
+
+ // Remove edit buttons and other insertr elements
+ tempDiv.querySelectorAll('.insertr-edit-btn').forEach(btn => btn.remove());
+
+ return tempDiv.textContent || tempDiv.innerText || '';
+ }
+
+ getTextNodes(node) {
+ const textNodes = [];
+ if (node.nodeType === Node.TEXT_NODE) {
+ textNodes.push(node);
+ } else {
+ for (let child of node.childNodes) {
+ textNodes.push(...this.getTextNodes(child));
+ }
+ }
+ return textNodes;
+ }
+
+ // Storage methods (mock persistence)
+ loadContentFromStorage() {
+ try {
+ const stored = localStorage.getItem(this.options.storageKey);
+ if (stored) {
+ const data = JSON.parse(stored);
+ this.state.contentCache = new Map(Object.entries(data));
+ }
+ } catch (error) {
+ console.warn('Failed to load content from storage:', error);
+ }
+ }
+
+ saveContentToStorage() {
+ try {
+ const data = Object.fromEntries(this.state.contentCache);
+ localStorage.setItem(this.options.storageKey, JSON.stringify(data));
+ } catch (error) {
+ console.warn('Failed to save content to storage:', error);
+ }
+ }
+
+ async simulateSave(contentId, content) {
+ // Simulate network delay
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ // Simulate occasional failures for testing
+ if (Math.random() < 0.05) {
+ throw new Error('Network error');
+ }
+
+ console.log(`š¾ Saved content for ${contentId}:`, content);
+ }
+}
+
+// Auto-initialize when DOM is ready
+document.addEventListener('DOMContentLoaded', () => {
+ window.insertr = new Insertr();
+});
+
+// Export for module usage
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = Insertr;
+}
\ No newline at end of file
diff --git a/demo-site/mock-api/content.json b/demo-site/mock-api/content.json
new file mode 100644
index 0000000..81813f2
--- /dev/null
+++ b/demo-site/mock-api/content.json
@@ -0,0 +1,88 @@
+{
+ "site": {
+ "id": "acme-consulting",
+ "domain": "acmeconsulting.example.com",
+ "owner": "client@acmeconsulting.com",
+ "created": "2024-01-15T10:30:00Z",
+ "updated": "2024-01-29T14:22:00Z"
+ },
+ "content": {
+ "nav-logo": {
+ "type": "simple",
+ "value": "Acme Consulting",
+ "updated": "2024-01-15T10:30:00Z",
+ "updatedBy": "client@acmeconsulting.com"
+ },
+ "hero-content": {
+ "type": "rich",
+ "value": "# Transform Your Business with Expert Consulting\n\nWe help small businesses grow through strategic planning, process optimization, and digital transformation. Our team brings 15+ years of experience to drive your success.\n\n[Get Started Today](contact.html)",
+ "updated": "2024-01-20T09:15:00Z",
+ "updatedBy": "client@acmeconsulting.com"
+ },
+ "services-title": {
+ "type": "rich",
+ "value": "## Our Services\n\nComprehensive solutions tailored to your business needs",
+ "updated": "2024-01-15T10:30:00Z",
+ "updatedBy": "client@acmeconsulting.com"
+ },
+ "service-strategy": {
+ "type": "rich",
+ "value": "### Strategic Planning\n\nDevelop clear roadmaps and actionable strategies that align with your business goals and drive sustainable growth.",
+ "updated": "2024-01-15T10:30:00Z",
+ "updatedBy": "client@acmeconsulting.com"
+ },
+ "service-operations": {
+ "type": "rich",
+ "value": "### Operations Optimization\n\nStreamline processes, reduce costs, and improve efficiency through proven methodologies and best practices.",
+ "updated": "2024-01-15T10:30:00Z",
+ "updatedBy": "client@acmeconsulting.com"
+ },
+ "service-digital": {
+ "type": "rich",
+ "value": "### Digital Transformation\n\nModernize your technology stack and digital presence to compete effectively in today's marketplace.",
+ "updated": "2024-01-15T10:30:00Z",
+ "updatedBy": "client@acmeconsulting.com"
+ },
+ "testimonial-content": {
+ "type": "rich",
+ "value": "> \"Acme Consulting transformed our operations completely. We saw a 40% increase in efficiency within 6 months of implementing their recommendations.\"\n> \n> ā Sarah Johnson, CEO of TechStart Inc.",
+ "updated": "2024-01-25T16:45:00Z",
+ "updatedBy": "client@acmeconsulting.com"
+ },
+ "cta-content": {
+ "type": "rich",
+ "value": "## Ready to Transform Your Business?\n\nContact us today for a free consultation and discover how we can help you achieve your goals.\n\n[Schedule Consultation](contact.html)",
+ "updated": "2024-01-15T10:30:00Z",
+ "updatedBy": "client@acmeconsulting.com"
+ },
+ "footer-info": {
+ "type": "simple",
+ "value": "Ā© 2024 Acme Consulting Services. All rights reserved.\nš§ info@acmeconsulting.com | š (555) 123-4567",
+ "updated": "2024-01-15T10:30:00Z",
+ "updatedBy": "client@acmeconsulting.com"
+ }
+ },
+ "permissions": {
+ "client@acmeconsulting.com": {
+ "role": "editor",
+ "canEdit": ["*"],
+ "canDelete": false,
+ "canCreatePages": false
+ },
+ "developer@example.com": {
+ "role": "admin",
+ "canEdit": ["*"],
+ "canDelete": true,
+ "canCreatePages": true
+ }
+ },
+ "history": [
+ {
+ "contentId": "testimonial-content",
+ "timestamp": "2024-01-25T16:45:00Z",
+ "user": "client@acmeconsulting.com",
+ "action": "update",
+ "previousValue": "> \"Working with Acme Consulting was a game-changer for our business.\"\n> \n> ā Sarah Johnson, CEO of TechStart Inc."
+ }
+ ]
+}
\ No newline at end of file