grapesjs-tailwind-iconify
Version:
Enhanced fork of grapesjs-tailwind with Custom Components Manager and Iconify integration
269 lines (230 loc) • 6.92 kB
JavaScript
class CustomComponentManager {
constructor(editor, options = {}) {
this.editor = editor;
this.options = {
defaultCategory: 'Custom',
cdnEndpoints: [],
localComponents: [],
jsonSources: [],
...options
};
this.loadedComponents = new Map();
this.init();
}
init() {
this.loadAllComponents();
}
/**
* Load components from multiple sources
*/
async loadAllComponents() {
if (this.options.localComponents.length > 0) {
this.loadLocalComponents(this.options.localComponents);
}
if (this.options.jsonSources.length > 0) {
await this.loadJSONComponents(this.options.jsonSources);
}
if (this.options.cdnEndpoints.length > 0) {
await this.loadCDNComponents(this.options.cdnEndpoints);
}
}
/**
* Load components from local JS objects
* @param {Array} components - Array of component objects
*/
loadLocalComponents(components) {
components.forEach(component => {
this.registerComponent(component);
});
}
/**
* Load components from JSON sources
* @param {Array} jsonSources - Array of JSON URLs or objects
*/
async loadJSONComponents(jsonSources) {
for (const source of jsonSources) {
try {
let data;
if (typeof source === 'string') {
const response = await fetch(source);
data = await response.json();
} else {
data = source;
}
const components = Array.isArray(data) ? data : [data];
components.forEach(component => {
this.registerComponent(component);
});
} catch (error) {
console.error(`Failed to load JSON components from ${source}:`, error);
}
}
}
/**
* Load components from CDN endpoints
* @param {Array} cdnEndpoints - Array of CDN URLs
*/
async loadCDNComponents(cdnEndpoints) {
for (const endpoint of cdnEndpoints) {
try {
await this.loadScript(endpoint);
if (window.customTailwindComponents) {
const components = window.customTailwindComponents;
components.forEach(component => {
this.registerComponent(component);
});
delete window.customTailwindComponents;
}
} catch (error) {
console.error(`Failed to load CDN components from ${endpoint}:`, error);
}
}
}
/**
* Load external script
* @param {string} src - Script source URL
*/
loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
/**
* Register a single component to GrapesJS
* @param {Object} component - Component definition
*/
registerComponent(component) {
try {
if (!this.validateComponent(component)) {
console.warn('Invalid component structure:', component);
return;
}
if (!component.id) {
component.id = this.generateComponentId(component.label || 'custom-component');
}
if (!component.category) {
component.category = this.options.defaultCategory;
}
this.editor.BlockManager.add(component.id, {
label: component.label || component.id,
content: component.content || component.html || '',
category: component.category,
attributes: {
class: component.class || 'custom-block',
title: component.description || component.label || component.id
},
media: component.media || component.icon || component.preview || this.generateSVGIcon(component.label)
});
this.loadedComponents.set(component.id, component);
console.log(`✅ Registered component: ${component.id}`);
} catch (error) {
console.error('Failed to register component:', component, error);
}
}
/**
* Validate component structure
* @param {Object} component - Component to validate
*/
validateComponent(component) {
return component &&
(component.content || component.html) &&
(component.label || component.id);
}
/**
* Generate unique component ID
* @param {string} baseName - Base name for ID
*/
generateComponentId(baseName) {
const sanitized = baseName.toLowerCase()
.replace(/[^a-z0-9]/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
let counter = 1;
let id = `custom-${sanitized}`;
while (this.loadedComponents.has(id)) {
id = `custom-${sanitized}-${counter}`;
counter++;
}
return id;
}
/**
* Generate default SVG icon for component
* @param {string} text - Text to display in icon
*/
generateSVGIcon(text = '□') {
const firstChar = text.charAt(0).toUpperCase();
return `
<svg width="100%" height="100%" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="2" width="20" height="20" rx="4" fill="#f3f4f6" stroke="#d1d5db" stroke-width="1"/>
<text x="12" y="16" text-anchor="middle" fill="#6b7280" font-size="10" font-family="Arial, sans-serif">
${firstChar}
</text>
</svg>
`;
}
/**
* Add single component dynamically
* @param {Object} component - Component definition
*/
addComponent(component) {
this.registerComponent(component);
}
/**
* Add multiple components dynamically
* @param {Array} components - Array of component definitions
*/
addComponents(components) {
components.forEach(component => {
this.registerComponent(component);
});
}
/**
* Remove component
* @param {string} componentId - Component ID to remove
*/
removeComponent(componentId) {
this.editor.BlockManager.remove(componentId);
this.loadedComponents.delete(componentId);
console.log(`🗑️ Removed component: ${componentId}`);
}
/**
* Get loaded component by ID
* @param {string} componentId - Component ID
*/
getComponent(componentId) {
return this.loadedComponents.get(componentId);
}
/**
* Get all loaded components
*/
getAllComponents() {
return Array.from(this.loadedComponents.values());
}
/**
* Load components from remote JSON URL
* @param {string} url - JSON URL
*/
async loadFromURL(url) {
try {
const response = await fetch(url);
const data = await response.json();
const components = Array.isArray(data) ? data : [data];
this.addComponents(components);
return components.length;
} catch (error) {
console.error(`Failed to load components from URL ${url}:`, error);
throw error;
}
}
/**
* Export current components as JSON
*/
exportComponents() {
return JSON.stringify(this.getAllComponents(), null, 2);
}
}
export default CustomComponentManager;