/**
* EditorJS block tool for embedding web components with props.
*
* Saved data format:
* {
* "name": "site-greeting",
* "props": { "name": "visitor", "theme": "dark" }
* }
*
* Renders in HTML as:
*/
export default class ComponentTool {
static get toolbox() {
return {
title: 'Component',
icon: '',
};
}
constructor({ data }) {
this.data = {
name: data.name || '',
props: data.props || {},
};
this.wrapper = null;
}
render() {
this.wrapper = document.createElement('div');
this.wrapper.classList.add('component-tool');
this._renderUI();
return this.wrapper;
}
_renderUI() {
this.wrapper.innerHTML = '';
// Component name input
const nameLabel = document.createElement('label');
nameLabel.textContent = 'Component tag name';
nameLabel.style.cssText = 'display:block;font-size:12px;color:#888;margin-bottom:2px;';
const nameInput = document.createElement('input');
nameInput.type = 'text';
nameInput.placeholder = 'e.g. site-greeting';
nameInput.value = this.data.name;
nameInput.style.cssText = 'width:100%;padding:6px 8px;border:1px solid #ccc;border-radius:4px;font-family:monospace;margin-bottom:8px;';
nameInput.addEventListener('input', () => { this.data.name = nameInput.value; });
// Props section
const propsLabel = document.createElement('label');
propsLabel.textContent = 'Props';
propsLabel.style.cssText = 'display:block;font-size:12px;color:#888;margin-bottom:2px;';
const propsContainer = document.createElement('div');
propsContainer.classList.add('props-container');
const addBtn = document.createElement('button');
addBtn.type = 'button';
addBtn.textContent = '+ Add prop';
addBtn.style.cssText = 'padding:4px 10px;font-size:12px;border:1px solid #ccc;border-radius:4px;background:#f5f5f5;cursor:pointer;margin-top:4px;';
addBtn.addEventListener('click', () => {
this.data.props[''] = '';
this._renderProps(propsContainer);
});
this.wrapper.append(nameLabel, nameInput, propsLabel, propsContainer, addBtn);
this._renderProps(propsContainer);
}
_renderProps(container) {
container.innerHTML = '';
const entries = Object.entries(this.data.props);
entries.forEach(([key, value], i) => {
const row = document.createElement('div');
row.style.cssText = 'display:flex;gap:4px;margin-bottom:4px;';
const kInput = document.createElement('input');
kInput.type = 'text';
kInput.placeholder = 'key';
kInput.value = key;
kInput.style.cssText = 'flex:1;padding:4px 6px;border:1px solid #ccc;border-radius:4px;font-family:monospace;';
const vInput = document.createElement('input');
vInput.type = 'text';
vInput.placeholder = 'value';
vInput.value = value;
vInput.style.cssText = 'flex:2;padding:4px 6px;border:1px solid #ccc;border-radius:4px;';
const removeBtn = document.createElement('button');
removeBtn.type = 'button';
removeBtn.textContent = '×';
removeBtn.style.cssText = 'padding:4px 8px;border:1px solid #ccc;border-radius:4px;background:#f5f5f5;cursor:pointer;';
// Rebuild props object on change
const updateProps = () => {
const newProps = {};
container.querySelectorAll('div').forEach((r) => {
const inputs = r.querySelectorAll('input');
if (inputs[0] && inputs[0].value) {
newProps[inputs[0].value] = inputs[1] ? inputs[1].value : '';
}
});
this.data.props = newProps;
};
kInput.addEventListener('input', updateProps);
vInput.addEventListener('input', updateProps);
removeBtn.addEventListener('click', () => {
row.remove();
updateProps();
});
row.append(kInput, vInput, removeBtn);
container.append(row);
});
}
save() {
// Clean out empty-key props
const cleaned = {};
for (const [k, v] of Object.entries(this.data.props)) {
if (k.trim()) cleaned[k.trim()] = v;
}
return {
name: this.data.name.trim(),
props: cleaned,
};
}
validate(savedData) {
return savedData.name.trim() !== '';
}
}