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:
175
demo-site/archive/insertr-old/config.js
Normal file
175
demo-site/archive/insertr-old/config.js
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Insertr Configuration System
|
||||
* Extensible field type detection and form configuration
|
||||
*/
|
||||
|
||||
class InsertrConfig {
|
||||
constructor(customConfig = {}) {
|
||||
// Default field type mappings
|
||||
this.defaultFieldTypes = {
|
||||
'H1': { type: 'text', label: 'Headline', maxLength: 60, placeholder: 'Enter headline...' },
|
||||
'H2': { type: 'text', label: 'Subheading', maxLength: 80, placeholder: 'Enter subheading...' },
|
||||
'H3': { type: 'text', label: 'Section Title', maxLength: 100, placeholder: 'Enter title...' },
|
||||
'H4': { type: 'text', label: 'Title', maxLength: 100, placeholder: 'Enter title...' },
|
||||
'H5': { type: 'text', label: 'Title', maxLength: 100, placeholder: 'Enter title...' },
|
||||
'H6': { type: 'text', label: 'Title', maxLength: 100, placeholder: 'Enter title...' },
|
||||
'P': { type: 'textarea', label: 'Paragraph', rows: 3, placeholder: 'Enter paragraph text...' },
|
||||
'A': { type: 'link', label: 'Link', placeholder: 'Enter link text...' },
|
||||
'SPAN': { type: 'text', label: 'Text', placeholder: 'Enter text...' },
|
||||
'CITE': { type: 'text', label: 'Citation', placeholder: 'Enter citation...' },
|
||||
'BUTTON': { type: 'text', label: 'Button Text', placeholder: 'Enter button text...' }
|
||||
};
|
||||
|
||||
// CSS class-based enhancements
|
||||
this.classEnhancements = {
|
||||
'lead': {
|
||||
label: 'Lead Paragraph',
|
||||
rows: 4,
|
||||
placeholder: 'Enter lead paragraph...'
|
||||
},
|
||||
'btn-primary': {
|
||||
type: 'link',
|
||||
label: 'Primary Button',
|
||||
includeUrl: true,
|
||||
placeholder: 'Enter button text...'
|
||||
},
|
||||
'btn-secondary': {
|
||||
type: 'link',
|
||||
label: 'Secondary Button',
|
||||
includeUrl: true,
|
||||
placeholder: 'Enter button text...'
|
||||
},
|
||||
'section-subtitle': {
|
||||
label: 'Section Subtitle',
|
||||
placeholder: 'Enter subtitle...'
|
||||
}
|
||||
};
|
||||
|
||||
// Content ID-based enhancements
|
||||
this.contentIdRules = [
|
||||
{
|
||||
pattern: /cta/i,
|
||||
config: { label: 'Call to Action' }
|
||||
},
|
||||
{
|
||||
pattern: /quote/i,
|
||||
config: {
|
||||
type: 'textarea',
|
||||
rows: 3,
|
||||
label: 'Quote',
|
||||
placeholder: 'Enter quote...'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// Validation limits
|
||||
this.limits = {
|
||||
maxContentLength: 10000,
|
||||
maxHtmlTags: 20,
|
||||
...customConfig.limits
|
||||
};
|
||||
|
||||
// Markdown configuration
|
||||
this.markdown = {
|
||||
enabled: true,
|
||||
label: 'Content (Markdown)',
|
||||
rows: 8,
|
||||
placeholder: 'Enter content in Markdown format...\n\nUse **bold**, *italic*, [links](url), and double line breaks for new paragraphs.',
|
||||
...customConfig.markdown
|
||||
};
|
||||
|
||||
// Merge custom configurations
|
||||
this.fieldTypes = { ...this.defaultFieldTypes, ...customConfig.fieldTypes };
|
||||
this.classEnhancements = { ...this.classEnhancements, ...customConfig.classEnhancements };
|
||||
|
||||
if (customConfig.contentIdRules) {
|
||||
this.contentIdRules = [...this.contentIdRules, ...customConfig.contentIdRules];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate field configuration for an element
|
||||
* @param {HTMLElement} element - The element to configure
|
||||
* @returns {Object} Field configuration
|
||||
*/
|
||||
generateFieldConfig(element) {
|
||||
// Check for explicit markdown type first
|
||||
const fieldType = element.getAttribute('data-field-type');
|
||||
if (fieldType === 'markdown') {
|
||||
return { type: 'markdown', ...this.markdown };
|
||||
}
|
||||
|
||||
// Start with tag-based configuration
|
||||
const tagName = element.tagName;
|
||||
let config = { ...this.fieldTypes[tagName] } || {
|
||||
type: 'text',
|
||||
label: 'Content',
|
||||
placeholder: 'Enter content...'
|
||||
};
|
||||
|
||||
// Apply class-based enhancements
|
||||
for (const [className, enhancement] of Object.entries(this.classEnhancements)) {
|
||||
if (element.classList.contains(className)) {
|
||||
config = { ...config, ...enhancement };
|
||||
}
|
||||
}
|
||||
|
||||
// Apply content ID-based rules
|
||||
const contentId = element.getAttribute('data-content-id');
|
||||
if (contentId) {
|
||||
for (const rule of this.contentIdRules) {
|
||||
if (rule.pattern.test(contentId)) {
|
||||
config = { ...config, ...rule.config };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or override field type mapping
|
||||
* @param {string} tagName - HTML tag name
|
||||
* @param {Object} config - Field configuration
|
||||
*/
|
||||
addFieldType(tagName, config) {
|
||||
this.fieldTypes[tagName.toUpperCase()] = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add class-based enhancement
|
||||
* @param {string} className - CSS class name
|
||||
* @param {Object} enhancement - Configuration enhancement
|
||||
*/
|
||||
addClassEnhancement(className, enhancement) {
|
||||
this.classEnhancements[className] = enhancement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add content ID rule
|
||||
* @param {RegExp|string} pattern - Pattern to match content IDs
|
||||
* @param {Object} config - Configuration to apply
|
||||
*/
|
||||
addContentIdRule(pattern, config) {
|
||||
const regexPattern = pattern instanceof RegExp ? pattern : new RegExp(pattern, 'i');
|
||||
this.contentIdRules.push({ pattern: regexPattern, config });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation limits
|
||||
* @returns {Object} Validation limits
|
||||
*/
|
||||
getValidationLimits() {
|
||||
return { ...this.limits };
|
||||
}
|
||||
}
|
||||
|
||||
// Export for module usage
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = InsertrConfig;
|
||||
}
|
||||
|
||||
// Global export for browser usage
|
||||
if (typeof window !== 'undefined') {
|
||||
window.InsertrConfig = InsertrConfig;
|
||||
}
|
||||
268
demo-site/archive/insertr-old/content-manager.js
Normal file
268
demo-site/archive/insertr-old/content-manager.js
Normal file
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* Insertr Content Manager Module
|
||||
* Handles content operations, storage, and API interactions
|
||||
*/
|
||||
|
||||
class InsertrContentManager {
|
||||
constructor(options = {}) {
|
||||
this.options = {
|
||||
apiEndpoint: options.apiEndpoint || '/api/content',
|
||||
storageKey: options.storageKey || 'insertr_content',
|
||||
...options
|
||||
};
|
||||
|
||||
this.contentCache = new Map();
|
||||
this.loadContentFromStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load content from localStorage
|
||||
*/
|
||||
loadContentFromStorage() {
|
||||
try {
|
||||
const stored = localStorage.getItem(this.options.storageKey);
|
||||
if (stored) {
|
||||
const data = JSON.parse(stored);
|
||||
this.contentCache = new Map(Object.entries(data));
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to load content from storage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save content to localStorage
|
||||
*/
|
||||
saveContentToStorage() {
|
||||
try {
|
||||
const data = Object.fromEntries(this.contentCache);
|
||||
localStorage.setItem(this.options.storageKey, JSON.stringify(data));
|
||||
} catch (error) {
|
||||
console.warn('Failed to save content to storage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content for a specific element
|
||||
* @param {string} contentId - Content identifier
|
||||
* @returns {string|Object} Content data
|
||||
*/
|
||||
getContent(contentId) {
|
||||
return this.contentCache.get(contentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content for an element
|
||||
* @param {string} contentId - Content identifier
|
||||
* @param {string|Object} content - Content data
|
||||
*/
|
||||
setContent(contentId, content) {
|
||||
this.contentCache.set(contentId, content);
|
||||
this.saveContentToStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply content to DOM element
|
||||
* @param {HTMLElement} element - Element to update
|
||||
* @param {string|Object} content - Content to apply
|
||||
*/
|
||||
applyContentToElement(element, content) {
|
||||
const config = element._insertrConfig;
|
||||
|
||||
if (config.type === 'markdown') {
|
||||
// Handle markdown collection - content is a string
|
||||
this.applyMarkdownContent(element, content);
|
||||
} else if (config.type === 'link' && config.includeUrl && content.url !== undefined) {
|
||||
// Update link text and URL
|
||||
element.textContent = this.sanitizeForDisplay(content.text, 'text') || element.textContent;
|
||||
if (content.url) {
|
||||
element.href = this.sanitizeForDisplay(content.url, 'url');
|
||||
}
|
||||
} else if (content.text !== undefined) {
|
||||
// Update text content
|
||||
element.textContent = this.sanitizeForDisplay(content.text, 'text');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply markdown content to element
|
||||
* @param {HTMLElement} element - Element to update
|
||||
* @param {string} markdownText - Markdown content
|
||||
*/
|
||||
applyMarkdownContent(element, markdownText) {
|
||||
// This method will be implemented by the main Insertr class
|
||||
// which has access to the markdown processor
|
||||
if (window.insertr && window.insertr.renderMarkdown) {
|
||||
window.insertr.renderMarkdown(element, markdownText);
|
||||
} else {
|
||||
console.warn('Markdown processor not available');
|
||||
element.textContent = markdownText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract content from DOM element
|
||||
* @param {HTMLElement} element - Element to extract from
|
||||
* @returns {string|Object} Extracted content
|
||||
*/
|
||||
extractContentFromElement(element) {
|
||||
const config = element._insertrConfig;
|
||||
|
||||
if (config.type === 'markdown') {
|
||||
// For markdown collections, return cached content or extract text
|
||||
const contentId = element.getAttribute('data-content-id');
|
||||
const cached = this.contentCache.get(contentId);
|
||||
|
||||
if (cached) {
|
||||
// Handle both old format (object) and new format (string)
|
||||
if (typeof cached === 'string') {
|
||||
return cached;
|
||||
} else if (cached.text && typeof cached.text === 'string') {
|
||||
return cached.text;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: extract basic text content
|
||||
const clone = element.cloneNode(true);
|
||||
const editBtn = clone.querySelector('.insertr-edit-btn');
|
||||
if (editBtn) editBtn.remove();
|
||||
|
||||
// Convert basic HTML structure to markdown
|
||||
return this.basicHtmlToMarkdown(clone.innerHTML);
|
||||
}
|
||||
|
||||
// Clone element to avoid modifying original
|
||||
const clone = element.cloneNode(true);
|
||||
|
||||
// Remove edit button from clone
|
||||
const editBtn = clone.querySelector('.insertr-edit-btn');
|
||||
if (editBtn) editBtn.remove();
|
||||
|
||||
// Extract content based on element type
|
||||
if (config.type === 'link' && config.includeUrl) {
|
||||
return {
|
||||
text: clone.textContent.trim(),
|
||||
url: element.href || ''
|
||||
};
|
||||
}
|
||||
|
||||
return clone.textContent.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert basic HTML to markdown (for initial content extraction)
|
||||
* @param {string} html - HTML content
|
||||
* @returns {string} Markdown content
|
||||
*/
|
||||
basicHtmlToMarkdown(html) {
|
||||
let markdown = html;
|
||||
|
||||
// Basic paragraph conversion
|
||||
markdown = markdown.replace(/<p[^>]*>(.*?)<\/p>/gis, (match, content) => {
|
||||
return content.trim() + '\n\n';
|
||||
});
|
||||
markdown = markdown.replace(/<br\s*\/?>/gi, '\n');
|
||||
|
||||
// Remove HTML tags
|
||||
markdown = markdown.replace(/<[^>]*>/g, '');
|
||||
|
||||
// Clean up entities
|
||||
markdown = markdown.replace(/ /g, ' ');
|
||||
markdown = markdown.replace(/&/g, '&');
|
||||
markdown = markdown.replace(/</g, '<');
|
||||
markdown = markdown.replace(/>/g, '>');
|
||||
|
||||
// Clean whitespace
|
||||
markdown = markdown
|
||||
.split('\n')
|
||||
.map(line => line.trim())
|
||||
.join('\n')
|
||||
.replace(/\n\n\n+/g, '\n\n')
|
||||
.trim();
|
||||
|
||||
return markdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save content to server (mock implementation)
|
||||
* @param {string} contentId - Content identifier
|
||||
* @param {string|Object} content - Content to save
|
||||
* @returns {Promise} Save operation promise
|
||||
*/
|
||||
async saveToServer(contentId, content) {
|
||||
// Mock API call - replace with real implementation
|
||||
try {
|
||||
console.log(`💾 Saving content for ${contentId}:`, content);
|
||||
|
||||
// Simulate network delay
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// Store locally for now
|
||||
this.setContent(contentId, content);
|
||||
|
||||
return { success: true, contentId, content };
|
||||
} catch (error) {
|
||||
console.error('Failed to save content:', error);
|
||||
throw new Error('Save operation failed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic sanitization for display
|
||||
* @param {string} content - Content to sanitize
|
||||
* @param {string} type - Content type
|
||||
* @returns {string} Sanitized content
|
||||
*/
|
||||
sanitizeForDisplay(content, type) {
|
||||
if (!content) return '';
|
||||
|
||||
switch (type) {
|
||||
case 'text':
|
||||
return this.escapeHtml(content);
|
||||
case 'url':
|
||||
if (content.startsWith('javascript:') || content.startsWith('data:')) {
|
||||
return '';
|
||||
}
|
||||
return content;
|
||||
default:
|
||||
return this.escapeHtml(content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape HTML characters
|
||||
* @param {string} text - Text to escape
|
||||
* @returns {string} Escaped text
|
||||
*/
|
||||
escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cached content
|
||||
*/
|
||||
clearCache() {
|
||||
this.contentCache.clear();
|
||||
localStorage.removeItem(this.options.storageKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all cached content
|
||||
* @returns {Object} All cached content
|
||||
*/
|
||||
getAllContent() {
|
||||
return Object.fromEntries(this.contentCache);
|
||||
}
|
||||
}
|
||||
|
||||
// Export for module usage
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = InsertrContentManager;
|
||||
}
|
||||
|
||||
// Global export for browser usage
|
||||
if (typeof window !== 'undefined') {
|
||||
window.InsertrContentManager = InsertrContentManager;
|
||||
}
|
||||
305
demo-site/archive/insertr-old/form-renderer.js
Normal file
305
demo-site/archive/insertr-old/form-renderer.js
Normal file
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* Insertr Form Renderer Module
|
||||
* Handles form creation and UI interactions
|
||||
*/
|
||||
|
||||
class InsertrFormRenderer {
|
||||
constructor(validation) {
|
||||
this.validation = validation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create edit form for a content element
|
||||
* @param {string} contentId - Content identifier
|
||||
* @param {Object} config - Field configuration
|
||||
* @param {string|Object} currentContent - Current content value
|
||||
* @returns {HTMLElement} Form element
|
||||
*/
|
||||
createEditForm(contentId, config, currentContent) {
|
||||
const form = document.createElement('div');
|
||||
form.className = 'insertr-edit-form';
|
||||
|
||||
let formHTML = `<div class="insertr-form-header">${config.label}</div>`;
|
||||
|
||||
if (config.type === 'markdown') {
|
||||
// Markdown collection editing
|
||||
formHTML += `
|
||||
<div class="insertr-form-group">
|
||||
<textarea class="insertr-form-textarea insertr-markdown-editor" name="content"
|
||||
rows="${config.rows || 8}"
|
||||
placeholder="${config.placeholder}">${this.validation.escapeHtml(currentContent)}</textarea>
|
||||
<div class="insertr-form-help">
|
||||
Live preview will appear here when you start typing
|
||||
</div>
|
||||
<div class="insertr-markdown-preview" style="display: none;">
|
||||
<div class="insertr-preview-label">Preview:</div>
|
||||
<div class="insertr-preview-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else if (config.type === 'link' && config.includeUrl) {
|
||||
// Link with URL field
|
||||
const linkText = typeof currentContent === 'object' ? currentContent.text || '' : currentContent;
|
||||
const linkUrl = typeof currentContent === 'object' ? currentContent.url || '' : '';
|
||||
|
||||
formHTML += `
|
||||
<div class="insertr-form-group">
|
||||
<label>Link Text:</label>
|
||||
<input type="text" class="insertr-form-input" name="text"
|
||||
value="${this.validation.escapeHtml(linkText)}"
|
||||
placeholder="${config.placeholder}"
|
||||
maxlength="${config.maxLength || 200}">
|
||||
</div>
|
||||
<div class="insertr-form-group">
|
||||
<label>Link URL:</label>
|
||||
<input type="url" class="insertr-form-input" name="url"
|
||||
value="${this.validation.escapeHtml(linkUrl)}"
|
||||
placeholder="https://example.com">
|
||||
</div>
|
||||
`;
|
||||
} else if (config.type === 'textarea') {
|
||||
// Textarea for longer content
|
||||
const content = typeof currentContent === 'object' ? currentContent.text || '' : currentContent;
|
||||
formHTML += `
|
||||
<div class="insertr-form-group">
|
||||
<textarea class="insertr-form-textarea" name="content"
|
||||
rows="${config.rows || 3}"
|
||||
placeholder="${config.placeholder}"
|
||||
maxlength="${config.maxLength || 1000}">${this.validation.escapeHtml(content)}</textarea>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
// Regular text input
|
||||
const content = typeof currentContent === 'object' ? currentContent.text || '' : currentContent;
|
||||
formHTML += `
|
||||
<div class="insertr-form-group">
|
||||
<input type="text" class="insertr-form-input" name="content"
|
||||
value="${this.validation.escapeHtml(content)}"
|
||||
placeholder="${config.placeholder}"
|
||||
maxlength="${config.maxLength || 200}">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Form buttons
|
||||
formHTML += `
|
||||
<div class="insertr-form-actions">
|
||||
<button type="button" class="insertr-btn-save">Save</button>
|
||||
<button type="button" class="insertr-btn-cancel">Cancel</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
form.innerHTML = formHTML;
|
||||
|
||||
// Setup form validation
|
||||
this.setupFormValidation(form, config);
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup real-time validation for form inputs
|
||||
* @param {HTMLElement} form - Form element
|
||||
* @param {Object} config - Field configuration
|
||||
*/
|
||||
setupFormValidation(form, config) {
|
||||
const inputs = form.querySelectorAll('input, textarea');
|
||||
|
||||
inputs.forEach(input => {
|
||||
// Real-time validation on input
|
||||
input.addEventListener('input', () => {
|
||||
this.validateFormField(input, config);
|
||||
});
|
||||
|
||||
// Also validate on blur for better UX
|
||||
input.addEventListener('blur', () => {
|
||||
this.validateFormField(input, config);
|
||||
});
|
||||
});
|
||||
|
||||
// Setup markdown preview if applicable
|
||||
if (config.type === 'markdown') {
|
||||
this.setupMarkdownPreview(form);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate individual form field
|
||||
* @param {HTMLElement} input - Input element to validate
|
||||
* @param {Object} config - Field configuration
|
||||
*/
|
||||
validateFormField(input, config) {
|
||||
const value = input.value.trim();
|
||||
let fieldType = config.type;
|
||||
|
||||
// Determine validation type based on input
|
||||
if (input.type === 'url' || input.name === 'url') {
|
||||
fieldType = 'link';
|
||||
}
|
||||
|
||||
const validation = this.validation.validateInput(value, fieldType);
|
||||
|
||||
// Visual feedback
|
||||
input.classList.toggle('error', !validation.valid);
|
||||
input.classList.toggle('valid', validation.valid && value.length > 0);
|
||||
|
||||
// Show/hide validation message
|
||||
if (!validation.valid) {
|
||||
this.validation.showValidationMessage(input, validation.message, true);
|
||||
} else {
|
||||
this.validation.showValidationMessage(input, '', false);
|
||||
}
|
||||
|
||||
return validation.valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup live markdown preview
|
||||
* @param {HTMLElement} form - Form containing markdown textarea
|
||||
*/
|
||||
setupMarkdownPreview(form) {
|
||||
const textarea = form.querySelector('.insertr-markdown-editor');
|
||||
const preview = form.querySelector('.insertr-markdown-preview');
|
||||
const previewContent = form.querySelector('.insertr-preview-content');
|
||||
|
||||
if (!textarea || !preview || !previewContent) return;
|
||||
|
||||
let previewTimeout;
|
||||
|
||||
textarea.addEventListener('input', () => {
|
||||
const content = textarea.value.trim();
|
||||
|
||||
if (content) {
|
||||
preview.style.display = 'block';
|
||||
|
||||
// Debounced preview update
|
||||
clearTimeout(previewTimeout);
|
||||
previewTimeout = setTimeout(() => {
|
||||
this.updateMarkdownPreview(previewContent, content);
|
||||
}, 300);
|
||||
} else {
|
||||
preview.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update markdown preview content
|
||||
* @param {HTMLElement} previewElement - Preview container
|
||||
* @param {string} markdown - Markdown content
|
||||
*/
|
||||
updateMarkdownPreview(previewElement, markdown) {
|
||||
// This method will be called by the main Insertr class
|
||||
// which has access to the markdown processor
|
||||
if (window.insertr && window.insertr.updateMarkdownPreview) {
|
||||
window.insertr.updateMarkdownPreview(previewElement, markdown);
|
||||
} else {
|
||||
previewElement.innerHTML = '<p><em>Preview unavailable</em></p>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Position edit form relative to element
|
||||
* @param {HTMLElement} element - Element being edited
|
||||
* @param {HTMLElement} overlay - Form overlay
|
||||
*/
|
||||
positionEditForm(element, overlay) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const form = overlay.querySelector('.insertr-edit-form');
|
||||
|
||||
// Calculate optimal width (responsive)
|
||||
const viewportWidth = window.innerWidth;
|
||||
let formWidth;
|
||||
|
||||
if (viewportWidth < 768) {
|
||||
formWidth = Math.min(viewportWidth - 40, 350);
|
||||
} else {
|
||||
formWidth = Math.min(Math.max(rect.width, 300), 500);
|
||||
}
|
||||
|
||||
form.style.width = `${formWidth}px`;
|
||||
|
||||
// Position below element with some spacing
|
||||
const top = rect.bottom + window.scrollY + 10;
|
||||
const left = Math.max(20, rect.left + window.scrollX);
|
||||
|
||||
overlay.style.position = 'absolute';
|
||||
overlay.style.top = `${top}px`;
|
||||
overlay.style.left = `${left}px`;
|
||||
overlay.style.zIndex = '10000';
|
||||
}
|
||||
|
||||
/**
|
||||
* Show edit form
|
||||
* @param {HTMLElement} element - Element being edited
|
||||
* @param {HTMLElement} form - Form to show
|
||||
*/
|
||||
showEditForm(element, form) {
|
||||
// Create overlay
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'insertr-form-overlay';
|
||||
overlay.appendChild(form);
|
||||
|
||||
// Position and show
|
||||
document.body.appendChild(overlay);
|
||||
this.positionEditForm(element, overlay);
|
||||
|
||||
// Focus first input
|
||||
const firstInput = form.querySelector('input, textarea');
|
||||
if (firstInput) {
|
||||
setTimeout(() => firstInput.focus(), 100);
|
||||
}
|
||||
|
||||
// Handle clicking outside to close
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) {
|
||||
this.hideEditForm(overlay);
|
||||
}
|
||||
});
|
||||
|
||||
return overlay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide edit form
|
||||
* @param {HTMLElement} overlay - Form overlay to hide
|
||||
*/
|
||||
hideEditForm(overlay) {
|
||||
if (overlay && overlay.parentNode) {
|
||||
overlay.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract form data
|
||||
* @param {HTMLElement} form - Form to extract data from
|
||||
* @param {Object} config - Field configuration
|
||||
* @returns {Object|string} Extracted form data
|
||||
*/
|
||||
extractFormData(form, config) {
|
||||
if (config.type === 'markdown') {
|
||||
const textarea = form.querySelector('[name="content"]');
|
||||
return textarea ? textarea.value.trim() : '';
|
||||
} else if (config.type === 'link' && config.includeUrl) {
|
||||
const textInput = form.querySelector('[name="text"]');
|
||||
const urlInput = form.querySelector('[name="url"]');
|
||||
return {
|
||||
text: textInput ? textInput.value.trim() : '',
|
||||
url: urlInput ? urlInput.value.trim() : ''
|
||||
};
|
||||
} else {
|
||||
const input = form.querySelector('[name="content"]');
|
||||
return input ? input.value.trim() : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export for module usage
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = InsertrFormRenderer;
|
||||
}
|
||||
|
||||
// Global export for browser usage
|
||||
if (typeof window !== 'undefined') {
|
||||
window.InsertrFormRenderer = InsertrFormRenderer;
|
||||
}
|
||||
268
demo-site/archive/insertr-old/insertr.css
Normal file
268
demo-site/archive/insertr-old/insertr.css
Normal file
@@ -0,0 +1,268 @@
|
||||
/* 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 overlay container */
|
||||
.insertr-edit-overlay {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* Edit form container */
|
||||
.insertr-edit-form {
|
||||
background: white;
|
||||
border: 2px solid #3b82f6;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Form header */
|
||||
.insertr-form-header {
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
font-size: 0.875rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
.insertr-markdown-editor {
|
||||
min-height: 200px;
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* Validation messages */
|
||||
.insertr-validation-message {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.875rem;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.insertr-validation-message.error {
|
||||
background-color: #fef2f2;
|
||||
border: 1px solid #fecaca;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.insertr-validation-message.success {
|
||||
background-color: #f0fdf4;
|
||||
border: 1px solid #bbf7d0;
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
409
demo-site/archive/insertr-old/insertr.js
Normal file
409
demo-site/archive/insertr-old/insertr.js
Normal 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();
|
||||
});
|
||||
194
demo-site/archive/insertr-old/markdown-processor.js
Normal file
194
demo-site/archive/insertr-old/markdown-processor.js
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* Insertr Markdown Processor Module
|
||||
* Handles markdown parsing and rendering with sanitization
|
||||
*/
|
||||
|
||||
class InsertrMarkdownProcessor {
|
||||
constructor() {
|
||||
this.marked = null;
|
||||
this.markedParser = null;
|
||||
this.DOMPurify = null;
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize markdown processor
|
||||
*/
|
||||
initialize() {
|
||||
// Check if marked is available
|
||||
if (typeof marked === 'undefined' && typeof window.marked === 'undefined') {
|
||||
console.warn('Marked.js not loaded! Markdown collections will not work.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the marked object
|
||||
this.marked = window.marked || marked;
|
||||
this.markedParser = this.marked.marked || this.marked.parse || this.marked;
|
||||
|
||||
if (typeof this.markedParser !== 'function') {
|
||||
console.warn('Cannot find marked parse function');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configure marked for basic use
|
||||
if (this.marked.use) {
|
||||
this.marked.use({
|
||||
breaks: true,
|
||||
gfm: true
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize DOMPurify if available
|
||||
if (typeof DOMPurify !== 'undefined' || typeof window.DOMPurify !== 'undefined') {
|
||||
this.DOMPurify = window.DOMPurify || DOMPurify;
|
||||
}
|
||||
|
||||
console.log('✅ Markdown processor initialized');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if markdown processor is ready
|
||||
* @returns {boolean} Ready status
|
||||
*/
|
||||
isReady() {
|
||||
return this.markedParser !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render markdown to HTML
|
||||
* @param {string} markdownText - Markdown content
|
||||
* @returns {string} Rendered HTML
|
||||
*/
|
||||
renderToHtml(markdownText) {
|
||||
if (!this.markedParser) {
|
||||
console.error('Markdown parser not available');
|
||||
return markdownText;
|
||||
}
|
||||
|
||||
if (typeof markdownText !== 'string') {
|
||||
console.error('Expected markdown string, got:', typeof markdownText, markdownText);
|
||||
return 'Markdown content error';
|
||||
}
|
||||
|
||||
try {
|
||||
// Convert markdown to HTML
|
||||
const html = this.markedParser(markdownText);
|
||||
|
||||
if (typeof html !== 'string') {
|
||||
console.error('Markdown parser returned non-string:', typeof html, html);
|
||||
return 'Markdown parsing error';
|
||||
}
|
||||
|
||||
// Sanitize HTML if DOMPurify is available
|
||||
if (this.DOMPurify) {
|
||||
return this.DOMPurify.sanitize(html, {
|
||||
ALLOWED_TAGS: ['p', 'strong', 'em', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'br'],
|
||||
ALLOWED_ATTR: ['href', 'class'],
|
||||
ALLOWED_SCHEMES: ['http', 'https', 'mailto']
|
||||
});
|
||||
}
|
||||
|
||||
return html;
|
||||
} catch (error) {
|
||||
console.error('Markdown rendering failed:', error);
|
||||
return markdownText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply markdown content to DOM element
|
||||
* @param {HTMLElement} element - Element to update
|
||||
* @param {string} markdownText - Markdown content
|
||||
*/
|
||||
applyToElement(element, markdownText) {
|
||||
const html = this.renderToHtml(markdownText);
|
||||
element.innerHTML = html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create markdown preview
|
||||
* @param {string} markdownText - Markdown content
|
||||
* @returns {string} HTML preview
|
||||
*/
|
||||
createPreview(markdownText) {
|
||||
return this.renderToHtml(markdownText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate markdown content
|
||||
* @param {string} markdownText - Markdown to validate
|
||||
* @returns {Object} Validation result
|
||||
*/
|
||||
validateMarkdown(markdownText) {
|
||||
try {
|
||||
const html = this.renderToHtml(markdownText);
|
||||
return {
|
||||
valid: true,
|
||||
html,
|
||||
warnings: this.getMarkdownWarnings(markdownText)
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
valid: false,
|
||||
message: 'Invalid markdown format',
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get warnings for markdown content
|
||||
* @param {string} markdownText - Markdown to check
|
||||
* @returns {Array} Array of warnings
|
||||
*/
|
||||
getMarkdownWarnings(markdownText) {
|
||||
const warnings = [];
|
||||
|
||||
// Check for potentially problematic patterns
|
||||
if (markdownText.includes('<script')) {
|
||||
warnings.push('Script tags detected in markdown');
|
||||
}
|
||||
|
||||
if (markdownText.includes('javascript:')) {
|
||||
warnings.push('JavaScript URLs detected in markdown');
|
||||
}
|
||||
|
||||
// Check for excessive nesting
|
||||
const headerCount = (markdownText.match(/^#+\s/gm) || []).length;
|
||||
if (headerCount > 10) {
|
||||
warnings.push('Many headers detected - consider simplifying structure');
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip markdown formatting to plain text
|
||||
* @param {string} markdownText - Markdown content
|
||||
* @returns {string} Plain text
|
||||
*/
|
||||
toPlainText(markdownText) {
|
||||
return markdownText
|
||||
.replace(/#{1,6}\s+/g, '') // Remove headers
|
||||
.replace(/\*\*(.*?)\*\*/g, '$1') // Remove bold
|
||||
.replace(/\*(.*?)\*/g, '$1') // Remove italic
|
||||
.replace(/\[(.*?)\]\(.*?\)/g, '$1') // Remove links, keep text
|
||||
.replace(/`(.*?)`/g, '$1') // Remove code
|
||||
.replace(/^\s*[-*+]\s+/gm, '') // Remove list markers
|
||||
.replace(/^\s*\d+\.\s+/gm, '') // Remove numbered list markers
|
||||
.replace(/\n\n+/g, '\n\n') // Normalize whitespace
|
||||
.trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Export for module usage
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = InsertrMarkdownProcessor;
|
||||
}
|
||||
|
||||
// Global export for browser usage
|
||||
if (typeof window !== 'undefined') {
|
||||
window.InsertrMarkdownProcessor = InsertrMarkdownProcessor;
|
||||
}
|
||||
194
demo-site/archive/insertr-old/validation.js
Normal file
194
demo-site/archive/insertr-old/validation.js
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* Insertr Validation Module
|
||||
* Client-side validation for user experience (not security)
|
||||
*/
|
||||
|
||||
class InsertrValidation {
|
||||
constructor(config, domPurify = null) {
|
||||
this.config = config;
|
||||
this.DOMPurify = domPurify;
|
||||
this.limits = config.getValidationLimits();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate input based on field type
|
||||
* @param {string} input - Input to validate
|
||||
* @param {string} fieldType - Type of field
|
||||
* @returns {Object} Validation result
|
||||
*/
|
||||
validateInput(input, fieldType) {
|
||||
if (!input || typeof input !== 'string') {
|
||||
return { valid: false, message: 'Content cannot be empty' };
|
||||
}
|
||||
|
||||
// Basic length validation
|
||||
if (input.length > this.limits.maxContentLength) {
|
||||
return {
|
||||
valid: false,
|
||||
message: `Content is too long (max ${this.limits.maxContentLength.toLocaleString()} characters)`
|
||||
};
|
||||
}
|
||||
|
||||
// Field-specific validation
|
||||
switch (fieldType) {
|
||||
case 'text':
|
||||
return this.validateTextInput(input);
|
||||
case 'textarea':
|
||||
return this.validateTextInput(input);
|
||||
case 'link':
|
||||
return this.validateLinkInput(input);
|
||||
case 'markdown':
|
||||
return this.validateMarkdownInput(input);
|
||||
default:
|
||||
return { valid: true };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate plain text input
|
||||
* @param {string} input - Text to validate
|
||||
* @returns {Object} Validation result
|
||||
*/
|
||||
validateTextInput(input) {
|
||||
// Check for obvious HTML that users might accidentally include
|
||||
if (input.includes('<script>') || input.includes('</script>')) {
|
||||
return {
|
||||
valid: false,
|
||||
message: 'Script tags are not allowed for security reasons'
|
||||
};
|
||||
}
|
||||
|
||||
if (input.includes('<') && input.includes('>')) {
|
||||
return {
|
||||
valid: false,
|
||||
message: 'HTML tags are not allowed in text fields. Use markdown collections for formatted content.'
|
||||
};
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate link/URL input
|
||||
* @param {string} input - URL to validate
|
||||
* @returns {Object} Validation result
|
||||
*/
|
||||
validateLinkInput(input) {
|
||||
// Basic URL validation for user feedback
|
||||
const urlPattern = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
|
||||
if (input.startsWith('http') && !urlPattern.test(input)) {
|
||||
return {
|
||||
valid: false,
|
||||
message: 'Please enter a valid URL (e.g., https://example.com)'
|
||||
};
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate markdown input
|
||||
* @param {string} input - Markdown to validate
|
||||
* @returns {Object} Validation result
|
||||
*/
|
||||
validateMarkdownInput(input) {
|
||||
// Check for potentially problematic content
|
||||
if (input.includes('<script>') || input.includes('javascript:')) {
|
||||
return {
|
||||
valid: false,
|
||||
message: 'Script content is not allowed for security reasons'
|
||||
};
|
||||
}
|
||||
|
||||
// Warn about excessive HTML (user might be pasting from Word/etc)
|
||||
const htmlTagCount = (input.match(/<[^>]+>/g) || []).length;
|
||||
if (htmlTagCount > this.limits.maxHtmlTags) {
|
||||
return {
|
||||
valid: false,
|
||||
message: 'Too much HTML detected. Please use markdown formatting instead of HTML tags.'
|
||||
};
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Show validation message to user
|
||||
* @param {HTMLElement} element - Element to show message near
|
||||
* @param {string} message - Message to show
|
||||
* @param {boolean} isError - Whether this is an error message
|
||||
*/
|
||||
showValidationMessage(element, message, isError = true) {
|
||||
// Remove existing message
|
||||
const existingMsg = element.parentNode.querySelector('.insertr-validation-message');
|
||||
if (existingMsg) {
|
||||
existingMsg.remove();
|
||||
}
|
||||
|
||||
if (!message) return;
|
||||
|
||||
// Create new message
|
||||
const msgElement = document.createElement('div');
|
||||
msgElement.className = `insertr-validation-message ${isError ? 'error' : 'success'}`;
|
||||
msgElement.textContent = message;
|
||||
|
||||
// Insert after the element
|
||||
element.parentNode.insertBefore(msgElement, element.nextSibling);
|
||||
|
||||
// Auto-remove after delay
|
||||
if (!isError) {
|
||||
setTimeout(() => {
|
||||
if (msgElement.parentNode) {
|
||||
msgElement.remove();
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize content for display (basic client-side sanitization)
|
||||
* @param {string} content - Content to sanitize
|
||||
* @param {string} type - Content type
|
||||
* @returns {string} Sanitized content
|
||||
*/
|
||||
sanitizeForDisplay(content, type) {
|
||||
if (!content) return '';
|
||||
|
||||
switch (type) {
|
||||
case 'text':
|
||||
return this.escapeHtml(content);
|
||||
case 'url':
|
||||
// Basic URL sanitization
|
||||
if (content.startsWith('javascript:') || content.startsWith('data:')) {
|
||||
return '';
|
||||
}
|
||||
return content;
|
||||
case 'markdown':
|
||||
// For markdown, we'll let marked.js handle it with our safe config
|
||||
return content;
|
||||
default:
|
||||
return this.escapeHtml(content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape HTML characters
|
||||
* @param {string} text - Text to escape
|
||||
* @returns {string} Escaped text
|
||||
*/
|
||||
escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
// Export for module usage
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = InsertrValidation;
|
||||
}
|
||||
|
||||
// Global export for browser usage
|
||||
if (typeof window !== 'undefined') {
|
||||
window.InsertrValidation = InsertrValidation;
|
||||
}
|
||||
Reference in New Issue
Block a user