Add working frontend prototype with edit-in-place functionality

 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
This commit is contained in:
2025-08-29 22:41:15 +02:00
parent 1f6279f79f
commit af88e57272
7 changed files with 1320 additions and 0 deletions

115
demo-site/README.md Normal file
View File

@@ -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
<!-- Simple text content -->
<div class="insertr" data-content-id="hero-title">
<h1>Your Website Title</h1>
</div>
<!-- Rich content with markdown -->
<div class="insertr" data-content-id="about-text" data-content-type="rich">
<h2>About Us</h2>
<p>We help businesses succeed...</p>
</div>
```
### Integration
Include the Insertr library in your HTML:
```html
<link rel="stylesheet" href="insertr/insertr.css">
<script src="insertr/insertr.js"></script>
```
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.

125
demo-site/about.html Normal file
View File

@@ -0,0 +1,125 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>About - Acme Consulting Services</title>
<link rel="stylesheet" href="assets/style.css">
<link rel="stylesheet" href="insertr/insertr.css">
</head>
<body>
<!-- Navigation -->
<nav class="navbar">
<div class="container">
<div class="insertr" data-content-id="nav-logo">
<h1 class="logo">Acme Consulting</h1>
</div>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="about.html">About</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
<!-- Authentication controls -->
<div class="auth-controls">
<button id="auth-toggle" class="btn-secondary">Login as Client</button>
<button id="edit-mode-toggle" class="btn-primary" style="display: none;">Edit Mode: Off</button>
</div>
</div>
</nav>
<!-- Hero Section -->
<section class="hero">
<div class="container">
<div class="insertr" data-content-id="about-hero" data-content-type="rich">
<h1>About Acme Consulting</h1>
<p class="lead">We're a team of experienced consultants dedicated to helping small businesses thrive in today's competitive marketplace.</p>
</div>
</div>
</section>
<!-- Story Section -->
<section class="services">
<div class="container">
<div class="insertr" data-content-id="our-story" data-content-type="rich">
<h2>Our Story</h2>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</div>
</div>
</section>
<!-- Team Section -->
<section class="cta">
<div class="container">
<div class="insertr" data-content-id="team-section">
<h2>Our Team</h2>
<p>We're a diverse group of strategists, operators, and technology experts united by our passion for helping businesses succeed.</p>
</div>
<div class="services-grid" style="margin-top: 3rem;">
<div class="service-card">
<div class="insertr" data-content-id="team-member-1" data-content-type="rich">
<h3>Sarah Chen</h3>
<p><strong>Founder & CEO</strong></p>
<p>Former McKinsey consultant with 15 years of experience in strategy and operations. MBA from Stanford.</p>
</div>
</div>
<div class="service-card">
<div class="insertr" data-content-id="team-member-2" data-content-type="rich">
<h3>Michael Rodriguez</h3>
<p><strong>Head of Operations</strong></p>
<p>20 years in manufacturing and supply chain optimization. Expert in lean methodologies and process improvement.</p>
</div>
</div>
<div class="service-card">
<div class="insertr" data-content-id="team-member-3" data-content-type="rich">
<h3>Emma Thompson</h3>
<p><strong>Digital Strategy Lead</strong></p>
<p>Former tech startup founder turned consultant. Specializes in digital transformation and technology adoption.</p>
</div>
</div>
</div>
</div>
</section>
<!-- Values Section -->
<section class="testimonial">
<div class="container">
<div class="insertr" data-content-id="values-section" data-content-type="rich">
<h2 style="margin-bottom: 2rem;">Our Values</h2>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 2rem; text-align: left;">
<div>
<h3>Client-First</h3>
<p>Every recommendation we make is designed with your specific business context and goals in mind.</p>
</div>
<div>
<h3>Practical Solutions</h3>
<p>We believe in strategies that you can actually implement with your current resources and capabilities.</p>
</div>
<div>
<h3>Long-term Partnership</h3>
<p>We're not just consultants; we're partners in your business success for the long haul.</p>
</div>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="container">
<div class="insertr" data-content-id="footer-info">
<p>&copy; 2024 Acme Consulting Services. All rights reserved.</p>
<p>📧 info@acmeconsulting.com | 📞 (555) 123-4567</p>
</div>
</div>
</footer>
<!-- Insertr JavaScript Library -->
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
<script src="insertr/insertr.js"></script>
</body>
</html>

254
demo-site/assets/style.css Normal file
View File

@@ -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;
}
}

109
demo-site/index.html Normal file
View File

@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Acme Consulting Services</title>
<link rel="stylesheet" href="assets/style.css">
<link rel="stylesheet" href="insertr/insertr.css">
</head>
<body>
<!-- Navigation -->
<nav class="navbar">
<div class="container">
<div class="insertr" data-content-id="nav-logo">
<h1 class="logo">Acme Consulting</h1>
</div>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="about.html">About</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
<!-- Authentication controls -->
<div class="auth-controls">
<button id="auth-toggle" class="btn-secondary">Login as Client</button>
<button id="edit-mode-toggle" class="btn-primary" style="display: none;">Edit Mode: Off</button>
</div>
</div>
</nav>
<!-- Hero Section -->
<section class="hero">
<div class="container">
<div class="insertr" data-content-id="hero-content" data-content-type="rich">
<h1>Transform Your Business with Expert Consulting</h1>
<p class="lead">We help small businesses grow through strategic planning, process optimization, and digital transformation. Our team brings 15+ years of experience to drive your success.</p>
<a href="contact.html" class="btn-primary">Get Started Today</a>
</div>
</div>
</section>
<!-- Services Section -->
<section class="services">
<div class="container">
<div class="insertr" data-content-id="services-title">
<h2>Our Services</h2>
<p class="section-subtitle">Comprehensive solutions tailored to your business needs</p>
</div>
<div class="services-grid">
<div class="service-card">
<div class="insertr" data-content-id="service-strategy" data-content-type="rich">
<h3>Strategic Planning</h3>
<p>Develop clear roadmaps and actionable strategies that align with your business goals and drive sustainable growth.</p>
</div>
</div>
<div class="service-card">
<div class="insertr" data-content-id="service-operations" data-content-type="rich">
<h3>Operations Optimization</h3>
<p>Streamline processes, reduce costs, and improve efficiency through proven methodologies and best practices.</p>
</div>
</div>
<div class="service-card">
<div class="insertr" data-content-id="service-digital" data-content-type="rich">
<h3>Digital Transformation</h3>
<p>Modernize your technology stack and digital presence to compete effectively in today's marketplace.</p>
</div>
</div>
</div>
</div>
</section>
<!-- Testimonial Section -->
<section class="testimonial">
<div class="container">
<div class="insertr" data-content-id="testimonial-content" data-content-type="rich">
<blockquote>
<p>"Acme Consulting transformed our operations completely. We saw a 40% increase in efficiency within 6 months of implementing their recommendations."</p>
<cite>Sarah Johnson, CEO of TechStart Inc.</cite>
</blockquote>
</div>
</div>
</section>
<!-- Call to Action -->
<section class="cta">
<div class="container">
<div class="insertr" data-content-id="cta-content">
<h2>Ready to Transform Your Business?</h2>
<p>Contact us today for a free consultation and discover how we can help you achieve your goals.</p>
<a href="contact.html" class="btn-primary">Schedule Consultation</a>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="container">
<div class="insertr" data-content-id="footer-info">
<p>&copy; 2024 Acme Consulting Services. All rights reserved.</p>
<p>📧 info@acmeconsulting.com | 📞 (555) 123-4567</p>
</div>
</div>
</footer>
<!-- Insertr JavaScript Library -->
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
<script src="insertr/insertr.js"></script>
</body>
</html>

View File

@@ -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;
}

View File

@@ -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 = `
<div class="insertr-form-group">
<label class="insertr-form-label">Content (Markdown)</label>
<textarea class="insertr-form-textarea" name="content" placeholder="Enter content in Markdown format...">${this.htmlToMarkdown(currentContent)}</textarea>
</div>
`;
} else {
formHTML = `
<div class="insertr-form-group">
<label class="insertr-form-label">Content</label>
<input type="text" class="insertr-form-input" name="content" value="${currentContent.replace(/"/g, '&quot;')}" placeholder="Enter content...">
</div>
`;
}
formHTML += `
<div class="insertr-form-actions">
<button type="button" class="insertr-btn-cancel">Cancel</button>
<button type="button" class="insertr-btn-save">Save</button>
</div>
`;
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 + '<button class="insertr-edit-btn">✏️</button>';
} 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, '<h1>$1</h1>')
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
.replace(/\*\*(.*?)\*\*/gim, '<strong>$1</strong>')
.replace(/\*(.*?)\*/gim, '<em>$1</em>')
.replace(/^\- (.*$)/gim, '<li>$1</li>')
.replace(/\n\n/gim, '</p><p>')
.replace(/^(?!<[h|l|p])(.+)$/gim, '<p>$1</p>')
.replace(/(<li>.*<\/li>)/gims, '<ul>$1</ul>');
}
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;
}

View File

@@ -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."
}
]
}