Files
insertr/demo-site/archive/insertr-old/content-manager.js
Joakim 9bc2751ff2 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
2025-09-03 12:18:43 +02:00

268 lines
8.4 KiB
JavaScript

/**
* 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(/&nbsp;/g, ' ');
markdown = markdown.replace(/&amp;/g, '&');
markdown = markdown.replace(/&lt;/g, '<');
markdown = markdown.replace(/&gt;/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;
}