/** * 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() !== ''; } }